import { useState } from "react"; import type { Preset } from "../types"; interface Props { store: { state: { datasets: { id: string; repo: string; name: string; split: string; n_instances: number }[]; presets: Preset[]; loading: boolean; }; loadDataset: (repo: string, split?: string) => Promise; unloadDataset: (dsId: string) => Promise; createPreset: (name: string, repo: string, split?: string) => Promise; deletePreset: (id: string) => Promise; loadPreset: (preset: Preset) => Promise; }; } export function Sidebar({ store }: Props) { const [repoInput, setRepoInput] = useState(""); const [splitInput, setSplitInput] = useState("train"); const [showAddForm, setShowAddForm] = useState(false); // Per-dataset save-as-preset state const [savingForDsId, setSavingForDsId] = useState(null); const [presetName, setPresetName] = useState(""); const [presetSearch, setPresetSearch] = useState(""); const handleLoad = async () => { const repo = repoInput.trim(); if (!repo) return; await store.loadDataset(repo, splitInput.trim() || "train"); setRepoInput(""); setShowAddForm(false); }; const handleSavePreset = async (ds: { repo: string; split: string }) => { if (!presetName.trim()) return; await store.createPreset(presetName.trim(), ds.repo, ds.split); setPresetName(""); setSavingForDsId(null); }; return (
{/* Header */}

Harbor Trace Viz

Harbor agent trajectory viewer

{/* Presets */}
Presets
{store.state.presets.length === 0 ? (
No saved presets
) : ( <> {store.state.presets.length > 6 && ( setPresetSearch(e.target.value)} placeholder="Search presets..." className="w-full px-2 py-1 mb-2 text-xs bg-gray-800 border border-gray-700 rounded text-gray-200 placeholder-gray-500 focus:border-teal-500 focus:outline-none" /> )}
{store.state.presets .filter((p) => !presetSearch || p.name.toLowerCase().includes(presetSearch.toLowerCase()) || p.repo.toLowerCase().includes(presetSearch.toLowerCase()) ) .map((p) => (
))}
)}
{/* Loaded datasets */}
Loaded ({store.state.datasets.length})
{store.state.datasets.map((ds) => (
{ds.name}
{ds.n_instances} instances
{/* Save as preset */} {/* Remove */}
{/* Inline save-as-preset form */} {savingForDsId === ds.id && (
setPresetName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleSavePreset(ds); if (e.key === "Escape") setSavingForDsId(null); }} placeholder="Preset name..." className="flex-1 px-2 py-1 text-xs bg-gray-800 border border-gray-700 rounded text-gray-200 placeholder-gray-500 focus:border-teal-500 focus:outline-none" autoFocus />
)}
))}
{/* Add repo */}
{!showAddForm ? ( ) : (
setRepoInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleLoad()} placeholder="org/repo-name" className="w-full px-2 py-1.5 text-sm bg-gray-800 border border-gray-700 rounded text-gray-200 placeholder-gray-500 focus:border-teal-500 focus:outline-none" autoFocus /> setSplitInput(e.target.value)} placeholder="Split" className="w-full px-2 py-1 text-xs bg-gray-800 border border-gray-700 rounded text-gray-200 placeholder-gray-500 focus:border-teal-500 focus:outline-none" />
)}
); }