2026-09-04 10:20:40 +08:00

103 lines
3.6 KiB
Python

"""Persistent assistant conversation endpoints."""
import json
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from fastapi.responses import StreamingResponse
from app.api.dependencies import DatabaseSession, require_permission
from app.models.entities import UserAccount
from app.schemas.common import ApiResponse
from app.schemas.conversation import (
ConversationCreateRequest,
ConversationData,
ConversationMessageCreateRequest,
ConversationSummary,
)
from app.services.conversations import ConversationService
router = APIRouter(prefix="/conversations", tags=["conversations"])
@router.get("", response_model=ApiResponse[list[ConversationSummary]])
async def list_conversations(
session: DatabaseSession,
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess"))],
limit: int = Query(50, ge=1, le=100),
search: str | None = Query(default=None, max_length=120),
) -> ApiResponse[list[ConversationSummary]]:
return ApiResponse(data=await ConversationService(session).list(current_user, limit=limit, search=search))
@router.post("", response_model=ApiResponse[ConversationData])
async def create_conversation(
payload: ConversationCreateRequest,
session: DatabaseSession,
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess", operate=True))],
) -> ApiResponse[ConversationData]:
return ApiResponse(data=await ConversationService(session).create(current_user, payload.title))
@router.get("/{conversation_id}", response_model=ApiResponse[ConversationData])
async def get_conversation(
conversation_id: int,
session: DatabaseSession,
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess"))],
limit: int = Query(20, ge=1, le=20),
offset: int = Query(0, ge=0),
) -> ApiResponse[ConversationData]:
return ApiResponse(
data=await ConversationService(session).get(
conversation_id,
current_user,
limit=limit,
offset=offset,
)
)
@router.post("/{conversation_id}/messages", response_model=ApiResponse[ConversationData])
async def send_conversation_message(
conversation_id: int,
payload: ConversationMessageCreateRequest,
session: DatabaseSession,
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess", operate=True))],
) -> ApiResponse[ConversationData]:
return ApiResponse(
data=await ConversationService(session).send(
conversation_id,
payload.content,
current_user,
message_type=payload.message_type,
),
)
@router.post("/{conversation_id}/stream")
async def stream_conversation_message(
conversation_id: int,
payload: ConversationMessageCreateRequest,
session: DatabaseSession,
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess", operate=True))],
) -> StreamingResponse:
service = ConversationService(session)
async def events():
try:
async for event in service.stream(
conversation_id,
payload.content,
current_user,
message_type=payload.message_type,
):
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
except Exception as exc: # noqa: BLE001 - turn route errors into SSE events
yield f"data: {json.dumps({'type': 'error', 'message': str(exc)}, ensure_ascii=False)}\n\n"
return StreamingResponse(
events(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)