import { useState, useCallback, useEffect, useMemo } from "react"; import type { DatasetInfo, Preset, PanelNav, OverviewData, ExampleDetailData, RlmIterDetail, } from "./types"; import { api } from "./api"; export function useAppState() { const [datasets, setDatasets] = useState([]); const [presets, setPresets] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState>({}); // Dual panel navigation const [panelA, setPanelA] = useState(null); const [panelB, setPanelB] = useState(null); const [comparisonMode, setComparisonMode] = useState(false); // Data caches const [overviewCache, setOverviewCache] = useState>({}); const [exampleDetailCache, setExampleDetailCache] = useState>({}); const [iterDetailCache, setIterDetailCache] = useState>({}); // Load presets on mount useEffect(() => { api.listPresets().then((data) => setPresets(data as unknown as Preset[])).catch(() => {}); }, []); const activeDatasets = useMemo(() => datasets.filter((d) => d.active), [datasets]); // Data fetching helpers const fetchOverview = useCallback(async (dsId: string) => { if (overviewCache[dsId]) return overviewCache[dsId]; const data = (await api.getOverview(dsId)) as unknown as OverviewData; setOverviewCache((prev) => ({ ...prev, [dsId]: data })); return data; }, [overviewCache]); const fetchExampleDetail = useCallback( async (dsId: string, exampleIdx: number) => { const key = `${dsId}:${exampleIdx}`; if (exampleDetailCache[key]) return exampleDetailCache[key]; const data = (await api.getExampleDetail(dsId, exampleIdx)) as unknown as ExampleDetailData; setExampleDetailCache((prev) => ({ ...prev, [key]: data })); return data; }, [exampleDetailCache] ); const fetchIterDetail = useCallback( async (dsId: string, exampleIdx: number, rlmIter: number) => { const key = `${dsId}:${exampleIdx}:${rlmIter}`; if (iterDetailCache[key]) return iterDetailCache[key]; const data = (await api.getIterDetail(dsId, exampleIdx, rlmIter)) as unknown as RlmIterDetail; setIterDetailCache((prev) => ({ ...prev, [key]: data })); return data; }, [iterDetailCache] ); // Dataset operations const addDataset = useCallback( async (repo: string, config?: string, split?: string, presetId?: string, presetName?: string) => { setLoading((prev) => ({ ...prev, [repo]: true })); setError(null); try { const result = await api.loadDataset(repo, config, split); const dsInfo: DatasetInfo = { id: result.id, repo: result.repo, name: result.name, config: result.config, split: result.split, metadata: result.metadata as unknown as DatasetInfo["metadata"], n_examples: result.n_examples, n_rows: result.n_rows, active: true, presetId, presetName, }; setDatasets((prev) => { if (prev.some((d) => d.id === dsInfo.id)) return prev; return [...prev, dsInfo]; }); // Auto-set panel A if not set setPanelA((prev) => prev || { datasetId: dsInfo.id, level: 1 }); } catch (e: unknown) { setError(e instanceof Error ? e.message : "Failed to load dataset"); } finally { setLoading((prev) => ({ ...prev, [repo]: false })); } }, [] ); const removeDataset = useCallback(async (id: string) => { await api.unloadDataset(id).catch(() => {}); setDatasets((prev) => prev.filter((d) => d.id !== id)); setPanelA((prev) => (prev?.datasetId === id ? null : prev)); setPanelB((prev) => (prev?.datasetId === id ? null : prev)); }, []); const toggleDataset = useCallback((id: string) => { setDatasets((prev) => prev.map((d) => (d.id === id ? { ...d, active: !d.active } : d))); }, []); // Navigation const navigatePanel = useCallback( (panel: "A" | "B", nav: PanelNav) => { if (panel === "A") setPanelA(nav); else setPanelB(nav); }, [] ); const goUp = useCallback((panel: "A" | "B") => { const setter = panel === "A" ? setPanelA : setPanelB; setter((prev) => { if (!prev) return prev; if (prev.level === 2) return { ...prev, level: 1, exampleIdx: undefined }; return prev; }); }, []); const updateDatasetPresetName = useCallback((dsId: string, name: string) => { setDatasets((prev) => prev.map((d) => (d.id === dsId ? { ...d, presetName: name } : d))); }, []); const clearDatasetPreset = useCallback((dsId: string) => { setDatasets((prev) => prev.map((d) => (d.id === dsId ? { ...d, presetId: undefined, presetName: undefined } : d)) ); }, []); const toggleComparison = useCallback(() => { setComparisonMode((prev) => { if (!prev && panelA) { // Entering comparison: initialize panel B same as A setPanelB({ ...panelA }); } else if (prev) { setPanelB(null); } return !prev; }); }, [panelA]); return { datasets, presets, setPresets, error, setError, loading, activeDatasets, panelA, panelB, comparisonMode, addDataset, removeDataset, toggleDataset, updateDatasetPresetName, clearDatasetPreset, navigatePanel, goUp, toggleComparison, fetchOverview, fetchExampleDetail, fetchIterDetail, overviewCache, exampleDetailCache, iterDetailCache, }; }