"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AnimatePresence, motion } from "framer-motion"; import { FileJson, Loader2, Save, X } from "lucide-react"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import { Textarea } from "@/components/ui/Textarea"; import { CompactTreeNavigator, type CompactNavigatorFocusMode } from "@/components/tree-visualizer/CompactTreeNavigator"; import { TreeCanvas, type TreeCanvasFocusRequest } from "@/components/tree-visualizer/TreeCanvas"; import { TreeCommandBar } from "@/components/tree-visualizer/TreeCommandBar"; import { TreeLibraryRail } from "@/components/tree-visualizer/TreeLibraryRail"; import { CreateTreeDialog } from "@/components/tree-visualizer/CreateTreeDialog"; import { ModelStreamPanel } from "@/components/tree-visualizer/ModelStreamPanel"; import { NodeInspector } from "@/components/tree-visualizer/NodeInspector"; import type { TreeNodeViewMode } from "@/components/tree-visualizer/viewMode"; import { createTreeFromRaw, deleteTree, deleteTrees, fetchTree, fetchTrees, updateTree, validateTreePayload } from "@/lib/api"; import { getTreeNavigationState } from "@/lib/tree/navigation"; import { parseTrajectoryText } from "@/lib/tree/parser"; import { visibleNodeIdsForStep } from "@/lib/tree/playback"; import type { TrajectoryTree, TrajectoryTreeNode, ValidationResult } from "@/lib/tree/types"; import { cn } from "@/lib/utils"; const defaultRailWidth = 300; const defaultInspectorWidth = 420; const railWidthKey = "tree-visualizer.layout.railWidth"; const inspectorWidthKey = "tree-visualizer.layout.inspectorWidth"; const nodeViewModeKey = "tree-visualizer.nodeViewMode"; function clamp(value: number, min: number, max: number) { return Math.min(max, Math.max(min, value)); } function storedWidth(key: string, fallback: number, min: number, max: number) { if (typeof window === "undefined") return fallback; const value = Number(window.localStorage.getItem(key)); return Number.isFinite(value) ? clamp(value, min, max) : fallback; } function storedNodeViewMode() { if (typeof window === "undefined") return "full" as const; return window.localStorage.getItem(nodeViewModeKey) === "atom" ? "atom" : "full"; } function stableTreeJson(tree: TrajectoryTree | null) { if (!tree) return ""; return JSON.stringify({ title: tree.title, raw_text: tree.raw_text, nodes: tree.nodes }); } function safeFilePart(value: string) { return value .toLowerCase() .replace(/[^a-z0-9_-]+/g, "_") .replace(/^_+|_+$/g, "") .slice(0, 80); } function downloadText(filename: string, content: string, type = "text/plain;charset=utf-8") { const blob = new Blob([content], { type }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); } function GeneratedTreeSaveDialog({ open, tree, isSaving, error, onClose, onSave }: { open: boolean; tree: TrajectoryTree | null; isSaving: boolean; error: Error | null; onClose: () => void; onSave: (title: string) => void; }) { const [title, setTitle] = useState(""); useEffect(() => { if (!open) return; setTitle(tree?.title || "Model Stream"); }, [open, tree?.title]); const preview = useMemo( () => JSON.stringify( { title: title.trim() || tree?.title || "Model Stream", rawText: tree?.raw_text ?? "", nodes: tree?.nodes ?? [] }, null, 2 ), [title, tree] ); return ( {open && tree && (

Save generated tree

{tree.nodes.length} generated nodes

{error && (
{error.message}
)}