"use client"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { WorkflowFile } from "@/store/workflowStore"; import { getAllPresets, PRESET_TEMPLATES } from "@/lib/quickstart/templates"; import { QuickstartBackButton } from "./QuickstartBackButton"; import { TemplateCard } from "./TemplateCard"; import { CommunityWorkflowMeta, TemplateCategory, TemplateMetadata } from "@/types/quickstart"; interface TemplateExplorerViewProps { onBack: () => void; onWorkflowSelected: (workflow: WorkflowFile) => void; } type CategoryFilter = "all" | TemplateCategory; const CATEGORY_OPTIONS: { id: CategoryFilter; label: string }[] = [ { id: "all", label: "All" }, { id: "product", label: "Product" }, { id: "style", label: "Style" }, { id: "composition", label: "Composition" }, { id: "community", label: "Community" }, ]; export function TemplateExplorerView({ onBack, onWorkflowSelected, }: TemplateExplorerViewProps) { const [communityWorkflows, setCommunityWorkflows] = useState([]); const [isLoadingList, setIsLoadingList] = useState(true); const [loadingWorkflowId, setLoadingWorkflowId] = useState(null); const [error, setError] = useState(null); // Filter state const [searchQuery, setSearchQuery] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); const [categoryFilter, setCategoryFilter] = useState("all"); const [selectedTags, setSelectedTags] = useState>(new Set()); const searchTimeoutRef = useRef(null); const presets = getAllPresets(); // Debounce search query useEffect(() => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } searchTimeoutRef.current = setTimeout(() => { setDebouncedSearch(searchQuery); }, 200); return () => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } }; }, [searchQuery]); // Calculate node counts for each preset const presetMetadata = useMemo(() => { const metadata: Record = {}; PRESET_TEMPLATES.forEach((template) => { metadata[template.id] = { nodeCount: template.workflow.nodes.length, category: template.category, tags: template.tags, }; }); return metadata; }, []); // Get workflow data for each template const presetWorkflows = useMemo(() => { const workflows: Record = {}; PRESET_TEMPLATES.forEach((template) => { workflows[template.id] = { nodes: template.workflow.nodes, edges: template.workflow.edges, }; }); return workflows; }, []); // Filter presets based on search, category, and tags const filteredPresets = useMemo(() => { return presets.filter((preset) => { // Search filter: match name or description if (debouncedSearch) { const searchLower = debouncedSearch.toLowerCase(); const matchesSearch = preset.name.toLowerCase().includes(searchLower) || preset.description.toLowerCase().includes(searchLower); if (!matchesSearch) return false; } // Category filter if (categoryFilter !== "all" && categoryFilter !== "community") { if (preset.category !== categoryFilter) return false; } // If "community" is selected, hide preset templates (they're not community) if (categoryFilter === "community") { return false; } // Tags filter (OR logic - match ANY selected tag) if (selectedTags.size > 0) { const hasMatchingTag = preset.tags.some((tag) => selectedTags.has(tag)); if (!hasMatchingTag) return false; } return true; }); }, [presets, debouncedSearch, categoryFilter, selectedTags]); // Filter community workflows const filteredCommunity = useMemo(() => { // Only show community workflows if "all" or "community" category selected if (categoryFilter !== "all" && categoryFilter !== "community") { return []; } return communityWorkflows.filter((workflow) => { // Search filter if (debouncedSearch) { const searchLower = debouncedSearch.toLowerCase(); const matchesSearch = workflow.name.toLowerCase().includes(searchLower) || workflow.author.toLowerCase().includes(searchLower); if (!matchesSearch) return false; } // Tags don't apply to community workflows (they don't have tags) return true; }); }, [communityWorkflows, debouncedSearch, categoryFilter]); // Collect all unique tags from filtered presets const availableTags = useMemo(() => { const tags = new Set(); // Get tags from ALL presets (not just filtered) so users can explore presets.forEach((preset) => { preset.tags.forEach((tag) => tags.add(tag)); }); return Array.from(tags).sort(); }, [presets]); // Toggle tag selection const toggleTag = useCallback((tag: string) => { setSelectedTags((prev) => { const next = new Set(prev); if (next.has(tag)) { next.delete(tag); } else { next.add(tag); } return next; }); }, []); // Clear all filters const clearFilters = useCallback(() => { setSearchQuery(""); setDebouncedSearch(""); setCategoryFilter("all"); setSelectedTags(new Set()); }, []); // Check if any filters are active const hasActiveFilters = searchQuery || categoryFilter !== "all" || selectedTags.size > 0; // Check if results are empty const hasNoResults = filteredPresets.length === 0 && (categoryFilter === "community" ? filteredCommunity.length === 0 : true) && !isLoadingList; // Fetch community workflows on mount useEffect(() => { async function fetchCommunityWorkflows() { try { const response = await fetch("/api/community-workflows"); const result = await response.json(); if (result.success) { setCommunityWorkflows(result.workflows); } else { console.error("Failed to fetch community workflows:", result.error); } } catch (err) { console.error("Error fetching community workflows:", err); } finally { setIsLoadingList(false); } } fetchCommunityWorkflows(); }, []); const handlePresetSelect = useCallback( async (templateId: string) => { setLoadingWorkflowId(templateId); setError(null); try { const response = await fetch("/api/quickstart", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ templateId, contentLevel: "full", }), }); const result = await response.json(); if (!result.success) { throw new Error(result.error || "Failed to load template"); } if (result.workflow) { onWorkflowSelected(result.workflow); } } catch (err) { console.error("Error loading preset:", err); setError(err instanceof Error ? err.message : "Failed to load template"); } finally { setLoadingWorkflowId(null); } }, [onWorkflowSelected] ); const handleCommunitySelect = useCallback( async (workflowId: string) => { setLoadingWorkflowId(workflowId); setError(null); try { const response = await fetch(`/api/community-workflows/${workflowId}`); const result = await response.json(); if (!result.success) { throw new Error(result.error || "Failed to load workflow"); } if (result.workflow) { onWorkflowSelected(result.workflow); } } catch (err) { console.error("Error loading community workflow:", err); setError(err instanceof Error ? err.message : "Failed to load workflow"); } finally { setLoadingWorkflowId(null); } }, [onWorkflowSelected] ); const isLoading = loadingWorkflowId !== null; return (
{/* Header */}

Template Explorer

{/* Content - Sidebar + Main Grid */}
{/* Sidebar */}
{/* Search Input */}
setSearchQuery(e.target.value)} placeholder="Search templates..." className="w-full pl-8 pr-3 py-2 text-sm bg-neutral-700/50 border border-neutral-600 rounded-lg text-neutral-200 placeholder-neutral-500 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
{/* Category Filters */}

Category

{CATEGORY_OPTIONS.map((option) => ( ))}
{/* Tags Section */}

Tags

{availableTags.map((tag) => ( ))}
{/* Clear Filters */} {hasActiveFilters && ( )}
{/* Main Content Area */}
{/* Empty State */} {hasNoResults && hasActiveFilters && (

No templates match your filters

Try adjusting your search or filters

)} {/* Quick Start Templates */} {filteredPresets.length > 0 && (

Quick Start

{filteredPresets.map((preset) => ( handlePresetSelect(preset.id)} disabled={isLoading} /> ))}
)} {/* Divider */} {filteredPresets.length > 0 && (filteredCommunity.length > 0 || (isLoadingList && categoryFilter !== "community")) && (
)} {/* Community Workflows */} {(filteredCommunity.length > 0 || (isLoadingList && (categoryFilter === "all" || categoryFilter === "community"))) && (

Community Workflows

{isLoadingList ? (
) : (
{filteredCommunity.map((workflow) => ( ))}
)} {/* Discord CTA */}

Want to share your workflow?{" "} Join our Discord {" "} to submit it to the community templates.

)} {/* Error */} {error && (

{error}

)}
); }