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) <noreply@anthropic.com>
This commit is contained in:
parent
3b4fa449f1
commit
bc2ad7c1a9
@ -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"""
|
||||
<button class="btn btn-r" onclick="resetExp()">Reset</button>
|
||||
</div>
|
||||
</h3>
|
||||
<div style="display:flex;gap:16px;margin-bottom:14px;font-size:13px">
|
||||
<div style="display:flex;gap:16px;margin-bottom:8px;font-size:13px">
|
||||
<div>Status: <span class="status-pill" id="mon-status">idle</span></div>
|
||||
<div>Trials: <strong id="mon-trials">0</strong></div>
|
||||
<div>Best: <strong id="mon-best" style="color:var(--green)">0.0</strong>/10</div>
|
||||
<div>Improvements: <strong id="mon-impr">0</strong></div>
|
||||
</div>
|
||||
<div class="live-status" id="live-status"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Score Progression</h3>
|
||||
@ -4408,7 +4411,8 @@ function renderExpList() {
|
||||
if (!experiments.length) { el.innerHTML = '<div class="empty">No experiments yet. Create one to get started.</div>'; return; }
|
||||
el.innerHTML = experiments.map(e => {
|
||||
const rate = e.total_trials > 0 ? ((e.improvements / e.total_trials) * 100).toFixed(0) : 0;
|
||||
return `<div class="exp-item" onclick="selectExp(${e.id})">
|
||||
const sel = activeExp && activeExp.id === e.id ? ' selected' : '';
|
||||
return `<div class="exp-item${sel}" onclick="selectExp(${e.id})">
|
||||
<div class="name">${e.name} <span class="status-pill ${e.status}">${e.status}</span></div>
|
||||
<div class="meta"><span>Trials: ${e.total_trials}</span><span>Best: ${(e.best_score||0).toFixed(1)}/10</span><span>Improvements: ${e.improvements} (${rate}%)</span><span>${e.metric}</span></div>
|
||||
</div>`;
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user