[PromptLab first commit]

This commit is contained in:
luisfepsale
2025-11-12 18:44:23 -03:00
parent 14db385a74
commit 2b2886a75c
15 changed files with 1284 additions and 577 deletions
+60 -233
View File
@@ -1,5 +1,6 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { CreateAgentDialog } from "@/components/agents/CreateAgentDialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -14,7 +15,7 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { Plus, Search, Bot, ChevronRight } from "lucide-react";
import { Plus, Search, Bot, ChevronRight, Loader2 } from "lucide-react";
import {
Select,
SelectContent,
@@ -22,232 +23,47 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { listAgents } from "@/lib/api/agents";
export default function Agents() {
const navigate = useNavigate();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [debouncedSearch, setDebouncedSearch] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(6);
const [agents] = useState([
{
id: 1,
name: "Agente de Atendimento",
status: "active" as const,
version: "2.1",
model: "Google Gemini 2.5 Flash",
createdAt: "2 dias atrás",
},
{
id: 2,
name: "Agente de Vendas",
status: "active" as const,
version: "1.5",
model: "OpenAI GPT-5",
createdAt: "5 dias atrás",
},
{
id: 3,
name: "Agente de Análise",
status: "inactive" as const,
version: "1.0",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "1 semana atrás",
},
{
id: 4,
name: "Agente de Suporte Técnico",
status: "active" as const,
version: "3.0",
model: "Google Gemini 2.5 Flash",
createdAt: "3 dias atrás",
},
{
id: 5,
name: "Agente de Marketing",
status: "active" as const,
version: "2.3",
model: "OpenAI GPT-5",
createdAt: "1 semana atrás",
},
{
id: 6,
name: "Agente de Recursos Humanos",
status: "inactive" as const,
version: "1.8",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "2 semanas atrás",
},
{
id: 7,
name: "Agente de Cobrança",
status: "active" as const,
version: "2.0",
model: "Google Gemini 2.5 Flash",
createdAt: "4 dias atrás",
},
{
id: 8,
name: "Agente de Pesquisa",
status: "active" as const,
version: "1.9",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "1 semana atrás",
},
{
id: 9,
name: "Agente de Tradução",
status: "inactive" as const,
version: "1.2",
model: "OpenAI GPT-5",
createdAt: "3 semanas atrás",
},
{
id: 10,
name: "Agente de Qualidade",
status: "active" as const,
version: "2.5",
model: "Google Gemini 2.5 Flash",
createdAt: "5 dias atrás",
},
{
id: 11,
name: "Agente de Onboarding",
status: "active" as const,
version: "1.7",
model: "OpenAI GPT-5",
createdAt: "1 semana atrás",
},
{
id: 12,
name: "Agente de Feedbacks",
status: "active" as const,
version: "3.1",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "2 dias atrás",
},
{
id: 13,
name: "Agente de Logística",
status: "inactive" as const,
version: "1.4",
model: "Google Gemini 2.5 Flash",
createdAt: "2 semanas atrás",
},
{
id: 14,
name: "Agente de Compliance",
status: "active" as const,
version: "2.2",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "6 dias atrás",
},
{
id: 15,
name: "Agente de Treinamento",
status: "active" as const,
version: "1.6",
model: "OpenAI GPT-5",
createdAt: "1 semana atrás",
},
{
id: 16,
name: "Agente de Relatórios",
status: "active" as const,
version: "2.8",
model: "Google Gemini 2.5 Flash",
createdAt: "3 dias atrás",
},
{
id: 17,
name: "Agente de Moderação",
status: "inactive" as const,
version: "1.3",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "4 semanas atrás",
},
{
id: 18,
name: "Agente de Recomendação",
status: "active" as const,
version: "2.4",
model: "OpenAI GPT-5",
createdAt: "1 semana atrás",
},
{
id: 19,
name: "Agente de Documentação",
status: "active" as const,
version: "1.1",
model: "Google Gemini 2.5 Flash",
createdAt: "5 dias atrás",
},
{
id: 20,
name: "Agente de Testes",
status: "inactive" as const,
version: "3.2",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "2 semanas atrás",
},
{
id: 21,
name: "Agente de Priorização",
status: "active" as const,
version: "2.6",
model: "OpenAI GPT-5",
createdAt: "4 dias atrás",
},
{
id: 22,
name: "Agente de Integração",
status: "active" as const,
version: "1.9",
model: "Google Gemini 2.5 Flash",
createdAt: "1 semana atrás",
},
{
id: 23,
name: "Agente de Notificações",
status: "active" as const,
version: "2.7",
model: "Anthropic Claude Sonnet 4.5",
createdAt: "3 dias atrás",
},
{
id: 24,
name: "Agente de Backup",
status: "inactive" as const,
version: "1.5",
model: "OpenAI GPT-5",
createdAt: "3 semanas atrás",
},
{
id: 25,
name: "Agente de Auditoria",
status: "active" as const,
version: "2.9",
model: "Google Gemini 2.5 Flash",
createdAt: "2 dias atrás",
},
]);
// Debounce search query
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearch(searchQuery);
setCurrentPage(1); // Reset to first page on search
}, 500);
const filteredAgents = agents.filter(agent =>
agent.name.toLowerCase().includes(searchQuery.toLowerCase())
);
return () => {
clearTimeout(handler);
};
}, [searchQuery]);
// Paginação
const totalPages = Math.ceil(filteredAgents.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedAgents = filteredAgents.slice(startIndex, endIndex);
// Fetch agents from API
const {
data: agentsData,
isLoading,
isError,
refetch,
} = useQuery({
queryKey: ["agents", debouncedSearch, currentPage, itemsPerPage],
queryFn: () =>
listAgents(
debouncedSearch || undefined,
currentPage,
itemsPerPage
),
});
// Reset para primeira página quando buscar
const handleSearch = (query: string) => {
setSearchQuery(query);
setCurrentPage(1);
};
const agents = agentsData?.data || [];
const totalPages = agentsData?.total_pages || 0;
const totalAgents = agentsData?.total || 0;
return (
<div className="animate-fade-in">
@@ -270,13 +86,13 @@ export default function Agents() {
<Input
placeholder="Pesquisar agentes..."
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 mt-2">
<p className="text-sm text-muted-foreground">
{filteredAgents.length} {filteredAgents.length === 1 ? 'agente encontrado' : 'agentes encontrados'}
{totalAgents} {totalAgents === 1 ? 'agente encontrado' : 'agentes encontrados'}
</p>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
@@ -309,22 +125,33 @@ export default function Agents() {
</div>
</div>
{filteredAgents.length === 0 ? (
{isLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
) : isError ? (
<div className="text-center py-12">
<p className="text-destructive mb-4">Erro ao carregar agentes.</p>
<Button onClick={() => refetch()} variant="outline">
Tentar novamente
</Button>
</div>
) : agents.length === 0 ? (
<div className="text-center py-12">
<p className="text-muted-foreground">Nenhum agente encontrado.</p>
</div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{paginatedAgents.map((agent) => (
<Card
key={agent.id}
{agents.map((agent) => (
<Card
key={agent.agente_id}
className="group relative overflow-hidden border-2 transition-all duration-300 hover:shadow-lg hover:scale-[1.02] bg-gradient-to-br from-card to-card/50 cursor-pointer"
onClick={() => navigate(`/agents/${agent.id}`)}
onClick={() => navigate(`/agents/${agent.agente_id}`)}
>
{/* Glow effect on hover */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<CardHeader className="relative pb-3">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-3 flex-1 min-w-0">
@@ -332,10 +159,10 @@ export default function Agents() {
<Bot className="h-7 w-7 text-primary" />
</div>
<div className="flex-1 min-w-0">
<CardTitle className="text-xl truncate">{agent.name}</CardTitle>
<CardTitle className="text-xl truncate">{agent.nome_agente}</CardTitle>
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline" className="text-xs">
v{agent.version}
v{agent.versao_agente}
</Badge>
</div>
</div>
@@ -349,20 +176,20 @@ export default function Agents() {
<CardContent className="relative space-y-3">
<div className="p-3 rounded-lg bg-muted/50">
<p className="text-xs text-muted-foreground mb-2">Modelo de IA</p>
<p className="text-sm font-medium">{agent.model}</p>
<p className="text-sm font-medium">{agent.modelo_ia}</p>
</div>
<div className="flex items-center justify-between pt-2 border-t">
<span className="text-xs text-muted-foreground">Status</span>
<Badge
variant={agent.status === "active" ? "default" : "secondary"}
<Badge
variant={agent.status === "ativo" ? "default" : "secondary"}
className={
agent.status === "active"
? "bg-green-600 hover:bg-green-700 shadow-sm"
agent.status === "ativo"
? "bg-green-600 hover:bg-green-700 shadow-sm"
: "bg-muted text-muted-foreground hover:bg-muted"
}
>
{agent.status === "active" ? "● Ativo" : "○ Inativo"}
{agent.status === "ativo" ? "● Ativo" : "○ Inativo"}
</Badge>
</div>
</CardContent>