diff --git a/llm_team_ui.py b/llm_team_ui.py
index 82f7f20..7233526 100644
--- a/llm_team_ui.py
+++ b/llm_team_ui.py
@@ -2587,29 +2587,47 @@ function toggleHistory() {
}
}
+var _historyView = 'active'; // active or archived
+
async function loadHistory() {
- const r = await fetch('/api/runs');
- const data = await r.json();
+ var show = _historyView === 'archived' ? 'archived' : 'active';
+ var r = await fetch('/api/runs?show=' + show);
+ var data = await r.json();
historyRuns = data.runs || [];
renderHistoryList();
}
function renderHistoryList() {
- const el = document.getElementById('hp-content');
+ var el = document.getElementById('hp-content');
+ var isArchived = _historyView === 'archived';
+
+ // Toggle bar
+ var toggleBar = '
No runs saved yet. Run a team to see history here.
';
+ el.innerHTML = toggleBar + '' + (isArchived ? 'No archived runs.' : 'No active runs. Run a team to see history here.') + '
';
return;
}
- el.innerHTML = '' + historyRuns.map(r => {
- const d = new Date(r.created_at);
- const time = d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
- const models = (r.models_used || []).length;
- const prompt = (r.prompt || '').substring(0, 80);
- return `
-
${r.mode}
-
${escapeHtml(prompt)}
-
${time}${models} model${models!==1?'s':''}
-
`;
+ el.innerHTML = toggleBar + '
' + historyRuns.map(function(r) {
+ var d = new Date(r.created_at);
+ var time = d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
+ var models = (r.models_used || []).length;
+ var prompt = (r.prompt || '').substring(0, 80);
+ return '
'
+ + '
' + r.mode + '
'
+ + '
' + escapeHtml(prompt) + '
'
+ + '
' + time + '' + models + ' model' + (models!==1?'s':'') + '
'
+ + '
';
}).join('') + '
';
}
@@ -2625,6 +2643,12 @@ async function viewRun(id) {
html += `
${escapeHtml(run.prompt)}
`;
html += `
`;
html += ``;
+ var isArch = run.archived;
+ if (isArch) {
+ html += ``;
+ } else {
+ html += ``;
+ }
html += ``;
html += `
`;
responses.forEach((resp, ri) => {
@@ -2651,10 +2675,33 @@ async function rerunFromHistory(id) {
toggleHistory();
}
+async function archiveRun(id) {
+ await fetch('/api/runs/' + id + '/archive', {method: 'POST'});
+ await loadHistory();
+}
+
+async function restoreRun(id) {
+ await fetch('/api/runs/' + id + '/restore', {method: 'POST'});
+ await loadHistory();
+}
+
+async function bulkArchive() {
+ if (!confirm('Archive all ' + historyRuns.length + ' active runs?')) return;
+ var ids = historyRuns.map(function(r) { return r.id; });
+ await fetch('/api/runs/bulk-archive', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({action: 'archive', ids: ids})});
+ await loadHistory();
+}
+
+async function bulkRestore() {
+ if (!confirm('Restore all ' + historyRuns.length + ' archived runs?')) return;
+ var ids = historyRuns.map(function(r) { return r.id; });
+ await fetch('/api/runs/bulk-archive', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({action: 'restore', ids: ids})});
+ await loadHistory();
+}
+
async function deleteRun(id) {
await fetch('/api/runs/' + id, {method: 'DELETE'});
await loadHistory();
- renderHistoryList();
}
// ─── DEMO MODE ───────────────────────────────
@@ -4844,10 +4891,16 @@ setInterval(poll,3000);
@app.route("/api/runs")
@login_required
def get_runs():
+ show = request.args.get("show", "active") # active, archived, all
try:
with get_db() as conn:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
- cur.execute("SELECT id, mode, prompt, models_used, created_at FROM team_runs ORDER BY created_at DESC LIMIT 50")
+ if show == "archived":
+ cur.execute("SELECT id, mode, prompt, models_used, created_at, archived FROM team_runs WHERE archived = true ORDER BY created_at DESC LIMIT 200")
+ elif show == "all":
+ cur.execute("SELECT id, mode, prompt, models_used, created_at, archived FROM team_runs ORDER BY created_at DESC LIMIT 200")
+ else:
+ cur.execute("SELECT id, mode, prompt, models_used, created_at, archived FROM team_runs WHERE archived = false ORDER BY created_at DESC LIMIT 50")
runs = cur.fetchall()
for r in runs:
r["created_at"] = r["created_at"].isoformat()
@@ -4885,6 +4938,60 @@ def delete_run(run_id):
return jsonify({"error": str(e)}), 500
+@app.route("/api/runs/
/archive", methods=["POST"])
+@login_required
+def archive_run(run_id):
+ try:
+ with get_db() as conn:
+ with conn.cursor() as cur:
+ cur.execute("UPDATE team_runs SET archived = true WHERE id = %s", (run_id,))
+ conn.commit()
+ return jsonify({"ok": True})
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+
+@app.route("/api/runs//restore", methods=["POST"])
+@login_required
+def restore_run(run_id):
+ try:
+ with get_db() as conn:
+ with conn.cursor() as cur:
+ cur.execute("UPDATE team_runs SET archived = false WHERE id = %s", (run_id,))
+ conn.commit()
+ return jsonify({"ok": True})
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+
+@app.route("/api/runs/bulk-archive", methods=["POST"])
+@admin_required
+def bulk_archive_runs():
+ data = request.json or {}
+ action = data.get("action", "archive") # archive or restore
+ ids = data.get("ids", [])
+ before = data.get("before") # archive all before this date
+ try:
+ with get_db() as conn:
+ with conn.cursor() as cur:
+ archived_val = action == "archive"
+ if ids:
+ cur.execute("UPDATE team_runs SET archived = %s WHERE id = ANY(%s)", (archived_val, ids))
+ count = cur.rowcount
+ elif before:
+ cur.execute("UPDATE team_runs SET archived = %s WHERE created_at < %s AND archived = %s",
+ (archived_val, before, not archived_val))
+ count = cur.rowcount
+ else:
+ # Archive all
+ cur.execute("UPDATE team_runs SET archived = true WHERE archived = false")
+ count = cur.rowcount
+ conn.commit()
+ return jsonify({"ok": True, "count": count})
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+
@app.route("/api/pipelines")
@login_required
def get_pipelines():