diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/client/index.html b/client/index.html index a97123d..91387a3 100644 --- a/client/index.html +++ b/client/index.html @@ -1,17 +1,15 @@ - + - GoClaw Control Center - + diff --git a/client/src/App.tsx b/client/src/App.tsx index 0828668..b0604c4 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,32 +4,33 @@ import NotFound from "@/pages/NotFound"; import { Route, Switch } from "wouter"; import ErrorBoundary from "./components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; -import Home from "./pages/Home"; - +import DashboardLayout from "./components/DashboardLayout"; +import Dashboard from "./pages/Dashboard"; +import Agents from "./pages/Agents"; +import Chat from "./pages/Chat"; +import Settings from "./pages/Settings"; +import Nodes from "./pages/Nodes"; function Router() { return ( - - - - {/* Final fallback route */} - - + + + + + + + + + + + ); } -// NOTE: About Theme -// - First choose a default theme according to your design style (dark or light bg), than change color palette in index.css -// to keep consistent foreground/background color across components -// - If you want to make theme switchable, pass `switchable` ThemeProvider and use `useTheme` hook - function App() { return ( - + diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx new file mode 100644 index 0000000..b042092 --- /dev/null +++ b/client/src/components/DashboardLayout.tsx @@ -0,0 +1,192 @@ +/* + * DashboardLayout — Mission Control style + * Design: Dark space theme, narrow icon sidebar, top status bar with cluster metrics + * Colors: Space deep (#0A0E1A), Cyan glow (#00D4FF), status indicators + * Typography: Inter for nav, JetBrains Mono for data + */ +import { ReactNode, useState } from "react"; +import { useLocation, Link } from "wouter"; +import { + LayoutDashboard, + Bot, + Server, + MessageSquare, + Settings, + ChevronLeft, + ChevronRight, + Activity, + Cpu, + HardDrive, + Wifi, +} from "lucide-react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { motion, AnimatePresence } from "framer-motion"; + +const NAV_ITEMS = [ + { path: "/", icon: LayoutDashboard, label: "Дашборд" }, + { path: "/agents", icon: Bot, label: "Агенты" }, + { path: "/nodes", icon: Server, label: "Ноды" }, + { path: "/chat", icon: MessageSquare, label: "Чат" }, + { path: "/settings", icon: Settings, label: "Настройки" }, +]; + +export default function DashboardLayout({ children }: { children: ReactNode }) { + const [location] = useLocation(); + const [collapsed, setCollapsed] = useState(false); + + return ( +
+ {/* Sidebar */} + + {/* Logo */} +
+
+ +
+ + {!collapsed && ( + + + GoClaw + + + v0.1 + + + )} + +
+ + {/* Navigation */} + + + {/* Collapse toggle */} + + + {/* Connection status */} +
+
+
+ + {!collapsed && ( + + GATEWAY ONLINE + + )} + +
+
+ + + {/* Main content area */} +
+ {/* Top status bar */} +
+
+ + + + + +
+
+
+ + + goclaw-swarm:18789 + +
+
+
+ + {/* Page content */} +
+ {children} +
+
+
+ ); +} + +function StatusMetric({ + icon: Icon, + label, + value, + color, +}: { + icon: typeof Activity; + label: string; + value: string; + color: string; +}) { + return ( +
+ +
+ + {label} + + {value} +
+
+ ); +} diff --git a/client/src/index.css b/client/src/index.css index 72b423d..cb3f01c 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -40,77 +40,51 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --font-sans: 'Inter', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', monospace; + --color-cyan-glow: oklch(0.82 0.15 195); + --color-neon-green: oklch(0.82 0.2 155); + --color-neon-amber: oklch(0.82 0.16 80); + --color-neon-red: oklch(0.7 0.22 20); + --color-space-deep: oklch(0.12 0.02 260); + --color-space-surface: oklch(0.16 0.02 260); + --color-space-panel: oklch(0.19 0.02 260); } :root { - --primary: var(--color-blue-700); - --primary-foreground: var(--color-blue-50); - --sidebar-primary: var(--color-blue-600); - --sidebar-primary-foreground: var(--color-blue-50); - --chart-1: var(--color-blue-300); - --chart-2: var(--color-blue-500); - --chart-3: var(--color-blue-600); - --chart-4: var(--color-blue-700); - --chart-5: var(--color-blue-800); - --radius: 0.65rem; - --background: oklch(1 0 0); - --foreground: oklch(0.235 0.015 65); - --card: oklch(1 0 0); - --card-foreground: oklch(0.235 0.015 65); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.235 0.015 65); - --secondary: oklch(0.98 0.001 286.375); - --secondary-foreground: oklch(0.4 0.015 65); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.141 0.005 285.823); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.985 0 0); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.623 0.214 259.815); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.235 0.015 65); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.141 0.005 285.823); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.623 0.214 259.815); -} - -.dark { - --primary: var(--color-blue-700); - --primary-foreground: var(--color-blue-50); - --sidebar-primary: var(--color-blue-500); - --sidebar-primary-foreground: var(--color-blue-50); - --background: oklch(0.141 0.005 285.823); - --foreground: oklch(0.85 0.005 65); - --card: oklch(0.21 0.006 285.885); - --card-foreground: oklch(0.85 0.005 65); - --popover: oklch(0.21 0.006 285.885); - --popover-foreground: oklch(0.85 0.005 65); - --secondary: oklch(0.24 0.006 286.033); - --secondary-foreground: oklch(0.7 0.005 65); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --accent-foreground: oklch(0.92 0.005 65); - --destructive: oklch(0.704 0.191 22.216); - --destructive-foreground: oklch(0.985 0 0); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.488 0.243 264.376); - --chart-1: var(--color-blue-300); - --chart-2: var(--color-blue-500); - --chart-3: var(--color-blue-600); - --chart-4: var(--color-blue-700); - --chart-5: var(--color-blue-800); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-foreground: oklch(0.85 0.005 65); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.488 0.243 264.376); + --radius: 0.5rem; + --background: oklch(0.12 0.02 260); + --foreground: oklch(0.92 0.01 260); + --card: oklch(0.16 0.02 260); + --card-foreground: oklch(0.92 0.01 260); + --popover: oklch(0.16 0.02 260); + --popover-foreground: oklch(0.92 0.01 260); + --primary: oklch(0.82 0.15 195); + --primary-foreground: oklch(0.12 0.02 260); + --secondary: oklch(0.22 0.02 260); + --secondary-foreground: oklch(0.75 0.01 260); + --muted: oklch(0.22 0.02 260); + --muted-foreground: oklch(0.6 0.02 260); + --accent: oklch(0.22 0.02 260); + --accent-foreground: oklch(0.92 0.01 260); + --destructive: oklch(0.7 0.22 20); + --destructive-foreground: oklch(0.98 0 0); + --border: oklch(0.28 0.02 260); + --input: oklch(0.22 0.02 260); + --ring: oklch(0.82 0.15 195); + --chart-1: oklch(0.82 0.15 195); + --chart-2: oklch(0.82 0.2 155); + --chart-3: oklch(0.82 0.16 80); + --chart-4: oklch(0.7 0.15 300); + --chart-5: oklch(0.7 0.22 20); + --sidebar: oklch(0.14 0.02 260); + --sidebar-foreground: oklch(0.92 0.01 260); + --sidebar-primary: oklch(0.82 0.15 195); + --sidebar-primary-foreground: oklch(0.12 0.02 260); + --sidebar-accent: oklch(0.22 0.02 260); + --sidebar-accent-foreground: oklch(0.92 0.01 260); + --sidebar-border: oklch(0.28 0.02 260); + --sidebar-ring: oklch(0.82 0.15 195); } @layer base { @@ -119,6 +93,7 @@ } body { @apply bg-background text-foreground; + font-family: 'Inter', system-ui, sans-serif; } button:not(:disabled), [role="button"]:not([aria-disabled="true"]), @@ -134,24 +109,11 @@ } @layer components { - /** - * Custom container utility that centers content and adds responsive padding. - * - * This overrides Tailwind's default container behavior to: - * - Auto-center content (mx-auto) - * - Add responsive horizontal padding - * - Set max-width for large screens - * - * Usage:
...
- * - * For custom widths, use max-w-* utilities directly: - *
...
- */ .container { width: 100%; margin-left: auto; margin-right: auto; - padding-left: 1rem; /* 16px - mobile padding */ + padding-left: 1rem; padding-right: 1rem; } @@ -162,16 +124,63 @@ @media (min-width: 640px) { .container { - padding-left: 1.5rem; /* 24px - tablet padding */ + padding-left: 1.5rem; padding-right: 1.5rem; } } @media (min-width: 1024px) { .container { - padding-left: 2rem; /* 32px - desktop padding */ + padding-left: 2rem; padding-right: 2rem; - max-width: 1280px; /* Standard content width */ + max-width: 1600px; } } -} \ No newline at end of file + + /* Mission Control glow effects */ + .glow-cyan { + box-shadow: 0 0 8px oklch(0.82 0.15 195 / 0.3), 0 0 20px oklch(0.82 0.15 195 / 0.1); + } + .glow-green { + box-shadow: 0 0 8px oklch(0.82 0.2 155 / 0.3), 0 0 20px oklch(0.82 0.2 155 / 0.1); + } + .glow-amber { + box-shadow: 0 0 8px oklch(0.82 0.16 80 / 0.3), 0 0 20px oklch(0.82 0.16 80 / 0.1); + } + .glow-red { + box-shadow: 0 0 8px oklch(0.7 0.22 20 / 0.3), 0 0 20px oklch(0.7 0.22 20 / 0.1); + } + + /* Pulsing indicator animation */ + @keyframes pulse-glow { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } + } + .pulse-indicator { + animation: pulse-glow 2s ease-in-out infinite; + } + + /* Terminal cursor blink */ + @keyframes blink-cursor { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } + } + .terminal-cursor { + animation: blink-cursor 1s step-end infinite; + } + + /* Scanline effect */ + .scanline::after { + content: ''; + position: absolute; + inset: 0; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + oklch(0.82 0.15 195 / 0.03) 2px, + oklch(0.82 0.15 195 / 0.03) 4px + ); + pointer-events: none; + } +} diff --git a/client/src/pages/Agents.tsx b/client/src/pages/Agents.tsx new file mode 100644 index 0000000..cb83eff --- /dev/null +++ b/client/src/pages/Agents.tsx @@ -0,0 +1,266 @@ +/* + * Agents — Agent Management Panel + * Design: Cards with glow borders, status indicators, model info + * Colors: Cyan glow primary, green/amber/red status + * Typography: JetBrains Mono for data, Inter for labels + */ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Progress } from "@/components/ui/progress"; +import { + Bot, + Play, + Pause, + RotateCcw, + Trash2, + Plus, + Cpu, + Clock, + Zap, + Brain, + Terminal, + Globe, + Mail, + FileText, + Eye, +} from "lucide-react"; +import { motion } from "framer-motion"; +import { useState } from "react"; +import { toast } from "sonner"; + +const AGENT_CARD_BG = "https://d2xsxph8kpxj0f.cloudfront.net/97147719/ZEGAT83geRq9CNvryykaQv/agent-card-bg-4pCQxardRUWZ77WDF6yvS3.webp"; + +const AGENTS_DATA = [ + { + id: "agent-coder", + name: "Coder Agent", + description: "Пишет и тестирует Go-модули, рефакторит код, создаёт новые скиллы", + model: "claude-3.5-sonnet", + provider: "Anthropic", + status: "running", + icon: Terminal, + tasks_completed: 147, + tasks_active: 3, + cpu: 45, + mem: 512, + uptime: "2d 14h 23m", + skills: ["go_compiler", "git_ops", "test_runner", "code_review"], + node: "goclaw-worker-01", + }, + { + id: "agent-browser", + name: "Browser Agent", + description: "Управляет Chromium, выполняет веб-скрапинг и автоматизацию", + model: "gpt-4o", + provider: "OpenAI", + status: "running", + icon: Globe, + tasks_completed: 89, + tasks_active: 1, + cpu: 62, + mem: 1024, + uptime: "2d 14h 23m", + skills: ["chromedp", "screenshot", "form_fill", "web_scrape"], + node: "goclaw-worker-01", + }, + { + id: "agent-mail", + name: "Mail Agent", + description: "Мониторит почту, классифицирует письма, отправляет ответы", + model: "gpt-4o-mini", + provider: "OpenAI", + status: "idle", + icon: Mail, + tasks_completed: 234, + tasks_active: 0, + cpu: 2, + mem: 128, + uptime: "2d 14h 23m", + skills: ["imap_reader", "smtp_sender", "email_classifier"], + node: "goclaw-worker-02", + }, + { + id: "agent-monitor", + name: "Monitor Agent", + description: "Следит за состоянием кластера, логами и метриками", + model: "llama-3.1-8b", + provider: "Ollama (local)", + status: "running", + icon: Eye, + tasks_completed: 1205, + tasks_active: 5, + cpu: 8, + mem: 256, + uptime: "2d 14h 23m", + skills: ["log_parser", "metric_collector", "alert_sender"], + node: "goclaw-manager-01", + }, + { + id: "agent-docs", + name: "Docs Agent", + description: "Работает с документами: PDF, DOCX, создаёт отчёты", + model: "claude-3-haiku", + provider: "Anthropic", + status: "error", + icon: FileText, + tasks_completed: 56, + tasks_active: 0, + cpu: 0, + mem: 0, + uptime: "0h 0m", + skills: ["pdf_parser", "docx_writer", "summarizer"], + node: "goclaw-worker-02", + }, +]; + +function getStatusConfig(status: string) { + switch (status) { + case "running": + return { color: "text-neon-green", bg: "bg-neon-green", badge: "bg-neon-green/15 text-neon-green border-neon-green/30", glow: "glow-green" }; + case "idle": + return { color: "text-primary", bg: "bg-primary", badge: "bg-primary/15 text-primary border-primary/30", glow: "glow-cyan" }; + case "error": + return { color: "text-neon-red", bg: "bg-neon-red", badge: "bg-neon-red/15 text-neon-red border-neon-red/30", glow: "glow-red" }; + default: + return { color: "text-muted-foreground", bg: "bg-muted", badge: "bg-muted text-muted-foreground border-border", glow: "" }; + } +} + +export default function Agents() { + const [agents] = useState(AGENTS_DATA); + + return ( +
+ {/* Header */} +
+
+

Agent Fleet

+

+ {agents.filter(a => a.status === "running").length} running · {agents.filter(a => a.status === "idle").length} idle · {agents.filter(a => a.status === "error").length} error +

+
+ +
+ + {/* Agent cards grid */} +
+ {agents.map((agent, i) => { + const sc = getStatusConfig(agent.status); + const Icon = agent.icon; + return ( + + + + {/* Top row */} +
+
+
+ +
+
+

{agent.name}

+

{agent.description}

+
+
+ + + {agent.status.toUpperCase()} + +
+ + {/* Model & Node info */} +
+
+
+ + MODEL +
+
{agent.model}
+
{agent.provider}
+
+
+
+ + NODE +
+
{agent.node}
+
+ CPU: {agent.cpu}% · MEM: {agent.mem}MB +
+
+
+ + {/* Metrics row */} +
+
+ + Tasks: + {agent.tasks_completed} +
+
+ + Uptime: + {agent.uptime} +
+
+ + Active: + {agent.tasks_active} +
+
+ + {/* Skills */} +
+ SKILLS +
+ {agent.skills.map((skill) => ( + + {skill} + + ))} +
+
+ + {/* Actions */} +
+ {agent.status === "running" ? ( + + ) : ( + + )} + + +
+
+
+
+ ); + })} +
+
+ ); +} diff --git a/client/src/pages/Chat.tsx b/client/src/pages/Chat.tsx new file mode 100644 index 0000000..5827b6a --- /dev/null +++ b/client/src/pages/Chat.tsx @@ -0,0 +1,305 @@ +/* + * Chat — Terminal-style chat with GoClaw Orchestrator + * Design: Terminal aesthetic, monospace font, typing animation, command history + * Colors: Cyan for system, green for success, amber for warnings, white for user + * Typography: JetBrains Mono exclusively + */ +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Send, Terminal, Bot, User, Zap, AlertTriangle, CheckCircle, Info } from "lucide-react"; +import { motion, AnimatePresence } from "framer-motion"; +import { useState, useRef, useEffect } from "react"; + +interface ChatMessage { + id: string; + role: "user" | "system" | "agent"; + content: string; + timestamp: string; + type?: "info" | "success" | "warning" | "error" | "command"; + agent?: string; +} + +const INITIAL_MESSAGES: ChatMessage[] = [ + { + id: "1", + role: "system", + content: "GoClaw Gateway v0.1.0 initialized. Connected to Swarm cluster 'goclaw-swarm' (4 nodes, 10 containers).", + timestamp: "19:20:00", + type: "info", + }, + { + id: "2", + role: "system", + content: "Agent fleet loaded: 5 agents registered (4 active, 1 error).", + timestamp: "19:20:01", + type: "success", + }, + { + id: "3", + role: "system", + content: "⚠ Docs Agent (agent-docs) failed to connect to Anthropic API. Check API key in settings.", + timestamp: "19:20:02", + type: "warning", + }, + { + id: "4", + role: "user", + content: "Покажи статус всех агентов", + timestamp: "19:21:15", + type: "command", + }, + { + id: "5", + role: "agent", + content: `Agent Status Report: +┌─────────────────┬──────────┬───────────────────┬────────┐ +│ Agent │ Status │ Model │ Tasks │ +├─────────────────┼──────────┼───────────────────┼────────┤ +│ Coder Agent │ RUNNING │ claude-3.5-sonnet │ 3 │ +│ Browser Agent │ RUNNING │ gpt-4o │ 1 │ +│ Mail Agent │ IDLE │ gpt-4o-mini │ 0 │ +│ Monitor Agent │ RUNNING │ llama-3.1-8b │ 5 │ +│ Docs Agent │ ERROR │ claude-3-haiku │ 0 │ +└─────────────────┴──────────┴───────────────────┴────────┘`, + timestamp: "19:21:16", + type: "info", + agent: "Gateway", + }, + { + id: "6", + role: "user", + content: "Перезапусти Docs Agent", + timestamp: "19:22:30", + type: "command", + }, + { + id: "7", + role: "agent", + content: "Restarting agent-docs on node goclaw-worker-02... Container recreated. Waiting for health check...", + timestamp: "19:22:31", + type: "info", + agent: "Gateway", + }, + { + id: "8", + role: "system", + content: "✗ Docs Agent restart failed: API key invalid. Please update ANTHROPIC_API_KEY in Settings → API Keys.", + timestamp: "19:22:35", + type: "error", + }, +]; + +const DEMO_RESPONSES: Record = { + "помощь": { + id: "", + role: "agent", + content: `Available Commands: + status — Show all agents status + nodes — Show cluster nodes + restart — Restart an agent + deploy — Deploy new agent from spec + logs — Show agent logs + scale N — Scale agent replicas + models — List available LLM models + config — Show gateway configuration + help — Show this help message`, + timestamp: "", + type: "info", + agent: "Gateway", + }, + "models": { + id: "", + role: "agent", + content: `Available LLM Models: +┌───────────────────────┬──────────┬─────────────┬──────────┐ +│ Model │ Provider │ Type │ Status │ +├───────────────────────┼──────────┼─────────────┼──────────┤ +│ gpt-4o │ OpenAI │ Cloud │ ✓ Ready │ +│ gpt-4o-mini │ OpenAI │ Cloud │ ✓ Ready │ +│ claude-3.5-sonnet │ Anthropic│ Cloud │ ✓ Ready │ +│ claude-3-haiku │ Anthropic│ Cloud │ ✗ No Key │ +│ llama-3.1-8b │ Ollama │ Local │ ✓ Ready │ +│ llama-3.1-70b │ Ollama │ Local │ ✓ Ready │ +│ codestral-22b │ Ollama │ Local │ ○ Loading│ +│ qwen2.5-coder-7b │ Ollama │ Local │ ✓ Ready │ +└───────────────────────┴──────────┴─────────────┴──────────┘`, + timestamp: "", + type: "info", + agent: "Gateway", + }, + "nodes": { + id: "", + role: "agent", + content: `Swarm Nodes: +┌─────────────────────┬─────────┬────────┬──────┬──────┬──────────┐ +│ Hostname │ Role │ Status │ CPU │ MEM │ Containers│ +├─────────────────────┼─────────┼────────┼──────┼──────┼──────────┤ +│ goclaw-manager-01 │ Manager │ Ready │ 42% │ 68% │ 5 │ +│ goclaw-worker-01 │ Worker │ Ready │ 28% │ 45% │ 3 │ +│ goclaw-worker-02 │ Worker │ Ready │ 15% │ 32% │ 2 │ +│ goclaw-worker-03 │ Worker │ Drain │ 0% │ 12% │ 0 │ +└─────────────────────┴─────────┴────────┴──────┴──────┴──────────┘`, + timestamp: "", + type: "info", + agent: "Gateway", + }, +}; + +function getMessageIcon(msg: ChatMessage) { + if (msg.role === "user") return ; + if (msg.role === "agent") return ; + switch (msg.type) { + case "success": return ; + case "warning": return ; + case "error": return ; + default: return ; + } +} + +function getMessageColor(msg: ChatMessage) { + if (msg.role === "user") return "text-foreground"; + switch (msg.type) { + case "success": return "text-neon-green"; + case "warning": return "text-neon-amber"; + case "error": return "text-neon-red"; + default: return "text-primary"; + } +} + +export default function Chat() { + const [messages, setMessages] = useState(INITIAL_MESSAGES); + const [input, setInput] = useState(""); + const scrollRef = useRef(null); + const inputRef = useRef(null); + + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [messages]); + + const sendMessage = () => { + if (!input.trim()) return; + const now = new Date(); + const ts = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`; + + const userMsg: ChatMessage = { + id: `user-${Date.now()}`, + role: "user", + content: input, + timestamp: ts, + type: "command", + }; + setMessages((prev) => [...prev, userMsg]); + + const lowerInput = input.toLowerCase().trim(); + const responseKey = Object.keys(DEMO_RESPONSES).find((k) => lowerInput.includes(k)); + + setTimeout(() => { + if (responseKey) { + const resp = { ...DEMO_RESPONSES[responseKey], id: `resp-${Date.now()}`, timestamp: ts }; + setMessages((prev) => [...prev, resp]); + } else { + const fallback: ChatMessage = { + id: `resp-${Date.now()}`, + role: "agent", + content: `Обрабатываю команду: "${input}"\nДля списка доступных команд введите "помощь" или "help".`, + timestamp: ts, + type: "info", + agent: "Gateway", + }; + setMessages((prev) => [...prev, fallback]); + } + }, 300); + + setInput(""); + }; + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

Gateway Terminal

+

+ Connected to goclaw-gateway:18789 +

+
+
+
+
+ LIVE +
+
+ + {/* Chat area */} + + +
+ + {messages.map((msg) => ( + +
+ {getMessageIcon(msg)} +
+
+
+ {msg.timestamp} + {msg.agent && ( + [{msg.agent}] + )} + {msg.role === "user" && ( + [you] + )} +
+
+                      {msg.content}
+                    
+
+
+ ))} +
+ {/* Terminal cursor */} +
+ $ + +
+
+ + {/* Input area */} +
+
+ $ + setInput(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && sendMessage()} + placeholder="Введите команду или сообщение для оркестратора..." + className="bg-transparent border-none text-foreground font-mono text-sm placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0 h-8" + /> + +
+
+
+
+
+ ); +} diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx new file mode 100644 index 0000000..157d40d --- /dev/null +++ b/client/src/pages/Dashboard.tsx @@ -0,0 +1,340 @@ +/* + * Dashboard — Mission Control Overview + * Design: Grid of metric cards, node status, agent activity feed, cluster health + * Colors: Cyan glow for primary metrics, green/amber/red for status + * Typography: JetBrains Mono for all data values + */ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { + Server, + Bot, + Cpu, + HardDrive, + Activity, + ArrowUpRight, + ArrowDownRight, + Clock, + Zap, + Network, +} from "lucide-react"; +import { motion } from "framer-motion"; + +const HERO_BG = "https://d2xsxph8kpxj0f.cloudfront.net/97147719/ZEGAT83geRq9CNvryykaQv/hero-bg-Si4yCvZwFbZMP4XaHUueFi.webp"; +const SWARM_IMG = "https://d2xsxph8kpxj0f.cloudfront.net/97147719/ZEGAT83geRq9CNvryykaQv/swarm-cluster-jkxdea5N7sXTSZfbAbKCfs.webp"; + +const NODES = [ + { id: "node-01", name: "goclaw-manager-01", role: "Manager", status: "ready", cpu: 42, mem: 68, containers: 5, ip: "192.168.1.10" }, + { id: "node-02", name: "goclaw-worker-01", role: "Worker", status: "ready", cpu: 28, mem: 45, containers: 3, ip: "192.168.1.11" }, + { id: "node-03", name: "goclaw-worker-02", role: "Worker", status: "ready", cpu: 15, mem: 32, containers: 2, ip: "192.168.1.12" }, + { id: "node-04", name: "goclaw-worker-03", role: "Worker", status: "drain", cpu: 0, mem: 12, containers: 0, ip: "192.168.1.13" }, +]; + +const AGENTS = [ + { id: "agent-coder", name: "Coder Agent", model: "claude-3.5-sonnet", status: "running", tasks: 3, uptime: "2d 14h" }, + { id: "agent-browser", name: "Browser Agent", model: "gpt-4o", status: "running", tasks: 1, uptime: "2d 14h" }, + { id: "agent-mail", name: "Mail Agent", model: "gpt-4o-mini", status: "idle", tasks: 0, uptime: "2d 14h" }, + { id: "agent-monitor", name: "Monitor Agent", model: "llama-3.1-8b", status: "running", tasks: 5, uptime: "2d 14h" }, + { id: "agent-docs", name: "Docs Agent", model: "claude-3-haiku", status: "error", tasks: 0, uptime: "0h 0m" }, +]; + +const ACTIVITY_LOG = [ + { time: "19:24:15", agent: "Coder Agent", action: "Завершил рефакторинг модуля memory.go", type: "success" }, + { time: "19:23:48", agent: "Browser Agent", action: "Открыл https://github.com/goclaw/core", type: "info" }, + { time: "19:22:11", agent: "Monitor Agent", action: "CPU на node-02 превысил 80% (пик)", type: "warning" }, + { time: "19:21:30", agent: "Mail Agent", action: "Обработал 12 входящих писем", type: "success" }, + { time: "19:20:05", agent: "Docs Agent", action: "Ошибка подключения к LLM API", type: "error" }, + { time: "19:18:44", agent: "Coder Agent", action: "Создал новый скилл: ssh_executor.go", type: "success" }, +]; + +function getStatusColor(status: string) { + switch (status) { + case "ready": + case "running": + case "success": + return "text-neon-green"; + case "idle": + case "info": + return "text-primary"; + case "drain": + case "warning": + return "text-neon-amber"; + case "error": + case "down": + return "text-neon-red"; + default: + return "text-muted-foreground"; + } +} + +function getStatusBadge(status: string) { + const colors: Record = { + ready: "bg-neon-green/15 text-neon-green border-neon-green/30", + running: "bg-neon-green/15 text-neon-green border-neon-green/30", + idle: "bg-primary/15 text-primary border-primary/30", + drain: "bg-neon-amber/15 text-neon-amber border-neon-amber/30", + error: "bg-neon-red/15 text-neon-red border-neon-red/30", + }; + return colors[status] || "bg-muted text-muted-foreground border-border"; +} + +export default function Dashboard() { + return ( +
+ {/* Hero banner */} + + +
+
+
+

+ GoClaw Swarm Control Center +

+

+ Кластер goclaw-swarm · 4 ноды · 7 агентов · Overlay Network: goclaw-net +

+
+
+ + + {/* Key metrics row */} +
+ + + + +
+ + {/* Main grid: Nodes + Agents + Activity */} +
+ {/* Nodes panel */} + + + + + Swarm Nodes + + + + {NODES.map((node) => ( + +
+
+
+ {node.name} +
+ + {node.status.toUpperCase()} + +
+
+
+ CPU + + 70 ? "text-neon-amber" : "text-neon-green"}`}>{node.cpu}% +
+
+ MEM + + 70 ? "text-neon-amber" : "text-neon-green"}`}>{node.mem}% +
+
+ CONTAINERS +
{node.containers}
+
+
+ + ))} + + + + {/* Agents panel */} + + + + + Active Agents + + + + {AGENTS.map((agent) => ( + +
+
+
+ {agent.name} +
+ + {agent.status.toUpperCase()} + +
+
+ Model: {agent.model} + Tasks: {agent.tasks} +
+ + ))} + + + + {/* Activity feed */} + + + + + Activity Feed + + + +
+
+ {ACTIVITY_LOG.map((entry, i) => ( + +
+
+
+ {entry.time} + {entry.agent} +
+

{entry.action}

+
+ + ))} +
+ + +
+ + {/* Cluster visualization */} + + + + + Cluster Topology + + + +
+ Swarm Cluster Topology +
+
+
+ Overlay Network: goclaw-net · Subnet: 10.0.0.0/24 +
+
+ Manager + Worker + Drain +
+
+
+ + +
+ ); +} + +function MetricCard({ + icon: Icon, + label, + value, + change, + trend, + color, +}: { + icon: typeof Server; + label: string; + value: string; + change: string; + trend: "up" | "down"; + color: string; +}) { + return ( + + + +
+ +
+ {trend === "up" ? ( + + ) : ( + + )} + {change} +
+
+
{value}
+
{label}
+
+
+
+ ); +} diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 0b184dd..90e2319 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1,25 +1,5 @@ -import { Button } from "@/components/ui/button"; -import { Loader2 } from "lucide-react"; -import { Streamdown } from 'streamdown'; +import { Redirect } from "wouter"; -/** - * All content in this page are only for example, replace with your own feature implementation - * When building pages, remember your instructions in Frontend Best Practices, Design Guide and Common Pitfalls - */ export default function Home() { - // If theme is switchable in App.tsx, we can implement theme toggling like this: - // const { theme, toggleTheme } = useTheme(); - - return ( -
-
- {/* Example: lucide-react for icons */} - - Example Page - {/* Example: Streamdown for markdown rendering */} - Any **markdown** content - -
-
- ); + return ; } diff --git a/client/src/pages/Nodes.tsx b/client/src/pages/Nodes.tsx new file mode 100644 index 0000000..e9a18e5 --- /dev/null +++ b/client/src/pages/Nodes.tsx @@ -0,0 +1,265 @@ +/* + * Nodes — Swarm Node Monitoring + * Design: Detailed node cards with resource gauges, container lists + * Colors: Cyan primary, green/amber/red for resource thresholds + * Typography: JetBrains Mono for all metrics + */ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { + Server, + Cpu, + HardDrive, + Network, + Container, + Clock, + MapPin, + Layers, +} from "lucide-react"; +import { motion } from "framer-motion"; + +const NODE_VIS = "https://d2xsxph8kpxj0f.cloudfront.net/97147719/ZEGAT83geRq9CNvryykaQv/node-visualization-eDRHrwiVpLDMaH6VnWFsxn.webp"; + +const NODES_DATA = [ + { + id: "node-01", + hostname: "goclaw-manager-01", + role: "Manager", + status: "ready", + ip: "192.168.1.10", + os: "Ubuntu 22.04 LTS", + arch: "amd64", + cpu_cores: 8, + cpu_usage: 42, + mem_total: "16 GB", + mem_usage: 68, + disk_total: "256 GB", + disk_usage: 45, + containers: [ + { name: "goclaw-gateway", status: "running", cpu: 12, mem: "256MB" }, + { name: "goclaw-monitor", status: "running", cpu: 8, mem: "128MB" }, + { name: "goclaw-redis", status: "running", cpu: 2, mem: "64MB" }, + { name: "goclaw-control-ui", status: "running", cpu: 5, mem: "128MB" }, + { name: "prometheus", status: "running", cpu: 15, mem: "512MB" }, + ], + uptime: "14d 7h 23m", + docker_version: "24.0.7", + }, + { + id: "node-02", + hostname: "goclaw-worker-01", + role: "Worker", + status: "ready", + ip: "192.168.1.11", + os: "Ubuntu 22.04 LTS", + arch: "amd64", + cpu_cores: 16, + cpu_usage: 28, + mem_total: "32 GB", + mem_usage: 45, + disk_total: "512 GB", + disk_usage: 32, + containers: [ + { name: "goclaw-coder", status: "running", cpu: 15, mem: "512MB" }, + { name: "goclaw-browser", status: "running", cpu: 10, mem: "1024MB" }, + { name: "chromium-sandbox", status: "running", cpu: 3, mem: "256MB" }, + ], + uptime: "14d 7h 23m", + docker_version: "24.0.7", + }, + { + id: "node-03", + hostname: "goclaw-worker-02", + role: "Worker", + status: "ready", + ip: "192.168.1.12", + os: "Debian 12", + arch: "arm64", + cpu_cores: 4, + cpu_usage: 15, + mem_total: "8 GB", + mem_usage: 32, + disk_total: "128 GB", + disk_usage: 22, + containers: [ + { name: "goclaw-mail", status: "idle", cpu: 2, mem: "128MB" }, + { name: "goclaw-docs", status: "error", cpu: 0, mem: "0MB" }, + ], + uptime: "7d 2h 45m", + docker_version: "24.0.7", + }, + { + id: "node-04", + hostname: "goclaw-worker-03", + role: "Worker", + status: "drain", + ip: "192.168.1.13", + os: "Ubuntu 22.04 LTS", + arch: "amd64", + cpu_cores: 4, + cpu_usage: 0, + mem_total: "8 GB", + mem_usage: 12, + disk_total: "128 GB", + disk_usage: 18, + containers: [], + uptime: "14d 7h 23m", + docker_version: "24.0.7", + }, +]; + +function getResourceColor(value: number) { + if (value > 80) return "text-neon-red"; + if (value > 60) return "text-neon-amber"; + return "text-neon-green"; +} + +function getStatusBadge(status: string) { + switch (status) { + case "ready": return "bg-neon-green/15 text-neon-green border-neon-green/30"; + case "drain": return "bg-neon-amber/15 text-neon-amber border-neon-amber/30"; + case "down": return "bg-neon-red/15 text-neon-red border-neon-red/30"; + default: return "bg-muted text-muted-foreground border-border"; + } +} + +export default function Nodes() { + return ( +
+ {/* Header with visualization */} +
+ +
+
+
+

Swarm Nodes

+

+ Docker Swarm Cluster · {NODES_DATA.filter(n => n.status === "ready").length} ready · {NODES_DATA.filter(n => n.status === "drain").length} drain +

+
+ Total CPU: 32 cores + Total RAM: 64 GB + Total Disk: 1024 GB +
+
+
+
+ + {/* Node cards */} +
+ {NODES_DATA.map((node, i) => ( + + + + {/* Header */} +
+
+
+ +
+
+

{node.hostname}

+
+ + {node.status.toUpperCase()} + + {node.role} +
+
+
+
+
{node.ip}
+
{node.uptime}
+
+
+ + {/* System info */} +
+ {node.os} + · + {node.arch} + · + Docker {node.docker_version} + · + {node.cpu_cores} cores +
+ + {/* Resource gauges */} +
+ + + +
+ + {/* Containers */} +
+
+ + CONTAINERS ({node.containers.length}) +
+ {node.containers.length > 0 ? ( +
+ {node.containers.map((c) => ( +
+
+
+ {c.name} +
+
+ CPU: {c.cpu}% + MEM: {c.mem} +
+
+ ))} +
+ ) : ( +
+ No containers running +
+ )} +
+ + + + ))} +
+
+ ); +} + +function ResourceGauge({ + label, + value, + icon: Icon, + subtitle, +}: { + label: string; + value: number; + icon: typeof Cpu; + subtitle?: string; +}) { + const color = getResourceColor(value); + return ( +
+
+ + {label} +
+
{value}%
+ + {subtitle &&
{subtitle}
} +
+ ); +} diff --git a/client/src/pages/Settings.tsx b/client/src/pages/Settings.tsx new file mode 100644 index 0000000..e4d98a0 --- /dev/null +++ b/client/src/pages/Settings.tsx @@ -0,0 +1,440 @@ +/* + * Settings — API Keys, Model Providers, Gateway Configuration + * Design: Form-based panels with status indicators, model scanning + * Colors: Cyan primary, green for connected, red for errors + * Typography: JetBrains Mono for technical values, Inter for labels + */ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Separator } from "@/components/ui/separator"; +import { + Key, + Globe, + Server, + Brain, + RefreshCw, + CheckCircle, + XCircle, + AlertTriangle, + Plus, + Trash2, + Eye, + EyeOff, + Wifi, + Database, + Shield, + Loader2, +} from "lucide-react"; +import { motion } from "framer-motion"; +import { useState } from "react"; +import { toast } from "sonner"; + +interface Provider { + id: string; + name: string; + type: "openai" | "anthropic" | "ollama" | "custom"; + baseUrl: string; + apiKey: string; + status: "connected" | "error" | "unchecked"; + models: string[]; + enabled: boolean; +} + +const INITIAL_PROVIDERS: Provider[] = [ + { + id: "openai", + name: "OpenAI", + type: "openai", + baseUrl: "https://api.openai.com/v1", + apiKey: "sk-proj-****************************", + status: "connected", + models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"], + enabled: true, + }, + { + id: "anthropic", + name: "Anthropic", + type: "anthropic", + baseUrl: "https://api.anthropic.com/v1", + apiKey: "sk-ant-****************************", + status: "error", + models: ["claude-3.5-sonnet", "claude-3-haiku"], + enabled: true, + }, + { + id: "ollama", + name: "Ollama (Local)", + type: "ollama", + baseUrl: "http://192.168.1.10:11434", + apiKey: "", + status: "connected", + models: ["llama-3.1-8b", "llama-3.1-70b", "codestral-22b", "qwen2.5-coder-7b"], + enabled: true, + }, +]; + +function getStatusIcon(status: string) { + switch (status) { + case "connected": return ; + case "error": return ; + default: return ; + } +} + +function getStatusBadge(status: string) { + switch (status) { + case "connected": return "bg-neon-green/15 text-neon-green border-neon-green/30"; + case "error": return "bg-neon-red/15 text-neon-red border-neon-red/30"; + default: return "bg-neon-amber/15 text-neon-amber border-neon-amber/30"; + } +} + +export default function Settings() { + const [providers, setProviders] = useState(INITIAL_PROVIDERS); + const [showKeys, setShowKeys] = useState>({}); + const [scanning, setScanning] = useState>({}); + + const toggleKeyVisibility = (id: string) => { + setShowKeys((prev) => ({ ...prev, [id]: !prev[id] })); + }; + + const scanModels = (id: string) => { + setScanning((prev) => ({ ...prev, [id]: true })); + setTimeout(() => { + setScanning((prev) => ({ ...prev, [id]: false })); + toast.success(`Модели для ${providers.find(p => p.id === id)?.name} обновлены`); + }, 2000); + }; + + const testConnection = (id: string) => { + toast.info(`Тестирование подключения к ${providers.find(p => p.id === id)?.name}...`); + setTimeout(() => { + const provider = providers.find(p => p.id === id); + if (provider?.id === "anthropic") { + toast.error("Ошибка подключения: Invalid API key"); + } else { + toast.success("Подключение успешно"); + } + }, 1500); + }; + + return ( +
+
+

Настройки

+

+ Конфигурация Gateway, API-ключи и провайдеры моделей +

+
+ + + + + Провайдеры + + + Gateway + + + Коннекторы + + + Безопасность + + + + {/* Providers Tab */} + +
+

+ Управление провайдерами LLM-моделей. Поддерживаются OpenAI-совместимые API. +

+ +
+ + {providers.map((provider, i) => ( + + + +
+
+ {getStatusIcon(provider.status)} +
+ {provider.name} + {provider.type.toUpperCase()} +
+
+
+ + {provider.status.toUpperCase()} + + { + setProviders(prev => prev.map(p => p.id === provider.id ? { ...p, enabled: !p.enabled } : p)); + }} + /> +
+
+
+ + {/* Base URL */} +
+ +
+ + +
+
+ + {/* API Key */} + {provider.type !== "ollama" && ( +
+ +
+
+ + +
+ +
+
+ )} + + + + {/* Models */} +
+
+ + +
+
+ {provider.models.map((model) => ( + + {model} + + ))} +
+
+
+
+
+ ))} +
+ + {/* Gateway Tab */} + + + + + + Gateway Configuration + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
Auto-scaling
+
Автоматическое масштабирование агентов при нагрузке
+
+ +
+
+
+
Health Checks
+
Периодическая проверка состояния агентов
+
+ +
+
+
+
Debug Logging
+
Расширенное логирование для отладки
+
+ +
+
+
+
+
+ + {/* Connectors Tab */} + + + + + + Внешние подключения (Connectors) + + + + {/* Telegram */} +
+
+
+
+ + + +
+
+
Telegram Bot
+
@goclaw_agent_bot
+
+
+
+ + CONNECTED + + +
+
+
+ + +
+
+ + {/* Placeholder for more connectors */} + +
+
+
+ + {/* Security Tab */} + + + + + + Безопасность и изоляция + + + +
+
+
+
Sandbox Isolation
+
Запуск кода агентов в изолированных контейнерах
+
+ +
+
+
+
Network Policy
+
Ограничение сетевого доступа между агентами
+
+ +
+
+
+
Skill Auto-Approve
+
Автоматическое одобрение новых скиллов (опасно)
+
+ +
+
+
+
Audit Log
+
Запись всех действий агентов в журнал аудита
+
+ +
+
+
+
+
+
+
+ ); +} diff --git a/ideas.md b/ideas.md new file mode 100644 index 0000000..b1e5f4c --- /dev/null +++ b/ideas.md @@ -0,0 +1,91 @@ +# GoClaw Control Center — Дизайн-идеи + + + +## 1. «Mission Control» — Космический центр управления + +**Design Movement:** Sci-Fi Command Center / NASA Mission Control aesthetic +**Core Principles:** +- Информационная плотность без визуального хаоса +- Тёмный фон с яркими акцентами состояния (зелёный = ОК, янтарный = предупреждение, красный = ошибка) +- Моноширинный шрифт для данных, гуманистический для навигации +- Ощущение «живой системы» — пульсирующие индикаторы, стриминг логов + +**Color Philosophy:** Глубокий космический чёрный (#0A0E1A) как основа. Электрический голубой (#00D4FF) для активных элементов. Зелёный неон (#00FF88) для здоровых нод. Янтарный (#FFB800) для предупреждений. Красный (#FF3366) для ошибок. Белый с пониженной непрозрачностью для вторичного текста. + +**Layout Paradigm:** Фиксированный левый сайдбар (узкий, иконки + тултипы). Основная область разделена на сетку «панелей» с возможностью ресайза. Верхняя полоса — статус-бар с ключевыми метриками кластера. + +**Signature Elements:** +- Пульсирующие точки-индикаторы состояния нод +- Терминальный стиль для логов и чата с оркестратором +- Тонкие светящиеся границы (glow borders) на карточках + +**Interaction Philosophy:** Мгновенная обратная связь. Hover-эффекты с лёгким свечением. Клик по ноде раскрывает детали с анимацией slide-in. + +**Animation:** Плавные fade-in при загрузке данных. Пульсация индикаторов. Typing-эффект в чате. Smooth scroll в логах. + +**Typography System:** JetBrains Mono для данных/метрик/логов. Inter для навигации и заголовков. Размерная иерархия: 11px данные → 13px основной → 16px заголовки → 24px секции. + +Космический центр управления с высокой информационной плотностью, тёмной темой и неоновыми акцентами состояния. +0.08 + + + + +## 2. «Industrial Terminal» — Индустриальный терминал + +**Design Movement:** Brutalist / Industrial HMI (Human-Machine Interface) +**Core Principles:** +- Грубая честность интерфейса — никаких украшений, только функция +- Монохромная палитра с единственным акцентным цветом +- Жёсткие углы, толстые границы, крупная типографика +- Ощущение промышленной панели управления + +**Color Philosophy:** Чёрный (#111111) фон. Серый (#888888) для границ и неактивных элементов. Белый (#EEEEEE) для текста. Единственный акцент — ярко-оранжевый (#FF6600) для действий и предупреждений. + +**Layout Paradigm:** Полноэкранная сетка без отступов от краёв. Толстые разделители между секциями. Верхняя панель с крупными цифрами метрик. Боковая панель — список агентов в виде «карточек-плиток». + +**Signature Elements:** +- Толстые рамки (3-4px) вокруг каждого блока +- Крупные моноширинные цифры для метрик +- ASCII-арт стилизация для заголовков + +**Interaction Philosophy:** Минимум анимаций. Резкие переходы. Hover меняет фон на акцентный цвет. + +**Animation:** Практически отсутствует. Только мгновенные переключения состояний. + +**Typography System:** IBM Plex Mono для всего. Размеры: 48px для ключевых метрик, 14px для данных, 12px для подписей. + +Брутальный индустриальный терминал с монохромной палитрой и единственным оранжевым акцентом. +0.04 + + + + +## 3. «Neural Network» — Нейросетевая визуализация + +**Design Movement:** Data Visualization Art / Generative Design +**Core Principles:** +- Визуализация связей между агентами как нейронная сеть +- Градиентные переходы между состояниями +- Стеклоподобные (glassmorphism) панели поверх тёмного фона +- Органические формы для представления данных + +**Color Philosophy:** Тёмно-синий (#0D1117) фон с градиентными пятнами. Фиолетово-голубой градиент (#7C3AED → #06B6D4) для активных элементов. Стеклянные панели с backdrop-blur. Зелёный (#10B981) для здоровья, красный (#EF4444) для ошибок. + +**Layout Paradigm:** Центральная область — интерактивная карта агентов (граф связей). Боковые панели выезжают по клику на узел графа. Нижняя панель — чат с оркестратором. + +**Signature Elements:** +- Анимированные линии связей между агентами +- Glassmorphism панели с размытием фона +- Градиентные пульсации при передаче данных между агентами + +**Interaction Philosophy:** Органическая, плавная. Панели «всплывают» при наведении. Линии связей подсвечиваются при активности. + +**Animation:** Постоянная лёгкая анимация графа. Плавные переходы панелей. Градиентные пульсации. + +**Typography System:** Space Grotesk для заголовков. Inter для основного текста. JetBrains Mono для кода и логов. + +Нейросетевая визуализация с glassmorphism панелями и анимированным графом связей агентов. +0.06 +