From 530e829680381680e267b4a7f8fef8ea54553a8d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 02:13:03 +0000 Subject: [PATCH] feat: Add conversation history and search to bots --- src/components/bots/BotChat.tsx | 144 ++++++++++++++--- src/components/bots/BotChatSidebar.tsx | 210 +++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 21 deletions(-) create mode 100644 src/components/bots/BotChatSidebar.tsx diff --git a/src/components/bots/BotChat.tsx b/src/components/bots/BotChat.tsx index 10c8a7c..88e00ca 100644 --- a/src/components/bots/BotChat.tsx +++ b/src/components/bots/BotChat.tsx @@ -1,9 +1,10 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { ArrowLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ChatMessage } from "@/components/chat/ChatMessage"; import { ChatInput } from "@/components/chat/ChatInput"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { BotChatSidebar, BotConversation } from "./BotChatSidebar"; interface Bot { id: string; @@ -30,17 +31,49 @@ interface BotChatProps { } export const BotChat = ({ bot, onBack }: BotChatProps) => { - const [messages, setMessages] = useState([ - { + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); + const [conversations, setConversations] = useState([]); + const [selectedConversationId, setSelectedConversationId] = useState(null); + const [conversationMessages, setConversationMessages] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + + // Initialize first conversation + useEffect(() => { + if (conversations.length === 0) { + createNewConversation(); + } + }, []); + + const createNewConversation = () => { + const newConversation: BotConversation = { + id: Date.now().toString(), + title: "Nova Conversa", + lastMessage: "Inicie uma conversa...", + timestamp: "Agora", + }; + + const initialMessage: Message = { id: "1", role: "assistant", content: "Envie sua mensagem", model: bot.model, - }, - ]); - const [isLoading, setIsLoading] = useState(false); + }; + + setConversations(prev => [newConversation, ...prev]); + setConversationMessages(prev => ({ + ...prev, + [newConversation.id]: [initialMessage], + })); + setSelectedConversationId(newConversation.id); + }; + + const currentMessages = selectedConversationId + ? conversationMessages[selectedConversationId] || [] + : []; const handleSendMessage = (content: string, files?: File[]) => { + if (!selectedConversationId) return; + const newMessage: Message = { id: Date.now().toString(), role: "user", @@ -52,7 +85,28 @@ export const BotChat = ({ bot, onBack }: BotChatProps) => { })), }; - setMessages((prev) => [...prev, newMessage]); + // Update messages + setConversationMessages(prev => ({ + ...prev, + [selectedConversationId]: [...(prev[selectedConversationId] || []), newMessage], + })); + + // Update conversation title and last message + setConversations(prev => prev.map(conv => { + if (conv.id === selectedConversationId) { + const title = conv.title === "Nova Conversa" + ? content.slice(0, 30) + (content.length > 30 ? "..." : "") + : conv.title; + return { + ...conv, + title, + lastMessage: content, + timestamp: "Agora", + }; + } + return conv; + })); + setIsLoading(true); // Simulate AI response @@ -63,13 +117,60 @@ export const BotChat = ({ bot, onBack }: BotChatProps) => { content: `Resposta do ${bot.name} usando ${bot.model}. Esta é uma resposta simulada considerando as instruções: "${bot.prompt}"`, model: bot.model, }; - setMessages((prev) => [...prev, aiResponse]); + + setConversationMessages(prev => ({ + ...prev, + [selectedConversationId]: [...(prev[selectedConversationId] || []), aiResponse], + })); + + // Update last message in conversation + setConversations(prev => prev.map(conv => { + if (conv.id === selectedConversationId) { + return { + ...conv, + lastMessage: aiResponse.content.slice(0, 50) + "...", + timestamp: "Agora", + }; + } + return conv; + })); + setIsLoading(false); }, 1500); }; + const handleDeleteConversation = (id: string) => { + setConversations(prev => prev.filter(conv => conv.id !== id)); + setConversationMessages(prev => { + const newMessages = { ...prev }; + delete newMessages[id]; + return newMessages; + }); + + // If deleting current conversation, select the first available one + if (selectedConversationId === id) { + const remainingConversations = conversations.filter(conv => conv.id !== id); + if (remainingConversations.length > 0) { + setSelectedConversationId(remainingConversations[0].id); + } else { + createNewConversation(); + } + } + }; + return ( -
+
+ setIsSidebarCollapsed(!isSidebarCollapsed)} + conversations={conversations} + selectedConversationId={selectedConversationId} + onSelectConversation={setSelectedConversationId} + onNewConversation={createNewConversation} + onDeleteConversation={handleDeleteConversation} + /> + +
{/* Header */}
- {/* Messages */} - -
- {messages.map((message) => ( + {/* Messages */} + +
+ {currentMessages.map((message) => ( { model={message.model} attachments={message.attachments} /> - ))} + ))} - {isLoading && ( + {isLoading && (
@@ -108,13 +209,14 @@ export const BotChat = ({ bot, onBack }: BotChatProps) => {
-
- )} -
- +
+ )} +
+ - {/* Input */} - + {/* Input */} + +
); }; diff --git a/src/components/bots/BotChatSidebar.tsx b/src/components/bots/BotChatSidebar.tsx new file mode 100644 index 0000000..2bba805 --- /dev/null +++ b/src/components/bots/BotChatSidebar.tsx @@ -0,0 +1,210 @@ +import { useState } from "react"; +import { Search, MessageSquare, MoreVertical, Trash2, ChevronLeft, ChevronRight, Plus } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +export interface BotConversation { + id: string; + title: string; + lastMessage: string; + timestamp: string; +} + +interface BotChatSidebarProps { + isCollapsed: boolean; + onToggleCollapse: () => void; + conversations: BotConversation[]; + selectedConversationId: string | null; + onSelectConversation: (id: string) => void; + onNewConversation: () => void; + onDeleteConversation: (id: string) => void; +} + +export const BotChatSidebar = ({ + isCollapsed, + onToggleCollapse, + conversations, + selectedConversationId, + onSelectConversation, + onNewConversation, + onDeleteConversation, +}: BotChatSidebarProps) => { + const [searchQuery, setSearchQuery] = useState(""); + const [deletingConversation, setDeletingConversation] = useState(null); + + const filteredConversations = conversations.filter( + (conv) => + conv.title.toLowerCase().includes(searchQuery.toLowerCase()) || + conv.lastMessage.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const handleDeleteConversation = () => { + if (deletingConversation) { + onDeleteConversation(deletingConversation.id); + setDeletingConversation(null); + } + }; + + if (isCollapsed) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* Header with collapse button */} +
+

Conversas

+ +
+ + {/* Search */} +
+
+ + setSearchQuery(e.target.value)} + placeholder="Pesquisar conversas..." + className="pl-9 bg-muted/50" + /> +
+ + +
+ + {/* Conversations List */} + +
+ {filteredConversations.length === 0 ? ( +
+ {searchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda"} +
+ ) : ( + filteredConversations.map((conv) => ( + onSelectConversation(conv.id)} + onDelete={() => setDeletingConversation(conv)} + /> + )) + )} +
+
+ + {/* Delete Conversation Alert */} + !open && setDeletingConversation(null)}> + + + Excluir Conversa? + + Esta ação não pode ser desfeita. A conversa será permanentemente excluída. + + + + Cancelar + + Excluir + + + + +
+ ); +}; + +interface ConversationItemProps { + conversation: BotConversation; + isSelected: boolean; + onSelect: () => void; + onDelete: () => void; +} + +const ConversationItem = ({ + conversation, + isSelected, + onSelect, + onDelete, +}: ConversationItemProps) => { + return ( +
+ +
+

{conversation.title}

+

+ {conversation.lastMessage} +

+

+ {conversation.timestamp} +

+
+ + + e.stopPropagation()}> + + + + { + e.stopPropagation(); + onDelete(); + }}> + + Excluir + + + +
+ ); +};