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

HeatmapMatrix.tsx (10406B)


      1 import { useState, useMemo } from "react";
      2 import type { Run, AxisName } from "../lib/types";
      3 import { AXIS_NAMES } from "../lib/types";
      4 import { groupIntoCells } from "../lib/analysis";
      5 
      6 interface HeatmapMatrixProps {
      7   runs: Run[];
      8 }
      9 
     10 const AXIS_LABELS: Record<AxisName, string> = {
     11   model: "Model",
     12   effort: "Effort",
     13   prompt_style: "Prompt Style",
     14   language: "Language",
     15   human_language: "Human Lang",
     16   tool_read: "Tool: Read",
     17   tool_write: "Tool: Write",
     18   tool_edit: "Tool: Edit",
     19   tool_glob: "Tool: Glob",
     20   tool_grep: "Tool: Grep",
     21   linter: "Linter",
     22   playwright: "Playwright",
     23   context_file: "Context File",
     24   web_search: "Web Search",
     25   max_budget: "Max Budget",
     26   tests_provided: "Tests Provided",
     27   strategy: "Strategy",
     28   design_guidance: "Design Guidance",
     29   architecture: "Architecture",
     30   error_checking: "Error Checking",
     31   context_noise: "Context Noise",
     32   renderer: "Renderer",
     33   provider: "Provider",
     34 };
     35 
     36 interface CellData {
     37   totalScore: number;
     38   count: number;
     39 }
     40 
     41 function scoreToColor(pct: number): string {
     42   // red (0%) -> yellow (50%) -> green (100%)
     43   // Using the CSS variable HSL values directly for interpolation
     44   if (pct <= 50) {
     45     // red to yellow
     46     const t = pct / 50;
     47     const h = 355 + t * (40 - 355 + 360); // wrap around hue
     48     const s = 52 + t * (71 - 52);
     49     const l = 64 + t * (73 - 64);
     50     return `hsl(${h % 360} ${s}% ${l}%)`;
     51   } else {
     52     // yellow to green
     53     const t = (pct - 50) / 50;
     54     const h = 40 + t * (92 - 40);
     55     const s = 71 + t * (28 - 71);
     56     const l = 73 + t * (65 - 73);
     57     return `hsl(${h} ${s}% ${l}%)`;
     58   }
     59 }
     60 
     61 function cellBackground(pct: number): string {
     62   const color = scoreToColor(pct);
     63   // Use the color at low opacity for the cell background
     64   return color.replace("hsl(", "hsla(").replace(")", " / 0.18)");
     65 }
     66 
     67 export default function HeatmapMatrix({ runs }: HeatmapMatrixProps) {
     68   const [rowAxis, setRowAxis] = useState<AxisName>("model");
     69   const [colAxis, setColAxis] = useState<AxisName>("prompt_style");
     70 
     71   const { rowValues, colValues, cells } = useMemo(() => {
     72     const analysisCells = groupIntoCells(runs);
     73     const cellMap: Record<string, Record<string, CellData>> = {};
     74     const rowSet = new Set<string>();
     75     const colSet = new Set<string>();
     76 
     77     for (const cell of analysisCells) {
     78       // Skip cells where no run has a score
     79       const hasScore = cell.runs.some((r) => r.eval_results?.score != null);
     80       if (!hasScore) continue;
     81       // Use the cell's average score as a single data point
     82       const cellAvg = cell.score.avg;
     83 
     84       const rv = String(cell.meta[rowAxis]);
     85       const cv = String(cell.meta[colAxis]);
     86 
     87       rowSet.add(rv);
     88       colSet.add(cv);
     89 
     90       if (!cellMap[rv]) cellMap[rv] = {};
     91       if (!cellMap[rv][cv]) cellMap[rv][cv] = { totalScore: 0, count: 0 };
     92 
     93       cellMap[rv][cv].totalScore += cellAvg;
     94       cellMap[rv][cv].count += 1;
     95     }
     96 
     97     return {
     98       rowValues: Array.from(rowSet).sort(),
     99       colValues: Array.from(colSet).sort(),
    100       cells: cellMap,
    101     };
    102   }, [runs, rowAxis, colAxis]);
    103 
    104   const selectorStyle: React.CSSProperties = {
    105     background: "var(--surface-2)",
    106     border: "1px solid var(--border)",
    107     borderRadius: 0,
    108     color: "var(--text)",
    109     fontFamily: "var(--font-mono)",
    110     fontSize: "var(--text-ui)",
    111     padding: "6px 10px",
    112     textTransform: "uppercase" as const,
    113     letterSpacing: "0.5px",
    114   };
    115 
    116   const labelStyle: React.CSSProperties = {
    117     fontSize: "var(--text-label)",
    118     color: "var(--text-muted)",
    119     textTransform: "uppercase" as const,
    120     letterSpacing: "1px",
    121     fontWeight: 500,
    122     fontFamily: "var(--font-mono)",
    123   };
    124 
    125   return (
    126     <div
    127       style={{
    128         background: "var(--surface-1)",
    129         border: "1px solid var(--border)",
    130         borderRadius: 0,
    131         padding: "20px",
    132       }}
    133     >
    134       <h3 style={{ margin: "0 0 16px" }}>Configuration Heatmap</h3>
    135 
    136       {/* Axis selectors */}
    137       <div
    138         style={{
    139           display: "flex",
    140           gap: "24px",
    141           marginBottom: "20px",
    142           flexWrap: "wrap",
    143           alignItems: "flex-end",
    144         }}
    145       >
    146         <div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
    147           <label style={labelStyle}>Row Axis</label>
    148           <select
    149             value={rowAxis}
    150             onChange={(e) => setRowAxis(e.target.value as AxisName)}
    151             style={selectorStyle}
    152           >
    153             {AXIS_NAMES.map((axis) => (
    154               <option key={axis} value={axis}>
    155                 {AXIS_LABELS[axis]}
    156               </option>
    157             ))}
    158           </select>
    159         </div>
    160         <div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
    161           <label style={labelStyle}>Column Axis</label>
    162           <select
    163             value={colAxis}
    164             onChange={(e) => setColAxis(e.target.value as AxisName)}
    165             style={selectorStyle}
    166           >
    167             {AXIS_NAMES.map((axis) => (
    168               <option key={axis} value={axis}>
    169                 {AXIS_LABELS[axis]}
    170               </option>
    171             ))}
    172           </select>
    173         </div>
    174       </div>
    175 
    176       {/* Heatmap table */}
    177       {rowValues.length === 0 || colValues.length === 0 ? (
    178         <div
    179           style={{
    180             textAlign: "center",
    181             padding: "40px",
    182             color: "var(--text-muted)",
    183             fontFamily: "var(--font-mono)",
    184           }}
    185         >
    186           No scored cells available for this axis combination.
    187         </div>
    188       ) : (
    189         <div style={{ overflowX: "auto" }}>
    190           <table
    191             style={{
    192               borderCollapse: "collapse",
    193               width: "auto",
    194               fontFamily: "var(--font-mono)",
    195             }}
    196           >
    197             <thead>
    198               <tr>
    199                 <th
    200                   style={{
    201                     padding: "8px 12px",
    202                     fontSize: "var(--text-label)",
    203                     textTransform: "uppercase",
    204                     letterSpacing: "1px",
    205                     fontWeight: 500,
    206                     color: "var(--text-muted)",
    207                     background: "var(--surface-2)",
    208                     border: "1px solid var(--border)",
    209                     borderRadius: 0,
    210                     textAlign: "left",
    211                   }}
    212                 >
    213                   {AXIS_LABELS[rowAxis]} \ {AXIS_LABELS[colAxis]}
    214                 </th>
    215                 {colValues.map((col) => (
    216                   <th
    217                     key={col}
    218                     style={{
    219                       padding: "8px 12px",
    220                       fontSize: "var(--text-label)",
    221                       textTransform: "uppercase",
    222                       letterSpacing: "1px",
    223                       fontWeight: 500,
    224                       color: "var(--text-muted)",
    225                       background: "var(--surface-2)",
    226                       border: "1px solid var(--border)",
    227                       borderRadius: 0,
    228                       textAlign: "center",
    229                       fontFamily: "var(--font-mono)",
    230                     }}
    231                   >
    232                     {col}
    233                   </th>
    234                 ))}
    235               </tr>
    236             </thead>
    237             <tbody>
    238               {rowValues.map((row) => (
    239                 <tr key={row}>
    240                   <td
    241                     style={{
    242                       padding: "8px 12px",
    243                       fontSize: "var(--text-label)",
    244                       textTransform: "uppercase",
    245                       letterSpacing: "1px",
    246                       fontWeight: 600,
    247                       fontFamily: "var(--font-mono)",
    248                       color: "var(--text)",
    249                       background: "var(--surface-2)",
    250                       border: "1px solid var(--border)",
    251                       borderRadius: 0,
    252                       whiteSpace: "nowrap",
    253                     }}
    254                   >
    255                     {row}
    256                   </td>
    257                   {colValues.map((col) => {
    258                     const cell = cells[row]?.[col];
    259                     if (!cell) {
    260                       return (
    261                         <td
    262                           key={col}
    263                           style={{
    264                             padding: "10px 16px",
    265                             textAlign: "center",
    266                             color: "var(--text-muted)",
    267                             fontFamily: "var(--font-mono)",
    268                             fontSize: "var(--text-ui)",
    269                             border: "1px solid var(--border)",
    270                             borderRadius: 0,
    271                             background: "var(--surface-0)",
    272                           }}
    273                         >
    274                           -
    275                         </td>
    276                       );
    277                     }
    278 
    279                     const avg = cell.totalScore / cell.count;
    280                     const pct = avg * 100;
    281 
    282                     return (
    283                       <td
    284                         key={col}
    285                         style={{
    286                           padding: "10px 16px",
    287                           textAlign: "center",
    288                           fontFamily: "var(--font-mono)",
    289                           border: "1px solid var(--border)",
    290                           borderRadius: 0,
    291                           background: cellBackground(pct),
    292                         }}
    293                       >
    294                         <div
    295                           style={{
    296                             fontSize: "var(--text-ui)",
    297                             fontWeight: 700,
    298                             color: scoreToColor(pct),
    299                             lineHeight: 1.3,
    300                           }}
    301                         >
    302                           {pct.toFixed(0)}%
    303                         </div>
    304                         <div
    305                           style={{
    306                             fontSize: "var(--text-label)",
    307                             fontWeight: 400,
    308                             color: "var(--text-muted)",
    309                             lineHeight: 1.3,
    310                           }}
    311                         >
    312                           {cell.count} {cell.count === 1 ? "cell" : "cells"}
    313                         </div>
    314                       </td>
    315                     );
    316                   })}
    317                 </tr>
    318               ))}
    319             </tbody>
    320           </table>
    321         </div>
    322       )}
    323     </div>
    324   );
    325 }

Impressum · Datenschutz