169 lines
5.9 KiB
Python
169 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app_dependencies import get_runtime
|
|
from message_pipeline import typed_message_from_api_notification
|
|
from nanobot_api_client import run_nanobot_agent_turn
|
|
from route_helpers import read_json_request
|
|
from web_runtime import WebAppRuntime
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/inbox")
|
|
async def get_inbox(
|
|
request: Request, runtime: WebAppRuntime = Depends(get_runtime)
|
|
) -> JSONResponse:
|
|
status_filter = str(request.query_params.get("status", "") or "").strip() or None
|
|
kind_filter = str(request.query_params.get("kind", "") or "").strip() or None
|
|
include_closed = str(request.query_params.get("include_closed", "") or "").strip().lower() in {
|
|
"1",
|
|
"true",
|
|
"yes",
|
|
"on",
|
|
}
|
|
limit_raw = str(request.query_params.get("limit", "") or "").strip()
|
|
try:
|
|
limit = max(1, min(int(limit_raw), 50)) if limit_raw else None
|
|
except ValueError:
|
|
return JSONResponse({"error": "limit must be an integer"}, status_code=400)
|
|
|
|
tags_raw = request.query_params.getlist("tag")
|
|
tags: list[str] = []
|
|
for raw in tags_raw:
|
|
for part in str(raw).split(","):
|
|
part_clean = part.strip()
|
|
if part_clean:
|
|
tags.append(part_clean)
|
|
|
|
try:
|
|
items = runtime.inbox_service.list_items(
|
|
status_filter=status_filter,
|
|
kind_filter=kind_filter,
|
|
include_closed=include_closed,
|
|
limit=limit,
|
|
tags=tags,
|
|
)
|
|
except ValueError as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
except Exception as exc:
|
|
return JSONResponse({"error": f"failed to load inbox: {exc}"}, status_code=500)
|
|
return JSONResponse({"items": items})
|
|
|
|
|
|
@router.post("/inbox/capture")
|
|
async def capture_inbox_item(
|
|
request: Request,
|
|
runtime: WebAppRuntime = Depends(get_runtime),
|
|
) -> JSONResponse:
|
|
try:
|
|
payload = await read_json_request(request)
|
|
except ValueError as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
|
|
title = str(payload.get("title", "") or "")
|
|
text = str(payload.get("text", "") or "")
|
|
kind = str(payload.get("kind", "unknown") or "unknown")
|
|
source = str(payload.get("source", "web-ui") or "web-ui")
|
|
due = str(payload.get("due", "") or "")
|
|
body = str(payload.get("body", "") or "")
|
|
session_id = str(payload.get("session_id", "") or "")
|
|
raw_tags = payload.get("tags", [])
|
|
tags = [str(tag) for tag in raw_tags] if isinstance(raw_tags, list) else []
|
|
confidence_value = payload.get("confidence")
|
|
confidence = confidence_value if isinstance(confidence_value, (int, float)) else None
|
|
|
|
try:
|
|
item = runtime.inbox_service.capture_item(
|
|
title=title,
|
|
text=text,
|
|
kind=kind,
|
|
source=source,
|
|
due=due,
|
|
body=body,
|
|
session_id=session_id,
|
|
tags=tags,
|
|
confidence=confidence,
|
|
)
|
|
except ValueError as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
except Exception as exc:
|
|
return JSONResponse({"error": f"failed to capture inbox item: {exc}"}, status_code=500)
|
|
|
|
await runtime.publish_inbox_changed()
|
|
return JSONResponse({"status": "ok", "item": item}, status_code=201)
|
|
|
|
|
|
@router.post("/inbox/accept-task")
|
|
async def accept_inbox_item_as_task(
|
|
request: Request,
|
|
runtime: WebAppRuntime = Depends(get_runtime),
|
|
) -> JSONResponse:
|
|
try:
|
|
payload = await read_json_request(request)
|
|
except ValueError as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
|
|
item = str(payload.get("item", "") or "").strip()
|
|
lane = str(payload.get("lane", "backlog") or "backlog").strip()
|
|
title = str(payload.get("title", "") or "")
|
|
due = str(payload.get("due", "") or "")
|
|
body = payload.get("body")
|
|
body_text = str(body) if isinstance(body, str) else None
|
|
raw_tags = payload.get("tags")
|
|
tags = [str(tag) for tag in raw_tags] if isinstance(raw_tags, list) else None
|
|
|
|
if not item:
|
|
return JSONResponse({"error": "item is required"}, status_code=400)
|
|
|
|
try:
|
|
result = await runtime.inbox_service.accept_item_as_task(
|
|
item=item,
|
|
lane=lane,
|
|
title=title,
|
|
due=due,
|
|
body_text=body_text,
|
|
tags=tags,
|
|
run_nanobot_turn=run_nanobot_agent_turn,
|
|
notification_to_event=lambda obj: typed_message_from_api_notification(
|
|
obj,
|
|
default_chat_id="inbox-groomer",
|
|
),
|
|
)
|
|
except (FileNotFoundError, ValueError) as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
except RuntimeError as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=503)
|
|
except Exception as exc:
|
|
return JSONResponse({"error": f"failed to accept inbox item: {exc}"}, status_code=500)
|
|
|
|
await runtime.publish_inbox_changed()
|
|
await runtime.publish_cards_changed()
|
|
return JSONResponse(result)
|
|
|
|
|
|
@router.post("/inbox/dismiss")
|
|
async def dismiss_inbox_item(
|
|
request: Request,
|
|
runtime: WebAppRuntime = Depends(get_runtime),
|
|
) -> JSONResponse:
|
|
try:
|
|
payload = await read_json_request(request)
|
|
except ValueError as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
|
|
item = str(payload.get("item", "") or "").strip()
|
|
if not item:
|
|
return JSONResponse({"error": "item is required"}, status_code=400)
|
|
|
|
try:
|
|
item_payload = runtime.inbox_service.dismiss_item(item)
|
|
except (FileNotFoundError, ValueError) as exc:
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
except Exception as exc:
|
|
return JSONResponse({"error": f"failed to dismiss inbox item: {exc}"}, status_code=500)
|
|
|
|
await runtime.publish_inbox_changed()
|
|
return JSONResponse({"status": "ok", "item": item_payload})
|