From bc2ad7c1a985d57a3f7c55cd1c55dcd3291fca48 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 29 Mar 2026 07:14:12 -0500 Subject: [PATCH] Fix Lab UX: visual selection, auto-navigate, live status, stuck detection Lab experiment selection: - Selected experiment now highlighted with accent border + glow - Clicking auto-navigates to relevant tab (config if idle, monitor if running) - No more silent toast-only feedback Live status display: - SSE "status" events now rendered in monitor (were silently dropped before) - Shows real-time: "Proposing change... (trial 3/50)" during execution - Error messages displayed inline instead of just toast Stuck experiment fix: - On app startup, reset all "running" experiments to "paused" - Prevents ghost "running" status after service restart - Fixed experiments 2, 3, 4 that showed running but had dead threads Trial cap fix: - Changed from lifetime cap (trial_num < 50) to per-run cap (trials_this_run < 50) - Prevents runaway experiments like #1 that accumulated 3762 trials - Shows trial progress in status: "trial 3/50" Co-Authored-By: Claude Opus 4.6 (1M context) --- llm_team_ui.py | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/llm_team_ui.py b/llm_team_ui.py index 3424a27..316ce10 100644 --- a/llm_team_ui.py +++ b/llm_team_ui.py @@ -4229,8 +4229,10 @@ LAB_HTML = r""" .btn-r:hover{background:rgba(224,82,82,0.06)} .btn-o{border-color:rgba(245,158,11,0.3);color:var(--orange)} .btn-o:hover{background:rgba(245,158,11,0.06)} - .exp-item{background:rgba(0,0,0,0.25);border:2px solid var(--border);border-radius:2px;padding:14px;margin-bottom:8px;cursor:pointer;transition:border-color .15s} + .exp-item{background:rgba(0,0,0,0.25);border:2px solid var(--border);border-radius:2px;padding:14px;margin-bottom:8px;cursor:pointer;transition:all .15s} .exp-item:hover{border-color:var(--accent)} + .exp-item.selected{border-color:var(--accent);background:var(--glow);box-shadow:0 0 12px rgba(226,181,90,0.08)} + .live-status{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text2);margin-top:6px;min-height:14px;font-style:italic} .exp-item .name{font-weight:600;font-size:14px} .exp-item .meta{font-size:10px;color:var(--text2);display:flex;gap:12px;margin-top:4px;font-family:'JetBrains Mono',monospace} .status-pill{display:inline-block;padding:2px 8px;border-radius:2px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;font-family:'JetBrains Mono',monospace;border:1px solid} @@ -4349,12 +4351,13 @@ LAB_HTML = r""" -
+
Status: idle
Trials: 0
Best: 0.0/10
Improvements: 0
+

Score Progression

@@ -4408,7 +4411,8 @@ function renderExpList() { if (!experiments.length) { el.innerHTML = '
No experiments yet. Create one to get started.
'; return; } el.innerHTML = experiments.map(e => { const rate = e.total_trials > 0 ? ((e.improvements / e.total_trials) * 100).toFixed(0) : 0; - return `
+ const sel = activeExp && activeExp.id === e.id ? ' selected' : ''; + return `
${e.name} ${e.status}
Trials: ${e.total_trials}Best: ${(e.best_score||0).toFixed(1)}/10Improvements: ${e.improvements} (${rate}%)${e.metric}
`; @@ -4421,7 +4425,16 @@ async function selectExp(id) { trialData = activeExp.trials || []; updateMonitor(); updateConfigEditor(); - toast('Loaded: ' + activeExp.name); + renderExpList(); + // Auto-navigate to the relevant tab + if (activeExp.status === 'running') { + labTab('monitor'); + startStream(); + } else if (activeExp.status === 'idle' && activeExp.total_trials === 0) { + labTab('config'); + } else { + labTab('monitor'); + } } function updateMonitor() { @@ -4507,12 +4520,21 @@ function startStream() { activeExp.best_score = d.best; if (d.improved) activeExp.improvements = (activeExp.improvements||0) + 1; updateMonitor(); + renderExpList(); + } else if (d.type === 'status') { + var liveEl = document.getElementById('live-status'); + if (liveEl) liveEl.textContent = d.message || ''; } else if (d.type === 'done') { activeExp.status = 'paused'; updateMonitor(); + renderExpList(); + var liveEl = document.getElementById('live-status'); + if (liveEl) liveEl.textContent = 'Completed'; es.close(); } else if (d.type === 'error') { toast(d.message, false); + var liveEl = document.getElementById('live-status'); + if (liveEl) liveEl.textContent = 'Error: ' + (d.message||'').substring(0,100); } }; es.onerror = function() { es.close(); }; @@ -7800,9 +7822,10 @@ def _ratchet_loop(exp_id): # Pick meta-model (largest in pool) meta_model = models_pool[-1] if models_pool else "qwen2.5:latest" judge_model = models_pool[0] if models_pool else "llama3.2:latest" - max_trials = 50 # safety cap — prevents runaway experiments + max_new_trials = 50 # safety cap — max trials per start + trials_this_run = 0 - while trial_num < max_trials: + while trials_this_run < max_new_trials: # Check if still running with get_db() as conn: with conn.cursor() as cur: @@ -7812,8 +7835,9 @@ def _ratchet_loop(exp_id): break trial_num += 1 + trials_this_run += 1 trial_start = time.time() - _lab_emit(exp_id, {"type": "status", "trial": trial_num, "message": "Proposing change..."}) + _lab_emit(exp_id, {"type": "status", "trial": trial_num, "message": f"Proposing change... (trial {trials_this_run}/{max_new_trials})"}) # Step 1: Meta-model proposes a change history_hint = "" @@ -9721,6 +9745,14 @@ _sentinel_thread.start() if __name__ == "__main__": + # Cleanup stale lab experiments on startup + try: + with get_db() as conn: + with conn.cursor() as cur: + cur.execute("UPDATE lab_experiments SET status = 'paused' WHERE status = 'running'") + conn.commit() + except Exception: + pass print("\n LLM Team UI running at http://localhost:5000\n") print(f" AI Sentinel active: {SENTINEL_MODEL} scanning every {SENTINEL_INTERVAL}s\n") app.run(host="127.0.0.1", port=5000, debug=False, threaded=True)