From 021f4979702577f577f7b42ce752d1c456fb9742 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 5 Apr 2026 09:56:51 +0800 Subject: [PATCH] Simplify video workflow canvas UI --- app/canvas/[id]/page.tsx | 139 +++--- components/canvas/nodes/WorkflowNode.tsx | 585 +++++++---------------- lib/video-workflow/catalog.ts | 90 +++- 3 files changed, 349 insertions(+), 465 deletions(-) diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index a047d4efc..d29f4e100 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -643,11 +643,11 @@ const AGENT_RESULT_MODE_LABELS: Record = sku_replacement: "SKU 替换", }; const VIDEO_CANVAS_PANEL_CLASS = - "pointer-events-auto overflow-hidden rounded-[28px] border border-slate-200 bg-white text-foreground shadow-[0_20px_50px_-28px_rgba(15,23,42,0.24)] dark:border-zinc-800 dark:bg-zinc-950"; + "pointer-events-auto overflow-hidden rounded-[24px] border border-slate-200 bg-white text-foreground shadow-[0_16px_36px_-24px_rgba(15,23,42,0.2)] dark:border-zinc-800 dark:bg-zinc-950"; const VIDEO_CANVAS_PANEL_BADGE_CLASS = - "border-slate-200 bg-white text-foreground/75 dark:border-zinc-800 dark:bg-zinc-950"; + "border-slate-200 bg-slate-50 text-foreground/70 dark:border-zinc-800 dark:bg-zinc-900"; const VIDEO_CANVAS_SUBSECTION_CLASS = - "rounded-2xl border border-slate-200 bg-white p-3 dark:border-zinc-800 dark:bg-zinc-950"; + "rounded-[18px] border border-slate-200 bg-slate-50 p-3 dark:border-zinc-800 dark:bg-zinc-900"; const VIDEO_CANVAS_TOOLBAR_CLASS = "flex items-center gap-1.5 rounded-[28px] border border-slate-200 bg-white px-2.5 py-1.5 shadow-[0_18px_40px_-24px_rgba(15,23,42,0.24)] dark:border-zinc-800 dark:bg-zinc-950"; const VIDEO_CANVAS_TOOLBAR_BUTTON_CLASS = @@ -1088,6 +1088,7 @@ export default function CanvasEditorPage({ params }: PageProps) { const autoTitleInFlightRef = useRef(null); const autoThumbnailSyncedRef = useRef(null); const lastLoadedCanvasIdRef = useRef(""); + const normalizedVideoNodeSizeIdsRef = useRef>(new Set()); const importVideoCanvasInputRef = useRef(null); const isAgentWorkspaceEnabled = siteSettingsData?.agentEnabled !== false; const isCanvasAgentModeEnabled = siteSettingsData?.canvasAgentModeEnabled !== false; @@ -1112,6 +1113,38 @@ export default function CanvasEditorPage({ params }: PageProps) { [siteSettingsData?.agentWorkflowBrandModelConfig] ); + useEffect(() => { + if (!isVideoMode || nodes.length === 0) { + return; + } + + nodes.forEach((node) => { + if (normalizedVideoNodeSizeIdsRef.current.has(node.id)) { + return; + } + + normalizedVideoNodeSizeIdsRef.current.add(node.id); + const templateType = inferVideoWorkflowTemplateType(node); + const definition = templateType ? getVideoWorkflowNodeDefinition(templateType) : null; + if (!definition) { + return; + } + + const nextWidth = Math.min(node.size.width, definition.size.width); + const nextHeight = Math.min(node.size.height, definition.size.height); + if (nextWidth === node.size.width && nextHeight === node.size.height) { + return; + } + + updateNode(node.id, { + size: { + width: nextWidth, + height: nextHeight, + }, + }); + }); + }, [isVideoMode, nodes, updateNode]); + // 获取选中的图片节点 const selectedImageNode = selectedNodeIds.length === 1 ? nodes.find(n => n.id === selectedNodeIds[0] && n.type === 'image') @@ -1185,10 +1218,10 @@ export default function CanvasEditorPage({ params }: PageProps) { : null; const videoModuleSidebarWidth = isVideoMode ? isVideoModuleSidebarCollapsed - ? 64 - : 296 + ? 56 + : 228 : 0; - const selectedWorkflowPanelLeft = isVideoMode ? videoModuleSidebarWidth + 28 : 16; + const selectedWorkflowPanelLeft = isVideoMode ? videoModuleSidebarWidth + 24 : 16; const handleDeleteSelectedWorkflowNode = useCallback(() => { if (!selectedVideoWorkflowNode) return; deleteNode(selectedVideoWorkflowNode.id); @@ -7538,20 +7571,20 @@ export default function CanvasEditorPage({ params }: PageProps) { style={{ width: `${videoModuleSidebarWidth}px` }} >
-
+
{isVideoModuleSidebarCollapsed ? (
) : ( -
-
视频模块栏
-
- 按工作流层级添加模块 -
+
+ + + +
模块
)} ); @@ -7648,7 +7677,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
) : ( -
+
{VIDEO_CANVAS_SIDEBAR_GROUPS.map((group) => { const GroupIcon = group.icon; return ( @@ -7662,7 +7691,7 @@ export default function CanvasEditorPage({ params }: PageProps) { [group.id]: true, })); }} - className="flex h-10 w-10 items-center justify-center rounded-xl border border-slate-200 bg-white text-xs font-semibold text-slate-500 transition-colors hover:bg-slate-50 hover:text-slate-800 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100" + className="flex h-10 w-10 items-center justify-center rounded-2xl border border-slate-200 bg-white text-xs font-semibold text-slate-500 transition-colors hover:bg-slate-50 hover:text-slate-800 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100" title={group.label} > @@ -7767,25 +7796,25 @@ export default function CanvasEditorPage({ params }: PageProps) { selectedVideoWorkflowDefinition && selectedWorkflowIsActionable ? (
{selectedWorkflowIsActionable ? ( - +
- 工作流执行 - + {selectedVideoWorkflowDefinition.label} + {selectedWorkflowActionHint}
- Phase 4 + 执行
- +
提示词 {selectedWorkflowInputs.promptCount} @@ -7847,10 +7876,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
-
- 上游提示 -
-
+
{selectedWorkflowPrompt.trim() ? selectedWorkflowPrompt : "当前还没有抽取到可执行提示词,先在上游接入脚本、分镜或参考素材。"} @@ -7866,9 +7892,6 @@ export default function CanvasEditorPage({ params }: PageProps) {
{selectedWorkflowLinkedAgentProject.title || selectedWorkflowLinkedAgentProject.id}
-
- 再点下方按钮会同步当前视频画布上下文,并直接打开该项目。 -
) : null} diff --git a/components/canvas/nodes/WorkflowNode.tsx b/components/canvas/nodes/WorkflowNode.tsx index f8ed0eda5..b71e53c62 100644 --- a/components/canvas/nodes/WorkflowNode.tsx +++ b/components/canvas/nodes/WorkflowNode.tsx @@ -79,6 +79,16 @@ const STATUS_LABELS = { failed: "异常", } as const; +const STATUS_CLASS_MAP = { + pending: "border-slate-200 bg-white text-foreground/70 dark:border-zinc-800 dark:bg-zinc-950", + generating: + "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900/40 dark:bg-blue-950/40 dark:text-blue-200", + completed: + "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/40 dark:bg-emerald-950/40 dark:text-emerald-200", + failed: + "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-900/40 dark:bg-rose-950/40 dark:text-rose-200", +} as const; + export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) { const deleteNode = useCanvasStore((state) => state.deleteNode); const data = node.data as WorkflowNodeData; @@ -92,42 +102,36 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) { const groupMeta = GROUP_META[resolvedGroup] || GROUP_META.tool; const GroupIcon = groupMeta.icon; const accentColor = data.accentColor || definition?.accentColor || "#e2e8f0"; + const accentBarColor = toAlphaColor(accentColor, 0.92) || accentColor; const title = data.title || definition?.label || "工作流节点"; - const description = data.description || definition?.description || "等待补充节点配置。"; - const previewUrl = - String(metadata.previewMediaUrl || metadata.resultMediaUrl || "").trim(); + const statusLabel = STATUS_LABELS[data.status] || STATUS_LABELS.pending; + const statusClassName = + STATUS_CLASS_MAP[data.status] || STATUS_CLASS_MAP.pending; + + const previewUrl = String(metadata.previewMediaUrl || metadata.resultMediaUrl || "").trim(); const previewKind = String(metadata.previewMediaKind || metadata.resultMediaKind || "").trim() === "video" ? "video" : "image"; + const compareItems = Array.isArray(metadata.compareItems) + ? metadata.compareItems.slice(0, 4) + : []; const storyboardShots = Array.isArray(metadata.storyboardShots) ? metadata.storyboardShots.slice(0, 4) : []; const analysisHighlights = Array.isArray(metadata.analysisHighlights) ? metadata.analysisHighlights.slice(0, 4) : []; - const compareItems = Array.isArray(metadata.compareItems) - ? metadata.compareItems.slice(0, 4) - : []; - const tableColumns = Array.isArray(metadata.tableColumns) - ? metadata.tableColumns.slice(0, 4) - : []; - const tableRows = Array.isArray(metadata.tableRows) - ? metadata.tableRows.slice(0, 4) - : []; - const characters = Array.isArray(metadata.characters) - ? metadata.characters.slice(0, 3) - : []; - const scenes = Array.isArray(metadata.scenes) ? metadata.scenes.slice(0, 3) : []; + const tableRows = Array.isArray(metadata.tableRows) ? metadata.tableRows.slice(0, 4) : []; + const characters = Array.isArray(metadata.characters) ? metadata.characters.slice(0, 4) : []; + const scenes = Array.isArray(metadata.scenes) ? metadata.scenes.slice(0, 4) : []; const characterAssets = Array.isArray(metadata.characterAssets) - ? metadata.characterAssets.slice(0, 3) + ? metadata.characterAssets.slice(0, 4) : []; const sceneAssets = Array.isArray(metadata.sceneAssets) - ? metadata.sceneAssets.slice(0, 3) - : []; - const agentPlan = Array.isArray(metadata.agentPlan) - ? metadata.agentPlan.slice(0, 4) + ? metadata.sceneAssets.slice(0, 4) : []; + const agentPlan = Array.isArray(metadata.agentPlan) ? metadata.agentPlan.slice(0, 4) : []; const deliverables = Array.isArray(metadata.deliverables) ? metadata.deliverables.slice(0, 4) : []; @@ -135,432 +139,209 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) { const linkedAgentProjectTitle = String(metadata.linkedAgentProjectTitle || "").trim(); const importedAgentResultCount = Math.max(Number(metadata.importedAgentResultCount || 0), 0); const pendingAgentResultCount = Math.max(Number(metadata.pendingAgentResultCount || 0), 0); - const agentResultGroups = Array.isArray(metadata.agentResultGroups) - ? metadata.agentResultGroups.slice(0, 4) - : []; const batchShotCount = Math.max( Number(metadata.batchShotCount || 0), storyboardShots.length ); - const batchResultCount = Number(metadata.batchResultCount || 0); - const batchFailedCount = Number(metadata.batchFailedCount || 0); - const batchMode = - String(metadata.batchMode || "").trim() === "gen-video" ? "视频批量" : "图片批量"; - const sectionClassName = - "rounded-2xl border border-slate-200 bg-white p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.78)] dark:border-zinc-800 dark:bg-zinc-950"; - const mutedKickerClassName = - "text-[11px] uppercase tracking-[0.18em] text-muted-foreground/80"; - const itemClassName = - "rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900"; - const badgeClassName = - "border-slate-200 bg-white text-foreground/80 dark:border-zinc-800 dark:bg-zinc-950"; - const accentBarColor = toAlphaColor(accentColor, 0.9) || accentColor; + const batchResultCount = Math.max(Number(metadata.batchResultCount || 0), 0); + const batchFailedCount = Math.max(Number(metadata.batchFailedCount || 0), 0); + + const comparePreviewItems = compareItems + .map((item, index) => { + const compareItem = (item || {}) as Record; + const url = String(compareItem.url || "").trim(); + if (!url) return null; + return { + id: `${data.templateType}-compare-${index}`, + url, + kind: String(compareItem.kind || "").trim() === "video" ? "video" : "image", + label: String(compareItem.label || compareItem.title || `对比 ${index + 1}`).trim(), + }; + }) + .filter(Boolean) as Array<{ id: string; url: string; kind: "image" | "video"; label: string }>; + + const compactMetrics: string[] = []; + if (batchShotCount > 0) compactMetrics.push(`镜头 ${batchShotCount}`); + if (batchResultCount > 0) compactMetrics.push(`结果 ${batchResultCount}`); + if (analysisHighlights.length > 0) compactMetrics.push(`分析 ${analysisHighlights.length}`); + if (tableRows.length > 0) compactMetrics.push(`表格 ${tableRows.length}`); + if (characters.length > 0) compactMetrics.push(`角色 ${characters.length}`); + if (scenes.length > 0) compactMetrics.push(`场景 ${scenes.length}`); + if (characterAssets.length > 0) compactMetrics.push(`角色资产 ${characterAssets.length}`); + if (sceneAssets.length > 0) compactMetrics.push(`场景资产 ${sceneAssets.length}`); + if (linkedAgentProjectId) compactMetrics.push("Agent"); + if (importedAgentResultCount > 0) compactMetrics.push(`已回流 ${importedAgentResultCount}`); + if (pendingAgentResultCount > 0) compactMetrics.push(`待回流 ${pendingAgentResultCount}`); + if (batchFailedCount > 0) compactMetrics.push(`失败 ${batchFailedCount}`); + + const primaryTextCandidates = [ + String(data.summary || "").trim(), + String(analysisHighlights[0] || "").trim(), + String((storyboardShots[0] as Record)?.title || "").trim(), + String((storyboardShots[0] as Record)?.description || "").trim(), + String((characters[0] as Record)?.description || "").trim(), + String((scenes[0] as Record)?.description || "").trim(), + String(agentPlan[0] || "").trim(), + String(deliverables[0] || "").trim(), + quickConnectOutputs.length > 0 ? `可接 ${quickConnectOutputs.map((item) => item.label).join(" / ")}` : "", + String(definition?.description || data.description || "").trim(), + ].filter(Boolean); + const primaryText = primaryTextCandidates[0] || ""; + + const compactDetailLines = [ + storyboardShots[0] + ? `分镜:${String((storyboardShots[0] as Record)?.title || "镜头草案").trim()}` + : "", + characters[0] + ? `角色:${String((characters[0] as Record)?.name || (characters[0] as Record)?.title || "角色线索").trim()}` + : "", + scenes[0] + ? `场景:${String((scenes[0] as Record)?.name || (scenes[0] as Record)?.title || "场景线索").trim()}` + : "", + tableRows[0] ? "已整理结构化表格" : "", + linkedAgentProjectId ? `Agent:${linkedAgentProjectTitle || linkedAgentProjectId}` : "", + deliverables[0] ? `交付:${String(deliverables[0]).trim()}` : "", + ] + .filter(Boolean) + .slice(0, 2); return (
-
-
-
-
-
- - - - {groupMeta.label} -
-
-

{title}

-

- {description} -

-
-
-
- {isSelected ? ( - - ) : null} - - {STATUS_LABELS[data.status] || STATUS_LABELS.pending} - -
-
+
+
+ + + -
-
节点标识
-
{data.templateType}
-

- {data.summary || "接在上游素材之后,用来处理或输出结果。"} -

-
+
+
+
+
+ + {groupMeta.label} + + + {statusLabel} + +
+

{title}

+ {primaryText ? ( +

+ {primaryText} +

+ ) : null} +
- {batchShotCount > 0 || batchResultCount > 0 ? ( -
-
批量执行
-
- {batchShotCount > 0 ? ( - - 镜头 {batchShotCount} - - ) : null} - {batchResultCount > 0 ? ( - - 结果 {batchResultCount} - - ) : null} - {batchFailedCount > 0 ? ( - event.stopPropagation()} + onMouseDown={(event) => event.stopPropagation()} + onClick={(event) => { + event.stopPropagation(); + deleteNode(node.id); + }} + className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-muted-foreground transition-colors hover:border-destructive/30 hover:bg-red-50 hover:text-destructive dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-destructive/40 dark:hover:bg-red-950/30" + aria-label="删除模块" + title="删除模块" > - 失败 {batchFailedCount} - - ) : null} - {batchResultCount > 0 ? ( - - {batchMode} - + + ) : null}
- ) : null} +
{previewUrl ? ( -
+
{previewKind === "video" ? (
- ) : null} - - {compareItems.length > 0 ? ( -
-
对比结果
-
- {compareItems.map((item, index) => { - const compareItem = (item || {}) as Record; - const kind = - String(compareItem.kind || "").trim() === "video" ? "video" : "image"; - const url = String(compareItem.url || "").trim(); - if (!url) { - return null; - } - return ( -
-
-
- {String(compareItem.label || `对比 ${index + 1}`)} -
-
- {String(compareItem.title || `素材 ${index + 1}`)} -
-
- {kind === "video" ? ( -
- ); - })} -
+ ) : comparePreviewItems.length > 0 ? ( +
+ {comparePreviewItems.slice(0, 4).map((item) => ( +
+ {item.kind === "video" ? ( +
+ ))}
) : null} - {storyboardShots.length > 0 ? ( -
-
镜头草案
-
- {storyboardShots.map((item, index) => { - const shot = (item || {}) as Record; - return ( -
-
- {String(shot.title || `镜头 ${index + 1}`)} -
-
- {String(shot.description || "")} -
-
- ); - })} -
+ {compactMetrics.length > 0 ? ( +
+ {compactMetrics.slice(0, 6).map((metric) => ( + + {metric} + + ))}
) : null} - {tableColumns.length > 0 && tableRows.length > 0 ? ( -
-
结构化表格
-
- {tableColumns.map((item, index) => { - const column = (item || {}) as Record; - return ( - - {String(column.label || column.key || `列 ${index + 1}`)} - - ); - })} -
-
- {tableRows.map((item, index) => { - const row = (item || {}) as Record; - return ( -
- {tableColumns.map((columnItem, columnIndex) => { - const column = (columnItem || {}) as Record; - const columnKey = String(column.key || `col-${columnIndex}`).trim(); - const label = String(column.label || columnKey || `列 ${columnIndex + 1}`).trim(); - const value = String(row[columnKey] || "").trim(); - if (!value) { - return null; - } - return ( -
0 ? "mt-2" : ""} - > -
- {label} -
-
- {value} -
-
- ); - })} -
- ); - })} -
+ {compactDetailLines.length > 0 ? ( +
+ {compactDetailLines.map((line) => ( +
+ {line} +
+ ))}
) : null} - {analysisHighlights.length > 0 ? ( -
-
分析摘要
-
- {analysisHighlights.map((item, index) => ( -
- {String(item || "")} -
- ))} -
-
- ) : null} - - {characters.length > 0 ? ( -
-
角色线索
-
- {characters.map((item, index) => { - const character = (item || {}) as Record; - return ( -
-
- {String(character.name || character.title || `角色 ${index + 1}`)} -
-
- {String(character.description || character.prompt || "")} -
-
- ); - })} -
-
- ) : null} - - {scenes.length > 0 ? ( -
-
场景线索
-
- {scenes.map((item, index) => { - const scene = (item || {}) as Record; - return ( -
-
- {String(scene.name || scene.title || `场景 ${index + 1}`)} -
-
- {String(scene.description || scene.prompt || "")} -
-
- ); - })} -
-
- ) : null} - - {characterAssets.length > 0 || sceneAssets.length > 0 ? ( -
-
资产沉淀
-
- {[...characterAssets, ...sceneAssets].slice(0, 4).map((item, index) => { - const asset = (item || {}) as Record; - return ( -
-
- {String(asset.name || asset.title || `资产 ${index + 1}`)} -
-
- {String(asset.description || asset.prompt || "")} -
-
- ); - })} -
-
- ) : null} - - {agentPlan.length > 0 || deliverables.length > 0 ? ( -
-
代理计划
-
- {agentPlan.map((item, index) => ( -
- {String(item || "")} -
- ))} - {deliverables.map((item, index) => ( -
- 交付物: {String(item || "")} -
- ))} -
-
- ) : null} - - {linkedAgentProjectId ? ( -
-
Agent 工作台
-
- - 已连接 - - {importedAgentResultCount > 0 ? ( - - 已回流 {importedAgentResultCount} - - ) : null} - {pendingAgentResultCount > 0 ? ( - - 待回流 {pendingAgentResultCount} - - ) : null} -
-
- {linkedAgentProjectTitle || linkedAgentProjectId} -
-
- ) : null} - - {agentResultGroups.length > 0 ? ( -
-
Agent 结果
-
- {agentResultGroups.map((item, index) => { - const group = (item || {}) as Record; - const label = String(group.label || `结果组 ${index + 1}`).trim(); - const count = Math.max(Number(group.count || 0), 0); - const importedCount = Math.max(Number(group.importedCount || 0), 0); - return ( -
-
-
{label}
-
- {importedCount}/{count} -
-
-
- 共 {count} 个结果,已回流 {importedCount} 个。 -
-
- ); - })} -
-
- ) : null} - -
-
下一步推荐
+
{quickConnectOutputs.length > 0 ? ( -
- {quickConnectOutputs.slice(0, 4).map((item) => ( - - {item.label} - - ))} -
+ quickConnectOutputs.slice(0, 4).map((item) => ( + + {item.label} + + )) ) : ( -
- 当前节点通常作为流程终点或独立处理节点 -
+ + 结束节点 + )}
diff --git a/lib/video-workflow/catalog.ts b/lib/video-workflow/catalog.ts index faa1dfb0e..6da1d736a 100644 --- a/lib/video-workflow/catalog.ts +++ b/lib/video-workflow/catalog.ts @@ -126,6 +126,86 @@ const SECTION_DEFINITIONS: VideoWorkflowSectionDefinition[] = [ { id: "output", label: "输出", description: "预览、对比和保存结果" }, ]; +const COMPACT_NODE_SIZE_OVERRIDES: Partial< + Record +> = { + "text-node": { width: 300, height: 170 }, + "novel-input": { width: 340, height: 240 }, + "input-image": { width: 220, height: 220 }, + "video-input": { width: 240, height: 220 }, + "video-analyze": { width: 300, height: 210 }, + "extract-characters-scenes": { width: 300, height: 210 }, + "storyboard-node": { width: 380, height: 240 }, + "storyboard-chart-node": { width: 360, height: 220 }, + "table-editor-node": { width: 340, height: 220 }, + "gen-image": { width: 280, height: 190 }, + "gen-video": { width: 280, height: 190 }, + "gen-music": { width: 300, height: 200 }, + "jimeng-super-resolution": { width: 260, height: 160 }, + "motion-control": { width: 300, height: 220 }, + "inpaint-crop": { width: 280, height: 180 }, + "inpaint-stitch": { width: 280, height: 180 }, + "professional-camera": { width: 280, height: 180 }, + "camera-movement": { width: 280, height: 180 }, + "image-compare": { width: 320, height: 220 }, + preview: { width: 320, height: 220 }, + "local-save": { width: 260, height: 180 }, + "character-description": { width: 300, height: 180 }, + "scene-description": { width: 300, height: 180 }, + "create-character": { width: 280, height: 160 }, + "create-scene": { width: 280, height: 160 }, + "generate-character-image": { width: 300, height: 200 }, + "generate-character-video": { width: 300, height: 200 }, + "generate-scene-image": { width: 300, height: 200 }, + "generate-scene-video": { width: 300, height: 200 }, + "custom-agent": { width: 340, height: 220 }, + "rh-app": { width: 280, height: 180 }, + "rh-comfy": { width: 280, height: 180 }, + "canvas-node": { width: 360, height: 240 }, + "doodle-canvas": { width: 300, height: 180 }, +}; + +function resolveCompactNodeSize( + type: VideoWorkflowTemplateType, + canvasNodeType: NodeType, + size: { width: number; height: number } +) { + const override = COMPACT_NODE_SIZE_OVERRIDES[type]; + if (override) { + return override; + } + + if (canvasNodeType === "workflow") { + return { + width: Math.min(size.width, 300), + height: Math.min(size.height, 180), + }; + } + + if (canvasNodeType === "text") { + return { + width: Math.min(size.width, 320), + height: Math.min(size.height, 180), + }; + } + + if (canvasNodeType === "image") { + return { + width: Math.min(size.width, 220), + height: Math.min(size.height, 220), + }; + } + + if (canvasNodeType === "video") { + return { + width: Math.min(size.width, 240), + height: Math.min(size.height, 220), + }; + } + + return size; +} + const createDefinition = ( type: VideoWorkflowTemplateType, label: string, @@ -139,7 +219,7 @@ const createDefinition = ( label, description, group, - size, + size: resolveCompactNodeSize(type, canvasNodeType, size), canvasNodeType, accentColor, quickConnectOutputs: QUICK_CONNECT_CONFIG[type] || [], @@ -625,10 +705,10 @@ export function buildVideoWorkflowCanvasNode( description: definition.description, summary: definition.quickConnectOutputs.length > 0 - ? `下一步可接:${definition.quickConnectOutputs + ? `可接:${definition.quickConnectOutputs .map((item) => item.label) .join(" / ")}` - : "当前节点主要承担中间处理或输出检查。", + : "当前节点用于处理中间结果或输出检查。", status: "pending", group: definition.group, accentColor: definition.accentColor, @@ -645,7 +725,7 @@ export function buildVideoWorkflowStarterTemplateGraph( const template = VIDEO_WORKFLOW_STARTER_TEMPLATES.find((item) => item.id === templateId) || VIDEO_WORKFLOW_STARTER_TEMPLATES[0]; - const laneHeight = 540; + const laneHeight = 320; let cursorX = origin.x; const nodes = template.nodes.map((type) => { @@ -657,7 +737,7 @@ export function buildVideoWorkflowStarterTemplateGraph( x: cursorX, y: origin.y + Math.max(0, (laneHeight - definition.size.height) / 2), }; - cursorX += definition.size.width + 180; + cursorX += definition.size.width + 96; return { type, node: buildVideoWorkflowCanvasNode(type, position),