slipstream / frontend /app.js
ashaibani's picture
Upload folder using huggingface_hub
2d4823c verified
Raw
History Blame Contribute Delete
33 kB
// Slipstream - interactive presentation (buildless Preact + htm + Highcharts), backed by gr.Server.
import { h, render } from "https://esm.sh/preact@10.25.4";
import { useState, useEffect, useRef, useMemo } from "https://esm.sh/preact@10.25.4/hooks";
import htm from "https://esm.sh/htm@3.1.1";
import { Client } from "https://esm.sh/@gradio/client@1.10.0";
const html = htm.bind(h);
const HC = window.Highcharts;
// ---- backend ------------------------------------------------------------------------------
let _client = null;
async function api(name, payload = {}) {
if (!_client) _client = await Client.connect(location.origin);
return (await _client.predict("/" + name, payload)).data[0];
}
// ---- palette / data encoding --------------------------------------------------------------
const FC = { classical: "#5aa9ff", ml: "#4ade80", foundation: "#fb923c",
naive: "#8b93a7", control: "#c084fc", agent: "#f472b6", other: "#8b93a7" };
const ACCENT = "#7c8cff";
const STR = ["short", "medium", "long", "very_long"], STR_L = ["short", "medium", "long", "very long"];
const STAGES = ["0.25", "0.40", "0.60", "0.75"];
// metric at a chosen completion stage, or averaged across all of them ("avg")
function metric(b, k, stage, field) {
if (stage === "avg") {
const vs = STAGES.map(s => b.methods[k]?.stages?.[s]?.[field]).filter(v => v != null);
return vs.length ? vs.reduce((a, c) => a + c, 0) / vs.length : null;
}
return b.methods[k]?.stages?.[stage]?.[field];
}
const stageLabel = (s) => s === "avg" ? "all snapshots (average)" : Math.round(parseFloat(s) * 100) + "% complete";
if (HC) HC.setOptions({
colors: [ACCENT], chart: { backgroundColor: "transparent", style: { fontFamily: "Inter, sans-serif" }, spacing: [8, 6, 8, 6] },
title: { style: { color: "#eef1f7", fontFamily: "Space Grotesk", fontSize: "15px", fontWeight: "600" } },
subtitle: { style: { color: "#8b93a7", fontSize: "12.5px" } },
xAxis: { labels: { style: { color: "#8b93a7", fontSize: "12px" } }, lineColor: "rgba(255,255,255,.12)", tickColor: "rgba(255,255,255,.12)", title: { style: { color: "#8b93a7" } } },
yAxis: { labels: { style: { color: "#8b93a7", fontSize: "12px" } }, gridLineColor: "rgba(255,255,255,.06)", title: { style: { color: "#8b93a7" } } },
legend: { itemStyle: { color: "#c7cdda", fontWeight: "500" }, itemHoverStyle: { color: "#fff" } },
tooltip: { backgroundColor: "rgba(14,17,24,.97)", borderColor: "rgba(255,255,255,.14)", borderRadius: 8,
style: { color: "#eef1f7", fontSize: "12.5px" }, useHTML: true, outside: true },
plotOptions: { series: { animation: { duration: 900 } } },
credits: { enabled: false }, accessibility: { enabled: false },
});
const NAV = [
{ id: "hero", n: "", t: "Slipstream" },
{ id: "benchmark", n: "01", t: "The benchmark" },
{ id: "gap", n: "02", t: "The gap" },
{ id: "distill", n: "03", t: "To the edge" },
{ id: "results", n: "04", t: "Results" },
{ id: "modal", n: "05", t: "On Modal" },
{ id: "demo", n: "06", t: "Try it live" },
{ id: "releases", n: "07", t: "Releases" },
];
// ---- chart component: draws in the first time it scrolls into view -------------------------
function Chart({ options, height = 380 }) {
const ref = useRef(null), inst = useRef(null);
useEffect(() => {
if (!ref.current || !HC) return;
const el = ref.current;
const io = new IntersectionObserver((es) => {
if (es[0].isIntersecting && options && !inst.current) inst.current = HC.chart(el, options);
}, { threshold: 0.2 });
io.observe(el);
return () => { io.disconnect(); if (inst.current) { try { inst.current.destroy(); } catch (e) {} inst.current = null; } };
}, [options]);
return html`<div class="chart" ref=${ref} style=${"height:" + height + "px"}></div>`;
}
// ---- animated count-up --------------------------------------------------------------------
function CountUp({ to, suffix = "" }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current; if (!el) return; let done = false;
const io = new IntersectionObserver((es) => {
if (!es[0].isIntersecting || done) return; done = true;
const dur = 1100, t0 = performance.now();
const tick = (t) => { const k = Math.min(1, (t - t0) / dur); const e = 1 - Math.pow(1 - k, 3);
el.textContent = Math.round(to * e) + suffix; if (k < 1) requestAnimationFrame(tick); };
requestAnimationFrame(tick);
}, { threshold: 0.5 });
io.observe(el); return () => io.disconnect();
}, [to]);
return html`<span ref=${ref}>0${suffix}</span>`;
}
// ---- option builders ----------------------------------------------------------------------
const stage40 = (b, k) => b.methods[k]?.stages?.["0.40"];
const strataFin = (b, k) => STR.map(s => b.methods[k]?.by_strata?.[s]?.finish_err_med ?? null);
function leaderboardOpts(b, stage) {
const keys = ["earned_schedule", "growth_curve", "xsm", "ml_tabpfn", "ml_catboost", "timesfm_2.5",
"agent_deepseek_v4_flash", "student_Nemotron-4B_sft", "student_gemma-4-E2B_sft", "student_MiniCPM5-1B_sft"];
const label = { earned_schedule: "Earned Schedule", growth_curve: "Growth curve", xsm: "XSM",
ml_tabpfn: "TabPFN (ML)", ml_catboost: "CatBoost (ML)", "timesfm_2.5": "TimesFM",
agent_deepseek_v4_flash: "Agent (teacher)", "student_Nemotron-4B_sft": "Nemotron 4B ·distilled",
"student_gemma-4-E2B_sft": "Gemma E2B ·distilled", "student_MiniCPM5-1B_sft": "MiniCPM5 1B ·distilled" };
const rows = keys.map(k => ({ k, m: b.methods[k], eac: metric(b, k, stage, "eac_ape_med") }))
.filter(r => r.eac != null).sort((a, c) => c.eac - a.eac);
return {
chart: { type: "bar", height: 430 },
title: { text: "Cost-forecast error · " + stageLabel(stage) }, subtitle: { text: "median %, lower is better" },
xAxis: { categories: rows.map(r => label[r.k] || r.k), labels: { style: { fontSize: "12.5px", color: "#c7cdda" } } },
yAxis: { min: 0, title: { text: null } },
legend: { enabled: false },
tooltip: { pointFormat: "<b>{point.y:.2f}%</b> cost error" },
plotOptions: { bar: { borderRadius: 3, pointWidth: 16, dataLabels: { enabled: true, format: "{y:.1f}",
style: { color: "#c7cdda", textOutline: "none", fontWeight: "600" } } } },
series: [{ data: rows.map(r => ({ y: r.eac, color: r.m.family === "agent" ? ACCENT : FC[r.m.family] })) }],
};
}
function blendOpts(b) {
const ser = [
["earned_schedule", "Earned Schedule", FC.classical, 2],
["ml_catboost", "ML reference-class", FC.ml, 2],
["agent_deepseek_v4_flash", "Agentic layer", ACCENT, 4],
].map(([k, name, color, lw]) => ({ name, color, lineWidth: lw, data: strataFin(b, k),
marker: { radius: lw > 2 ? 5 : 4, symbol: "circle" } }));
return {
chart: { type: "line", height: 430 },
title: { text: "Finish-date error grows with project length - unless you blend" },
subtitle: { text: "median periods off the true finish, at 40% complete" },
xAxis: { categories: STR_L, title: { text: "project length" } },
yAxis: { min: 0, title: { text: "finish error (periods)" } },
tooltip: { shared: true, valueDecimals: 2, valueSuffix: " periods" },
series: ser,
};
}
function baseSftOpts(b, stage) {
const studs = [["MiniCPM5-1B", "MiniCPM5 1B"], ["gemma-4-E2B", "Gemma E2B"],
["Nemotron-4B", "Nemotron 4B"], ["Qwen3.5-4B", "Qwen3.5 4B"], ["Qwen3.5-2B", "Qwen3.5 2B"]];
const vr = (k, v) => (metric(b, `student_${k}_${v}`, stage, "valid_rate") ?? 0) * 100;
return {
chart: { type: "column", height: 400 },
title: { text: "Off-the-shelf vs distilled: usable-forecast rate" },
subtitle: { text: "% of projects the small model returns a valid forecast for · " + stageLabel(stage) },
xAxis: { categories: studs.map(s => s[1]) },
yAxis: { min: 0, max: 100, title: { text: null }, labels: { format: "{value}%" } },
tooltip: { shared: true, valueDecimals: 1, valueSuffix: "%" },
plotOptions: { column: { borderRadius: 3, groupPadding: 0.12 } },
series: [
{ name: "base (off the shelf)", color: "rgba(139,147,167,.5)", data: studs.map(s => vr(s[0], "base")) },
{ name: "distilled", color: ACCENT, data: studs.map(s => vr(s[0], "sft")) },
],
};
}
function scatterOpts(b, stage) {
const CAPX = 12, CAPY = 5;
const names = Object.keys(b.methods).filter(k => metric(b, k, stage, "eac_ape_med") != null);
const byFam = {};
names.forEach(k => {
const ex = metric(b, k, stage, "eac_ape_med"), fy = metric(b, k, stage, "finish_err_med");
if (ex == null || fy == null || ex > CAPX || fy > CAPY) return;
const f = b.methods[k].family; (byFam[f] = byFam[f] || []).push({ x: ex, y: fy, name: k });
});
const FL = { classical: "classical", ml: "ML / tabular", foundation: "time-series FM", naive: "naive", control: "control", agent: "agentic" };
return {
chart: { type: "scatter", height: 420, zoomType: "xy" },
title: { text: "Cost vs schedule · " + stageLabel(stage) + " - bottom-left is best" },
xAxis: { min: 0, max: CAPX, title: { text: "cost error %" }, gridLineWidth: 1 },
yAxis: { min: 0, max: CAPY, title: { text: "finish error (periods)" } },
tooltip: { pointFormat: "<b>{point.name}</b><br/>{point.x:.2f}% · {point.y:.2f} periods" },
plotOptions: { scatter: { marker: { radius: 5, symbol: "circle", states: { hover: { radiusPlus: 3 } } } } },
series: Object.keys(byFam).map(f => ({ name: FL[f] || f, color: f === "agent" ? ACCENT : FC[f],
data: byFam[f], marker: { lineColor: "rgba(0,0,0,.4)", lineWidth: f === "agent" ? 1.2 : 0,
radius: f === "agent" ? 7 : 5, symbol: f === "agent" ? "diamond" : "circle" } })),
};
}
function stratifiedOpts(b) {
const keys = ["earned_schedule", "xsm", "growth_curve", "ml_tabpfn", "ml_catboost", "ml_lightgbm",
"timesfm_2.5", "chronos_2", "agent_deepseek_v4_flash", "student_Nemotron-4B_sft"];
const label = { earned_schedule: "Earned Schedule", xsm: "XSM", growth_curve: "Growth curve",
ml_tabpfn: "TabPFN", ml_catboost: "CatBoost", ml_lightgbm: "LightGBM", "timesfm_2.5": "TimesFM",
chronos_2: "Chronos", agent_deepseek_v4_flash: "Agent (teacher)", "student_Nemotron-4B_sft": "Nemotron ·distilled" };
const rows = keys.filter(k => b.methods[k]);
const CAP = 8, data = [];
rows.forEach((k, yi) => STR.forEach((s, xi) => {
const v = b.methods[k].by_strata?.[s]?.finish_err_med;
if (v != null) data.push({ x: xi, y: yi, value: Math.min(v, CAP), actual: v });
}));
return {
chart: { type: "heatmap", height: 420 },
title: { text: "Finish error by project length" }, subtitle: { text: "periods off; pale = accurate, bright = inaccurate" },
xAxis: { categories: STR_L },
yAxis: { categories: rows.map(k => label[k] || k), reversed: true, title: { text: null }, labels: { style: { fontSize: "11.5px", color: "#c7cdda" } } },
colorAxis: { min: 0, max: CAP, stops: [[0, "#10331f"], [0.3, "#1f6b46"], [0.6, "#cfa53a"], [1, "#e0556b"]], labels: { style: { color: "#8b93a7" } } },
legend: { enabled: false },
tooltip: { formatter() { return `<b>${rows.map(k => label[k] || k)[this.point.y]}</b><br/>${STR_L[this.point.x]}: <b>${this.point.actual.toFixed(2)}</b> periods`; } },
series: [{ borderWidth: 2, borderColor: "#08090d", data,
dataLabels: { enabled: true, formatter() { return this.point.actual.toFixed(1); }, style: { fontSize: "10.5px", color: "#eef1f7", textOutline: "none" } } }],
};
}
// ---- live-demo formatting + charts --------------------------------------------------------
const money = (v) => v == null ? "-" : "£" + Math.round(v).toLocaleString();
const per = (v) => { if (v == null) return "-"; const r = Math.round(v * 10) / 10; return r + (r === 1 ? " period" : " periods"); };
// methods to draw on the live comparison, in fixed order (the agent is the headline)
function demoRows(res, key) {
const g = (o) => o ? o[key] : null;
return [
{ name: "Earned Schedule", v: g(res.classical?.earned_schedule), color: FC.classical },
{ name: "TimesFM", v: g(res.timesfm), color: FC.foundation },
{ name: "TabPFN", v: g(res.tabpfn), color: FC.ml },
{ name: "Agentic layer", v: g(res.agent), color: ACCENT, bold: true },
];
}
function truthLine(value, text) {
return [{ value, color: "#eef1f7", width: 2, dashStyle: "Dash", zIndex: 5,
label: { text, style: { color: "#eef1f7", fontWeight: "600", fontSize: "11px" }, align: "right", x: -4 } }];
}
function demoFinishOpts(res) {
const rows = demoRows(res, "finish");
return {
chart: { type: "bar", height: 250 },
title: { text: "Finish period" }, subtitle: { text: "when the project completes" },
xAxis: { categories: rows.map(r => r.name), labels: { style: { color: "#c7cdda", fontSize: "12px" } } },
yAxis: { min: 0, title: { text: null }, plotLines: truthLine(res.truth.finish, "actual " + res.truth.finish) },
legend: { enabled: false },
tooltip: { pointFormat: "<b>{point.y:.1f}</b> periods" },
plotOptions: { bar: { borderRadius: 3, pointWidth: 18, dataLabels: { enabled: true, format: "{y:.1f}",
style: { color: "#c7cdda", textOutline: "none", fontWeight: "600" } } } },
series: [{ data: rows.map(r => r.v == null ? null : { y: r.v, color: r.color }) }],
};
}
function demoEacOpts(res) {
const rows = demoRows(res, "eac");
return {
chart: { type: "bar", height: 250 },
title: { text: "Final cost (EAC)" }, subtitle: { text: "total cost at completion" },
xAxis: { categories: rows.map(r => r.name), labels: { style: { color: "#c7cdda", fontSize: "12px" } } },
yAxis: { min: 0, title: { text: null }, labels: { formatter() { return "£" + Math.round(this.value / 1000) + "k"; } },
plotLines: truthLine(res.truth.eac, "actual " + money(res.truth.eac)) },
legend: { enabled: false },
tooltip: { pointFormatter() { return "<b>" + money(this.y) + "</b>"; } },
plotOptions: { bar: { borderRadius: 3, pointWidth: 18, dataLabels: { enabled: true,
formatter() { return "£" + Math.round(this.y / 1000) + "k"; },
style: { color: "#c7cdda", textOutline: "none", fontWeight: "600" } } } },
series: [{ data: rows.map(r => r.v == null ? null : { y: r.v, color: r.color }) }],
};
}
// ---- slides -------------------------------------------------------------------------------
const Slide = (id, ...kids) => html`<section class="slide" data-id=${id}><div class="slide-inner">${kids}</div></section>`;
function Hero({ bench, stage }) {
const opts = useMemo(() => bench ? leaderboardOpts(bench, stage) : null, [bench, stage]);
return html`<section class="slide hero" data-id="hero">
<div class="hero-grid">
<div>
<h1 class="reveal">Slipstream</h1>
<p class="tag reveal">project-controls forecasting</p>
<p class="promise reveal">Forecasting a project's final cost and finish date - before it finishes.</p>
<p class="sub reveal">A benchmark of 37 methods on real projects, an agentic layer that blends the best of
them, and small models that run it on the edge.</p>
<p class="herolinks reveal">
<a href="https://huggingface.co/blog/build-small-hackathon/slipstream" target="_blank" rel="noopener">Read the write-up →</a>
<a href="https://huggingface.co/build-small-hackathon" target="_blank" rel="noopener">Models & dataset →</a>
</p>
</div>
<div class="reveal">${opts && html`<${Chart} options=${opts} height=${430} />`}</div>
</div>
<div class="scrollcue">scroll ↓</div>
</section>`;
}
function Benchmark({ summary }) {
const s = summary || {};
const cells = [
[s.n_projects ?? 0, "", "real projects scored", "held-out" + (s.n_sourced ? `, of ${s.n_sourced} with outcomes` : "")],
[s.n_methods ?? 0, "", "methods compared", "classical · ML · time-series FMs · agents"],
[s.stages ? s.stages.length : 0, "", "snapshots", "forecast at 25 / 40 / 60 / 75% done"],
[s.families ? s.families.length : 0, "", "method families", "from simple formulas to AI agents"],
];
return Slide("benchmark",
html`<span class="eyebrow reveal">01 - The benchmark</span>`,
html`<h2 class="reveal">Forecast a project before it finishes</h2>`,
html`<p class="lead reveal">Reveal only the first part of a project's history, then predict its
<b>total final cost</b> and the <b>period it will finish</b>. Every method is scored the same way,
on the same real projects it never saw in training.</p>`,
html`<div class="stats reveal">
${cells.map(([n, sfx, lab, sub]) => html`<div class="stat">
<div class="num"><${CountUp} to=${n} suffix=${sfx} /></div>
<div class="lab">${lab}</div><div class="sub2">${sub}</div></div>`)}
</div>`);
}
function Gap({ bench }) {
const opts = useMemo(() => bench ? blendOpts(bench) : null, [bench]);
return Slide("gap",
html`<span class="eyebrow reveal">02 - The gap an agent fills</span>`,
html`<h2 class="reveal">No single method wins across a project's life</h2>`,
html`<div class="reveal chart-wrap">${opts && html`<${Chart} options=${opts} height=${430} />`}</div>`,
html`<p class="reveal">Earned Schedule (the industry standard) is excellent early but its finish forecast
collapses on long projects - from <b>0.35</b> to <b>5.4</b> periods. ML reference-class models stay robust
late but are weaker early. The <b class="accent">agentic layer</b> writes code to call every tool, trusts
the ML reference-class for long-horizon timing and Earned Schedule's BAC/CPI for cost, and explains its
choice - the only method strong across <i>all</i> horizons.</p>`);
}
function Distill({ bench, stage }) {
const opts = useMemo(() => bench ? baseSftOpts(bench, stage) : null, [bench, stage]);
return Slide("distill",
html`<span class="eyebrow reveal">03 - Distilling to the edge</span>`,
html`<h2 class="reveal">Shrink the agent for air-gapped, on-device forecasting</h2>`,
html`<p class="lead reveal">Much project data - defence, infrastructure, government - can't leave its
environment. So we distil the teacher's reasoning into <b>1-4B open models</b> that run offline.</p>`,
html`<div class="reveal chart-wrap">${opts && html`<${Chart} options=${opts} height=${400} />`}</div>`,
html`<p class="reveal">Off the shelf, a 1B model returns a usable forecast under <b>2%</b> of the time.
After distillation the best reach <b class="accent">parity with the teacher and Earned Schedule</b>
(~2.3-2.4% cost error) - small enough to forecast at the edge.</p>`);
}
function Results({ bench, stage }) {
const sc = useMemo(() => bench ? scatterOpts(bench, stage) : null, [bench, stage]);
const st = useMemo(() => bench ? stratifiedOpts(bench) : null, [bench]); // by-strata only at 40%
return Slide("results",
html`<span class="eyebrow reveal">04 - Results & limits</span>`,
html`<h2 class="reveal">What the benchmark shows, honestly</h2>`,
html`<div class="two-col reveal">
<div>${sc && html`<${Chart} options=${sc} height=${420} />`}</div>
<div>${st && html`<${Chart} options=${st} height=${420} />`}</div>
</div>`,
html`<ul class="limits reveal">
<li>Cost is largely solved by Earned Schedule - the agent matches it; its real win is on schedule, plus auditability and edge-deployability.</li>
<li>The "realcv" ML figures are an in-domain ceiling (trained on the real labels), not a deployable result.</li>
<li>Small still has a floor: the 2B Qwen never reliably learned the tool-calling format.</li>
<li>107 real projects, one simulator, one teacher - results are bounded accordingly.</li>
</ul>`);
}
function Modal_() {
const steps = [
["Simulate", "Real libraries have structure but not full cost/progress outcomes - we simulate them to completion."],
["Generate", "The teacher agent runs the code-action loop over the simulated projects; its reasoning traces are captured."],
["Fine-tune", "Five small models LoRA-fine-tuned on the traces, each on the stack it needs."],
["Evaluate", "Every model + baseline scored through one shared harness on the held-out real projects."],
];
return Slide("modal",
html`<span class="eyebrow reveal">05 - Built on Modal</span>`,
html`<h2 class="reveal">The whole pipeline runs on Modal</h2>`,
html`<p class="lead reveal">Simulation, trace generation, fine-tuning and evaluation - all on Modal's GPUs,
with a strict firewall: distillation seeds are simulation-only, the real projects stay a clean test set.</p>`,
html`<div class="steps reveal">
${steps.map(([t, d], i) => html`<div class="step"><div class="sn">${i + 1}</div><b>${t}</b><div class="sd">${d}</div></div>`)}
</div>`);
}
function Releases() {
const HF = "https://huggingface.co/";
const items = [
["Distillation dataset", "Multi-turn code-action forecasting traces - the data the students learn from (CC-BY-4.0).", "datasets/build-small-hackathon/slipstream-evm-sft", "slipstream-evm-sft"],
["MiniCPM5-1B agent", "From an unusable 1B base to Earned-Schedule parity.", "build-small-hackathon/slipstream-minicpm5-1b-evm", "slipstream-minicpm5-1b-evm"],
["Nemotron-3-Nano 4B agent", "The Mamba-hybrid agent, distilled.", "build-small-hackathon/slipstream-nemotron3-nano-4b-evm", "slipstream-nemotron3-nano-4b-evm"],
["Gemma-E2B agent", "The compact Gemma agent.", "build-small-hackathon/slipstream-gemma4-e2b-evm", "slipstream-gemma4-e2b-evm"],
];
return Slide("releases",
html`<span class="eyebrow reveal">07 - Open releases</span>`,
html`<h2 class="reveal">Released on Hugging Face</h2>`,
html`<p class="lead reveal">On the <a class="accent" href="${HF}build-small-hackathon" target="_blank" rel="noopener">Build Small Hackathon</a>
organisation, with a <a class="accent" href="${HF}blog/build-small-hackathon/slipstream" target="_blank" rel="noopener">full write-up</a>.
The dataset is CC-BY-4.0; each model inherits its base model's licence. Trained, evaluated and benchmarked entirely on Modal.</p>`,
html`<div class="rel reveal">${items.map(([t, d, path, slug]) => html`<a class="rel-item" href="${HF + path}" target="_blank" rel="noopener">
<b>${t}</b><div class="rd">${d}</div><div class="slug">build-small-hackathon/${slug}</div></a>`)}</div>`,
html`<a class="btn reveal" href="${HF}blog/build-small-hackathon/slipstream" target="_blank" rel="noopener">Read the technical write-up →</a>`);
}
function Demo() {
const [meta, setMeta] = useState(null);
const [pid, setPid] = useState("");
const [stage, setStage] = useState(0.4);
const [model, setModel] = useState("MiniCPM5-1B");
const [res, setRes] = useState(null);
const [status, setStatus] = useState("idle"); // idle | running | done | error
const [msg, setMsg] = useState("");
const poll = useRef(null);
useEffect(() => {
fetch("/demo/projects").then(r => r.json()).then(m => {
if (m.error) { setMsg(m.error); return; }
setMeta(m); setModel(m.default_model || "MiniCPM5-1B");
const pick = m.projects.find(p => p.strata === "long") || m.projects[0];
if (pick) setPid(pick.id);
}).catch(e => setMsg(String(e)));
return () => clearInterval(poll.current);
}, []);
const run = async () => {
if (!pid) return;
clearInterval(poll.current);
setRes(null); setStatus("running");
setMsg("Every run cold-starts a fresh GPU on Modal, so expect roughly 5-7 minutes. Results stream in as each method lands - Earned Schedule and the real outcome are instant; TabPFN and the live agent follow.");
let base;
try {
const r = await fetch("/demo/forecast", { method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ project_id: pid, stage, model }) });
base = await r.json();
} catch (e) { setStatus("error"); setMsg("Request failed: " + e); return; }
if (base.error) { setStatus("error"); setMsg(base.error); return; }
setRes(base);
const j = base.jobs || {}; const t0 = Date.now();
poll.current = setInterval(async () => {
let pr;
try { pr = await (await fetch(`/demo/result?agent=${j.agent || ""}&tabpfn=${j.tabpfn || ""}`)).json(); }
catch (e) { return; }
setRes(prev => {
if (!prev) return prev; const n = { ...prev }; let ch = false;
if (pr.timesfm && !prev.timesfm) { n.timesfm = pr.timesfm; ch = true; }
if (pr.tabpfn && !prev.tabpfn) { n.tabpfn = pr.tabpfn; ch = true; }
if (pr.agent && !prev.agent) { n.agent = pr.agent; ch = true; }
if (pr.agent_error && !prev.agent_error) { n.agent_error = pr.agent_error; ch = true; }
return ch ? n : prev;
});
const secs = Math.round((Date.now() - t0) / 1000), mm = Math.floor(secs / 60), ss = secs % 60;
const tick = (ok) => ok ? "✓" : "…";
setMsg(`Running live on Modal · ${mm}:${String(ss).padStart(2, "0")} - `
+ `TabPFN ${tick(pr.tabpfn)} · agentic layer ${tick(pr.agent)} (cold start, ~5-7 min)`);
if (pr.done) { clearInterval(poll.current); setStatus("done"); setMsg(""); }
}, 4000);
};
const finishOpts = useMemo(() => res ? demoFinishOpts(res) : null, [res]);
const eacOpts = useMemo(() => res ? demoEacOpts(res) : null, [res]);
const grouped = useMemo(() => {
const g = {}; (meta?.projects || []).forEach(p => (g[p.strata] = g[p.strata] || []).push(p)); return g;
}, [meta]);
// headline readout once the agent has landed
let readout = null;
if (res && res.agent && res.agent.finish != null) {
const a = res.agent, T = res.truth;
const fErr = Math.abs(a.finish - T.finish), cErr = Math.abs(a.eac - T.eac) / T.eac * 100;
const es = res.classical?.earned_schedule;
const esF = es && es.finish != null ? Math.abs(es.finish - T.finish) : null;
readout = html`<div class="demo-read reveal">
The distilled <b>${res.model}</b> agent forecast a finish within <b class="accent">${per(fErr)}</b>${" "}
and a final cost within <b class="accent">${cErr.toFixed(1)}%</b> of the real outcome${
esF != null && esF > fErr + 0.5 ? html` - versus Earned Schedule's <b>${per(esF)}</b> off on the finish` : ""}.
</div>`;
}
const STG = [[0.25, "25%"], [0.4, "40%"], [0.6, "60%"], [0.75, "75%"]];
return Slide("demo",
html`<span class="eyebrow reveal">06 - Try it live</span>`,
html`<h2 class="reveal">Forecast a real project, live</h2>`,
html`<p class="lead reveal">Pick a real held-out project and reveal only part of its history. The
<b class="accent">agentic layer</b> runs live on Modal - calling every tool and reconciling them -
next to Earned Schedule, TimesFM and TabPFN, against the true outcome. Each run cold-starts a GPU,
so it takes <b>~5-7 minutes</b> and the methods stream in as they finish.</p>`,
html`<div class="demo-ctl reveal">
<label>Project
<select onChange=${e => setPid(e.target.value)} value=${pid}>
${STR.map((s, i) => (grouped[s] || []).length ? html`<optgroup label=${STR_L[i] + " projects"}>
${grouped[s].map(p => html`<option value=${p.id}>${p.id} · ${p.n_periods} periods</option>`)}
</optgroup>` : null)}
</select>
</label>
<label>Model
<select onChange=${e => setModel(e.target.value)} value=${model}>
${(meta?.models || ["MiniCPM5-1B"]).map(m => html`<option value=${m}>${m}</option>`)}
</select>
</label>
<div class="demo-stg">${STG.map(([v, l]) => html`<button
class=${"sseg" + (v === stage ? " on" : "")} onClick=${() => setStage(v)}>${l}</button>`)}</div>
<button class="demo-run" disabled=${status === "running"} onClick=${run}>
${status === "running" ? "Running…" : "Forecast ▸"}</button>
</div>`,
msg ? html`<div class=${"demo-msg reveal" + (status === "error" ? " err" : "")}>${msg}</div>` : null,
res ? html`<div class="demo-grid reveal">
<div>${finishOpts && html`<${Chart} options=${finishOpts} height=${250} />`}</div>
<div>${eacOpts && html`<${Chart} options=${eacOpts} height=${250} />`}</div>
</div>` : null,
readout,
res && res.agent && res.agent.trace && res.agent.trace.length ? html`<details class="demo-trace reveal">
<summary>How the agent reasoned (${res.agent.turns} turns · ${res.agent.exit_reason})</summary>
${res.agent.trace.map(t => html`<div class="tturn">
${t.reasoning ? html`<div class="tr">${t.reasoning}</div>` : null}
${t.code ? html`<pre class="tc">${t.code}</pre>` : null}
${t.output ? html`<pre class="to">${t.output}</pre>` : null}
</div>`)}
</details>` : null);
}
// ---- app shell ----------------------------------------------------------------------------
function App() {
const [d, setD] = useState({ summary: null, bench: null });
const [stage, setStage] = useState("0.40");
const [err, setErr] = useState(null);
const deckRef = useRef(null);
const nav = (dir) => {
const deck = deckRef.current; if (!deck) return;
const ss = [...deck.querySelectorAll(".slide")];
const cur = ss.findIndex(s => s.getBoundingClientRect().top >= -window.innerHeight * 0.4);
const i = Math.max(0, Math.min(ss.length - 1, (cur < 0 ? 0 : cur) + dir));
ss[i]?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
api("summary").then(s => setD(p => ({ ...p, summary: s }))).catch(e => setErr(String(e)));
api("benchmark").then(b => setD(p => ({ ...p, bench: b }))).catch(e => setErr(String(e)));
}, []);
// scroll behaviour set up once the slides exist (both summary+bench loaded -> render gate passed).
useEffect(() => {
if (!d.bench || !d.summary) return;
const slides = [...document.querySelectorAll(".slide")];
const prog = document.querySelector(".progress");
const STAGE_SLIDES = ["hero", "distill", "results"]; // slides whose charts are stage-dependent
const ratios = new Map();
const io = new IntersectionObserver((es) => {
es.forEach(e => { ratios.set(e.target, e.intersectionRatio); if (e.isIntersecting) e.target.classList.add("in"); });
let best = null, bestR = -1; // global max over ALL slides, not just changed entries (avoids label lag)
slides.forEach(s => { const r = ratios.get(s) || 0; if (r > bestR) { bestR = r; best = s; } });
if (best && bestR > 0) {
const id = best.dataset.id, i = NAV.findIndex(n => n.id === id);
if (prog) prog.style.width = ((i + 1) / NAV.length * 100) + "%";
const lab = document.querySelector(".ctl-pos");
if (lab) lab.textContent = String(i + 1).padStart(2, "0") + " / 0" + NAV.length + " · " + NAV[i].t;
const sel = document.querySelector(".stagesel"), show = STAGE_SLIDES.includes(id);
if (sel) { sel.style.opacity = show ? "1" : "0"; sel.style.pointerEvents = show ? "auto" : "none"; }
}
}, { threshold: [0, 0.2, 0.4, 0.6, 0.8, 1] });
slides.forEach(s => io.observe(s));
const onKey = (ev) => {
if (["ArrowDown", "PageDown", "ArrowRight", " "].includes(ev.key)) { ev.preventDefault(); nav(1); }
else if (["ArrowUp", "PageUp", "ArrowLeft"].includes(ev.key)) { ev.preventDefault(); nav(-1); }
};
window.addEventListener("keydown", onKey);
return () => { io.disconnect(); window.removeEventListener("keydown", onKey); };
}, [d.bench, d.summary]);
if (err) return html`<div style="padding:40px"><div class="err">Backend error: ${err}</div></div>`;
if (!d.bench || !d.summary) return html`<div class="glow"></div><div style="height:100vh;display:flex;align-items:center;justify-content:center;color:#8b93a7;font-family:Space Grotesk">Loading the benchmark…</div>`;
const STAGE_OPTS = [["0.25", "25%"], ["0.40", "40%"], ["0.60", "60%"], ["0.75", "75%"], ["avg", "Avg"]];
return html`
<div class="glow"></div>
<div class="progress" style="width:14%"></div>
<div class="mark" onClick=${() => nav(-99)}>Slipstream</div>
<div class="stagesel" style="opacity:0;pointer-events:none">
<span class="ssl">charts at</span>
${STAGE_OPTS.map(([v, l]) => html`<button class=${"sseg" + (v === stage ? " on" : "")} onClick=${() => setStage(v)}>${l}</button>`)}
</div>
<div class="deck" ref=${deckRef}>
<${Hero} bench=${d.bench} stage=${stage} />
<${Benchmark} summary=${d.summary} />
<${Gap} bench=${d.bench} />
<${Distill} bench=${d.bench} stage=${stage} />
<${Results} bench=${d.bench} stage=${stage} />
<${Modal_} />
<${Demo} />
<${Releases} />
</div>
<div class="controls">
<button class="cbtn" onClick=${() => nav(-1)} title="previous"></button>
<span class="ctl-pos">01 / 0${NAV.length} · Slipstream</span>
<button class="cbtn" onClick=${() => nav(1)} title="next"></button>
</div>
`;
}
render(html`<${App} />`, document.getElementById("app"));