diff --git a/src/components/quickstart/TemplateCard.tsx b/src/components/quickstart/TemplateCard.tsx index 7b0c79fe..0591adb0 100644 --- a/src/components/quickstart/TemplateCard.tsx +++ b/src/components/quickstart/TemplateCard.tsx @@ -14,7 +14,7 @@ interface TemplateCardProps { nodeCount: number; previewImage?: string; isLoading?: boolean; - onClick: () => void; + onUseWorkflow: () => void; disabled?: boolean; } @@ -37,57 +37,33 @@ export function TemplateCard({ nodeCount, previewImage, isLoading = false, - onClick, + onUseWorkflow, disabled = false, }: TemplateCardProps) { return ( - + - + ); } diff --git a/src/components/quickstart/TemplateDetailModal.tsx b/src/components/quickstart/TemplateDetailModal.tsx deleted file mode 100644 index 9a654aa9..00000000 --- a/src/components/quickstart/TemplateDetailModal.tsx +++ /dev/null @@ -1,231 +0,0 @@ -"use client"; - -import { useEffect, useCallback } from "react"; -import { TemplateCategory } from "@/types/quickstart"; - -interface TemplateDetailModalProps { - isOpen: boolean; - onClose: () => void; - onUseWorkflow: () => void; - isLoading: boolean; - template: { - id: string; - name: string; - description: string; - category: TemplateCategory; - tags: string[]; - }; - nodeCount: number; - previewImage?: string; -} - -const CATEGORY_LABELS: Record = { - product: "Product", - style: "Style", - composition: "Composition", - community: "Community", -}; - -const CATEGORY_COLORS: Record = { - product: "bg-blue-500/20 text-blue-300 border-blue-500/30", - style: "bg-purple-500/20 text-purple-300 border-purple-500/30", - composition: "bg-green-500/20 text-green-300 border-green-500/30", - community: "bg-amber-500/20 text-amber-300 border-amber-500/30", -}; - -export function TemplateDetailModal({ - isOpen, - onClose, - onUseWorkflow, - isLoading, - template, - nodeCount, - previewImage, -}: TemplateDetailModalProps) { - // Handle escape key to close - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.key === "Escape" && !isLoading) { - onClose(); - } - }, - [onClose, isLoading] - ); - - useEffect(() => { - if (isOpen) { - document.addEventListener("keydown", handleKeyDown); - return () => document.removeEventListener("keydown", handleKeyDown); - } - }, [isOpen, handleKeyDown]); - - if (!isOpen) return null; - - return ( -
- {/* Backdrop */} -
- - {/* Modal */} -
e.stopPropagation()} - > - {/* Header */} -
-
-
-

- {template.name} -

- - {CATEGORY_LABELS[template.category]} - -
-

- {template.description} -

-
- -
- - {/* Workflow Screenshot */} -
-
- {previewImage ? ( - /* eslint-disable-next-line @next/next/no-img-element */ - {`${template.name} - ) : ( -
No preview available
- )} -
-
- - {/* Footer */} -
-
- {/* Metadata */} -
-
- - - - {nodeCount} nodes -
- - {/* Tags */} -
- {template.tags.map((tag) => ( - - {tag} - - ))} -
-
- - {/* Actions */} -
- - -
-
-
-
-
- ); -} diff --git a/src/components/quickstart/TemplateExplorerView.tsx b/src/components/quickstart/TemplateExplorerView.tsx index 0938548f..980459fa 100644 --- a/src/components/quickstart/TemplateExplorerView.tsx +++ b/src/components/quickstart/TemplateExplorerView.tsx @@ -5,7 +5,6 @@ import { WorkflowFile } from "@/store/workflowStore"; import { getAllPresets, PRESET_TEMPLATES, getTemplateContent } from "@/lib/quickstart/templates"; import { QuickstartBackButton } from "./QuickstartBackButton"; import { TemplateCard } from "./TemplateCard"; -import { TemplateDetailModal } from "./TemplateDetailModal"; import { CommunityWorkflowMeta, TemplateCategory, TemplateMetadata } from "@/types/quickstart"; interface TemplateExplorerViewProps { @@ -31,7 +30,6 @@ export function TemplateExplorerView({ const [isLoadingList, setIsLoadingList] = useState(true); const [loadingWorkflowId, setLoadingWorkflowId] = useState(null); const [error, setError] = useState(null); - const [selectedTemplateId, setSelectedTemplateId] = useState(null); // Filter state const [searchQuery, setSearchQuery] = useState(""); @@ -401,15 +399,16 @@ export function TemplateExplorerView({

Quick Start

-
+
{filteredPresets.map((preset) => ( setSelectedTemplateId(preset.id)} - disabled={isLoading} + isLoading={loadingWorkflowId === preset.id} + onUseWorkflow={() => handlePresetSelect(preset.id)} + disabled={isLoading && loadingWorkflowId !== preset.id} /> ))}
@@ -582,20 +581,6 @@ export function TemplateExplorerView({
- {/* Template Detail Modal */} - {selectedTemplateId && ( - setSelectedTemplateId(null)} - onUseWorkflow={() => { - handlePresetSelect(selectedTemplateId); - }} - isLoading={loadingWorkflowId === selectedTemplateId} - template={presets.find((p) => p.id === selectedTemplateId)!} - nodeCount={presetMetadata[selectedTemplateId]?.nodeCount ?? 0} - previewImage={previewImages[selectedTemplateId]} - /> - )} ); } diff --git a/src/components/quickstart/WorkflowPreview.tsx b/src/components/quickstart/WorkflowPreview.tsx deleted file mode 100644 index 63ba28b3..00000000 --- a/src/components/quickstart/WorkflowPreview.tsx +++ /dev/null @@ -1,239 +0,0 @@ -"use client"; - -import { useMemo, useCallback } from "react"; -import { - ReactFlow, - ReactFlowProvider, - Node, - Edge, - useReactFlow, - NodeProps, - Handle, - Position, -} from "@xyflow/react"; -import "@xyflow/react/dist/style.css"; - -// Node type to color mapping -const NODE_COLORS: Record = { - imageInput: "#22c55e", // green-500 - annotation: "#eab308", // yellow-500 - prompt: "#3b82f6", // blue-500 - nanoBanana: "#f97316", // orange-500 - generateVideo: "#a855f7", // purple-500 - llmGenerate: "#06b6d4", // cyan-500 - splitGrid: "#ec4899", // pink-500 - output: "#6b7280", // gray-500 -}; - -// 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 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 */} - - -
- ); -} - -const previewNodeTypes = { - preview: PreviewNode, -}; - -interface WorkflowPreviewProps { - workflow: { - nodes: Node[]; - edges: Edge[]; - }; - 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 with labels - const previewNodes = useMemo(() => { - 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]); - - // Create visible edges with better styling - const previewEdges = useMemo(() => { - return workflow.edges.map((edge) => ({ - id: edge.id, - source: edge.source, - target: edge.target, - type: "smoothstep", - style: { - stroke: "#525252", - strokeWidth: 2, - }, - animated: false, - })); - }, [workflow.edges]); - - // Fit view when nodes load - const onInit = useCallback(() => { - setTimeout(() => { - fitView({ padding: 0.2, duration: 0 }); - }, 50); - }, [fitView]); - - return ( -
- -
- ); -} - -// Wrap with ReactFlowProvider for standalone usage -export function WorkflowPreview(props: WorkflowPreviewProps) { - return ( - - - - ); -} diff --git a/src/components/quickstart/WorkflowPreviewModal.tsx b/src/components/quickstart/WorkflowPreviewModal.tsx deleted file mode 100644 index bff26ef2..00000000 --- a/src/components/quickstart/WorkflowPreviewModal.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; - -import { useEffect, useCallback } from "react"; -import { Node, Edge } from "@xyflow/react"; -import { WorkflowPreview } from "./WorkflowPreview"; - -interface WorkflowPreviewModalProps { - isOpen: boolean; - onClose: () => void; - templateName: string; - workflow: { nodes: Node[]; edges: Edge[] }; -} - -export function WorkflowPreviewModal({ - isOpen, - onClose, - templateName, - workflow, -}: WorkflowPreviewModalProps) { - // Handle escape key to close - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.key === "Escape") { - onClose(); - } - }, - [onClose] - ); - - useEffect(() => { - if (isOpen) { - document.addEventListener("keydown", handleKeyDown); - return () => document.removeEventListener("keydown", handleKeyDown); - } - }, [isOpen, handleKeyDown]); - - if (!isOpen) return null; - - return ( -
- {/* Backdrop */} -
- - {/* Modal */} -
e.stopPropagation()} - > - {/* Header */} -
-
-

- {templateName} -

-

- Workflow structure preview -

-
- -
- - {/* Preview Content */} -
-
- -
-
- - {/* Footer hint */} -
-

- Press Esc or click outside to close -

-
-
-
- ); -}