diff --git a/backend/council.py b/backend/council.py index b7f8839..5069abe 100644 --- a/backend/council.py +++ b/backend/council.py @@ -255,6 +255,44 @@ def calculate_aggregate_rankings( return aggregate +async def generate_conversation_title(user_query: str) -> str: + """ + Generate a short title for a conversation based on the first user message. + + Args: + user_query: The first user message + + Returns: + A short title (3-5 words) + """ + title_prompt = f"""Generate a very short title (3-5 words maximum) that summarizes the following question. +The title should be concise and descriptive. Do not use quotes or punctuation in the title. + +Question: {user_query} + +Title:""" + + messages = [{"role": "user", "content": title_prompt}] + + # Use gemini-2.5-flash for title generation (fast and cheap) + response = await query_model("google/gemini-2.5-flash", messages, timeout=30.0) + + if response is None: + # Fallback to a generic title + return "New Conversation" + + title = response.get('content', 'New Conversation').strip() + + # Clean up the title - remove quotes, limit length + title = title.strip('"\'') + + # Truncate if too long + if len(title) > 50: + title = title[:47] + "..." + + return title + + async def run_full_council(user_query: str) -> Tuple[List, List, Dict, Dict]: """ Run the complete 3-stage council process. diff --git a/backend/main.py b/backend/main.py index cbb836f..e896bf2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,7 @@ from typing import List, Dict, Any import uuid from . import storage -from .council import run_full_council +from .council import run_full_council, generate_conversation_title app = FastAPI(title="LLM Council API") @@ -35,6 +35,7 @@ class ConversationMetadata(BaseModel): """Conversation metadata for list view.""" id: str created_at: str + title: str message_count: int @@ -42,6 +43,7 @@ class Conversation(BaseModel): """Full conversation with all messages.""" id: str created_at: str + title: str messages: List[Dict[str, Any]] @@ -85,9 +87,17 @@ async def send_message(conversation_id: str, request: SendMessageRequest): if conversation is None: raise HTTPException(status_code=404, detail="Conversation not found") + # Check if this is the first message + is_first_message = len(conversation["messages"]) == 0 + # Add user message storage.add_user_message(conversation_id, request.content) + # If this is the first message, generate a title + if is_first_message: + title = await generate_conversation_title(request.content) + storage.update_conversation_title(conversation_id, title) + # Run the 3-stage council process stage1_results, stage2_results, stage3_result, metadata = await run_full_council( request.content diff --git a/backend/storage.py b/backend/storage.py index dd17a1a..180111d 100644 --- a/backend/storage.py +++ b/backend/storage.py @@ -33,6 +33,7 @@ def create_conversation(conversation_id: str) -> Dict[str, Any]: conversation = { "id": conversation_id, "created_at": datetime.utcnow().isoformat(), + "title": "New Conversation", "messages": [] } @@ -96,6 +97,7 @@ def list_conversations() -> List[Dict[str, Any]]: conversations.append({ "id": data["id"], "created_at": data["created_at"], + "title": data.get("title", "New Conversation"), "message_count": len(data["messages"]) }) @@ -152,3 +154,19 @@ def add_assistant_message( }) save_conversation(conversation) + + +def update_conversation_title(conversation_id: str, title: str): + """ + Update the title of a conversation. + + Args: + conversation_id: Conversation identifier + title: New title for the conversation + """ + conversation = get_conversation(conversation_id) + if conversation is None: + raise ValueError(f"Conversation {conversation_id} not found") + + conversation["title"] = title + save_conversation(conversation) diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index c189690..1376f4e 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -29,7 +29,7 @@ export default function Sidebar({ onClick={() => onSelectConversation(conv.id)} >