fix(canvas): 媒体提示词 - 统一 Agent 更新与画布生成读取生成合同

This commit is contained in:
ddcat666
2026-10-04 10:43:55 +08:00
parent 853c7d2ce7
commit 6d757b7001
20 changed files with 174 additions and 48 deletions

View File

@@ -22,7 +22,7 @@ import { getContextResourceNodes } from "@/lib/canvas/canvas-resource-references
import { getPublicCanvasShare } from "@/services/api/canvas-share";
import { useCanvasThemeStore, useCanvasThemeScope } from "@/stores/canvas/use-canvas-theme-store";
import { CanvasNodeType, type CanvasNodeData, type Position, type ViewportTransform } from "@/types/canvas";
import { nodeGenerationPrompt } from "@/lib/canvas/generation-contract";
type ContextMenu = { x: number; y: number; world: Position; nodeId?: string };
type DragState = { primaryId: string; nodeIds: string[]; startX: number; startY: number; origins: Map<string, Position>; moved: boolean };
@@ -314,7 +314,7 @@ function SharedConfigNode({ node, onUnauthorized }: { node: CanvasNodeData; onUn
const theme = canvasThemes[useCanvasThemeStore((state) => state.theme)];
return <div className="flex h-full w-full flex-col overflow-hidden rounded-[var(--panel-radius)]">
<div className="flex h-10 shrink-0 items-center gap-2 border-b px-4" style={{ background: theme.node.panel, borderColor: theme.node.stroke }}><ImageIcon className="size-4" /><span className="min-w-0 flex-1 truncate text-sm font-semibold">{node.title}</span></div>
<div className="min-h-0 flex-1 whitespace-pre-wrap break-words p-4 text-sm leading-6" style={{ color: theme.node.muted }}>{node.metadata?.composerContent || node.metadata?.prompt || "未填写提示词"}</div>
<div className="min-h-0 flex-1 whitespace-pre-wrap break-words p-4 text-sm leading-6" style={{ color: theme.node.muted }}>{nodeGenerationPrompt(node) || "未填写提示词"}</div>
<div className="flex h-12 shrink-0 items-center justify-end border-t px-3" style={{ borderColor: theme.node.stroke }}><Button size="small" icon={<Send className="size-3.5" />} onMouseDown={(event) => event.stopPropagation()} onClick={(event) => { event.stopPropagation(); onUnauthorized(); }}>生成</Button></div>
</div>;
}
@@ -325,6 +325,6 @@ function SharedScriptNode({ node, onUnauthorized }: { node: CanvasNodeData; onUn
return <div className="flex h-full w-full flex-col overflow-hidden rounded-[var(--panel-radius)]">
<div className="flex h-10 shrink-0 items-center gap-2 border-b px-4" style={{ background: theme.node.panel, borderColor: theme.node.stroke }}><Clapperboard className="size-4" /><span className="min-w-0 flex-1 truncate text-sm font-semibold">{node.title}</span><span className="text-xs" style={{ color: theme.node.muted }}>{rows.length} 镜</span><button type="button" className="grid size-7 place-items-center rounded hover:bg-black/5 dark:hover:bg-white/10" onMouseDown={(event) => event.stopPropagation()} onClick={(event) => { event.stopPropagation(); onUnauthorized(); }} aria-label="一键创建视频节点"><Video className="size-3.5" /></button></div>
<div data-canvas-wheel-scroll className="min-h-0 flex-1 overflow-y-auto" onWheel={(event) => event.stopPropagation()}>{rows.length ? rows.map((row) => <div key={row.id} className="grid grid-cols-[52px_72px_minmax(180px,1fr)_minmax(150px,.8fr)] border-b text-xs leading-5" style={{ minHeight: 48, borderColor: theme.node.stroke }}><span className="grid place-items-center border-r" style={{ borderColor: theme.node.stroke, color: theme.node.muted }}>#{row.shotNumber}</span><span className="grid place-items-center border-r" style={{ borderColor: theme.node.stroke }}>{row.durationSeconds}s</span><span className="border-r px-3 py-2" style={{ borderColor: theme.node.stroke }}>{row.plotDescription || "-"}</span><span className="px-3 py-2" style={{ color: theme.node.muted }}>{row.dialogue || "-"}</span></div>) : <div className="grid h-full place-items-center text-sm" style={{ color: theme.node.muted }}>暂无分镜</div>}</div>
{node.metadata?.composerContent ? <div className="max-h-24 shrink-0 overflow-y-auto border-t px-3 py-2 text-xs leading-5" style={{ borderColor: theme.node.stroke, color: theme.node.muted }}>{node.metadata.composerContent}</div> : null}
{nodeGenerationPrompt(node) ? <div className="max-h-24 shrink-0 overflow-y-auto border-t px-3 py-2 text-xs leading-5" style={{ borderColor: theme.node.stroke, color: theme.node.muted }}>{nodeGenerationPrompt(node)}</div> : null}
</div>;
}

View File

@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
import { generationBatchStatus, isGenerationCostUncertainError } from "@/lib/canvas/canvas-generation-batch";
import { buildGenerationConfig, createGenerationRetryContext, generationTaskMetadata, resetGenerationTaskMetadata } from "@/lib/canvas/canvas-project-generation";
import { unchangedModeratedPrompt } from "@/lib/generation-error";
import { nodeGenerationPrompt } from "@/lib/canvas/generation-contract";
import { listGenerationTasks } from "@/services/api/task-center";
import { useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
import { useUserStore } from "@/stores/use-user-store";
@@ -207,7 +208,7 @@ export function useCanvasGenerationBatches({ projectId, projectLoaded, nodes, no
updateBatch(batch.sourceNodeId, batch.id, (current) => withUpdatedItem(current, item.id, { status: "failed", errorDetails: "生成模型未配置,请完成配置后重试" }));
continue;
}
const prompt = (node.metadata?.composerContent || node.metadata?.prompt || "").trim();
const prompt = nodeGenerationPrompt(node).trim();
if (!prompt) {
updateBatch(batch.sourceNodeId, batch.id, (current) => withUpdatedItem(current, item.id, { status: "failed", errorDetails: "生成提示词为空" }));
continue;
@@ -242,7 +243,7 @@ export function useCanvasGenerationBatches({ projectId, projectLoaded, nodes, no
const nodeById = new Map(nodesRef.current.map((node) => [node.id, node]));
const blockedItems = failedItems.filter((item) => {
const node = nodeById.get(item.nodeId);
return unchangedModeratedPrompt(node?.metadata, node?.metadata?.composerContent || node?.metadata?.prompt || "");
return unchangedModeratedPrompt(node?.metadata, node ? nodeGenerationPrompt(node) : "");
});
const retryableItems = failedItems.filter((item) => !blockedItems.includes(item));
if (blockedItems.length) message.warning(`${blockedItems.length} 个镜头未通过内容审核,请先修改提示词`);

View File

@@ -2,6 +2,7 @@ import { useCallback, type Dispatch, type SetStateAction } from "react";
import { App } from "antd";
import { buildNodeGenerationContext, hydrateNodeGenerationContext } from "@/components/canvas/canvas-node-generation";
import { nodeGenerationPrompt } from "@/lib/canvas/generation-contract";
import { producedModelCandidateForGeneration } from "@/lib/canvas/produced-model";
import type { CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
import { buildEmotionImageArtifacts, emotionGenerationSize, emotionProviderMask, normalizeEmotionPromptForProvider, resolveEmotionEditPlan } from "@/lib/canvas/canvas-emotion";
@@ -132,7 +133,7 @@ export function useCanvasGenerationRetry({
return;
}
const retryPromptSource = sourceNode.metadata?.composerContent || sourceNode.metadata?.prompt || node.metadata?.prompt || "";
const retryPromptSource = nodeGenerationPrompt(sourceNode) || nodeGenerationPrompt(node);
const retryContextPrompt = retryMode === "image" && sourceNode.metadata?.portraitTexture ? buildPortraitTexturePrompt(retryPromptSource, sourceNode.metadata.portraitTexture) : retryPromptSource;
if (unchangedModeratedPrompt(node.metadata, retryPromptSource)) {
message.warning("该提示词未通过内容审核,请先修改提示词再重新生成");

View File

@@ -8,6 +8,7 @@ import { buildCanvasNodeMentionReferenceMap, buildCanvasResourceReferences, buil
import { buildSkillMentionReferences } from "@/lib/canvas/canvas-skill-mentions";
import { buildCanvasSpatialIndex, canvasNodeBounds, type CanvasSpatialIndex, type CanvasSpatialIndexEntry } from "@/lib/canvas/canvas-spatial-index";
import { canvasOverviewMode, resolveCanvasNodeLOD, type CanvasNodeRenderLOD } from "@/lib/canvas/canvas-node-lod";
import { nodeGenerationPrompt } from "@/lib/canvas/generation-contract";
import { intersectsCanvasBounds, selectCanvasVisibleNodes } from "@/lib/canvas/canvas-node-visibility";
import type { Skill } from "@/services/api/skills";
import type { Asset, ImageAsset } from "@/stores/use-asset-store";
@@ -410,7 +411,7 @@ export function useCanvasRenderModel({
const toolMentionReferencesByNodeId = useMemo(() => {
const map = new Map<string, ReturnType<typeof buildToolMentionReference>[]>();
for (const node of semanticNodes) {
const text = node.metadata?.composerContent ?? node.metadata?.prompt ?? "";
const text = nodeGenerationPrompt(node);
const tokens = parseToolMentionTokens(text);
if (!tokens.length) continue;
const seen = new Set<string>();