d3ro-voice/apps/admin/src/app/(admin)/pipelines/page.tsx
Yun Chan 5aa268970a feat(admin): read live back-office data in every console view
Models, pipelines, users, subscriptions, usage, audit log, ads, and releases
still rendered placeholder or duplicated implementations from the earlier
admin split. They now read the live back-office API, share one sidebar and
console theme, and the license issuer dialog uses the rotated signing key.
2026-09-16 23:24:35 +09:00

397 lines
22 KiB
TypeScript

// apps/admin/src/app/(admin)/pipelines/page.tsx
// D3RO Voice — AI & Voice Pipeline Intelligence Console (Phase 1~15.5 SSOT)
import { Box, Typography, Button } from '@mui/material'
import { fetchServerStats } from '@/lib/api-server'
import { C, FONT_SANS, FONT_MONO, panelSx, statusBadgeSx } from '@/lib/console-theme'
import { StatRing, TactileBadge, DoubleBezelCard } from '@d3ro/ui/components/ds'
export default async function PipelinesPage(): Promise<React.ReactElement> {
const stats = await fetchServerStats()
if (!stats.pipelines) {
return (
<DoubleBezelCard bezelPadding="6px" innerPadding="24px">
<Typography component="h1" sx={{ fontFamily: FONT_SANS, fontSize: '20px', fontWeight: 500, color: C.bright, mb: 1 }}>
Pipeline telemetry unavailable
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '12px', color: C.dim, lineHeight: 1.7 }}>
The backend does not currently expose measured pipeline telemetry. No simulated engine, latency, accuracy, or capacity values are shown.
</Typography>
</DoubleBezelCard>
)
}
const { whisper, ollama, realtimeVoice, ragVector, meetingIntelligence } = stats.pipelines
return (
<>
{/* Header Bar */}
<Box
sx={{
...panelSx,
minHeight: 76,
px: { xs: 2.5, md: 4 },
py: 2,
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'space-between',
gap: 2,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Box
sx={{
width: 4,
height: 28,
borderRadius: '999px',
background: 'linear-gradient(180deg, var(--d3-tag-cyan) 0%, var(--d3-accent-main) 100%)',
}}
/>
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography
component="h1"
sx={{
fontFamily: FONT_SANS,
fontSize: '18px',
fontWeight: 500,
color: C.bright,
letterSpacing: '-0.02em',
m: 0,
}}
>
AI & Voice Pipeline Matrix
</Typography>
<TactileBadge ledColor="green" ledPulse tone="success" mono>
ORCHESTRATOR ONLINE
</TactileBadge>
</Box>
<Typography
sx={{
fontFamily: FONT_MONO,
fontSize: '11px',
color: C.dim,
letterSpacing: '0.04em',
mt: 0.25,
}}
>
Local Whisper, Ollama v0.32.1, GPT-Realtime 2.1, Vector RAG, Diarization
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Button
variant="outlined"
size="small"
sx={{
fontFamily: FONT_SANS,
fontSize: '12px',
color: C.bright,
borderColor: C.borderHl,
bgcolor: 'var(--d3-bg-card-soft)',
borderRadius: '10px',
textTransform: 'none',
px: 2,
'&:hover': { borderColor: C.accentLight, bgcolor: 'var(--d3-bg-raised-soft)' },
}}
>
Run Diagnostic Ping
</Button>
</Box>
</Box>
{/* Main Grid: 5 Core Pipeline Domains */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* Top Split: Whisper STT & Ollama LLM */}
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', lg: '1fr 1fr' }, gap: 3 }}>
{/* Whisper STT Card */}
<DoubleBezelCard interactive bezelPadding="6px" innerPadding="24px">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<StatRing color="blue" size={48}>
<svg width="22" height="22" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 100-6 3 3 0 000 6z" />
</svg>
</StatRing>
<Box>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '16px', fontWeight: 500, color: C.bright }}>
Faster-Whisper STT Sidecar
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.cyanLight }}>
{whisper.activeModel}
</Typography>
</Box>
</Box>
<Box component="span" sx={statusBadgeSx('blue')}>
DEFAULT STT
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>AVG LATENCY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{whisper.avgLatencyMs}ms
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>SPEEDUP FACTOR</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}>
{whisper.speedupFactor}
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>GPU VRAM</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}>
{whisper.gpuVramUsage}
</Typography>
</Box>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.text, lineHeight: 1.6 }}>
<strong>Dual-Condition Flush:</strong> Audio buffer flushes parallel with model warm-up.<br />
<strong>Realtime Streaming:</strong> {whisper.partialStreamingFps} fps interim partial transcription in RecordingTip popup.<br />
<strong>Total Today:</strong> {whisper.totalTranscriptionsToday.toLocaleString()} voice transcriptions processed.
</Typography>
</Box>
</DoubleBezelCard>
{/* Bundled Ollama LLM Card */}
<DoubleBezelCard interactive bezelPadding="6px" innerPadding="24px">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<StatRing color="purple" size={48}>
<svg width="22" height="22" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</StatRing>
<Box>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '16px', fontWeight: 500, color: C.bright }}>
Bundled Ollama Runtime
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.purple400 }}>
{ollama.version} (119MB Pruned)
</Typography>
</Box>
</Box>
<Box component="span" sx={statusBadgeSx('purple')}>
LOCAL LLM
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>THROUGHPUT</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{ollama.tokensPerSecond} tok/s
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>CONTEXT LIMIT</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}>
{ollama.activeContextLimit}
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>VRAM OCCUPANCY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.purple400 }}>
{ollama.vramAllocated}
</Typography>
</Box>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.text, lineHeight: 1.6 }}>
<strong>Loaded Models:</strong> {ollama.loadedModels.join(', ')}<br />
<strong>NDJSON Streaming:</strong> Zero-latency token streaming for Auto Polish & AI Chat.<br />
<strong>Active Local Sessions:</strong> {ollama.activeSessions} concurrent local inference threads.
</Typography>
</Box>
</DoubleBezelCard>
</Box>
{/* Middle Split: Realtime Live Voice & Vector RAG */}
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', lg: '1fr 1fr' }, gap: 3 }}>
{/* GPT-Realtime 2.1 Voice Engine */}
<DoubleBezelCard interactive bezelPadding="6px" innerPadding="24px">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<StatRing color="orange" size={48}>
<svg width="22" height="22" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
</StatRing>
<Box>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '16px', fontWeight: 500, color: C.bright }}>
GPT-Realtime 2.1 Live Engine
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.orange400 }}>
{realtimeVoice.backend}
</Typography>
</Box>
</Box>
<Box component="span" sx={statusBadgeSx('orange')}>
PRO+ PREMIUM
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>LIVE STREAMS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.orange400 }}>
{realtimeVoice.activeStreams} Active
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>AUDIO RTT</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{realtimeVoice.avgAudioRttMs}ms
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>LOCAL FALLBACK</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}>
{realtimeVoice.localFallbackRate}
</Typography>
</Box>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.text, lineHeight: 1.6 }}>
<strong>Ultra-low Latency:</strong> Full-duplex bidirectional voice-to-voice stream.<br />
<strong>Auto Failover:</strong> Drops gracefully to Local STT + Ollama + TTS if network drops.<br />
<strong>Stream Uptime:</strong> {realtimeVoice.streamUptime}% across all regional connections.
</Typography>
</Box>
</DoubleBezelCard>
{/* SQLite Vector RAG Knowledge Base */}
<DoubleBezelCard interactive bezelPadding="6px" innerPadding="24px">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<StatRing color="green" size={48}>
<svg width="22" height="22" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
</StatRing>
<Box>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '16px', fontWeight: 500, color: C.bright }}>
SQLite Vector RAG Engine
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.green400 }}>
{ragVector.embeddingModel}
</Typography>
</Box>
</Box>
<Box component="span" sx={statusBadgeSx('green')}>
KNOWLEDGE BASE
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>INDEXED DOCS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{ragVector.indexedDocuments.toLocaleString()}
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>VECTOR CHUNKS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}>
{ragVector.totalVectorChunks.toLocaleString()}
</Typography>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>SEARCH HIT RATE</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}>
{ragVector.topHitRatePercent}%
</Typography>
</Box>
</Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.text, lineHeight: 1.6 }}>
<strong>Local Vector Store:</strong> Zero-cloud-leakage SQLite embeddings with cosine similarity.<br />
<strong>Average Search Latency:</strong> {ragVector.avgSearchLatencyMs}ms per 512-dim query.<br />
<strong>Semantic Q&A:</strong> Grounded context injection into Voice Mode & Meeting Summary.
</Typography>
</Box>
</DoubleBezelCard>
</Box>
{/* Bottom Full-Width: Meeting Intelligence & Speaker Diarization (Phase 14~15.5) */}
<DoubleBezelCard interactive bezelPadding="6px" innerPadding="24px">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<StatRing color="accent" size={52}>
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</StatRing>
<Box>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '18px', fontWeight: 500, color: C.bright }}>
Meeting Intelligence & Speaker Diarization (Phase 14~15.5)
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '12px', color: C.accentLight }}>
{meetingIntelligence.diarizationEngine}
</Typography>
</Box>
</Box>
<Box component="span" sx={statusBadgeSx('green')}>
PHASE 15.5 COMPLIANT
</Box>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(4, 1fr)' }, gap: 2, mb: 3 }}>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>SPEAKER ACCURACY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.green400 }}>
{meetingIntelligence.speakerAccuracyPercent}%
</Typography>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>
Pyannote + LLM Consensus
</Typography>
</Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>ACTIVE MEETINGS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.cyanLight }}>
{meetingIntelligence.activeMeetingSessions} Live
</Typography>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>
Realtime Captions & Record
</Typography>
</Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>TEMPLATES TODAY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.purple400 }}>
{meetingIntelligence.templatesGeneratedToday} Docs
</Typography>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>
Summary, Action Items, Jira
</Typography>
</Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: C.dim }}>MINDMAP EXPORTS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.orange400 }}>
{meetingIntelligence.mindmapsExported} Maps
</Typography>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>
Interactive Visual Graphs
</Typography>
</Box>
</Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '13px', color: C.text, lineHeight: 1.7 }}>
<strong>Speaker Attribution Architecture:</strong> Hybrid pipeline combining Pyannote 3.1 voiceprint embeddings with LLM conversational speaker inference.<br />
<strong>Multi-Document Synthesis (Phase 14.5):</strong> Simultaneous generation of Executive Summary, Action Item Checklist, Jira Issue Drafts, and Interactive Markdown Mindmaps.<br />
<strong>File Transcription Pipeline:</strong> High-speed sequential STT for uploaded MP3/WAV audio recordings up to 4 hours.
</Typography>
</Box>
</DoubleBezelCard>
</Box>
</>
)
}