import { useState } from "react"; import { CheckCircle2, Layout, Save, FolderOpen, FilePlus, Download, Upload, PlayCircle, Flag, Trash2, BookTemplate, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { useGraphStore, type AgentNodeData, type GraphTemplate } from "@/stores/graphStore"; import type { GraphListItem } from "@/types/graph"; // ── Predefined graph templates ────────────────────────────────────────── const GRAPH_TEMPLATES: GraphTemplate[] = [ { name: "Linear Pipeline", description: "Sequential: Researcher -> Writer -> Reviewer", agents: [ { agent_id: "researcher", display_name: "Researcher", persona: "Expert researcher", description: "Gathers information", tools: ["web_search"] }, { agent_id: "writer", display_name: "Writer", persona: "Technical writer", description: "Writes content", tools: [] }, { agent_id: "reviewer", display_name: "Reviewer", persona: "Quality reviewer", description: "Reviews output", tools: [] }, ], edges: [ { source: "researcher", target: "writer", weight: 1.0, condition: null }, { source: "writer", target: "reviewer", weight: 1.0, condition: null }, ], start_node: "researcher", end_node: "reviewer", }, { name: "Fan-Out / Fan-In", description: "Planner fans out to parallel workers, Aggregator collects results", agents: [ { agent_id: "planner", display_name: "Planner", persona: "Task planner", description: "Breaks task into subtasks", tools: [] }, { agent_id: "worker_a", display_name: "Worker A", persona: "Specialist A", description: "Handles subtask A", tools: [] }, { agent_id: "worker_b", display_name: "Worker B", persona: "Specialist B", description: "Handles subtask B", tools: [] }, { agent_id: "aggregator", display_name: "Aggregator", persona: "Synthesizer", description: "Combines results", tools: [] }, ], edges: [ { source: "planner", target: "worker_a", weight: 1.0, condition: null }, { source: "planner", target: "worker_b", weight: 1.0, condition: null }, { source: "worker_a", target: "aggregator", weight: 1.0, condition: null }, { source: "worker_b", target: "aggregator", weight: 1.0, condition: null }, ], start_node: "planner", end_node: "aggregator", }, { name: "Conditional Branching", description: "Writer outputs, conditional edges route to Editor or Fixer based on response", agents: [ { agent_id: "writer", display_name: "Writer", persona: "Content writer", description: "Generates draft", tools: [] }, { agent_id: "editor", display_name: "Editor", persona: "Editor", description: "Polishes good drafts", tools: [] }, { agent_id: "fixer", display_name: "Fixer", persona: "Error corrector", description: "Fixes problematic drafts", tools: [] }, { agent_id: "publisher", display_name: "Publisher", persona: "Publisher", description: "Publishes final version", tools: [] }, ], edges: [ { source: "writer", target: "editor", weight: 0.9, condition: "contains:APPROVED" }, { source: "writer", target: "fixer", weight: 0.7, condition: "source_failed" }, { source: "editor", target: "publisher", weight: 1.0, condition: null }, { source: "fixer", target: "publisher", weight: 1.0, condition: null }, ], start_node: "writer", end_node: "publisher", }, { name: "Review Loop", description: "Coder writes code, Reviewer checks quality; conditional edges route back or forward", agents: [ { agent_id: "planner", display_name: "Planner", persona: "Architect", description: "Plans the approach", tools: [] }, { agent_id: "coder", display_name: "Coder", persona: "Software developer", description: "Writes code", tools: ["code_interpreter"] }, { agent_id: "reviewer", display_name: "Reviewer", persona: "Code reviewer", description: "Reviews code quality", tools: [] }, { agent_id: "finalizer", display_name: "Finalizer", persona: "Integrator", description: "Delivers final result", tools: [] }, ], edges: [ { source: "planner", target: "coder", weight: 1.0, condition: null }, { source: "coder", target: "reviewer", weight: 1.0, condition: null }, { source: "reviewer", target: "finalizer", weight: 0.9, condition: "contains:LGTM" }, { source: "reviewer", target: "coder", weight: 0.5, condition: "contains:REVISE" }, ], start_node: "planner", end_node: "finalizer", }, { name: "Diamond with Weights", description: "Dispatcher -> two parallel paths with different weights -> Merger", agents: [ { agent_id: "start", display_name: "Dispatcher", persona: "Task dispatcher", description: "Dispatches task", tools: [] }, { agent_id: "fast_path", display_name: "Fast Path", persona: "Quick processor", description: "Fast but less thorough", tools: [] }, { agent_id: "deep_path", display_name: "Deep Path", persona: "Deep analyzer", description: "Slow but thorough", tools: ["web_search"] }, { agent_id: "merger", display_name: "Merger", persona: "Result merger", description: "Merges results", tools: [] }, ], edges: [ { source: "start", target: "fast_path", weight: 0.6, condition: null }, { source: "start", target: "deep_path", weight: 1.0, condition: null }, { source: "fast_path", target: "merger", weight: 0.6, condition: null }, { source: "deep_path", target: "merger", weight: 1.0, condition: null }, ], start_node: "start", end_node: "merger", }, ]; interface GraphToolbarProps { onValidate: () => void; onRun: () => void; isRunning: boolean; } export function GraphToolbar({ onValidate, onRun, isRunning }: GraphToolbarProps) { const { graphName, setGraphName, autoLayout, saveGraph, loadGraph, newGraph, loadTemplate, fetchSavedGraphs, savedGraphs, nodes, validationErrors, validationWarnings, setStartNode, setEndNode, startNode, endNode, toGraphRequest, } = useGraphStore(); const [saving, setSaving] = useState(false); const [loadOpen, setLoadOpen] = useState(false); const [startEndOpen, setStartEndOpen] = useState(false); const [templatesOpen, setTemplatesOpen] = useState(false); const handleSave = async () => { setSaving(true); try { await saveGraph(); } catch { // handle error } finally { setSaving(false); } }; const handleLoadOpen = async () => { await fetchSavedGraphs(); setLoadOpen(true); }; const handleLoad = async (graph: GraphListItem) => { await loadGraph(graph.graph_id); setLoadOpen(false); }; const handleExport = () => { const data = toGraphRequest(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${graphName.replace(/\s+/g, "-").toLowerCase()}.json`; a.click(); URL.revokeObjectURL(url); }; const handleImport = () => { const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; input.onchange = async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; const text = await file.text(); try { const data = JSON.parse(text); if (data.name) setGraphName(data.name); } catch { // invalid JSON } }; input.click(); }; const handleLoadTemplate = (template: GraphTemplate) => { loadTemplate(template); setTemplatesOpen(false); }; return ( <>