Spaces:
Running
Running
| import { useEffect, useState, useMemo } from "react"; | |
| // ββ Types ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| interface Row { | |
| query_id: string; | |
| rationale: string; | |
| selected_indices: number[]; | |
| k_requested: number; | |
| k_effective: number; | |
| excerpt: string; | |
| new_trajectory: string; | |
| direct_answer: boolean; | |
| tool_call_counts: Record<string, number>; | |
| total_tool_calls: number; | |
| status: string; | |
| } | |
| // ββ Block parsers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| type BlockType = "reasoning" | "tool_call" | "tool_result" | "final_answer" | "unknown"; | |
| interface Block { type: BlockType; label?: string; content: string } | |
| const BLOCK_STYLES: Record<BlockType, { border: string; labelColor: string; bg: string }> = { | |
| reasoning: { border: "border-purple-700", labelColor: "text-purple-400", bg: "bg-purple-950/30" }, | |
| tool_call: { border: "border-blue-700", labelColor: "text-blue-400", bg: "bg-blue-950/30" }, | |
| tool_result: { border: "border-gray-600", labelColor: "text-gray-400", bg: "bg-gray-800/30" }, | |
| final_answer: { border: "border-green-700", labelColor: "text-green-400", bg: "bg-green-950/30" }, | |
| unknown: { border: "border-gray-700", labelColor: "text-gray-500", bg: "" }, | |
| }; | |
| /** | |
| * Parse the `excerpt` column (reference trajectory format): | |
| * [Reasoning]: text | |
| * [Tool call] tool_name\narguments:\n{...} | |
| * [Tool result]:\n[...] | |
| */ | |
| function parseExcerpt(text: string): Block[] { | |
| if (!text) return []; | |
| const blocks: Block[] = []; | |
| const parts = text.split(/\n\n(?=\[)/); | |
| for (const part of parts) { | |
| const p = part.trim(); | |
| if (p.startsWith("[Reasoning]:")) { | |
| blocks.push({ type: "reasoning", label: "Reasoning", content: p.slice("[Reasoning]:".length).trim() }); | |
| } else if (p.startsWith("[Tool call]")) { | |
| blocks.push({ type: "tool_call", label: "Tool Call", content: p.slice("[Tool call]".length).trim() }); | |
| } else if (p.startsWith("[Tool result]:")) { | |
| blocks.push({ type: "tool_result", label: "Tool Result", content: p.slice("[Tool result]:".length).trim() }); | |
| } else if (p.startsWith("[Final answer]:")) { | |
| blocks.push({ type: "final_answer", label: "Final Answer", content: p.slice("[Final answer]:".length).trim() }); | |
| } else if (p) { | |
| blocks.push({ type: "unknown", label: "β", content: p }); | |
| } | |
| } | |
| return blocks; | |
| } | |
| /** | |
| * Parse the `new_trajectory` column (our formatted output): | |
| * [Reasoning]\ntext | |
| * [Tool Call: name]\nArguments:\n{...}\n\n[Tool Result]\n{...} | |
| * [Final Answer]\ntext | |
| * Blocks separated by \n\n---\n\n | |
| */ | |
| function parseNewTrajectory(text: string): Block[] { | |
| if (!text) return []; | |
| const blocks: Block[] = []; | |
| const parts = text.split("\n\n---\n\n"); | |
| for (const part of parts) { | |
| const p = part.trim(); | |
| if (p.startsWith("[Reasoning]\n")) { | |
| blocks.push({ type: "reasoning", label: "Reasoning", content: p.slice("[Reasoning]\n".length).trim() }); | |
| } else if (p.startsWith("[Tool Call:")) { | |
| // Split into call and result at the embedded [Tool Result] marker | |
| const resultMarker = "\n\n[Tool Result]\n"; | |
| const resultIdx = p.indexOf(resultMarker); | |
| const headerEnd = p.indexOf("]\n"); | |
| const toolName = headerEnd >= 0 ? p.slice("[Tool Call:".length, headerEnd).trim() : "unknown"; | |
| if (resultIdx >= 0) { | |
| const callContent = p.slice(0, resultIdx).replace(/^\[Tool Call:[^\]]*\]\n/, "").trim(); | |
| const resultContent = p.slice(resultIdx + resultMarker.length).trim(); | |
| blocks.push({ type: "tool_call", label: `Tool Call: ${toolName}`, content: callContent }); | |
| blocks.push({ type: "tool_result", label: "Tool Result", content: resultContent }); | |
| } else { | |
| const callContent = p.replace(/^\[Tool Call:[^\]]*\]\n/, "").trim(); | |
| blocks.push({ type: "tool_call", label: `Tool Call: ${toolName}`, content: callContent }); | |
| } | |
| } else if (p.startsWith("[Final Answer]\n")) { | |
| blocks.push({ type: "final_answer", label: "Final Answer", content: p.slice("[Final Answer]\n".length).trim() }); | |
| } else if (p) { | |
| blocks.push({ type: "unknown", label: "β", content: p }); | |
| } | |
| } | |
| return blocks; | |
| } | |
| // ββ Trajectory renderer ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function TrajectoryView({ blocks }: { blocks: Block[] }) { | |
| if (blocks.length === 0) return <div className="text-gray-500 text-xs italic">No steps.</div>; | |
| return ( | |
| <div className="space-y-2"> | |
| {blocks.map((b, i) => { | |
| const s = BLOCK_STYLES[b.type]; | |
| return ( | |
| <div key={i} className={`border-l-2 ${s.border} ${s.bg} pl-3 py-1.5 rounded-r`}> | |
| <div className={`text-[10px] font-bold uppercase tracking-widest mb-1 ${s.labelColor}`}> | |
| {b.label ?? b.type} | |
| </div> | |
| <pre className="text-xs text-gray-300 whitespace-pre-wrap font-mono leading-relaxed">{b.content}</pre> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| } | |
| // ββ Filter type ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| type FilterMode = "all" | "direct" | "searched"; | |
| // ββ Main component βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export default function SelectedToolsApp() { | |
| const [data, setData] = useState<Row[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [selectedIdx, setSelectedIdx] = useState(0); | |
| const [search, setSearch] = useState(""); | |
| const [filter, setFilter] = useState<FilterMode>("all"); | |
| useEffect(() => { | |
| setLoading(true); | |
| fetch("/api/selected-tools/") | |
| .then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }) | |
| .then((d: { rows: Row[] }) => { setData(d.rows); setLoading(false); }) | |
| .catch(e => { setError(e.message); setLoading(false); }); | |
| }, []); | |
| const filtered = useMemo(() => { | |
| let rows = data; | |
| if (filter === "direct") rows = rows.filter(r => r.direct_answer); | |
| if (filter === "searched") rows = rows.filter(r => !r.direct_answer); | |
| if (search.trim()) { | |
| const q = search.toLowerCase(); | |
| rows = rows.filter(r => r.query_id.includes(q)); | |
| } | |
| return rows; | |
| }, [data, search, filter]); | |
| const current = filtered[selectedIdx] ?? null; | |
| const excerptBlocks = useMemo(() => current ? parseExcerpt(current.excerpt) : [], [current]); | |
| const trajectoryBlocks = useMemo(() => current ? parseNewTrajectory(current.new_trajectory) : [], [current]); | |
| // Stats | |
| const directCount = data.filter(r => r.direct_answer).length; | |
| const directPct = data.length ? Math.round(100 * directCount / data.length) : 0; | |
| if (loading) return <div className="h-full flex items-center justify-center text-gray-400">Loading from HuggingFaceβ¦</div>; | |
| if (error) return <div className="h-full flex items-center justify-center text-red-400">Error: {error}</div>; | |
| return ( | |
| <div className="h-full flex overflow-hidden bg-gray-950 text-gray-100"> | |
| {/* ββ Sidebar ββββββββββββββββββββββββββββββββββββββββββββββββ */} | |
| <div className="w-64 shrink-0 flex flex-col border-r border-gray-800 bg-gray-900"> | |
| {/* Stats banner */} | |
| <div className="px-3 py-2 border-b border-gray-800 bg-gray-900/80"> | |
| <div className="text-[10px] text-gray-500 uppercase tracking-widest mb-1">Direct-answer rate</div> | |
| <div className="text-lg font-bold text-emerald-400">{directPct}%</div> | |
| <div className="text-[10px] text-gray-600">{directCount} / {data.length} no tool calls</div> | |
| </div> | |
| {/* Filter toggles */} | |
| <div className="flex gap-1 px-2 py-2 border-b border-gray-800"> | |
| {(["all", "direct", "searched"] as FilterMode[]).map(m => ( | |
| <button | |
| key={m} | |
| onClick={() => { setFilter(m); setSelectedIdx(0); }} | |
| className={`flex-1 text-[10px] py-1 rounded border transition-colors capitalize ${ | |
| filter === m | |
| ? m === "direct" ? "bg-emerald-900/60 border-emerald-600 text-emerald-300" | |
| : m === "searched" ? "bg-blue-900/60 border-blue-600 text-blue-300" | |
| : "bg-gray-700 border-gray-500 text-gray-200" | |
| : "bg-gray-800/50 border-gray-700 text-gray-500 hover:border-gray-500" | |
| }`} | |
| >{m}</button> | |
| ))} | |
| </div> | |
| {/* Search */} | |
| <div className="px-2 py-1.5 border-b border-gray-800"> | |
| <input | |
| type="text" | |
| placeholder="Search query IDβ¦" | |
| value={search} | |
| onChange={e => { setSearch(e.target.value); setSelectedIdx(0); }} | |
| className="w-full bg-gray-800 border border-gray-700 text-gray-200 text-xs rounded px-2 py-1.5 placeholder-gray-600" | |
| /> | |
| <div className="text-[10px] text-gray-600 mt-1">{filtered.length} / {data.length}</div> | |
| </div> | |
| {/* Query list */} | |
| <div className="flex-1 overflow-y-auto"> | |
| {filtered.map((row, i) => ( | |
| <button | |
| key={row.query_id} | |
| onClick={() => setSelectedIdx(i)} | |
| className={`w-full text-left px-3 py-2 border-b border-gray-800/50 text-xs transition-colors ${ | |
| selectedIdx === i | |
| ? "bg-blue-900/40 text-blue-200 border-l-2 border-l-blue-500" | |
| : "text-gray-400 hover:bg-gray-800" | |
| }`} | |
| > | |
| <div className="flex items-center justify-between"> | |
| <span className="font-medium text-gray-200">#{row.query_id}</span> | |
| {row.direct_answer | |
| ? <span className="text-[9px] px-1.5 py-0.5 rounded-full bg-emerald-900/60 text-emerald-400 border border-emerald-800">direct</span> | |
| : <span className="text-[9px] px-1.5 py-0.5 rounded-full bg-blue-900/60 text-blue-400 border border-blue-800">{row.total_tool_calls} calls</span> | |
| } | |
| </div> | |
| <div className="text-[10px] text-gray-600 mt-0.5">k={row.k_effective} selected steps</div> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| {/* ββ Main: two-column side-by-side ββββββββββββββββββββββββ */} | |
| {current ? ( | |
| <div className="flex-1 flex flex-col min-w-0 overflow-hidden"> | |
| {/* Header */} | |
| <div className="px-4 py-2 bg-gray-900/60 border-b border-gray-800 shrink-0"> | |
| <div className="flex items-center gap-3 flex-wrap"> | |
| <span className="text-sm font-medium text-gray-100">Query #{current.query_id}</span> | |
| {current.direct_answer | |
| ? <span className="text-xs px-2 py-0.5 rounded-full bg-emerald-900/50 text-emerald-300 border border-emerald-800">Direct answer</span> | |
| : <span className="text-xs px-2 py-0.5 rounded-full bg-blue-900/50 text-blue-300 border border-blue-800">{current.total_tool_calls} tool calls</span> | |
| } | |
| <span className="text-xs text-gray-500">k={current.k_effective} steps selected</span> | |
| <span className={`text-xs px-2 py-0.5 rounded-full ${current.status === "completed" ? "bg-gray-800 text-gray-400" : "bg-amber-900/50 text-amber-300"}`}> | |
| {current.status} | |
| </span> | |
| </div> | |
| {/* Selected indices */} | |
| <div className="mt-1.5 flex items-center gap-1.5 flex-wrap"> | |
| <span className="text-[10px] font-bold uppercase tracking-widest text-gray-500">Selected steps</span> | |
| {current.selected_indices.map(idx => ( | |
| <span key={idx} className="text-[10px] px-1.5 py-0.5 rounded bg-gray-800 border border-gray-700 text-gray-300 font-mono"> | |
| #{idx} | |
| </span> | |
| ))} | |
| </div> | |
| {/* Rationale */} | |
| <div className="mt-1.5 text-xs text-gray-400 leading-snug bg-gray-800/50 rounded px-2 py-1.5 border border-gray-700"> | |
| <span className="text-[10px] font-bold uppercase tracking-widest text-amber-500 mr-2">Rationale</span> | |
| {current.rationale} | |
| </div> | |
| </div> | |
| {/* Side-by-side columns */} | |
| <div className="flex-1 flex overflow-hidden min-w-0"> | |
| {/* Left: excerpt (selected tool calls from reference trajectory) */} | |
| <div className="flex-1 flex flex-col min-w-0 border-r border-gray-800 overflow-hidden"> | |
| <div className="px-3 py-1.5 bg-gray-900/40 border-b border-gray-800 shrink-0"> | |
| <span className="text-[11px] font-semibold text-amber-400 uppercase tracking-widest">Selected Tool Calls</span> | |
| <span className="text-[10px] text-gray-600 ml-2">reference trajectory Β· {current.k_effective} steps</span> | |
| </div> | |
| <div className="flex-1 overflow-y-auto p-3"> | |
| <TrajectoryView blocks={excerptBlocks} /> | |
| </div> | |
| </div> | |
| {/* Right: new trajectory */} | |
| <div className="flex-1 flex flex-col min-w-0 overflow-hidden"> | |
| <div className="px-3 py-1.5 bg-gray-900/40 border-b border-gray-800 shrink-0"> | |
| <span className="text-[11px] font-semibold text-sky-400 uppercase tracking-widest">New Trajectory</span> | |
| <span className="text-[10px] text-gray-600 ml-2">gpt-oss-120b Β· conditioned on selected steps</span> | |
| </div> | |
| <div className="flex-1 overflow-y-auto p-3"> | |
| <TrajectoryView blocks={trajectoryBlocks} /> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ) : ( | |
| <div className="flex-1 flex items-center justify-center text-gray-500">No query selected.</div> | |
| )} | |
| </div> | |
| ); | |
| } | |