Add optimization history, reconnect, and duplicate prevention
History detail panel now shows optimization results: - If a run has been optimized, shows results section with best score, original score, and link to view the winning variation - Fetches full optimization history via GET /api/optimize-history/<id> - Shows count of optimizations run and child variation count - Button changes to "Re-Optimize" for already-optimized runs Reconnect to active optimizations: - If optimization is already running, returns job_id in error response - Frontend detects this and reconnects to the SSE stream - No more losing progress when navigating away and coming back - Refactored startOptimize() into startOptimize() + _showOptimizeStream() New endpoint: GET /api/optimize-history/<run_id> - Returns all pipeline_runs where pipeline='optimize' for that parent - Returns all child team_runs created by optimization - Includes scores, strategies, rankings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bc2ad7c1a9
commit
7b9b7f6641
@ -6485,6 +6485,47 @@ def start_optimize(run_id):
|
||||
return jsonify({"ok": True, "job_id": job_id})
|
||||
|
||||
|
||||
@app.route("/api/optimize-history/<int:run_id>")
|
||||
@login_required
|
||||
def optimize_history(run_id):
|
||||
"""Get optimization results for a specific parent run."""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
|
||||
# Find all optimize pipeline runs for this parent
|
||||
cur.execute("""
|
||||
SELECT id, result, duration_ms, completed_at
|
||||
FROM pipeline_runs
|
||||
WHERE pipeline = 'optimize' AND result->>'parent_run' = %s
|
||||
ORDER BY completed_at DESC
|
||||
""", (str(run_id),))
|
||||
results = []
|
||||
for r in cur.fetchall():
|
||||
res = r.get("result") or {}
|
||||
results.append({
|
||||
"id": r["id"],
|
||||
"best_score": res.get("best_score"),
|
||||
"original_score": res.get("original_score"),
|
||||
"improvement": res.get("improvement"),
|
||||
"variations_tested": res.get("variations_tested"),
|
||||
"calls_used": res.get("calls_used"),
|
||||
"ranked": res.get("ranked", [])[:3],
|
||||
"completed_at": str(r["completed_at"]) if r["completed_at"] else None,
|
||||
"duration_ms": r["duration_ms"],
|
||||
})
|
||||
# Also find child runs
|
||||
cur.execute("""
|
||||
SELECT id, mode, quality_score, config->>'strategy' as strategy, config->>'variation' as variation
|
||||
FROM team_runs
|
||||
WHERE config->>'parent_run' = %s
|
||||
ORDER BY quality_score DESC NULLS LAST
|
||||
""", (str(run_id),))
|
||||
children = [dict(r) for r in cur.fetchall()]
|
||||
return jsonify({"results": results, "children": children})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e), "results": [], "children": []}), 500
|
||||
|
||||
|
||||
@app.route("/api/optimize/<job_id>/stream")
|
||||
@login_required
|
||||
def optimize_stream(job_id):
|
||||
@ -6787,9 +6828,19 @@ function toast(msg, ok) {
|
||||
async function startOptimize(runId) {
|
||||
var r = await fetch('/api/runs/'+runId+'/optimize', {method:'POST'});
|
||||
var data = await r.json();
|
||||
if (data.error && data.job_id) {
|
||||
// Already running — reconnect to existing stream
|
||||
toast('Reconnecting to active optimization...', true);
|
||||
var jobId = data.job_id;
|
||||
_showOptimizeStream(runId, jobId);
|
||||
return;
|
||||
}
|
||||
if (data.error) { toast(data.error, false); return; }
|
||||
var jobId = data.job_id;
|
||||
_showOptimizeStream(runId, jobId);
|
||||
}
|
||||
|
||||
function _showOptimizeStream(runId, jobId) {
|
||||
var panel = document.getElementById('detail-panel');
|
||||
panel.textContent = '';
|
||||
|
||||
@ -7036,13 +7087,48 @@ async function openDetail(id) {
|
||||
var delBtn = document.createElement('button'); delBtn.className = 'tool-btn red'; delBtn.textContent = 'Delete';
|
||||
delBtn.onclick = function(){ if(confirm('Delete permanently?')){fetch('/api/runs/'+id,{method:'DELETE'}).then(function(){toast('Deleted',true);loadRuns();panel.className='detail-panel'})} };
|
||||
actions.appendChild(delBtn);
|
||||
var meta = run.score_metadata || {};
|
||||
var isOptimized = meta.optimized;
|
||||
var optBtn = document.createElement('button'); optBtn.className = 'tool-btn';
|
||||
optBtn.style.cssText = 'color:var(--accent);border-color:var(--accent);margin-left:auto';
|
||||
optBtn.textContent = '\u26A1 Optimize';
|
||||
optBtn.textContent = isOptimized ? '\u26A1 Re-Optimize' : '\u26A1 Optimize';
|
||||
optBtn.onclick = function(){ startOptimize(id); };
|
||||
actions.appendChild(optBtn);
|
||||
panel.appendChild(actions);
|
||||
|
||||
// Optimization history
|
||||
if (isOptimized) {
|
||||
var optSection = document.createElement('div');
|
||||
optSection.style.cssText = 'background:var(--glow);border:1px solid var(--accent);border-radius:2px;padding:12px;margin-bottom:12px';
|
||||
var optTitle = document.createElement('div');
|
||||
optTitle.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--accent);margin-bottom:6px;font-weight:700';
|
||||
optTitle.textContent = 'Optimization Results';
|
||||
optSection.appendChild(optTitle);
|
||||
var optInfo = document.createElement('div');
|
||||
optInfo.style.cssText = 'font-size:12px;color:var(--text);line-height:1.6';
|
||||
var bestId = meta.best_variation_run;
|
||||
var jobId = meta.optimize_job || '';
|
||||
optInfo.textContent = 'Best variation: Run #' + (bestId||'?') + ' | Job: ' + jobId;
|
||||
optSection.appendChild(optInfo);
|
||||
if (bestId) {
|
||||
var viewBtn = document.createElement('button'); viewBtn.className = 'tool-btn';
|
||||
viewBtn.style.cssText = 'margin-top:8px;font-size:9px';
|
||||
viewBtn.textContent = 'View Best Variation (#' + bestId + ')';
|
||||
viewBtn.onclick = function(){ openDetail(bestId); };
|
||||
optSection.appendChild(viewBtn);
|
||||
}
|
||||
// Load pipeline results for more detail
|
||||
fetch('/api/optimize-history/' + id).then(function(r){return r.json()}).then(function(d){
|
||||
if (d.results && d.results.length) {
|
||||
var count = document.createElement('div');
|
||||
count.style.cssText = 'font-size:11px;color:var(--text2);margin-top:6px;font-family:JetBrains Mono,monospace';
|
||||
count.textContent = d.results.length + ' optimization(s) run | Best: ' + (d.results[0].best_score||'?') + '/10 | Original: ' + (d.results[0].original_score||'?') + '/10';
|
||||
optSection.appendChild(count);
|
||||
}
|
||||
}).catch(function(){});
|
||||
panel.appendChild(optSection);
|
||||
}
|
||||
|
||||
// Responses
|
||||
var responses = run.responses || [];
|
||||
var respTitle = document.createElement('div');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user