Zeitachse (24h-Historie), Verbinden-Autofill, Modelltausch abgesichert
Paket 1 - Metrik-Historie mit Zeitachse (User: 'WANN war Last?'): - services/metrics_history.py: 10s-Sampler (CPU/RAM/GPU/Disk + Token-Totale) in 24h-Ringpuffer (aelteres faellt automatisch raus, nichts waechst unbegrenzt), persistiert alle 5 min -> uebersteht Deploys; GET /api/system/history?minutes mit Downsampling auf ~300 Punkte; Lifespan-Task in app.py - Cockpit-Karten System-Status + Token-Durchsatz: Bereichs-Schalter Live/1h/24h, sichtbare Zeitachse (HH:MM:SS bei kurzen, HH:MM bei langen Fenstern), Tooltip zeigt Uhrzeit; Token-Raten in 1h/24h aus Totale-Deltas Paket 2 - Verbinden selbsterklaerend: - Box-IP vorbefuellt aus window.location.hostname (Dev-Fallback bleibt) - MCP-Scriptpfad-Feld aus der Kopfzeile in die 'Gedaechtnis anbinden'-Karte verschoben, mit Erklaerung WANN man es braucht Paket 3 - Modelltausch-Loecher gestopft (Review 16.07.): - install: Registrierung OHNE Alias-Umzug; Rolle wird erst NACH erfolgreichem Download uebernommen (on_done) - hermes dabei durch den warm-bewussten set_agent_brain-Flow statt rohem Alias-Move (Lucy waere sonst bis Download- Ende tot gewesen); Re-Install erhaelt bestehende Aliase - set_role_alias: lebenswichtige Aliase (hermes/embed) des Ziel-Modells ueberleben jeden Rollen-Klick (Qwen3.6 haelt live hermes+fast!) - Rollen-Endpoint verweigert Rollen-Wechsel am Halter geschuetzter Aliase mit klarer Anleitung; Werkbank sperrt die Rollen-Chips sichtbar Verifiziert: py_compile + Sampler-Smoke (2 Punkte, Persist ok) + Alias-Logik- Unittest (3 Faelle) + Browser (Zeitachse tickt, Schalter, Leerzustand, Verbinden). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,8 +72,16 @@ function CodeWindow({
|
||||
)
|
||||
}
|
||||
|
||||
// Die Box-Adresse kennt der Browser schon — er ist ja über sie verbunden. Nur im
|
||||
// Dev-Modus (localhost + Vite-Proxy) greift der bekannte LAN-Fallback.
|
||||
function defaultHost(): string {
|
||||
const h = window.location.hostname
|
||||
if (h && h !== "localhost" && h !== "127.0.0.1") return h
|
||||
return "192.168.178.151"
|
||||
}
|
||||
|
||||
export function ConnectView() {
|
||||
const [host, setHost] = useState(localStorage.getItem("mc_host") || "192.168.178.151")
|
||||
const [host, setHost] = useState(localStorage.getItem("mc_host") || defaultHost())
|
||||
const [mcpPath, setMcpPath] = useState(localStorage.getItem("mc_mcp_path") || "")
|
||||
// Legacy-Tools (roher /v1-Zugang) leben im Ausklapper — Hermes Desktop ist der Hauptweg.
|
||||
const [active, setActive] = useState("kilo")
|
||||
@@ -187,11 +195,12 @@ export function ConnectView() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Gemeinsame Variablen */}
|
||||
<div className="grid gap-4 md:grid-cols-2 p-5 mc-card">
|
||||
<div className="space-y-1.5">
|
||||
{/* Box-Adresse — vorbefüllt aus der Browser-URL; anfassen nur, wenn sich die Box-IP ändert. */}
|
||||
<div className="p-5 mc-card">
|
||||
<div className="space-y-1.5 max-w-md">
|
||||
<label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground flex items-center gap-1.5">
|
||||
<Globe className="h-3.5 w-3.5 text-primary" /> Box LAN IP-Adresse
|
||||
<span className="normal-case font-semibold tracking-normal text-muted-foreground/60">— vorbefüllt aus der Adresse dieser Seite</span>
|
||||
</label>
|
||||
<input
|
||||
value={host}
|
||||
@@ -201,21 +210,9 @@ export function ConnectView() {
|
||||
placeholder="z.B. 192.168.178.151"
|
||||
className="w-full h-9 rounded-lg border border-border/60 bg-background/40 px-3 font-mono text-xs outline-none focus:ring-1 focus:ring-primary/50 text-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground flex items-center gap-1.5">
|
||||
<FolderOpen className="h-3.5 w-3.5 text-violet-400" /> Lokaler MCP-Scriptpfad
|
||||
<span className="text-violet-400/70 normal-case font-semibold tracking-normal">(nur Leitung 2)</span>
|
||||
</label>
|
||||
<input
|
||||
value={mcpPath}
|
||||
onChange={(e) => saveMcpPath(e.target.value)}
|
||||
aria-label="Lokaler MCP-Scriptpfad"
|
||||
spellCheck={false}
|
||||
placeholder="z.B. F:\Coding Stuff\mission-control-2\mcp\mcp_memory.py"
|
||||
className="w-full h-9 rounded-lg border border-border/60 bg-background/40 px-3 font-mono text-xs outline-none focus:ring-1 focus:ring-violet-500/50 text-foreground"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground/70">
|
||||
Wird in alle Snippets eingebacken. Ändern musst du sie nur, wenn die Box eine neue IP bekommt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -332,6 +329,26 @@ export function ConnectView() {
|
||||
<span>{data.memory.note}</span>
|
||||
</div>
|
||||
|
||||
{/* Der Pfad wirkt NUR auf dieses Snippet — deshalb wohnt das Feld hier,
|
||||
nicht mehr prominent in der Kopfzeile („Wann brauche ich das?"). */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground flex items-center gap-1.5">
|
||||
<FolderOpen className="h-3.5 w-3.5 text-violet-400" /> Wo liegt mcp_memory.py auf DIESEM PC?
|
||||
</label>
|
||||
<input
|
||||
value={mcpPath}
|
||||
onChange={(e) => saveMcpPath(e.target.value)}
|
||||
aria-label="Lokaler MCP-Scriptpfad"
|
||||
spellCheck={false}
|
||||
placeholder="z.B. F:\Coding Stuff\mission-control-2\mcp\mcp_memory.py"
|
||||
className="w-full h-9 rounded-lg border border-border/60 bg-background/40 px-3 font-mono text-xs outline-none focus:ring-1 focus:ring-violet-500/50 text-foreground"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground/70">
|
||||
Nur nötig, wenn du das Gedächtnis in einem PC-Tool anschließt — das Script läuft lokal
|
||||
und spricht mit der Box. Der Pfad landet direkt im Snippet unten.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CodeWindow
|
||||
tool={data.memory}
|
||||
fileName="mcp.json"
|
||||
|
||||
@@ -259,14 +259,18 @@ function Detail({
|
||||
{lockUnload ? "🔒 bleibt geladen" : warm ? "Entladen" : "Ins Warm-Set laden"}
|
||||
</button>
|
||||
|
||||
{/* Rolle wählen */}
|
||||
{/* Rolle wählen — bei lebenswichtigen Rollen (Hirn/Gedächtnis) gesperrt: die Rolle
|
||||
wandert nur, indem man sie einem ANDEREN Modell zuweist (Backend erzwingt das auch). */}
|
||||
<div className="rounded-xl border border-border/30 bg-background/20 p-3">
|
||||
<div className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">Rolle</div>
|
||||
<div className="mb-2 flex items-center justify-between text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<span>Rolle</span>
|
||||
{protectedRole && <span className="normal-case font-medium tracking-normal text-muted-foreground/70">🔒 lebenswichtig — zum Tausch die Rolle woanders zuweisen</span>}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<RoleChip active={!m.role} onClick={() => onRole("")} dot="bg-slate-400" label="keine" />
|
||||
<RoleChip active={!m.role} onClick={() => onRole("")} dot="bg-slate-400" label="keine" disabled={protectedRole} />
|
||||
{ROLE_META.map((r) => (
|
||||
<RoleChip key={r.role} active={m.role === r.role} onClick={() => onRole(r.role)}
|
||||
dot={roleBarColor(r.role).dot} label={r.short} />
|
||||
dot={roleBarColor(r.role).dot} label={r.short} disabled={protectedRole && m.role !== r.role} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -322,11 +326,16 @@ function Detail({
|
||||
)
|
||||
}
|
||||
|
||||
function RoleChip({ active, onClick, dot, label }: { active: boolean; onClick: () => void; dot: string; label: string }) {
|
||||
function RoleChip({ active, onClick, dot, label, disabled }: {
|
||||
active: boolean; onClick: () => void; dot: string; label: string; disabled?: boolean
|
||||
}) {
|
||||
return (
|
||||
<button onClick={onClick}
|
||||
className={cn("flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[10px] font-semibold transition-all cursor-pointer",
|
||||
active ? "border-primary/60 bg-primary/15 text-foreground" : "border-border/40 bg-background/20 text-muted-foreground hover:border-primary/30")}>
|
||||
<button onClick={onClick} disabled={disabled}
|
||||
title={disabled ? "Gesperrt: dieses Modell hält eine lebenswichtige Rolle." : undefined}
|
||||
className={cn("flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[10px] font-semibold transition-all",
|
||||
disabled ? "cursor-not-allowed opacity-40 border-border/40 bg-background/20 text-muted-foreground"
|
||||
: active ? "cursor-pointer border-primary/60 bg-primary/15 text-foreground"
|
||||
: "cursor-pointer border-border/40 bg-background/20 text-muted-foreground hover:border-primary/30")}>
|
||||
<span className={cn("h-2 w-2 rounded-sm", dot)} />
|
||||
{label}
|
||||
{active && <Check className="h-3 w-3 text-primary" />}
|
||||
|
||||
Reference in New Issue
Block a user