import { useState, useRef, useCallback, useMemo } from "react" import { Bot, ExternalLink, Activity, Cpu, Wrench, Monitor, Shield, Check, X } from "lucide-react" import { api } from "@/lib/api" import { useAgentStatus, useModels, useQueryClient, qk } from "@/lib/queries" import { useDialog } from "@/lib/useDialog" import { cn, resolveExternalUrl } from "@/lib/utils" function Tile({ label, ok, detail, icon: Icon, onClick }: { label: string; ok: boolean; detail?: string; icon: any; onClick?: () => void }) { return (
{label}
{ok ? "Bereit / Online" : "Offline / Inaktiv"}
{detail &&
{detail}
}
{onClick && ( )}
) } export function AgentView() { const { data: s, error: sErr } = useAgentStatus(5_000) const { data: modelsData } = useModels() const { showAlert, dialogElement } = useDialog() const qc = useQueryClient() const error = sErr ? String(sErr) : "" const availableModels = useMemo(() => { const names = (modelsData?.models ?? []).map( (m) => m.name.split("/").pop()?.replace(".gguf", "") || m.name) return ["auto", "fast", "heavy", ...names] }, [modelsData]) // Graph UI state const [hoveredNode, setHoveredNode] = useState(null) const [showBrainSelect, setShowBrainSelect] = useState(false) // Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling const [dimensions, setDimensions] = useState({ width: 800, height: 360 }) const observerRef = useRef(null) const containerRef = useCallback((node: HTMLDivElement | null) => { if (observerRef.current) { observerRef.current.disconnect() observerRef.current = null } if (node) { const observer = new ResizeObserver((entries) => { if (!entries || entries.length === 0) return const rect = entries[0].contentRect setDimensions({ width: rect.width, height: rect.height }) }) observer.observe(node) observerRef.current = observer } }, []) const w = dimensions.width const h = dimensions.height // Helper to generate curve between any two points const getCurvePath = (startX: number, startY: number, endX: number, endY: number) => { const midX = (startX + endX) / 2 return `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}` } async function changeBrainModel(model: string) { try { await api("/api/agent/brain", { method: "POST", body: JSON.stringify({ model }) }) showAlert("Erfolgreich", `Hermes-Gehirn wurde auf '${model}' geändert. Der Gateway-Dienst wurde neu gestartet.`) qc.invalidateQueries({ queryKey: qk.agentStatus }) setShowBrainSelect(false) } catch (e: any) { showAlert("Fehler", `Fehler beim Wechseln des Gehirns: ${e.message}`) } } return (
{/* Styles inside AgentView for dash flow animations */} {/* Header */}

Hermes Agenten-Cockpit

Hermes ist ein autonomer Agent, der im Hintergrund der Box läuft (Port :8642) und seine eigene UI besitzt.

{s?.terminal_url && ( Terminal öffnen )}
{error && (
Status nicht lesbar ({error}).
)} {s && (
{/* Tile Grid */}
setShowBrainSelect(true)} />
{/* Interactive Gateway Graph */}
Interactive Agent Flow Graph

Visualisiert das Zusammenspiel und die Verbindungspfade zwischen den Hermes-Systemkomponenten.

{/* The Graph Canvas Area */}
{/* Connection: Terminal to Gateway */} {(hoveredNode === "terminal" || s.terminal_reachable) && ( )} {/* Connection: Gateway to Gehirn */} {(hoveredNode === "gateway" || hoveredNode === "brain" || s.gateway_reachable) && ( )} {/* Connection: Gateway to Verdrahtung */} {(hoveredNode === "gateway" || hoveredNode === "wiring" || s.gateway_reachable) && ( )} {/* COLUMN 1: Interface */}
setHoveredNode("terminal")} onMouseLeave={() => setHoveredNode(null)} onClick={() => s.terminal_reachable && window.open(resolveExternalUrl(s.terminal_url), "_blank")} title={s.terminal_reachable ? "Klicken um das Hermes-Terminal zu öffnen" : "Terminal offline"} > Terminal
{/* COLUMN 2: Gateway */}
setHoveredNode("gateway")} onMouseLeave={() => setHoveredNode(null)} >
Agent Gateway
Port :8642
{s.gateway_reachable ? "Online" : "Offline"}
{/* COLUMN 3: Brain & Wiring */}
setHoveredNode("brain")} onMouseLeave={() => setHoveredNode(null)} onClick={() => setShowBrainSelect(true)} >
Aktives Gehirn
{s.gateway_reachable && }
{s.brain_model || "auto"}
setHoveredNode("wiring")} onMouseLeave={() => setHoveredNode(null)} >
Verdrahtung
{s.has_config && }
Config: {s.has_config ? "✓" : "—"} Skills: {s.has_skills ? "✓" : "—"} Memory: {s.has_memories ? "✓" : "—"}
{/* Legend */}
Cyan-Fluss: Client-Anfrage an Agenten Grüner Puls: Verbindung hergestellt / Modul online
{/* PC Executor Status Card */}

PC Verbindung

{s.pc_executor_reachable ? "Erreichbar" : "Nicht verbunden"}

Der Hermes PC Executor läuft auf 192.168.178.98:7777 und ermöglicht Hermes den direkten Zugriff auf deinen Windows-PC — Shell-Befehle, Screenshots, Tastatureingaben.

Hermes erreicht den PC über den MCP-Server hermes-pc-control, der automatisch beim Gateway-Start verbunden wird.

{s.pc_executor_reachable ? (
PC Executor verbunden

Hermes kann jetzt per Telegram oder Terminal Befehle auf TobisNicerPC ausführen. Nutze pc_shell, pc_screenshot oder pc_open.

) : (

PC Executor starten

Starte start.bat im Ordner client\hermes-pc\ auf dem Windows-PC und lasse das Fenster offen.

client\hermes-pc\start.bat
)}
{/* Diagnostic Details if Offline */} {!s.gateway_reachable && (

Hermes-Agent Diagnostics

Der Hermes-Dienst ist auf der Box derzeit offline oder kann sich nicht mit dem lokalen llama-swap verbinden.

Stelle sicher, dass die systemd-Dienste auf der Box laufen. Du kannst den Status im OS & Updates Drawer prüfen und die Dienste bei Bedarf neu starten.

# Dienste manuell auf der Box prüfen:
systemctl --user status hermes-gateway
systemctl --user status hermes-terminal

Weitere Einrichtungsdetails (Brain-Konfiguration, MCP-Kopplungen, SSH-Tunneling und Such-Engines) findest du in der Dokumentationsdatei: docs/HERMES_SETUP.md.

)}
)} {/* Brain Selection Modal */} {s && showBrainSelect && (

Hermes-Gehirn konfigurieren

Das „Gehirn" ist das primäre LLM für Hermes. Wähle ein Modell aus deiner Bibliothek oder nutze ein Gateway-Alias (auto / fast / heavy):

{availableModels.map((m) => { const isAlias = ["auto", "fast", "heavy"].includes(m); return ( ); })}
)} {dialogElement}
) }