diff --git a/submodules/moragents_dockers/agents/src/models/messages.py b/submodules/moragents_dockers/agents/src/models/messages.py index e17d693..52d6880 100644 --- a/submodules/moragents_dockers/agents/src/models/messages.py +++ b/submodules/moragents_dockers/agents/src/models/messages.py @@ -1,3 +1,4 @@ +from typing import List, Optional from pydantic import BaseModel @@ -10,3 +11,14 @@ class ChatRequest(BaseModel): prompt: ChatMessage chain_id: str wallet_address: str + + +class ChatMessage(BaseModel): + role: str + content: str + agentName: Optional[str] = None + + +class Conversation(BaseModel): + messages: List[ChatMessage] + has_uploaded_file: bool = False diff --git a/submodules/moragents_dockers/agents/src/routes/chat_manager_routes.py b/submodules/moragents_dockers/agents/src/routes/chat_manager_routes.py index a1df779..1346647 100644 --- a/submodules/moragents_dockers/agents/src/routes/chat_manager_routes.py +++ b/submodules/moragents_dockers/agents/src/routes/chat_manager_routes.py @@ -1,5 +1,5 @@ import logging -from fastapi import APIRouter +from fastapi import APIRouter, Query from src.stores import chat_manager_instance logger = logging.getLogger(__name__) @@ -8,15 +8,33 @@ @router.get("/messages") -async def get_messages(): - """Get all chat messages""" - logger.info("Received get_messages request") - return {"messages": chat_manager_instance.get_messages()} +async def get_messages(conversation_id: str = Query(default="default")): + """Get all chat messages for a conversation""" + logger.info(f"Received get_messages request for conversation {conversation_id}") + return {"messages": chat_manager_instance.get_messages(conversation_id)} @router.get("/clear") -async def clear_messages(): - """Clear chat message history""" - logger.info("Clearing message history") - chat_manager_instance.clear_messages() +async def clear_messages(conversation_id: str = Query(default="default")): + """Clear chat message history for a conversation""" + logger.info(f"Clearing message history for conversation {conversation_id}") + chat_manager_instance.clear_messages(conversation_id) return {"response": "successfully cleared message history"} + + +@router.post("/conversations") +async def create_conversation(): + """Create a new conversation""" + # The conversation will be created automatically when first accessed + new_id = f"conversation_{len(chat_manager_instance.get_all_conversation_ids())}" + chat_manager_instance.get_messages(new_id) # This creates the conversation + logger.info(f"Created new conversation with ID: {new_id}") + return {"conversation_id": new_id} + + +@router.delete("/conversations/{conversation_id}") +async def delete_conversation(conversation_id: str): + """Delete a conversation""" + logger.info(f"Deleting conversation {conversation_id}") + chat_manager_instance.delete_conversation(conversation_id) + return {"response": f"successfully deleted conversation {conversation_id}"} diff --git a/submodules/moragents_dockers/agents/src/stores/chat_manager.py b/submodules/moragents_dockers/agents/src/stores/chat_manager.py index b0960fb..b4ee14c 100644 --- a/submodules/moragents_dockers/agents/src/stores/chat_manager.py +++ b/submodules/moragents_dockers/agents/src/stores/chat_manager.py @@ -1,52 +1,84 @@ import logging from typing import Dict, List +from src.models.messages import ChatMessage, Conversation logger = logging.getLogger(__name__) class ChatManager: def __init__(self): - self.has_uploaded_file = False - self.messages: List[Dict[str, str]] = [ - { - "role": "assistant", - "agentName": "Morpheus AI", - "content": """This highly experimental chatbot is not intended for making important decisions, - and its responses are generated based on incomplete data and algorithms that may evolve rapidly. - By using this chatbot, you acknowledge that you use it at your own discretion - and assume all risks associated with its limitations and potential errors.""", - } - ] - - def add_message(self, message: Dict[str, str]): - self.messages.append(message) - logger.info(f"Added message: {message}") - - def get_messages(self) -> List[Dict[str, str]]: - return self.messages - - def set_uploaded_file(self, has_file: bool): - self.has_uploaded_file = has_file - logger.info(f"Set uploaded file status to: {has_file}") - - def get_uploaded_file_status(self) -> bool: - return self.has_uploaded_file - - def clear_messages(self): - self.messages = [self.messages[0]] # Keep the initial message - logger.info("Cleared message history") - - def get_last_message(self) -> Dict[str, str]: - return self.messages[-1] if self.messages else {} - - def add_response(self, response: Dict[str, str], agent_name: str): + self.conversations: Dict[str, Conversation] = {} + self.default_message = ChatMessage( + role="assistant", + agentName="Morpheus AI", + content="""This highly experimental chatbot is not intended for making important decisions. Its + responses are generated using AI models and may not always be accurate. + By using this chatbot, you acknowledge that you use it at your own discretion + and assume all risks associated with its limitations and potential errors.""", + ) + + def _get_or_create_conversation(self, conversation_id: str) -> Conversation: + if conversation_id not in self.conversations: + self.conversations[conversation_id] = Conversation( + messages=[self.default_message], has_uploaded_file=False + ) + return self.conversations[conversation_id] + + def add_message(self, message: Dict[str, str], conversation_id: str): + conversation = self._get_or_create_conversation(conversation_id) + chat_message = ChatMessage(**message) + conversation.messages.append(chat_message) + logger.info(f"Added message to conversation {conversation_id}: {message}") + + def get_messages(self, conversation_id: str) -> List[Dict[str, str]]: + conversation = self._get_or_create_conversation(conversation_id) + return [msg.dict() for msg in conversation.messages] + + def set_uploaded_file(self, has_file: bool, conversation_id: str): + conversation = self._get_or_create_conversation(conversation_id) + conversation.has_uploaded_file = has_file + logger.info(f"Set uploaded file status to {has_file} for conversation {conversation_id}") + + def get_uploaded_file_status(self, conversation_id: str) -> bool: + conversation = self._get_or_create_conversation(conversation_id) + return conversation.has_uploaded_file + + def clear_messages(self, conversation_id: str): + conversation = self._get_or_create_conversation(conversation_id) + conversation.messages = [self.default_message] # Keep the initial message + logger.info(f"Cleared message history for conversation {conversation_id}") + + def get_last_message(self, conversation_id: str) -> Dict[str, str]: + conversation = self._get_or_create_conversation(conversation_id) + return conversation.messages[-1].dict() if conversation.messages else {} + + def add_response(self, response: Dict[str, str], agent_name: str, conversation_id: str): response_with_agent = response.copy() response_with_agent["agentName"] = agent_name - self.add_message(response_with_agent) - logger.info(f"Added response from agent {agent_name}: {response_with_agent}") + chat_message = ChatMessage(**response_with_agent) + self.add_message(chat_message.dict(), conversation_id) + logger.info( + f"Added response from agent {agent_name} to conversation {conversation_id}: {response_with_agent}" + ) + + def get_chat_history(self, conversation_id: str) -> str: + conversation = self._get_or_create_conversation(conversation_id) + return "\n".join([f"{msg.role}: {msg.content}" for msg in conversation.messages]) + + def get_all_conversation_ids(self) -> List[str]: + """Get a list of all conversation IDs""" + return list(self.conversations.keys()) + + def delete_conversation(self, conversation_id: str): + """Delete a conversation by ID""" + if conversation_id in self.conversations: + del self.conversations[conversation_id] + logger.info(f"Deleted conversation {conversation_id}") - def get_chat_history(self) -> str: - return "\n".join([f"{msg['role']}: {msg['content']}" for msg in self.messages]) + def create_conversation(self, conversation_id: str) -> Dict: + """Create a new conversation with the given ID""" + conversation = self._get_or_create_conversation(conversation_id) + return conversation.dict() # Create an instance to act as a singleton store diff --git a/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx b/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx index 8d77c47..072816b 100644 --- a/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx +++ b/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx @@ -1,24 +1,68 @@ -import React, { FC } from 'react'; -import { - Box, - VStack, - Text, - Link -} from "@chakra-ui/react"; -import { ConnectButton } from '@rainbow-me/rainbowkit'; +import React, { FC, useEffect, useState } from "react"; +import { Box, VStack, Text, Button } from "@chakra-ui/react"; +import { getHttpClient } from "@/services/constants"; +export type LeftSidebarProps = {}; -export type LeftSidebarProps = { +export const LeftSidebar: FC = () => { + const [conversations, setConversations] = useState([]); -}; + const fetchConversations = async () => { + try { + const response = await getHttpClient().get("/chat/conversations"); + setConversations(response.data.conversation_ids); + } catch (error) { + console.error("Failed to fetch conversations:", error); + } + }; -export const LeftSidebar: FC = () => { - return ( - - + const createNewConversation = async () => { + try { + await getHttpClient().post("/chat/conversations"); + fetchConversations(); + } catch (error) { + console.error("Failed to create new conversation:", error); + } + }; - {/* Dynamic chat list can be added here */} - - - ); -} + const deleteConversation = async (conversationId: string) => { + try { + await getHttpClient().delete(`/chat/conversations/${conversationId}`); + fetchConversations(); + } catch (error) { + console.error("Failed to delete conversation:", error); + } + }; + + useEffect(() => { + fetchConversations(); + }, []); + + return ( + + + + {conversations.map((conversationId) => ( + + {conversationId} + + + ))} + + + ); +}; diff --git a/submodules/moragents_dockers/frontend/components/LoadingIndicator/index.tsx b/submodules/moragents_dockers/frontend/components/LoadingIndicator/index.tsx index 95931ae..91b5a61 100644 --- a/submodules/moragents_dockers/frontend/components/LoadingIndicator/index.tsx +++ b/submodules/moragents_dockers/frontend/components/LoadingIndicator/index.tsx @@ -12,7 +12,8 @@ type LoadingIndicatorProps = { export const LoadingIndicator: FC = ({ selectedAgent, }) => { - const agentName = availableAgents[selectedAgent]?.name || "Finding an agent"; + const agentName = + availableAgents[selectedAgent]?.name || "Finding the best agent"; return (