Add conversation history to Test Prompt

Implements persistent/voluminous chat history on the Testar Agente screen:
- Prepare to store and load previous conversations (localStorage) for the selected agent
- Create structure for conversations and messages
- Load existing conversations on mount and save changes
- Add support to create new conversations and load them when selected
This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 20:04:58 +00:00
parent da94a7e4db
commit c1d2cf6d79
+173 -28
View File
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { Send, Bot, User, RotateCcw } from "lucide-react"; import { Send, Bot, User, RotateCcw, MessageSquare, Plus } from "lucide-react";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { import {
Select, Select,
@@ -13,6 +13,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
type Message = { type Message = {
id: string; id: string;
@@ -20,11 +21,23 @@ type Message = {
content: string; content: string;
}; };
type Conversation = {
id: string;
agentId: string;
agentName: string;
title: string;
messages: Message[];
createdAt: string;
updatedAt: string;
};
export default function TestPrompt() { export default function TestPrompt() {
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [selectedAgent, setSelectedAgent] = useState(""); const [selectedAgent, setSelectedAgent] = useState("");
const [conversations, setConversations] = useState<Conversation[]>([]);
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null);
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const { toast } = useToast(); const { toast } = useToast();
@@ -35,6 +48,21 @@ export default function TestPrompt() {
{ id: "3", name: "Agente de Análise" }, { id: "3", name: "Agente de Análise" },
]; ];
// Carrega conversas do localStorage
useEffect(() => {
const stored = localStorage.getItem("test_conversations");
if (stored) {
setConversations(JSON.parse(stored));
}
}, []);
// Salva conversas no localStorage
useEffect(() => {
if (conversations.length > 0) {
localStorage.setItem("test_conversations", JSON.stringify(conversations));
}
}, [conversations]);
useEffect(() => { useEffect(() => {
if (scrollRef.current) { if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight; scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
@@ -43,9 +71,59 @@ export default function TestPrompt() {
// Limpa a conversa ao trocar de agente // Limpa a conversa ao trocar de agente
useEffect(() => { useEffect(() => {
setMessages([]); handleNewConversation();
}, [selectedAgent]); }, [selectedAgent]);
const handleNewConversation = () => {
if (!selectedAgent) return;
const agent = agents.find((a) => a.id === selectedAgent);
const newConversation: Conversation = {
id: Date.now().toString(),
agentId: selectedAgent,
agentName: agent?.name || "Agente",
title: "Nova Conversa",
messages: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
setConversations((prev) => [newConversation, ...prev]);
setCurrentConversationId(newConversation.id);
setMessages([]);
};
const handleSelectConversation = (conversationId: string) => {
const conversation = conversations.find((c) => c.id === conversationId);
if (conversation) {
setCurrentConversationId(conversationId);
setSelectedAgent(conversation.agentId);
setMessages(conversation.messages);
}
};
const updateConversation = (newMessages: Message[]) => {
if (!currentConversationId) return;
setConversations((prev) =>
prev.map((conv) => {
if (conv.id === currentConversationId) {
const title =
newMessages.length > 0
? newMessages[0].content.substring(0, 50) + "..."
: "Nova Conversa";
return {
...conv,
messages: newMessages,
title,
updatedAt: new Date().toISOString(),
};
}
return conv;
})
);
};
const handleSend = () => { const handleSend = () => {
if (!input.trim() || !selectedAgent) return; if (!input.trim() || !selectedAgent) return;
@@ -55,7 +133,9 @@ export default function TestPrompt() {
content: input, content: input,
}; };
setMessages((prev) => [...prev, userMessage]); const newMessages = [...messages, userMessage];
setMessages(newMessages);
updateConversation(newMessages);
setInput(""); setInput("");
setIsLoading(true); setIsLoading(true);
@@ -66,7 +146,9 @@ export default function TestPrompt() {
content: content:
"Esta é uma resposta simulada do agente de IA. Em produção, esta resposta seria gerada pelo modelo de IA configurado no agente selecionado.", "Esta é uma resposta simulada do agente de IA. Em produção, esta resposta seria gerada pelo modelo de IA configurado no agente selecionado.",
}; };
setMessages((prev) => [...prev, aiMessage]); const finalMessages = [...newMessages, aiMessage];
setMessages(finalMessages);
updateConversation(finalMessages);
setIsLoading(false); setIsLoading(false);
}, 1500); }, 1500);
}; };
@@ -78,34 +160,42 @@ export default function TestPrompt() {
} }
}; };
const handleClearChat = () => { const handleDeleteConversation = (conversationId: string) => {
setConversations((prev) => prev.filter((c) => c.id !== conversationId));
if (currentConversationId === conversationId) {
setCurrentConversationId(null);
setMessages([]); setMessages([]);
}
toast({ toast({
title: "Conversa limpa", title: "Conversa excluída",
description: "O histórico de mensagens foi limpo com sucesso.", description: "A conversa foi removida com sucesso.",
}); });
}; };
return ( return (
<div className="animate-fade-in h-full flex flex-col max-w-5xl mx-auto"> <div className="animate-fade-in h-full flex gap-4">
<div className="mb-4 md:mb-6"> {/* Sidebar com conversas anteriores */}
<h1 className="text-2xl md:text-3xl font-bold text-foreground mb-2">Testar Agente</h1> <Card className="w-80 flex-shrink-0 flex flex-col overflow-hidden">
<p className="text-sm md:text-base text-muted-foreground">
Converse com o agente para testar seu comportamento
</p>
</div>
<Card className="flex-1 flex flex-col overflow-hidden">
{/* Selector de Agente */}
<div className="p-4 border-b border-border"> <div className="p-4 border-b border-border">
<div className="flex items-end justify-between gap-4"> <div className="flex items-center justify-between mb-4">
<div className="flex-1"> <h2 className="font-semibold text-foreground">Conversas</h2>
<Label htmlFor="agent-select" className="mb-2 block"> <Button
Selecione o Agente size="sm"
onClick={handleNewConversation}
disabled={!selectedAgent}
className="gap-2"
>
<Plus className="h-4 w-4" />
Nova
</Button>
</div>
<div>
<Label htmlFor="sidebar-agent-select" className="mb-2 block text-sm">
Agente
</Label> </Label>
<Select value={selectedAgent} onValueChange={setSelectedAgent}> <Select value={selectedAgent} onValueChange={setSelectedAgent}>
<SelectTrigger id="agent-select"> <SelectTrigger id="sidebar-agent-select" className="h-9">
<SelectValue placeholder="Escolha um agente" /> <SelectValue placeholder="Selecione" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{agents.map((agent) => ( {agents.map((agent) => (
@@ -116,15 +206,70 @@ export default function TestPrompt() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
</div>
<ScrollArea className="flex-1">
<div className="p-2 space-y-1">
{conversations
.filter((conv) => conv.agentId === selectedAgent)
.map((conversation) => (
<button
key={conversation.id}
onClick={() => handleSelectConversation(conversation.id)}
className={`w-full text-left p-3 rounded-lg transition-colors ${
currentConversationId === conversation.id
? "bg-primary/10 border border-primary/20"
: "hover:bg-muted"
}`}
>
<div className="flex items-start gap-2">
<MessageSquare className="h-4 w-4 mt-1 flex-shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">
{conversation.title}
</p>
<p className="text-xs text-muted-foreground">
{new Date(conversation.createdAt).toLocaleDateString("pt-BR")}
</p>
</div>
</div>
</button>
))}
{selectedAgent && conversations.filter((c) => c.agentId === selectedAgent).length === 0 && (
<div className="text-center py-8 text-muted-foreground text-sm">
Nenhuma conversa ainda
</div>
)}
</div>
</ScrollArea>
</Card>
{/* Área principal do chat */}
<Card className="flex-1 flex flex-col overflow-hidden">
{/* Header */}
<div className="p-4 border-b border-border">
<div className="flex items-center justify-between">
<div>
<h1 className="text-lg font-semibold text-foreground">
{selectedAgent
? agents.find((a) => a.id === selectedAgent)?.name
: "Testar Agente"}
</h1>
{currentConversationId && (
<p className="text-xs text-muted-foreground mt-1">
Conversa atual
</p>
)}
</div>
<Button <Button
variant="outline" variant="outline"
size="default" size="sm"
onClick={handleClearChat} onClick={handleNewConversation}
disabled={messages.length === 0} disabled={!selectedAgent}
className="gap-2" className="gap-2"
> >
<RotateCcw className="h-4 w-4" /> <Plus className="h-4 w-4" />
<span className="hidden sm:inline">Nova Conversa</span> Nova Conversa
</Button> </Button>
</div> </div>
</div> </div>