"use client"; import { Suspense, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { ArrowLeft, FileJson, LayoutGrid, Play, Save, Trash2, X, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { PipelineCanvas } from "@/components/canvas/pipeline-canvas"; import { api, type StoredFlow } from "@/lib/api"; import type { PipelineDefinition } from "@/lib/pipeline-graph"; type Tab = "canvas" | "json"; const EMPTY: PipelineDefinition = { nodes: [], connections: {} }; export default function PipelineDetailPage() { return ( Loading…}> ); } function PipelineDetail() { const router = useRouter(); const params = useSearchParams(); const id = params.get("id") ?? ""; const [flow, setFlow] = useState(null); const [name, setName] = useState(""); const [loaded, setLoaded] = useState(false); const [nodeCount, setNodeCount] = useState(0); const defRef = useRef(EMPTY); const [jsonDraft, setJsonDraft] = useState("{}"); const [tab, setTab] = useState("canvas"); const [showRun, setShowRun] = useState(false); const [input, setInput] = useState("[{}]"); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [running, setRunning] = useState(false); const [lastExecId, setLastExecId] = useState(null); async function load() { if (!id) return; setLoaded(false); try { const f = await api.getFlow(id); const def = (f.definition as PipelineDefinition | null) ?? EMPTY; setFlow(f); setName(f.name); defRef.current = def; setNodeCount(def.nodes?.length ?? 0); setJsonDraft(JSON.stringify(def, null, 2)); setError(null); } catch (e) { setError(e instanceof Error ? e.message : "load failed"); } finally { setLoaded(true); } } useEffect(() => { load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); function switchTab(next: Tab) { if (next === "json") { setJsonDraft(JSON.stringify({ ...defRef.current, name }, null, 2)); setTab(next); return; } try { const parsed = JSON.parse(jsonDraft) as PipelineDefinition; defRef.current = parsed; setNodeCount(parsed.nodes?.length ?? 0); if (parsed.name) setName(parsed.name); setError(null); } catch (e) { setError(`JSON parse failed: ${(e as Error).message}`); return; } setTab(next); } async function onSave() { setSaving(true); setError(null); try { const toSave: PipelineDefinition = tab === "json" ? JSON.parse(jsonDraft) : defRef.current; await api.updateFlow(id, { name, definition: { ...toSave, name } }); await load(); } catch (e) { setError(e instanceof Error ? e.message : "save failed"); } finally { setSaving(false); } } async function onRun() { setRunning(true); setError(null); try { let parsedInput: unknown[] | undefined; if (input.trim()) { const v = JSON.parse(input); if (!Array.isArray(v)) throw new Error("Input must be a JSON array"); parsedInput = v; } const res = await api.executeWorkflow({ workflow_id: id, input: parsedInput, }); setLastExecId(res.execution_id); router.push( `/executions/view/?id=${encodeURIComponent(res.execution_id)}` ); } catch (e) { setError(e instanceof Error ? e.message : "run failed"); } finally { setRunning(false); } } async function onDelete() { if (!confirm("Delete this pipeline? This cannot be undone.")) return; try { await api.deleteFlow(id); router.push("/"); } catch (e) { setError(e instanceof Error ? e.message : "delete failed"); } } if (!id) { return (
Missing id query param.
); } return (
setName(e.target.value)} placeholder="Pipeline name" className="h-8 max-w-[280px] text-sm" /> {nodeCount} nodes {flow && ( {flow.id} )}
{error && (
{error}
)}
{tab === "canvas" ? ( loaded ? ( { defRef.current = d; setNodeCount(d.nodes?.length ?? 0); }} fullBleed /> ) : (
Loading pipeline…
) ) : (