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:
+185
-40
@@ -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,53 +160,116 @@ export default function TestPrompt() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearChat = () => {
|
const handleDeleteConversation = (conversationId: string) => {
|
||||||
setMessages([]);
|
setConversations((prev) => prev.filter((c) => c.id !== conversationId));
|
||||||
|
if (currentConversationId === conversationId) {
|
||||||
|
setCurrentConversationId(null);
|
||||||
|
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"
|
||||||
</Label>
|
onClick={handleNewConversation}
|
||||||
<Select value={selectedAgent} onValueChange={setSelectedAgent}>
|
disabled={!selectedAgent}
|
||||||
<SelectTrigger id="agent-select">
|
className="gap-2"
|
||||||
<SelectValue placeholder="Escolha um agente" />
|
>
|
||||||
</SelectTrigger>
|
<Plus className="h-4 w-4" />
|
||||||
<SelectContent>
|
Nova
|
||||||
{agents.map((agent) => (
|
</Button>
|
||||||
<SelectItem key={agent.id} value={agent.id}>
|
</div>
|
||||||
{agent.name}
|
<div>
|
||||||
</SelectItem>
|
<Label htmlFor="sidebar-agent-select" className="mb-2 block text-sm">
|
||||||
))}
|
Agente
|
||||||
</SelectContent>
|
</Label>
|
||||||
</Select>
|
<Select value={selectedAgent} onValueChange={setSelectedAgent}>
|
||||||
|
<SelectTrigger id="sidebar-agent-select" className="h-9">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{agents.map((agent) => (
|
||||||
|
<SelectItem key={agent.id} value={agent.id}>
|
||||||
|
{agent.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</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>
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user