Management UI: corpus delete, process panel, activity feed, diagnostics

- DELETE /api/training/corpus/{id} + delete button on training page,
  with confirmation dialog and recompute hint
- /api/system/tasks + floating process panel (bottom-left) showing
  active background tasks with live 3s polling
- /api/system/recent-activity derives a feed from cases, style_corpus,
  and last style_patterns run; sidebar on home page renders with
  relative timestamps
- /api/system/diagnostics + /#/diagnostics page showing DB health,
  row counts per table, active tasks, stuck documents (>10 min),
  failed extractions
- Cosmetic: signature phrase headline now prefers clean phrases over
  bracket-heavy templates for display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 12:04:13 +00:00
parent fcb2e1a325
commit 3e0221ccec
3 changed files with 689 additions and 23 deletions

View File

@@ -794,6 +794,51 @@ async def add_to_style_corpus(
return corpus_id
async def delete_from_style_corpus(corpus_id: UUID) -> dict:
"""Remove a decision from style_corpus + related documents (cascades chunks).
Also tries to delete the [קורפוס] document associated by title match,
since the current training pipeline inserts style_corpus with document_id=NULL.
"""
pool = await get_pool()
async with pool.acquire() as conn:
async with conn.transaction():
row = await conn.fetchrow(
"DELETE FROM style_corpus WHERE id = $1 "
"RETURNING decision_number, document_id",
corpus_id,
)
if not row:
return {"deleted": False, "reason": "not found"}
docs_deleted = 0
if row["document_id"]:
await conn.execute(
"DELETE FROM documents WHERE id = $1", row["document_id"]
)
docs_deleted = 1
else:
# Best-effort: match a [קורפוס] document by the decision_number
# in its title. Only for single, unambiguous matches.
if row["decision_number"]:
docs = await conn.fetch(
"SELECT id FROM documents "
"WHERE case_id IS NULL AND title LIKE $1",
f"%{row['decision_number']}%",
)
if len(docs) == 1:
await conn.execute(
"DELETE FROM documents WHERE id = $1", docs[0]["id"]
)
docs_deleted = 1
return {
"deleted": True,
"decision_number": row["decision_number"],
"docs_deleted": docs_deleted,
}
async def get_style_patterns(pattern_type: str | None = None) -> list[dict]:
pool = await get_pool()
async with pool.acquire() as conn: