gateway/modules/routes/routeChatbot.py
2025-10-01 16:00:19 +02:00

254 lines
8.5 KiB
Python

from pydantic import BaseModel, Field
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.requests import Request
from typing import Any, Dict, List, Optional
from datetime import datetime
import logging
import uuid
from modules.datamodels.datamodelUam import User
from modules.security.auth import getCurrentUser, limiter
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/api/chatbot",
tags=["Chatbot"],
responses={404: {"description": "Not found"}},
)
# --- Pydantic models for requests and responses ---
class ChatMessageRequest(BaseModel):
"""Request model for posting a chat message"""
thread_id: Optional[str] = Field(
None, description="Thread ID (creates new thread if not provided)"
)
message: str = Field(..., description="User message content")
class MessageItem(BaseModel):
"""Individual message in a thread"""
role: str = Field(..., description="Message role (user or assistant)")
content: str = Field(..., description="Message content")
timestamp: float = Field(..., description="Message timestamp (Unix timestamp)")
class ChatMessageResponse(BaseModel):
"""Response model for posting a chat message"""
thread_id: str = Field(..., description="Thread ID")
messages: List[MessageItem] = Field(..., description="All messages in thread")
class ThreadSummary(BaseModel):
"""Summary of a chat thread for list view"""
thread_id: str = Field(..., description="Thread ID")
created_at: float = Field(..., description="Thread creation timestamp")
last_message: str = Field(..., description="Last message content")
message_count: int = Field(..., description="Total number of messages")
class ThreadListResponse(BaseModel):
"""Response model for listing all threads"""
threads: List[ThreadSummary] = Field(..., description="List of thread summaries")
class ThreadDetail(BaseModel):
"""Detailed view of a single thread"""
thread_id: str = Field(..., description="Thread ID")
created_at: float = Field(..., description="Thread creation timestamp")
messages: List[MessageItem] = Field(
..., description="All messages in chronological order"
)
class DeleteResponse(BaseModel):
"""Response model for delete operations"""
message: str = Field(..., description="Confirmation message")
thread_id: str = Field(..., description="Deleted thread ID")
# --- Actual endpoints for chatbot ---
@router.post("/message", response_model=ChatMessageResponse)
@limiter.limit("30/minute")
async def post_chat_message(
*,
request: Request,
message_request: ChatMessageRequest,
currentUser: User = Depends(getCurrentUser),
) -> ChatMessageResponse:
"""
Post a message to a chat thread and get assistant response.
Creates a new thread if thread_id is not provided.
This endpoint will later be connected to LangGraph's checkpointer.
"""
try:
# Generate or use existing thread_id
thread_id = message_request.thread_id or f"thread_{uuid.uuid4()}"
# Get current timestamp
current_time = datetime.now().timestamp()
# Create dummy message history
# In production, this will fetch from LangGraph's checkpointer
messages = [
MessageItem(
role="user", content=message_request.message, timestamp=current_time
),
MessageItem(
role="assistant",
content=f"Echo: {message_request.message} (This is a dummy response. LangGraph integration pending.)",
timestamp=current_time + 0.5,
),
]
logger.info(f"User {currentUser.id} posted message to thread {thread_id}")
return ChatMessageResponse(thread_id=thread_id, messages=messages)
except Exception as e:
logger.error(f"Error posting chat message: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to post message: {str(e)}",
)
@router.get("/threads", response_model=ThreadListResponse)
@limiter.limit("30/minute")
async def get_all_threads(
*, request: Request, currentUser: User = Depends(getCurrentUser)
) -> ThreadListResponse:
"""
Get all chat threads for the current user.
This endpoint will later fetch from LangGraph's PostgreSQL checkpointer.
"""
try:
# Return dummy thread data
# In production, this will query LangGraph's checkpointer database
dummy_threads = [
ThreadSummary(
thread_id="thread_001",
created_at=datetime.now().timestamp() - 86400, # 1 day ago
last_message="Hello, how can I help you?",
message_count=4,
),
ThreadSummary(
thread_id="thread_002",
created_at=datetime.now().timestamp() - 3600, # 1 hour ago
last_message="Thank you for your help!",
message_count=8,
),
ThreadSummary(
thread_id="thread_003",
created_at=datetime.now().timestamp() - 300, # 5 minutes ago
last_message="Can you explain this concept?",
message_count=2,
),
]
logger.info(f"User {currentUser.id} retrieved {len(dummy_threads)} threads")
return ThreadListResponse(threads=dummy_threads)
except Exception as e:
logger.error(f"Error retrieving threads: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to retrieve threads: {str(e)}",
)
@router.get("/threads/{thread_id}", response_model=ThreadDetail)
@limiter.limit("30/minute")
async def get_thread_by_id(
*, request: Request, thread_id: str, currentUser: User = Depends(getCurrentUser)
) -> ThreadDetail:
"""
Get a specific chat thread with all its messages.
This endpoint will later fetch from LangGraph's PostgreSQL checkpointer.
"""
try:
# Return dummy thread detail
# In production, this will query LangGraph's checkpointer for the specific thread
current_time = datetime.now().timestamp()
dummy_messages = [
MessageItem(
role="user",
content="Hello! I need help with Python.",
timestamp=current_time - 120,
),
MessageItem(
role="assistant",
content="Hello! I'd be happy to help you with Python. What would you like to know?",
timestamp=current_time - 119,
),
MessageItem(
role="user",
content="How do I use list comprehensions?",
timestamp=current_time - 60,
),
MessageItem(
role="assistant",
content="List comprehensions are a concise way to create lists. Here's an example: [x**2 for x in range(10)]",
timestamp=current_time - 59,
),
]
logger.info(f"User {currentUser.id} retrieved thread {thread_id}")
return ThreadDetail(
thread_id=thread_id, created_at=current_time - 120, messages=dummy_messages
)
except Exception as e:
logger.error(f"Error retrieving thread {thread_id}: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to retrieve thread: {str(e)}",
)
@router.delete("/threads/{thread_id}", response_model=DeleteResponse)
@limiter.limit("10/minute")
async def delete_thread(
*, request: Request, thread_id: str, currentUser: User = Depends(getCurrentUser)
) -> DeleteResponse:
"""
Delete a chat thread and all its associated data.
This endpoint will later delete from LangGraph's PostgreSQL checkpointer.
"""
try:
# In production, this will:
# 1. Verify the thread belongs to the current user
# 2. Delete the thread from LangGraph's checkpointer
# 3. Clean up any associated data
logger.info(f"User {currentUser.id} deleted thread {thread_id}")
return DeleteResponse(
message=f"Thread {thread_id} successfully deleted (dummy response)",
thread_id=thread_id,
)
except Exception as e:
logger.error(f"Error deleting thread {thread_id}: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to delete thread: {str(e)}",
)