From 6efb71b59bc5e8059b146eb42b54eb3e30bc8bc3 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:06:30 +0000 Subject: [PATCH] Create HGTX PromptLab UI --- index.html | 10 +-- src/App.tsx | 15 +++- src/components/agents/AgentCard.tsx | 48 ++++++++++ src/components/layout/AppLayout.tsx | 16 ++++ src/components/layout/AppSidebar.tsx | 77 ++++++++++++++++ src/components/prompts/PromptCard.tsx | 63 +++++++++++++ src/index.css | 102 +++++++++++---------- src/pages/Agents.tsx | 59 ++++++++++++ src/pages/History.tsx | 124 ++++++++++++++++++++++++++ src/pages/LinkPrompt.tsx | 117 ++++++++++++++++++++++++ src/pages/Prompts.tsx | 70 +++++++++++++++ src/pages/TestPrompt.tsx | 113 +++++++++++++++++++++++ tailwind.config.ts | 29 ++++++ 13 files changed, 784 insertions(+), 59 deletions(-) create mode 100644 src/components/agents/AgentCard.tsx create mode 100644 src/components/layout/AppLayout.tsx create mode 100644 src/components/layout/AppSidebar.tsx create mode 100644 src/components/prompts/PromptCard.tsx create mode 100644 src/pages/Agents.tsx create mode 100644 src/pages/History.tsx create mode 100644 src/pages/LinkPrompt.tsx create mode 100644 src/pages/Prompts.tsx create mode 100644 src/pages/TestPrompt.tsx diff --git a/index.html b/index.html index 60afa3a..cd52b19 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,12 @@ - promptlab-hub - - + HGTX PromptLab - Laboratório de Prompts de IA + + - - + + diff --git a/src/App.tsx b/src/App.tsx index 18daf2e..132dd69 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,12 @@ import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 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"; const queryClient = new QueryClient(); @@ -15,7 +20,13 @@ const App = () => ( - } /> + }> + } /> + } /> + } /> + } /> + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/agents/AgentCard.tsx b/src/components/agents/AgentCard.tsx new file mode 100644 index 0000000..121ae14 --- /dev/null +++ b/src/components/agents/AgentCard.tsx @@ -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 ( + +
+
+
+ +
+
+

{name}

+ + {status === "active" ? "Ativo" : "Inativo"} + +
+
+
+ +

{description}

+ + {promptName ? ( +
+
+

Prompt vinculado

+

{promptName}

+
+ +
+ ) : ( + + )} +
+ ); +} diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx new file mode 100644 index 0000000..1649024 --- /dev/null +++ b/src/components/layout/AppLayout.tsx @@ -0,0 +1,16 @@ +import { SidebarProvider } from "@/components/ui/sidebar"; +import { AppSidebar } from "./AppSidebar"; +import { Outlet } from "react-router-dom"; + +export function AppLayout() { + return ( + +
+ +
+ +
+
+
+ ); +} diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx new file mode 100644 index 0000000..689e51d --- /dev/null +++ b/src/components/layout/AppSidebar.tsx @@ -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 ( + + +
+
+ +
+
+

HGTX

+

PromptLab

+
+
+
+ + + + + Navegação + + + + {menuItems.map((item) => ( + + + + `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.title} + + + + ))} + + + + +
+ ); +} diff --git a/src/components/prompts/PromptCard.tsx b/src/components/prompts/PromptCard.tsx new file mode 100644 index 0000000..393c98b --- /dev/null +++ b/src/components/prompts/PromptCard.tsx @@ -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 ( + +
+
+
+ +
+
+

{title}

+

+ por {creator} • {lastModified} +

+
+
+ + {status === "published" ? "Publicado" : "Rascunho"} + +
+ +
+ + + + +
+
+ ); +} diff --git a/src/index.css b/src/index.css index 4844bbd..b40404d 100644 --- a/src/index.css +++ b/src/index.css @@ -8,89 +8,87 @@ All colors MUST be HSL. @layer base { :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; + --background: 0 0% 98%; + --foreground: 222 47% 11%; --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card-foreground: 222 47% 11%; --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover-foreground: 222 47% 11%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; + --primary: 221 83% 53%; + --primary-foreground: 0 0% 100%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: 220 14% 96%; + --secondary-foreground: 222 47% 11%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: 220 14% 96%; + --muted-foreground: 215 16% 47%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: 262 83% 58%; + --accent-foreground: 0 0% 100%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; + --destructive: 0 84% 60%; + --destructive-foreground: 0 0% 100%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --border: 220 13% 91%; + --input: 220 13% 91%; + --ring: 221 83% 53%; - --radius: 0.5rem; - - --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%; + --radius: 0.75rem; + --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-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 { - --background: 222.2 84% 4.9%; + --background: 222 47% 11%; --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; + --card: 224 71% 4%; --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; + --popover: 224 71% 4%; --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; + --primary: 221 83% 53%; + --primary-foreground: 0 0% 100%; - --secondary: 217.2 32.6% 17.5%; + --secondary: 217 33% 17%; --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; + --muted: 217 33% 17%; + --muted-foreground: 215 20% 65%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; + --accent: 262 83% 58%; + --accent-foreground: 0 0% 100%; - --destructive: 0 62.8% 30.6%; + --destructive: 0 63% 31%; --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; + --border: 217 33% 17%; + --input: 217 33% 17%; + --ring: 224 76% 48%; + + --sidebar-background: 224 71% 4%; + --sidebar-foreground: 210 40% 98%; + --sidebar-primary: 221 83% 53%; --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-accent: 217 33% 17%; + --sidebar-accent-foreground: 210 40% 98%; + --sidebar-border: 217 33% 17%; + --sidebar-ring: 224 76% 48%; } } diff --git a/src/pages/Agents.tsx b/src/pages/Agents.tsx new file mode 100644 index 0000000..7016c32 --- /dev/null +++ b/src/pages/Agents.tsx @@ -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 ( +
+
+
+

Agentes de IA

+

+ Gerencie os agentes de IA e seus prompts vinculados +

+
+ +
+ +
+ {agents.map((agent) => ( + console.log("Link prompt", agent.id)} + /> + ))} +
+
+ ); +} diff --git a/src/pages/History.tsx b/src/pages/History.tsx new file mode 100644 index 0000000..87d2470 --- /dev/null +++ b/src/pages/History.tsx @@ -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 ( +
+
+

Histórico de Versões

+

+ Visualize e restaure versões anteriores dos seus prompts +

+
+ + +
+
+ + +
+
+
+ +
+
+ +
+ {versions.map((version) => ( +
+
+ + +
+
+ + {version.version} + + {version.current && ( + + + Atual + + )} +
+ {!version.current && ( + + )} +
+ +

{version.summary}

+

+ por {version.author} • {version.date} +

+
+
+ ))} +
+
+
+ ); +} diff --git a/src/pages/LinkPrompt.tsx b/src/pages/LinkPrompt.tsx new file mode 100644 index 0000000..8fb1465 --- /dev/null +++ b/src/pages/LinkPrompt.tsx @@ -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 ( +
+
+

+ Vincular Prompt ao Agente +

+

+ Associe um prompt específico a um agente de IA +

+
+ + +
+
+ + +
+ +
+ + +
+ + {selectedAgent && selectedPrompt && ( + +

Resumo da Vinculação

+
+
+ Agente: + + {selectedAgent === "suporte" && "Agente de Suporte"} + {selectedAgent === "vendas" && "Agente de Vendas"} + {selectedAgent === "analise" && "Agente de Análise"} + +
+
+ Prompt: + + {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"} + +
+
+ Data: + + {new Date().toLocaleDateString("pt-BR")} + +
+
+
+ )} + + +
+
+
+ ); +} diff --git a/src/pages/Prompts.tsx b/src/pages/Prompts.tsx new file mode 100644 index 0000000..ca466d4 --- /dev/null +++ b/src/pages/Prompts.tsx @@ -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 ( +
+
+
+

Gestão de Prompts

+

+ Crie, edite e gerencie os prompts dos seus agentes de IA +

+
+ +
+ +
+ {prompts.map((prompt) => ( + console.log("Edit", prompt.id)} + onHistory={() => console.log("History", prompt.id)} + onTest={() => console.log("Test", prompt.id)} + onDuplicate={() => console.log("Duplicate", prompt.id)} + /> + ))} +
+
+ ); +} diff --git a/src/pages/TestPrompt.tsx b/src/pages/TestPrompt.tsx new file mode 100644 index 0000000..07e8e37 --- /dev/null +++ b/src/pages/TestPrompt.tsx @@ -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 ( +
+
+

Teste de Prompt

+

+ Teste seus prompts em tempo real e avalie as respostas geradas +

+
+ +
+ +
+
+ + +
+ +
+ +