fix(canvas): 画布媒体 - 修复预览首帧音量和创作状态展示
This commit is contained in:
@@ -1,19 +1,38 @@
|
||||
import { buildLibTVVideoPreviewUrl } from "@/lib/canvas/libtv-import";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
||||
|
||||
export type CanvasVideoPreviewReference = {
|
||||
src: string;
|
||||
storageKey?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the passive preview image reference for a video node.
|
||||
*
|
||||
* `storageKey` is kept separate from the fallback URL so UI callers can
|
||||
* resolve a fresh OSS/CDN display URL instead of mounting a stale historical
|
||||
* `/api/resources/:id/file` URL stored in older canvas data.
|
||||
*/
|
||||
export function canvasNodeVideoPreviewReference(node: CanvasNodeData): CanvasVideoPreviewReference | null {
|
||||
if (node.type !== CanvasNodeType.Video) return null;
|
||||
const content = node.metadata?.content || "";
|
||||
const generatedPreview = node.metadata?.videoPreview;
|
||||
if (generatedPreview?.storageKey || (generatedPreview?.content && generatedPreview.content !== content)) {
|
||||
return { src: generatedPreview.content || "", storageKey: generatedPreview.storageKey };
|
||||
}
|
||||
const explicitPreview = node.metadata?.previewContent || "";
|
||||
if (explicitPreview && explicitPreview !== content) return { src: explicitPreview };
|
||||
const importedPreview = buildLibTVVideoPreviewUrl(content);
|
||||
return importedPreview ? { src: importedPreview } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image source that is safe to mount for a passive video preview.
|
||||
* The original video URL is deliberately never returned: callers must fall
|
||||
* back to a video icon instead of creating a decoder outside the active node.
|
||||
*/
|
||||
export function canvasNodeVideoPreviewUrl(node: CanvasNodeData) {
|
||||
if (node.type !== CanvasNodeType.Video) return "";
|
||||
const content = node.metadata?.content || "";
|
||||
const generatedPreview = node.metadata?.videoPreview?.content || "";
|
||||
if (generatedPreview && generatedPreview !== content) return generatedPreview;
|
||||
const explicitPreview = node.metadata?.previewContent || "";
|
||||
if (explicitPreview && explicitPreview !== content) return explicitPreview;
|
||||
return buildLibTVVideoPreviewUrl(content);
|
||||
return canvasNodeVideoPreviewReference(node)?.src || "";
|
||||
}
|
||||
|
||||
export function canvasVideoAssetPreviewUrl(videoUrl: string, coverUrl?: string) {
|
||||
|
||||
@@ -8,7 +8,27 @@ export const statusLabel: Record<TaskStatus, string> = {
|
||||
cancelled: "已取消",
|
||||
};
|
||||
|
||||
type GenerationTaskDisplayTarget = Pick<GenerationTask, "status" | "stage" | "mediaStage">;
|
||||
type GenerationTaskDisplayTarget = Pick<GenerationTask, "status" | "stage" | "mediaStage" | "progress" | "providerRequestId" | "providerCancelStatus">;
|
||||
|
||||
const cancellablePreSubmissionStages = new Set(["queued", "等待队列调度", "后端接管任务", "正在准备创作"]);
|
||||
|
||||
/**
|
||||
* 只有在第三方请求尚未提交时允许取消。
|
||||
*
|
||||
* 一旦拿到 providerRequestId、进入取消协调状态,或提交结果不确定,
|
||||
* 任务可能已经产生上游费用;所有画布入口必须共用这条规则,避免
|
||||
* 活动面板、详情弹窗和任务列表出现不一致。
|
||||
*/
|
||||
export function canCancelGenerationTask(task: GenerationTaskDisplayTarget) {
|
||||
if (task.status !== "queued" && task.status !== "running") return false;
|
||||
if (task.providerRequestId || task.providerCancelStatus || task.mediaStage) return false;
|
||||
const stage = (task.stage || "").trim().toLowerCase();
|
||||
// Unknown running stages are treated as already submitted. A provider may
|
||||
// return a custom stage name, and showing a cancel button in that state is
|
||||
// more dangerous than conservatively hiding it after billing may begin.
|
||||
if (!stage) return task.status === "queued";
|
||||
return cancellablePreSubmissionStages.has(stage);
|
||||
}
|
||||
|
||||
export function mediaDeliverySummary(status: TaskStatus | undefined, stage: GenerationTask["mediaStage"]) {
|
||||
if (!stage) return "";
|
||||
@@ -31,9 +51,24 @@ export function generationTaskStatusLabel(task: GenerationTaskDisplayTarget) {
|
||||
|
||||
export function generationTaskStageLabel(task: GenerationTaskDisplayTarget) {
|
||||
if (isGenerationTaskSubmissionUncertain(task)) return "为避免重复扣费,未自动重试";
|
||||
if (task.stage === "generating") return "生成中";
|
||||
if (task.stage === "queued") return "排队中";
|
||||
return task.stage || generationTaskStatusLabel(task);
|
||||
switch (task.stage) {
|
||||
case "queued":
|
||||
case "等待队列调度":
|
||||
case "后端接管任务":
|
||||
return "正在准备创作";
|
||||
case "generating":
|
||||
case "调用生成模型":
|
||||
case "正在准备参考素材":
|
||||
case "正在连接上游":
|
||||
case "作品创作中":
|
||||
case "上游生成中":
|
||||
case "后台仍在生成":
|
||||
return "作品创作中";
|
||||
case "等待上游任务同步":
|
||||
return "正在同步创作进度";
|
||||
default:
|
||||
return task.stage || generationTaskStatusLabel(task);
|
||||
}
|
||||
}
|
||||
|
||||
export function generationTaskShowsProgress(task: GenerationTaskDisplayTarget) {
|
||||
@@ -41,8 +76,8 @@ export function generationTaskShowsProgress(task: GenerationTaskDisplayTarget) {
|
||||
if (isGenerationTaskSubmissionUncertain(task)) return false;
|
||||
// 排队、后端接管和连接供应商都没有真实百分比。只有上游状态响应
|
||||
// 已经写回任务后才显示进度,避免所有图片/视频长期停在同一个假数值。
|
||||
if (["等待队列调度", "后端接管任务", "正在连接上游", "调用生成模型"].includes(task.stage || "")) return false;
|
||||
return true;
|
||||
if (["queued", "等待队列调度", "后端接管任务", "正在准备参考素材", "正在连接上游", "调用生成模型", "作品创作中", "submitting", "submitted"].includes(task.stage || "")) return false;
|
||||
return typeof task.progress === "number" && task.progress > 0;
|
||||
}
|
||||
|
||||
export const operationOptions = [
|
||||
|
||||
Reference in New Issue
Block a user