import { useEffect, useState, useRef, useCallback } from "react" import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, Check, X } from "lucide-react" import { api, type AgentStatus } from "@/lib/api" import { cn, resolveExternalUrl } from "@/lib/utils" import { CustomDialog } from "@/components/CustomDialog" 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 [s, setS] = useState(null) const [error, setError] = useState("") // Graph UI state const [hoveredNode, setHoveredNode] = useState(null) const [showBrainSelect, setShowBrainSelect] = useState(false) const [availableModels, setAvailableModels] = useState([]) // Custom Dialog State const [dialog, setDialog] = useState<{ type: "alert" | "confirm" title: string message: string onConfirm?: () => void onCancel?: () => void } | null>(null) function showAlert(title: string, message: string, onConfirm?: () => void) { setDialog({ type: "alert", title, message, onConfirm }) } // 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}` } function loadData() { api("/api/agent/status").then(setS).catch((e) => setError(String(e))) } function loadModels() { api<{ models: { name: string }[] }>("/api/models") .then((res) => { const names = res.models.map(m => m.name.split("/").pop()?.replace(".gguf", "") || m.name) setAvailableModels(["auto", "fast", "heavy", ...names]) }) .catch((e) => console.error("Error loading models", e)) } 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.`) loadData() setShowBrainSelect(false) } catch (e: any) { showAlert("Fehler", `Fehler beim Wechseln des Gehirns: ${e.message}`) } } useEffect(() => { loadData() loadModels() const t = setInterval(loadData, 5000) return () => clearInterval(t) }, []) 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?.webui_url && ( Hermes WebUI ö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: WebUI to Gateway */} {(hoveredNode === "webui" || s.webui_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("webui")} onMouseLeave={() => setHoveredNode(null)} onClick={() => s.webui_reachable && window.open(resolveExternalUrl(s.webui_url), "_blank")} title={s.webui_reachable ? "Klicken um Chat-WebUI zu öffnen" : "WebUI Offline"} > Agent WebUI
{/* 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
{/* SSH / Sandbox-Hilfe Card */}

SSH-Kopplung & Sandbox-Umgehung

1. Warum verweigert Hermes Aktionen auf dem PC?

Hermes ist standardmäßig so ausgerichtet, dass er sich als virtueller Assistent ohne Systemzugriff versteht. Um dies zu umgehen, musst du Hermes' **System-Prompt** in der Chat-WebUI anpassen.

Beispiel System-Prompt Erweiterung:
"Du hast über das terminal-Tool vollen SSH-Zugriff auf den Windows-PC des Users (TobisPC). Nutze SSH-Befehle um Aktionen dort auszuführen, wenn danach gefragt wird. Behaupte nie, du könntest das nicht!"

2. SSH-Verbindung zum lokalen Windows-PC einrichten

Damit der Agent Befehle auf deinem PC ausführen kann, muss OpenSSH auf Windows aktiv und mit einem Key gekoppelt sein:

  • OpenSSH Server auf Windows starten: In PowerShell als Admin ausführen: Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
  • SSH-Key auf der Box erzeugen: ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_hermes_agent
  • Key autorisieren: Kopiere den Inhalt von ~/.ssh/id_ed25519_hermes_agent.pub in deine Windows-Datei C:\Users\TobisPC\.ssh\authorized_keys
{/* 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-webui

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 ( ); })}
)} {dialog && ( dialog.onConfirm && dialog.onConfirm()} onCancel={dialog.onCancel} /> )}
) }