From 804898b658612f40c0951157b198d79f128baeee Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Mar 2026 04:49:17 -0500 Subject: [PATCH] Auto-save self-analysis reports to DB with browsable history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Database: - self_reports table: report_type, model, report text, data_size, timestamp - Reports auto-saved on generation (no extra step needed) API: - GET /api/self-reports — list all past reports (id, type, model, size, date) - GET /api/self-reports/:id — full report text UI: - "✓ Saved as report #N" indicator after generation - "Past Reports (N)" section below self-analysis buttons - Click any past report → expands inline (toggle on/off) - Shows: type, model, timestamp for each saved report - Reports persist across page refreshes and restarts Co-Authored-By: Claude Opus 4.6 (1M context) --- llm_team_ui.py | 118 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/llm_team_ui.py b/llm_team_ui.py index d1b993f..fc6921a 100644 --- a/llm_team_ui.py +++ b/llm_team_ui.py @@ -3388,6 +3388,7 @@ LAB_HTML = r"""

Self-Analysis AI reports from your own system data

+

Experiment Templates click to auto-fill the create form

@@ -3866,14 +3867,84 @@ async function runSelfReport(type, card) { content.textContent = d.report; panel.appendChild(content); card.parentNode.insertBefore(panel, card.nextSibling); - toast('Report generated', true); + if (d.id) { + var saved = document.createElement('div'); + saved.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;color:var(--green);margin-top:8px;text-transform:uppercase;letter-spacing:1px'; + saved.textContent = '✓ Saved as report #' + d.id; + panel.appendChild(saved); + } + toast('Report generated & saved', true); + loadPastReports(); } catch(e) { toast('Error: '+e.message, false); } btn.textContent = 'Run →'; btn.style.color = 'var(--green)'; card.style.borderColor = origBorder; } +async function loadPastReports() { + var el = document.getElementById('past-reports'); + if (!el) return; + try { + var r = await fetch('/api/self-reports'); + var d = await r.json(); + var reports = d.reports || []; + if (!reports.length) { el.textContent = ''; return; } + el.textContent = ''; + var title = document.createElement('div'); + title.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:8px;text-transform:uppercase;letter-spacing:2px;color:var(--text2);margin-bottom:8px;padding-top:8px;border-top:1px solid var(--border)'; + title.textContent = 'Past Reports (' + reports.length + ')'; + el.appendChild(title); + reports.forEach(function(rpt) { + var row = document.createElement('div'); + row.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid rgba(42,45,53,0.3);cursor:pointer;font-size:11px'; + row.onmouseenter = function(){row.style.background='rgba(74,222,128,0.03)'}; + row.onmouseleave = function(){row.style.background='transparent'}; + var typeEl = document.createElement('span'); + typeEl.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;text-transform:uppercase;letter-spacing:0.5px;color:var(--green);font-weight:700;min-width:130px'; + typeEl.textContent = rpt.report_type.replace(/_/g,' '); + var modelEl = document.createElement('span'); + modelEl.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;color:var(--text2);min-width:90px'; + modelEl.textContent = rpt.model; + var dateEl = document.createElement('span'); + dateEl.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;color:var(--text2);margin-left:auto'; + dateEl.textContent = new Date(rpt.created_at).toLocaleString(); + row.appendChild(typeEl); row.appendChild(modelEl); row.appendChild(dateEl); + row.onclick = function(){viewPastReport(rpt.id)}; + el.appendChild(row); + }); + } catch(e) {} +} + +async function viewPastReport(id) { + var resultId = 'report-past-' + id; + var existing = document.getElementById(resultId); + if (existing) { existing.remove(); return; } + try { + var r = await fetch('/api/self-reports/' + id); + var d = await r.json(); + if (d.error) return; + var panel = document.createElement('div'); + panel.id = resultId; + panel.style.cssText = 'background:rgba(0,0,0,0.3);border:2px solid var(--green);border-radius:2px;padding:16px;margin:8px 0;max-height:500px;overflow-y:auto'; + var title = document.createElement('div'); + title.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--green);margin-bottom:8px;display:flex;justify-content:space-between'; + title.textContent = d.report_type.replace(/_/g,' ') + ' — ' + d.model + ' — ' + new Date(d.created_at).toLocaleString(); + var closeBtn = document.createElement('span'); + closeBtn.style.cssText = 'cursor:pointer;opacity:0.6'; + closeBtn.textContent = '✕'; + closeBtn.onclick = function(e){e.stopPropagation();panel.remove()}; + title.appendChild(closeBtn); + panel.appendChild(title); + var content = document.createElement('div'); + content.style.cssText = 'font-size:12px;line-height:1.7;white-space:pre-wrap;color:var(--text)'; + content.textContent = d.report; + panel.appendChild(content); + document.getElementById('past-reports').appendChild(panel); + } catch(e) {} +} + renderSelfReports(); +loadPastReports(); function renderTemplates() { var el = document.getElementById('template-list'); @@ -5689,7 +5760,50 @@ def self_analyze(): except Exception as e: return jsonify({"error": str(e)}), 500 - return jsonify({"report": report, "type": report_type, "model": model, "data_size": len(context)}) + # Save to DB + report_id = None + try: + with get_db() as conn: + with conn.cursor() as cur: + cur.execute("INSERT INTO self_reports (report_type, model, report, data_size) VALUES (%s,%s,%s,%s) RETURNING id", + (report_type, model, report, len(context))) + report_id = cur.fetchone()[0] + conn.commit() + except Exception: + pass + + return jsonify({"report": report, "type": report_type, "model": model, "data_size": len(context), "id": report_id}) + + +@app.route("/api/self-reports") +@admin_required +def list_self_reports(): + try: + with get_db() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute("SELECT id, report_type, model, LENGTH(report) as report_len, data_size, created_at FROM self_reports ORDER BY created_at DESC LIMIT 50") + rows = cur.fetchall() + for r in rows: + r["created_at"] = r["created_at"].isoformat() + return jsonify({"reports": rows}) + except Exception as e: + return jsonify({"reports": [], "error": str(e)}) + + +@app.route("/api/self-reports/") +@admin_required +def get_self_report(rid): + try: + with get_db() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute("SELECT * FROM self_reports WHERE id = %s", (rid,)) + row = cur.fetchone() + if not row: + return jsonify({"error": "not found"}), 404 + row["created_at"] = row["created_at"].isoformat() + return jsonify(row) + except Exception as e: + return jsonify({"error": str(e)}), 500 @app.route("/api/pipelines")