This commit is contained in:
@@ -33,6 +33,10 @@ import { communityClient } from "../../api/communityClient";
|
||||
import { modelCapabilitiesClient } from "../../api/modelCapabilitiesClient";
|
||||
import type { CreatePreviewTaskInput } from "../../api/webGenerationGateway";
|
||||
import WorkspacePageShell from "../../components/WorkspacePageShell";
|
||||
import {
|
||||
DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS,
|
||||
DEFAULT_VIDEO_GENERATION_EXPECTED_DURATION_MS,
|
||||
} from "../../hooks/useSmoothedProgress";
|
||||
import type { WebCanvasWorkflow } from "../../types";
|
||||
import type { AssetLibraryCategory } from "../assets/localAssetStore";
|
||||
import {
|
||||
@@ -4492,6 +4496,7 @@ function CanvasPage({
|
||||
progress={imageNodeProgress}
|
||||
status={imageTaskState?.status || "running"}
|
||||
message={imageTaskState?.message || "图片生成中"}
|
||||
expectedDurationMs={DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS}
|
||||
/>
|
||||
) : null}
|
||||
{imageNodeFocusActive && imageFocusSelectionReady ? (
|
||||
@@ -4866,6 +4871,7 @@ function CanvasPage({
|
||||
progress={videoNodeProgress}
|
||||
status={videoTaskState?.status || "running"}
|
||||
message={videoTaskState?.message || "视频生成中"}
|
||||
expectedDurationMs={DEFAULT_VIDEO_GENERATION_EXPECTED_DURATION_MS}
|
||||
/>
|
||||
) : null}
|
||||
{renderConnectorButton({ kind: "video", nodeId: videoNode.id, side: "left", slot: "center" }, "studio-canvas-video-node__connector")}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { useSmoothedProgress } from "../../hooks/useSmoothedProgress";
|
||||
import {
|
||||
DEFAULT_GENERATION_EXPECTED_DURATION_MS,
|
||||
useSmoothedProgress,
|
||||
type ProgressSource,
|
||||
} from "../../hooks/useSmoothedProgress";
|
||||
import { canvasGenerationProgressStyle } from "./canvasUtils";
|
||||
|
||||
type NodeGenStatus = "submitting" | "running" | "success" | "error";
|
||||
@@ -7,10 +11,24 @@ interface CanvasSmoothedProgressRingProps {
|
||||
progress: number;
|
||||
status: NodeGenStatus;
|
||||
message?: string;
|
||||
progressSource?: ProgressSource;
|
||||
startedAt?: number | string | Date | null;
|
||||
expectedDurationMs?: number | null;
|
||||
}
|
||||
|
||||
export function CanvasSmoothedProgressRing({ progress, status, message }: CanvasSmoothedProgressRingProps) {
|
||||
const smoothed = useSmoothedProgress(progress, status);
|
||||
export function CanvasSmoothedProgressRing({
|
||||
progress,
|
||||
status,
|
||||
message,
|
||||
progressSource = "estimated",
|
||||
startedAt,
|
||||
expectedDurationMs = DEFAULT_GENERATION_EXPECTED_DURATION_MS,
|
||||
}: CanvasSmoothedProgressRingProps) {
|
||||
const smoothed = useSmoothedProgress(progress, status, {
|
||||
progressSource,
|
||||
startedAt,
|
||||
expectedDurationMs,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className="studio-canvas-node-generation-progress"
|
||||
@@ -18,7 +36,10 @@ export function CanvasSmoothedProgressRing({ progress, status, message }: Canvas
|
||||
aria-live="polite"
|
||||
style={canvasGenerationProgressStyle(smoothed)}
|
||||
>
|
||||
<span className="studio-canvas-node-generation-progress__ring" aria-hidden="true" />
|
||||
<span
|
||||
className="studio-canvas-node-generation-progress__ring"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<strong>{message}</strong>
|
||||
<em>{smoothed}%</em>
|
||||
</div>
|
||||
|
||||
@@ -262,7 +262,17 @@ export async function waitForImageTaskResult(
|
||||
abortRef,
|
||||
onProgress: (e) => {
|
||||
if (onStatus) {
|
||||
onStatus({ taskId, status: e.status, progress: e.progress, resultUrl: e.resultUrl ?? undefined, error: e.error ?? undefined } as AiTaskStatus);
|
||||
onStatus({
|
||||
taskId,
|
||||
status: e.status,
|
||||
progress: e.progress,
|
||||
progressSource: e.progressSource,
|
||||
stage: e.stage,
|
||||
startedAt: e.startedAt,
|
||||
expectedDurationMs: e.expectedDurationMs,
|
||||
resultUrl: e.resultUrl ?? undefined,
|
||||
error: e.error ?? undefined,
|
||||
} as AiTaskStatus);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -281,7 +291,17 @@ export async function waitForVideoTaskResult(
|
||||
abortRef,
|
||||
onProgress: (e) => {
|
||||
if (onStatus) {
|
||||
onStatus({ taskId, status: e.status, progress: e.progress, resultUrl: e.resultUrl ?? undefined, error: e.error ?? undefined } as AiTaskStatus);
|
||||
onStatus({
|
||||
taskId,
|
||||
status: e.status,
|
||||
progress: e.progress,
|
||||
progressSource: e.progressSource,
|
||||
stage: e.stage,
|
||||
startedAt: e.startedAt,
|
||||
expectedDurationMs: e.expectedDurationMs,
|
||||
resultUrl: e.resultUrl ?? undefined,
|
||||
error: e.error ?? undefined,
|
||||
} as AiTaskStatus);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { useSmoothedProgress } from "../../hooks/useSmoothedProgress";
|
||||
import { useRef } from "react";
|
||||
import {
|
||||
DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS,
|
||||
formatEstimatedRemainingLabel,
|
||||
useSmoothedProgress,
|
||||
} from "../../hooks/useSmoothedProgress";
|
||||
|
||||
interface EcommerceProgressBarProps {
|
||||
status: "idle" | "generating" | "done" | "failed" | string;
|
||||
@@ -12,19 +17,42 @@ function mapStatus(status: string): "running" | "completed" | "failed" {
|
||||
return "running";
|
||||
}
|
||||
|
||||
export function EcommerceProgressBar({ status, label }: EcommerceProgressBarProps) {
|
||||
const progress = mapStatus(status) === "running" ? 50 : 100;
|
||||
const smoothed = useSmoothedProgress(progress, mapStatus(status));
|
||||
export function EcommerceProgressBar({
|
||||
status,
|
||||
label,
|
||||
}: EcommerceProgressBarProps) {
|
||||
const startedAtRef = useRef(Date.now());
|
||||
const mappedStatus = mapStatus(status);
|
||||
const progress = mappedStatus === "running" ? 50 : 100;
|
||||
const smoothed = useSmoothedProgress(progress, mappedStatus, {
|
||||
progressSource: mappedStatus === "running" ? "estimated" : "real",
|
||||
startedAt: startedAtRef.current,
|
||||
expectedDurationMs: DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS,
|
||||
});
|
||||
const remainingLabel =
|
||||
mappedStatus === "running"
|
||||
? formatEstimatedRemainingLabel({
|
||||
nowMs: Date.now(),
|
||||
startedAt: startedAtRef.current,
|
||||
expectedDurationMs: DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS,
|
||||
})
|
||||
: null;
|
||||
|
||||
if (status === "idle") return null;
|
||||
|
||||
return (
|
||||
<div className="ecommerce-progress-bar">
|
||||
<span className="ecommerce-progress-bar__label">{label || "AI 正在生成"}</span>
|
||||
<span className="ecommerce-progress-bar__label">
|
||||
{label || "AI 正在生成"}
|
||||
{remainingLabel ? ` / ${remainingLabel}` : ""}
|
||||
</span>
|
||||
<div className="ecommerce-progress-bar__track">
|
||||
<div className="ecommerce-progress-bar__fill" style={{ width: `${smoothed}%` }} />
|
||||
<div
|
||||
className="ecommerce-progress-bar__fill"
|
||||
style={{ width: `${smoothed}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="ecommerce-progress-bar__value">{smoothed}%</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { useSmoothedProgress } from "../../hooks/useSmoothedProgress";
|
||||
import {
|
||||
DEFAULT_SUPER_RESOLUTION_EXPECTED_DURATION_MS,
|
||||
useSmoothedProgress,
|
||||
type ProgressSource,
|
||||
} from "../../hooks/useSmoothedProgress";
|
||||
|
||||
type MessageStatus = "thinking" | "completed" | "failed" | string;
|
||||
|
||||
@@ -6,6 +10,9 @@ interface SmoothedProgressBarProps {
|
||||
progress: number;
|
||||
status: MessageStatus;
|
||||
label?: string;
|
||||
progressSource?: ProgressSource;
|
||||
startedAt?: number | string | Date | null;
|
||||
expectedDurationMs?: number | null;
|
||||
}
|
||||
|
||||
function mapMessageStatus(status: MessageStatus) {
|
||||
@@ -15,8 +22,19 @@ function mapMessageStatus(status: MessageStatus) {
|
||||
return "running" as const;
|
||||
}
|
||||
|
||||
export function SmoothedProgressBar({ progress, status, label }: SmoothedProgressBarProps) {
|
||||
const smoothed = useSmoothedProgress(progress, mapMessageStatus(status));
|
||||
export function SmoothedProgressBar({
|
||||
progress,
|
||||
status,
|
||||
label,
|
||||
progressSource = "estimated",
|
||||
startedAt,
|
||||
expectedDurationMs = DEFAULT_SUPER_RESOLUTION_EXPECTED_DURATION_MS,
|
||||
}: SmoothedProgressBarProps) {
|
||||
const smoothed = useSmoothedProgress(progress, mapMessageStatus(status), {
|
||||
progressSource,
|
||||
startedAt,
|
||||
expectedDurationMs,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<span>{label || "超分处理中..."}</span>
|
||||
|
||||
@@ -19,7 +19,13 @@ import { assetClient } from "../../../api/assetClient";
|
||||
import { communityClient } from "../../../api/communityClient";
|
||||
import { saveAssetToLocalLibrary } from "../../assets/localAssetStore";
|
||||
import { SmoothedProgressBar } from "../SmoothedProgressBar";
|
||||
import { useSmoothedProgress } from "../../../hooks/useSmoothedProgress";
|
||||
import {
|
||||
DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS,
|
||||
DEFAULT_SUPER_RESOLUTION_EXPECTED_DURATION_MS,
|
||||
DEFAULT_VIDEO_GENERATION_EXPECTED_DURATION_MS,
|
||||
formatEstimatedRemainingLabel,
|
||||
useSmoothedProgress,
|
||||
} from "../../../hooks/useSmoothedProgress";
|
||||
import { renderMarkdownBlocks } from "../markdownRenderer";
|
||||
import { downloadResultAsset } from "../workbenchDownload";
|
||||
import type { WorkbenchChatAttachment, WorkbenchChatMessage, WorkbenchResultActionPayload } from "../workbenchChatTypes";
|
||||
@@ -456,6 +462,8 @@ export const ResultCard = memo(function ResultCard({
|
||||
progress={message.taskProgress ?? 18}
|
||||
status={message.status || "thinking"}
|
||||
label={message.taskStatusLabel || "超分处理中..."}
|
||||
progressSource="estimated"
|
||||
expectedDurationMs={DEFAULT_SUPER_RESOLUTION_EXPECTED_DURATION_MS}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -575,7 +583,23 @@ export const GenerationPendingCard = memo(function GenerationPendingCard({
|
||||
const specs = message.result?.specs || [];
|
||||
const prompt = message.prompt || message.body;
|
||||
const isVideo = message.mode === "video";
|
||||
const smoothed = useSmoothedProgress(message.taskProgress ?? 5, message.status === "thinking" ? "running" : "completed");
|
||||
const expectedDurationMs = isVideo
|
||||
? DEFAULT_VIDEO_GENERATION_EXPECTED_DURATION_MS
|
||||
: DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS;
|
||||
const progressStatus = message.status === "thinking" ? "running" : "completed";
|
||||
const smoothed = useSmoothedProgress(message.taskProgress ?? 5, progressStatus, {
|
||||
progressSource: progressStatus === "running" ? "estimated" : "real",
|
||||
startedAt: message.createdAt,
|
||||
expectedDurationMs,
|
||||
});
|
||||
const remainingLabel = progressStatus === "running"
|
||||
? formatEstimatedRemainingLabel({
|
||||
nowMs: Date.now(),
|
||||
startedAt: message.createdAt,
|
||||
expectedDurationMs,
|
||||
})
|
||||
: null;
|
||||
const statusLabel = message.taskStatusLabel || (isVideo ? "视频生成中" : "图片生成中");
|
||||
|
||||
return (
|
||||
<div className={`ai-generation-pending-card${isVideo ? " is-video" : " is-image"}`}>
|
||||
@@ -590,7 +614,7 @@ export const GenerationPendingCard = memo(function GenerationPendingCard({
|
||||
</div>
|
||||
<div className="ai-generation-pending-card__meta">
|
||||
<div>
|
||||
<strong>{message.taskStatusLabel || "Generating..."}</strong>
|
||||
<strong>{remainingLabel ? `${statusLabel} / ${remainingLabel}` : statusLabel}</strong>
|
||||
<span>{prompt}</span>
|
||||
</div>
|
||||
{specs.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user