117 lines
3.7 KiB
Python
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)
|