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:
Hitonabi
2026-07-03 20:23:13 +02:00
parent 8a2db70f3c
commit 494037dee5
11 changed files with 174 additions and 280 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-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>
)
}
-11
View File
@@ -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
-5
View File
@@ -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
View File
@@ -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>
)
}
+1 -5
View File
@@ -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 &amp; Telemetrie</ZoneLabel>
<div className="grid gap-6 md:grid-cols-2">
<ServicesCard />
<VoiceLatencyCard />
</div>
<ServicesCard />
</section>
{/* Betrieb & Wissen */}