"""Message routes""" import json from fastapi import APIRouter, Depends, Response from fastapi.responses import StreamingResponse from pydantic import BaseModel from sqlalchemy.orm import Session from datetime import datetime from luxx.database import get_db from luxx.models import Conversation, Message, User from luxx.routes.auth import get_current_user from luxx.services.chat import chat_service from luxx.utils.helpers import generate_id, success_response, error_response router = APIRouter(prefix="/messages", tags=["Messages"]) class MessageCreate(BaseModel): """Create message model""" conversation_id: str content: str class MessageResponse(BaseModel): """Message response model""" id: str role: str content: str token_count: int @router.get("/", response_model=dict) def list_messages( conversation_id: str, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get message list""" conversation = db.query(Conversation).filter( Conversation.id == conversation_id, Conversation.user_id == current_user.id ).first() if not conversation: return error_response("Conversation not found", 404) messages = db.query(Message).filter( Message.conversation_id == conversation_id ).order_by(Message.created_at).all() return success_response(data={ "messages": [m.to_dict() for m in messages] }) @router.post("/", response_model=dict) def send_message( data: MessageCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Send message (non-streaming)""" conversation = db.query(Conversation).filter( Conversation.id == data.conversation_id, Conversation.user_id == current_user.id ).first() if not conversation: return error_response("Conversation not found", 404) user_message = Message( id=generate_id("msg"), conversation_id=data.conversation_id, role="user", content=data.content, token_count=len(data.content) // 4 ) db.add(user_message) from datetime import datetime conversation.updated_at = datetime.utcnow() response = chat_service.non_stream_response( conversation=conversation, user_message=data.content, tools_enabled=False ) if not response.get("success"): return error_response(response.get("error", "Failed to generate response"), 500) ai_content = response.get("content", "") ai_message = Message( id=generate_id("msg"), conversation_id=data.conversation_id, role="assistant", content=ai_content, token_count=len(ai_content) // 4 ) db.add(ai_message) db.commit() return success_response(data={ "user_message": user_message.to_dict(), "assistant_message": ai_message.to_dict() }) @router.post("/stream") async def stream_message( data: MessageCreate, tools_enabled: bool = True, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Send message (streaming response - SSE)""" conversation = db.query(Conversation).filter( Conversation.id == data.conversation_id, Conversation.user_id == current_user.id ).first() if not conversation: return error_response("Conversation not found", 404) user_message = Message( id=generate_id("msg"), conversation_id=data.conversation_id, role="user", content=data.content, token_count=len(data.content) // 4 ) db.add(user_message) conversation.updated_at = datetime.utcnow() db.commit() async def event_generator(): full_response = "" async for event in chat_service.stream_response( conversation=conversation, user_message=data.content, tools_enabled=tools_enabled ): event_type = event.get("type") if event_type == "text": content = event.get("content", "") full_response += content yield f"data: {json.dumps({'type': 'text', 'content': content})}\n\n" elif event_type == "tool_call": yield f"data: {json.dumps({'type': 'tool_call', 'data': event.get('data')})}\n\n" elif event_type == "tool_result": yield f"data: {json.dumps({'type': 'tool_result', 'data': event.get('data')})}\n\n" elif event_type == "done": try: ai_message = Message( id=generate_id("msg"), conversation_id=data.conversation_id, role="assistant", content=full_response, token_count=len(full_response) // 4 ) db.add(ai_message) db.commit() except Exception: pass yield f"data: {json.dumps({'type': 'done', 'message_id': ai_message.id if 'ai_message' in dir() else None})}\n\n" elif event_type == "error": yield f"data: {json.dumps({'type': 'error', 'error': event.get('error')})}\n\n" yield "data: [DONE]\n\n" return StreamingResponse( event_generator(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" } ) @router.delete("/{message_id}", response_model=dict) def delete_message( message_id: str, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Delete message""" message = db.query(Message).join(Conversation).filter( Message.id == message_id, Conversation.user_id == current_user.id ).first() if not message: return error_response("Message not found", 404) db.delete(message) db.commit() return success_response(message="Message deleted successfully")