Create HGTX PromptLab UI

This commit is contained in:
gpt-engineer-app[bot]
2025-10-29 12:06:30 +00:00
parent d712cc7bb5
commit 6efb71b59b
13 changed files with 784 additions and 59 deletions
+5 -5
View File
@@ -3,12 +3,12 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>promptlab-hub</title> <title>HGTX PromptLab - Laboratório de Prompts de IA</title>
<meta name="description" content="Lovable Generated Project" /> <meta name="description" content="Crie, edite, teste e versione prompts para agentes de IA de forma profissional e eficiente" />
<meta name="author" content="Lovable" /> <meta name="author" content="HGTX" />
<meta property="og:title" content="promptlab-hub" /> <meta property="og:title" content="HGTX PromptLab - Laboratório de Prompts de IA" />
<meta property="og:description" content="Lovable Generated Project" /> <meta property="og:description" content="Crie, edite, teste e versione prompts para agentes de IA de forma profissional e eficiente" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" /> <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
+13 -2
View File
@@ -3,7 +3,12 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index"; import { AppLayout } from "./components/layout/AppLayout";
import Agents from "./pages/Agents";
import Prompts from "./pages/Prompts";
import TestPrompt from "./pages/TestPrompt";
import History from "./pages/History";
import LinkPrompt from "./pages/LinkPrompt";
import NotFound from "./pages/NotFound"; import NotFound from "./pages/NotFound";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -15,7 +20,13 @@ const App = () => (
<Sonner /> <Sonner />
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route element={<AppLayout />}>
<Route path="/" element={<Agents />} />
<Route path="/prompts" element={<Prompts />} />
<Route path="/test" element={<TestPrompt />} />
<Route path="/history" element={<History />} />
<Route path="/link" element={<LinkPrompt />} />
</Route>
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>
+48
View File
@@ -0,0 +1,48 @@
import { Bot, ExternalLink } from "lucide-react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
interface AgentCardProps {
name: string;
description: string;
promptName?: string;
status: "active" | "inactive";
onLinkPrompt: () => void;
}
export function AgentCard({ name, description, promptName, status, onLinkPrompt }: AgentCardProps) {
return (
<Card className="p-6 hover:shadow-hover transition-all duration-200 animate-fade-in">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div className="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center">
<Bot className="h-6 w-6 text-primary" />
</div>
<div>
<h3 className="font-semibold text-foreground">{name}</h3>
<Badge variant={status === "active" ? "default" : "secondary"} className="mt-1">
{status === "active" ? "Ativo" : "Inativo"}
</Badge>
</div>
</div>
</div>
<p className="text-sm text-muted-foreground mb-4">{description}</p>
{promptName ? (
<div className="flex items-center justify-between p-3 bg-muted rounded-lg">
<div>
<p className="text-xs text-muted-foreground">Prompt vinculado</p>
<p className="text-sm font-medium text-foreground">{promptName}</p>
</div>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</div>
) : (
<Button onClick={onLinkPrompt} variant="outline" className="w-full">
Vincular Prompt
</Button>
)}
</Card>
);
}
+16
View File
@@ -0,0 +1,16 @@
import { SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "./AppSidebar";
import { Outlet } from "react-router-dom";
export function AppLayout() {
return (
<SidebarProvider>
<div className="min-h-screen flex w-full bg-background">
<AppSidebar />
<main className="flex-1 p-6 lg:p-8">
<Outlet />
</main>
</div>
</SidebarProvider>
);
}
+77
View File
@@ -0,0 +1,77 @@
import { NavLink } from "react-router-dom";
import {
Bot,
FileText,
TestTube,
History,
Link,
} from "lucide-react";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarHeader,
} from "@/components/ui/sidebar";
const menuItems = [
{ title: "Agentes", url: "/", icon: Bot },
{ title: "Prompts", url: "/prompts", icon: FileText },
{ title: "Testar Prompt", url: "/test", icon: TestTube },
{ title: "Histórico", url: "/history", icon: History },
{ title: "Vincular", url: "/link", icon: Link },
];
export function AppSidebar() {
return (
<Sidebar className="border-r border-border">
<SidebarHeader className="border-b border-border px-6 py-4">
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
<Bot className="h-5 w-5 text-primary-foreground" />
</div>
<div>
<h1 className="text-lg font-semibold text-foreground">HGTX</h1>
<p className="text-xs text-muted-foreground">PromptLab</p>
</div>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel className="text-xs font-medium text-muted-foreground px-3">
Navegação
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{menuItems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<NavLink
to={item.url}
end
className={({ isActive }) =>
`flex items-center gap-3 px-3 py-2 rounded-md transition-colors ${
isActive
? "bg-primary text-primary-foreground font-medium"
: "text-foreground hover:bg-muted"
}`
}
>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</NavLink>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
);
}
+63
View File
@@ -0,0 +1,63 @@
import { FileText, Edit, History, Copy, TestTube } from "lucide-react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
interface PromptCardProps {
title: string;
creator: string;
lastModified: string;
status: "draft" | "published";
onEdit: () => void;
onHistory: () => void;
onTest: () => void;
onDuplicate: () => void;
}
export function PromptCard({
title,
creator,
lastModified,
status,
onEdit,
onHistory,
onTest,
onDuplicate,
}: PromptCardProps) {
return (
<Card className="p-5 hover:shadow-hover transition-all duration-200 animate-fade-in">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div className="h-10 w-10 rounded-lg bg-accent/10 flex items-center justify-center">
<FileText className="h-5 w-5 text-accent" />
</div>
<div>
<h3 className="font-semibold text-foreground">{title}</h3>
<p className="text-xs text-muted-foreground mt-0.5">
por {creator} {lastModified}
</p>
</div>
</div>
<Badge variant={status === "published" ? "default" : "secondary"}>
{status === "published" ? "Publicado" : "Rascunho"}
</Badge>
</div>
<div className="flex items-center gap-2">
<Button onClick={onEdit} variant="outline" size="sm" className="flex-1">
<Edit className="h-4 w-4 mr-2" />
Editar
</Button>
<Button onClick={onHistory} variant="outline" size="sm">
<History className="h-4 w-4" />
</Button>
<Button onClick={onTest} variant="outline" size="sm">
<TestTube className="h-4 w-4" />
</Button>
<Button onClick={onDuplicate} variant="outline" size="sm">
<Copy className="h-4 w-4" />
</Button>
</div>
</Card>
);
}
+50 -52
View File
@@ -8,89 +8,87 @@ All colors MUST be HSL.
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 98%;
--foreground: 222.2 84% 4.9%; --foreground: 222 47% 11%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 222 47% 11%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 222 47% 11%;
--primary: 222.2 47.4% 11.2%; --primary: 221 83% 53%;
--primary-foreground: 210 40% 98%; --primary-foreground: 0 0% 100%;
--secondary: 210 40% 96.1%; --secondary: 220 14% 96%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 222 47% 11%;
--muted: 210 40% 96.1%; --muted: 220 14% 96%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 215 16% 47%;
--accent: 210 40% 96.1%; --accent: 262 83% 58%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 0 0% 100%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84% 60%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 100%;
--border: 214.3 31.8% 91.4%; --border: 220 13% 91%;
--input: 214.3 31.8% 91.4%; --input: 220 13% 91%;
--ring: 222.2 84% 4.9%; --ring: 221 83% 53%;
--radius: 0.5rem; --radius: 0.75rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 222 47% 11%;
--sidebar-primary: 221 83% 53%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 220 14% 96%;
--sidebar-accent-foreground: 222 47% 11%;
--sidebar-border: 220 13% 91%; --sidebar-border: 220 13% 91%;
--sidebar-ring: 221 83% 53%;
--sidebar-ring: 217.2 91.2% 59.8%; --shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-hover: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--transition-smooth: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 222 47% 11%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 224 71% 4%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%; --popover: 224 71% 4%;
--popover-foreground: 210 40% 98%; --popover-foreground: 210 40% 98%;
--primary: 210 40% 98%; --primary: 221 83% 53%;
--primary-foreground: 222.2 47.4% 11.2%; --primary-foreground: 0 0% 100%;
--secondary: 217.2 32.6% 17.5%; --secondary: 217 33% 17%;
--secondary-foreground: 210 40% 98%; --secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%; --muted: 217 33% 17%;
--muted-foreground: 215 20.2% 65.1%; --muted-foreground: 215 20% 65%;
--accent: 217.2 32.6% 17.5%; --accent: 262 83% 58%;
--accent-foreground: 210 40% 98%; --accent-foreground: 0 0% 100%;
--destructive: 0 62.8% 30.6%; --destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%; --border: 217 33% 17%;
--input: 217.2 32.6% 17.5%; --input: 217 33% 17%;
--ring: 212.7 26.8% 83.9%; --ring: 224 76% 48%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%; --sidebar-background: 224 71% 4%;
--sidebar-primary: 224.3 76.3% 48%; --sidebar-foreground: 210 40% 98%;
--sidebar-primary: 221 83% 53%;
--sidebar-primary-foreground: 0 0% 100%; --sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%; --sidebar-accent: 217 33% 17%;
--sidebar-accent-foreground: 240 4.8% 95.9%; --sidebar-accent-foreground: 210 40% 98%;
--sidebar-border: 240 3.7% 15.9%; --sidebar-border: 217 33% 17%;
--sidebar-ring: 217.2 91.2% 59.8%; --sidebar-ring: 224 76% 48%;
} }
} }
+59
View File
@@ -0,0 +1,59 @@
import { useState } from "react";
import { AgentCard } from "@/components/agents/AgentCard";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
export default function Agents() {
const [agents] = useState([
{
id: 1,
name: "Agente de Suporte",
description: "Responde dúvidas de clientes sobre produtos e serviços da empresa",
promptName: "Prompt de Atendimento v2.1",
status: "active" as const,
},
{
id: 2,
name: "Agente de Vendas",
description: "Qualifica leads e apresenta produtos de forma personalizada",
promptName: "Prompt de Vendas v1.5",
status: "active" as const,
},
{
id: 3,
name: "Agente de Análise",
description: "Analisa dados e gera insights para tomada de decisão",
status: "inactive" as const,
},
]);
return (
<div className="animate-fade-in">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold text-foreground mb-2">Agentes de IA</h1>
<p className="text-muted-foreground">
Gerencie os agentes de IA e seus prompts vinculados
</p>
</div>
<Button className="gap-2">
<Plus className="h-4 w-4" />
Novo Agente
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{agents.map((agent) => (
<AgentCard
key={agent.id}
name={agent.name}
description={agent.description}
promptName={agent.promptName}
status={agent.status}
onLinkPrompt={() => console.log("Link prompt", agent.id)}
/>
))}
</div>
</div>
);
}
+124
View File
@@ -0,0 +1,124 @@
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { RotateCcw, GitBranch } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function History() {
const versions = [
{
id: 1,
version: "v2.1",
author: "João Silva",
date: "2 dias atrás",
summary: "Melhorias na formatação de respostas e tom mais amigável",
current: true,
},
{
id: 2,
version: "v2.0",
author: "João Silva",
date: "1 semana atrás",
summary: "Adicionado contexto sobre produtos lançados em 2024",
current: false,
},
{
id: 3,
version: "v1.9",
author: "Maria Santos",
date: "2 semanas atrás",
summary: "Ajustes no limite de caracteres da resposta",
current: false,
},
{
id: 4,
version: "v1.8",
author: "Pedro Costa",
date: "3 semanas atrás",
summary: "Versão inicial com estrutura básica do prompt",
current: false,
},
];
return (
<div className="animate-fade-in max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground mb-2">Histórico de Versões</h1>
<p className="text-muted-foreground">
Visualize e restaure versões anteriores dos seus prompts
</p>
</div>
<Card className="p-6 mb-6">
<div className="flex items-center gap-4">
<div className="flex-1">
<label className="text-sm font-medium text-foreground mb-2 block">
Selecione o Prompt
</label>
<Select defaultValue="atendimento">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="atendimento">Prompt de Atendimento</SelectItem>
<SelectItem value="vendas">Prompt de Vendas</SelectItem>
<SelectItem value="analise">Prompt de Análise</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</Card>
<div className="relative">
<div className="absolute left-8 top-0 bottom-0 w-0.5 bg-border" />
<div className="space-y-6">
{versions.map((version) => (
<div key={version.id} className="relative pl-16 animate-fade-in">
<div
className={`absolute left-6 w-5 h-5 rounded-full border-2 ${
version.current
? "bg-primary border-primary"
: "bg-background border-border"
}`}
/>
<Card className="p-5 hover:shadow-hover transition-shadow">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<Badge variant={version.current ? "default" : "secondary"}>
{version.version}
</Badge>
{version.current && (
<Badge variant="outline" className="gap-1">
<GitBranch className="h-3 w-3" />
Atual
</Badge>
)}
</div>
{!version.current && (
<Button variant="outline" size="sm" className="gap-2">
<RotateCcw className="h-4 w-4" />
Restaurar
</Button>
)}
</div>
<p className="text-foreground font-medium mb-2">{version.summary}</p>
<p className="text-sm text-muted-foreground">
por {version.author} {version.date}
</p>
</Card>
</div>
))}
</div>
</div>
</div>
);
}
+117
View File
@@ -0,0 +1,117 @@
import { useState } from "react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Link as LinkIcon, Check } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useToast } from "@/hooks/use-toast";
export default function LinkPrompt() {
const [selectedAgent, setSelectedAgent] = useState("");
const [selectedPrompt, setSelectedPrompt] = useState("");
const { toast } = useToast();
const handleLink = () => {
if (selectedAgent && selectedPrompt) {
toast({
title: "Prompt vinculado com sucesso!",
description: "O agente já está usando o novo prompt.",
});
}
};
return (
<div className="animate-fade-in max-w-3xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground mb-2">
Vincular Prompt ao Agente
</h1>
<p className="text-muted-foreground">
Associe um prompt específico a um agente de IA
</p>
</div>
<Card className="p-8">
<div className="space-y-6">
<div>
<Label htmlFor="agent-select">Selecione o Agente</Label>
<Select value={selectedAgent} onValueChange={setSelectedAgent}>
<SelectTrigger id="agent-select">
<SelectValue placeholder="Escolha um agente" />
</SelectTrigger>
<SelectContent>
<SelectItem value="suporte">Agente de Suporte</SelectItem>
<SelectItem value="vendas">Agente de Vendas</SelectItem>
<SelectItem value="analise">Agente de Análise</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="prompt-select">Selecione o Prompt</Label>
<Select value={selectedPrompt} onValueChange={setSelectedPrompt}>
<SelectTrigger id="prompt-select">
<SelectValue placeholder="Escolha um prompt" />
</SelectTrigger>
<SelectContent>
<SelectItem value="atendimento">
Prompt de Atendimento v2.1
</SelectItem>
<SelectItem value="vendas">Prompt de Vendas v1.5</SelectItem>
<SelectItem value="analise">Prompt de Análise</SelectItem>
<SelectItem value="marketing">Prompt de Marketing</SelectItem>
</SelectContent>
</Select>
</div>
{selectedAgent && selectedPrompt && (
<Card className="p-4 bg-muted animate-scale-in">
<h3 className="font-medium text-foreground mb-2">Resumo da Vinculação</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Agente:</span>
<span className="text-foreground font-medium">
{selectedAgent === "suporte" && "Agente de Suporte"}
{selectedAgent === "vendas" && "Agente de Vendas"}
{selectedAgent === "analise" && "Agente de Análise"}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Prompt:</span>
<span className="text-foreground font-medium">
{selectedPrompt === "atendimento" && "Prompt de Atendimento v2.1"}
{selectedPrompt === "vendas" && "Prompt de Vendas v1.5"}
{selectedPrompt === "analise" && "Prompt de Análise"}
{selectedPrompt === "marketing" && "Prompt de Marketing"}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Data:</span>
<span className="text-foreground font-medium">
{new Date().toLocaleDateString("pt-BR")}
</span>
</div>
</div>
</Card>
)}
<Button
onClick={handleLink}
disabled={!selectedAgent || !selectedPrompt}
className="w-full gap-2"
size="lg"
>
<LinkIcon className="h-4 w-4" />
Vincular Prompt
</Button>
</div>
</Card>
</div>
);
}
+70
View File
@@ -0,0 +1,70 @@
import { useState } from "react";
import { PromptCard } from "@/components/prompts/PromptCard";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
export default function Prompts() {
const [prompts] = useState([
{
id: 1,
title: "Prompt de Atendimento v2.1",
creator: "João Silva",
lastModified: "2 dias atrás",
status: "published" as const,
},
{
id: 2,
title: "Prompt de Vendas v1.5",
creator: "Maria Santos",
lastModified: "5 dias atrás",
status: "published" as const,
},
{
id: 3,
title: "Prompt de Análise",
creator: "Pedro Costa",
lastModified: "1 semana atrás",
status: "draft" as const,
},
{
id: 4,
title: "Prompt de Marketing",
creator: "Ana Lima",
lastModified: "3 dias atrás",
status: "published" as const,
},
]);
return (
<div className="animate-fade-in">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold text-foreground mb-2">Gestão de Prompts</h1>
<p className="text-muted-foreground">
Crie, edite e gerencie os prompts dos seus agentes de IA
</p>
</div>
<Button className="gap-2">
<Plus className="h-4 w-4" />
Criar Prompt
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{prompts.map((prompt) => (
<PromptCard
key={prompt.id}
title={prompt.title}
creator={prompt.creator}
lastModified={prompt.lastModified}
status={prompt.status}
onEdit={() => console.log("Edit", prompt.id)}
onHistory={() => console.log("History", prompt.id)}
onTest={() => console.log("Test", prompt.id)}
onDuplicate={() => console.log("Duplicate", prompt.id)}
/>
))}
</div>
</div>
);
}
+113
View File
@@ -0,0 +1,113 @@
import { useState } from "react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { ThumbsUp, ThumbsDown, Send, Sparkles } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function TestPrompt() {
const [input, setInput] = useState("");
const [output, setOutput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handleTest = () => {
setIsLoading(true);
setTimeout(() => {
setOutput(
"Esta é uma resposta simulada do agente de IA. Em produção, esta resposta seria gerada pelo modelo de IA configurado com o prompt selecionado."
);
setIsLoading(false);
}, 1500);
};
return (
<div className="animate-fade-in max-w-5xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground mb-2">Teste de Prompt</h1>
<p className="text-muted-foreground">
Teste seus prompts em tempo real e avalie as respostas geradas
</p>
</div>
<div className="grid grid-cols-1 gap-6">
<Card className="p-6">
<div className="space-y-4">
<div>
<Label htmlFor="prompt-select">Selecione o Prompt</Label>
<Select>
<SelectTrigger id="prompt-select">
<SelectValue placeholder="Escolha um prompt" />
</SelectTrigger>
<SelectContent>
<SelectItem value="atendimento">Prompt de Atendimento v2.1</SelectItem>
<SelectItem value="vendas">Prompt de Vendas v1.5</SelectItem>
<SelectItem value="analise">Prompt de Análise</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="input">Entrada de Teste</Label>
<Textarea
id="input"
placeholder="Digite sua mensagem de teste aqui..."
value={input}
onChange={(e) => setInput(e.target.value)}
rows={4}
className="resize-none"
/>
</div>
<Button onClick={handleTest} disabled={!input || isLoading} className="w-full gap-2">
{isLoading ? (
<>
<Sparkles className="h-4 w-4 animate-spin" />
Processando...
</>
) : (
<>
<Send className="h-4 w-4" />
Testar Prompt
</>
)}
</Button>
</div>
</Card>
{output && (
<Card className="p-6 animate-scale-in">
<div className="space-y-4">
<div>
<Label>Resposta Gerada</Label>
<div className="mt-2 p-4 bg-muted rounded-lg">
<p className="text-foreground">{output}</p>
</div>
</div>
<div>
<Label className="mb-2 block">Avaliar Resposta</Label>
<div className="flex gap-2">
<Button variant="outline" className="flex-1 gap-2">
<ThumbsUp className="h-4 w-4" />
Boa Resposta
</Button>
<Button variant="outline" className="flex-1 gap-2">
<ThumbsDown className="h-4 w-4" />
Precisa Melhorar
</Button>
</div>
</div>
</div>
</Card>
)}
</div>
</div>
);
}
+29
View File
@@ -80,10 +80,39 @@ export default {
height: "0", height: "0",
}, },
}, },
"fade-in": {
"0%": {
opacity: "0",
transform: "translateY(10px)"
},
"100%": {
opacity: "1",
transform: "translateY(0)"
}
},
"scale-in": {
"0%": {
transform: "scale(0.95)",
opacity: "0"
},
"100%": {
transform: "scale(1)",
opacity: "1"
}
},
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
"fade-in": "fade-in 0.3s ease-out",
"scale-in": "scale-in 0.2s ease-out",
},
boxShadow: {
'card': 'var(--shadow-card)',
'hover': 'var(--shadow-hover)',
},
transitionTimingFunction: {
'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',
}, },
}, },
}, },