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,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>
|
||||
<ServicesCard />
|
||||
</section>
|
||||
|
||||
{/* Betrieb & Wissen */}
|
||||
|
||||
Reference in New Issue
Block a user