From f6b9a6f73bcbde6eba8129c9fb117a197bbd0258 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 5 Apr 2026 02:26:18 +0800 Subject: [PATCH] Refine video canvas workspace UX --- app/canvas/[id]/page.tsx | 210 ++++++-------------------- components/canvas/CanvasWorkspace.tsx | 21 ++- 2 files changed, 66 insertions(+), 165 deletions(-) diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index 421b88f1a..6bd9dd021 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -79,13 +79,11 @@ import { } from "@/services/canvas"; import { buildVideoWorkflowCanvasNode, - buildVideoWorkflowStarterTemplateGraph, getVideoWorkflowNodeDefinition, getVideoWorkflowQuickConnectOutputs, getVideoWorkflowSectionNodes, inferVideoWorkflowTemplateType, VIDEO_WORKFLOW_LIBRARY_SECTIONS, - VIDEO_WORKFLOW_STARTER_TEMPLATES, type VideoWorkflowTemplateType, } from "@/lib/video-workflow/catalog"; import { @@ -428,10 +426,6 @@ const VIDEO_CANVAS_PANEL_BADGE_CLASS = "border-border/60 bg-background/82 text-foreground/75"; const VIDEO_CANVAS_SUBSECTION_CLASS = "rounded-2xl border border-border/60 bg-background/72 p-3"; -const VIDEO_CANVAS_TEMPLATE_CARD_CLASS = - "rounded-[24px] border border-border/60 bg-background/76 p-4 text-left shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/50 hover:bg-background/92 hover:shadow-lg"; -const VIDEO_CANVAS_SIDEBAR_SHELL_CLASS = - "h-full w-full overflow-hidden rounded-l-[30px] border border-r-0 border-border/60 bg-background/94 shadow-[0_30px_80px_-34px_rgba(15,23,42,0.42)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/82"; const VIDEO_CANVAS_TOOLBAR_CLASS = "flex items-center gap-1.5 rounded-[28px] border border-border/60 bg-background/94 px-2.5 py-1.5 shadow-2xl backdrop-blur-xl supports-[backdrop-filter]:bg-background/82"; const VIDEO_CANVAS_TOOLBAR_BUTTON_CLASS = @@ -739,7 +733,7 @@ export default function CanvasEditorPage({ params }: PageProps) { const [isEditingTitle, setIsEditingTitle] = useState(false); const [editTitle, setEditTitle] = useState(""); const [activeWorkflowExecutionTaskKey, setActiveWorkflowExecutionTaskKey] = useState(null); - const [isPanelOpen, setIsPanelOpen] = useState(true); + const [isPanelOpen, setIsPanelOpen] = useState(() => !isVideoMode); const [sceneFusionDialogOpen, setSceneFusionDialogOpen] = useState(false); // 面板模式: 'chat' 聊天面板, 'fileList' 文件列表 const [panelMode, setPanelMode] = useState<'chat' | 'fileList'>('chat'); @@ -1376,8 +1370,9 @@ export default function CanvasEditorPage({ params }: PageProps) { const resolvedPanelWidth = viewportWidth > 0 ? Math.min(FLOATING_PANEL_WIDTH, Math.max(viewportWidth - DOCKED_PANEL_INSET, 0)) : FLOATING_PANEL_WIDTH; - const panelOcclusionRight = isPanelOpen ? resolvedPanelWidth + DOCKED_PANEL_INSET : 0; - const floatingToolbarOffset = isPanelOpen ? -(panelOcclusionRight / 2) : 0; + const isFloatingSidebarVisible = !isVideoMode && isPanelOpen; + const panelOcclusionRight = isFloatingSidebarVisible ? resolvedPanelWidth + DOCKED_PANEL_INSET : 0; + const floatingToolbarOffset = isFloatingSidebarVisible ? -(panelOcclusionRight / 2) : 0; const compactControlsLeft = FLOATING_UI_GAP; const updateCanvasSearchParams = useCallback( @@ -4476,63 +4471,6 @@ export default function CanvasEditorPage({ params }: PageProps) { [createCanvasNode, getVideoWorkflowPlacement] ); - const handleInsertVideoWorkflowTemplate = useCallback( - (templateId: string) => { - const upstreamNodeId = - isVideoMode && selectedNodeIds.length === 1 ? selectedNodeIds[0] || null : null; - const starterTemplate = - VIDEO_WORKFLOW_STARTER_TEMPLATES.find((item) => item.id === templateId) || - VIDEO_WORKFLOW_STARTER_TEMPLATES[0]; - const firstNodeType = starterTemplate.nodes[0] || "text-node"; - const firstNodeDefinition = getVideoWorkflowNodeDefinition(firstNodeType); - const origin = upstreamNodeId - ? getVideoWorkflowPlacement( - firstNodeDefinition?.size || { width: 360, height: 280 } - ) - : getVisibleCanvasOrigin(); - const { nodes: templateNodes, edges: templateEdges } = - buildVideoWorkflowStarterTemplateGraph(templateId, origin); - const createdIds = templateNodes.map((item) => - createCanvasNode(item.node, { - connectFromSelection: false, - selectNewNode: false, - }) - ); - - if (upstreamNodeId && createdIds[0] && upstreamNodeId !== createdIds[0]) { - addEdge({ - source: upstreamNodeId, - target: createdIds[0], - type: "default", - }); - } - - templateEdges.forEach((edge) => { - const sourceId = createdIds[edge.sourceIndex]; - const targetId = createdIds[edge.targetIndex]; - if (!sourceId || !targetId) return; - addEdge({ - source: sourceId, - target: targetId, - type: edge.type, - }); - }); - - if (createdIds.length > 0) { - selectNodes(createdIds); - } - }, - [ - addEdge, - createCanvasNode, - getVisibleCanvasOrigin, - getVideoWorkflowPlacement, - isVideoMode, - selectNodes, - selectedNodeIds, - ] - ); - const handleCreateSelectedAgentResultBranches = useCallback(async () => { if ( !selectedVideoWorkflowNode || @@ -7140,6 +7078,7 @@ export default function CanvasEditorPage({ params }: PageProps) { occlusionRight={panelOcclusionRight} workspaceLabel={workspaceLabel} workspaceVariant={isVideoMode ? "video" : "canvas"} + showEmptyState={!isVideoMode} emptyStateDescription={ isVideoMode ? "先在右侧选择文生视频、主体参考或首尾帧模式,再开始生成" @@ -7156,54 +7095,6 @@ export default function CanvasEditorPage({ params }: PageProps) { ) : null} - {isVideoMode && nodes.length === 0 ? ( -
- - -
-
- 参考工作流模板 - - 这里按参考 APP 的核心链路预置了脚本成片、图片转视频、视频拆解、角色拆解和局部重绘几条起手流程。 - -
- - 第四阶段 - -
-
- - {VIDEO_WORKFLOW_STARTER_TEMPLATES.map((template) => ( - - ))} - -
-
- ) : null} - {isVideoMode && selectedVideoWorkflowDefinition && (selectedVideoWorkflowQuickConnects.length > 0 || selectedWorkflowIsActionable) ? ( @@ -7428,7 +7319,7 @@ export default function CanvasEditorPage({ params }: PageProps) { {agentDisabledNotice} {/* 2. Floating Actions (Top Right) */} - {!isPanelOpen && ( + {!isVideoMode && !isPanelOpen && (
+ ) : null} {/* 5. Floating Toolbar (Bottom) */}
{ @@ -1507,7 +1522,7 @@ export function CanvasWorkspace({ )} {/* 空状态提示 */} - {nodes.length === 0 && ( + {showEmptyState && nodes.length === 0 && (
{isWorkflowCanvas ? (