119 lines
4.1 KiB
Python
119 lines
4.1 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.delete("/{conversation_id}/messages/{message_id}", response_model=ApiResponse[ConversationData])
|
|
async def withdraw_conversation_message(
|
|
conversation_id: int,
|
|
message_id: int,
|
|
session: DatabaseSession,
|
|
current_user: Annotated[UserAccount, Depends(require_permission("risk.assess", operate=True))],
|
|
) -> ApiResponse[ConversationData]:
|
|
return ApiResponse(
|
|
data=await ConversationService(session).withdraw_turn(
|
|
conversation_id,
|
|
message_id,
|
|
current_user,
|
|
)
|
|
)
|
|
|
|
|
|
@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"},
|
|
)
|