import { RAW } from "./evals.js";
import { GO_MODELS, FREE_NOTES, CATEGORIES } from "./data.js";
/* ---------- shared helpers ---------- */
const el = (id) => document.getElementById(id);
const tip = el("tip");
function showTip(html, e) {
tip.style.display = "block";
tip.style.left = Math.min(e.clientX + 14, window.innerWidth - 260) + "px";
tip.style.top = Math.min(e.clientY + 14, window.innerHeight - 140) + "px";
tip.innerHTML = html;
}
const hideTip = () => (tip.style.display = "none");
function color(v) {
if (v === null || v === undefined) return "transparent";
const stops = [
[14, 16, 26], [28, 50, 80], [31, 106, 143], [47, 163, 160],
[87, 204, 138], [182, 227, 106], [242, 214, 75], [243, 156, 61], [239, 83, 80]
];
const p = Math.max(0, Math.min(0.999, v / 100)) * (stops.length - 1);
const i = Math.floor(p), f = p - i, a = stops[i], b = stops[i + 1] || a;
const c = a.map((x, k) => Math.round(x + (b[k] - x) * f));
return `rgb(${c[0]},${c[1]},${c[2]})`;
}
const txtCol = (v) => (v >= 62 ? "#101319" : "#e6e9f2");
const TASKS = [
{ key: "coding", label: "Frontier coding", picks: [["fc", "FrontierCode"]] },
{ key: "scicode", label: "Sci coding", picks: [["scicode", "SciCode"]] },
{ key: "terminal", label: "Terminal agent", picks: [["tb21", "Terminal-Bench 2.1"], ["tb40", "Terminal-Bench 4.0"], ["tbh", "Terminal-Bench Hard"]] },
{ key: "tools", label: "Tool use (τ-bench)", picks: [["tau2", "τ²-bench"], ["taub", "τ-bench Banking"]] },
{ key: "agentic", label: "Agent workflows", picks: [["apex", "APEX-Agents"], ["analyst", "Analyst Agent"]] },
{ key: "auto", label: "Automation", picks: [["auto", "AutomationBench"]] },
{ key: "science", label: "Science reasoning", picks: [["gpqa", "GPQA Diamond"], ["critpt", "CritPt"]] },
{ key: "hle", label: "Knowledge (HLE)", picks: [["hle", "Humanity's Last Exam"]] },
{ key: "lcr", label: "Long context", picks: [["lcr", "AA Long-Context Reasoning"]] },
{ key: "ifbench", label: "Instruction following", picks: [["ifbench", "IFBench"]] },
{ key: "vision", label: "Vision (MMMU-Pro)", picks: [["mmmu", "MMMU-Pro"]] },
{ key: "pro", label: "Professional work", picks: [["gdpv", "GDPval-AA"], ["gdppdf", "GDP-PDF"]] }
];
RAW.forEach((m) => {
m.tasks = {}; m.src = {};
TASKS.forEach((t) => {
for (const [f, label] of t.picks) {
const v = m[f];
if (v !== null && v !== undefined) { m.tasks[t.key] = v; m.src[t.key] = label; break; }
}
});
m.blend = m.free ? 0 : 0.7 * m.pi + 0.3 * m.po;
});
const byId = Object.fromEntries(RAW.map((m) => [m.id, m]));
/* ---------- header cards ---------- */
const scored = RAW.filter((m) => m.ii !== null);
const topOverall = scored.slice().sort((a, b) => b.ii - a.ii)[0];
const bestValue = scored.filter((m) => !m.free).slice().sort((a, b) => b.ii / b.blend - a.ii / a.blend)[0];
el("cards").innerHTML = [
["Models in Go", RAW.length, "in the Go catalog"],
["With public evals", scored.length, "Artificial Analysis harness"],
["Top overall", topOverall.n, "index " + topOverall.ii.toFixed(1)],
["Best value", bestValue.n, "index " + bestValue.ii.toFixed(1) + " at $" + bestValue.blend.toFixed(2) + "/1M"],
["Free tier", RAW.filter((m) => m.free).length, "limited-time free models"]
].map(([k, v, s]) => `
`).join("");
/* ---------- matrix ---------- */
const cols = [
{ key: "name", label: "Model", get: (m) => m.n, txt: true },
{ key: "ii", label: "Index", get: (m) => m.ii },
{ key: "blend", label: "Price $/1M", get: (m) => m.blend, price: true },
...TASKS.map((t) => ({ key: t.key, label: t.label, get: (m) => m.tasks[t.key] }))
];
let sortKey = "ii", sortDir = -1;
function renderMatrix() {
const best = {};
cols.forEach((c) => {
if (c.key === "name" || c.key === "blend") return;
const vals = RAW.map((m) => c.get(m)).filter((v) => v !== null && v !== undefined);
best[c.key] = vals.length ? Math.max(...vals) : null;
});
const rows = RAW.slice().sort((a, b) => {
const c = cols.find((x) => x.key === sortKey);
const va = c.get(a), vb = c.get(b);
if (sortKey === "name") return String(va).localeCompare(String(vb)) * sortDir;
if (va === null || va === undefined) return 1;
if (vb === null || vb === undefined) return -1;
return (va - vb) * sortDir;
});
let h = "";
cols.forEach((c) => {
h += `| ${c.label}${c.key === sortKey ? (sortDir < 0 ? " ▾" : " ▴") : ""} | `;
});
h += "
";
rows.forEach((m) => {
h += `| ${m.n}${m.free ? 'free' : ""} ${m.lab} · $${m.pi}/${m.po} · ${(m.ctx / 1e6).toFixed(m.ctx % 1e6 ? 2 : 0)}M ctx${m.ii === null ? " · no public evals" : ""} | `;
cols.forEach((c) => {
if (c.key === "name") return;
const v = c.get(m);
if (c.key === "ii") {
h += `${v === null ? 'n/a' : `${v.toFixed(1)}`} | `;
return;
}
if (c.key === "blend") { h += `${m.free ? "$0" : "$" + v.toFixed(2)} | `; return; }
if (v === null || v === undefined) { h += `– | `; return; }
const isBest = best[c.key] !== null && Math.abs(v - best[c.key]) < 0.001;
h += ` | `;
});
h += "
";
});
h += "";
const t = el("tbl");
t.innerHTML = h;
t.querySelectorAll("th").forEach((th) => {
th.onclick = () => {
const k = th.dataset.k;
if (k === sortKey) sortDir *= -1; else { sortKey = k; sortDir = k === "name" ? 1 : -1; }
renderMatrix();
};
});
t.querySelectorAll(".cell[data-m]").forEach((c) => {
c.onmousemove = (e) => showTip(`${c.dataset.m}
${c.dataset.t} — ${c.dataset.src}`, e);
c.onmouseleave = hideTip;
});
}
renderMatrix();
/* ---------- svg helpers ---------- */
function svgEl(tag, attrs, parent) {
const e = document.createElementNS("http://www.w3.org/2000/svg", tag);
for (const k in attrs) e.setAttribute(k, attrs[k]);
if (parent) parent.appendChild(e);
return e;
}
/* ---------- scatter ---------- */
(function scatter() {
const box = el("scatter"); box.innerHTML = "";
const W = 520, H = 340, m = { l: 52, r: 16, t: 16, b: 44 };
const svg = svgEl("svg", { viewBox: `0 0 ${W} ${H}`, width: "100%", height: "auto" }, box);
const pts = scored.filter((x) => x.ii !== null);
const maxX = Math.max(...pts.map((p) => p.blend)) * 1.1 || 1, maxY = 60;
const X = (v) => m.l + (v / maxX) * (W - m.l - m.r);
const Y = (v) => H - m.b - (v / maxY) * (H - m.t - m.b);
for (let g = 0; g <= maxY; g += 10) {
svgEl("line", { x1: m.l, y1: Y(g), x2: W - m.r, y2: Y(g), stroke: "#232838" }, svg);
svgEl("text", { x: m.l - 8, y: Y(g) + 4, fill: "#8b93a7", "font-size": 10, "text-anchor": "end" }, svg).textContent = g;
}
for (let g = 0; g <= maxX; g += 2) {
svgEl("line", { x1: X(g), y1: m.t, x2: X(g), y2: H - m.b, stroke: "#1c2130" }, svg);
svgEl("text", { x: X(g), y: H - m.b + 16, fill: "#8b93a7", "font-size": 10, "text-anchor": "middle" }, svg).textContent = "$" + g;
}
svgEl("text", { x: (m.l + W - m.r) / 2, y: H - 6, fill: "#8b93a7", "font-size": 11, "text-anchor": "middle" }, svg).textContent = "blended price $/1M tokens";
svgEl("text", { x: 14, y: (m.t + H - m.b) / 2, fill: "#8b93a7", "font-size": 11, "text-anchor": "middle", transform: `rotate(-90 14 ${(m.t + H - m.b) / 2})` }, svg).textContent = "Intelligence Index";
pts.forEach((p) => {
const c = svgEl("circle", { cx: X(p.blend), cy: Y(p.ii), r: 5, fill: p.free ? "#2ecc71" : "#7cc4ff", "fill-opacity": 0.85, stroke: "#0b0d12", "stroke-width": 1.5 }, svg);
const lbl = svgEl("text", { x: X(p.blend) + 8, y: Y(p.ii) + 3, fill: "#c8cede", "font-size": 10 }, svg);
lbl.textContent = p.n.length > 22 ? p.n.slice(0, 21) + "…" : p.n;
c.onmousemove = (e) => showTip(`${p.n}
Index ${p.ii.toFixed(1)}
${p.free ? "Free" : `$${p.pi} in / $${p.po} out per 1M`}`, e);
c.onmouseleave = hideTip;
});
})();
/* ---------- value ranking ---------- */
(function valueChart() {
const box = el("value"); box.innerHTML = "";
const W = 520, H = 340, m = { l: 96, r: 16, t: 12, b: 28 };
const svg = svgEl("svg", { viewBox: `0 0 ${W} ${H}`, width: "100%", height: "auto" }, box);
const pts = scored.filter((p) => !p.free).map((p) => ({ ...p, v: p.ii / p.blend })).sort((a, b) => b.v - a.v);
const maxV = Math.max(...pts.map((p) => p.v)) * 1.08;
const step = (H - m.t - m.b) / Math.max(pts.length, 1);
pts.forEach((p, i) => {
const y = 26 + i * step;
const w = Math.max(2, (p.v / maxV) * (W - m.l - m.r));
svgEl("rect", { x: m.l, y: y - 9, width: w, height: 15, rx: 3, fill: "#7cc4ff", "fill-opacity": 0.85 }, svg);
svgEl("text", { x: m.l - 8, y: y + 3, fill: "#c8cede", "font-size": 11, "text-anchor": "end" }, svg).textContent = p.n.length > 17 ? p.n.slice(0, 16) + "…" : p.n;
svgEl("text", { x: m.l + w + 6, y: y + 3, fill: "#8b93a7", "font-size": 10 }, svg).textContent = p.v.toFixed(0) + " pts/$";
});
const free = scored.filter((p) => p.free);
svgEl("text", { x: m.l, y: H - 6, fill: "#2ecc71", "font-size": 11 }, svg).textContent = "Free models (not ranked): " + free.map((f) => f.n).join(", ");
})();
/* ---------- model guide ---------- */
function modelCard(m) {
const tags = (m.best || []).map((b) => {
const key = Object.keys(CATEGORIES).find((k) => CATEGORIES[k].label.toLowerCase() === b.toLowerCase());
const cls = key === "free" ? "tag free" : key === "imagegen" ? "tag warn" : "tag";
return `${b}`;
}).join("");
const ev = byId[m.id];
const idx = ev && ev.ii !== null ? `index ${ev.ii.toFixed(1)}` : "no public evals";
return `
${m.name}
${m.lab}
released ${m.released} · $${m.price.in}/${m.price.out} per 1M · ${m.ctx} context · ${m.modalities} · ${idx}
${tags}
${m.blurb}
Targeted for: ${m.target}
${m.caveats && m.caveats.length ? `
Caveats${m.caveats.map((c) => `- ${c}
`).join("")}
` : ""}
`;
}
// loose keyword matching so a model's plain-language tags still hit the right filter
const FILTER_WORDS = {
agentic: ["agent"],
coding: ["cod", "repo"],
data: ["data", "spreadsheet", "analys"],
vision: ["vision", "image", "multimodal", "perception", "visual"],
imagegen: ["generation", "generat"],
research: ["research", "knowledge work", "document", "deep"],
fast: ["cheap", "fast", "high-volume", "efficient", "budget"]
};
function renderGuide(filter) {
const list = GO_MODELS.filter((m) => {
if (!filter || filter === "all") return true;
if (filter === "free") return m.free;
if (filter === "new") return m.released >= "2026-08-01";
const words = FILTER_WORDS[filter] || [];
const hay = m.best.join(" ").toLowerCase();
return words.some((w) => hay.includes(w));
});
const extra = filter === "all" || filter === "free" ? `
Also free outside the Go plan
${FREE_NOTES.map((f) => `${f.name} (${f.lab}, ${f.where}) — ${f.target} ${f.note}`).join("
")}
` : "";
const none = list.length ? "" : `No model in the Go catalog matches this category. Every Go model is text-output — none of them generate images, so image generation needs a different provider.
`;
el("guide").innerHTML = list.map(modelCard).join("") + extra + none;
}
const filters = [
["all", "All models"], ["free", "Free tier"], ["new", "New (since Aug 2026)"],
["agentic", "Agentic"], ["coding", "Coding"], ["data", "Data analysis"],
["vision", "Image understanding"], ["imagegen", "Image generation"],
["research", "Deep research"], ["fast", "Fast & cheap"]
];
el("filters").innerHTML = filters.map(([k, l]) => `${l}
`).join("");
el("filters").querySelectorAll(".chip").forEach((c) => {
c.onclick = () => {
el("filters").querySelectorAll(".chip").forEach((x) => x.classList.remove("on"));
c.classList.add("on");
renderGuide(c.dataset.f);
};
});
renderGuide("all");
el("foot").innerHTML = `
How to read the matrix
• Each cell is the highest-scoring published evaluation for that task family on a 0–100 scale; the benchmark behind it is named in the hover tooltip.
• Terminal-agent, tool-use, science and professional-work columns fall back to a second benchmark when a model has not been run on the first (Terminal-Bench 2.1 → 4.0 → Hard; τ²-bench → τ-bench Banking; GPQA → CritPt; GDPval → GDP-PDF). Scores are comparable within a column, but the underlying benchmark can differ per row — hover to check.
• "Index" is the Artificial Analysis Intelligence Index, a weighted composite of those evaluations.
• Prices are OpenCode Go list prices per 1M tokens (input/output). Blended price = 70% input / 30% output.
• Free-tier models (MiniMax M3.1 Flash Free, Beam Free, LongCat 2.5 Preview Free) are limited-time offers with unlimited included usage.
• Hy4 preview and LongCat 2.5 Preview Free have no public third-party evaluations yet, so they show as "n/a". Qwen3.8 Flash maps to Artificial Analysis' "Qwen3.8-Flash-Next" entry.
• No model in the Go catalog generates images — all Go models are text-output. "Image understanding" rows mean the model can read images, video, PDFs or screenshots as input.
Sources: Artificial Analysis (benchmarks, Intelligence Index), OpenCode Data (catalog, usage, context windows) and OpenCode Go docs (pricing, allowances, privacy). Vendor announcements for model positioning. Retrieved 2026-10-21.`;