Files

117 lines
3.7 KiB
Python

"""Copilot router: chat, action execution, history, and voice endpoints.
All endpoints require authentication (get_current_user).
Actions are proposed by the AI and only executed after user confirmation.
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.dependencies import get_current_user, get_pagination
from app.models.user import User
from app.schemas.copilot import (
ActionRequest,
ActionResponse,
ChatHistoryResponse,
ChatMessageItem,
ChatRequest,
ChatResponse,
VoiceRequest,
VoiceResponse,
)
from app.services import copilot_service
router = APIRouter(prefix="/copilot", tags=["copilot"])
@router.post("/chat", response_model=ChatResponse, status_code=status.HTTP_200_OK)
async def copilot_chat(
body: ChatRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Send a message to the Copilot and receive a response with action proposals."""
try:
result = await copilot_service.chat(
db,
user_id=current_user.id,
message=body.message,
session_id=body.session_id,
)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={"error": {"code": "COPILOT_ERROR", "message": str(exc)}},
)
return ChatResponse(**result)
@router.post("/action", response_model=ActionResponse, status_code=status.HTTP_200_OK)
async def copilot_action(
body: ActionRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Execute a user-confirmed action proposed by the Copilot."""
try:
result = await copilot_service.execute_confirmed_action(
db,
action_type=body.action,
params=body.params,
)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={"error": {"code": "INVALID_ACTION", "message": str(exc)}},
)
return ActionResponse(**result)
@router.get(
"/history", response_model=ChatHistoryResponse, status_code=status.HTTP_200_OK
)
async def copilot_history(
db: AsyncSession = Depends(get_db),
pagination: dict = Depends(get_pagination),
session_id: str | None = Query(None, description="Filter by session ID"),
current_user: User = Depends(get_current_user),
):
"""Get paginated chat history for the current user."""
messages, total = await copilot_service.get_history(
db,
user_id=current_user.id,
page=pagination["page"],
page_size=pagination["page_size"],
session_id=session_id,
)
return ChatHistoryResponse(
items=[ChatMessageItem.model_validate(m.to_dict()) for m in messages],
total=total,
page=pagination["page"],
page_size=pagination["page_size"],
)
@router.post("/voice", response_model=VoiceResponse, status_code=status.HTTP_200_OK)
async def copilot_voice(
body: VoiceRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Accept audio, transcribe, and return a chat response with action proposals."""
try:
result = await copilot_service.voice_chat(
db,
user_id=current_user.id,
audio_b64=body.audio,
mime_type=body.mime_type,
session_id=body.session_id,
)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={"error": {"code": "VOICE_ERROR", "message": str(exc)}},
)
return VoiceResponse(**result)