[PromptLab first commit]
This commit is contained in:
+60
-233
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user