feat(dashboard): pending queue + confirm/reject endpoints
This commit is contained in:
171
fastapi_app.py
171
fastapi_app.py
@@ -14,6 +14,7 @@ import sqlite3
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import FastAPI, Form, Query, Request
|
||||
@@ -70,6 +71,73 @@ def parse_engram(row: sqlite3.Row) -> dict:
|
||||
"grounding": meta.get("grounding", 0),
|
||||
}
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _update_correctness(engram_id: str, *, action: str, reason: str | None = None) -> dict:
|
||||
"""
|
||||
Update correctness_json for an engram. action: confirm|reject
|
||||
"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
row = c.execute("SELECT correctness_json FROM engrams WHERE id = ?", (engram_id,)).fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
raise FileNotFoundError(f"Engram not found: {engram_id}")
|
||||
|
||||
corr = json.loads(row["correctness_json"] or "{}")
|
||||
corr.setdefault("confirmed", False)
|
||||
corr.setdefault("confirmations", 0)
|
||||
corr.setdefault("rejections", 0)
|
||||
corr.setdefault("review_history", [])
|
||||
corr["last_reviewed"] = _now_iso()
|
||||
|
||||
entry = {
|
||||
"by": "dashboard",
|
||||
"action": action,
|
||||
"at": corr["last_reviewed"],
|
||||
"note": (reason or "").strip(),
|
||||
}
|
||||
try:
|
||||
corr["review_history"].append(entry)
|
||||
except Exception:
|
||||
corr["review_history"] = [entry]
|
||||
|
||||
if action == "confirm":
|
||||
corr["confirmed"] = True
|
||||
corr["confirmations"] = int(corr.get("confirmations", 0) or 0) + 1
|
||||
elif action == "reject":
|
||||
corr["rejections"] = int(corr.get("rejections", 0) or 0) + 1
|
||||
|
||||
c.execute(
|
||||
"UPDATE engrams SET correctness_json = ?, modified_at = ? WHERE id = ?",
|
||||
(json.dumps(corr, ensure_ascii=False), corr["last_reviewed"], engram_id),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
def _bump_access(engram_id: str) -> dict:
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
row = c.execute("SELECT metadata_json FROM engrams WHERE id = ?", (engram_id,)).fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
raise FileNotFoundError(f"Engram not found: {engram_id}")
|
||||
meta = json.loads(row["metadata_json"] or "{}")
|
||||
meta["access_count"] = int(meta.get("access_count", 0) or 0) + 1
|
||||
meta["last_accessed"] = _now_iso()
|
||||
c.execute(
|
||||
"UPDATE engrams SET metadata_json = ?, modified_at = ? WHERE id = ?",
|
||||
(json.dumps(meta, ensure_ascii=False), meta["last_accessed"], engram_id),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"ok": True}
|
||||
|
||||
def _safe_json_extract_tags(meta_json: str) -> list[str]:
|
||||
try:
|
||||
d = json.loads(meta_json or "{}")
|
||||
@@ -519,6 +587,109 @@ def api_engram_detail(engram_id: str):
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/api/pending")
|
||||
def api_pending(
|
||||
limit: int = Query(20, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
source: str | None = Query(None),
|
||||
):
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
where = ["json_extract(correctness_json, '$.confirmed') = 0"]
|
||||
params: list = []
|
||||
if source:
|
||||
where.append("json_extract(metadata_json, '$.source') = ?")
|
||||
params.append(source)
|
||||
|
||||
rows = c.execute(
|
||||
f"""
|
||||
SELECT * FROM engrams
|
||||
WHERE {' AND '.join(where)}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""",
|
||||
params + [limit, offset],
|
||||
).fetchall()
|
||||
items = [parse_engram(r) for r in rows]
|
||||
conn.close()
|
||||
return {"items": items, "limit": limit, "offset": offset}
|
||||
|
||||
|
||||
@app.post("/api/engrams")
|
||||
def api_create_engram(content: str = Form(...), tags: str = Form("")):
|
||||
content = (content or "").strip()
|
||||
if not content:
|
||||
return JSONResponse({"error": "content required"}, status_code=400)
|
||||
tag_list = [t.strip() for t in (tags or "").split(",") if t.strip()]
|
||||
now = _now_iso()
|
||||
engram_id = str(uuid4())
|
||||
meta = {
|
||||
"source": "user",
|
||||
"confidence": 0.7,
|
||||
"created": now,
|
||||
"modified": now,
|
||||
"access_count": 0,
|
||||
"last_accessed": now,
|
||||
"tags": tag_list,
|
||||
"session_id": None,
|
||||
"agent_id": None,
|
||||
"grounding": 0,
|
||||
}
|
||||
corr = {
|
||||
"confirmed": False,
|
||||
"confirmations": 0,
|
||||
"rejections": 0,
|
||||
"last_reviewed": None,
|
||||
"review_history": [],
|
||||
}
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO engrams (id, content, metadata_json, correctness_json, links_json, hierarchy_json, embedding_json, created_at, modified_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
engram_id,
|
||||
content,
|
||||
json.dumps(meta, ensure_ascii=False),
|
||||
json.dumps(corr, ensure_ascii=False),
|
||||
"[]",
|
||||
"{}",
|
||||
None,
|
||||
now,
|
||||
now,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"id": engram_id}
|
||||
|
||||
|
||||
@app.post("/api/engrams/{engram_id}/confirm")
|
||||
def api_confirm_engram(engram_id: str, reason: str = Form("")):
|
||||
try:
|
||||
return _update_correctness(engram_id, action="confirm", reason=reason)
|
||||
except FileNotFoundError as e:
|
||||
return JSONResponse({"error": str(e)}, status_code=404)
|
||||
|
||||
|
||||
@app.post("/api/engrams/{engram_id}/reject")
|
||||
def api_reject_engram(engram_id: str, reason: str = Form("")):
|
||||
try:
|
||||
return _update_correctness(engram_id, action="reject", reason=reason)
|
||||
except FileNotFoundError as e:
|
||||
return JSONResponse({"error": str(e)}, status_code=404)
|
||||
|
||||
|
||||
@app.post("/api/engrams/{engram_id}/refresh")
|
||||
def api_refresh_engram(engram_id: str):
|
||||
try:
|
||||
return _bump_access(engram_id)
|
||||
except FileNotFoundError as e:
|
||||
return JSONResponse({"error": str(e)}, status_code=404)
|
||||
|
||||
|
||||
@app.post("/api/engrams/{engram_id}/confirm")
|
||||
def api_confirm(engram_id: str, reason: str = Form("")):
|
||||
conn = get_db()
|
||||
|
||||
Reference in New Issue
Block a user