feat: /admin/triage web UI + auto-drain loop
Makes human triage sustainable. Before: command-line-only review,
auto-triage stopped after 100 candidates/run. Now:
1. Web UI at /admin/triage
- Lists all pending candidates with inline promote/reject/edit
- Edit content in-place before promoting (PUT /memory/{id})
- Change type via dropdown
- Keyboard shortcuts: Y=promote, N=reject, E=edit, S=scroll-next
- Cards fade out after action, queue count updates live
- Zero JS framework — vanilla fetch + event delegation
2. auto_triage.py drains queue
- Loops up to 20 batches (default) of 100 candidates each
- Tracks seen IDs so needs_human items don't reprocess
- Exits cleanly when queue empty
- Nightly cron naturally drains everything
3. Dashboard + wiki surface triage queue
- Dashboard /admin/dashboard: new "triage" section with pending
count + /admin/triage URL + warning/notice severity levels
- Wiki homepage: prominent callout "N candidates awaiting triage —
review now" linking to /admin/triage, styled with triage-warning
(>50) or triage-notice (>20) CSS
Pattern: human intervenes only when AI can't decide. The UI makes
that intervention fast (20 candidates in 60 seconds). Nightly
auto-triage drains the queue so the human queue stays bounded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,17 @@ def wiki_search(q: str = "") -> HTMLResponse:
|
||||
return HTMLResponse(content=render_search(q))
|
||||
|
||||
|
||||
@router.get("/admin/triage", response_class=HTMLResponse)
|
||||
def admin_triage(limit: int = 100) -> HTMLResponse:
|
||||
"""Human triage UI for candidate memories.
|
||||
|
||||
Lists pending candidates with inline promote/reject/edit buttons.
|
||||
Keyboard shortcuts: Y=promote, N=reject, E=edit content.
|
||||
"""
|
||||
from atocore.engineering.triage_ui import render_triage_page
|
||||
return HTMLResponse(content=render_triage_page(limit=limit))
|
||||
|
||||
|
||||
# --- Request/Response models ---
|
||||
|
||||
|
||||
@@ -1022,6 +1033,16 @@ def api_dashboard() -> dict:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Triage queue health
|
||||
triage: dict = {
|
||||
"pending": len(candidates),
|
||||
"review_url": "/admin/triage",
|
||||
}
|
||||
if len(candidates) > 50:
|
||||
triage["warning"] = f"High queue: {len(candidates)} candidates pending review."
|
||||
elif len(candidates) > 20:
|
||||
triage["notice"] = f"{len(candidates)} candidates awaiting triage."
|
||||
|
||||
return {
|
||||
"memories": {
|
||||
"active": len(active),
|
||||
@@ -1037,6 +1058,7 @@ def api_dashboard() -> dict:
|
||||
"interactions": interaction_stats,
|
||||
"extraction_pipeline": extract_state,
|
||||
"pipeline": pipeline,
|
||||
"triage": triage,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user