fix: live-activity persistence on refresh + reports page rebuild
Refresh-during-run no longer wipes the dashboard - pipeline now keeps in-memory rolling logs of ai_thinking, param_changes, validation events, and the current early-termination state. Logs reset at the start of each new run (capped: 200 thinking / 50 param-change records / 40 validation events) - new GET /api/live_activity returns those logs + current phase + running state in one shot — the dashboard hits it on page load - restoreHistory() in dashboard.js now replays each event into addThinking / addParamChanges / validationRunStart-Complete-Done / showEarlyTermination, and re-applies the active phase via setPhaseActive. Reload F5 mid-run no longer shows a fresh empty dashboard - addThinking() preserves the original ts on replay (was using nowStr() so every replayed entry got the refresh time) Reports page (/reports) rebuilt - previous template crashed with 500 on legacy summary.json files that pre-date the win_rate / drawdown_pct fields. Server now backfills sane defaults for every metric the template touches - runs now sorted by ts (was filesystem-iterdir order) - new template: search bar + filter chips (All / Exploration / AI Iteration / Validation / AI insight / Has .set), phase tags, AI/.set badges, three action buttons per card (View Params / Download .set / Full Report) - per-card detail modal shows metrics grid + full parameters table + AI reasoning + Download .set button — the missing "click to see params + download" path the user reported - has_set detected per-run by globbing run_dir/*.set; AI insight tag shown when ai_insight.json exists on disk
This commit is contained in:
@@ -1043,12 +1043,22 @@ function renderThinkingFeed() {
|
||||
}
|
||||
|
||||
function addThinking(d) {
|
||||
// For replayed events, use the original ts from the server. For live ones, use now.
|
||||
let timeLabel = nowStr();
|
||||
if (d.ts) {
|
||||
try {
|
||||
const dt = new Date(d.ts);
|
||||
if (!isNaN(dt)) {
|
||||
timeLabel = dt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
const entry = {
|
||||
msg: d.msg || '',
|
||||
kind: d.kind || 'info',
|
||||
iteration: d.iteration,
|
||||
phase: d.phase,
|
||||
time: nowStr(),
|
||||
time: timeLabel,
|
||||
};
|
||||
thinkingFeedState.entries.push(entry);
|
||||
if (thinkingFeedState.entries.length > MAX_THINKING_ENTRIES) {
|
||||
@@ -1324,9 +1334,10 @@ safe('btn-stop', stopBtn => {
|
||||
|
||||
async function restoreHistory() {
|
||||
try {
|
||||
const [histResp, aiResp] = await Promise.all([
|
||||
const [histResp, aiResp, liveResp] = await Promise.all([
|
||||
fetch('/api/history'),
|
||||
fetch('/api/ai_insight/latest'),
|
||||
fetch('/api/live_activity'),
|
||||
]);
|
||||
|
||||
const runs = await histResp.json();
|
||||
@@ -1366,6 +1377,60 @@ async function restoreHistory() {
|
||||
updateAIPanel(insight);
|
||||
addLog('AI insight restored (run: ' + (insight.run_id || '?') + ').', 'info');
|
||||
}
|
||||
|
||||
// ── Restore Live Intelligence (thinking feed, param changes, validation, banner) ──
|
||||
// This is what makes a refresh-mid-run feel like nothing happened.
|
||||
const live = await liveResp.json();
|
||||
if (live) {
|
||||
// Thinking feed
|
||||
const thinking = Array.isArray(live.thinking) ? live.thinking : [];
|
||||
thinking.forEach(t => addThinking(t));
|
||||
|
||||
// Param changes
|
||||
const pcs = Array.isArray(live.param_changes) ? live.param_changes : [];
|
||||
pcs.forEach(pc => addParamChanges(pc));
|
||||
|
||||
// Validation panel
|
||||
const val = Array.isArray(live.validation) ? live.validation : [];
|
||||
val.forEach(v => {
|
||||
if (v.event === 'validation_start') {
|
||||
validationState.runs = [];
|
||||
validationState.planned = v.planned_runs || 0;
|
||||
validationState.complete = 0;
|
||||
validationState.active = true;
|
||||
} else if (v.event === 'validation_run_start') {
|
||||
validationRunStart(v);
|
||||
} else if (v.event === 'validation_run_complete') {
|
||||
validationRunComplete(v);
|
||||
} else if (v.event === 'validation_done') {
|
||||
validationDone(v);
|
||||
}
|
||||
});
|
||||
if (val.length) renderValidation();
|
||||
|
||||
// Early-termination banner
|
||||
if (live.early_termination) {
|
||||
showEarlyTermination(live.early_termination);
|
||||
}
|
||||
|
||||
// Phase tracker — restore active phase even if no events have arrived yet
|
||||
const phaseMapLocal = { phase1: 1, phase2: 2, phase2_ai: 2, phase3: 3, phase3_oos: 3, phase3_sens: 3 };
|
||||
const ph = phaseMapLocal[live.phase] || (live.running ? 1 : 0);
|
||||
if (ph) {
|
||||
setPhaseActive(ph, { mode: live.phase_mode });
|
||||
}
|
||||
if (live.running) {
|
||||
setRunningState(true, live.phase || 'Optimizing...');
|
||||
}
|
||||
|
||||
if (thinking.length || pcs.length || val.length) {
|
||||
addLog(
|
||||
`Restored live activity: ${thinking.length} thinking, `
|
||||
+ `${pcs.length} param-change iterations, ${val.length} validation events.`,
|
||||
'info'
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
addLog('History restore: ' + e.message, 'warn');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user