From d4b65f3933a4d8109bdae2ebd4c5cda8ea9d93a0 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Fri, 16 Jan 2026 22:59:33 +1300 Subject: [PATCH] feat(26-01): improve workflow preview with outlined nodes and labels - Replace solid colored boxes with outlined nodes - Add labels inside each node (Image Input, Prompt, etc.) - Show model name for generate nodes (Gemini Pro, etc.) - Use smoothstep edges for visible connections - Simplify modal by removing redundant legend --- src/components/quickstart/WorkflowPreview.tsx | 159 +++++++++++++++--- .../quickstart/WorkflowPreviewModal.tsx | 49 ++---- 2 files changed, 147 insertions(+), 61 deletions(-) diff --git a/src/components/quickstart/WorkflowPreview.tsx b/src/components/quickstart/WorkflowPreview.tsx index be69903a..63ba28b3 100644 --- a/src/components/quickstart/WorkflowPreview.tsx +++ b/src/components/quickstart/WorkflowPreview.tsx @@ -8,10 +8,12 @@ import { Edge, useReactFlow, NodeProps, + Handle, + Position, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; -// Node type to color mapping - using similar colors from WorkflowCanvas minimap +// Node type to color mapping const NODE_COLORS: Record = { imageInput: "#22c55e", // green-500 annotation: "#eab308", // yellow-500 @@ -23,22 +25,94 @@ const NODE_COLORS: Record = { output: "#6b7280", // gray-500 }; -// Simple preview node component - just a colored rectangle +// Node type to display label mapping +const NODE_LABELS: Record = { + imageInput: "Image Input", + annotation: "Annotation", + prompt: "Prompt", + nanoBanana: "Generate Image", + generateVideo: "Generate Video", + llmGenerate: "LLM", + splitGrid: "Split Grid", + output: "Output", +}; + +interface PreviewNodeData extends Record { + nodeType: string; + label?: string; + model?: string; +} + +// Preview node component with outline style and labels function PreviewNode({ data }: NodeProps) { - const nodeType = data?.nodeType as string || "unknown"; + const nodeData = data as PreviewNodeData; + const nodeType = nodeData?.nodeType || "unknown"; const color = NODE_COLORS[nodeType] || "#6b7280"; + const label = nodeData?.label || NODE_LABELS[nodeType] || nodeType; + const model = nodeData?.model; + + // Determine if this is a generate node that should show model + const isGenerateNode = nodeType === "nanoBanana" || nodeType === "generateVideo"; return (
+ > + {/* Node label */} +
+ {label} +
+ + {/* Model name for generate nodes */} + {isGenerateNode && model && ( +
+ {model} +
+ )} + + {/* Invisible handles for edge connections */} + + +
); } @@ -54,41 +128,72 @@ interface WorkflowPreviewProps { className?: string; } +// Helper to extract model name from node data +function getModelName(nodeData: Record): string | undefined { + // Check for model field (common in generate nodes) + if (typeof nodeData?.model === "string") { + // Format model name to be more readable + const model = nodeData.model as string; + // Map internal names to display names + if (model === "nano-banana") return "Gemini Flash"; + if (model === "nano-banana-pro") return "Gemini Pro"; + // For other models, show shortened version + if (model.includes("/")) { + const parts = model.split("/"); + return parts[parts.length - 1]; + } + return model; + } + return undefined; +} + // Inner component that can use useReactFlow function WorkflowPreviewInner({ workflow, className = "" }: WorkflowPreviewProps) { const { fitView } = useReactFlow(); - // Transform workflow nodes to preview nodes - keep original sizes/positions for better layout + // Transform workflow nodes to preview nodes with labels const previewNodes = useMemo(() => { - return workflow.nodes.map((node) => ({ - id: node.id, - type: "preview", - position: node.position, - data: { nodeType: node.type || "unknown" }, - // Use scaled-down versions of the original dimensions - style: { - width: ((node.style?.width as number) || 300) * 0.3, - height: ((node.style?.height as number) || 280) * 0.3, - }, - })); + return workflow.nodes.map((node) => { + const nodeType = node.type || "unknown"; + const nodeData = node.data as Record; + + return { + id: node.id, + type: "preview", + position: node.position, + data: { + nodeType, + label: NODE_LABELS[nodeType] || nodeType, + model: getModelName(nodeData), + } as PreviewNodeData, + // Use scaled-down versions of the original dimensions + style: { + width: ((node.style?.width as number) || 300) * 0.35, + height: ((node.style?.height as number) || 280) * 0.3, + }, + }; + }); }, [workflow.nodes]); - // Use the workflow edges with simplified styling + // Create visible edges with better styling const previewEdges = useMemo(() => { return workflow.edges.map((edge) => ({ id: edge.id, source: edge.source, target: edge.target, - style: { stroke: "#525252", strokeWidth: 2 }, - type: "default", + type: "smoothstep", + style: { + stroke: "#525252", + strokeWidth: 2, + }, + animated: false, })); }, [workflow.edges]); // Fit view when nodes load const onInit = useCallback(() => { - // Small delay to ensure nodes are rendered setTimeout(() => { - fitView({ padding: 0.3, duration: 0 }); + fitView({ padding: 0.2, duration: 0 }); }, 50); }, [fitView]); @@ -110,13 +215,13 @@ function WorkflowPreviewInner({ workflow, className = "" }: WorkflowPreviewProps preventScrolling={true} // Fit the preview to container fitView={true} - fitViewOptions={{ padding: 0.3 }} + fitViewOptions={{ padding: 0.2 }} // Hide attribution proOptions={{ hideAttribution: true }} // Styling className="bg-transparent" defaultEdgeOptions={{ - type: "default", + type: "smoothstep", animated: false, }} /> diff --git a/src/components/quickstart/WorkflowPreviewModal.tsx b/src/components/quickstart/WorkflowPreviewModal.tsx index 85ad7d3c..bff26ef2 100644 --- a/src/components/quickstart/WorkflowPreviewModal.tsx +++ b/src/components/quickstart/WorkflowPreviewModal.tsx @@ -46,14 +46,19 @@ export function WorkflowPreviewModal({ {/* Modal */}
e.stopPropagation()} > {/* Header */}
-

- {templateName} — Workflow Preview -

+
+

+ {templateName} +

+

+ Workflow structure preview +

+