MC2 Final (D16 f): Voice-Metrik-Reste raus + README auf Final-Stand (C15)
f) Lucy/Voice-Reste aus dem MC2-Web-UI entfernt (kein "Sprechen"-Tab mehr): VoiceLatencyCard + Sprach-Latenz-Plumbing (queries/api-Typen) und der Backend-Leseendpoint /voice/metrics raus. Der Proaktivität-/Alarm-Kanal (/voice/announce, /alarm) und die STT/TTS/chat-Proxys bleiben unberührt. C15) README auf den Final-/Autonomie-Stand aktualisiert (Mem0 statt SQLite-MCP, live+eingefroren, Lucy als eigenes Repo, Fangnetz-Updates/Werkstatt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +1,41 @@
|
||||
# Mission Control 2.0
|
||||
|
||||
Komponierbarer Local-AI-Stack für den Bosgame M5. Greenfield-Neuaufbau —
|
||||
siehe Architektur-Plan (`docs/` bzw. der genehmigte Plan).
|
||||
Komponierbarer Local-AI-Stack für den Bosgame M5 (Strix Halo). Läuft **live auf der Box**
|
||||
als sudo-freier systemd-User-Dienst (`:9001`) und ist als **Final-Version eingefroren** —
|
||||
Pflege übernehmen ab jetzt die selbst-wartenden Box-KIs (Auto-Update mit Fangnetz + die
|
||||
„Werkstatt"), nicht mehr manuelle Releases.
|
||||
|
||||
**Schichten:** Engine (llama-swap, **Vulkan/RADV** auf Strix Halo) · **Builtin-Routing-Gateway**
|
||||
in MC2 (`model: auto`, kein externer LiteLLM-Dienst — scheitert auf Python 3.14) ·
|
||||
Mission Control 2.0 (FastAPI + React/shadcn) · Hermes Agent + hermes-webui ·
|
||||
Shared Memory (SQLite via MCP). Jede Schicht hinter stabilem Vertrag austauschbar.
|
||||
**Schichten:** Engine (llama.cpp **Vulkan/RADV** auf Strix Halo) · Router (**llama-swap**,
|
||||
Ko-Residenz-Warm-Set) · **Builtin-Routing-Gateway** in MC2 (`model: auto`, kein externer
|
||||
LiteLLM — scheitert auf Python 3.14) · Mission Control 2.0 (FastAPI + React/shadcn) ·
|
||||
**Hermes Agent** (api_server-Gateway :8642, Tools/MCP/Telegram/cron) · auto-lernendes
|
||||
**Gedächtnis** (Mem0-Sidecar, semantisch). Jede Schicht hinter stabilem Vertrag austauschbar.
|
||||
|
||||
## Status: Phasen 0–5 ✅ · MC2 **live auf der Box** (:9001) · Modelle/Hermes-Wiring + Cutover offen
|
||||
> **Sprachassistentin Lucy** (Voice + 3D-Avatar) ist in ein **eigenes Repo** ausgelagert
|
||||
> (`Hitonabi/lucy`) und spricht über den Hermes-Gateway. Aus MC2 selbst sind die Voice-/
|
||||
> Web-Reste entfernt — MC2 ist die Steuerzentrale, kein Sprach-Frontend.
|
||||
|
||||
Fortschritt & Resume-Guide: siehe [`docs/STATUS.md`](docs/STATUS.md).
|
||||
## Status: **live + eingefroren (MC2 Final)**
|
||||
|
||||
- **Phase 0** — FastAPI-Skeleton + React/shadcn-Shell (Cmd+K, Dark, PWA).
|
||||
- **Phase 1** — Compute-Module (fit/caps/sources, portiert), **Discover** (live HF + Fit + Caps),
|
||||
**Engine-Write** (register + `groups`/Ko-Residenz + vocab-geprüfte Spec-Drafts),
|
||||
**Builtin-Gateway** (`model: auto` + Fallbacks), Frontend **Modelle & Routing** (Caps-Chips, Fit, Discover, Routing-View).
|
||||
Der ganze Stack ist auf der Box in Betrieb und auf **Autonomie** ausgelegt (Ziel: läuft auch
|
||||
ohne Betreuung monatelang weiter):
|
||||
|
||||
- **Phase 2** — System-Status (CPU/RAM/GPU/Disk), Wartung (restart/self-update, sudo-frei),
|
||||
**Connect** (saubere IDE-Snippets → Gateway `model:auto`, LAN-IP-Override).
|
||||
- **Phase 3** — Geteiltes **Gedächtnis** (SQLite/WAL, 5 Kategorien, Dedupe-Kurator) + **MCP-Server**
|
||||
(`mcp/mcp_memory.py` shared, `mcp/mcp_mc.py` Stack-Management für Hermes), MemoryView.
|
||||
- **Modelle & Routing** — Discover (live HF + Fit/Caps), Engine-Write (register, `groups`/
|
||||
Ko-Residenz, vocab-geprüfte Spec-Drafts), Gateway `model: auto` + Fallbacks.
|
||||
- **System & Wartung** — Status (CPU/RAM/GPU/Disk), **ehrliche Speicher-Zahlen** (echte KV-Bytes
|
||||
aus GGUF-Architektur), Logs mit Fehler-Filter, Dienst-Neustart (sudo-frei).
|
||||
- **Updates mit Fangnetz** — OS/Engine/Router/Hermes per Timer: Backup → Update → Postcheck →
|
||||
bei Fehler **Auto-Rollback + Pin**; verständliche Zusammenfassung („Musst du etwas tun?") in
|
||||
Lucys Stimme. Eigene Software wird eingefroren, nicht geupdatet.
|
||||
- **Gedächtnis** — Mem0-Sidecar (auto-lernend, semantisch), 4 Kategorien, Auto-Dedupe.
|
||||
- **Selbsterhaltung** — Health-Wächter (`sentry`), Warm-Set-Wächter (`warmer`), tägliche
|
||||
Self-Smokes, Lucy-Watchdog + Alarmkette; **Werkstatt** (Hermes wartet den Stack via Gitea-
|
||||
Branch → Gate → Deploy selbst).
|
||||
|
||||
- **Phase 4** — Hermes-**Agent-Status** (`/api/agent/status`, AgentView mit Tiles + „Hermes öffnen"),
|
||||
`deploy/hermes-webui.service`, **Box-Runbook** [`docs/HERMES_SETUP.md`](docs/HERMES_SETUP.md)
|
||||
(hermes-webui, Brain=`auto`, Tools/MCP-Verdrahtung). Box-Ausführung steht noch aus.
|
||||
|
||||
- **Phase 5** — **Backup** (Memory + Configs), **Services-Health** + Observability-Links,
|
||||
**Theme-Toggle** (Hell/Dunkel). Box-Deploy/-Wiring + Cutover (Phase 6) brauchen die Box.
|
||||
|
||||
API: `health · models · discover · fit · models/register · groups · routing · system/* · connect ·
|
||||
memory/* · agent/status` (Details in `docs/STATUS.md`). MCP: `mcp/` (siehe `mcp/requirements.txt`).
|
||||
Box-Runbooks: `docs/HERMES_SETUP.md` + `deploy/` (Units, deploy.sh, backup.sh).
|
||||
API: `health · models · discover · fit · models/register · groups · routing · system/* ·
|
||||
maintenance/* · connect · memory/* · agent/* · voice/announce · reminders/*`. MCP: `mcp/`.
|
||||
Box-Runbooks: [`docs/HERMES_SETUP.md`](docs/HERMES_SETUP.md), [`docs/RUNBOOK.md`](docs/RUNBOOK.md),
|
||||
[`docs/BEDIENUNG.md`](docs/BEDIENUNG.md) + `deploy/` (Units, `deploy.sh`, `backup.sh`, `warmup.sh`).
|
||||
|
||||
## Entwickeln
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from pydantic import BaseModel
|
||||
|
||||
from config import HERMES_API_KEY, HERMES_API_MODEL, HERMES_API_URL, LLAMA_SWAP_URL, VOICE_SERVICE_URL
|
||||
from services import announce
|
||||
from services.voice_metrics import Timer, get_metrics, record_stage # Per-Stage-Latenz (C2)
|
||||
from services.voice_metrics import Timer, record_stage # Per-Stage-Latenz (intern)
|
||||
|
||||
# Injection-Schutz (Stufe 0): guard.py liegt im mcp/-Verzeichnis. Per Pfad laden (eigene MC2-Venv).
|
||||
import sys as _sys
|
||||
@@ -140,13 +140,6 @@ def voice_announcements(after: int | None = None, limit: int = 20) -> dict:
|
||||
return announce.list_after(after, limit)
|
||||
|
||||
|
||||
@router.get("/voice/metrics")
|
||||
def voice_metrics() -> dict:
|
||||
"""Per-Stage-Latenz (STT/Vision/Chat-TTFB/TTS) — rollende Statistik, macht die Voice-Pipeline
|
||||
messbar (C2). Anzeige im Frontend-Overhaul (E)."""
|
||||
return get_metrics()
|
||||
|
||||
|
||||
@router.get("/voice/health")
|
||||
def voice_health() -> dict:
|
||||
"""Erreichbarkeit des Voice-Sidecars + ob der Hermes-API-Key gesetzt ist."""
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+138
-138
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-B0tHcMp_.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DQJ4LntK.css">
|
||||
<script type="module" crossorigin src="/assets/index-DZPeq_n9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BPiDiqMB.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Gauge } from "lucide-react"
|
||||
import { useVoiceMetrics } from "@/lib/queries"
|
||||
import type { VoiceStageStat } from "@/lib/api"
|
||||
|
||||
// Stabile Reihenfolge + deutsche Labels, spiegelt STAGES in backend/services/voice_metrics.py.
|
||||
const STAGES: { key: string; label: string; hint: string }[] = [
|
||||
{ key: "stt", label: "STT", hint: "Sprache → Text" },
|
||||
{ key: "vision", label: "Bildschirm-Sicht", hint: "Vision-Beschreibung" },
|
||||
{ key: "chat_ttfb", label: "Chat-TTFB", hint: "Zeit bis 1. Token" },
|
||||
{ key: "tts", label: "TTS", hint: "Text → Sprache" },
|
||||
]
|
||||
|
||||
function fmtMs(ms?: number): string {
|
||||
if (ms == null) return "—"
|
||||
return ms >= 1000 ? `${(ms / 1000).toFixed(2)} s` : `${Math.round(ms)} ms`
|
||||
}
|
||||
|
||||
// p95 relativ zum langsamsten p95 aller Stufen → grobe Balkenlänge.
|
||||
function StageRow({ stat, label, hint, maxP95 }: { stat?: VoiceStageStat; label: string; hint: string; maxP95: number }) {
|
||||
const has = !!stat && stat.count > 0
|
||||
const pct = has && stat!.p95_ms && maxP95 > 0 ? Math.max(4, Math.min(100, (stat!.p95_ms! / maxP95) * 100)) : 0
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<div className="flex items-baseline gap-2 min-w-0">
|
||||
<span className="text-xs font-semibold text-foreground">{label}</span>
|
||||
<span className="truncate text-[10px] text-muted-foreground/60">{hint}</span>
|
||||
</div>
|
||||
{has ? (
|
||||
<span className="shrink-0 font-mono text-sm font-bold tabular-nums text-foreground">{fmtMs(stat!.p50_ms)}</span>
|
||||
) : (
|
||||
<span className="shrink-0 text-[10px] italic text-muted-foreground/50">noch keine Messungen</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-1.5 w-full overflow-hidden rounded-full bg-background/50 border border-border/30">
|
||||
<div
|
||||
className="h-full rounded-full bg-gradient-to-r from-teal-500 to-indigo-500 transition-all duration-500"
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
{has && (
|
||||
<div className="flex items-center gap-3 font-mono text-[10px] text-muted-foreground/65">
|
||||
<span>p50 {fmtMs(stat!.p50_ms)}</span>
|
||||
<span>p95 {fmtMs(stat!.p95_ms)}</span>
|
||||
<span>zuletzt {fmtMs(stat!.last_ms)}</span>
|
||||
<span className="text-muted-foreground/45">· n={stat!.count}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function VoiceLatencyCard() {
|
||||
const { data: metrics } = useVoiceMetrics(5_000)
|
||||
const maxP95 = Math.max(1, ...STAGES.map((s) => metrics?.[s.key]?.p95_ms ?? 0))
|
||||
const anyData = STAGES.some((s) => (metrics?.[s.key]?.count ?? 0) > 0)
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<Gauge className="h-4.5 w-4.5 text-primary" />
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-foreground">Sprach-Latenz</h2>
|
||||
<span className="ml-1 inline-flex items-center gap-1 text-[10px] font-medium text-muted-foreground/70">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" /> live
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{STAGES.map((s) => (
|
||||
<StageRow key={s.key} stat={metrics?.[s.key]} label={s.label} hint={s.hint} maxP95={maxP95} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 border-t border-border/30 pt-2 text-[10px] text-muted-foreground/70">
|
||||
{anyData
|
||||
? "Server-seitige Dauer je Pipeline-Stufe (p50 prominent). Rollender Schnitt über die letzten Turns; Reset bei Neustart."
|
||||
: "Noch keine Voice-Turns gemessen — sprich einmal über den „Sprechen“-Tab, dann erscheinen hier STT/Vision/Chat/TTS."}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -225,17 +225,6 @@ export const getRoutingPolicy = () => api<RoutingPolicyMeta>("/api/routing/polic
|
||||
export const updateRoutingPolicy = (patch: Partial<RoutingPolicy>) =>
|
||||
api<{ policy: RoutingPolicy }>("/api/routing/policy", { method: "PUT", body: JSON.stringify(patch) })
|
||||
|
||||
// Per-Stage-Latenz der Voice/Lucy-Pipeline (GET /api/voice/metrics, C2).
|
||||
// Schlüssel = Stufe (stt|vision|chat_ttfb|tts), Wert = rollende Statistik.
|
||||
export interface VoiceStageStat {
|
||||
count: number
|
||||
avg_ms?: number
|
||||
p50_ms?: number
|
||||
p95_ms?: number
|
||||
last_ms?: number
|
||||
}
|
||||
export type VoiceMetrics = Record<string, VoiceStageStat>
|
||||
|
||||
export interface GitInfo {
|
||||
hash: string
|
||||
date: string
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
type SystemStatus,
|
||||
type TokenStats,
|
||||
type UpdatesResp,
|
||||
type VoiceMetrics,
|
||||
} from "./api"
|
||||
|
||||
// Zentrale Query-Keys (eine Quelle der Wahrheit für invalidate).
|
||||
@@ -35,7 +34,6 @@ export const qk = {
|
||||
groups: ["groups"] as const,
|
||||
routing: ["routing"] as const,
|
||||
routingPolicy: ["routing-policy"] as const,
|
||||
voiceMetrics: ["voice-metrics"] as const,
|
||||
jobs: ["jobs"] as const,
|
||||
tokenStats: ["token-stats"] as const,
|
||||
agentStatus: ["agent-status"] as const,
|
||||
@@ -74,9 +72,6 @@ export const useGroups = (refetchInterval = 8_000) =>
|
||||
export const useRouting = (refetchInterval = 4_000) =>
|
||||
useQuery({ queryKey: qk.routing, queryFn: () => api<RoutingResp>("/api/routing"), refetchInterval })
|
||||
|
||||
export const useVoiceMetrics = (refetchInterval = 5_000) =>
|
||||
useQuery({ queryKey: qk.voiceMetrics, queryFn: () => api<VoiceMetrics>("/api/voice/metrics"), refetchInterval })
|
||||
|
||||
export const useRoutingPolicy = () =>
|
||||
useQuery({ queryKey: qk.routingPolicy, queryFn: () => api<RoutingPolicyMeta>("/api/routing/policy") })
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ArrowUpCircle, ChevronRight, Check, Cpu, Code } from "lucide-react"
|
||||
import { LucyHealthCard } from "@/components/dashboard/LucyHealthCard"
|
||||
import { SystemStatusCard } from "@/components/dashboard/SystemStatusCard"
|
||||
import { TokenPerformanceCard } from "@/components/dashboard/TokenPerformanceCard"
|
||||
import { VoiceLatencyCard } from "@/components/dashboard/VoiceLatencyCard"
|
||||
import { useUpdates, useModels } from "@/lib/queries"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
@@ -89,7 +88,6 @@ export function Mc3Start() {
|
||||
<SystemStatusCard />
|
||||
<TokenPerformanceCard />
|
||||
</div>
|
||||
<VoiceLatencyCard />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { AgentStatusCard } from "@/components/dashboard/AgentStatusCard"
|
||||
import { MemoryInputCard } from "@/components/dashboard/MemoryInputCard"
|
||||
import { TokenPerformanceCard } from "@/components/dashboard/TokenPerformanceCard"
|
||||
import { ServicesCard } from "@/components/dashboard/ServicesCard"
|
||||
import { VoiceLatencyCard } from "@/components/dashboard/VoiceLatencyCard"
|
||||
import { ModelsCard } from "@/components/dashboard/ModelsCard"
|
||||
import { LucyHealthCard } from "@/components/dashboard/LucyHealthCard"
|
||||
|
||||
@@ -35,10 +34,7 @@ export function DashboardView() {
|
||||
{/* Stack & Telemetrie */}
|
||||
<section>
|
||||
<ZoneLabel>Stack & Telemetrie</ZoneLabel>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<ServicesCard />
|
||||
<VoiceLatencyCard />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Betrieb & Wissen */}
|
||||
|
||||
Reference in New Issue
Block a user