feat: integrate Guide-Tab (Anleitung) for local Vibe Coding and compile frontend

This commit is contained in:
Hitonabi
2026-06-25 22:36:58 +02:00
parent bd6aacf3ed
commit b08e867c1a
10 changed files with 674 additions and 344 deletions
+4 -1
View File
@@ -24,13 +24,16 @@ def agent_status() -> dict:
config_path = home / "config.yaml"
if config_path.exists():
try:
from ruamel.yaml import YAML
r_yaml = YAML()
with config_path.open("r", encoding="utf-8") as f:
cfg = yaml.load(f) or {}
cfg = r_yaml.load(f) or {}
if isinstance(cfg, dict):
brain_model = cfg.get("model", {}).get("model", "auto")
except Exception:
pass
return {
"gateway_url": HERMES_API_URL,
"webui_url": HERMES_WEBUI_URL,
+10 -3
View File
@@ -12,7 +12,7 @@ import re
import httpx
from ruamel.yaml.scalarstring import LiteralScalarString
from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, yaml
from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL
# Kanonische Rollen (vereinheitlicht ggü. v1: kein manager/reviewer mehr).
ROLE_IDS = {"vision", "coder", "reasoning", "agent", "scout"}
@@ -26,8 +26,11 @@ _QUANT_RE = re.compile(r"(Q\d_[A-Z0-9_]+|IQ\d_[A-Z0-9_]+|fp16|bf16)\.gguf", re.I
def read_config() -> dict:
if not CONFIG_PATH.exists():
return {"models": {}}
from ruamel.yaml import YAML
r_yaml = YAML()
r_yaml.preserve_quotes = True
with CONFIG_PATH.open("r", encoding="utf-8") as f:
data = yaml.load(f) or {}
data = r_yaml.load(f) or {}
if not data.get("models"):
data["models"] = {}
return data
@@ -143,8 +146,11 @@ def write_config(cfg: dict) -> None:
try:
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
tmp = CONFIG_PATH.with_name(CONFIG_PATH.name + ".tmp")
from ruamel.yaml import YAML
r_yaml = YAML()
r_yaml.preserve_quotes = True
with tmp.open("w", encoding="utf-8") as f:
yaml.dump(cfg, f)
r_yaml.dump(cfg, f)
os.replace(tmp, CONFIG_PATH)
except PermissionError as exc:
raise PermissionError(
@@ -153,6 +159,7 @@ def write_config(cfg: dict) -> None:
) from exc
def register_model(model_path: str, role: str | None = None, ctx: int = 8192,
ttl: int | None = None, mmproj_path: str | None = None,
jinja: bool = False) -> str:
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
@@ -6,8 +6,8 @@
<meta name="theme-color" content="#0d1117" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-CmIKN_Pq.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D5kA4UxS.css">
<script type="module" crossorigin src="/assets/index-DQSWM8o1.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-1l8zG4Un.css">
</head>
<body>
<div id="root"></div>
+3 -1
View File
@@ -8,6 +8,7 @@ import { SystemView } from "@/views/SystemView"
import { ConnectView } from "@/views/ConnectView"
import { MemoryView } from "@/views/MemoryView"
import { AgentView } from "@/views/AgentView"
import { GuideView } from "@/views/GuideView"
import { Placeholder } from "@/views/Placeholder"
import { SystemDrawer } from "@/components/SystemDrawer"
import { api, type Health, type UpdatesResp } from "@/lib/api"
@@ -191,7 +192,8 @@ export default function App() {
{view === "connect" && <ConnectView />}
{view === "memory" && <MemoryView />}
{view === "agent" && <AgentView />}
{!["dashboard", "models", "system", "connect", "memory", "agent"].includes(view) && (
{view === "guide" && <GuideView />}
{!["dashboard", "models", "system", "connect", "memory", "agent", "guide"].includes(view) && (
<Placeholder title={active.label} hint={active.hint} />
)}
</main>
+4 -1
View File
@@ -5,10 +5,11 @@ import {
Brain,
Plug,
Bot,
HelpCircle,
type LucideIcon,
} from "lucide-react"
export type ViewId = "dashboard" | "models" | "system" | "memory" | "connect" | "agent"
export type ViewId = "dashboard" | "models" | "system" | "memory" | "connect" | "agent" | "guide"
export interface NavItem {
id: ViewId
@@ -25,5 +26,7 @@ export const NAV: NavItem[] = [
{ id: "memory", label: "Gedächtnis", hint: "Geteiltes Memory verwalten", icon: Brain },
{ id: "connect", label: "Verbinden", hint: "IDE-/Agent-Configs erzeugen", icon: Plug },
{ id: "agent", label: "Hermes", hint: "Agent-Status & WebUI öffnen", icon: Bot },
{ id: "guide", label: "Anleitung", hint: "Einrichten & Vibe-Coding", icon: HelpCircle },
]
+305
View File
@@ -0,0 +1,305 @@
import { useState, useEffect } from "react"
import { BookOpen, Layers, Brain, Terminal, Code, RefreshCw, Cpu, Star } from "lucide-react"
import { api, type Health } from "@/lib/api"
import { cn } from "@/lib/utils"
export function GuideView() {
const [activeTab, setActiveTab] = useState<"roocode" | "cursor" | "opencode">("roocode")
const [health, setHealth] = useState<Health | null>(null)
const [testing, setTesting] = useState(false)
const [testResult, setTestResult] = useState<string | null>(null)
function checkConnection() {
setTesting(true)
api<Health>("/api/health")
.then((h) => {
setHealth(h)
setTestResult(h.engine_reachable ? "success" : "partial")
})
.catch(() => {
setHealth(null)
setTestResult("fail")
})
.finally(() => setTesting(false))
}
useEffect(() => {
checkConnection()
}, [])
return (
<div className="space-y-6">
{/* Title */}
<div>
<h1 className="text-2xl font-space font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text text-transparent">
Stack-Anleitung &amp; Vibe-Coding-Guide
</h1>
<p className="text-sm text-muted-foreground">
Einsteigerfreundliche Erklärungen zu deinem Stack und Schritt-für-Schritt-Anleitungen zur Anbindung deiner Editoren.
</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>
<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>
{/* 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>
</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>
{/* 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>
{/* 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>
<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://192.168.178.151: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>
)}
{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-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://192.168.178.151: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]">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>
)}
{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-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://192.168.178.151: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]">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>
{/* 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) 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>
</div>
)
}