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 ?