import { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import api from "@/lib/api"; import ActivationTimelineHeatmap from "@/components/ActivationTimelineHeatmap"; import SaeFeatureDriftChart from "@/components/SaeFeatureDriftChart"; import HallucinationRiskTimeline from "@/components/HallucinationRiskTimeline"; import PatchMatrix from "@/components/PatchMatrix"; import CircuitQueryBox from "@/components/CircuitQueryBox"; import StepList from "@/components/StepList"; import ReproSnippet from "@/components/ReproSnippet"; import VizPanel from "@/components/VizPanel"; import LoadingProgress from "@/components/LoadingProgress"; import StatusDot from "@/components/StatusDot"; import { Wand2, Loader2 } from "lucide-react"; import { toast } from "sonner"; export default function RunAnalysis({ presetRun = null }) { const params = useParams(); const navigate = useNavigate(); const id = params.id || presetRun?.id; const [run, setRun] = useState(presetRun); const [activeStep, setActiveStep] = useState(1); const [pickedFeatureId, setPickedFeatureId] = useState(null); const [patchLayer, setPatchLayer] = useState(12); const [patchInspector, setPatchInspector] = useState(null); const [matrixLoading, setMatrixLoading] = useState(false); const timer = useRef(null); // poll while running useEffect(() => { if (!id || presetRun) return; let alive = true; async function tick() { try { const r = await api.getRun(id); if (!alive) return; setRun(r); if (r.status !== "done" && r.status !== "error") { timer.current = setTimeout(tick, 2000); } } catch { timer.current = setTimeout(tick, 4000); } } tick(); return () => { alive = false; if (timer.current) clearTimeout(timer.current); }; }, [id, presetRun]); const trajectoryReady = run?.status === "done"; const steps = run?.steps || []; const timelines = run?.feature_timelines || []; const patchMatrix = run?.patch_matrix || []; const filteredTimelines = useMemo(() => { if (!pickedFeatureId) return timelines; return timelines.filter((t) => t.feature_id === pickedFeatureId).concat(timelines.filter((t) => t.feature_id !== pickedFeatureId)); }, [timelines, pickedFeatureId]); async function runMatrix() { setMatrixLoading(true); try { const res = await api.patchMatrix(id, { layers: [6, 12, 18] }); setRun((r) => ({ ...r, patch_matrix: res.patch_matrix })); toast.success(`patch matrix swept · ${res.patch_matrix.length} cells`); } catch (e) { toast.error(`patch matrix failed: ${e.message}`); } finally { setMatrixLoading(false); } } async function runPatch(cell) { // If viewing a pre-computed experiment, look up the cell in the cached matrix. if (presetRun) { const cached = (run.patch_matrix || []).find( (m) => m.source_step === cell.source && m.target_step === cell.target && m.patch_layer === cell.layer, ); if (cached) { setPatchInspector({ source_step: cell.source, target_step: cell.target, patch_layer: cell.layer, kl: cached.kl, significant: cached.significant, token_changes: cached.top_token_change ? [cached.top_token_change] : [], interpretation: `Patching layer ${cell.layer} activations from step ${cell.source} into step ${cell.target} ` + `${cached.significant ? "significantly changes" : "does not significantly change"} ` + `the output distribution (KL=${cached.kl.toFixed(4)}). ` + `${cached.significant ? "Causal link between these steps confirmed." : "No strong causal link at this layer."} ` + `(Precomputed from experiment cache.)`, }); return; } } try { const res = await api.patch(id, { source_step: cell.source, target_step: cell.target, patch_layer: cell.layer, }); setPatchInspector({ ...cell, ...res }); } catch (e) { toast.error(`patch failed: ${e.message}`); } } if (!run) { return (