fix(canvas): 画布资源恢复 - 生成结果下载或 OSS 落盘失败支持原任务重新加载
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode, type RefObject } from "react";
|
||||
import { AlertCircle, BookOpenCheck, Clock3, FileText, Image as ImageIcon, LoaderCircle, Music2, Pencil, Play, RefreshCw, Video } from "lucide-react";
|
||||
import { AlertCircle, BookOpenCheck, Clock3, Download, FileText, Image as ImageIcon, LoaderCircle, Music2, Pencil, Play, RefreshCw, Video } from "lucide-react";
|
||||
|
||||
import { VideoPlayer } from "@/components/video-player";
|
||||
import { CONTENT_MODERATION_ERROR_CODE, generationErrorMessage, isContentModerationError } from "@/lib/generation-error";
|
||||
@@ -43,6 +43,7 @@ type CanvasNodeContentProps = {
|
||||
onStopEditing: () => void;
|
||||
mentionReferences: CanvasResourceReference[];
|
||||
onRetry?: (node: CanvasNodeData) => void;
|
||||
onReloadResource?: (node: CanvasNodeData) => void;
|
||||
onOpenTaskDetails?: (node: CanvasNodeData) => void;
|
||||
onToggleBatch?: () => void;
|
||||
reduceMediaEffects?: boolean;
|
||||
@@ -58,7 +59,7 @@ export function CanvasNodeContent(props: CanvasNodeContentProps) {
|
||||
if (hasCustomContent && props.renderNodeContent) return props.renderNodeContent(props.node);
|
||||
if (props.isBatchRoot) return <ImageNodeContent {...props} />;
|
||||
if (props.node.metadata?.status === "loading") return <LoadingContent node={props.node} theme={props.theme} onOpenTaskDetails={props.onOpenTaskDetails} />;
|
||||
if (props.node.metadata?.status === "error") return <ErrorContent node={props.node} theme={props.theme} onRetry={props.onRetry} />;
|
||||
if (props.node.metadata?.status === "error") return <ErrorContent node={props.node} theme={props.theme} onRetry={props.onRetry} onReloadResource={props.onReloadResource} />;
|
||||
|
||||
const Renderer = nodeContentRenderers[props.node.type];
|
||||
return Renderer ? <Renderer {...props} /> : <UnknownNodeContent theme={props.theme} />;
|
||||
@@ -196,7 +197,7 @@ function shortTaskId(id: string) {
|
||||
return `${id.slice(0, 14)}...${id.slice(-4)}`;
|
||||
}
|
||||
|
||||
function ErrorContent({ node, theme, onRetry }: Pick<CanvasNodeContentProps, "node" | "theme" | "onRetry">) {
|
||||
function ErrorContent({ node, theme, onRetry, onReloadResource }: Pick<CanvasNodeContentProps, "node" | "theme" | "onRetry" | "onReloadResource">) {
|
||||
const moderationFailure = node.metadata?.generationErrorCode === CONTENT_MODERATION_ERROR_CODE || isContentModerationError(node.metadata?.errorDetails);
|
||||
const errorDisplayTask = {
|
||||
provider: node.metadata?.taskProvider,
|
||||
@@ -217,6 +218,29 @@ function ErrorContent({ node, theme, onRetry }: Pick<CanvasNodeContentProps, "no
|
||||
<div className="rounded-[var(--r-sm)] px-3 py-2 text-[var(--fs-label)] leading-4" style={{ background: theme.toolbar.itemHover, color: theme.node.muted }}>
|
||||
修改节点提示词后,可重新点击生成。
|
||||
</div>
|
||||
) : node.metadata?.resourceReloadAvailable ? (
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-8 items-center gap-1.5 rounded-[var(--r-md)] px-3 text-xs font-medium transition-colors"
|
||||
style={{ background: theme.accent.primary, color: theme.accent.onPrimary }}
|
||||
onClick={(event) => { event.stopPropagation(); onReloadResource?.(node); }}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<Download className="size-3.5" />
|
||||
重新加载资源
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-8 items-center gap-1.5 rounded-[var(--r-md)] px-3 text-xs font-medium transition-colors"
|
||||
style={{ background: theme.toolbar.itemHover, color: theme.node.text }}
|
||||
onClick={(event) => { event.stopPropagation(); onRetry?.(node); }}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<RefreshCw className="size-3.5" />
|
||||
重新生成
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
@@ -229,7 +253,7 @@ function ErrorContent({ node, theme, onRetry }: Pick<CanvasNodeContentProps, "no
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<RefreshCw className="size-3.5" />
|
||||
{node.metadata?.isBatchRoot ? "重试失败项" : "重试"}
|
||||
{node.metadata?.isBatchRoot ? "重新生成失败项" : "重新生成"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -335,7 +359,7 @@ function ImageNodeContent(props: CanvasNodeContentProps) {
|
||||
const content = props.node.metadata?.status === "loading"
|
||||
? <LoadingContent node={props.node} theme={props.theme} />
|
||||
: props.node.metadata?.status === "error"
|
||||
? <ErrorContent node={props.node} theme={props.theme} onRetry={props.onRetry} />
|
||||
? <ErrorContent node={props.node} theme={props.theme} onRetry={props.onRetry} onReloadResource={props.onReloadResource} />
|
||||
: <EmptyImageContent {...props} isBatchRoot={false} />;
|
||||
return <BatchFrame batchCount={props.batchCount} batchExpanded={props.batchExpanded} batchOpening={props.batchOpening} batchRecovering={props.batchRecovering} theme={props.theme} onToggleBatch={props.onToggleBatch}>{content}</BatchFrame>;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ type CanvasNodeProps = {
|
||||
onToggleBatch?: (nodeId: string) => void;
|
||||
onSetBatchPrimary?: (node: CanvasNodeData) => void;
|
||||
onRetry?: (node: CanvasNodeData) => void;
|
||||
onReloadResource?: (node: CanvasNodeData) => void;
|
||||
onOpenTaskDetails?: (node: CanvasNodeData) => void;
|
||||
onOpenVersions?: (node: CanvasNodeData) => void;
|
||||
onViewImage?: (node: CanvasNodeData) => void;
|
||||
@@ -92,6 +93,7 @@ export const CanvasNode = React.memo(function CanvasNode({
|
||||
onToggleBatch,
|
||||
onSetBatchPrimary,
|
||||
onRetry,
|
||||
onReloadResource,
|
||||
onOpenTaskDetails,
|
||||
onOpenVersions,
|
||||
onViewImage,
|
||||
@@ -360,6 +362,7 @@ export const CanvasNode = React.memo(function CanvasNode({
|
||||
onContentChange={onContentChange}
|
||||
onStopEditing={() => setIsEditingContent(false)}
|
||||
onRetry={onRetry}
|
||||
onReloadResource={onReloadResource}
|
||||
onOpenTaskDetails={onOpenTaskDetails}
|
||||
onToggleBatch={() => onToggleBatch?.(data.id)}
|
||||
reduceMediaEffects={reduceMediaEffects}
|
||||
@@ -425,7 +428,8 @@ export const CanvasNode = React.memo(function CanvasNode({
|
||||
{hasImageContent ? (
|
||||
<BatchChildActionButton theme={theme} label={batchPrimary ? "当前主图" : "设为主图"} icon={<Star className={`size-3.5 ${batchPrimary ? "fill-current" : ""}`} style={{ color: theme.accent.primary }} />} onClick={() => onSetBatchPrimary?.(data)} />
|
||||
) : null}
|
||||
{data.metadata?.status === "error" ? <BatchChildActionButton theme={theme} label="重试" icon={<RefreshCw className="size-3.5" />} onClick={() => onRetry?.(data)} /> : null}
|
||||
{data.metadata?.status === "error" && data.metadata.resourceReloadAvailable ? <BatchChildActionButton theme={theme} label="重新加载资源" icon={<Download className="size-3.5" />} onClick={() => onReloadResource?.(data)} /> : null}
|
||||
{data.metadata?.status === "error" ? <BatchChildActionButton theme={theme} label="重新生成" icon={<RefreshCw className="size-3.5" />} onClick={() => onRetry?.(data)} /> : null}
|
||||
{data.metadata?.status === "error" ? <BatchChildActionButton theme={theme} label="删除" icon={<Trash2 className="size-3.5" />} danger onClick={() => deleteNode?.(data)} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -495,6 +499,7 @@ function areCanvasNodePropsEqual(previous: CanvasNodeProps, next: CanvasNodeProp
|
||||
previous.onToggleBatch === next.onToggleBatch &&
|
||||
previous.onSetBatchPrimary === next.onSetBatchPrimary &&
|
||||
previous.onRetry === next.onRetry &&
|
||||
previous.onReloadResource === next.onReloadResource &&
|
||||
previous.onOpenTaskDetails === next.onOpenTaskDetails &&
|
||||
previous.onOpenVersions === next.onOpenVersions &&
|
||||
previous.onViewImage === next.onViewImage &&
|
||||
|
||||
Reference in New Issue
Block a user