849 lines
46 KiB
TypeScript
849 lines
46 KiB
TypeScript
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
|
|
import { useState } from "react";
|
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Server,
|
|
Activity,
|
|
Cpu,
|
|
Database,
|
|
RefreshCw,
|
|
Play,
|
|
Square,
|
|
CheckCircle,
|
|
XCircle,
|
|
AlertCircle,
|
|
Loader2,
|
|
Bot,
|
|
Gauge,
|
|
BarChart3,
|
|
Zap,
|
|
FileText,
|
|
Calculator,
|
|
ShoppingCart,
|
|
Clock,
|
|
Signal,
|
|
HardDrive,
|
|
Workflow,
|
|
Users,
|
|
Brain,
|
|
Sparkles,
|
|
Search,
|
|
Code,
|
|
Terminal,
|
|
Globe,
|
|
Shield,
|
|
ArrowRight,
|
|
Layers,
|
|
Network,
|
|
X,
|
|
} from "lucide-react";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
} from "@/components/ui/dialog";
|
|
|
|
interface EngineStatus {
|
|
name: string;
|
|
displayName: string;
|
|
type: string;
|
|
port: number;
|
|
category: string;
|
|
description: string;
|
|
status: "online" | "offline" | "error";
|
|
responseTime?: number;
|
|
details?: any;
|
|
error?: string;
|
|
}
|
|
|
|
interface AgentStatus {
|
|
name: string;
|
|
running: boolean;
|
|
processedTasks?: number;
|
|
errorCount?: number;
|
|
}
|
|
|
|
interface EngineRoomData {
|
|
engines: EngineStatus[];
|
|
agents: AgentStatus[];
|
|
summary: {
|
|
total_engines: number;
|
|
online_engines: number;
|
|
offline_engines: number;
|
|
health_pct: number;
|
|
total_agents: number;
|
|
running_agents: number;
|
|
};
|
|
timestamp: string;
|
|
}
|
|
|
|
const ENGINE_ICONS: Record<string, any> = {
|
|
"manus-ia": Brain,
|
|
"plus": ShoppingCart,
|
|
"contabil": Calculator,
|
|
"fisco": FileText,
|
|
"bi-engine": BarChart3,
|
|
"automation-engine": Zap,
|
|
};
|
|
|
|
const CATEGORY_COLORS: Record<string, string> = {
|
|
erp: "bg-blue-500/10 text-blue-400 border-blue-500/20",
|
|
fiscal: "bg-amber-500/10 text-amber-400 border-amber-500/20",
|
|
data: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
|
|
automation: "bg-purple-500/10 text-purple-400 border-purple-500/20",
|
|
intelligence: "bg-cyan-500/10 text-cyan-400 border-cyan-500/20",
|
|
};
|
|
|
|
const STATUS_CONFIG = {
|
|
online: { icon: CheckCircle, color: "text-green-400", bg: "bg-green-500/10", label: "Online" },
|
|
offline: { icon: XCircle, color: "text-red-400", bg: "bg-red-500/10", label: "Offline" },
|
|
error: { icon: AlertCircle, color: "text-amber-400", bg: "bg-amber-500/10", label: "Erro" },
|
|
};
|
|
|
|
function EngineCard({ engine, onClick }: { engine: EngineStatus; onClick?: () => void }) {
|
|
const statusConf = STATUS_CONFIG[engine.status];
|
|
const StatusIcon = statusConf.icon;
|
|
const EngineIcon = ENGINE_ICONS[engine.name] || Server;
|
|
const isClickable = !!onClick;
|
|
|
|
return (
|
|
<Card
|
|
data-testid={`engine-card-${engine.name}`}
|
|
className={`bg-[#1a1a2e] border-[#2a2a4a] hover:border-[#3a3a5a] transition-all ${isClickable ? "cursor-pointer hover:shadow-lg hover:shadow-violet-500/5 hover:border-violet-500/30" : ""}`}
|
|
onClick={onClick}
|
|
>
|
|
<CardContent className="p-4">
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-2 rounded-lg ${CATEGORY_COLORS[engine.category] || "bg-gray-500/10"}`}>
|
|
<EngineIcon className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-white text-sm">{engine.displayName}</h3>
|
|
<p className="text-xs text-gray-400">{engine.description}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{isClickable && (
|
|
<Badge variant="outline" className="border-violet-500/30 text-violet-400 bg-violet-500/5 text-[10px]">
|
|
Clique para detalhes
|
|
</Badge>
|
|
)}
|
|
<div className={`flex items-center gap-1.5 px-2 py-1 rounded-full ${statusConf.bg}`}>
|
|
<StatusIcon className={`w-3.5 h-3.5 ${statusConf.color}`} />
|
|
<span className={`text-xs font-medium ${statusConf.color}`}>{statusConf.label}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-2 mt-3">
|
|
<div className="text-center p-2 rounded bg-[#0d0d1a]">
|
|
<p className="text-[10px] text-gray-500 uppercase">Tipo</p>
|
|
<p className="text-xs font-medium text-gray-300">{engine.type.toUpperCase()}</p>
|
|
</div>
|
|
<div className="text-center p-2 rounded bg-[#0d0d1a]">
|
|
<p className="text-[10px] text-gray-500 uppercase">Porta</p>
|
|
<p className="text-xs font-medium text-gray-300">{engine.port}</p>
|
|
</div>
|
|
<div className="text-center p-2 rounded bg-[#0d0d1a]">
|
|
<p className="text-[10px] text-gray-500 uppercase">Resposta</p>
|
|
<p className="text-xs font-medium text-gray-300">
|
|
{engine.responseTime ? `${engine.responseTime}ms` : "---"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{engine.details && engine.status === "online" && (
|
|
<div className="mt-3 p-2 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-[10px] text-gray-500 uppercase mb-1">Detalhes</p>
|
|
{engine.details.version && (
|
|
<p className="text-xs text-gray-400">Versao: <span className="text-gray-300">{engine.details.version}</span></p>
|
|
)}
|
|
{engine.details.database && (
|
|
<p className="text-xs text-gray-400">DB: <span className={engine.details.database === "connected" ? "text-green-400" : "text-red-400"}>{engine.details.database}</span></p>
|
|
)}
|
|
{engine.details.cache && (
|
|
<p className="text-xs text-gray-400">Cache: <span className="text-gray-300">{engine.details.cache.entries} entradas, {engine.details.cache.hit_rate}% hit</span></p>
|
|
)}
|
|
{engine.details.scheduler && (
|
|
<p className="text-xs text-gray-400">Scheduler: <span className={engine.details.scheduler.is_running ? "text-green-400" : "text-gray-500"}>{engine.details.scheduler.is_running ? "Ativo" : "Parado"}</span> ({engine.details.scheduler.active_entries} entradas)</p>
|
|
)}
|
|
{engine.details.workflows && engine.details.workflows.total_workflows !== undefined && (
|
|
<p className="text-xs text-gray-400">Workflows: <span className="text-gray-300">{engine.details.workflows.total_workflows} registrados, {engine.details.workflows.total_executions} execucoes</span></p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{engine.error && (
|
|
<div className="mt-3 p-2 rounded bg-red-500/5 border border-red-500/10">
|
|
<p className="text-xs text-red-400">{engine.error}</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
function ManusStructurePanel({ open, onOpenChange, metrics }: { open: boolean; onOpenChange: (open: boolean) => void; metrics: any }) {
|
|
const AGENTS = [
|
|
{ name: "Architect", role: "Design & Arquitetura", icon: Layers, color: "text-blue-400", bg: "bg-blue-500/10" },
|
|
{ name: "Generator", role: "Geração de Código", icon: Code, color: "text-green-400", bg: "bg-green-500/10" },
|
|
{ name: "Validator", role: "Validação TypeScript", icon: Shield, color: "text-amber-400", bg: "bg-amber-500/10" },
|
|
{ name: "Executor", role: "Execução & Staging", icon: Terminal, color: "text-red-400", bg: "bg-red-500/10" },
|
|
{ name: "Researcher", role: "Pesquisa & Contexto", icon: Search, color: "text-cyan-400", bg: "bg-cyan-500/10" },
|
|
{ name: "Evolution", role: "Aprendizado Evolutivo", icon: Sparkles, color: "text-purple-400", bg: "bg-purple-500/10" },
|
|
];
|
|
|
|
const TOOL_CATEGORIES = [
|
|
{ name: "Busca Semântica", count: 8, icon: Search, color: "text-violet-400" },
|
|
{ name: "Leitura/Escrita de Arquivos", count: 12, icon: FileText, color: "text-blue-400" },
|
|
{ name: "Comandos Shell", count: 6, icon: Terminal, color: "text-green-400" },
|
|
{ name: "Web Research", count: 5, icon: Globe, color: "text-cyan-400" },
|
|
{ name: "Knowledge Graph", count: 8, icon: Network, color: "text-amber-400" },
|
|
{ name: "ERP & Database", count: 10, icon: Database, color: "text-emerald-400" },
|
|
{ name: "Análise de Código", count: 7, icon: Code, color: "text-pink-400" },
|
|
];
|
|
|
|
const uptime = metrics?.metrics?.uptime
|
|
? `${Math.floor(metrics.metrics.uptime / 3600)}h ${Math.floor((metrics.metrics.uptime % 3600) / 60)}m`
|
|
: "---";
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] bg-[#0d0d1a] border-[#2a2a4a] text-white overflow-hidden p-0">
|
|
<div className="sticky top-0 z-10 bg-gradient-to-r from-violet-600/20 via-purple-600/20 to-indigo-600/20 border-b border-violet-500/20 p-6">
|
|
<DialogHeader>
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-3 rounded-xl bg-gradient-to-br from-violet-500/30 to-purple-600/30 border border-violet-500/30">
|
|
<Brain className="w-7 h-7 text-violet-400" />
|
|
</div>
|
|
<div>
|
|
<DialogTitle className="text-xl font-bold text-white">Manus IA - Cérebro Central</DialogTitle>
|
|
<DialogDescription className="text-violet-300/80">
|
|
Arquitetura do motor de inteligência que alimenta todos os agentes
|
|
</DialogDescription>
|
|
</div>
|
|
</div>
|
|
</DialogHeader>
|
|
|
|
<div className="grid grid-cols-4 gap-3 mt-4">
|
|
<div className="text-center p-3 rounded-lg bg-[#1a1a2e]/80 border border-[#2a2a4a]">
|
|
<p className="text-2xl font-bold text-violet-400">{metrics?.model || "GPT-4o"}</p>
|
|
<p className="text-[10px] text-gray-400 uppercase mt-1">Modelo</p>
|
|
</div>
|
|
<div className="text-center p-3 rounded-lg bg-[#1a1a2e]/80 border border-[#2a2a4a]">
|
|
<p className="text-2xl font-bold text-white">{metrics?.metrics?.totalCalls || 0}</p>
|
|
<p className="text-[10px] text-gray-400 uppercase mt-1">Chamadas IA</p>
|
|
</div>
|
|
<div className="text-center p-3 rounded-lg bg-[#1a1a2e]/80 border border-[#2a2a4a]">
|
|
<p className="text-2xl font-bold text-emerald-400">{((metrics?.metrics?.totalTokens || 0) / 1000).toFixed(1)}k</p>
|
|
<p className="text-[10px] text-gray-400 uppercase mt-1">Tokens</p>
|
|
</div>
|
|
<div className="text-center p-3 rounded-lg bg-[#1a1a2e]/80 border border-[#2a2a4a]">
|
|
<p className="text-2xl font-bold text-cyan-400">{uptime}</p>
|
|
<p className="text-[10px] text-gray-400 uppercase mt-1">Uptime</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ScrollArea className="max-h-[calc(90vh-220px)]">
|
|
<div className="p-6 space-y-6">
|
|
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-gray-300 mb-3 flex items-center gap-2">
|
|
<Layers className="w-4 h-4 text-violet-400" />
|
|
Arquitetura: Fluxo de Inteligência
|
|
</h3>
|
|
<div className="relative p-4 rounded-xl bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<div className="w-full p-3 rounded-lg bg-gradient-to-r from-violet-500/10 to-purple-500/10 border border-violet-500/20 text-center">
|
|
<div className="flex items-center justify-center gap-2 mb-1">
|
|
<Brain className="w-5 h-5 text-violet-400" />
|
|
<span className="text-sm font-bold text-violet-300">ManusIntelligence (Singleton)</span>
|
|
</div>
|
|
<p className="text-[10px] text-gray-400">GPT-4o + ToolManager + Context Enrichment</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1 text-gray-500">
|
|
<ArrowRight className="w-4 h-4 rotate-90" />
|
|
<span className="text-[10px]">generate() / think()</span>
|
|
<ArrowRight className="w-4 h-4 rotate-90" />
|
|
</div>
|
|
|
|
<div className="w-full p-3 rounded-lg bg-[#0d0d1a] border border-[#1a1a3a] text-center">
|
|
<span className="text-xs font-medium text-amber-400">enrichWithContext()</span>
|
|
<p className="text-[10px] text-gray-500 mt-1">ToolManager.search_code → Contexto Semântico Automático</p>
|
|
</div>
|
|
|
|
<ArrowRight className="w-4 h-4 rotate-90 text-gray-500" />
|
|
|
|
<div className="grid grid-cols-3 gap-2 w-full">
|
|
{AGENTS.map((agent) => (
|
|
<div key={agent.name} className={`p-2.5 rounded-lg ${agent.bg} border border-[#2a2a4a] text-center`}>
|
|
<agent.icon className={`w-4 h-4 ${agent.color} mx-auto mb-1`} />
|
|
<p className={`text-xs font-semibold ${agent.color}`}>{agent.name}</p>
|
|
<p className="text-[9px] text-gray-500 mt-0.5">{agent.role}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-gray-300 mb-3 flex items-center gap-2">
|
|
<Zap className="w-4 h-4 text-amber-400" />
|
|
Ferramentas Disponíveis ({metrics?.capabilities?.tools || 56})
|
|
</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
|
{TOOL_CATEGORIES.map((cat) => (
|
|
<div key={cat.name} className="p-3 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a] hover:border-[#3a3a5a] transition-all">
|
|
<div className="flex items-center gap-2 mb-1.5">
|
|
<cat.icon className={`w-3.5 h-3.5 ${cat.color}`} />
|
|
<span className={`text-xs font-medium ${cat.color}`}>{cat.count}</span>
|
|
</div>
|
|
<p className="text-[10px] text-gray-400">{cat.name}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-gray-300 mb-3 flex items-center gap-2">
|
|
<CheckCircle className="w-4 h-4 text-emerald-400" />
|
|
Capacidades Ativas
|
|
</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
{[
|
|
{ name: "Cérebro Central (GPT-4o)", active: true },
|
|
{ name: "Enriquecimento de Contexto", active: true },
|
|
{ name: "Busca Semântica de Código", active: true },
|
|
{ name: "Knowledge Graph", active: true },
|
|
{ name: "Pipeline Autônomo de Dev", active: true },
|
|
{ name: "Orquestração de 6 Agentes", active: true },
|
|
{ name: "Leitura/Escrita de Arquivos", active: true },
|
|
{ name: "Execução de Comandos Shell", active: true },
|
|
{ name: "Web Research", active: true },
|
|
{ name: "Análise de Código", active: true },
|
|
{ name: "Validação TypeScript", active: true },
|
|
{ name: "Memória Evolutiva", active: true },
|
|
].map((cap) => (
|
|
<div key={cap.name} className="flex items-center gap-2 p-2 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<CheckCircle className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
|
|
<span className="text-xs text-gray-300">{cap.name}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{metrics?.metrics?.errorCount > 0 && (
|
|
<div className="p-3 rounded-lg bg-red-500/5 border border-red-500/20">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<AlertCircle className="w-4 h-4 text-red-400" />
|
|
<span className="text-xs font-medium text-red-400">Erros Registrados</span>
|
|
</div>
|
|
<p className="text-xs text-gray-400">
|
|
Total: <span className="text-red-400 font-medium">{metrics.metrics.errorCount}</span>
|
|
{metrics.metrics.lastCallAt && (
|
|
<> | Última chamada: <span className="text-gray-300">{new Date(metrics.metrics.lastCallAt).toLocaleString("pt-BR")}</span></>
|
|
)}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="p-4 rounded-xl bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<h3 className="text-sm font-semibold text-gray-300 mb-3 flex items-center gap-2">
|
|
<Network className="w-4 h-4 text-violet-400" />
|
|
Diagrama de Fluxo
|
|
</h3>
|
|
<div className="font-mono text-[11px] text-gray-400 space-y-0.5 bg-[#0a0a15] p-4 rounded-lg border border-[#1a1a3a]">
|
|
<p className="text-violet-400">{"┌──────────────────────────────────────────────────────────┐"}</p>
|
|
<p className="text-violet-400">{"│ MANUS INTELLIGENCE (Singleton GPT-4o) │"}</p>
|
|
<p className="text-violet-400">{"├──────────────────────────────────────────────────────────┤"}</p>
|
|
<p>{"│ .generate(prompt) → enrichWithContext() → OpenAI │"}</p>
|
|
<p>{"│ .think(prompt) → enrichWithContext() → OpenAI │"}</p>
|
|
<p>{"│ .getMetrics() → calls, tokens, errors, uptime │"}</p>
|
|
<p className="text-violet-400">{"├──────────────────────────────────────────────────────────┤"}</p>
|
|
<p className="text-amber-400">{"│ ToolManager.executeTool('search_code', query) │"}</p>
|
|
<p className="text-amber-400">{"│ → Contexto semântico injetado automaticamente │"}</p>
|
|
<p className="text-violet-400">{"├──────────────────────────────────────────────────────────┤"}</p>
|
|
<p className="text-blue-400">{"│ Architect ──────┐ │"}</p>
|
|
<p className="text-green-400">{"│ Generator ──────┤ │"}</p>
|
|
<p className="text-amber-400">{"│ Validator ──────┤── Todos via manusIntelligence ──► │"}</p>
|
|
<p className="text-red-400">{"│ Executor ──────┤ │"}</p>
|
|
<p className="text-cyan-400">{"│ Researcher ──────┤ │"}</p>
|
|
<p className="text-purple-400">{"│ Evolution ──────┘ │"}</p>
|
|
<p className="text-violet-400">{"├──────────────────────────────────────────────────────────┤"}</p>
|
|
<p className="text-emerald-400">{"│ /api/manus/health → Status, métricas, capacidades │"}</p>
|
|
<p className="text-violet-400">{"└──────────────────────────────────────────────────────────┘"}</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</ScrollArea>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
function AgentCard({ agent }: { agent: AgentStatus }) {
|
|
return (
|
|
<div data-testid={`agent-card-${agent.name}`} className="flex items-center justify-between p-3 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-1.5 rounded-lg ${agent.running ? "bg-green-500/10" : "bg-gray-500/10"}`}>
|
|
<Bot className={`w-4 h-4 ${agent.running ? "text-green-400" : "text-gray-500"}`} />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-white capitalize">{agent.name}</p>
|
|
<p className="text-xs text-gray-500">Agente XOS</p>
|
|
</div>
|
|
</div>
|
|
<Badge variant="outline" className={agent.running ? "border-green-500/30 text-green-400 bg-green-500/5" : "border-gray-600 text-gray-400 bg-gray-500/5"}>
|
|
{agent.running ? "Ativo" : "Parado"}
|
|
</Badge>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SummaryCards({ summary }: { summary: EngineRoomData["summary"] }) {
|
|
const cards = [
|
|
{ label: "Motores Online", value: `${summary.online_engines}/${summary.total_engines}`, icon: Server, color: "text-green-400", bg: "bg-green-500/10" },
|
|
{ label: "Saude Geral", value: `${summary.health_pct}%`, icon: Activity, color: "text-cyan-400", bg: "bg-cyan-500/10" },
|
|
{ label: "Agentes Ativos", value: `${summary.running_agents}/${summary.total_agents}`, icon: Bot, color: "text-purple-400", bg: "bg-purple-500/10" },
|
|
{ label: "Motores Offline", value: `${summary.offline_engines}`, icon: XCircle, color: summary.offline_engines > 0 ? "text-red-400" : "text-gray-500", bg: summary.offline_engines > 0 ? "bg-red-500/10" : "bg-gray-500/10" },
|
|
];
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
|
|
{cards.map((card, i) => (
|
|
<Card key={i} data-testid={`summary-card-${i}`} className="bg-[#1a1a2e] border-[#2a2a4a]">
|
|
<CardContent className="p-4 flex items-center gap-3">
|
|
<div className={`p-2 rounded-lg ${card.bg}`}>
|
|
<card.icon className={`w-5 h-5 ${card.color}`} />
|
|
</div>
|
|
<div>
|
|
<p className="text-2xl font-bold text-white">{card.value}</p>
|
|
<p className="text-xs text-gray-400">{card.label}</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function EngineRoom() {
|
|
const queryClient = useQueryClient();
|
|
const [activeTab, setActiveTab] = useState("overview");
|
|
const [manusOpen, setManusOpen] = useState(false);
|
|
|
|
const { data, isLoading, isRefetching } = useQuery<EngineRoomData>({
|
|
queryKey: ["/api/engine-room/status"],
|
|
refetchInterval: 15000,
|
|
});
|
|
|
|
const { data: biMetrics } = useQuery<any>({
|
|
queryKey: ["/api/bi-engine/metrics"],
|
|
enabled: activeTab === "bi",
|
|
refetchInterval: 10000,
|
|
});
|
|
|
|
const { data: autoMetrics } = useQuery<any>({
|
|
queryKey: ["/api/automation-engine/metrics"],
|
|
enabled: activeTab === "automation",
|
|
refetchInterval: 10000,
|
|
});
|
|
|
|
const { data: manusMetrics } = useQuery<any>({
|
|
queryKey: ["/api/manus/health"],
|
|
enabled: activeTab === "manus" || manusOpen,
|
|
refetchInterval: 10000,
|
|
});
|
|
|
|
const handleRefresh = () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/engine-room/status"] });
|
|
};
|
|
|
|
const handleStartAgents = async () => {
|
|
try {
|
|
await fetch("/api/engine-room/agents/start", { method: "POST", credentials: "include" });
|
|
queryClient.invalidateQueries({ queryKey: ["/api/engine-room/status"] });
|
|
} catch {}
|
|
};
|
|
|
|
const handleStopAgents = async () => {
|
|
try {
|
|
await fetch("/api/engine-room/agents/stop", { method: "POST", credentials: "include" });
|
|
queryClient.invalidateQueries({ queryKey: ["/api/engine-room/status"] });
|
|
} catch {}
|
|
};
|
|
|
|
const content = (
|
|
<div className="min-h-screen bg-[#0d0d1a] text-white">
|
|
<div className="p-4 md:p-6 max-w-7xl mx-auto">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 rounded-lg bg-gradient-to-br from-amber-500/20 to-orange-500/20 border border-amber-500/20">
|
|
<Cpu className="w-6 h-6 text-amber-400" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-bold" data-testid="text-engine-room-title">Casa de Maquinas</h1>
|
|
<p className="text-sm text-gray-400">Painel de controle de todos os motores e agentes</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{data && (
|
|
<span className="text-xs text-gray-500">
|
|
Atualizado: {new Date(data.timestamp).toLocaleTimeString("pt-BR")}
|
|
</span>
|
|
)}
|
|
<Button
|
|
data-testid="button-refresh"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleRefresh}
|
|
disabled={isRefetching}
|
|
className="border-[#2a2a4a] text-gray-300 hover:bg-[#1a1a2e]"
|
|
>
|
|
<RefreshCw className={`w-4 h-4 mr-1 ${isRefetching ? "animate-spin" : ""}`} />
|
|
Atualizar
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center h-64">
|
|
<Loader2 className="w-8 h-8 animate-spin text-amber-400" />
|
|
</div>
|
|
) : data ? (
|
|
<>
|
|
<SummaryCards summary={data.summary} />
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
<TabsList className="bg-[#1a1a2e] border border-[#2a2a4a] mb-4">
|
|
<TabsTrigger value="overview" data-testid="tab-overview" className="data-[state=active]:bg-amber-500/10 data-[state=active]:text-amber-400">
|
|
<Server className="w-4 h-4 mr-1.5" /> Visao Geral
|
|
</TabsTrigger>
|
|
<TabsTrigger value="bi" data-testid="tab-bi" className="data-[state=active]:bg-emerald-500/10 data-[state=active]:text-emerald-400">
|
|
<BarChart3 className="w-4 h-4 mr-1.5" /> Motor BI
|
|
</TabsTrigger>
|
|
<TabsTrigger value="automation" data-testid="tab-automation" className="data-[state=active]:bg-purple-500/10 data-[state=active]:text-purple-400">
|
|
<Zap className="w-4 h-4 mr-1.5" /> Motor Automacao
|
|
</TabsTrigger>
|
|
<TabsTrigger value="manus" data-testid="tab-manus" className="data-[state=active]:bg-violet-500/10 data-[state=active]:text-violet-400">
|
|
<Brain className="w-4 h-4 mr-1.5" /> Manus IA
|
|
</TabsTrigger>
|
|
<TabsTrigger value="agents" data-testid="tab-agents" className="data-[state=active]:bg-cyan-500/10 data-[state=active]:text-cyan-400">
|
|
<Bot className="w-4 h-4 mr-1.5" /> Agentes XOS
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="overview">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{data.engines.map((engine) => (
|
|
<EngineCard
|
|
key={engine.name}
|
|
engine={engine}
|
|
onClick={engine.name === "manus-ia" ? () => setManusOpen(true) : undefined}
|
|
/>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="bi">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{data.engines.filter(e => e.name === "bi-engine").map(e => (
|
|
<EngineCard key={e.name} engine={e} />
|
|
))}
|
|
{biMetrics && (
|
|
<Card className="bg-[#1a1a2e] border-[#2a2a4a]">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-300 flex items-center gap-2">
|
|
<Gauge className="w-4 h-4 text-emerald-400" /> Metricas do Motor BI
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{biMetrics.cache && (
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Cache</p>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-white">{biMetrics.cache.entries}</p>
|
|
<p className="text-[10px] text-gray-500">Entradas</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-emerald-400">{biMetrics.cache.hit_rate}%</p>
|
|
<p className="text-[10px] text-gray-500">Hit Rate</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-white">{biMetrics.cache.hits + biMetrics.cache.misses}</p>
|
|
<p className="text-[10px] text-gray-500">Requisicoes</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{biMetrics.limits && (
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Limites</p>
|
|
<p className="text-xs text-gray-400">Max linhas: <span className="text-gray-300">{biMetrics.limits.max_rows?.toLocaleString()}</span></p>
|
|
<p className="text-xs text-gray-400">Timeout: <span className="text-gray-300">{biMetrics.limits.query_timeout_ms?.toLocaleString()}ms</span></p>
|
|
<p className="text-xs text-gray-400">Cache TTL: <span className="text-gray-300">{biMetrics.limits.cache_ttl_seconds}s</span></p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
<div className="mt-4 p-4 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<h3 className="text-sm font-medium text-gray-300 mb-2 flex items-center gap-2">
|
|
<Signal className="w-4 h-4 text-emerald-400" /> Capacidades do Motor BI
|
|
</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
{["SQL Query (read-only)", "Chart Data Generation", "Micro-BI API", "Data Analysis (Pandas)", "Aggregation Engine", "Query Cache"].map(cap => (
|
|
<div key={cap} className="flex items-center gap-2 p-2 rounded bg-[#0d0d1a]">
|
|
<CheckCircle className="w-3.5 h-3.5 text-emerald-400" />
|
|
<span className="text-xs text-gray-300">{cap}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="automation">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{data.engines.filter(e => e.name === "automation-engine").map(e => (
|
|
<EngineCard key={e.name} engine={e} />
|
|
))}
|
|
{autoMetrics && (
|
|
<Card className="bg-[#1a1a2e] border-[#2a2a4a]">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-300 flex items-center gap-2">
|
|
<Gauge className="w-4 h-4 text-purple-400" /> Metricas do Motor Automacao
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{autoMetrics.scheduler && (
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Scheduler</p>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-white">{autoMetrics.scheduler.total_entries}</p>
|
|
<p className="text-[10px] text-gray-500">Entradas</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-white">{autoMetrics.scheduler.active_entries}</p>
|
|
<p className="text-[10px] text-gray-500">Ativas</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className={`text-lg font-bold ${autoMetrics.scheduler.is_running ? "text-green-400" : "text-gray-500"}`}>
|
|
{autoMetrics.scheduler.is_running ? "ON" : "OFF"}
|
|
</p>
|
|
<p className="text-[10px] text-gray-500">Status</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{autoMetrics.event_bus && (
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Event Bus</p>
|
|
<p className="text-xs text-gray-400">Tipos de evento: <span className="text-gray-300">{autoMetrics.event_bus.total_event_types}</span></p>
|
|
<p className="text-xs text-gray-400">Subscribers: <span className="text-gray-300">{autoMetrics.event_bus.total_subscribers}</span></p>
|
|
<p className="text-xs text-gray-400">Historico: <span className="text-gray-300">{autoMetrics.event_bus.history_size} eventos</span></p>
|
|
</div>
|
|
)}
|
|
{autoMetrics.workflows && (
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Workflows</p>
|
|
<p className="text-xs text-gray-400">Registrados: <span className="text-gray-300">{autoMetrics.workflows.total_workflows}</span></p>
|
|
<p className="text-xs text-gray-400">Execucoes: <span className="text-gray-300">{autoMetrics.workflows.total_executions}</span></p>
|
|
<p className="text-xs text-gray-400">Taxa sucesso: <span className="text-emerald-400">{autoMetrics.workflows.success_rate}%</span></p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
<div className="mt-4 p-4 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<h3 className="text-sm font-medium text-gray-300 mb-2 flex items-center gap-2">
|
|
<Workflow className="w-4 h-4 text-purple-400" /> Capacidades do Motor Automacao
|
|
</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
{["Cron Scheduler", "Event Bus", "Workflow Executor", "HTTP Actions", "SQL Queries (read-only)", "Transform & Filter"].map(cap => (
|
|
<div key={cap} className="flex items-center gap-2 p-2 rounded bg-[#0d0d1a]">
|
|
<CheckCircle className="w-3.5 h-3.5 text-purple-400" />
|
|
<span className="text-xs text-gray-300">{cap}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="manus">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{data.engines.filter(e => e.name === "manus-ia").map(e => (
|
|
<EngineCard key={e.name} engine={e} onClick={() => setManusOpen(true)} />
|
|
))}
|
|
<Card className="bg-[#1a1a2e] border-[#2a2a4a]">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-300 flex items-center gap-2">
|
|
<Sparkles className="w-4 h-4 text-violet-400" /> Metricas do Manus IA
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{manusMetrics ? (
|
|
<>
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Modelo & Performance</p>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-violet-400">{manusMetrics.model || "GPT-4o"}</p>
|
|
<p className="text-[10px] text-gray-500">Modelo</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-white">{manusMetrics.metrics?.totalCalls || 0}</p>
|
|
<p className="text-[10px] text-gray-500">Chamadas IA</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-lg font-bold text-emerald-400">{((manusMetrics.metrics?.totalTokens || 0) / 1000).toFixed(1)}k</p>
|
|
<p className="text-[10px] text-gray-500">Tokens</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Capacidades Ativas</p>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-lg font-bold text-white">{manusMetrics.capabilities?.tools || 56}</span>
|
|
<span className="text-xs text-gray-400">Ferramentas</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-lg font-bold text-white">{manusMetrics.capabilities?.agents || 0}</span>
|
|
<span className="text-xs text-gray-400">Agentes Ativos</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="p-3 rounded bg-[#0d0d1a] border border-[#1a1a3a]">
|
|
<p className="text-xs text-gray-500 uppercase mb-2">Uptime</p>
|
|
<p className="text-sm text-gray-300">
|
|
{manusMetrics.metrics?.uptime
|
|
? `${Math.floor(manusMetrics.metrics.uptime / 3600)}h ${Math.floor((manusMetrics.metrics.uptime % 3600) / 60)}m`
|
|
: "---"}
|
|
</p>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="text-center py-4 text-gray-500">
|
|
<Loader2 className="w-6 h-6 animate-spin mx-auto mb-2" />
|
|
<p className="text-xs">Carregando metricas...</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
<div className="mt-4 p-4 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<h3 className="text-sm font-medium text-gray-300 mb-2 flex items-center gap-2">
|
|
<Brain className="w-4 h-4 text-violet-400" /> Capacidades do Manus IA
|
|
</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
{["GPT-4o (Cerebro Central)", "56 Ferramentas Integradas", "Knowledge Graph", "Busca Semantica", "Pipeline de Dev Autonomo", "Orquestrador de Agentes", "Leitura/Escrita de Arquivos", "Execucao de Comandos", "Web Research", "Analise de Codigo", "Validacao TypeScript", "Memoria Evolutiva"].map(cap => (
|
|
<div key={cap} className="flex items-center gap-2 p-2 rounded bg-[#0d0d1a]">
|
|
<CheckCircle className="w-3.5 h-3.5 text-violet-400 shrink-0" />
|
|
<span className="text-xs text-gray-300">{cap}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="agents">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-sm font-medium text-gray-300 flex items-center gap-2">
|
|
<Bot className="w-4 h-4 text-cyan-400" /> Agentes Autonomos XOS
|
|
</h3>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
data-testid="button-start-agents"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleStartAgents}
|
|
className="border-green-500/30 text-green-400 hover:bg-green-500/10"
|
|
>
|
|
<Play className="w-3.5 h-3.5 mr-1" /> Iniciar Todos
|
|
</Button>
|
|
<Button
|
|
data-testid="button-stop-agents"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleStopAgents}
|
|
className="border-red-500/30 text-red-400 hover:bg-red-500/10"
|
|
>
|
|
<Square className="w-3.5 h-3.5 mr-1" /> Parar Todos
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{data.agents.length > 0 ? (
|
|
data.agents.map((agent) => (
|
|
<AgentCard key={agent.name} agent={agent} />
|
|
))
|
|
) : (
|
|
<div className="col-span-2 text-center py-8 text-gray-500">
|
|
<Bot className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
|
<p className="text-sm">Nenhum agente registrado</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
<div className="mt-6 p-4 rounded-lg bg-[#1a1a2e] border border-[#2a2a4a]">
|
|
<h3 className="text-sm font-medium text-gray-300 mb-3 flex items-center gap-2">
|
|
<HardDrive className="w-4 h-4 text-amber-400" /> Arquitetura dos Motores
|
|
</h3>
|
|
<div className="font-mono text-xs text-gray-400 space-y-1 bg-[#0d0d1a] p-4 rounded-lg border border-[#1a1a3a]">
|
|
<p className="text-amber-400">{"┌─────────────────────────────────────────────────────┐"}</p>
|
|
<p className="text-amber-400">{"│ ARCADIA SUITE - CASA DE MAQUINAS │"}</p>
|
|
<p className="text-amber-400">{"├─────────────────────────────────────────────────────┤"}</p>
|
|
<p>{"│ Express.js (5000) ─── Orquestracao + API Gateway │"}</p>
|
|
<p className="text-amber-400">{"├─────────────────────────────────────────────────────┤"}</p>
|
|
<p className="text-violet-400 font-bold">{"│ Manus IA (5000) ─── GPT-4o + 56 Tools (Cerebro)│"}</p>
|
|
<p className="text-amber-400">{"├─────────────────────────────────────────────────────┤"}</p>
|
|
<p className="text-blue-400">{"│ Plus ERP (8080) ─── Laravel/PHP - ERP Completo │"}</p>
|
|
<p className="text-amber-300">{"│ Contabil (8003) ─── FastAPI - DRE/Balancete │"}</p>
|
|
<p className="text-amber-300">{"│ Fiscal (8002) ─── FastAPI - NF-e/SEFAZ │"}</p>
|
|
<p className="text-emerald-400">{"│ BI Engine (8004) ─── FastAPI - SQL/Charts/Cache │"}</p>
|
|
<p className="text-purple-400">{"│ Automacao (8005) ─── FastAPI - Scheduler/Events │"}</p>
|
|
<p className="text-amber-400">{"├─────────────────────────────────────────────────────┤"}</p>
|
|
<p className="text-cyan-400">{"│ XOS Agents ─── 6 Agentes via ManusIntel. │"}</p>
|
|
<p className="text-amber-400">{"└─────────────────────────────────────────────────────┘"}</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="text-center py-12 text-gray-500">
|
|
<Server className="w-12 h-12 mx-auto mb-3 opacity-30" />
|
|
<p>Nao foi possivel carregar o status dos motores</p>
|
|
</div>
|
|
)}
|
|
|
|
<ManusStructurePanel open={manusOpen} onOpenChange={setManusOpen} metrics={manusMetrics} />
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<BrowserFrame>
|
|
<ScrollArea className="h-full">
|
|
{content}
|
|
</ScrollArea>
|
|
</BrowserFrame>
|
|
);
|
|
}
|