Create HGTX PromptLab UI
This commit is contained in:
+5
-5
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
@@ -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%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user