Add canvas generation pending placeholders
This commit is contained in:
parent
e21f99eaca
commit
f3869f5e63
@ -1116,6 +1116,12 @@ function getDraftString(node: CloneNode | null | undefined, key: string) {
|
|||||||
return typeof value === "string" ? value : "";
|
return typeof value === "string" ? value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDraftNumber(node: CloneNode | null | undefined, key: string, fallback = 0) {
|
||||||
|
const value = node?.draftValues?.[key];
|
||||||
|
const numericValue = typeof value === "number" ? value : Number(value);
|
||||||
|
return Number.isFinite(numericValue) ? numericValue : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
function getNodeMediaUrl(node: CloneNode | null | undefined) {
|
function getNodeMediaUrl(node: CloneNode | null | undefined) {
|
||||||
const direct = getDraftString(node, "mediaUrl") || getDraftString(node, "thumbnailUrl") || getDraftString(node, "resultImage");
|
const direct = getDraftString(node, "mediaUrl") || getDraftString(node, "thumbnailUrl") || getDraftString(node, "resultImage");
|
||||||
if (direct) return direct;
|
if (direct) return direct;
|
||||||
@ -1846,6 +1852,39 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
if (last) setSelectedNodeId(last.id);
|
if (last) setSelectedNodeId(last.id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = window.setInterval(() => {
|
||||||
|
setNodes((current) => {
|
||||||
|
let changed = false;
|
||||||
|
const nextNodes = current.map((node) => {
|
||||||
|
if (getDraftString(node, "generationStatus") !== "pending") return node;
|
||||||
|
const currentProgress = getDraftNumber(node, "generationProgress", 2);
|
||||||
|
const step = currentProgress < 24 ? 2 : currentProgress < 68 ? 1 : 0.35;
|
||||||
|
const nextProgress = Math.min(96, Number((currentProgress + step).toFixed(1)));
|
||||||
|
if (nextProgress === currentProgress) return node;
|
||||||
|
changed = true;
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
draftValues: {
|
||||||
|
...(node.draftValues || {}),
|
||||||
|
generationProgress: nextProgress,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return changed ? nextNodes : current;
|
||||||
|
});
|
||||||
|
}, 850);
|
||||||
|
|
||||||
|
return () => window.clearInterval(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const cancelGenerationNode = useCallback((nodeId: string) => {
|
||||||
|
setNodes((current) => current.filter((node) => node.id !== nodeId));
|
||||||
|
setEdges((current) => current.filter((edge) => edge.source !== nodeId && edge.target !== nodeId));
|
||||||
|
setSelectedNodeId((current) => (current === nodeId ? null : current));
|
||||||
|
toast.success("已取消生成");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const addWorkflowPreset = useCallback(
|
const addWorkflowPreset = useCallback(
|
||||||
(workflow: WorkflowPreset, index = 0) => {
|
(workflow: WorkflowPreset, index = 0) => {
|
||||||
const title = String(workflow.name || workflow.title || "工具箱预设");
|
const title = String(workflow.name || workflow.title || "工具箱预设");
|
||||||
@ -2489,7 +2528,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
if (action === "grid") {
|
if (action === "grid") {
|
||||||
const { columns, rows } = normalizeGridOption(option || "9宫格");
|
const { columns, rows } = normalizeGridOption(option || "9宫格");
|
||||||
const gridDefinition = getConfiguredFunction(videoCanvasFunctionConfig, "multi-view-nine-grid");
|
const gridDefinition = getConfiguredFunction(videoCanvasFunctionConfig, "multi-view-nine-grid");
|
||||||
let gridNode = createNineGridCompositeNode({
|
const gridNode = createNineGridCompositeNode({
|
||||||
anchor,
|
anchor,
|
||||||
columns,
|
columns,
|
||||||
option,
|
option,
|
||||||
@ -2498,6 +2537,30 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
x: rightX,
|
x: rightX,
|
||||||
y: centerY(347),
|
y: centerY(347),
|
||||||
});
|
});
|
||||||
|
const shouldGenerate = videoCanvasFunctionConfig.enabled && gridDefinition?.enabled;
|
||||||
|
const pendingGridNode: CloneNode = {
|
||||||
|
...gridNode,
|
||||||
|
draftValues: {
|
||||||
|
...(gridNode.draftValues || {}),
|
||||||
|
generated: !shouldGenerate,
|
||||||
|
generationProgress: shouldGenerate ? 2 : 100,
|
||||||
|
generationStatus: shouldGenerate ? "pending" : "complete",
|
||||||
|
mediaUrl: shouldGenerate ? "" : getDraftString(gridNode, "mediaUrl"),
|
||||||
|
pendingLabel: "生成中",
|
||||||
|
sourceNodeId: anchor.id,
|
||||||
|
sourceUrl,
|
||||||
|
url: shouldGenerate ? [] : gridNode.draftValues?.url,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setNodes((current) => [...current, pendingGridNode]);
|
||||||
|
setEdges((current) => [...current, { id: makeId("edge"), source: anchor.id, target: pendingGridNode.id }]);
|
||||||
|
setSelectedNodeId(pendingGridNode.id);
|
||||||
|
setCanvasCommand(null);
|
||||||
|
setNodeContextMenu(null);
|
||||||
|
setConnectionMenu(null);
|
||||||
|
setActivePanel(null);
|
||||||
|
|
||||||
if (videoCanvasFunctionConfig.enabled && gridDefinition?.enabled) {
|
if (videoCanvasFunctionConfig.enabled && gridDefinition?.enabled) {
|
||||||
const prompt = composeFunctionPrompt(gridDefinition, option);
|
const prompt = composeFunctionPrompt(gridDefinition, option);
|
||||||
const response = await imageGenerationService.generateImage({
|
const response = await imageGenerationService.generateImage({
|
||||||
@ -2509,30 +2572,46 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
});
|
});
|
||||||
const generatedUrl = response.success ? getFirstGeneratedImageUrl(response) : "";
|
const generatedUrl = response.success ? getFirstGeneratedImageUrl(response) : "";
|
||||||
if (generatedUrl) {
|
if (generatedUrl) {
|
||||||
gridNode = {
|
setNodes((current) =>
|
||||||
...gridNode,
|
current.map((node) =>
|
||||||
draftValues: {
|
node.id === pendingGridNode.id && getDraftString(node, "generationStatus") === "pending"
|
||||||
...(gridNode.draftValues || {}),
|
? {
|
||||||
generated: true,
|
...node,
|
||||||
mediaUrl: generatedUrl,
|
draftValues: {
|
||||||
prompt,
|
...(node.draftValues || {}),
|
||||||
sourceNodeId: anchor.id,
|
generated: true,
|
||||||
sourceUrl,
|
generationProgress: 100,
|
||||||
url: [generatedUrl],
|
generationStatus: "complete",
|
||||||
},
|
mediaUrl: generatedUrl,
|
||||||
};
|
prompt,
|
||||||
} else if (!response.success) {
|
sourceNodeId: anchor.id,
|
||||||
toast.error(response.error || "九宫格生成失败,已保留可切分占位节点");
|
sourceUrl,
|
||||||
|
url: [generatedUrl],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const errorMessage = response.error || "九宫格生成未返回结果";
|
||||||
|
setNodes((current) =>
|
||||||
|
current.map((node) =>
|
||||||
|
node.id === pendingGridNode.id && getDraftString(node, "generationStatus") === "pending"
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
draftValues: {
|
||||||
|
...(node.draftValues || {}),
|
||||||
|
generationStatus: "failed",
|
||||||
|
generationError: errorMessage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
toast.error(`${errorMessage},已保留可切分占位节点`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNodes((current) => [...current, gridNode]);
|
toast.success(shouldGenerate ? "已创建生成中占位节点" : "已按预设提示词生成多机位九宫格结果图");
|
||||||
setEdges((current) => [...current, { id: makeId("edge"), source: anchor.id, target: gridNode.id }]);
|
|
||||||
setSelectedNodeId(gridNode.id);
|
|
||||||
setCanvasCommand(null);
|
|
||||||
setNodeContextMenu(null);
|
|
||||||
setConnectionMenu(null);
|
|
||||||
setActivePanel(null);
|
|
||||||
toast.success("已按预设提示词生成多机位九宫格结果图");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2554,6 +2633,45 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
const prompt = composeFunctionPrompt(definition, option?.trim() || title);
|
const prompt = composeFunctionPrompt(definition, option?.trim() || title);
|
||||||
|
|
||||||
if (videoCanvasFunctionConfig.enabled && definition?.enabled) {
|
if (videoCanvasFunctionConfig.enabled && definition?.enabled) {
|
||||||
|
const pendingResultNode = createMediaResourceNode({
|
||||||
|
action: "IMAGE_RESOURCE",
|
||||||
|
height: 280,
|
||||||
|
mediaType: "image",
|
||||||
|
mediaUrl: "",
|
||||||
|
prompt,
|
||||||
|
title: definition.label || title,
|
||||||
|
width: 420,
|
||||||
|
x: rightX,
|
||||||
|
y: centerY(280),
|
||||||
|
});
|
||||||
|
const pendingNode: CloneNode = {
|
||||||
|
...pendingResultNode,
|
||||||
|
draftValues: {
|
||||||
|
...(pendingResultNode.draftValues || {}),
|
||||||
|
generated: false,
|
||||||
|
generationProgress: 2,
|
||||||
|
generationStatus: "pending",
|
||||||
|
params: {
|
||||||
|
mode: action,
|
||||||
|
prompt,
|
||||||
|
sourceNodeId: anchor.id,
|
||||||
|
sourceUrl,
|
||||||
|
},
|
||||||
|
pendingLabel: "生成中",
|
||||||
|
sourceNodeId: anchor.id,
|
||||||
|
sourceUrl,
|
||||||
|
toolbarOption: option || "",
|
||||||
|
url: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setNodes((current) => [...current, pendingNode]);
|
||||||
|
setEdges((current) => [...current, { id: makeId("edge"), source: anchor.id, target: pendingNode.id }]);
|
||||||
|
setSelectedNodeId(pendingNode.id);
|
||||||
|
setCanvasCommand(null);
|
||||||
|
setNodeContextMenu(null);
|
||||||
|
setConnectionMenu(null);
|
||||||
|
setActivePanel(null);
|
||||||
|
|
||||||
let generatedUrl = "";
|
let generatedUrl = "";
|
||||||
if (definition.route === "enhance") {
|
if (definition.route === "enhance") {
|
||||||
const response = await imageGenerationService.enhanceImage(sourceUrl);
|
const response = await imageGenerationService.enhanceImage(sourceUrl);
|
||||||
@ -2580,45 +2698,50 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (generatedUrl) {
|
if (generatedUrl) {
|
||||||
const resultNode = createMediaResourceNode({
|
setNodes((current) =>
|
||||||
action: "IMAGE_RESOURCE",
|
current.map((node) =>
|
||||||
height: 280,
|
node.id === pendingNode.id && getDraftString(node, "generationStatus") === "pending"
|
||||||
mediaType: "image",
|
? {
|
||||||
mediaUrl: generatedUrl,
|
...node,
|
||||||
prompt,
|
draftValues: {
|
||||||
title: definition.label || title,
|
...(node.draftValues || {}),
|
||||||
width: 420,
|
generated: true,
|
||||||
x: rightX,
|
generationProgress: 100,
|
||||||
y: centerY(280),
|
generationStatus: "complete",
|
||||||
});
|
mediaUrl: generatedUrl,
|
||||||
setNodes((current) => [
|
params: {
|
||||||
...current,
|
mode: action,
|
||||||
{
|
prompt,
|
||||||
...resultNode,
|
sourceNodeId: anchor.id,
|
||||||
draftValues: {
|
sourceUrl,
|
||||||
...(resultNode.draftValues || {}),
|
},
|
||||||
generated: true,
|
sourceNodeId: anchor.id,
|
||||||
params: {
|
sourceUrl,
|
||||||
mode: action,
|
toolbarOption: option || "",
|
||||||
prompt,
|
url: [generatedUrl],
|
||||||
sourceNodeId: anchor.id,
|
},
|
||||||
sourceUrl,
|
}
|
||||||
},
|
: node
|
||||||
sourceNodeId: anchor.id,
|
)
|
||||||
sourceUrl,
|
);
|
||||||
toolbarOption: option || "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setEdges((current) => [...current, { id: makeId("edge"), source: anchor.id, target: resultNode.id }]);
|
|
||||||
setSelectedNodeId(resultNode.id);
|
|
||||||
setCanvasCommand(null);
|
|
||||||
setNodeContextMenu(null);
|
|
||||||
setConnectionMenu(null);
|
|
||||||
setActivePanel(null);
|
|
||||||
toast.success(`已生成「${definition.label || title}」结果`);
|
toast.success(`已生成「${definition.label || title}」结果`);
|
||||||
return;
|
} else {
|
||||||
|
setNodes((current) =>
|
||||||
|
current.map((node) =>
|
||||||
|
node.id === pendingNode.id && getDraftString(node, "generationStatus") === "pending"
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
draftValues: {
|
||||||
|
...(node.draftValues || {}),
|
||||||
|
generationStatus: "failed",
|
||||||
|
generationError: `${definition.label || title}处理失败`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: node
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const created = createTargetModuleNode("image", rightX, 0, nodes.length + 1);
|
const created = createTargetModuleNode("image", rightX, 0, nodes.length + 1);
|
||||||
@ -3360,6 +3483,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
|
|||||||
node={node}
|
node={node}
|
||||||
selected={node.id === selectedNodeId}
|
selected={node.id === selectedNodeId}
|
||||||
onContextMenu={(event) => openNodeContext(event, node)}
|
onContextMenu={(event) => openNodeContext(event, node)}
|
||||||
|
onCancelGeneration={() => cancelGenerationNode(node.id)}
|
||||||
onModuleAction={(actionLabel) => runTargetModuleAction(node.id, actionLabel)}
|
onModuleAction={(actionLabel) => runTargetModuleAction(node.id, actionLabel)}
|
||||||
onOpenConnectionMenu={(event, side) => openConnectionMenu(event, node, side)}
|
onOpenConnectionMenu={(event, side) => openConnectionMenu(event, node, side)}
|
||||||
onPointerDown={(event) => startNodeDrag(event, node)}
|
onPointerDown={(event) => startNodeDrag(event, node)}
|
||||||
@ -6342,6 +6466,7 @@ function EdgeFlowSegments({
|
|||||||
|
|
||||||
function CloneCanvasNode({
|
function CloneCanvasNode({
|
||||||
node,
|
node,
|
||||||
|
onCancelGeneration,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
onModuleAction,
|
onModuleAction,
|
||||||
onOpenConnectionMenu,
|
onOpenConnectionMenu,
|
||||||
@ -6350,6 +6475,7 @@ function CloneCanvasNode({
|
|||||||
selected,
|
selected,
|
||||||
}: {
|
}: {
|
||||||
node: CloneNode;
|
node: CloneNode;
|
||||||
|
onCancelGeneration: () => void;
|
||||||
onContextMenu: (event: ReactMouseEvent<HTMLDivElement>) => void;
|
onContextMenu: (event: ReactMouseEvent<HTMLDivElement>) => void;
|
||||||
onModuleAction: (actionLabel: TargetModuleActionLabel) => void;
|
onModuleAction: (actionLabel: TargetModuleActionLabel) => void;
|
||||||
onOpenConnectionMenu: (event: ReactMouseEvent<HTMLButtonElement>, side: ConnectionSide) => void;
|
onOpenConnectionMenu: (event: ReactMouseEvent<HTMLButtonElement>, side: ConnectionSide) => void;
|
||||||
@ -6377,7 +6503,7 @@ function CloneCanvasNode({
|
|||||||
if (node.kind === "media") {
|
if (node.kind === "media") {
|
||||||
return (
|
return (
|
||||||
<CanvasNodeShell node={node} onContextMenu={onContextMenu} onOpenConnectionMenu={onOpenConnectionMenu} onPointerDown={onPointerDown} selected={selected}>
|
<CanvasNodeShell node={node} onContextMenu={onContextMenu} onOpenConnectionMenu={onOpenConnectionMenu} onPointerDown={onPointerDown} selected={selected}>
|
||||||
<PresetMediaCanvasNode node={node} selected={selected} />
|
<PresetMediaCanvasNode node={node} onCancelGeneration={onCancelGeneration} selected={selected} />
|
||||||
</CanvasNodeShell>
|
</CanvasNodeShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -6835,11 +6961,22 @@ function CanvasNodeShell({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PresetMediaCanvasNode({ node, selected }: { node: CloneNode; selected: boolean }) {
|
function PresetMediaCanvasNode({
|
||||||
|
node,
|
||||||
|
onCancelGeneration,
|
||||||
|
selected,
|
||||||
|
}: {
|
||||||
|
node: CloneNode;
|
||||||
|
onCancelGeneration: () => void;
|
||||||
|
selected: boolean;
|
||||||
|
}) {
|
||||||
const mediaType = getDraftString(node, "mediaType") || node.inputType || "image";
|
const mediaType = getDraftString(node, "mediaType") || node.inputType || "image";
|
||||||
const mediaUrl = getNodeMediaUrl(node);
|
const mediaUrl = getNodeMediaUrl(node);
|
||||||
const sourceUrl = getNodeSourceMediaUrl(node);
|
const sourceUrl = getNodeSourceMediaUrl(node);
|
||||||
const generated = Boolean(node.draftValues?.generated);
|
const generated = Boolean(node.draftValues?.generated);
|
||||||
|
const generationStatus = getDraftString(node, "generationStatus");
|
||||||
|
const isGenerating = generationStatus === "pending";
|
||||||
|
const isGenerationFailed = generationStatus === "failed";
|
||||||
const annotationMode = Boolean(node.draftValues?.annotationMode);
|
const annotationMode = Boolean(node.draftValues?.annotationMode);
|
||||||
const rotation = Number(node.draftValues?.rotation || 0);
|
const rotation = Number(node.draftValues?.rotation || 0);
|
||||||
const outputSizeLabel = getDraftString(node, "outputSizeLabel");
|
const outputSizeLabel = getDraftString(node, "outputSizeLabel");
|
||||||
@ -6897,7 +7034,14 @@ function PresetMediaCanvasNode({ node, selected }: { node: CloneNode; selected:
|
|||||||
selected && "outline outline-2 outline-[#8b96ff]"
|
selected && "outline outline-2 outline-[#8b96ff]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isNineGridComposite ? (
|
{isGenerating || isGenerationFailed ? (
|
||||||
|
<GenerationPendingPreview
|
||||||
|
errorMessage={getDraftString(node, "generationError")}
|
||||||
|
onCancel={onCancelGeneration}
|
||||||
|
progress={getDraftNumber(node, "generationProgress", 2)}
|
||||||
|
status={generationStatus}
|
||||||
|
/>
|
||||||
|
) : isNineGridComposite ? (
|
||||||
<NineGridCompositePreview node={node} sourceUrl={sourceUrl || mediaUrl} />
|
<NineGridCompositePreview node={node} sourceUrl={sourceUrl || mediaUrl} />
|
||||||
) : cropTile && mediaUrl && gridCellVisual ? (
|
) : cropTile && mediaUrl && gridCellVisual ? (
|
||||||
<GridCellImage alt={node.title} src={mediaUrl} visual={gridCellVisual} />
|
<GridCellImage alt={node.title} src={mediaUrl} visual={gridCellVisual} />
|
||||||
@ -6921,7 +7065,7 @@ function PresetMediaCanvasNode({ node, selected }: { node: CloneNode; selected:
|
|||||||
<span className="absolute left-[38%] top-[calc(32%-26px)] rounded-[7px] bg-black/68 px-2 py-1 text-[11px] text-white">主体</span>
|
<span className="absolute left-[38%] top-[calc(32%-26px)] rounded-[7px] bg-black/68 px-2 py-1 text-[11px] text-white">主体</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{!generated ? (
|
{!generated && !isGenerating && !isGenerationFailed ? (
|
||||||
<button
|
<button
|
||||||
className="absolute right-3 top-3 flex h-10 w-10 items-center justify-center rounded-[8px] bg-black/56 text-white/90"
|
className="absolute right-3 top-3 flex h-10 w-10 items-center justify-center rounded-[8px] bg-black/56 text-white/90"
|
||||||
onClick={() => toast.info(`${node.title}素材替换接口待接入。`)}
|
onClick={() => toast.info(`${node.title}素材替换接口待接入。`)}
|
||||||
@ -6936,6 +7080,71 @@ function PresetMediaCanvasNode({ node, selected }: { node: CloneNode; selected:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GenerationPendingPreview({
|
||||||
|
errorMessage,
|
||||||
|
onCancel,
|
||||||
|
progress,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
errorMessage?: string;
|
||||||
|
onCancel: () => void;
|
||||||
|
progress: number;
|
||||||
|
status: string;
|
||||||
|
}) {
|
||||||
|
const displayProgress = Math.max(2, Math.min(99, Math.round(progress)));
|
||||||
|
const failed = status === "failed";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center overflow-hidden bg-[#303030]">
|
||||||
|
<style>{`
|
||||||
|
@keyframes libtv-loading-sweep {
|
||||||
|
0% { transform: translateX(0); opacity: 0; }
|
||||||
|
18% { opacity: 1; }
|
||||||
|
100% { transform: translateX(300%); opacity: 0; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
{!failed ? (
|
||||||
|
<div className="absolute inset-0 opacity-45">
|
||||||
|
<span className="absolute left-[-30%] top-0 h-full w-[44%] animate-[libtv-loading-sweep_1.8s_ease-in-out_infinite] bg-gradient-to-r from-transparent via-white/8 to-transparent" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="relative flex h-[34px] min-w-[164px] items-center justify-center gap-3 rounded-[7px] border border-white/14 bg-[#1f1f1f]/86 px-5 text-[14px] font-semibold text-white shadow-[0_10px_28px_rgba(0,0,0,0.34)]">
|
||||||
|
{failed ? (
|
||||||
|
<>
|
||||||
|
<span className="text-white/78">{errorMessage || "生成失败"}</span>
|
||||||
|
<button
|
||||||
|
className="text-white/44 transition hover:text-white/80"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
移除
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span>生成中 {displayProgress}%...</span>
|
||||||
|
<button
|
||||||
|
className="font-medium text-white/42 transition hover:text-white/80"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function PureTextResourceCanvasNode({
|
function PureTextResourceCanvasNode({
|
||||||
node,
|
node,
|
||||||
onTextResourceChange,
|
onTextResourceChange,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user