Feat: Add manual updates check, Settings tab, direct model upgrades, logs password redirection, fix engine version detection, and expand AI agent Guide tab

This commit is contained in:
Hitonabi
2026-06-26 13:09:50 +02:00
parent 563e7837b9
commit 0c7b0b19af
11 changed files with 1122 additions and 599 deletions
+7
View File
@@ -21,6 +21,13 @@ class RestartReq(BaseModel):
def updates() -> dict:
return maintenance.updates()
@router.post("/maintenance/check-updates")
def check_updates(body: SudoReq) -> dict:
res = maintenance.check_updates_job(body.sudo_password)
if isinstance(res, dict) and not res.get("ok", True):
return res
return res
@router.post("/maintenance/os-update")
def os_update(body: SudoReq) -> dict:
+11
View File
@@ -168,6 +168,17 @@ def logs(service: str, lines: int = 200, sudo_password: str | None = None) -> di
return {"ok": False, "text": "", "err": "Dienst nicht erlaubt."}
return {"ok": r["ok"], "text": r["out"] or r["err"]}
def check_updates_job(sudo_password: str | None = None) -> dict:
if err := check_sudo_needs_password(sudo_password):
return err
def on_done():
_engine_cache.update(ts=0.0, avail=False)
cmd = "sudo apt-get update"
job_id = jobengine.start_job(["bash", "-c", cmd], "Nach Updates suchen", on_done=on_done, sudo_password=sudo_password)
return {"ok": True, "job_id": job_id}
def os_update_job(sudo_password: str | None = None) -> dict:
if err := check_sudo_needs_password(sudo_password):
+20 -13
View File
@@ -132,20 +132,27 @@ def get_engine_version() -> dict:
if git_info:
return {**git_info, "type": "git"}
try:
binary = os.path.join(engine_path, "llama-server")
if not os.path.exists(binary):
binary = os.path.join(engine_path, "bin", "llama-server")
if not os.path.exists(binary):
binary = "llama-server"
candidates = [
os.path.join(engine_path, "llama-server"),
os.path.join(engine_path, "bin", "llama-server"),
"/usr/local/bin/llama-server",
"/usr/bin/llama-server",
"llama-server"
]
for binary in candidates:
if binary != "llama-server" and not os.path.exists(binary):
continue
try:
res = subprocess.run([binary, "--version"], capture_output=True, text=True, timeout=2)
output = (res.stdout or "").strip() or (res.stderr or "").strip()
if output:
lines = output.splitlines()
ver = lines[0] if lines else "unknown"
return {"version_text": ver, "type": "binary"}
except Exception:
pass
res = subprocess.run([binary, "--version"], capture_output=True, text=True, timeout=2)
if res.returncode == 0:
lines = res.stdout.strip().splitlines()
ver = lines[0] if lines else "unknown"
return {"version_text": ver, "type": "binary"}
except Exception:
pass
return {"type": "unknown"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-Dk6KzQ7B.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-nR6_L3dg.css">
<script type="module" crossorigin src="/assets/index-oiIMDlga.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-WcZJ9RJs.css">
</head>
<body>
<div id="root"></div>
+211 -8
View File
@@ -1,12 +1,12 @@
import { useEffect, useState, useRef } from "react"
import { X, RefreshCw, Terminal, Cpu, Server, Shield, Power } from "lucide-react"
import { X, RefreshCw, Terminal, Cpu, Server, Shield, Power, Eye, EyeOff, Key, Download, AlertTriangle } from "lucide-react"
import { api, type Job, type UpdatesResp } from "@/lib/api"
import { cn } from "@/lib/utils"
interface SystemDrawerProps {
open: boolean
onClose: () => void
defaultTab?: "maintenance" | "logs"
defaultTab?: "maintenance" | "logs" | "settings"
}
const SERVICES = [
@@ -29,8 +29,23 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
const [selectedService, setSelectedService] = useState("llama-swap")
const [logs, setLogs] = useState("")
const [loadingLogs, setLoadingLogs] = useState(false)
const [logStatus, setLogStatus] = useState<string | null>(null)
const [restartingServices, setRestartingServices] = useState<Record<string, boolean>>({})
const [activeTab, setActiveTab] = useState<"maintenance" | "logs">("maintenance")
const [activeTab, setActiveTab] = useState<"maintenance" | "logs" | "settings">("maintenance")
const [checkingUpdates, setCheckingUpdates] = useState(false)
// Credentials State
const [sudoPasswordInput, setSudoPasswordInput] = useState("")
const [hfTokenInput, setHfTokenInput] = useState("")
const [showSudo, setShowSudo] = useState(false)
const [showHf, setShowHf] = useState(false)
useEffect(() => {
if (open) {
setSudoPasswordInput(localStorage.getItem("mc_sudo_password") || "")
setHfTokenInput(localStorage.getItem("mc_hf_token") || "")
}
}, [open])
useEffect(() => {
if (open && defaultTab) {
@@ -54,12 +69,16 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
function loadServiceLogs(service: string) {
setLoadingLogs(true)
api<{ ok: boolean; text: string; err?: string }>(`/api/maintenance/logs?service=${service}&lines=150`)
setLogStatus(null)
api<{ ok: boolean; text: string; err?: string; status?: string }>(`/api/maintenance/logs?service=${service}&lines=150`)
.then((res) => {
if (res.ok) {
setLogs(res.text)
} else {
setLogs(`Fehler beim Laden der Logs: ${res.err || "Unbekannter Fehler"}`)
if (res.status === "incorrect_password" || res.status === "password_required") {
setLogStatus(res.status)
}
}
})
.catch((e) => setLogs(`Fehler: ${e.message}`))
@@ -112,6 +131,33 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
}
}
async function triggerCheckUpdates() {
setCheckingUpdates(true)
try {
await api("/api/maintenance/check-updates", { method: "POST" })
loadJobs()
setActiveTab("maintenance")
} catch (e: any) {
alert(`Fehler bei der Update-Suche: ${e.message}`)
} finally {
setCheckingUpdates(false)
}
}
async function triggerModelUpgrade(repo: string, role: string) {
try {
await api("/api/models/install", {
method: "POST",
body: JSON.stringify({ repo, role })
})
alert(`Modell-Upgrade für '${role}' (${repo}) gestartet.`)
loadJobs()
setActiveTab("maintenance")
} catch (e: any) {
alert(`Fehler beim Starten des Modell-Upgrades: ${e.message}`)
}
}
async function triggerReboot() {
if (!confirm("Bist du sicher, dass du das gesamte Host-System neu starten willst?")) return
try {
@@ -210,15 +256,36 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
>
System-Logs
</button>
<button
onClick={() => setActiveTab("settings")}
className={cn(
"flex-1 py-3 text-xs font-semibold uppercase tracking-wider border-b-2 text-center transition-all",
activeTab === "settings"
? "border-primary text-primary"
: "border-transparent text-muted-foreground hover:text-foreground"
)}
>
Einstellungen
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6 space-y-6">
{activeTab === "maintenance" ? (
{activeTab === "maintenance" && (
<>
{/* Quick Actions */}
<div className="space-y-3">
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">Wartungsaktionen</h3>
<div className="flex items-center justify-between">
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">Wartungsaktionen</h3>
<button
onClick={triggerCheckUpdates}
disabled={checkingUpdates}
className="flex items-center gap-1 text-[10px] font-semibold text-primary hover:text-primary/80 transition-colors disabled:opacity-50"
>
<RefreshCw className={cn("h-3 w-3", checkingUpdates && "animate-spin")} />
Nach Updates suchen
</button>
</div>
<div className="grid grid-cols-2 gap-3">
<button
@@ -256,6 +323,33 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
</button>
</div>
{/* Modell Upgrades */}
{updates?.model_list && updates.model_list.length > 0 && (
<div className="space-y-3">
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">Verfügbare Modell-Upgrades</h3>
<div className="space-y-2">
{updates.model_list.map((m) => (
<div key={m.role} className="p-3 rounded-xl border border-border/60 bg-background/20 flex flex-col gap-2">
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-xs font-semibold">{m.title}</div>
<div className="text-[10px] font-mono text-muted-foreground">{m.repo}</div>
<div className="text-[10px] text-primary font-semibold uppercase mt-0.5">Rolle: {m.role}</div>
</div>
<button
onClick={() => triggerModelUpgrade(m.repo, m.role)}
className="flex items-center gap-1.5 text-[10px] font-semibold text-emerald-400 hover:text-emerald-300 border border-emerald-500/20 bg-emerald-500/5 hover:bg-emerald-500/10 px-2 py-1 rounded-lg transition-colors shrink-0"
>
<Download className="h-3.5 w-3.5" />
Upgrade
</button>
</div>
</div>
))}
</div>
</div>
)}
{/* Active Jobs */}
<div className="space-y-3">
<div className="flex items-center justify-between">
@@ -347,7 +441,9 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
</div>
</div>
</>
) : (
)}
{activeTab === "logs" && (
// Logs View
<div className="flex flex-col h-full space-y-4">
{/* Service Select & Restart */}
@@ -397,7 +493,23 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
ref={logContainerRef}
className="flex-1 p-4 overflow-y-auto text-[10px] font-mono text-cyan-300/90 whitespace-pre-wrap select-text leading-relaxed scrollbar-thin"
>
{loadingLogs && !logs ? (
{logStatus === "password_required" || logStatus === "incorrect_password" ? (
<div className="flex flex-col items-center justify-center p-6 text-center h-full space-y-3">
<AlertTriangle className="h-8 w-8 text-amber-400 animate-pulse animate-duration-1000" />
<div className="text-xs font-semibold text-amber-300">
{logStatus === "incorrect_password" ? "Falsches Sudo-Passwort hinterlegt." : "Sudo-Passwort für systemd-Dienste erforderlich."}
</div>
<p className="text-[10px] text-muted-foreground max-w-xs leading-normal">
Für das Auslesen der systemd-Logs von {selectedService} werden Root-Rechte benötigt. Hinterlege dein Passwort in den Einstellungen.
</p>
<button
onClick={() => setActiveTab("settings")}
className="px-3 py-1.5 text-[10px] font-semibold text-primary-foreground bg-primary hover:bg-primary/95 rounded-md transition-colors mt-2"
>
Sudo-Passwort eintragen
</button>
</div>
) : loadingLogs && !logs ? (
<span className="text-muted-foreground">Lade Logs...</span>
) : (
logs || <span className="text-muted-foreground">Keine Logeinträge vorhanden.</span>
@@ -406,6 +518,97 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
</div>
</div>
)}
{activeTab === "settings" && (
<div className="space-y-6">
<div className="space-y-2">
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">Zugangsdaten & Schlüssel</h3>
<p className="text-[10px] text-muted-foreground leading-normal">
Diese Daten werden ausschließlich lokal in deinem Browser (localStorage) gespeichert und bei Bedarf an das Backend übertragen.
</p>
</div>
{/* Sudo Passwort */}
<div className="space-y-2">
<label className="text-xs font-semibold flex items-center gap-1.5">
<Shield className="h-4 w-4 text-amber-400" />
Host Sudo-Passwort
</label>
<div className="relative">
<input
type={showSudo ? "text" : "password"}
value={sudoPasswordInput}
onChange={(e) => setSudoPasswordInput(e.target.value)}
placeholder="Sudo-Passwort für System-Operationen"
className="w-full h-10 pl-3 pr-10 text-xs bg-background/40 border border-border/60 rounded-lg outline-none focus:border-primary transition-colors text-foreground"
/>
<button
type="button"
onClick={() => setShowSudo(!showSudo)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
>
{showSudo ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
<p className="text-[9px] text-muted-foreground leading-normal">
Wird benötigt für systemd-Dienste (Llama Swap logs/restart), OS-Update (apt) und Reboot.
</p>
</div>
{/* HuggingFace Token */}
<div className="space-y-2">
<label className="text-xs font-semibold flex items-center gap-1.5">
<Key className="h-4 w-4 text-violet-400" />
HuggingFace API Token
</label>
<div className="relative">
<input
type={showHf ? "text" : "password"}
value={hfTokenInput}
onChange={(e) => setHfTokenInput(e.target.value)}
placeholder="hf_..."
className="w-full h-10 pl-3 pr-10 text-xs bg-background/40 border border-border/60 rounded-lg outline-none focus:border-primary transition-colors text-foreground"
/>
<button
type="button"
onClick={() => setShowHf(!showHf)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
>
{showHf ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
<p className="text-[9px] text-muted-foreground leading-normal">
Erlaubt das Herunterladen von geschützten oder Gate-restricted Modellen direkt über Mission Control.
</p>
</div>
{/* Buttons */}
<div className="flex gap-3 pt-2">
<button
onClick={() => {
localStorage.setItem("mc_sudo_password", sudoPasswordInput);
localStorage.setItem("mc_hf_token", hfTokenInput);
alert("Einstellungen erfolgreich lokal gespeichert.");
}}
className="flex-1 h-9 flex items-center justify-center text-xs font-semibold text-primary-foreground bg-primary hover:bg-primary/90 rounded-lg transition-colors"
>
Speichern
</button>
<button
onClick={() => {
setSudoPasswordInput("");
setHfTokenInput("");
localStorage.removeItem("mc_sudo_password");
localStorage.removeItem("mc_hf_token");
alert("Zugangsdaten gelöscht.");
}}
className="h-9 px-4 flex items-center justify-center text-xs font-semibold text-muted-foreground border border-border/60 bg-background/20 hover:bg-accent rounded-lg transition-colors"
>
Zurücksetzen
</button>
</div>
</div>
)}
</div>
</div>
</>
+45 -1
View File
@@ -1,9 +1,53 @@
// Schmaler Fetch-Helfer gegen das MC-2-Backend (/api/*).
export async function api<T = unknown>(path: string, init?: RequestInit): Promise<T> {
const headers = {
"Content-Type": "application/json",
...init?.headers,
} as Record<string, string>
const sudoPassword = localStorage.getItem("mc_sudo_password")
const hfToken = localStorage.getItem("mc_hf_token")
if (sudoPassword) {
headers["X-Sudo-Password"] = sudoPassword
}
let body = init?.body
const method = init?.method?.toUpperCase() || "GET"
if (method === "POST") {
if (typeof body === "string") {
try {
const data = JSON.parse(body)
let changed = false
if (sudoPassword && !("sudo_password" in data)) {
data["sudo_password"] = sudoPassword
changed = true
}
if (hfToken && !("hf_token" in data)) {
data["hf_token"] = hfToken
changed = true
}
if (changed) {
body = JSON.stringify(data)
}
} catch (e) {
// ignore JSON parse errors
}
} else if (!body) {
const data: Record<string, any> = {}
if (sudoPassword) data["sudo_password"] = sudoPassword
if (hfToken) data["hf_token"] = hfToken
if (Object.keys(data).length > 0) {
body = JSON.stringify(data)
}
}
}
const res = await fetch(path, {
headers: { "Content-Type": "application/json" },
...init,
headers,
body,
})
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
return res.json() as Promise<T>
+448 -222
View File
@@ -1,9 +1,10 @@
import { useState, useEffect } from "react"
import { BookOpen, Layers, Brain, Terminal, Code, RefreshCw, Cpu, Star } from "lucide-react"
import { BookOpen, Layers, Brain, Terminal, Code, RefreshCw, Cpu, Star, Compass, Wrench, Shield, ArrowRight } from "lucide-react"
import { api, type Health } from "@/lib/api"
import { cn } from "@/lib/utils"
export function GuideView() {
const [mainTab, setMainTab] = useState<"connect" | "concepts">("connect")
const [activeTab, setActiveTab] = useState<"roocode" | "cursor" | "opencode">("roocode")
const [health, setHealth] = useState<Health | null>(null)
@@ -42,267 +43,492 @@ export function GuideView() {
</p>
</div>
{/* Connection HUD Panel */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/10 flex flex-col sm:flex-row justify-between sm:items-center gap-4">
<div className="flex items-center gap-3">
<span className={cn(
"h-3 w-3 rounded-full ring-2 ring-black/40",
testResult === "success" && "bg-emerald-500 animate-pulse",
testResult === "partial" && "bg-amber-500",
testResult === "fail" && "bg-red-500",
!testResult && "bg-muted"
)} />
<div>
<div className="text-xs font-bold uppercase tracking-wider text-foreground">Lokaler Verbindungs-Check</div>
<div className="text-[10px] text-muted-foreground mt-0.5 font-mono">
{testResult === "success" && `Erfolgreich! Dein PC hat Zugriff auf das Box-Gateway (v${health?.version || ""}).`}
{testResult === "partial" && "Gateway erreichbar, aber die llama-cpp-Engine ist offline."}
{testResult === "fail" && "Verbindung fehlgeschlagen. Ist die Box im selben LAN-Netzwerk?"}
{!testResult && "Verbindung wird geprüft..."}
</div>
</div>
</div>
{/* Main Mode Tabs */}
<div className="flex gap-4 border-b border-border/40 pb-px">
<button
onClick={checkConnection}
disabled={testing}
className="h-8 px-3 flex items-center gap-1.5 text-xs font-semibold rounded-lg border border-border/60 bg-background/20 hover:border-primary/50 transition-colors disabled:opacity-50 cursor-pointer self-start sm:self-auto shrink-0"
onClick={() => setMainTab("connect")}
className={cn(
"pb-3 text-xs font-bold uppercase tracking-wider border-b-2 px-1 transition-all",
mainTab === "connect"
? "border-primary text-primary"
: "border-transparent text-muted-foreground hover:text-foreground"
)}
>
<RefreshCw className={cn("h-3.5 w-3.5", testing && "animate-spin")} />
<span>Testen</span>
Editor-Anbindung
</button>
<button
onClick={() => setMainTab("concepts")}
className={cn(
"pb-3 text-xs font-bold uppercase tracking-wider border-b-2 px-1 transition-all",
mainTab === "concepts"
? "border-primary text-primary"
: "border-transparent text-muted-foreground hover:text-foreground"
)}
>
KI-Wissensdatenbank (Juni 2026)
</button>
</div>
{/* Conceptual Explanation Grid */}
<div className="space-y-3">
<div className="flex items-center gap-2 px-1">
<BookOpen className="h-4.5 w-4.5 text-primary" />
<h2 className="text-xs font-bold uppercase tracking-wider text-foreground">Wie funktioniert mein Stack?</h2>
</div>
<div className="grid gap-4 sm:grid-cols-3">
{/* Card 1: Dashboard */}
<div className="p-4 rounded-xl border border-border/60 bg-card/20 space-y-2">
<div className="flex items-center gap-1.5">
<Cpu className="h-4 w-4 text-cyan-400" />
<h3 className="text-xs font-bold text-foreground">1. Die Zentrale</h3>
{mainTab === "connect" ? (
<>
{/* Connection HUD Panel */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/10 flex flex-col sm:flex-row justify-between sm:items-center gap-4">
<div className="flex items-center gap-3">
<span className={cn(
"h-3 w-3 rounded-full ring-2 ring-black/40",
testResult === "success" && "bg-emerald-500 animate-pulse",
testResult === "partial" && "bg-amber-500",
testResult === "fail" && "bg-red-500",
!testResult && "bg-muted"
)} />
<div>
<div className="text-xs font-bold uppercase tracking-wider text-foreground">Lokaler Verbindungs-Check</div>
<div className="text-[10px] text-muted-foreground mt-0.5 font-mono">
{testResult === "success" && `Erfolgreich! Dein PC hat Zugriff auf das Box-Gateway (v${health?.version || ""}).`}
{testResult === "partial" && "Gateway erreichbar, aber die llama-cpp-Engine ist offline."}
{testResult === "fail" && "Verbindung fehlgeschlagen. Ist die Box im selben LAN-Netzwerk?"}
{!testResult && "Verbindung wird geprüft..."}
</div>
</div>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Dein Dashboard. Hier siehst du die CPU-/RAM- und GPU-Last der Box und siehst sofort, ob Updates anstehen.
</p>
<button
onClick={checkConnection}
disabled={testing}
className="h-8 px-3 flex items-center gap-1.5 text-xs font-semibold rounded-lg border border-border/60 bg-background/20 hover:border-primary/50 transition-colors disabled:opacity-50 cursor-pointer self-start sm:self-auto shrink-0"
>
<RefreshCw className={cn("h-3.5 w-3.5", testing && "animate-spin")} />
<span>Testen</span>
</button>
</div>
{/* Card 2: Llama Swap */}
<div className="p-4 rounded-xl border border-border/60 bg-card/20 space-y-2">
<div className="flex items-center gap-1.5">
<Layers className="h-4 w-4 text-violet-400" />
<h3 className="text-xs font-bold text-foreground">2. Modell-Zentrale</h3>
{/* Conceptual Explanation Grid */}
<div className="space-y-3">
<div className="flex items-center gap-2 px-1">
<BookOpen className="h-4.5 w-4.5 text-primary" />
<h2 className="text-xs font-bold uppercase tracking-wider text-foreground">Wie funktioniert mein Stack?</h2>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Deine GGUF-Datenbank. Gesteuert von <strong>llama-swap</strong>. Lädt dein schnelles Alltags-Hirn (`fast`) oder dein schweres Logik-Hirn (`heavy`) vollautomatisch im VRAM.
</p>
</div>
{/* Card 3: Memory */}
<div className="p-4 rounded-xl border border-border/60 bg-card/20 space-y-2">
<div className="flex items-center gap-1.5">
<Brain className="h-4 w-4 text-indigo-400" />
<h3 className="text-xs font-bold text-foreground">3. Das Gedächtnis</h3>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Dein geteiltes Langzeitgedächtnis (Memory-Pool). Hier merkt sich dein Agent Regeln, Projekt-Details und Vorlieben.
</p>
</div>
</div>
</div>
{/* Editor Integration Guides */}
<div className="space-y-4">
<div className="flex items-center gap-2 px-1">
<Code className="h-4.5 w-4.5 text-primary" />
<h2 className="text-xs font-bold uppercase tracking-wider text-foreground">Vibe Coding auf dem PC einrichten</h2>
</div>
{/* Editor Selector Tabs */}
<div className="flex gap-1.5 bg-card/20 p-1 border border-border/40 rounded-xl max-w-fit">
<button
onClick={() => setActiveTab("roocode")}
className={cn(
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide uppercase transition-all cursor-pointer flex items-center gap-1.5",
activeTab === "roocode"
? "bg-primary text-primary-foreground shadow-md shadow-primary/10"
: "text-muted-foreground hover:text-foreground"
)}
>
<Star className="h-3.5 w-3.5 fill-amber-400/20" />
Roo Code (VS Code)
</button>
<button
onClick={() => setActiveTab("cursor")}
className={cn(
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide uppercase transition-all cursor-pointer",
activeTab === "cursor"
? "bg-primary text-primary-foreground shadow-md shadow-primary/10"
: "text-muted-foreground hover:text-foreground"
)}
>
Cursor IDE
</button>
<button
onClick={() => setActiveTab("opencode")}
className={cn(
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide uppercase transition-all cursor-pointer",
activeTab === "opencode"
? "bg-primary text-primary-foreground shadow-md shadow-primary/10"
: "text-muted-foreground hover:text-foreground"
)}
>
OpenCode Desktop
</button>
</div>
{/* Guide Content */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-6 space-y-4 shadow-lg shadow-black/10">
{activeTab === "roocode" && (
<div className="space-y-4 text-xs leading-relaxed text-muted-foreground">
<div className="space-y-1">
<h3 className="text-sm font-bold text-foreground">Roo Code Kopplung (Empfohlene Open-Source-Erweiterung)</h3>
<p>Roo Code ist die beliebteste und flexibelste Vibe-Coding-Erweiterung für VS Code im Jahr 2026. Sie ermöglicht vollen Zugriff auf das Terminal und das MCP-Gedächtnis.</p>
<div className="grid gap-4 sm:grid-cols-3">
{/* Card 1: Dashboard */}
<div className="p-4 rounded-xl border border-border/60 bg-card/20 space-y-2">
<div className="flex items-center gap-1.5">
<Cpu className="h-4 w-4 text-cyan-400" />
<h3 className="text-xs font-bold text-foreground">1. Die Zentrale</h3>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Dein Dashboard. Hier siehst du die CPU-/RAM- und GPU-Last der Box und siehst sofort, ob Updates anstehen.
</p>
</div>
<div className="space-y-3.5 border-t border-border/20 pt-4">
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">1</span>
Roo Code installieren
</div>
<p className="pl-6">Suche in VS Code nach der Erweiterung <strong>Roo Code</strong> und installiere sie.</p>
{/* Card 2: Llama Swap */}
<div className="p-4 rounded-xl border border-border/60 bg-card/20 space-y-2">
<div className="flex items-center gap-1.5">
<Layers className="h-4 w-4 text-violet-400" />
<h3 className="text-xs font-bold text-foreground">2. Modell-Zentrale</h3>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Deine GGUF-Datenbank. Gesteuert von <strong>llama-swap</strong>. Lädt dein schnelles Alltags-Hirn (`fast`) oder dein schweres Logik-Hirn (`heavy`) vollautomatisch im VRAM.
</p>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">2</span>
API-Anbindung konfigurieren
{/* Card 3: Memory */}
<div className="p-4 rounded-xl border border-border/60 bg-card/20 space-y-2">
<div className="flex items-center gap-1.5">
<Brain className="h-4 w-4 text-indigo-400" />
<h3 className="text-xs font-bold text-foreground">3. Das Gedächtnis</h3>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Dein geteiltes Langzeitgedächtnis (Memory-Pool). Hier merkt sich dein Agent Regeln, Projekt-Details und Vorlieben.
</p>
</div>
</div>
</div>
{/* Editor Integration Guides */}
<div className="space-y-4">
<div className="flex items-center gap-2 px-1">
<Code className="h-4.5 w-4.5 text-primary" />
<h2 className="text-xs font-bold uppercase tracking-wider text-foreground">Vibe Coding auf dem PC einrichten</h2>
</div>
{/* Editor Selector Tabs */}
<div className="flex gap-1.5 bg-card/20 p-1 border border-border/40 rounded-xl max-w-fit">
<button
onClick={() => setActiveTab("roocode")}
className={cn(
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide uppercase transition-all cursor-pointer flex items-center gap-1.5",
activeTab === "roocode"
? "bg-primary text-primary-foreground shadow-md shadow-primary/10"
: "text-muted-foreground hover:text-foreground"
)}
>
<Star className="h-3.5 w-3.5 fill-amber-400/20" />
Roo Code (VS Code)
</button>
<button
onClick={() => setActiveTab("cursor")}
className={cn(
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide uppercase transition-all cursor-pointer",
activeTab === "cursor"
? "bg-primary text-primary-foreground shadow-md shadow-primary/10"
: "text-muted-foreground hover:text-foreground"
)}
>
Cursor IDE
</button>
<button
onClick={() => setActiveTab("opencode")}
className={cn(
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide uppercase transition-all cursor-pointer",
activeTab === "opencode"
? "bg-primary text-primary-foreground shadow-md shadow-primary/10"
: "text-muted-foreground hover:text-foreground"
)}
>
OpenCode Desktop
</button>
</div>
{/* Guide Content */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-6 space-y-4 shadow-lg shadow-black/10">
{activeTab === "roocode" && (
<div className="space-y-4 text-xs leading-relaxed text-muted-foreground">
<div className="space-y-1">
<h3 className="text-sm font-bold text-foreground">Roo Code Kopplung (Empfohlene Open-Source-Erweiterung)</h3>
<p>Roo Code ist die beliebteste und flexibelste Vibe-Coding-Erweiterung für VS Code im Jahr 2026. Sie ermöglicht vollen Zugriff auf das Terminal und das MCP-Gedächtnis.</p>
</div>
<p className="pl-6">Klicke auf das Roo-Code-Symbol in der Sidebar, öffne die Einstellungen (Zahnrad) und stelle folgendes ein:</p>
<div className="pl-6 pt-1">
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1 text-foreground">
<div><span className="text-muted-foreground/60">API Provider:</span> OpenAI Compatible</div>
<div><span className="text-muted-foreground/60">Base URL:</span> http://{currentHost}:9001/v1</div>
<div><span className="text-muted-foreground/60">API Key:</span> <span className="italic text-muted-foreground/50">beliebig (z.B. "local")</span></div>
<div><span className="text-muted-foreground/60">Model ID:</span> auto</div>
<div className="space-y-3.5 border-t border-border/20 pt-4">
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">1</span>
Roo Code installieren
</div>
<p className="pl-6">Suche in VS Code nach der Erweiterung <strong>Roo Code</strong> und installiere sie.</p>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">2</span>
API-Anbindung konfigurieren
</div>
<p className="pl-6">Klicke auf das Roo-Code-Symbol in der Sidebar, öffne die Einstellungen (Zahnrad) und stelle folgendes ein:</p>
<div className="pl-6 pt-1">
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1 text-foreground">
<div><span className="text-muted-foreground/60">API Provider:</span> OpenAI Compatible</div>
<div><span className="text-muted-foreground/60">Base URL:</span> http://{currentHost}:9001/v1</div>
<div><span className="text-muted-foreground/60">API Key:</span> <span className="italic text-muted-foreground/50">beliebig (z.B. "local")</span></div>
<div><span className="text-muted-foreground/60">Model ID:</span> auto</div>
</div>
</div>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">3</span>
MCP Gedächtnis verknüpfen (Optional, aber empfohlen)
</div>
<p className="pl-6">Damit Roo Code auf deinen <strong>Gedächtnis-Pool</strong> zugreifen kann, kopiere die MCP-Konfiguration aus dem Reiter <strong>Verbinden</strong> und füge sie in deine lokale Roo-Code-Konfigurationsdatei ein.</p>
</div>
</div>
</div>
)}
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">3</span>
MCP Gedächtnis verknüpfen (Optional, aber empfohlen)
{activeTab === "cursor" && (
<div className="space-y-4 text-xs leading-relaxed text-muted-foreground">
<div className="space-y-1">
<h3 className="text-sm font-bold text-foreground">Cursor IDE Kopplung (Proprietäre All-in-One IDE)</h3>
<p>Cursor ist ein polierter, extrem schneller VS-Code-Fork mit hervorragenden, tief integrierten Autovervollständigungen (Tab-Completions).</p>
</div>
<p className="pl-6">Damit Roo Code auf deinen <strong>Gedächtnis-Pool</strong> zugreifen kann, kopiere die MCP-Konfiguration aus dem Reiter <strong>Verbinden</strong> und füge sie in deine lokale Roo-Code-Konfigurationsdatei ein.</p>
</div>
</div>
</div>
)}
{activeTab === "cursor" && (
<div className="space-y-4 text-xs leading-relaxed text-muted-foreground">
<div className="space-y-1">
<h3 className="text-sm font-bold text-foreground">Cursor IDE Kopplung (Proprietäre All-in-One IDE)</h3>
<p>Cursor ist ein polierter, extrem schneller VS-Code-Fork mit hervorragenden, tief integrierten Autovervollständigungen (Tab-Completions).</p>
</div>
<div className="space-y-3.5 border-t border-border/20 pt-4">
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">1</span>
Einstellungen öffnen
</div>
<p className="pl-6">Öffne Cursor, klicke oben rechts auf das Zahnrad (Settings) und navigiere zu <strong>Models</strong>.</p>
</div>
<div className="space-y-3.5 border-t border-border/20 pt-4">
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">1</span>
Einstellungen öffnen
</div>
<p className="pl-6">Öffne Cursor, klicke oben rechts auf das Zahnrad (Settings) und navigiere zu <strong>Models</strong>.</p>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">2</span>
OpenAI API überschreiben
</div>
<p className="pl-6">Deaktiviere die Standard-Cloudmodelle, klappe den Bereich <strong>OpenAI API</strong> auf und konfiguriere:</p>
<div className="pl-6 pt-1">
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1 text-foreground">
<div><span className="text-muted-foreground/60">Override Base URL:</span> http://{currentHost}:9001/v1</div>
<div><span className="text-muted-foreground/60">API Key:</span> <span className="italic text-muted-foreground/50">beliebig (z.B. "local")</span></div>
</div>
</div>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">2</span>
OpenAI API überschreiben
</div>
<p className="pl-6">Deaktiviere die Standard-Cloudmodelle, klappe den Bereich <strong>OpenAI API</strong> auf und konfiguriere:</p>
<div className="pl-6 pt-1">
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1 text-foreground">
<div><span className="text-muted-foreground/60">Override Base URL:</span> http://{currentHost}:9001/v1</div>
<div><span className="text-muted-foreground/60">API Key:</span> <span className="italic text-muted-foreground/50">beliebig (z.B. "local")</span></div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">3</span>
Modell hinzufügen
</div>
<p className="pl-6">Trage in der Modell-Liste ein neues Modell mit dem Namen <strong>auto</strong> ein und wähle es als aktives Modell aus. Cursor leitet ab jetzt alle deine Anfragen an die Box weiter.</p>
</div>
</div>
</div>
)}
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">3</span>
Modell hinzufügen
{activeTab === "opencode" && (
<div className="space-y-4 text-xs leading-relaxed text-muted-foreground">
<div className="space-y-1">
<h3 className="text-sm font-bold text-foreground">OpenCode Desktop Kopplung (Open-Source Vibe-Coding-App)</h3>
<p>OpenCode ist eine standalone Desktop-App für ein ablenkungsfreies Coden über natürliche Sprache. Es trennt den KI-Prozess von deinem Editor.</p>
</div>
<p className="pl-6">Trage in der Modell-Liste ein neues Modell mit dem Namen <strong>auto</strong> ein und wähle es als aktives Modell aus. Cursor leitet ab jetzt alle deine Anfragen an die Box weiter.</p>
</div>
</div>
</div>
)}
{activeTab === "opencode" && (
<div className="space-y-4 text-xs leading-relaxed text-muted-foreground">
<div className="space-y-1">
<h3 className="text-sm font-bold text-foreground">OpenCode Desktop Kopplung (Open-Source Vibe-Coding-App)</h3>
<p>OpenCode ist eine standalone Desktop-App für ein ablenkungsfreies Coden über natürliche Sprache. Es trennt den KI-Prozess von deinem Editor.</p>
</div>
<div className="space-y-3.5 border-t border-border/20 pt-4">
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">1</span>
OpenCode Desktop herunterladen
</div>
<p className="pl-6">Lade die Desktop-Anwendung von der offiziellen Website (opencode.ai) herunter und starte sie.</p>
</div>
<div className="space-y-3.5 border-t border-border/20 pt-4">
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">1</span>
OpenCode Desktop herunterladen
</div>
<p className="pl-6">Lade die Desktop-Anwendung von der offiziellen Website (opencode.ai) herunter und starte sie.</p>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">2</span>
Endpunkt auf Box-Gateway setzen
</div>
<p className="pl-6">Gehe in den Bereich Einstellungen Provider, deaktiviere die Cloud-Voreinstellungen und trage deinen lokalen Server ein:</p>
<div className="pl-6 pt-1">
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1 text-foreground">
<div><span className="text-muted-foreground/60">Base URL:</span> http://{currentHost}:9001/v1</div>
<div><span className="text-muted-foreground/60">Model:</span> auto</div>
</div>
</div>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">2</span>
Endpunkt auf Box-Gateway setzen
</div>
<p className="pl-6">Gehe in den Bereich Einstellungen Provider, deaktiviere die Cloud-Voreinstellungen und trage deinen lokalen Server ein:</p>
<div className="pl-6 pt-1">
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1 text-foreground">
<div><span className="text-muted-foreground/60">Base URL:</span> http://{currentHost}:9001/v1</div>
<div><span className="text-muted-foreground/60">Model:</span> auto</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">3</span>
Erster Vibe-Coding Test
</div>
<p className="pl-6">Starte eine neue Session und teste die Verbindung mit einem einfachen Prompt, z. B. *"Erstelle ein einfaches Skript, das die Fibonaccizahlen berechnet"*. Das Box-Gateway tauscht das Modell im Hintergrund vollautomatisch aus.</p>
</div>
</div>
</div>
)}
</div>
</div>
<div className="space-y-1">
<div className="font-semibold text-foreground flex items-center gap-1.5">
<span className="h-4.5 w-4.5 rounded-full bg-primary/20 text-primary flex items-center justify-center font-mono text-[10px]">3</span>
Erster Vibe-Coding Test
</div>
<p className="pl-6">Starte eine neue Session und teste die Verbindung mit einem einfachen Prompt, z. B. *"Erstelle ein einfaches Skript, das die Fibonaccizahlen berechnet"*. Das Box-Gateway tauscht das Modell im Hintergrund vollautomatisch aus.</p>
{/* Diagnostic Tips Card */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 space-y-3 shadow-lg shadow-black/10">
<div className="flex items-center gap-2">
<Terminal className="h-4.5 w-4.5 text-primary" />
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">Was tun, wenn das Coden hakt?</h3>
</div>
<ul className="text-[11px] text-muted-foreground space-y-2 list-disc pl-4 leading-relaxed">
<li><strong>Keine Verbindung?</strong> Überprüfe im Live-Verbindungsprüfer oben, ob die Box-IP erreichbar ist. Stelle sicher, dass dein lokaler PC im selben WLAN/LAN-Netzwerk wie die Box eingeloggt ist.</li>
<li><strong>Modell antwortet nicht?</strong> Schaue unter <strong>Diagnose</strong>, ob der Dienst `llama-swap` aktiv (grün) is. Wenn nicht, klicke daneben auf <strong>Restart</strong>.</li>
<li><strong>Hermes Agent reagiert merkwürdig?</strong> Starte in der Hermes WebUI einfach eine frische Session (neuen Chat). Ältere Chats sammeln Kontext-Müll an.</li>
</ul>
</div>
</>
) : (
/* KI-Wissensdatenbank Concepts Tab */
<div className="space-y-6">
{/* Introduction */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/10 flex items-start gap-4">
<Compass className="h-8 w-8 text-primary shrink-0 mt-0.5" />
<div className="space-y-1">
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">Entwickler-Guide: Modernes Agentic Coding (2026)</h3>
<p className="text-xs text-muted-foreground leading-normal">
Willkommen im Wissenszentrum für dein Mission Control 2 Setup. Hier erfährst du, wie die verschiedenen Technologien (MoE, MCP, Skills, Hermes) zusammenarbeiten und wie du das Maximum aus deinen AI-Prozessabläufen herausholst.
</p>
</div>
</div>
{/* Concepts Grid */}
<div className="grid gap-6 md:grid-cols-2">
{/* MoE Card */}
<div className="rounded-2xl border border-border/60 bg-card/45 p-6 shadow-lg shadow-black/10 space-y-4">
<div className="flex items-center gap-2 border-b border-border/20 pb-3">
<Layers className="h-5 w-5 text-cyan-400" />
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">1. Mixture of Experts (MoE)</h3>
<span className="text-[9px] text-cyan-400 font-mono">Effizienz durch Spezialisierung</span>
</div>
</div>
<div className="text-xs text-muted-foreground space-y-2 leading-normal">
<p>
<strong>Was ist das?</strong> Bei traditionellen LLMs wird für jedes Wort das gesamte neuronale Netz aktiviert. Bei MoE besteht das Modell aus mehreren spezialisierten Teilnetzwerken (den <em>Experts</em>). Ein intelligenter <em>Router</em> entscheidet pro Token (Wortteil), welche Experten zur Berechnung herangezogen werden.
</p>
<p>
<strong>Warum in MC2?</strong> So können extrem leistungsstarke Modelle (wie DeepSeek-V3, Mixtral oder Command R+) mit wesentlich geringeren Hardwarekosten ausgeführt werden. Es wird nur ein Bruchteil der Parameter geladen und aktiv berechnet, was Speicherplatz spart und die Inferenz beschleunigt.
</p>
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] text-foreground">
<span className="text-cyan-400">Vorteil:</span> GPT-4-Klasse Performance bei einem Bruchteil der aktiven VRAM-Last auf deinem Homelab!
</div>
</div>
</div>
)}
</div>
</div>
{/* Diagnostic Tips Card */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 space-y-3 shadow-lg shadow-black/10">
<div className="flex items-center gap-2">
<Terminal className="h-4.5 w-4.5 text-primary" />
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">Was tun, wenn das Coden hakt?</h3>
</div>
{/* MCP Card */}
<div className="rounded-2xl border border-border/60 bg-card/45 p-6 shadow-lg shadow-black/10 space-y-4">
<div className="flex items-center gap-2 border-b border-border/20 pb-3">
<Compass className="h-5 w-5 text-violet-400" />
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">2. Model Context Protocol (MCP)</h3>
<span className="text-[9px] text-violet-400 font-mono">Standardisierte Agenten-Tools</span>
</div>
</div>
<div className="text-xs text-muted-foreground space-y-2 leading-normal">
<p>
<strong>Was ist das?</strong> MCP ist ein offenes Protokoll (initiiert von Anthropic), das festlegt, wie ein KI-Client (z.B. Roo Code auf deinem PC) mit externen Datenquellen und Tools kommuniziert. Es funktioniert wie ein USB-Standard für KI.
</p>
<p>
<strong>Warum in MC2?</strong> MCP trennt den AI-Kern von der Umgebung. Statt für jeden Editor eigene Tools zu schreiben, binden deine Agenten (Roo Code, Hermes) einfach MCP-Server an. Diese Server können Dateien lesen, Websuchen durchführen, Git bedienen oder mit deiner App interagieren.
</p>
<div className="p-3 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] text-foreground">
<span className="text-violet-400">Prinzip:</span> Agent MCP Gateway Lokale Tools (Dateien, Terminal, Web)
</div>
</div>
</div>
<ul className="text-[11px] text-muted-foreground space-y-2 list-disc pl-4 leading-relaxed">
<li><strong>Keine Verbindung?</strong> Überprüfe im Live-Verbindungsprüfer oben, ob die Box-IP erreichbar ist. Stelle sicher, dass dein lokaler PC im selben WLAN/LAN-Netzwerk wie die Box eingeloggt ist.</li>
<li><strong>Modell antwortet nicht?</strong> Schaue unter <strong>Diagnose</strong>, ob der Dienst `llama-swap` aktiv (grün) ist. Wenn nicht, klicke daneben auf <strong>Restart</strong>.</li>
<li><strong>Hermes Agent reagiert merkwürdig?</strong> Starte in der Hermes WebUI einfach eine frische Session (neuen Chat). Ältere Chats sammeln Kontext-Müll an.</li>
</ul>
</div>
{/* Skills Card */}
<div className="rounded-2xl border border-border/60 bg-card/45 p-6 shadow-lg shadow-black/10 space-y-4">
<div className="flex items-center gap-2 border-b border-border/20 pb-3">
<Brain className="h-5 w-5 text-emerald-400" />
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">3. Agent Skills</h3>
<span className="text-[9px] text-emerald-400 font-mono">Modulbasierte Fähigkeiten</span>
</div>
</div>
<div className="text-xs text-muted-foreground space-y-2 leading-normal">
<p>
<strong>Was ist das?</strong> Ein Skill ist ein Verzeichnis mit standardisierten Anweisungen, Scripten und Beispielen, das deine Agenten für spezifische Aufgaben trainiert (z.B. Test-Driven Development, Code-Vereinfachung, API-Design).
</p>
<p>
<strong>Wie benutzt man sie?</strong> Lege einen Skill-Ordner unter <code>.agents/skills/&lt;name&gt;</code> in deinem Projekt an. Das Herzstück ist die Datei <code>SKILL.md</code> mit folgendem Aufbau:
</p>
<pre className="p-2.5 bg-background/25 rounded-lg border border-border/30 font-mono text-[9px] text-foreground overflow-x-auto whitespace-pre">
{`---
name: tdd-pro
description: Drive development with strict TDD practices
---
# Instructions
...`}
</pre>
<p>
Der Agent scannt diese Ordner automatisch und erweitert seine Systembefehle bei passenden Workflows. Gute Quellen sind dein lokales Mission Control Repository oder GitHub.
</p>
</div>
</div>
{/* Hermes Card */}
<div className="rounded-2xl border border-border/60 bg-card/45 p-6 shadow-lg shadow-black/10 space-y-4">
<div className="flex items-center gap-2 border-b border-border/20 pb-3">
<Cpu className="h-5 w-5 text-indigo-400" />
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">4. Arbeiten mit Hermes</h3>
<span className="text-[9px] text-indigo-400 font-mono">Autonomer Box-Agent</span>
</div>
</div>
<div className="text-xs text-muted-foreground space-y-2 leading-normal">
<p>
<strong>Was ist das?</strong> Hermes ist der auf der Box installierte, autonome Hintergrund-Agent. Er verwaltet das Dateisystem und kann über REST (Port 8642) oder eine interaktive ChatUI (Port 8787) gesteuert werden.
</p>
<p>
<strong>Best Practices für Hermes:</strong>
</p>
<ul className="list-disc pl-4 space-y-1">
<li><strong>Chat-Kontext sauber halten:</strong> Starte regelmäßig neue Chats. Zu große Historien verlangsamen den Agenten und führen zu Halluzinationen.</li>
<li><strong>Gehirn festlegen:</strong> Konfiguriere im Gateway die Modell-Rolle <code>brain</code> für Hermes, damit er automatisch das passende Modell per Llama Swap lädt.</li>
<li><strong>Sandbox umgehen:</strong> Erweitere Hermes' System-Prompt (WebUI-Einstellungen), um Befehle über SSH an deinen Windows-PC zu leiten.</li>
</ul>
</div>
</div>
{/* Tools Best Practices */}
<div className="rounded-2xl border border-border/60 bg-card/45 p-6 shadow-lg shadow-black/10 space-y-4 md:col-span-2">
<div className="flex items-center gap-2 border-b border-border/20 pb-3">
<Wrench className="h-5 w-5 text-amber-400" />
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">5. Richtiges Arbeiten mit Tools (Dateien, Terminal, DevTools)</h3>
<span className="text-[9px] text-amber-400 font-mono">Fehler vermeiden &amp; Kosten senken</span>
</div>
</div>
<div className="grid md:grid-cols-3 gap-4 text-xs text-muted-foreground leading-normal">
<div className="space-y-1.5 p-3 bg-background/10 rounded-xl border border-border/30">
<h4 className="font-bold text-foreground flex items-center gap-1">
<Terminal className="h-3.5 w-3.5 text-primary" /> Terminal
</h4>
<p className="text-[11px]">
Verwende nur non-interaktive Befehle. Hänge bei langen Tasks (wie dev-Servern) ein <code>&amp;</code> an oder nutze die integrierte Job-Verwaltung. Verwende niemals interaktive Eingabeaufforderungen.
</p>
</div>
<div className="space-y-1.5 p-3 bg-background/10 rounded-xl border border-border/30">
<h4 className="font-bold text-foreground flex items-center gap-1">
<Code className="h-3.5 w-3.5 text-cyan-400" /> Dateimanager
</h4>
<p className="text-[11px]">
Überschreibe keine ganzen Dateien, wenn du nur eine Zeile ändern willst. Verwende gezielte Ersetzungs-Tools (wie <code>replace_file_content</code>). Das spart massiv Token-Kosten und beugt Fehlern vor.
</p>
</div>
<div className="space-y-1.5 p-3 bg-background/10 rounded-xl border border-border/30">
<h4 className="font-bold text-foreground flex items-center gap-1">
<Wrench className="h-3.5 w-3.5 text-violet-400" /> Browser DevTools
</h4>
<p className="text-[11px]">
Koppele deine Debug-Dienste mit dem Chrome-DevTools-Plugin. So kann der Agent Fehler in der Konsole live analysieren und das DOM verifizieren, anstatt blind zu raten.
</p>
</div>
</div>
</div>
{/* Autonomie Erklärung */}
<div className="rounded-2xl border border-border/60 bg-card/45 p-6 shadow-lg shadow-black/10 space-y-4 md:col-span-2">
<div className="flex items-center gap-2 border-b border-border/20 pb-3">
<Shield className="h-5 w-5 text-emerald-400" />
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-foreground">Wie autonom ist Mission Control 2 wirklich?</h3>
<span className="text-[9px] text-emerald-400 font-mono">Die Grenze zwischen Automatisierung und Kontrolle</span>
</div>
</div>
<div className="text-xs text-muted-foreground space-y-3 leading-normal">
<p>
Mission Control 2 ist als <strong>semi-autonomes Gateway</strong> konzipiert. Es besitzt die Fähigkeit, komplexe Workflows komplett selbstständig durchzuführen, unterliegt jedoch strikten Sicherheitsplanken:
</p>
<div className="grid sm:grid-cols-2 gap-4 pt-1">
<div className="space-y-1.5">
<h4 className="font-bold text-foreground flex items-center gap-1 text-[11px]">
<ArrowRight className="h-3 w-3 text-emerald-400" /> Was läuft vollautomatisch?
</h4>
<ul className="list-disc pl-4 space-y-1 text-[11px]">
<li>Modellaustausch (Routing) per llama-swap je nach Anfragetyp (z.B. Code vs. Reasoning).</li>
<li>Hintergrund-Synchronisation und Speicherung von Gedächtniseinträgen (Memory).</li>
<li>Modell-Installation und API-Key-Injektionen in Gateway-Verbindungen.</li>
</ul>
</div>
<div className="space-y-1.5">
<h4 className="font-bold text-foreground flex items-center gap-1 text-[11px]">
<ArrowRight className="h-3 w-3 text-amber-400" /> Wo ist menschliche Freigabe nötig?
</h4>
<ul className="list-disc pl-4 space-y-1 text-[11px]">
<li><strong>Systembefehle:</strong> Das Ausführen von Terminal-Kommandos auf deinem PC erfordert standardmäßig deine Bestätigung.</li>
<li><strong>Kritische Systemeingriffe:</strong> OS-Updates, Engine-Rebuilds und Host-Reboots müssen manuell über das Dashboard ausgelöst werden.</li>
<li><strong>Gedächtnis-Löschung:</strong> Permanentes Verwerfen von gesammelten Memorys wird von dir freigegeben.</li>
</ul>
</div>
</div>
<p className="text-[11px] bg-background/25 p-3 rounded-xl border border-border/30 mt-2">
<strong>Fazit:</strong> Der Stack erledigt die Kärrnerarbeit (Modelle tauschen, API-Adapter bereitstellen, Sandbox-Verbindungen herstellen) komplett im Hintergrund. Er agiert als dein persönlicher, treuer Copilot, ohne jemals ungefragt schädliche Operationen auf deinem Hauptsystem auszuführen.
</p>
</div>
</div>
</div>
</div>
)}
</div>
)
}