Feat: UI-Audit — Telegram/MCP/PC-Status, incomplete-Badge, SSH-Karte ersetzt
- backend: PC_EXECUTOR_URL Env-Var + 3 neue agent_status()-Felder (telegram_enabled, mcp_server_count, pc_executor_reachable) - AgentStatusCard: 2x2-Grid (Gateway/WebUI/Gehirn/Verdrahtung) + echte Status-Footer-Zeilen fuer Telegram, MCP-Server-Anzahl, PC - TokenStatsCard: 'Juni 2026' entfernt, Null-Fallback fuer pricing - AgentView: SSH-Bypass-Karte ersetzt durch PC-Executor-Statuskarte - Cockpit + RolesCard: Datei-fehlt-Badge + Load-Button disabled fuer incomplete-Modelle - api.ts: AgentStatus-Interface um 3 neue Felder erweitert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,8 @@ GATEWAY_CONFIG_PATH = Path(os.environ.get(
|
|||||||
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642").rstrip("/")
|
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642").rstrip("/")
|
||||||
HERMES_WEBUI_URL = os.environ.get("HERMES_WEBUI_URL", "http://127.0.0.1:8787").rstrip("/")
|
HERMES_WEBUI_URL = os.environ.get("HERMES_WEBUI_URL", "http://127.0.0.1:8787").rstrip("/")
|
||||||
HERMES_HOME = Path(os.path.expanduser(os.environ.get("HERMES_HOME", "~/.hermes")))
|
HERMES_HOME = Path(os.path.expanduser(os.environ.get("HERMES_HOME", "~/.hermes")))
|
||||||
|
# PC Executor — läuft auf dem Windows-PC, erreichbar über LAN.
|
||||||
|
PC_EXECUTOR_URL = os.environ.get("MC_PC_EXECUTOR_URL", "http://192.168.178.98:7777").rstrip("/")
|
||||||
|
|
||||||
# --- Server ------------------------------------------------------------------
|
# --- Server ------------------------------------------------------------------
|
||||||
HOST = os.environ.get("MC_HOST", "0.0.0.0")
|
HOST = os.environ.get("MC_HOST", "0.0.0.0")
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ Hermes' eigener Config verdrahtet (siehe docs/HERMES_SETUP.md).
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from config import HERMES_API_URL, HERMES_HOME, HERMES_WEBUI_URL
|
from config import HERMES_API_URL, HERMES_HOME, HERMES_WEBUI_URL, PC_EXECUTOR_URL
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,6 +22,24 @@ def _reach(url: str, path: str = "") -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _count_enabled_mcp_servers() -> int:
|
||||||
|
config_path = HERMES_HOME / "config.yaml"
|
||||||
|
if not config_path.exists():
|
||||||
|
return 0
|
||||||
|
try:
|
||||||
|
from ruamel.yaml import YAML
|
||||||
|
r_yaml = YAML()
|
||||||
|
with config_path.open("r", encoding="utf-8") as f:
|
||||||
|
cfg = r_yaml.load(f) or {}
|
||||||
|
mcp_servers = cfg.get("mcp_servers", {}) if isinstance(cfg, dict) else {}
|
||||||
|
if not isinstance(mcp_servers, dict):
|
||||||
|
return 0
|
||||||
|
return sum(1 for v in mcp_servers.values() if isinstance(v, dict) and v.get("enabled", True))
|
||||||
|
except Exception:
|
||||||
|
log.debug("_count_enabled_mcp_servers: Fehler", exc_info=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def agent_status() -> dict:
|
def agent_status() -> dict:
|
||||||
"""Erreichbarkeit von Gateway (:8642) + WebUI (:8787) + lokale Hinweise."""
|
"""Erreichbarkeit von Gateway (:8642) + WebUI (:8787) + lokale Hinweise."""
|
||||||
home = HERMES_HOME
|
home = HERMES_HOME
|
||||||
@@ -49,6 +68,10 @@ def agent_status() -> dict:
|
|||||||
"has_config": (home / "config.yaml").exists() or (home / "config.json").exists(),
|
"has_config": (home / "config.yaml").exists() or (home / "config.json").exists(),
|
||||||
"has_skills": (home / "skills").exists(),
|
"has_skills": (home / "skills").exists(),
|
||||||
"has_memories": (home / "memories").exists(),
|
"has_memories": (home / "memories").exists(),
|
||||||
|
# Neue Felder: Telegram, MCP-Server-Anzahl, PC-Executor-Erreichbarkeit.
|
||||||
|
"telegram_enabled": bool(os.environ.get("TELEGRAM_BOT_TOKEN", "")),
|
||||||
|
"mcp_server_count": _count_enabled_mcp_servers(),
|
||||||
|
"pc_executor_reachable": _reach(PC_EXECUTOR_URL, "/health"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
-1
File diff suppressed because one or more lines are too long
+395
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-390
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
|||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>Mission Control 2.0</title>
|
<title>Mission Control 2.0</title>
|
||||||
<script type="module" crossorigin src="/assets/index-l7I88DA-.js"></script>
|
<script type="module" crossorigin src="/assets/index-BRFJuGKC.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-1UOWeoZj.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C0dfTDFj.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Bot, ExternalLink, Cpu, Layers, X, Check } from "lucide-react"
|
import { Bot, ExternalLink, Cpu, Layers, Wrench, X, Check } from "lucide-react"
|
||||||
import { api } from "@/lib/api"
|
import { api } from "@/lib/api"
|
||||||
import { useAgentStatus, useModels, useQueryClient, qk } from "@/lib/queries"
|
import { useAgentStatus, useModels, useQueryClient, qk } from "@/lib/queries"
|
||||||
import { useDialog } from "@/lib/useDialog"
|
import { useDialog } from "@/lib/useDialog"
|
||||||
@@ -51,37 +51,43 @@ export function AgentStatusCard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{agent ? (
|
{agent ? (
|
||||||
<div className="space-y-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
||||||
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
<div className="text-[10px] text-muted-foreground uppercase font-semibold">Gateway</div>
|
||||||
<div className="text-[10px] text-muted-foreground uppercase font-semibold">Gateway</div>
|
<div className="flex items-center gap-2 mt-1">
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<span className={cn("h-2 w-2 rounded-full", agent.gateway_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
||||||
<span className={cn("h-2 w-2 rounded-full", agent.gateway_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
<span className="text-xs font-medium">{agent.gateway_reachable ? "Online" : "Offline"}</span>
|
||||||
<span className="text-xs font-medium">{agent.gateway_reachable ? "Online" : "Offline"}</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
||||||
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
<div className="text-[10px] text-muted-foreground uppercase font-semibold">WebUI</div>
|
||||||
<div className="text-[10px] text-muted-foreground uppercase font-semibold">WebUI</div>
|
<div className="flex items-center gap-2 mt-1">
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<span className={cn("h-2 w-2 rounded-full", agent.webui_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
||||||
<span className={cn("h-2 w-2 rounded-full", agent.webui_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
<span className="text-xs font-medium">{agent.webui_reachable ? "Online" : "Offline"}</span>
|
||||||
<span className="text-xs font-medium">{agent.webui_reachable ? "Online" : "Offline"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
onClick={() => setShowBrainSelect(true)}
|
onClick={() => setShowBrainSelect(true)}
|
||||||
className="p-3 bg-background/20 rounded-xl border border-border/40 hover:border-primary/45 hover:bg-background/30 transition-all duration-300 cursor-pointer group"
|
className="p-3 bg-background/20 rounded-xl border border-border/40 hover:border-primary/45 hover:bg-background/30 transition-all duration-300 cursor-pointer"
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-[10px] text-muted-foreground uppercase font-semibold">Aktives Gehirn</span>
|
<span className="text-[10px] text-muted-foreground uppercase font-semibold">Gehirn</span>
|
||||||
<button className="flex items-center gap-1 text-[9px] font-bold uppercase tracking-wider text-primary border border-primary/20 bg-primary/10 hover:bg-primary/20 px-1.5 py-0.5 rounded transition-all cursor-pointer font-space">
|
<Cpu className="h-3 w-3 text-primary" />
|
||||||
<Cpu className="h-3 w-3" /> Ändern
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-medium mt-1 font-mono text-primary flex items-center gap-1.5">
|
<div className="text-xs font-medium mt-1 font-mono text-primary truncate flex items-center gap-1">
|
||||||
<Layers className="h-3.5 w-3.5" />
|
<Layers className="h-3 w-3 shrink-0" />
|
||||||
{agent.brain_model ? `model: ${agent.brain_model}` : "model: auto"}
|
{agent.brain_model || "auto"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-[10px] text-muted-foreground uppercase font-semibold">Verdrahtung</span>
|
||||||
|
<Wrench className="h-3 w-3 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] font-mono mt-1 space-y-0.5 text-muted-foreground">
|
||||||
|
<div>Config: {agent.has_config ? <span className="text-emerald-400">✓</span> : "—"}</div>
|
||||||
|
<div>Skills: {agent.has_skills ? <span className="text-emerald-400">✓</span> : "—"}</div>
|
||||||
|
<div>Memory: {agent.has_memories ? <span className="text-emerald-400">✓</span> : "—"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,9 +95,26 @@ export function AgentStatusCard() {
|
|||||||
<div className="h-28 flex items-center justify-center text-xs text-muted-foreground">Lade Agenten-Status…</div>
|
<div className="h-28 flex items-center justify-center text-xs text-muted-foreground">Lade Agenten-Status…</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-[10px] text-muted-foreground/80 border-t border-border/30 pt-3">
|
{agent && (
|
||||||
Gedächtnis & Stack-Tools via MCP gekoppelt.
|
<div className="mt-3 border-t border-border/30 pt-3 space-y-1.5">
|
||||||
</div>
|
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
||||||
|
<span>Telegram</span>
|
||||||
|
<span className={cn("font-semibold", agent.telegram_enabled ? "text-emerald-400" : "")}>
|
||||||
|
{agent.telegram_enabled ? "aktiv" : "nicht konfiguriert"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
||||||
|
<span>MCP-Server</span>
|
||||||
|
<span className="font-semibold text-foreground">{agent.mcp_server_count ?? 0} verbunden</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-[10px] text-muted-foreground">
|
||||||
|
<span>PC Executor</span>
|
||||||
|
<span className={cn("font-semibold", agent.pc_executor_reachable ? "text-emerald-400" : "")}>
|
||||||
|
{agent.pc_executor_reachable ? "erreichbar" : "nicht verbunden"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{agent && showBrainSelect && (
|
{agent && showBrainSelect && (
|
||||||
<div className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
|
<div className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ export function RolesCard() {
|
|||||||
SLOTS: {m.parallel_slots}
|
SLOTS: {m.parallel_slots}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{m.incomplete && (
|
||||||
|
<span className="text-[7px] leading-none font-mono font-bold bg-amber-500/10 text-amber-400 border border-amber-500/20 px-1 py-0.5 rounded" title="GGUF-Datei fehlt">
|
||||||
|
⚠ fehlt
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ export function TokenStatsCard() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-[9px] text-muted-foreground/70 border-t border-border/30 pt-2.5 leading-normal">
|
<div className="mt-4 text-[9px] text-muted-foreground/70 border-t border-border/30 pt-2.5 leading-normal">
|
||||||
Berechnet im Vergleich zu Cloud-APIs von Juni 2026
|
Berechnet ggü. Cloud-APIs
|
||||||
{tokenStats?.pricing?.heavy
|
{tokenStats?.pricing?.heavy
|
||||||
? ` (Ø ${tokenStats.pricing.heavy.in.toFixed(2).replace(".", ",")} $ / ${tokenStats.pricing.heavy.out.toFixed(2).replace(".", ",")} $ pro 1M tkn).`
|
? ` (Ø ${(tokenStats.pricing.heavy.in ?? 0).toFixed(2).replace(".", ",")} $ / ${(tokenStats.pricing.heavy.out ?? 0).toFixed(2).replace(".", ",")} $ pro 1M tkn).`
|
||||||
: "."}
|
: "."}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -224,6 +224,9 @@ export interface AgentStatus {
|
|||||||
has_config: boolean
|
has_config: boolean
|
||||||
has_skills: boolean
|
has_skills: boolean
|
||||||
has_memories: boolean
|
has_memories: boolean
|
||||||
|
telegram_enabled?: boolean
|
||||||
|
mcp_server_count?: number
|
||||||
|
pc_executor_reachable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Job {
|
export interface Job {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useRef, useCallback, useMemo } from "react"
|
import { useState, useRef, useCallback, useMemo } from "react"
|
||||||
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, Check, X } from "lucide-react"
|
import { Bot, ExternalLink, Activity, Cpu, Wrench, Monitor, Shield, Check, X } from "lucide-react"
|
||||||
import { api } from "@/lib/api"
|
import { api } from "@/lib/api"
|
||||||
import { useAgentStatus, useModels, useQueryClient, qk } from "@/lib/queries"
|
import { useAgentStatus, useModels, useQueryClient, qk } from "@/lib/queries"
|
||||||
import { useDialog } from "@/lib/useDialog"
|
import { useDialog } from "@/lib/useDialog"
|
||||||
@@ -327,37 +327,51 @@ export function AgentView() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SSH / Sandbox-Hilfe Card */}
|
{/* PC Executor Status Card */}
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-6 space-y-4 shadow-lg shadow-black/10">
|
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-6 space-y-4 shadow-lg shadow-black/10">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center justify-between">
|
||||||
<Shield className="h-5 w-5 text-primary" />
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-sm font-bold uppercase tracking-wider text-foreground">SSH-Kopplung & Sandbox-Umgehung</h3>
|
<Monitor className="h-5 w-5 text-primary" />
|
||||||
|
<h3 className="text-sm font-bold uppercase tracking-wider text-foreground">PC Verbindung</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={cn("h-2 w-2 rounded-full", s.pc_executor_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
||||||
|
<span className="text-xs font-semibold text-foreground">
|
||||||
|
{s.pc_executor_reachable ? "Erreichbar" : "Nicht verbunden"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-2 text-xs text-muted-foreground leading-relaxed text-left">
|
<div className="grid gap-6 md:grid-cols-2 text-xs text-muted-foreground leading-relaxed text-left">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="text-xs font-bold text-foreground">1. Warum verweigert Hermes Aktionen auf dem PC?</h4>
|
|
||||||
<p>
|
<p>
|
||||||
Hermes ist standardmäßig so ausgerichtet, dass er sich als virtueller Assistent ohne Systemzugriff versteht.
|
Der <strong className="text-foreground">Hermes PC Executor</strong> läuft auf <code className="text-foreground font-mono px-1 py-0.5 rounded bg-muted/40 border border-border/30">192.168.178.98:7777</code> und ermöglicht Hermes den direkten Zugriff auf deinen Windows-PC — Shell-Befehle, Screenshots, Tastatureingaben.
|
||||||
Um dies zu umgehen, musst du Hermes' **System-Prompt** in der Chat-WebUI anpassen.
|
</p>
|
||||||
|
<p>
|
||||||
|
Hermes erreicht den PC über den MCP-Server <code className="text-foreground font-mono px-1 py-0.5 rounded bg-muted/40 border border-border/30">hermes-pc-control</code>, der automatisch beim Gateway-Start verbunden wird.
|
||||||
</p>
|
</p>
|
||||||
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[9px] text-cyan-300">
|
|
||||||
<span className="text-muted-foreground/60">Beispiel System-Prompt Erweiterung:</span>
|
|
||||||
<br />
|
|
||||||
"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!"
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="text-xs font-bold text-foreground">2. SSH-Verbindung zum lokalen Windows-PC einrichten</h4>
|
{s.pc_executor_reachable ? (
|
||||||
<p>
|
<div className="p-3.5 bg-emerald-500/5 border border-emerald-500/20 rounded-xl space-y-1">
|
||||||
Damit der Agent Befehle auf deinem PC ausführen kann, muss OpenSSH auf Windows aktiv und mit einem Key gekoppelt sein:
|
<div className="text-emerald-400 font-semibold text-xs flex items-center gap-1.5">
|
||||||
</p>
|
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||||
<ul className="list-disc pl-4 space-y-1.5 text-[11px]">
|
PC Executor verbunden
|
||||||
<li><strong>OpenSSH Server auf Windows starten:</strong> In PowerShell als Admin ausführen: <code className="text-foreground font-mono">Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0</code></li>
|
</div>
|
||||||
<li><strong>SSH-Key auf der Box erzeugen:</strong> <code className="text-foreground font-mono">ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_hermes_agent</code></li>
|
<p className="text-[10px]">
|
||||||
<li><strong>Key autorisieren:</strong> Kopiere den Inhalt von <code className="text-foreground font-mono">~/.ssh/id_ed25519_hermes_agent.pub</code> in deine Windows-Datei <code className="text-foreground font-mono">C:\Users\TobisPC\.ssh\authorized_keys</code></li>
|
Hermes kann jetzt per Telegram oder WebUI Befehle auf TobisNicerPC ausführen. Nutze <code className="text-foreground">pc_shell</code>, <code className="text-foreground">pc_screenshot</code> oder <code className="text-foreground">pc_open</code>.
|
||||||
</ul>
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-xs font-bold text-foreground">PC Executor starten</h4>
|
||||||
|
<p>Starte <code className="text-foreground font-mono">start.bat</code> im Ordner <code className="text-foreground font-mono">client\hermes-pc\</code> auf dem Windows-PC und lasse das Fenster offen.</p>
|
||||||
|
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[9px] text-cyan-300">
|
||||||
|
client\hermes-pc\start.bat
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -754,6 +754,11 @@ export function Cockpit() {
|
|||||||
SLOTS: {m.parallel_slots}
|
SLOTS: {m.parallel_slots}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{m.incomplete && (
|
||||||
|
<span className="px-1.5 py-0.5 rounded bg-amber-500/10 border border-amber-500/20 text-[9px] font-mono text-amber-400 font-bold uppercase" title="GGUF-Datei fehlt — Modell kann nicht geladen werden">
|
||||||
|
⚠ Datei fehlt
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -803,11 +808,14 @@ export function Cockpit() {
|
|||||||
<div className="flex items-center gap-1.5 border-t border-border/20 pt-3 mt-auto">
|
<div className="flex items-center gap-1.5 border-t border-border/20 pt-3 mt-auto">
|
||||||
<button
|
<button
|
||||||
onClick={() => isRunning ? handleUnloadModel(m.name) : handleLoadModel(m.name)}
|
onClick={() => isRunning ? handleUnloadModel(m.name) : handleLoadModel(m.name)}
|
||||||
|
disabled={m.incomplete && !isRunning}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 h-7 rounded-lg text-[9px] font-bold uppercase transition-all cursor-pointer border flex items-center justify-center gap-1",
|
"flex-1 h-7 rounded-lg text-[9px] font-bold uppercase transition-all border flex items-center justify-center gap-1",
|
||||||
isRunning
|
m.incomplete && !isRunning
|
||||||
? "border-amber-500/30 bg-amber-500/5 hover:bg-amber-500/15 text-amber-400"
|
? "border-border/30 bg-background/10 text-muted-foreground/50 cursor-not-allowed"
|
||||||
: "border-primary/30 bg-primary/5 hover:bg-primary/15 text-primary"
|
: isRunning
|
||||||
|
? "border-amber-500/30 bg-amber-500/5 hover:bg-amber-500/15 text-amber-400 cursor-pointer"
|
||||||
|
: "border-primary/30 bg-primary/5 hover:bg-primary/15 text-primary cursor-pointer"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isRunning ? "Entladen" : "Laden"}
|
{isRunning ? "Entladen" : "Laden"}
|
||||||
@@ -889,6 +897,11 @@ export function Cockpit() {
|
|||||||
SLOTS: {m.parallel_slots}
|
SLOTS: {m.parallel_slots}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{m.incomplete && (
|
||||||
|
<span className="px-1.5 py-0.5 rounded bg-amber-500/10 border border-amber-500/20 text-[8px] font-mono text-amber-400 font-bold uppercase shrink-0" title="GGUF-Datei fehlt">
|
||||||
|
⚠ Datei fehlt
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
<span className="flex items-center gap-0.5 text-[8px] font-semibold text-emerald-400 uppercase tracking-wider shrink-0">
|
<span className="flex items-center gap-0.5 text-[8px] font-semibold text-emerald-400 uppercase tracking-wider shrink-0">
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" /> warm
|
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" /> warm
|
||||||
@@ -914,11 +927,14 @@ export function Cockpit() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<button
|
<button
|
||||||
onClick={() => isRunning ? handleUnloadModel(m.name) : handleLoadModel(m.name)}
|
onClick={() => isRunning ? handleUnloadModel(m.name) : handleLoadModel(m.name)}
|
||||||
|
disabled={m.incomplete && !isRunning}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-7 px-3 rounded-lg text-[9px] font-bold uppercase transition-all cursor-pointer border flex items-center gap-1",
|
"h-7 px-3 rounded-lg text-[9px] font-bold uppercase transition-all border flex items-center gap-1",
|
||||||
isRunning
|
m.incomplete && !isRunning
|
||||||
? "border-amber-500/30 bg-amber-500/5 hover:bg-amber-500/15 text-amber-400"
|
? "border-border/30 bg-background/10 text-muted-foreground/50 cursor-not-allowed"
|
||||||
: "border-primary/30 bg-primary/5 hover:bg-primary/15 text-primary"
|
: isRunning
|
||||||
|
? "border-amber-500/30 bg-amber-500/5 hover:bg-amber-500/15 text-amber-400 cursor-pointer"
|
||||||
|
: "border-primary/30 bg-primary/5 hover:bg-primary/15 text-primary cursor-pointer"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isRunning ? "Entladen" : "Laden"}
|
{isRunning ? "Entladen" : "Laden"}
|
||||||
|
|||||||
Reference in New Issue
Block a user