forked from Rockachopa/Timmy-time-dashboard
Task Queue system: - New /tasks page with three-column layout (Pending/Active/Completed) - Full CRUD API at /api/tasks with approve/veto/modify/pause/cancel/retry - SQLite persistence in task_queue table - WebSocket live updates via ws_manager - Create task modal with agent assignment and priority - Auto-approve rules for low-risk tasks - HTMX polling for real-time column updates - HOME TASK buttons now link to task queue with agent pre-selected - MARKET HIRE buttons link to task queue with agent pre-selected Work Order system: - External submission API for agents/users (POST /work-orders/submit) - Risk scoring and configurable auto-execution thresholds - Dashboard at /work-orders/queue with approve/reject/execute flow - Integration with swarm task system for execution UI & Dashboard bug fixes: - EVENTS: add startup event so page is never empty - LEDGER: fix empty filter params in URL - MISSION CONTROL: LLM backend and model now read from /health - MISSION CONTROL: agent count fallback to /swarm/agents - SWARM: HTMX fallback loads initial data if WebSocket is slow - MEMORY: add edit/delete buttons for personal facts - UPGRADES: add empty state guidance with links - BRIEFING: add regenerate button and POST /briefing/regenerate endpoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
121 lines
3.2 KiB
Python
121 lines
3.2 KiB
Python
"""Memory (vector store) routes for browsing and searching memories."""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Form, HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from memory.vector_store import (
|
|
store_memory,
|
|
search_memories,
|
|
get_memory_stats,
|
|
recall_personal_facts,
|
|
recall_personal_facts_with_ids,
|
|
store_personal_fact,
|
|
update_personal_fact,
|
|
delete_memory,
|
|
)
|
|
|
|
router = APIRouter(prefix="/memory", tags=["memory"])
|
|
templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
|
async def memory_page(
|
|
request: Request,
|
|
query: Optional[str] = None,
|
|
context_type: Optional[str] = None,
|
|
agent_id: Optional[str] = None,
|
|
):
|
|
"""Memory browser and search page."""
|
|
results = []
|
|
if query:
|
|
results = search_memories(
|
|
query=query,
|
|
context_type=context_type,
|
|
agent_id=agent_id,
|
|
limit=20,
|
|
)
|
|
|
|
stats = get_memory_stats()
|
|
facts = recall_personal_facts_with_ids()[:10]
|
|
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"memory.html",
|
|
{
|
|
"page_title": "Memory Browser",
|
|
"query": query,
|
|
"results": results,
|
|
"stats": stats,
|
|
"facts": facts,
|
|
"filter_type": context_type,
|
|
"filter_agent": agent_id,
|
|
},
|
|
)
|
|
|
|
|
|
@router.post("/search", response_class=HTMLResponse)
|
|
async def memory_search(
|
|
request: Request,
|
|
query: str = Form(...),
|
|
context_type: Optional[str] = Form(None),
|
|
):
|
|
"""Search memories (form submission)."""
|
|
results = search_memories(
|
|
query=query,
|
|
context_type=context_type,
|
|
limit=20,
|
|
)
|
|
|
|
# Return partial for HTMX
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"partials/memory_results.html",
|
|
{
|
|
"query": query,
|
|
"results": results,
|
|
},
|
|
)
|
|
|
|
|
|
@router.post("/fact", response_class=HTMLResponse)
|
|
async def add_fact(
|
|
request: Request,
|
|
fact: str = Form(...),
|
|
agent_id: Optional[str] = Form(None),
|
|
):
|
|
"""Add a personal fact to memory."""
|
|
store_personal_fact(fact, agent_id=agent_id)
|
|
|
|
facts = recall_personal_facts_with_ids()[:10]
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"partials/memory_facts.html",
|
|
{"facts": facts},
|
|
)
|
|
|
|
|
|
@router.put("/fact/{fact_id}", response_class=JSONResponse)
|
|
async def edit_fact(fact_id: str, request: Request):
|
|
"""Update a personal fact."""
|
|
body = await request.json()
|
|
new_content = body.get("content", "").strip()
|
|
if not new_content:
|
|
raise HTTPException(400, "Content cannot be empty")
|
|
ok = update_personal_fact(fact_id, new_content)
|
|
if not ok:
|
|
raise HTTPException(404, "Fact not found")
|
|
return {"success": True, "id": fact_id, "content": new_content}
|
|
|
|
|
|
@router.delete("/fact/{fact_id}", response_class=JSONResponse)
|
|
async def delete_fact(fact_id: str):
|
|
"""Delete a personal fact."""
|
|
ok = delete_memory(fact_id)
|
|
if not ok:
|
|
raise HTTPException(404, "Fact not found")
|
|
return {"success": True, "id": fact_id}
|