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})
|
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")
|
@app.route("/api/optimize/<job_id>/stream")
|
||||||
@login_required
|
@login_required
|
||||||
def optimize_stream(job_id):
|
def optimize_stream(job_id):
|
||||||
@ -6787,9 +6828,19 @@ function toast(msg, ok) {
|
|||||||
async function startOptimize(runId) {
|
async function startOptimize(runId) {
|
||||||
var r = await fetch('/api/runs/'+runId+'/optimize', {method:'POST'});
|
var r = await fetch('/api/runs/'+runId+'/optimize', {method:'POST'});
|
||||||
var data = await r.json();
|
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; }
|
if (data.error) { toast(data.error, false); return; }
|
||||||
var jobId = data.job_id;
|
var jobId = data.job_id;
|
||||||
|
_showOptimizeStream(runId, jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _showOptimizeStream(runId, jobId) {
|
||||||
var panel = document.getElementById('detail-panel');
|
var panel = document.getElementById('detail-panel');
|
||||||
panel.textContent = '';
|
panel.textContent = '';
|
||||||
|
|
||||||
@ -7036,13 +7087,48 @@ async function openDetail(id) {
|
|||||||
var delBtn = document.createElement('button'); delBtn.className = 'tool-btn red'; delBtn.textContent = 'Delete';
|
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'})} };
|
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);
|
actions.appendChild(delBtn);
|
||||||
|
var meta = run.score_metadata || {};
|
||||||
|
var isOptimized = meta.optimized;
|
||||||
var optBtn = document.createElement('button'); optBtn.className = 'tool-btn';
|
var optBtn = document.createElement('button'); optBtn.className = 'tool-btn';
|
||||||
optBtn.style.cssText = 'color:var(--accent);border-color:var(--accent);margin-left:auto';
|
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); };
|
optBtn.onclick = function(){ startOptimize(id); };
|
||||||
actions.appendChild(optBtn);
|
actions.appendChild(optBtn);
|
||||||
panel.appendChild(actions);
|
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
|
// Responses
|
||||||
var responses = run.responses || [];
|
var responses = run.responses || [];
|
||||||
var respTitle = document.createElement('div');
|
var respTitle = document.createElement('div');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user