2025-05-17 09:49:47 +00:00

122 lines
4.3 KiB
Python

"""
Router for the chat-to-tasks functionality.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.api import deps
from app.crud import task as task_crud
from app.models.user import User
from app.schemas.chat import ChatInput, ChatResponse, ChatProcessingError
from app.schemas.task import TaskCreate, TaskRead
from app.services.llm_service import LLMService, get_llm_service
from app.db.session import get_db
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/chat-to-tasks", response_model=ChatResponse)
async def create_tasks_from_chat(
chat_input: ChatInput,
db: Session = Depends(get_db),
current_user: User = Depends(deps.get_current_active_user),
llm_service: LLMService = Depends(get_llm_service),
):
"""
Convert natural language chat input into one or more structured task objects.
Steps:
1. Validate the input message length.
2. Send the message to the LLM service for task extraction.
3. Parse the response into TaskCreate schemas.
4. Persist tasks in the database linked to the authenticated user.
5. Return the list of created tasks or an error response.
"""
message = chat_input.message.strip()
if len(message) < 3:
raise HTTPException(
status_code=400,
detail="Message must be at least 3 characters long",
)
response = ChatResponse(original_message=message)
try:
logger.info(f"Received chat input for task extraction: {message[:50]}...")
# Extract tasks from the LLM service
llm_tasks = await llm_service.chat_to_tasks(message)
if not llm_tasks:
logger.warning("LLM service returned no tasks.")
response.processing_successful = False
response.error = ChatProcessingError(
error_type="parsing_error",
error_detail="No tasks could be extracted from your message.",
)
return response
created_tasks = []
for task_data in llm_tasks:
try:
# Map LLM response fields to TaskCreate schema fields
task_create_data = {
"title": task_data.get("title", "Untitled Task"),
"description": task_data.get("description", ""),
"priority": task_data.get("priority", "medium").lower(),
}
# Validate and include due_date if present and valid
due_date = task_data.get("due_date")
if due_date and due_date != "null":
task_create_data["due_date"] = due_date
# Map status field and normalize to internal status naming
status = task_data.get("status", "").lower()
if status == "pending":
task_create_data["status"] = "todo"
elif status:
task_create_data["status"] = status
# Validate input data against TaskCreate schema
task_in = TaskCreate(**task_create_data)
# Create task with ownership linked to current user
db_task = task_crud.task.create_with_owner(
db=db,
obj_in=task_in,
user_id=current_user.id,
)
created_tasks.append(TaskRead.model_validate(db_task))
except Exception as task_exc:
logger.error(f"Failed to create task from LLM data: {task_exc}")
# Continue processing remaining tasks even if one fails
if not created_tasks:
response.processing_successful = False
response.error = ChatProcessingError(
error_type="creation_error",
error_detail="Failed to create any tasks from the provided message.",
)
else:
response.tasks = created_tasks
return response
except Exception as exc:
logger.exception(f"Unexpected error in chat-to-tasks endpoint: {exc}")
response.processing_successful = False
response.error = ChatProcessingError(
error_type="processing_error",
error_detail=f"An error occurred while processing your request: {str(exc)}",
)
return response