Compare commits
4 Commits
6a8e55cc43
...
b08e867c1a
| Author | SHA1 | Date | |
|---|---|---|---|
| b08e867c1a | |||
| bd6aacf3ed | |||
| cb02ede5fb | |||
| d15744a812 |
@@ -51,7 +51,13 @@ if FRONTEND_DIST.exists():
|
|||||||
|
|
||||||
@app.get("/{full_path:path}")
|
@app.get("/{full_path:path}")
|
||||||
def spa(full_path: str):
|
def spa(full_path: str):
|
||||||
|
# Falls die Datei direkt in FRONTEND_DIST liegt (z.B. manifest.webmanifest, favicon.ico), liefere sie aus
|
||||||
|
target = FRONTEND_DIST / full_path
|
||||||
|
if target.is_file():
|
||||||
|
return FileResponse(target)
|
||||||
|
|
||||||
index = FRONTEND_DIST / "index.html"
|
index = FRONTEND_DIST / "index.html"
|
||||||
if index.exists():
|
if index.exists():
|
||||||
return FileResponse(index)
|
return FileResponse(index)
|
||||||
return {"detail": "frontend not built"}
|
return {"detail": "frontend not built"}
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,16 @@ def agent_status() -> dict:
|
|||||||
config_path = home / "config.yaml"
|
config_path = home / "config.yaml"
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
try:
|
try:
|
||||||
|
from ruamel.yaml import YAML
|
||||||
|
r_yaml = YAML()
|
||||||
with config_path.open("r", encoding="utf-8") as f:
|
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):
|
if isinstance(cfg, dict):
|
||||||
brain_model = cfg.get("model", {}).get("model", "auto")
|
brain_model = cfg.get("model", {}).get("model", "auto")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"gateway_url": HERMES_API_URL,
|
"gateway_url": HERMES_API_URL,
|
||||||
"webui_url": HERMES_WEBUI_URL,
|
"webui_url": HERMES_WEBUI_URL,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import re
|
|||||||
import httpx
|
import httpx
|
||||||
from ruamel.yaml.scalarstring import LiteralScalarString
|
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).
|
# Kanonische Rollen (vereinheitlicht ggü. v1: kein manager/reviewer mehr).
|
||||||
ROLE_IDS = {"vision", "coder", "reasoning", "agent", "scout"}
|
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:
|
def read_config() -> dict:
|
||||||
if not CONFIG_PATH.exists():
|
if not CONFIG_PATH.exists():
|
||||||
return {"models": {}}
|
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:
|
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"):
|
if not data.get("models"):
|
||||||
data["models"] = {}
|
data["models"] = {}
|
||||||
return data
|
return data
|
||||||
@@ -143,8 +146,11 @@ def write_config(cfg: dict) -> None:
|
|||||||
try:
|
try:
|
||||||
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
tmp = CONFIG_PATH.with_name(CONFIG_PATH.name + ".tmp")
|
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:
|
with tmp.open("w", encoding="utf-8") as f:
|
||||||
yaml.dump(cfg, f)
|
r_yaml.dump(cfg, f)
|
||||||
os.replace(tmp, CONFIG_PATH)
|
os.replace(tmp, CONFIG_PATH)
|
||||||
except PermissionError as exc:
|
except PermissionError as exc:
|
||||||
raise PermissionError(
|
raise PermissionError(
|
||||||
@@ -153,6 +159,7 @@ def write_config(cfg: dict) -> None:
|
|||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def register_model(model_path: str, role: str | None = None, ctx: int = 8192,
|
def register_model(model_path: str, role: str | None = None, ctx: int = 8192,
|
||||||
ttl: int | None = None, mmproj_path: str | None = None,
|
ttl: int | None = None, mmproj_path: str | None = None,
|
||||||
jinja: bool = False) -> str:
|
jinja: bool = False) -> str:
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ Environment=MC_CONFIG_PATH=/etc/llama-swap/config.yaml
|
|||||||
Environment=MC_MODELS_DIR=/srv/models
|
Environment=MC_MODELS_DIR=/srv/models
|
||||||
# Geteiltes Gedächtnis = die bestehende v1-DB (Kontinuität bis/über Cutover).
|
# Geteiltes Gedächtnis = die bestehende v1-DB (Kontinuität bis/über Cutover).
|
||||||
Environment=MC_MEMORY_DB=/srv/models/mission-control-memory.db
|
Environment=MC_MEMORY_DB=/srv/models/mission-control-memory.db
|
||||||
|
Environment=MC_ENGINE_UPDATE_CMD=/usr/local/bin/update-llamacpp
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
+1
File diff suppressed because one or more lines are too long
+345
File diff suppressed because one or more lines are too long
-335
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
<meta name="theme-color" content="#0d1117" />
|
<meta name="theme-color" content="#0d1117" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<title>Mission Control 2.0</title>
|
<title>Mission Control 2.0</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DpycPcE0.js"></script>
|
<script type="module" crossorigin src="/assets/index-DQSWM8o1.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-GX6Y3U0B.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-1l8zG4Un.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+10
-3
@@ -4,11 +4,11 @@ import { NAV, type ViewId } from "@/nav"
|
|||||||
import { CommandPalette } from "@/components/CommandPalette"
|
import { CommandPalette } from "@/components/CommandPalette"
|
||||||
import { DashboardView } from "@/views/DashboardView"
|
import { DashboardView } from "@/views/DashboardView"
|
||||||
import { ModelsView } from "@/views/ModelsView"
|
import { ModelsView } from "@/views/ModelsView"
|
||||||
import { RoutingView } from "@/views/RoutingView"
|
|
||||||
import { SystemView } from "@/views/SystemView"
|
import { SystemView } from "@/views/SystemView"
|
||||||
import { ConnectView } from "@/views/ConnectView"
|
import { ConnectView } from "@/views/ConnectView"
|
||||||
import { MemoryView } from "@/views/MemoryView"
|
import { MemoryView } from "@/views/MemoryView"
|
||||||
import { AgentView } from "@/views/AgentView"
|
import { AgentView } from "@/views/AgentView"
|
||||||
|
import { GuideView } from "@/views/GuideView"
|
||||||
import { Placeholder } from "@/views/Placeholder"
|
import { Placeholder } from "@/views/Placeholder"
|
||||||
import { SystemDrawer } from "@/components/SystemDrawer"
|
import { SystemDrawer } from "@/components/SystemDrawer"
|
||||||
import { api, type Health, type UpdatesResp } from "@/lib/api"
|
import { api, type Health, type UpdatesResp } from "@/lib/api"
|
||||||
@@ -41,6 +41,12 @@ export default function App() {
|
|||||||
localStorage.setItem("mc_theme", dark ? "dark" : "light")
|
localStorage.setItem("mc_theme", dark ? "dark" : "light")
|
||||||
}, [dark])
|
}, [dark])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleOpen = () => setDrawerOpen(true)
|
||||||
|
window.addEventListener("open-system-drawer", handleOpen)
|
||||||
|
return () => window.removeEventListener("open-system-drawer", handleOpen)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const active = NAV.find((n) => n.id === view)!
|
const active = NAV.find((n) => n.id === view)!
|
||||||
const totalUpdates = updates ? (updates.os + updates.engine + updates.models) : 0
|
const totalUpdates = updates ? (updates.os + updates.engine + updates.models) : 0
|
||||||
|
|
||||||
@@ -182,17 +188,18 @@ export default function App() {
|
|||||||
<main className="flex-1 overflow-y-auto p-6 scrollbar-thin">
|
<main className="flex-1 overflow-y-auto p-6 scrollbar-thin">
|
||||||
{view === "dashboard" && <DashboardView />}
|
{view === "dashboard" && <DashboardView />}
|
||||||
{view === "models" && <ModelsView />}
|
{view === "models" && <ModelsView />}
|
||||||
{view === "routing" && <RoutingView />}
|
|
||||||
{view === "system" && <SystemView />}
|
{view === "system" && <SystemView />}
|
||||||
{view === "connect" && <ConnectView />}
|
{view === "connect" && <ConnectView />}
|
||||||
{view === "memory" && <MemoryView />}
|
{view === "memory" && <MemoryView />}
|
||||||
{view === "agent" && <AgentView />}
|
{view === "agent" && <AgentView />}
|
||||||
{!["dashboard", "models", "routing", "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} />
|
<Placeholder title={active.label} hint={active.hint} />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -1,15 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Boxes,
|
Boxes,
|
||||||
Route,
|
|
||||||
Cpu,
|
Cpu,
|
||||||
Brain,
|
Brain,
|
||||||
Plug,
|
Plug,
|
||||||
Bot,
|
Bot,
|
||||||
|
HelpCircle,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
|
||||||
export type ViewId = "dashboard" | "models" | "routing" | "system" | "memory" | "connect" | "agent"
|
export type ViewId = "dashboard" | "models" | "system" | "memory" | "connect" | "agent" | "guide"
|
||||||
|
|
||||||
export interface NavItem {
|
export interface NavItem {
|
||||||
id: ViewId
|
id: ViewId
|
||||||
@@ -21,10 +21,12 @@ export interface NavItem {
|
|||||||
// Eine Quelle der Wahrheit für Sidebar UND Command-Palette.
|
// Eine Quelle der Wahrheit für Sidebar UND Command-Palette.
|
||||||
export const NAV: NavItem[] = [
|
export const NAV: NavItem[] = [
|
||||||
{ id: "dashboard", label: "Zentrale", hint: "System- & Stack-Status", icon: LayoutDashboard },
|
{ id: "dashboard", label: "Zentrale", hint: "System- & Stack-Status", icon: LayoutDashboard },
|
||||||
{ id: "models", label: "Modelle & Routing", hint: "Modelle kuratieren, Gruppen & Auto-Routing", icon: Boxes },
|
{ id: "models", label: "Modell-Zentrale", hint: "Verwalten, laden & Gateway-Routing", icon: Boxes },
|
||||||
{ id: "routing", label: "Routing", hint: "Gateway-Regeln: schnell ↔ schwer", icon: Route },
|
|
||||||
{ id: "system", label: "Diagnose", hint: "Metriken, Dienste, Logs", icon: Cpu },
|
{ id: "system", label: "Diagnose", hint: "Metriken, Dienste, Logs", icon: Cpu },
|
||||||
{ id: "memory", label: "Gedächtnis", hint: "Geteiltes Memory verwalten", icon: Brain },
|
{ id: "memory", label: "Gedächtnis", hint: "Geteiltes Memory verwalten", icon: Brain },
|
||||||
{ id: "connect", label: "Verbinden", hint: "IDE-/Agent-Configs erzeugen", icon: Plug },
|
{ id: "connect", label: "Verbinden", hint: "IDE-/Agent-Configs erzeugen", icon: Plug },
|
||||||
{ id: "agent", label: "Hermes", hint: "Agent-Status & WebUI öffnen", icon: Bot },
|
{ id: "agent", label: "Hermes", hint: "Agent-Status & WebUI öffnen", icon: Bot },
|
||||||
|
{ id: "guide", label: "Anleitung", hint: "Einrichten & Vibe-Coding", icon: HelpCircle },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus } from "lucide-react"
|
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus, ShieldAlert } from "lucide-react"
|
||||||
import { api, type SystemStatus, type AgentStatus, type ModelInfo, type Memory } from "@/lib/api"
|
import { api, type SystemStatus, type AgentStatus, type ModelInfo, type Memory, type UpdatesResp } from "@/lib/api"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function gb(b: number) {
|
function gb(b: number) {
|
||||||
@@ -12,7 +12,6 @@ function RadialGauge({ value, label, detail }: { value: number; label: string; d
|
|||||||
const circ = 2 * Math.PI * radius
|
const circ = 2 * Math.PI * radius
|
||||||
const offset = circ - (Math.min(value, 100) / 100) * circ
|
const offset = circ - (Math.min(value, 100) / 100) * circ
|
||||||
|
|
||||||
// Verfärbung bei hoher Last
|
|
||||||
const strokeColor = value > 90
|
const strokeColor = value > 90
|
||||||
? "stroke-red-500"
|
? "stroke-red-500"
|
||||||
: value > 75
|
: value > 75
|
||||||
@@ -40,6 +39,7 @@ export function DashboardView() {
|
|||||||
const [models, setModels] = useState<ModelInfo[]>([])
|
const [models, setModels] = useState<ModelInfo[]>([])
|
||||||
const [running, setRunning] = useState<string[]>([])
|
const [running, setRunning] = useState<string[]>([])
|
||||||
const [memories, setMemories] = useState<Memory[]>([])
|
const [memories, setMemories] = useState<Memory[]>([])
|
||||||
|
const [updates, setUpdates] = useState<UpdatesResp | null>(null)
|
||||||
|
|
||||||
// Quick Memory Form State
|
// Quick Memory Form State
|
||||||
const [memContent, setMemContent] = useState("")
|
const [memContent, setMemContent] = useState("")
|
||||||
@@ -51,11 +51,12 @@ export function DashboardView() {
|
|||||||
api<AgentStatus>("/api/agent/status").then(setAgent).catch(() => {})
|
api<AgentStatus>("/api/agent/status").then(setAgent).catch(() => {})
|
||||||
api<{ models: ModelInfo[]; running?: string[] }>("/api/models")
|
api<{ models: ModelInfo[]; running?: string[] }>("/api/models")
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
setModels(d.models)
|
setModels(d.models || [])
|
||||||
setRunning(d.running || [])
|
setRunning(d.running || [])
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
api<Memory[]>("/api/memory?category=").then((d) => setMemories(d.slice(0, 3))).catch(() => {})
|
api<Memory[]>("/api/memory?category=").then((d) => setMemories(d.slice(0, 3))).catch(() => {})
|
||||||
|
api<UpdatesResp>("/api/maintenance/updates").then(setUpdates).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -73,7 +74,6 @@ export function DashboardView() {
|
|||||||
body: JSON.stringify({ content: memContent, category: memCat, source: "dashboard" }),
|
body: JSON.stringify({ content: memContent, category: memCat, source: "dashboard" }),
|
||||||
})
|
})
|
||||||
setMemContent("")
|
setMemContent("")
|
||||||
// Liste sofort aktualisieren
|
|
||||||
api<Memory[]>("/api/memory?category=").then((d) => setMemories(d.slice(0, 3))).catch(() => {})
|
api<Memory[]>("/api/memory?category=").then((d) => setMemories(d.slice(0, 3))).catch(() => {})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -86,6 +86,7 @@ export function DashboardView() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
{/* Title */}
|
||||||
<div>
|
<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">
|
<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">
|
||||||
Zentrale
|
Zentrale
|
||||||
@@ -93,9 +94,10 @@ export function DashboardView() {
|
|||||||
<p className="text-sm text-muted-foreground">Aktueller Status von System, Modellen und Agent.</p>
|
<p className="text-sm text-muted-foreground">Aktueller Status von System, Modellen und Agent.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-6 sm:grid-cols-2">
|
{/* Top Grid: System stats & Updates (3 Columns) */}
|
||||||
{/* Card 1: System Status */}
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
<div className="flex flex-col justify-between rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15">
|
{/* Card 1: System Status (2 columns wide) */}
|
||||||
|
<div className="md:col-span-2 flex flex-col justify-between rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Cpu className="h-4.5 w-4.5 text-primary" />
|
<Cpu className="h-4.5 w-4.5 text-primary" />
|
||||||
@@ -105,11 +107,11 @@ export function DashboardView() {
|
|||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
<RadialGauge value={sys.cpu.percent} label="CPU" detail={`${sys.cpu.cores} Cores`} />
|
<RadialGauge value={sys.cpu.percent} label="CPU" detail={`${sys.cpu.cores} Cores`} />
|
||||||
<RadialGauge value={sys.ram.percent} label="RAM" detail={`${gb(sys.ram.used)} / ${gb(sys.ram.total)} GB`} />
|
<RadialGauge value={sys.ram.percent} label="RAM" detail={`${gb(sys.ram.used)} / ${gb(sys.ram.total)} GB`} />
|
||||||
{sys.gpu && sys.gpu.busy_percent != null && (
|
{sys.gpu && sys.gpu.busy_percent != null && sys.gpu.gtt_used != null && sys.gpu.gtt_total != null && (
|
||||||
<RadialGauge
|
<RadialGauge
|
||||||
value={sys.gpu.busy_percent}
|
value={sys.gpu.busy_percent}
|
||||||
label="GPU"
|
label="GPU"
|
||||||
detail={sys.gpu.gtt_used != null && sys.gpu.gtt_total != null ? `${gb(sys.gpu.gtt_used)} / ${gb(sys.gpu.gtt_total)} GB` : undefined}
|
detail={`${gb(sys.gpu.gtt_used)} / ${gb(sys.gpu.gtt_total)} GB`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sys.disk && (
|
{sys.disk && (
|
||||||
@@ -128,7 +130,80 @@ export function DashboardView() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card 2: Hermes Agent Status */}
|
{/* Card 2: Updates & Wartung (1 column wide) */}
|
||||||
|
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<ShieldAlert className="h-4.5 w-4.5 text-primary" />
|
||||||
|
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Updates & Pflege</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{updates ? (
|
||||||
|
<div className="space-y-2.5">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center justify-between px-3 py-1.5 rounded-lg border text-xs",
|
||||||
|
updates.os > 0
|
||||||
|
? "border-amber-500/30 bg-amber-500/5 text-amber-400 font-semibold"
|
||||||
|
: "border-border/30 bg-background/25 text-muted-foreground"
|
||||||
|
)}>
|
||||||
|
<span>OS-Pakete</span>
|
||||||
|
<span className="font-mono">{updates.os > 0 ? `${updates.os} verfügbar` : "aktuell"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center justify-between px-3 py-1.5 rounded-lg border text-xs",
|
||||||
|
updates.engine > 0
|
||||||
|
? "border-amber-500/30 bg-amber-500/5 text-amber-400 font-semibold"
|
||||||
|
: "border-border/30 bg-background/25 text-muted-foreground"
|
||||||
|
)}>
|
||||||
|
<span>Engine (llama.cpp)</span>
|
||||||
|
<span className="font-mono">{updates.engine > 0 ? "Update verfügbar" : "aktuell"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center justify-between px-3 py-1.5 rounded-lg border text-xs",
|
||||||
|
updates.models > 0
|
||||||
|
? "border-primary/30 bg-primary/5 text-primary font-semibold animate-pulse"
|
||||||
|
: "border-border/30 bg-background/25 text-muted-foreground"
|
||||||
|
)}>
|
||||||
|
<span>Modell-Upgrades</span>
|
||||||
|
<span className="font-mono">{updates.models > 0 ? `${updates.models} verfügbar` : "aktuell"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{updates.model_list.length > 0 && (
|
||||||
|
<div className="mt-2 space-y-1">
|
||||||
|
<div className="text-[9px] uppercase font-bold text-muted-foreground/60 tracking-wider">Upgrades:</div>
|
||||||
|
<div className="max-h-20 overflow-y-auto space-y-1 pr-1 scrollbar-thin">
|
||||||
|
{updates.model_list.map((m) => (
|
||||||
|
<div key={m.repo} className="text-[9px] bg-background/20 border border-border/20 rounded p-1.5 font-mono text-muted-foreground truncate" title={`${m.role}: ${m.repo}`}>
|
||||||
|
<span className="text-primary font-semibold uppercase">{m.role}</span>: {m.repo.split("/").pop()}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="h-24 flex items-center justify-center text-xs text-muted-foreground">Lade Updates...</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 border-t border-border/30 pt-3 shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => window.dispatchEvent(new CustomEvent("open-system-drawer"))}
|
||||||
|
className="w-full h-8 flex items-center justify-center gap-1.5 rounded-lg bg-primary text-primary-foreground text-xs font-semibold hover:opacity-90 transition-all cursor-pointer shadow-md shadow-primary/10"
|
||||||
|
>
|
||||||
|
System-Zentrale öffnen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Grid: Agent, Roles & Memory (3 Columns) */}
|
||||||
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
|
{/* Card 3: Hermes Agent Status */}
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
@@ -188,10 +263,8 @@ export function DashboardView() {
|
|||||||
Gedächtnis & Stack-Tools via MCP gekoppelt.
|
Gedächtnis & Stack-Tools via MCP gekoppelt.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-6 sm:grid-cols-2">
|
{/* Card 4: Active Model Roles */}
|
||||||
{/* Card 3: Active Model Roles */}
|
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
@@ -199,7 +272,7 @@ export function DashboardView() {
|
|||||||
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Aktive Rollen</h2>
|
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Aktive Rollen</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 max-h-48 overflow-y-auto pr-1 scrollbar-thin">
|
||||||
{activeModels.length === 0 ? (
|
{activeModels.length === 0 ? (
|
||||||
<div className="text-xs text-muted-foreground py-6 text-center">Keine Modelle als Rollen zugewiesen.</div>
|
<div className="text-xs text-muted-foreground py-6 text-center">Keine Modelle als Rollen zugewiesen.</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -209,15 +282,15 @@ export function DashboardView() {
|
|||||||
<div
|
<div
|
||||||
key={m.name}
|
key={m.name}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-between p-3 rounded-xl border transition-all duration-300",
|
"flex items-center justify-between p-2.5 rounded-xl border transition-all duration-300",
|
||||||
isRunning
|
isRunning
|
||||||
? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5"
|
? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5"
|
||||||
: "border-border/40 bg-background/20"
|
: "border-border/40 bg-background/20"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="min-w-0 flex-1 mr-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={cn("text-xs font-semibold uppercase px-1.5 py-0.5 rounded",
|
<span className={cn("text-[9px] font-semibold uppercase px-1.5 py-0.5 rounded font-mono",
|
||||||
m.role === "fast" ? "bg-cyan-500/15 text-cyan-400" :
|
m.role === "fast" ? "bg-cyan-500/15 text-cyan-400" :
|
||||||
m.role === "heavy" ? "bg-amber-500/15 text-amber-400" :
|
m.role === "heavy" ? "bg-amber-500/15 text-amber-400" :
|
||||||
m.role === "coder" ? "bg-violet-500/15 text-violet-400" :
|
m.role === "coder" ? "bg-violet-500/15 text-violet-400" :
|
||||||
@@ -232,12 +305,12 @@ export function DashboardView() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-medium mt-1.5 truncate max-w-[200px] sm:max-w-[280px]" title={m.name}>
|
<div className="text-[10px] font-medium mt-1 truncate" title={m.name}>
|
||||||
{m.name}
|
{m.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] font-mono text-muted-foreground">
|
<div className="text-[9px] font-mono text-muted-foreground shrink-0">
|
||||||
{isRunning ? "Warm / Aktiv" : "Bereit"}
|
{isRunning ? "Warm" : "Bereit"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -250,29 +323,27 @@ export function DashboardView() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card 4: Quick Memory Input */}
|
{/* Card 5: Quick Memory Input */}
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Brain className="h-4.5 w-4.5 text-primary" />
|
<Brain className="h-4.5 w-4.5 text-primary" />
|
||||||
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Gedächtnis-Schnellform</h2>
|
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Gedächtnis</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-start gap-2">
|
<textarea
|
||||||
<textarea
|
value={memContent}
|
||||||
value={memContent}
|
onChange={(e) => setMemContent(e.target.value)}
|
||||||
onChange={(e) => setMemContent(e.target.value)}
|
placeholder="Fakt / Regel im Pool speichern..."
|
||||||
placeholder="Fakt / Regel auf der Box speichern..."
|
rows={2}
|
||||||
rows={2}
|
className="w-full resize-none rounded-xl border border-border/50 bg-background/30 p-2 text-[10px] outline-none focus:ring-1 focus:ring-primary transition-all leading-normal"
|
||||||
className="flex-1 resize-none rounded-xl border border-border/50 bg-background/30 px-3 py-2 text-xs outline-none focus:ring-1.5 focus:ring-primary transition-all"
|
/>
|
||||||
/>
|
<div className="flex items-center gap-2 justify-between">
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 justify-end">
|
|
||||||
<select
|
<select
|
||||||
value={memCat}
|
value={memCat}
|
||||||
onChange={(e) => setMemCat(e.target.value)}
|
onChange={(e) => setMemCat(e.target.value)}
|
||||||
className="rounded-lg border border-border/50 bg-background/50 px-2 py-1 text-xs outline-none"
|
className="h-7 rounded-lg border border-border/50 bg-background/50 px-2 text-[10px] outline-none cursor-pointer"
|
||||||
>
|
>
|
||||||
<option value="stable">🔵 Fakt</option>
|
<option value="stable">🔵 Fakt</option>
|
||||||
<option value="instruction">📋 Regel</option>
|
<option value="instruction">📋 Regel</option>
|
||||||
@@ -282,33 +353,35 @@ export function DashboardView() {
|
|||||||
<button
|
<button
|
||||||
onClick={saveQuickMemory}
|
onClick={saveQuickMemory}
|
||||||
disabled={!memContent.trim() || savingMem}
|
disabled={!memContent.trim() || savingMem}
|
||||||
className="flex items-center gap-1 rounded-lg bg-primary px-3 py-1 text-xs font-semibold text-primary-foreground hover:opacity-90 disabled:opacity-50 transition-all cursor-pointer"
|
className="flex h-7 items-center gap-1 rounded-lg bg-primary px-3 text-[10px] font-semibold text-primary-foreground hover:opacity-90 disabled:opacity-50 transition-all cursor-pointer"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" /> Speichern
|
<Plus className="h-3.5 w-3.5" /> Speichern
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-3.5 space-y-1.5">
|
||||||
<div className="text-[10px] text-muted-foreground uppercase font-semibold tracking-wider">Zuletzt gespeichert:</div>
|
<div className="text-[9px] text-muted-foreground uppercase font-bold tracking-wider">Zuletzt gespeichert:</div>
|
||||||
{memories.length === 0 ? (
|
<div className="max-h-20 overflow-y-auto space-y-1 pr-1 scrollbar-thin">
|
||||||
<div className="text-xs text-muted-foreground/75 py-2">Keine Einträge vorhanden.</div>
|
{memories.length === 0 ? (
|
||||||
) : (
|
<div className="text-[10px] text-muted-foreground/75 py-1">Keine Einträge vorhanden.</div>
|
||||||
memories.map((m) => (
|
) : (
|
||||||
<div key={m.id} className="text-xs bg-background/10 border border-border/30 rounded-lg p-2 flex items-start gap-2">
|
memories.map((m) => (
|
||||||
<span className="shrink-0 text-[10px] font-mono text-muted-foreground/80 px-1 py-0.5 rounded bg-muted/20">
|
<div key={m.id} className="text-[10px] bg-background/10 border border-border/30 rounded p-1.5 flex items-start gap-1.5">
|
||||||
{m.category}
|
<span className="shrink-0 text-[8px] font-mono text-muted-foreground/80 px-1 py-0.5 rounded bg-muted/20">
|
||||||
</span>
|
{m.category}
|
||||||
<span className="truncate flex-1 text-muted-foreground hover:text-foreground transition-colors" title={m.content}>
|
</span>
|
||||||
{m.content}
|
<span className="truncate flex-1 text-muted-foreground hover:text-foreground transition-colors" title={m.content}>
|
||||||
</span>
|
{m.content}
|
||||||
</div>
|
</span>
|
||||||
))
|
</div>
|
||||||
)}
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-[10px] text-muted-foreground/80 border-t border-border/30 pt-3">
|
<div className="mt-4 text-[10px] text-muted-foreground/80 border-t border-border/30 pt-3">
|
||||||
Steht allen Clients (IDEs, Hermes) per MCP zur Verfügung.
|
Steht allen Clients per MCP zur Verfügung.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 & 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react"
|
|||||||
import { Download, Search, Trash2, Edit3, Star, Layers, Activity, HardDrive } from "lucide-react"
|
import { Download, Search, Trash2, Edit3, Star, Layers, Activity, HardDrive } from "lucide-react"
|
||||||
import { api, type DiscoverResp, type Fit, type Job, type ModelInfo } from "@/lib/api"
|
import { api, type DiscoverResp, type Fit, type Job, type ModelInfo } from "@/lib/api"
|
||||||
import { CapsChips } from "@/components/CapsChips"
|
import { CapsChips } from "@/components/CapsChips"
|
||||||
|
import { RoutingView } from "./RoutingView"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function fmtBytes(b?: number) {
|
function fmtBytes(b?: number) {
|
||||||
@@ -492,7 +493,7 @@ function Discover() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ModelsView() {
|
export function ModelsView() {
|
||||||
const [tab, setTab] = useState<"installed" | "discover">("installed")
|
const [tab, setTab] = useState<"installed" | "discover" | "routing">("installed")
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-4">
|
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-4">
|
||||||
@@ -501,12 +502,12 @@ export function ModelsView() {
|
|||||||
Modell-Zentrale
|
Modell-Zentrale
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Verwalte installierte GGUFs, weise Systemrollen zu und lade neue Modelle von HuggingFace.
|
Verwalte installierte GGUFs, weise Systemrollen zu, lade neue Modelle oder konfiguriere das Gateway-Routing.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex rounded-xl border border-border/60 bg-card/45 backdrop-blur-md p-1 self-start">
|
<div className="flex rounded-xl border border-border/60 bg-card/45 backdrop-blur-md p-1 self-start">
|
||||||
{(["installed", "discover"] as const).map((t) => (
|
{(["installed", "discover", "routing"] as const).map((t) => (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
onClick={() => setTab(t)}
|
onClick={() => setTab(t)}
|
||||||
@@ -517,7 +518,7 @@ export function ModelsView() {
|
|||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t === "installed" ? "Installiert" : "Suchen & Entdecken"}
|
{t === "installed" ? "Bibliothek" : t === "discover" ? "Modelle finden" : "Gateway-Routing"}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -526,8 +527,9 @@ export function ModelsView() {
|
|||||||
<JobsBar />
|
<JobsBar />
|
||||||
|
|
||||||
<div className="transition-all duration-300">
|
<div className="transition-all duration-300">
|
||||||
{tab === "installed" ? <Installed /> : <Discover />}
|
{tab === "installed" ? <Installed /> : tab === "discover" ? <Discover /> : <RoutingView />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user