import { useState, useRef, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { BrowserFrame } from "@/components/Browser/BrowserFrame"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Github, Settings, CheckCircle, XCircle, Loader2, GitBranch, GitCommit, FolderSearch, FileCode, Zap, Bot, ExternalLink, Sparkles, Play, Eye, Paperclip, X, FileText, Upload, RotateCcw, History, Monitor, Rocket, RefreshCw, Smartphone, Tablet, Globe, Code2, Layers, ChevronDown, ChevronRight, ImageIcon, Layout, LayoutGrid, Clock, ArrowRight, ThumbsUp, ThumbsDown, Cpu, Shield, Brain, Undo2, BookOpen, Gauge, ShieldCheck, AlertTriangle, CheckSquare, Square, Fingerprint, PanelLeft, Save, Info } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; import { apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import DevHistory from "@/components/DevHistory"; // ============================================================================ // PHASE 6: Agent Factory Tabs (Design, Assemble, Deploy, Galeria) // ============================================================================ type AgentDef = { id: number; name: string; description: string | null; spec: { mode: "markdown" | "typescript" | "visual"; content: string } | null; status: "draft" | "assembling" | "ready" | "deployed"; version: number; lastTaskId: number | null; tenantId: string | null; userId: string | null; createdAt: string; updatedAt: string; }; const statusColor: Record = { draft: "bg-zinc-500", assembling: "bg-blue-500", ready: "bg-green-500", deployed: "bg-purple-500", }; const statusLabel: Record = { draft: "Rascunho", assembling: "Montando...", ready: "Pronto", deployed: "Implantado", }; // ── AssembleRow: linha da Assemble Line com painel de progresso da task ────── function AssembleRow({ def, taskStatus, onAssemble, assembling }: { def: AgentDef; taskStatus?: string; onAssemble: () => void; assembling: boolean; }) { const [expanded, setExpanded] = useState(false); const { data: taskData } = useQuery<{ success: boolean; task: any; subtasks: any[]; logs: any[] }>({ queryKey: [`/api/blackboard/task/${def.lastTaskId}`], enabled: !!def.lastTaskId && def.status === "assembling" && expanded, refetchInterval: 3000, }); const subtasks: any[] = taskData?.subtasks ?? []; return (
{def.name} {statusLabel[def.status]} {def.status === "assembling" && ( )}
{def.description &&

{def.description}

} {def.lastTaskId && ( )}
{def.status === "draft" && ( )} {def.status === "assembling" && def.lastTaskId && ( )}
{/* Painel de progresso */} {expanded && def.lastTaskId && (
{subtasks.length === 0 ? (

Aguardando subtasks...

) : (
{subtasks.map((st: any) => (
{st.status === "completed" ? ( ) : st.status === "failed" ? ( ) : st.status === "in_progress" ? ( ) : ( )} {st.title || st.description?.slice(0, 60) || `Subtask #${st.id}`} {st.agentRole ?? ""}
))}
)}
)}
); } function AgentFactoryTabs() { const { toast } = useToast(); const queryClient = useQueryClient(); // ---- Design tab state ---- const [selectedDefId, setSelectedDefId] = useState(null); const [editorMode, setEditorMode] = useState<"markdown" | "typescript" | "visual">("markdown"); const [editorContent, setEditorContent] = useState(""); const [defName, setDefName] = useState(""); const [defDescription, setDefDescription] = useState(""); const [isCreatingNew, setIsCreatingNew] = useState(false); // ---- Assemble tab state ---- const [assemblePollingId, setAssemblePollingId] = useState(null); const [assembleTaskStatus, setAssembleTaskStatus] = useState>({}); // ---- Galeria filter + detail ---- const [galFilter, setGalFilter] = useState(""); const [galDetailDef, setGalDetailDef] = useState<(typeof defs)[0] | null>(null); const { data: defsData, isLoading: defsLoading } = useQuery<{ success: boolean; data: AgentDef[] }>({ queryKey: ["/api/agent-defs"], refetchInterval: assemblePollingId ? 3000 : false, }); const defs = defsData?.data || []; // Poll task status for assembling defs — verifica imediatamente e depois a cada 3s useEffect(() => { const assembling = defs.filter(d => d.status === "assembling" && d.lastTaskId); if (assembling.length === 0) return; async function checkAll() { for (const def of assembling) { if (!def.lastTaskId) continue; try { const res = await fetch(`/api/blackboard/task/${def.lastTaskId}`, { credentials: "include" }); const data = await res.json(); const taskStatus = data?.task?.status; if (taskStatus === "completed" || taskStatus === "failed") { await fetch(`/api/agent-defs/${def.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ status: taskStatus === "completed" ? "ready" : "draft" }), }); queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); } setAssembleTaskStatus(prev => ({ ...prev, [def.id]: taskStatus || "unknown" })); } catch { /* ignore */ } } } // Verifica imediatamente (cobre tasks que já completaram antes do polling iniciar) checkAll(); const interval = setInterval(checkAll, 3000); return () => clearInterval(interval); }, [defs, queryClient]); const createMutation = useMutation({ mutationFn: async () => { const res = await apiRequest("POST", "/api/agent-defs", { name: defName, description: defDescription, spec: { mode: editorMode, content: editorContent }, }); return res.json(); }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); setSelectedDefId(data.data.id); setIsCreatingNew(false); toast({ title: "Agente criado", description: data.data.name }); }, onError: (e: any) => toast({ title: "Erro", description: e.message, variant: "destructive" }), }); const saveMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("PATCH", `/api/agent-defs/${id}`, { name: defName, description: defDescription, spec: { mode: editorMode, content: editorContent }, }); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); toast({ title: "Salvo" }); }, onError: (e: any) => toast({ title: "Erro ao salvar", description: e.message, variant: "destructive" }), }); const assembleMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("POST", `/api/agent-defs/${id}/assemble`, {}); return res.json(); }, onSuccess: (data, id) => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); setAssemblePollingId(id); toast({ title: "Montagem iniciada", description: `Task #${data.taskId}` }); }, onError: (e: any) => toast({ title: "Erro", description: e.message, variant: "destructive" }), }); const deployMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("POST", `/api/agent-defs/${id}/deploy`, {}); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); toast({ title: "Deploy realizado!" }); }, onError: (e: any) => toast({ title: "Erro no deploy", description: e.message, variant: "destructive" }), }); const redraftMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("POST", `/api/agent-defs/${id}/redraft`, {}); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); toast({ title: "Volta para rascunho" }); }, onError: (e: any) => toast({ title: "Erro", description: e.message, variant: "destructive" }), }); const runMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("POST", `/api/agent-defs/${id}/run`, {}); return res.json(); }, onSuccess: (data) => { toast({ title: "Execução iniciada", description: `Task #${data.taskId}` }); }, onError: (e: any) => toast({ title: "Erro ao executar", description: e.message, variant: "destructive" }), }); const forkMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("POST", `/api/agent-defs/${id}/fork`, {}); return res.json(); }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); toast({ title: "Fork criado", description: data.data.name }); }, onError: (e: any) => toast({ title: "Erro ao fazer fork", description: e.message, variant: "destructive" }), }); const deleteMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("DELETE", `/api/agent-defs/${id}`, undefined); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/agent-defs"] }); setSelectedDefId(null); toast({ title: "Excluído" }); }, onError: (e: any) => toast({ title: "Erro", description: e.message, variant: "destructive" }), }); // Quando seleciona um def existente, carrega os campos const selectDef = (def: AgentDef) => { setSelectedDefId(def.id); setDefName(def.name); setDefDescription(def.description || ""); const spec = def.spec || { mode: "markdown", content: "" }; setEditorMode(spec.mode || "markdown"); setEditorContent(spec.content || ""); setIsCreatingNew(false); }; const startNew = () => { setSelectedDefId(null); setDefName(""); setDefDescription(""); setEditorMode("markdown"); setEditorContent(""); setIsCreatingNew(true); }; const galDefs = defs.filter(d => d.status === "deployed" && (d.name.toLowerCase().includes(galFilter.toLowerCase()) || (d.description || "").toLowerCase().includes(galFilter.toLowerCase())) ); return ( <> {/* ---- TAB: DESIGN ---- */}
{/* Lista de defs */}
Agentes
{defsLoading ? (
) : defs.length === 0 ? (

Nenhum agente ainda.
Clique em "+ Novo" para criar.

) : (
{defs.map(def => ( ))}
)}
{/* Editor */} {(selectedDefId || isCreatingNew) ? ( <>
setDefName(e.target.value)} placeholder="Nome do agente" className="h-7 text-sm font-medium" /> setDefDescription(e.target.value)} placeholder="Descrição (opcional)" className="h-6 text-xs text-zinc-500" />
{(["markdown", "typescript", "visual"] as const).map(m => ( ))}
{isCreatingNew ? ( ) : ( <> )}
{editorMode === "visual" ? (

Defina os nós do agente em JSON (name, type, connections):