loop-benchmarking

Controlled experiments across agentic coding configurations. Same task, one variable, what actually works.
git clone https://git.shiptheloop.com/loop-benchmarking.git
Log | Files | Refs | README

Insights.tsx (5296B)


      1 import { useState, useMemo } from "react";
      2 import type { Run } from "../lib/types";
      3 import { computeMainEffects, computeInteraction, groupIntoCells } from "../lib/analysis";
      4 import { modelSortOrder } from "../lib/colors";
      5 import TornadoChart from "./TornadoChart";
      6 import Heatmap from "./Heatmap";
      7 import ModelSelector from "./ModelSelector";
      8 
      9 interface InsightsProps {
     10   runs: Run[];
     11 }
     12 
     13 const METRICS = [
     14   { key: "score", label: "Outcome" },
     15   { key: "gameplay", label: "Gameplay" },
     16   { key: "sonarqube", label: "Code Quality" },
     17   { key: "quality", label: "Build Quality" },
     18   { key: "structural", label: "Structural" },
     19   { key: "code_quality", label: "Code Analysis" },
     20   { key: "transcript", label: "Agent Eff." },
     21   { key: "cost", label: "Cost" },
     22   { key: "turns", label: "Turns" },
     23   { key: "wall_time", label: "Time" },
     24 ];
     25 
     26 export default function Insights({ runs }: InsightsProps) {
     27   const [metric, setMetric] = useState("score");
     28   const [axisA, setAxisA] = useState("");
     29   const [axisB, setAxisB] = useState("");
     30 
     31   // Model filter
     32   const allModels = useMemo(() => {
     33     const models = new Set<string>();
     34     for (const run of runs) {
     35       models.add(run.meta.actual_model || run.meta.model);
     36     }
     37     return [...models].sort((a, b) => modelSortOrder(a) - modelSortOrder(b) || a.localeCompare(b));
     38   }, [runs]);
     39 
     40   const [selectedModels, setSelectedModels] = useState<Set<string>>(() => new Set(allModels));
     41 
     42   const filteredRuns = useMemo(
     43     () => runs.filter((r) => selectedModels.has(r.meta.actual_model || r.meta.model)),
     44     [runs, selectedModels]
     45   );
     46 
     47   const effects = useMemo(
     48     () => computeMainEffects(filteredRuns, metric),
     49     [filteredRuns, metric]
     50   );
     51 
     52   const filteredCells = useMemo(() => groupIntoCells(filteredRuns), [filteredRuns]);
     53 
     54   // Auto-pick top 2 axes for interaction if not selected
     55   const topAxes = useMemo(() => effects.slice(0, 6).map((e) => e.axis), [effects]);
     56 
     57   const interaction = useMemo(() => {
     58     const a = axisA || topAxes[0] || "";
     59     const b = axisB || topAxes[1] || "";
     60     if (!a || !b || a === b) return null;
     61     return computeInteraction(filteredRuns, a, b, metric);
     62   }, [filteredRuns, axisA, axisB, metric, topAxes]);
     63 
     64   return (
     65     <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
     66       {/* Overall sample size subtitle */}
     67       <div style={{ fontSize: "10px", fontFamily: "'JetBrains Mono', monospace", color: "var(--text-muted, hsl(213 14% 65%))" }}>
     68         (n={filteredRuns.length} runs across {filteredCells.length} cells)
     69       </div>
     70 
     71       {/* Metric selector */}
     72       <div style={{ display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" }}>
     73         <span style={{ fontSize: "0.8rem", color: "var(--text-muted)" }}>
     74           Metric:
     75         </span>
     76         {METRICS.map((m) => (
     77           <button
     78             key={m.key}
     79             onClick={() => setMetric(m.key)}
     80             style={{
     81               padding: "4px 12px",
     82               borderRadius: "4px",
     83               border:
     84                 metric === m.key
     85                   ? "1px solid var(--accent)"
     86                   : "1px solid var(--border)",
     87               background:
     88                 metric === m.key ? "rgba(99, 102, 241, 0.15)" : "transparent",
     89               color: metric === m.key ? "var(--accent)" : "var(--text-muted)",
     90               cursor: "pointer",
     91               fontSize: "0.8rem",
     92             }}
     93           >
     94             {m.label}
     95           </button>
     96         ))}
     97       </div>
     98 
     99       {/* Model filter */}
    100       <div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
    101         <span style={{ fontSize: "0.8rem", color: "var(--text-muted)" }}>
    102           Models:
    103         </span>
    104         <ModelSelector
    105           allModels={allModels}
    106           selectedModels={selectedModels}
    107           onChange={setSelectedModels}
    108         />
    109       </div>
    110 
    111       {/* Tornado chart */}
    112       <TornadoChart effects={effects} metric={metric} totalRuns={filteredRuns.length} totalCells={filteredCells.length} runs={filteredRuns} />
    113 
    114       {/* Interaction explorer */}
    115       <div className="card">
    116         <h3 style={{ marginBottom: "12px" }}>Interaction Explorer</h3>
    117         <div style={{ display: "flex", gap: "12px", marginBottom: "16px" }}>
    118           <div className="filter-group">
    119             <label>Axis A</label>
    120             <select
    121               value={axisA || topAxes[0] || ""}
    122               onChange={(e) => setAxisA(e.target.value)}
    123             >
    124               {topAxes.map((a) => (
    125                 <option key={a} value={a}>
    126                   {a}
    127                 </option>
    128               ))}
    129             </select>
    130           </div>
    131           <div className="filter-group">
    132             <label>Axis B</label>
    133             <select
    134               value={axisB || topAxes[1] || ""}
    135               onChange={(e) => setAxisB(e.target.value)}
    136             >
    137               {topAxes
    138                 .filter((a) => a !== (axisA || topAxes[0]))
    139                 .map((a) => (
    140                   <option key={a} value={a}>
    141                     {a}
    142                   </option>
    143                 ))}
    144             </select>
    145           </div>
    146         </div>
    147 
    148         {interaction && <Heatmap data={interaction} metric={metric} totalRuns={filteredRuns.length} totalCells={filteredCells.length} />}
    149       </div>
    150     </div>
    151   );
    152 }

Impressum · Datenschutz