From 12796b048cbb58b3c79ac5be7762bef29126d173 Mon Sep 17 00:00:00 2001 From: Codex Audit Date: Wed, 23 Sep 2026 11:10:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(canvas):=20=E7=94=BB=E5=B8=83=E5=AA=92?= =?UTF-8?q?=E4=BD=93=20-=20=E4=BF=AE=E5=A4=8D=E9=A2=84=E8=A7=88=E9=A6=96?= =?UTF-8?q?=E5=B8=A7=E9=9F=B3=E9=87=8F=E5=92=8C=E5=88=9B=E4=BD=9C=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/cached-resource-image.tsx | 12 +- .../canvas/canvas-active-task-panel.tsx | 4 +- .../canvas/canvas-creative-interaction.tsx | 39 ++++- .../canvas/canvas-folder-preview.tsx | 8 +- .../components/canvas/canvas-frame-node.tsx | 6 +- .../canvas/canvas-image-preview.tsx | 155 +++++++++++++++-- .../components/canvas/canvas-node-content.tsx | 164 +++++++++++++++--- .../canvas/canvas-node-search-modal.tsx | 9 +- .../canvas/canvas-version-compare-modal.tsx | 5 +- .../canvas/canvas-video-preview-image.tsx | 32 ++++ .../canvas-workspace-node-list-panel.tsx | 17 +- .../canvas-workspace-task-list-item.tsx | 4 +- .../canvas/storyboard-assets-cell.tsx | 9 +- .../layout/user-oss-settings-form.tsx | 4 +- web/src/components/video-player.tsx | 3 +- web/src/lib/canvas/canvas-media-preview.ts | 33 +++- web/src/lib/generation-task-display.ts | 47 ++++- .../admin/settings/storage-settings-page.tsx | 2 +- web/src/pages/assets/index.tsx | 10 +- .../canvas/canvas-project-status-dialogs.tsx | 43 ++++- web/src/pages/canvas/project.tsx | 21 ++- .../pages/canvas/use-canvas-node-editor.ts | 18 +- web/src/pages/projects/detail/assets.tsx | 38 ++-- .../detail/workflow-production-workbench.tsx | 16 +- web/src/pages/tasks/task-grid-card.tsx | 10 +- web/src/pages/tasks/task-list-row.tsx | 10 +- web/src/services/canvas-audio-playback.ts | 4 +- web/src/services/canvas-video-preview.ts | 55 +++--- web/test/canvas-media-performance.test.ts | 59 ++++++- web/test/generation-task-display.test.ts | 34 +++- 30 files changed, 689 insertions(+), 182 deletions(-) create mode 100644 web/src/components/canvas/canvas-video-preview-image.tsx diff --git a/web/src/components/cached-resource-image.tsx b/web/src/components/cached-resource-image.tsx index cb2e845bd..9eab73374 100644 --- a/web/src/components/cached-resource-image.tsx +++ b/web/src/components/cached-resource-image.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState, type ImgHTMLAttributes, type ReactNode } from "react"; -import { resourceFileUrl, resourceIdFromStorageKey } from "@/services/api/resources"; +import { getResourceAccess, resolveResourceAccessURL, resourceIdFromStorageKey } from "@/services/api/resources"; import { resolveImageUrl } from "@/services/image-storage"; type CachedResourceImageProps = Omit, "src"> & { @@ -12,7 +12,7 @@ type CachedResourceImageProps = Omit, "src"> }; /** - * 远程资源图片统一使用稳定的资源文件地址。 + * 远程资源图片统一使用 OSS/CDN 授权地址。 * Blob 缓存仍可用于导出、抽帧等字节处理,但不作为媒体展示 src,避免把 * `blob:http(s)://...` 泄露到节点、素材库和浏览器媒体链路中。 */ @@ -59,7 +59,13 @@ export function CachedResourceImage({ storageKey, src = "", fallback = null, loa cancelled = true; }; } - setCachedSrc(resourceFileUrl(resourceId)); + void getResourceAccess(storageKey, "display") + .then((access) => { + if (!cancelled) setCachedSrc(resolveResourceAccessURL(access.url)); + }) + .catch(() => { + if (!cancelled) setCacheFailed(true); + }); return () => { cancelled = true; }; diff --git a/web/src/components/canvas/canvas-active-task-panel.tsx b/web/src/components/canvas/canvas-active-task-panel.tsx index 4d89d3cb4..a3f05a2b0 100644 --- a/web/src/components/canvas/canvas-active-task-panel.tsx +++ b/web/src/components/canvas/canvas-active-task-panel.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from "react"; import { formatCredits } from "@/constant/credits"; import { aceternityMotion } from "@/lib/aceternity-motion"; -import { formatTaskKind, generationTaskShowsProgress, generationTaskStageLabel, generationTaskStatusLabel } from "@/lib/generation-task-display"; +import { canCancelGenerationTask, formatTaskKind, generationTaskShowsProgress, generationTaskStageLabel, generationTaskStatusLabel } from "@/lib/generation-task-display"; import { canvasThemes } from "@/lib/canvas-theme"; import type { GenerationTask } from "@/services/api/task-center"; import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; @@ -224,7 +224,7 @@ function ActiveTaskCard({ {generationTaskStageLabel(task)} - {onCancelTask && (task.status === "queued" || task.status === "running") ? ( + {onCancelTask && canCancelGenerationTask(task) ? ( : null} /> : null} {item.error &&

这项作品还需要处理,已有结果会保留。

} {item.error &&
查看失败原因

{item.error}

} {props.active && item.status === "failed" && item.failureKind !== "observation" && } -
{url && 查看作品}{props.active && key === "redo" ? invoke(controller.current?.redo(item.ref)) : modify(`请调整《${view.state.proposal?.workflow.nodes.find((entry) => entry.ref === item.ref)?.title || item.ref}》,保留其他作品:`) }}>}
+ {!hasResource && props.active ?
key === "redo" ? invoke(controller.current?.redo(item.ref)) : modify(`请调整《${view.state.proposal?.workflow.nodes.find((entry) => entry.ref === item.ref)?.title || item.ref}》,保留其他作品:`) }}>
: null} ; })} {!props.active && rawError &&
这一步未完成,后续对话中可继续调整

{feedback}

} @@ -183,6 +184,32 @@ export function CanvasCreativeInteraction(props: Props) { ; } +function CreativeAgentMedia({ storageKey, video, alt, actions }: { storageKey: string; video: boolean; alt: string; actions?: ReactNode }) { + const [url, setUrl] = useState(""); + const [failed, setFailed] = useState(false); + + useEffect(() => { + let cancelled = false; + setUrl(""); + setFailed(false); + void resolveMediaUrl(storageKey) + .then((resolved) => { + if (!cancelled) setUrl(resolved); + }) + .catch(() => { + if (!cancelled) setFailed(true); + }); + return () => { + cancelled = true; + }; + }, [storageKey]); + + return <> + {url ? (video ?