"use client"; import { useEffect, useMemo, useState } from "react"; import { useMutation } from "@tanstack/react-query"; import { AnimatePresence, motion } from "framer-motion"; import { AlertTriangle, CheckCircle2, Columns3, FileJson, FileUp, Loader2, X } from "lucide-react"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import { Select } from "@/components/ui/Select"; import { Textarea } from "@/components/ui/Textarea"; import { Badge } from "@/components/ui/Badge"; import { TreeValidationPanel } from "@/components/tree-visualizer/TreeValidationPanel"; import { createTreeFromRaw, createTreesFromJsonl, parseTrajectory, previewBulkTreeImport } from "@/lib/api"; import type { BulkImportPreview, ParseResult, TrajectoryTree } from "@/lib/tree/types"; import { cn } from "@/lib/utils"; interface CreateTreeDialogProps { open: boolean; onClose: () => void; onCreated: (tree: TrajectoryTree) => void; } type ImportMode = "single" | "bulk"; const exampleText = `<|trajectory|>{ "v": 1, "id": "n1", "parent": null, "kind": "understand", "goal": "Restate the problem as a counting probability task", "skill": ["probability", "combinatorics"], "inputs": { "problem_refs": ["p0"], "node_refs": [] }, "state_delta": { "claims": [], "equations": [], "constraints": [], "definitions": [] }, "retrieval": { "needed": false, "source": "none", "queries": [], "evidence_refs": [] }, "tool": null, "verification": { "kind": "none", "status": "pending", "notes": "" }, "branch": { "mode": "main", "label": "direct" } }<|end_of_trajectory|> We need to reinterpret the question as a finite counting problem before choosing a probability model.`; const exampleJsonl = JSON.stringify({ name: "Counting example", trajectory: exampleText }) + "\n"; function columnOptions(preview: BulkImportPreview | null) { return preview?.columns ?? []; } export function CreateTreeDialog({ open, onClose, onCreated }: CreateTreeDialogProps) { const [mode, setMode] = useState("single"); const [title, setTitle] = useState(""); const [rawText, setRawText] = useState(""); const [parseResult, setParseResult] = useState(null); const [jsonlText, setJsonlText] = useState(""); const [bulkPreview, setBulkPreview] = useState(null); const [trajectoryColumn, setTrajectoryColumn] = useState(""); const [titleColumn, setTitleColumn] = useState(""); useEffect(() => { if (!open) return; setParseResult(null); setBulkPreview(null); }, [open]); const parseMutation = useMutation({ mutationFn: () => parseTrajectory(rawText), onSuccess: ({ parse }) => { setParseResult(parse); if (!title.trim()) setTitle(parse.title); } }); const createMutation = useMutation({ mutationFn: () => createTreeFromRaw({ title: title.trim() || parseResult?.title, rawText }), onSuccess: ({ tree }) => { onCreated(tree); setRawText(""); setTitle(""); setParseResult(null); onClose(); } }); const bulkPreviewMutation = useMutation({ mutationFn: () => previewBulkTreeImport({ rawJsonl: jsonlText, trajectoryColumn: trajectoryColumn || undefined, titleColumn: titleColumn || undefined }), onSuccess: ({ preview }) => { setBulkPreview(preview); setTrajectoryColumn(preview.trajectoryColumn); setTitleColumn(preview.titleColumn); } }); const bulkCreateMutation = useMutation({ mutationFn: () => createTreesFromJsonl({ rawJsonl: jsonlText, trajectoryColumn, titleColumn: titleColumn || undefined }), onSuccess: ({ tree }) => { if (tree) onCreated(tree); setJsonlText(""); setBulkPreview(null); setTrajectoryColumn(""); setTitleColumn(""); onClose(); } }); const activeError = parseMutation.error || createMutation.error || bulkPreviewMutation.error || bulkCreateMutation.error; const bulkRows = useMemo(() => bulkPreview?.rows.slice(0, 80) ?? [], [bulkPreview?.rows]); const loadJsonlFile = async (file: File | undefined) => { if (!file) return; setJsonlText(await file.text()); setBulkPreview(null); setTrajectoryColumn(""); setTitleColumn(""); }; return ( {open && (

Create Tree

Paste one trace or bulk import JSONL rows with trajectory columns.

{(["single", "bulk"] as const).map((item) => ( ))}
{mode === "single" ? (
setTitle(event.target.value)} placeholder="Optional title" />