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.
This commit is contained in:
Yun Chan 2026-09-16 23:24:35 +09:00
parent cfc58458a8
commit 5aa268970a
23 changed files with 187 additions and 174 deletions

9
apps/admin/AGENTS.md Normal file
View file

@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
<!-- END:nextjs-agent-rules -->

1
apps/admin/CLAUDE.md Normal file
View file

@ -0,0 +1 @@
@AGENTS.md

View file

@ -1,6 +1,7 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" /> import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View file

@ -99,7 +99,7 @@ function RewardKpis({ stats }: { stats: AdRewardStats }): React.ReactElement {
> >
{card.title} {card.title}
</Typography> </Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '22px', fontWeight: 700, color: C.bright, lineHeight: 1.25 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '22px', fontWeight: 500, color: C.bright, lineHeight: 1.25 }}>
{card.value} {card.value}
</Typography> </Typography>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>{card.subtext}</Typography> <Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>{card.subtext}</Typography>

View file

@ -71,7 +71,7 @@ export default async function AuditLogDetailPage({ params }: PageProps): Promise
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #3b82f6 0%, #8b5cf6 100%)', background: 'linear-gradient(180deg, var(--d3-accent-main) 0%, var(--d3-tag-purple) 100%)',
}} }}
/> />
<Box> <Box>
@ -131,7 +131,7 @@ export default async function AuditLogDetailPage({ params }: PageProps): Promise
sx={{ sx={{
p: 2, p: 2,
borderRadius: '12px', borderRadius: '12px',
bgcolor: 'rgba(10, 17, 31, 0.7)', bgcolor: 'var(--d3-bg-inset-soft)',
border: `1px solid ${C.border}`, border: `1px solid ${C.border}`,
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
fontSize: '13px', fontSize: '13px',

View file

@ -72,7 +72,7 @@ export default async function AuditLogPage({ searchParams }: PageProps): Promise
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-accent-light) 0%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>
@ -182,7 +182,7 @@ export default async function AuditLogPage({ searchParams }: PageProps): Promise
borderRadius: '8px', borderRadius: '8px',
textTransform: 'none', textTransform: 'none',
px: 1.5, px: 1.5,
'&:hover': { bgcolor: 'rgba(59, 130, 246, 0.15)', borderColor: C.accentLight }, '&:hover': { bgcolor: 'var(--d3-accent-glow)', borderColor: C.accentLight },
}} }}
> >
Inspect Inspect

View file

@ -345,7 +345,7 @@ export default function ServiceModelsPage(): React.ReactElement {
width: 4, width: 4,
height: 34, height: 34,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #06b6d4 0%, #8b5cf6 50%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-tag-cyan) 0%, var(--d3-tag-purple) 50%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>
@ -457,8 +457,8 @@ export default function ServiceModelsPage(): React.ReactElement {
width: 44, width: 44,
height: 44, height: 44,
borderRadius: '12px', borderRadius: '12px',
bgcolor: 'rgba(6, 182, 212, 0.15)', bgcolor: 'var(--d3-tag-cyan)',
border: '1px solid rgba(6, 182, 212, 0.3)', border: '1px solid var(--d3-tag-cyan)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -483,7 +483,7 @@ export default function ServiceModelsPage(): React.ReactElement {
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.cyanLight, bgcolor: 'rgba(6, 182, 212, 0.1)', px: 1.5, py: 0.75, borderRadius: '8px', border: `1px solid ${C.borderHl}` }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.cyanLight, bgcolor: 'var(--d3-tag-cyan)', px: 1.5, py: 0.75, borderRadius: '8px', border: `1px solid ${C.borderHl}` }}>
🛡 Auto Failover Route: {sttEndpoints.map((e) => e.providerType).join(' ➔ ')} 🛡 Auto Failover Route: {sttEndpoints.map((e) => e.providerType).join(' ➔ ')}
</Typography> </Typography>
</Box> </Box>
@ -530,7 +530,7 @@ export default function ServiceModelsPage(): React.ReactElement {
</Box> </Box>
) : ( ) : (
sttEndpoints.map((ep) => ( sttEndpoints.map((ep) => (
<Box component="tr" key={ep.id} sx={{ bgcolor: ep.isDefault ? 'rgba(59, 130, 246, 0.05)' : 'transparent' }}> <Box component="tr" key={ep.id} sx={{ bgcolor: ep.isDefault ? 'var(--d3-accent-glow)' : 'transparent' }}>
<Box component="td" sx={{ fontFamily: FONT_MONO, fontSize: '12px', color: C.cyanLight, fontWeight: 500 }}> <Box component="td" sx={{ fontFamily: FONT_MONO, fontSize: '12px', color: C.cyanLight, fontWeight: 500 }}>
#{ep.fallbackPriority} #{ep.fallbackPriority}
</Box> </Box>
@ -556,7 +556,7 @@ export default function ServiceModelsPage(): React.ReactElement {
</Box> </Box>
<Box component="td"> <Box component="td">
{ep.isDefault ? ( {ep.isDefault ? (
<Box component="span" sx={{ px: 1, py: 0.3, borderRadius: '4px', bgcolor: 'rgba(34, 197, 94, 0.2)', color: C.green400, fontSize: '10px', fontWeight: 500 }}> <Box component="span" sx={{ px: 1, py: 0.3, borderRadius: '4px', bgcolor: 'var(--d3-status-success)', color: C.green400, fontSize: '10px', fontWeight: 500 }}>
DEFAULT DEFAULT
</Box> </Box>
) : ( ) : (
@ -576,10 +576,10 @@ export default function ServiceModelsPage(): React.ReactElement {
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
fontSize: '10px', fontSize: '10px',
color: C.green400, color: C.green400,
borderColor: 'rgba(34, 197, 94, 0.4)', borderColor: 'var(--d3-status-success)',
px: 1, px: 1,
py: 0.25, py: 0.25,
'&:hover': { bgcolor: 'rgba(34, 197, 94, 0.15)', borderColor: C.green400 }, '&:hover': { bgcolor: 'var(--d3-status-success)', borderColor: C.green400 },
}} }}
> >
Set Default Set Default
@ -596,7 +596,7 @@ export default function ServiceModelsPage(): React.ReactElement {
borderColor: C.borderHl, borderColor: C.borderHl,
px: 1, px: 1,
py: 0.25, py: 0.25,
'&:hover': { bgcolor: 'rgba(59, 130, 246, 0.15)', borderColor: C.accentLight }, '&:hover': { bgcolor: 'var(--d3-accent-glow)', borderColor: C.accentLight },
}} }}
> >
{sttPingStatus[ep.id] || '⚡ Test'} {sttPingStatus[ep.id] || '⚡ Test'}
@ -676,7 +676,7 @@ export default function ServiceModelsPage(): React.ReactElement {
<Box component="td" sx={{ fontFamily: FONT_MONO, fontSize: '12px', color: C.bright, fontWeight: 600 }}> <Box component="td" sx={{ fontFamily: FONT_MONO, fontSize: '12px', color: C.bright, fontWeight: 600 }}>
{ep.modelId} {ep.modelId}
{ep.isDefault && ( {ep.isDefault && (
<Box component="span" sx={{ ml: 1, px: 0.75, py: 0.1, borderRadius: '4px', bgcolor: 'rgba(59, 130, 246, 0.2)', color: C.cyanLight, fontSize: '9px' }}> <Box component="span" sx={{ ml: 1, px: 0.75, py: 0.1, borderRadius: '4px', bgcolor: 'var(--d3-accent-glow)', color: C.cyanLight, fontSize: '9px' }}>
DEFAULT DEFAULT
</Box> </Box>
)} )}
@ -716,7 +716,7 @@ export default function ServiceModelsPage(): React.ReactElement {
borderColor: C.borderHl, borderColor: C.borderHl,
px: 1, px: 1,
py: 0.25, py: 0.25,
'&:hover': { bgcolor: 'rgba(59, 130, 246, 0.15)', borderColor: C.accentLight }, '&:hover': { bgcolor: 'var(--d3-accent-glow)', borderColor: C.accentLight },
}} }}
> >
Delete Delete
@ -740,11 +740,11 @@ export default function ServiceModelsPage(): React.ReactElement {
fullWidth fullWidth
PaperProps={{ PaperProps={{
sx: { sx: {
bgcolor: 'rgba(17, 26, 48, 0.95)', bgcolor: 'var(--d3-bg-card-soft)',
backdropFilter: 'blur(32px)', backdropFilter: 'blur(32px)',
border: `1px solid ${C.borderHl}`, border: `1px solid ${C.borderHl}`,
borderRadius: '20px', borderRadius: '20px',
boxShadow: '0 24px 60px rgba(3, 7, 18, 0.8)', boxShadow: '0 24px 60px var(--d3-scrim)',
}, },
}} }}
> >
@ -772,7 +772,7 @@ export default function ServiceModelsPage(): React.ReactElement {
if (preset.prompt) setSttPrompt(preset.prompt) if (preset.prompt) setSttPrompt(preset.prompt)
}} }}
sx={{ sx={{
bgcolor: 'rgba(10, 17, 31, 0.7)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
'& fieldset': { borderColor: C.border }, '& fieldset': { borderColor: C.border },
@ -796,7 +796,7 @@ export default function ServiceModelsPage(): React.ReactElement {
size="small" size="small"
placeholder="e.g. Groq Whisper LPU Fast" placeholder="e.g. Groq Whisper LPU Fast"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px' }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px' },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -807,7 +807,7 @@ export default function ServiceModelsPage(): React.ReactElement {
label="Provider Type" label="Provider Type"
onChange={(e) => setSttProviderType(e.target.value as STTProviderCategory)} onChange={(e) => setSttProviderType(e.target.value as STTProviderCategory)}
sx={{ sx={{
bgcolor: 'rgba(10, 17, 31, 0.7)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
'& fieldset': { borderColor: C.border }, '& fieldset': { borderColor: C.border },
@ -834,7 +834,7 @@ export default function ServiceModelsPage(): React.ReactElement {
size="small" size="small"
placeholder="https://api.groq.com/openai/v1/audio/transcriptions" placeholder="https://api.groq.com/openai/v1/audio/transcriptions"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -849,7 +849,7 @@ export default function ServiceModelsPage(): React.ReactElement {
size="small" size="small"
placeholder={sttEditingId ? '•••••••• (변경 시 입력)' : 'gsk_... or sk-... or token'} placeholder={sttEditingId ? '•••••••• (변경 시 입력)' : 'gsk_... or sk-... or token'}
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -862,7 +862,7 @@ export default function ServiceModelsPage(): React.ReactElement {
size="small" size="small"
placeholder="e.g. whisper-large-v3-turbo, nova-3" placeholder="e.g. whisper-large-v3-turbo, nova-3"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -876,7 +876,7 @@ export default function ServiceModelsPage(): React.ReactElement {
label="Dispatch Method" label="Dispatch Method"
onChange={(e) => setSttMethod(e.target.value as SttProviderEndpoint['method'])} onChange={(e) => setSttMethod(e.target.value as SttProviderEndpoint['method'])}
sx={{ sx={{
bgcolor: 'rgba(10, 17, 31, 0.7)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
'& fieldset': { borderColor: C.border }, '& fieldset': { borderColor: C.border },
@ -897,7 +897,7 @@ export default function ServiceModelsPage(): React.ReactElement {
size="small" size="small"
placeholder="ko, en, auto" placeholder="ko, en, auto"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -911,7 +911,7 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
size="small" size="small"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -926,7 +926,7 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
size="small" size="small"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -937,7 +937,7 @@ export default function ServiceModelsPage(): React.ReactElement {
size="small" size="small"
placeholder="e.g. D3RO, Kubernetes, React, LLM" placeholder="e.g. D3RO, Kubernetes, React, LLM"
sx={{ sx={{
'& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px' }, '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px' },
'& .MuiInputLabel-root': { color: C.dim }, '& .MuiInputLabel-root': { color: C.dim },
}} }}
/> />
@ -950,11 +950,11 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
helperText="변경 사유를 3자 이상 입력하세요." helperText="변경 사유를 3자 이상 입력하세요."
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px' } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px' } }}
/> />
{/* Test Connection Inside Modal */} {/* Test Connection Inside Modal */}
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.6)', border: `1px solid ${C.border}`, display: 'flex', flexDirection: 'column', gap: 1.5 }}> <Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}`, display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '13px', fontWeight: 600, color: C.bright }}> <Typography sx={{ fontFamily: FONT_SANS, fontSize: '13px', fontWeight: 600, color: C.bright }}>
Live Connection Test (Synthetic Audio Ping) Live Connection Test (Synthetic Audio Ping)
@ -970,7 +970,7 @@ export default function ServiceModelsPage(): React.ReactElement {
color: C.bright, color: C.bright,
borderColor: C.borderHl, borderColor: C.borderHl,
textTransform: 'none', textTransform: 'none',
'&:hover': { borderColor: C.accentLight, bgcolor: 'rgba(59, 130, 246, 0.15)' }, '&:hover': { borderColor: C.accentLight, bgcolor: 'var(--d3-accent-glow)' },
}} }}
> >
{testingInModal ? <CircularProgress size={14} sx={{ color: C.bright, mr: 1 }} /> : null} {testingInModal ? <CircularProgress size={14} sx={{ color: C.bright, mr: 1 }} /> : null}
@ -985,8 +985,8 @@ export default function ServiceModelsPage(): React.ReactElement {
sx={{ sx={{
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
fontSize: '12px', fontSize: '12px',
borderColor: sttTestModalResult.success ? 'rgba(34, 197, 94, 0.5)' : 'rgba(239, 68, 68, 0.5)', borderColor: sttTestModalResult.success ? 'var(--d3-status-success)' : 'var(--d3-status-danger)',
color: sttTestModalResult.success ? C.green400 : '#f87171', color: sttTestModalResult.success ? C.green400 : 'var(--d3-status-danger)',
}} }}
> >
{sttTestModalResult.message} {sttTestModalResult.message}
@ -1013,11 +1013,11 @@ export default function ServiceModelsPage(): React.ReactElement {
fullWidth fullWidth
PaperProps={{ PaperProps={{
sx: { sx: {
bgcolor: 'rgba(17, 26, 48, 0.95)', bgcolor: 'var(--d3-bg-card-soft)',
backdropFilter: 'blur(32px)', backdropFilter: 'blur(32px)',
border: `1px solid ${C.borderHl}`, border: `1px solid ${C.borderHl}`,
borderRadius: '20px', borderRadius: '20px',
boxShadow: '0 24px 60px rgba(3, 7, 18, 0.8)', boxShadow: '0 24px 60px var(--d3-scrim)',
}, },
}} }}
> >
@ -1042,7 +1042,7 @@ export default function ServiceModelsPage(): React.ReactElement {
setLlmCompletionCost(preset.completionCost) setLlmCompletionCost(preset.completionCost)
}} }}
sx={{ sx={{
bgcolor: 'rgba(10, 17, 31, 0.7)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
'& fieldset': { borderColor: C.border }, '& fieldset': { borderColor: C.border },
@ -1062,7 +1062,7 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
fullWidth fullWidth
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }}
/> />
<TextField <TextField
label="Display Name" label="Display Name"
@ -1071,7 +1071,7 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
fullWidth fullWidth
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px' } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px' } }}
/> />
<FormControl fullWidth size="small"> <FormControl fullWidth size="small">
<InputLabel sx={{ color: C.dim }}>Provider Category</InputLabel> <InputLabel sx={{ color: C.dim }}>Provider Category</InputLabel>
@ -1079,7 +1079,7 @@ export default function ServiceModelsPage(): React.ReactElement {
value={llmProvider} value={llmProvider}
label="Provider Category" label="Provider Category"
onChange={(e) => setLlmProvider(e.target.value)} onChange={(e) => setLlmProvider(e.target.value)}
sx={{ bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px' }} sx={{ bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px' }}
> >
<MenuItem value="Local Sidecar">Local Sidecar</MenuItem> <MenuItem value="Local Sidecar">Local Sidecar</MenuItem>
<MenuItem value="Ollama Local">Ollama Local Engine</MenuItem> <MenuItem value="Ollama Local">Ollama Local Engine</MenuItem>
@ -1095,7 +1095,7 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
fullWidth fullWidth
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }}
/> />
<TextField <TextField
label="API Key (Optional)" label="API Key (Optional)"
@ -1104,7 +1104,7 @@ export default function ServiceModelsPage(): React.ReactElement {
onChange={(e) => setLlmApiKey(e.target.value)} onChange={(e) => setLlmApiKey(e.target.value)}
fullWidth fullWidth
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }}
/> />
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}> <Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}>
<TextField <TextField
@ -1115,7 +1115,7 @@ export default function ServiceModelsPage(): React.ReactElement {
onChange={(e) => setLlmPromptCost(e.target.value)} onChange={(e) => setLlmPromptCost(e.target.value)}
required required
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }}
/> />
<TextField <TextField
label="Completion Cost ($/1K)" label="Completion Cost ($/1K)"
@ -1125,7 +1125,7 @@ export default function ServiceModelsPage(): React.ReactElement {
onChange={(e) => setLlmCompletionCost(e.target.value)} onChange={(e) => setLlmCompletionCost(e.target.value)}
required required
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px', fontFamily: FONT_MONO } }}
/> />
</Box> </Box>
<TextField <TextField
@ -1135,7 +1135,7 @@ export default function ServiceModelsPage(): React.ReactElement {
required required
helperText="생성 사유를 3자 이상 입력하세요." helperText="생성 사유를 3자 이상 입력하세요."
size="small" size="small"
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(10, 17, 31, 0.7)', color: C.bright, borderRadius: '10px' } }} sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'var(--d3-bg-inset-soft)', color: C.bright, borderRadius: '10px' } }}
/> />
</DialogContent> </DialogContent>
<DialogActions sx={{ px: 3, pb: 3, pt: 1, borderTop: `1px solid ${C.border}` }}> <DialogActions sx={{ px: 3, pb: 3, pt: 1, borderTop: `1px solid ${C.border}` }}>

View file

@ -128,7 +128,7 @@ export default async function AdminOverviewPage(): Promise<React.ReactElement> {
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #06b6d4 0%, #3b82f6 50%, #8b5cf6 100%)', background: 'linear-gradient(180deg, var(--d3-tag-cyan) 0%, var(--d3-accent-main) 50%, var(--d3-tag-purple) 100%)',
}} }}
/> />
<Box> <Box>
@ -174,11 +174,11 @@ export default async function AdminOverviewPage(): Promise<React.ReactElement> {
fontSize: '12px', fontSize: '12px',
color: C.bright, color: C.bright,
borderColor: C.borderHl, borderColor: C.borderHl,
bgcolor: 'rgba(17, 26, 48, 0.6)', bgcolor: 'var(--d3-bg-card-soft)',
borderRadius: '10px', borderRadius: '10px',
textTransform: 'none', textTransform: 'none',
px: 2, px: 2,
'&:hover': { borderColor: C.accentLight, bgcolor: 'rgba(26, 38, 68, 0.8)' }, '&:hover': { borderColor: C.accentLight, bgcolor: 'var(--d3-bg-raised-soft)' },
}} }}
> >
View Pipelines Console View Pipelines Console
@ -352,12 +352,12 @@ export default async function AdminOverviewPage(): Promise<React.ReactElement> {
sx={{ sx={{
p: 2, p: 2,
borderRadius: '14px', borderRadius: '14px',
bgcolor: 'rgba(10, 17, 31, 0.65)', bgcolor: 'var(--d3-bg-inset-soft)',
border: `1px solid ${C.border}`, border: `1px solid ${C.border}`,
transition: 'all 0.2s ease', transition: 'all 0.2s ease',
'&:hover': { '&:hover': {
borderColor: C.accentLight, borderColor: C.accentLight,
bgcolor: 'rgba(17, 26, 48, 0.8)', bgcolor: 'var(--d3-bg-card-soft)',
transform: 'translateY(-2px)', transform: 'translateY(-2px)',
}, },
}} }}
@ -371,8 +371,8 @@ export default async function AdminOverviewPage(): Promise<React.ReactElement> {
width: 8, width: 8,
height: 8, height: 8,
borderRadius: '50%', borderRadius: '50%',
bgcolor: node.status === 'operational' ? '#10b981' : node.status === 'degraded' ? '#f59e0b' : '#ef4444', bgcolor: node.status === 'operational' ? 'var(--d3-status-success)' : node.status === 'degraded' ? 'var(--d3-status-warning)' : 'var(--d3-status-danger)',
boxShadow: `0 0 8px ${node.status === 'operational' ? '#10b981' : node.status === 'degraded' ? '#f59e0b' : '#ef4444'}`, boxShadow: `0 0 8px ${node.status === 'operational' ? 'var(--d3-status-success)' : node.status === 'degraded' ? 'var(--d3-status-warning)' : 'var(--d3-status-danger)'}`,
}} }}
/> />
</Box> </Box>

View file

@ -44,7 +44,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
width: 4, width: 4,
height: 28, height: 28,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #06b6d4 0%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-tag-cyan) 0%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>
@ -89,11 +89,11 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
fontSize: '12px', fontSize: '12px',
color: C.bright, color: C.bright,
borderColor: C.borderHl, borderColor: C.borderHl,
bgcolor: 'rgba(17, 26, 48, 0.6)', bgcolor: 'var(--d3-bg-card-soft)',
borderRadius: '10px', borderRadius: '10px',
textTransform: 'none', textTransform: 'none',
px: 2, px: 2,
'&:hover': { borderColor: C.accentLight, bgcolor: 'rgba(26, 38, 68, 0.8)' }, '&:hover': { borderColor: C.accentLight, bgcolor: 'var(--d3-bg-raised-soft)' },
}} }}
> >
Run Diagnostic Ping Run Diagnostic Ping
@ -129,19 +129,19 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>AVG LATENCY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{whisper.avgLatencyMs}ms {whisper.avgLatencyMs}ms
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>SPEEDUP FACTOR</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}>
{whisper.speedupFactor} {whisper.speedupFactor}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>GPU VRAM</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}>
{whisper.gpuVramUsage} {whisper.gpuVramUsage}
@ -149,7 +149,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.5)', border: `1px solid ${C.border}` }}> <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 }}> <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>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>Realtime Streaming:</strong> {whisper.partialStreamingFps} fps interim partial transcription in RecordingTip popup.<br />
@ -182,19 +182,19 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>THROUGHPUT</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{ollama.tokensPerSecond} tok/s {ollama.tokensPerSecond} tok/s
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>CONTEXT LIMIT</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}>
{ollama.activeContextLimit} {ollama.activeContextLimit}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>VRAM OCCUPANCY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.purple400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.purple400 }}>
{ollama.vramAllocated} {ollama.vramAllocated}
@ -202,7 +202,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.5)', border: `1px solid ${C.border}` }}> <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 }}> <Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.text, lineHeight: 1.6 }}>
<strong>Loaded Models:</strong> {ollama.loadedModels.join(', ')}<br /> <strong>Loaded Models:</strong> {ollama.loadedModels.join(', ')}<br />
<strong>NDJSON Streaming:</strong> Zero-latency token streaming for Auto Polish & AI Chat.<br /> <strong>NDJSON Streaming:</strong> Zero-latency token streaming for Auto Polish & AI Chat.<br />
@ -238,19 +238,19 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>LIVE STREAMS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.orange400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.orange400 }}>
{realtimeVoice.activeStreams} Active {realtimeVoice.activeStreams} Active
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>AUDIO RTT</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{realtimeVoice.avgAudioRttMs}ms {realtimeVoice.avgAudioRttMs}ms
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>LOCAL FALLBACK</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}>
{realtimeVoice.localFallbackRate} {realtimeVoice.localFallbackRate}
@ -258,7 +258,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.5)', border: `1px solid ${C.border}` }}> <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 }}> <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>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>Auto Failover:</strong> Drops gracefully to Local STT + Ollama + TTS if network drops.<br />
@ -291,19 +291,19 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1.5, mb: 2.5 }}>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>INDEXED DOCS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.bright }}>
{ragVector.indexedDocuments.toLocaleString()} {ragVector.indexedDocuments.toLocaleString()}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>VECTOR CHUNKS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.cyanLight }}>
{ragVector.totalVectorChunks.toLocaleString()} {ragVector.totalVectorChunks.toLocaleString()}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>SEARCH HIT RATE</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '16px', fontWeight: 500, color: C.green400 }}>
{ragVector.topHitRatePercent}% {ragVector.topHitRatePercent}%
@ -311,7 +311,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
</Box> </Box>
<Box sx={{ p: 1.5, borderRadius: '10px', bgcolor: 'rgba(10, 17, 31, 0.5)', border: `1px solid ${C.border}` }}> <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 }}> <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>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>Average Search Latency:</strong> {ragVector.avgSearchLatencyMs}ms per 512-dim query.<br />
@ -345,7 +345,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(4, 1fr)' }, gap: 2, mb: 3 }}> <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: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>SPEAKER ACCURACY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.green400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.green400 }}>
{meetingIntelligence.speakerAccuracyPercent}% {meetingIntelligence.speakerAccuracyPercent}%
@ -354,7 +354,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
Pyannote + LLM Consensus Pyannote + LLM Consensus
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>ACTIVE MEETINGS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.cyanLight }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.cyanLight }}>
{meetingIntelligence.activeMeetingSessions} Live {meetingIntelligence.activeMeetingSessions} Live
@ -363,7 +363,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
Realtime Captions & Record Realtime Captions & Record
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>TEMPLATES TODAY</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.purple400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.purple400 }}>
{meetingIntelligence.templatesGeneratedToday} Docs {meetingIntelligence.templatesGeneratedToday} Docs
@ -372,7 +372,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
Summary, Action Items, Jira Summary, Action Items, Jira
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <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: '10px', color: C.dim }}>MINDMAP EXPORTS</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.orange400 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 500, color: C.orange400 }}>
{meetingIntelligence.mindmapsExported} Maps {meetingIntelligence.mindmapsExported} Maps
@ -383,7 +383,7 @@ export default async function PipelinesPage(): Promise<React.ReactElement> {
</Box> </Box>
</Box> </Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.5)', border: `1px solid ${C.border}` }}> <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 }}> <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>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>Multi-Document Synthesis (Phase 14.5):</strong> Simultaneous generation of Executive Summary, Action Item Checklist, Jira Issue Drafts, and Interactive Markdown Mindmaps.<br />

View file

@ -165,7 +165,7 @@ function KpiCards({ hub }: { hub: ReleaseHub }): React.ReactElement {
> >
{card.title} {card.title}
</Typography> </Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '22px', fontWeight: 700, color: C.bright, lineHeight: 1.25 }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '22px', fontWeight: 500, color: C.bright, lineHeight: 1.25 }}>
{card.value} {card.value}
</Typography> </Typography>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>{card.subtext}</Typography> <Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>{card.subtext}</Typography>

View file

@ -80,7 +80,7 @@ export default async function SubscriptionDetailPage({ params }: PageProps): Pro
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #a855f7 0%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-tag-purple) 0%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>

View file

@ -31,7 +31,7 @@ export function NewSubscriptionClient({ initialUserId }: NewSubscriptionClientPr
placeholder="Supabase user UUID" placeholder="Supabase user UUID"
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
bgcolor: 'rgba(10, 17, 31, 0.7)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
fontFamily: FONT_MONO, fontFamily: FONT_MONO,

View file

@ -39,7 +39,7 @@ export default async function NewSubscriptionPage({ searchParams }: PageProps):
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #a855f7 0%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-tag-purple) 0%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>

View file

@ -115,7 +115,7 @@ export default async function AdminSubscriptionsPage({ searchParams }: PageProps
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #a855f7 0%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-tag-purple) 0%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>
@ -305,7 +305,7 @@ export default async function AdminSubscriptionsPage({ searchParams }: PageProps
borderRadius: '8px', borderRadius: '8px',
textTransform: 'none', textTransform: 'none',
px: 1.5, px: 1.5,
'&:hover': { bgcolor: 'rgba(59, 130, 246, 0.15)', borderColor: C.accentLight }, '&:hover': { bgcolor: 'var(--d3-accent-glow)', borderColor: C.accentLight },
}} }}
> >
Edit Edit

View file

@ -84,7 +84,7 @@ export default async function AdminUsagePage(): Promise<React.ReactElement> {
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #10b981 0%, #3b82f6 100%)', background: 'linear-gradient(180deg, var(--d3-status-success) 0%, var(--d3-accent-main) 100%)',
}} }}
/> />
<Box> <Box>
@ -221,12 +221,12 @@ export default async function AdminUsagePage(): Promise<React.ReactElement> {
{(f.totalCalls ?? f.callCount ?? 0).toLocaleString()} calls ({(f.percentage ?? 10).toFixed(1)}%) ${(f.estimatedCostUsd ?? f.totalCost).toFixed(2)} {(f.totalCalls ?? f.callCount ?? 0).toLocaleString()} calls ({(f.percentage ?? 10).toFixed(1)}%) ${(f.estimatedCostUsd ?? f.totalCost).toFixed(2)}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ width: '100%', height: 8, bgcolor: 'rgba(10, 17, 31, 0.8)', borderRadius: '999px', overflow: 'hidden', border: `1px solid ${C.border}` }}> <Box sx={{ width: '100%', height: 8, bgcolor: 'var(--d3-bg-inset-soft)', borderRadius: '999px', overflow: 'hidden', border: `1px solid ${C.border}` }}>
<Box <Box
sx={{ sx={{
width: `${f.percentage ?? 10}%`, width: `${f.percentage ?? 10}%`,
height: '100%', height: '100%',
background: 'linear-gradient(90deg, #06b6d4 0%, #3b82f6 50%, #8b5cf6 100%)', background: 'linear-gradient(90deg, var(--d3-tag-cyan) 0%, var(--d3-accent-main) 50%, var(--d3-tag-purple) 100%)',
borderRadius: '999px', borderRadius: '999px',
transition: 'width 0.8s cubic-bezier(0.16, 1, 0.3, 1)', transition: 'width 0.8s cubic-bezier(0.16, 1, 0.3, 1)',
}} }}

View file

@ -84,7 +84,7 @@ export default async function AdminUserDetailPage({ params }: PageProps): Promis
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #3b82f6 0%, #8b5cf6 100%)', background: 'linear-gradient(180deg, var(--d3-accent-main) 0%, var(--d3-tag-purple) 100%)',
}} }}
/> />
<Box> <Box>
@ -138,15 +138,15 @@ export default async function AdminUserDetailPage({ params }: PageProps): Promis
height: 48, height: 48,
borderRadius: '14px', borderRadius: '14px',
background: isProPlus background: isProPlus
? 'linear-gradient(135deg, #a855f7 0%, #3b82f6 100%)' ? 'linear-gradient(135deg, var(--d3-tag-purple) 0%, var(--d3-accent-main) 100%)'
: 'linear-gradient(135deg, #10b981 0%, #3b82f6 100%)', : 'linear-gradient(135deg, var(--d3-status-success) 0%, var(--d3-accent-main) 100%)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
color: '#ffffff', color: 'var(--d3-text-inverse)',
fontWeight: 500, fontWeight: 500,
fontSize: '20px', fontSize: '20px',
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4)', boxShadow: '0 0 20px var(--d3-accent-glow)',
}} }}
> >
{((profile.name as string) || (profile.email as string) || '?').charAt(0).toUpperCase()} {((profile.name as string) || (profile.email as string) || '?').charAt(0).toUpperCase()}
@ -188,7 +188,7 @@ export default async function AdminUserDetailPage({ params }: PageProps): Promis
<Row label="Cancel Scheduled" value={sub?.cancel_at ? new Date(sub.cancel_at as string).toLocaleDateString() : 'Not scheduled'} /> <Row label="Cancel Scheduled" value={sub?.cancel_at ? new Date(sub.cancel_at as string).toLocaleDateString() : 'Not scheduled'} />
</Box> </Box>
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', fontWeight: 600, color: C.bright, mb: 1 }}> <Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', fontWeight: 600, color: C.bright, mb: 1 }}>
Entitlement source Entitlement source
</Typography> </Typography>
@ -200,7 +200,7 @@ export default async function AdminUserDetailPage({ params }: PageProps): Promis
</Box> </Box>
{/* Tier-based Enabled Features */} {/* Tier-based Enabled Features */}
<Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'rgba(10, 17, 31, 0.7)', border: `1px solid ${C.border}` }}> <Box sx={{ p: 2, borderRadius: '12px', bgcolor: 'var(--d3-bg-inset-soft)', border: `1px solid ${C.border}` }}>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', fontWeight: 600, color: C.bright, mb: 1 }}> <Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', fontWeight: 600, color: C.bright, mb: 1 }}>
Enabled Features Enabled Features
</Typography> </Typography>

View file

@ -68,7 +68,7 @@ export default async function AdminUsersPage({ searchParams }: PageProps): Promi
width: 4, width: 4,
height: 32, height: 32,
borderRadius: '999px', borderRadius: '999px',
background: 'linear-gradient(180deg, #3b82f6 0%, #8b5cf6 100%)', background: 'linear-gradient(180deg, var(--d3-accent-main) 0%, var(--d3-tag-purple) 100%)',
}} }}
/> />
<Box> <Box>
@ -163,17 +163,17 @@ export default async function AdminUsersPage({ searchParams }: PageProps): Promi
height: 34, height: 34,
borderRadius: '10px', borderRadius: '10px',
background: isProPlus background: isProPlus
? 'linear-gradient(135deg, #a855f7 0%, #3b82f6 100%)' ? 'linear-gradient(135deg, var(--d3-tag-purple) 0%, var(--d3-accent-main) 100%)'
: isPro : isPro
? 'linear-gradient(135deg, #10b981 0%, #3b82f6 100%)' ? 'linear-gradient(135deg, var(--d3-status-success) 0%, var(--d3-accent-main) 100%)'
: 'linear-gradient(135deg, #64748b 0%, #475569 100%)', : 'linear-gradient(135deg, var(--d3-text-label) 0%, var(--d3-text-label) 100%)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
color: '#ffffff', color: 'var(--d3-text-inverse)',
fontWeight: 500, fontWeight: 500,
fontSize: '13px', fontSize: '13px',
boxShadow: isProPlus ? '0 0 12px rgba(168, 85, 247, 0.4)' : 'none', boxShadow: isProPlus ? '0 0 12px var(--d3-tag-purple)' : 'none',
}} }}
> >
{u.name.charAt(0)} {u.name.charAt(0)}
@ -238,7 +238,7 @@ export default async function AdminUsersPage({ searchParams }: PageProps): Promi
borderRadius: '8px', borderRadius: '8px',
textTransform: 'none', textTransform: 'none',
px: 1.5, px: 1.5,
'&:hover': { bgcolor: 'rgba(59, 130, 246, 0.15)', borderColor: C.accentLight }, '&:hover': { bgcolor: 'var(--d3-accent-glow)', borderColor: C.accentLight },
}} }}
> >
Manage Manage

View file

@ -1,29 +1,28 @@
/* D3RO Voice Admin CRM — "Midnight Glass v2" Global Styles */ /* D3RO Voice Admin CRM — "Midnight Glass v3" Global Styles */
/* 폰트는 layout.tsx <head> Pretendard Variable 다이나믹 서브셋 링크로 로드한다 /* 폰트는 layout.tsx <head> Pretendard Variable 다이나믹 서브셋 링크로 로드한다
(렌더를 막는 @import 이중 로드는 제거). (렌더를 막는 @import 이중 로드는 제거).
변수는 @d3ro/ui SSOT가 MuiCssBaseline로 주입하는 --d3-* 그대로 쓴다 색은 @d3ro/ui SSOT가 MuiCssBaseline로 주입하는 --d3-* 쓴다 (로컬 hex 폴백 없음). */
(관리자는 고정 다크: 미지정 아래 폴백). */
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
/* 의미적 강조는 세미볼드(600)까지 — 볼드(700+) 타이포그래피 금지 */ /* 의미적 강조는 세미볼드(600)까지 — 볼드(700+) 타이포그래피 금지 */
strong, b { font-weight: 600; } strong, b { font-weight: var(--d3-weight-strong); }
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: var(--d3-bg-app, #070b16); background-color: var(--d3-bg-app);
color: var(--d3-text-primary, #eef2fb); color: var(--d3-text-primary);
font-family: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif; font-family: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
overflow-x: hidden; overflow-x: hidden;
background-image: background-image:
radial-gradient(ellipse 60% 40% at 10% 0%, rgba(6, 182, 212, 0.08) 0%, transparent 60%), radial-gradient(ellipse 60% 40% at 10% 0%, color-mix(in srgb, var(--d3-tag-cyan) 8%, transparent) 0%, transparent 60%),
radial-gradient(ellipse 50% 50% at 90% 10%, rgba(59, 130, 246, 0.09) 0%, transparent 60%), radial-gradient(ellipse 50% 50% at 90% 10%, color-mix(in srgb, var(--d3-accent-main) 9%, transparent) 0%, transparent 60%),
radial-gradient(ellipse 40% 40% at 50% 90%, rgba(139, 92, 246, 0.06) 0%, transparent 50%); radial-gradient(ellipse 40% 40% at 50% 90%, color-mix(in srgb, var(--d3-tag-purple) 6%, transparent) 0%, transparent 50%);
background-attachment: fixed; background-attachment: fixed;
} }
@ -34,7 +33,12 @@ body {
left: 0; left: 0;
right: 0; right: 0;
height: 320px; height: 320px;
background: radial-gradient(50% 100% at 50% 0%, rgba(59, 130, 246, 0.12) 0%, rgba(6, 182, 212, 0.04) 40%, transparent 100%); background: radial-gradient(
50% 100% at 50% 0%,
color-mix(in srgb, var(--d3-accent-main) 12%, transparent) 0%,
color-mix(in srgb, var(--d3-tag-cyan) 4%, transparent) 40%,
transparent 100%
);
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 0;
} }
@ -42,8 +46,8 @@ body {
/* High-End Subtle Grid Pattern */ /* High-End Subtle Grid Pattern */
.bg-grid { .bg-grid {
background-image: background-image:
linear-gradient(to right, rgba(148, 180, 255, 0.03) 1px, transparent 1px), linear-gradient(to right, var(--d3-glass-hairline) 1px, transparent 1px),
linear-gradient(to bottom, rgba(148, 180, 255, 0.03) 1px, transparent 1px); linear-gradient(to bottom, var(--d3-glass-hairline) 1px, transparent 1px);
background-size: 32px 32px; background-size: 32px 32px;
} }
@ -57,25 +61,24 @@ body {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: inherit; border-radius: inherit;
background: radial-gradient(ellipse 70% 55% at 18% -8%, rgba(59, 130, 246, 0.12) 0%, transparent 55%), background: var(--d3-glass-sheen);
linear-gradient(180deg, rgba(148, 180, 255, 0.05) 0%, rgba(148, 180, 255, 0) 40%);
pointer-events: none; pointer-events: none;
} }
/* Glow effects */ /* Glow effects */
.glow-white { text-shadow: 0 0 15px rgba(255, 255, 255, 0.4); } .glow-white { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-text-primary) 40%, transparent); }
.glow-green { text-shadow: 0 0 15px rgba(16, 185, 129, 0.5); } .glow-green { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-status-success) 50%, transparent); }
.glow-orange { text-shadow: 0 0 15px rgba(245, 158, 11, 0.5); } .glow-orange { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-status-warning) 50%, transparent); }
.glow-red { text-shadow: 0 0 15px rgba(239, 68, 68, 0.5); } .glow-red { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-status-danger) 50%, transparent); }
.glow-blue { text-shadow: 0 0 15px rgba(59, 130, 246, 0.5); } .glow-blue { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-accent-main) 50%, transparent); }
.glow-purple { text-shadow: 0 0 15px rgba(139, 92, 246, 0.5); } .glow-purple { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-tag-purple) 50%, transparent); }
.glow-cyan { text-shadow: 0 0 15px rgba(6, 182, 212, 0.5); } .glow-cyan { text-shadow: 0 0 15px color-mix(in srgb, var(--d3-tag-cyan) 50%, transparent); }
/* Custom Sleek Scrollbar */ /* Custom Sleek Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: rgba(7, 11, 22, 0.4); } ::-webkit-scrollbar-track { background: var(--d3-bg-inset-soft); }
::-webkit-scrollbar-thumb { background: rgba(148, 180, 255, 0.15); border-radius: 999px; } ::-webkit-scrollbar-thumb { background: var(--d3-glass-hairline); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: rgba(148, 180, 255, 0.3); } ::-webkit-scrollbar-thumb:hover { background: var(--d3-glass-hairlineStrong); }
/* Pulse animation for live status */ /* Pulse animation for live status */
@keyframes pulse-ring { @keyframes pulse-ring {
@ -91,13 +94,12 @@ body {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: 50%; border-radius: 50%;
background: #10b981; background: var(--d3-status-success);
animation: pulse-ring 2s cubic-bezier(0.24, 0, 0.38, 1) infinite; animation: pulse-ring 2s var(--d3-ease-out) infinite;
} }
/* Selection */ /* Selection */
::selection { ::selection {
background: rgba(59, 130, 246, 0.35); background: color-mix(in srgb, var(--d3-accent-main) 35%, transparent);
color: #ffffff; color: var(--d3-accent-ink);
} }

View file

@ -94,8 +94,8 @@ function LoginForm(): React.ReactElement {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
minHeight: '100vh', minHeight: '100vh',
bgcolor: '#070b16', bgcolor: 'var(--d3-bg-crtGlass)',
backgroundImage: 'radial-gradient(ellipse 60% 50% at 50% 40%, rgba(59, 130, 246, 0.15) 0%, transparent 80%)', backgroundImage: 'radial-gradient(ellipse 60% 50% at 50% 40%, var(--d3-accent-glow) 0%, transparent 80%)',
p: 2, p: 2,
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
@ -110,7 +110,7 @@ function LoginForm(): React.ReactElement {
sx={{ sx={{
width: '100%', width: '100%',
maxWidth: 440, maxWidth: 440,
boxShadow: '0 25px 60px rgba(0, 0, 0, 0.8), 0 0 40px rgba(59, 130, 246, 0.15)', boxShadow: '0 25px 60px var(--d3-scrim), 0 0 40px var(--d3-accent-glow)',
}} }}
> >
{/* Brand Header */} {/* Brand Header */}
@ -120,15 +120,15 @@ function LoginForm(): React.ReactElement {
width: 52, width: 52,
height: 52, height: 52,
borderRadius: '16px', borderRadius: '16px',
background: 'linear-gradient(135deg, #06b6d4 0%, #3b82f6 50%, #8b5cf6 100%)', background: 'linear-gradient(135deg, var(--d3-tag-cyan) 0%, var(--d3-accent-main) 50%, var(--d3-tag-purple) 100%)',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
boxShadow: '0 0 25px rgba(59, 130, 246, 0.5)', boxShadow: '0 0 25px var(--d3-accent-glow)',
mb: 2, mb: 2,
}} }}
> >
<LockOutlinedIcon sx={{ color: '#ffffff', fontSize: 28 }} /> <LockOutlinedIcon sx={{ color: 'var(--d3-text-inverse)', fontSize: 28 }} />
</Box> </Box>
<Typography <Typography
@ -168,9 +168,9 @@ function LoginForm(): React.ReactElement {
severity="error" severity="error"
sx={{ sx={{
mb: 2.5, mb: 2.5,
bgcolor: 'rgba(239, 68, 68, 0.15)', bgcolor: 'var(--d3-status-danger)',
color: '#fca5a5', color: 'var(--d3-status-danger)',
border: '1px solid rgba(239, 68, 68, 0.3)', border: '1px solid var(--d3-status-danger)',
borderRadius: '10px', borderRadius: '10px',
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
fontSize: '12px', fontSize: '12px',
@ -193,7 +193,7 @@ function LoginForm(): React.ReactElement {
autoComplete="email" autoComplete="email"
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
bgcolor: 'rgba(10, 17, 31, 0.8)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
@ -217,7 +217,7 @@ function LoginForm(): React.ReactElement {
autoComplete="current-password" autoComplete="current-password"
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
bgcolor: 'rgba(10, 17, 31, 0.8)', bgcolor: 'var(--d3-bg-inset-soft)',
color: C.bright, color: C.bright,
borderRadius: '10px', borderRadius: '10px',
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
@ -260,7 +260,7 @@ function LoginForm(): React.ReactElement {
sx={{ sx={{
borderColor: C.borderHl, borderColor: C.borderHl,
color: C.bright, color: C.bright,
bgcolor: 'rgba(17, 26, 48, 0.5)', bgcolor: 'var(--d3-bg-card-soft)',
borderRadius: '10px', borderRadius: '10px',
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
fontSize: '13px', fontSize: '13px',
@ -269,7 +269,7 @@ function LoginForm(): React.ReactElement {
py: 1.1, py: 1.1,
'&:hover': { '&:hover': {
borderColor: C.accentLight, borderColor: C.accentLight,
bgcolor: 'rgba(26, 38, 68, 0.8)', bgcolor: 'var(--d3-bg-raised-soft)',
}, },
}} }}
> >
@ -290,7 +290,7 @@ function LoginForm(): React.ReactElement {
export default function AdminLoginPage(): React.ReactElement { export default function AdminLoginPage(): React.ReactElement {
return ( return (
<Suspense fallback={<Box sx={{ minHeight: '100vh', bgcolor: '#070b16' }} />}> <Suspense fallback={<Box sx={{ minHeight: '100vh', bgcolor: 'var(--d3-bg-crtGlass)' }} />}>
<LoginForm /> <LoginForm />
</Suspense> </Suspense>
) )

View file

@ -14,8 +14,8 @@ export default function UnauthorizedPage(): React.ReactElement {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
minHeight: '100vh', minHeight: '100vh',
bgcolor: '#070b16', bgcolor: 'var(--d3-bg-crtGlass)',
backgroundImage: 'radial-gradient(ellipse 60% 50% at 50% 40%, rgba(239, 68, 68, 0.15) 0%, transparent 80%)', backgroundImage: 'radial-gradient(ellipse 60% 50% at 50% 40%, var(--d3-status-danger) 0%, transparent 80%)',
p: 2, p: 2,
}} }}
> >
@ -26,7 +26,7 @@ export default function UnauthorizedPage(): React.ReactElement {
width: '100%', width: '100%',
maxWidth: 440, maxWidth: 440,
textAlign: 'center', textAlign: 'center',
boxShadow: '0 25px 60px rgba(0, 0, 0, 0.8), 0 0 40px rgba(239, 68, 68, 0.2)', boxShadow: '0 25px 60px var(--d3-scrim), 0 0 40px var(--d3-status-danger)',
}} }}
> >
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
@ -66,7 +66,7 @@ export default function UnauthorizedPage(): React.ReactElement {
sx={{ sx={{
borderColor: C.borderHl, borderColor: C.borderHl,
color: C.bright, color: C.bright,
bgcolor: 'rgba(17, 26, 48, 0.5)', bgcolor: 'var(--d3-bg-card-soft)',
borderRadius: '10px', borderRadius: '10px',
fontFamily: FONT_SANS, fontFamily: FONT_SANS,
fontSize: '13px', fontSize: '13px',
@ -75,7 +75,7 @@ export default function UnauthorizedPage(): React.ReactElement {
py: 1.1, py: 1.1,
'&:hover': { '&:hover': {
borderColor: C.accentLight, borderColor: C.accentLight,
bgcolor: 'rgba(26, 38, 68, 0.8)', bgcolor: 'var(--d3-bg-raised-soft)',
}, },
}} }}
> >

View file

@ -168,14 +168,14 @@ export function AdminSidebar({ identity }: {
height: '100%', height: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
bgcolor: 'rgba(11, 16, 31, 0.82)', bgcolor: 'var(--d3-bg-card-soft)',
backdropFilter: 'blur(28px)', backdropFilter: 'blur(28px)',
border: `1px solid ${C.border}`, border: `1px solid ${C.border}`,
borderRadius: '22px', borderRadius: '22px',
flexShrink: 0, flexShrink: 0,
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
boxShadow: '0 24px 60px rgba(3, 7, 18, 0.7), inset 0 1px 0 rgba(148, 180, 255, 0.1)', boxShadow: '0 24px 60px var(--d3-scrim), inset 0 1px 0 var(--d3-glass-hairlineStrong)',
}} }}
> >
{/* Top Ambient Sheen */} {/* Top Ambient Sheen */}
@ -186,7 +186,7 @@ export function AdminSidebar({ identity }: {
left: 0, left: 0,
right: 0, right: 0,
height: 120, height: 120,
background: 'radial-gradient(ellipse 80% 60% at 50% -10%, rgba(59, 130, 246, 0.18) 0%, transparent 80%)', background: 'radial-gradient(ellipse 80% 60% at 50% -10%, var(--d3-accent-glow) 0%, transparent 80%)',
pointerEvents: 'none', pointerEvents: 'none',
}} }}
/> />
@ -210,11 +210,11 @@ export function AdminSidebar({ identity }: {
width: 34, width: 34,
height: 34, height: 34,
borderRadius: '10px', borderRadius: '10px',
background: 'linear-gradient(135deg, #06b6d4 0%, #3b82f6 50%, #8b5cf6 100%)', background: 'linear-gradient(135deg, var(--d3-tag-cyan) 0%, var(--d3-accent-main) 50%, var(--d3-tag-purple) 100%)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
boxShadow: '0 0 20px rgba(59, 130, 246, 0.45)', boxShadow: '0 0 20px var(--d3-accent-glow)',
}} }}
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" strokeWidth="2.2"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" strokeWidth="2.2">
@ -260,8 +260,8 @@ export function AdminSidebar({ identity }: {
px: 1, px: 1,
py: 0.35, py: 0.35,
borderRadius: '999px', borderRadius: '999px',
bgcolor: 'rgba(16, 185, 129, 0.12)', bgcolor: 'var(--d3-status-success)',
border: '1px solid rgba(16, 185, 129, 0.25)', border: '1px solid var(--d3-status-success)',
}} }}
> >
<Box <Box
@ -269,12 +269,12 @@ export function AdminSidebar({ identity }: {
width: 6, width: 6,
height: 6, height: 6,
borderRadius: '50%', borderRadius: '50%',
bgcolor: '#10b981', bgcolor: 'var(--d3-status-success)',
boxShadow: '0 0 8px #10b981', boxShadow: '0 0 8px var(--d3-status-success)',
animation: 'pulse-ring 2s infinite', animation: 'pulse-ring 2s infinite',
}} }}
/> />
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '9px', fontWeight: 600, color: '#34d399' }}> <Typography sx={{ fontFamily: FONT_MONO, fontSize: '9px', fontWeight: 600, color: 'var(--d3-status-success)' }}>
SESSION SESSION
</Typography> </Typography>
</Box> </Box>
@ -328,14 +328,14 @@ export function AdminSidebar({ identity }: {
borderRadius: '12px', borderRadius: '12px',
cursor: 'pointer', cursor: 'pointer',
position: 'relative', position: 'relative',
border: `1px solid ${active ? 'rgba(59, 130, 246, 0.35)' : 'transparent'}`, border: `1px solid ${active ? 'var(--d3-accent-glow)' : 'transparent'}`,
bgcolor: active ? 'rgba(59, 130, 246, 0.14)' : 'transparent', bgcolor: active ? 'var(--d3-accent-glow)' : 'transparent',
color: active ? C.bright : C.text, color: active ? C.bright : C.text,
boxShadow: active ? '0 0 20px rgba(59, 130, 246, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1)' : 'none', boxShadow: active ? '0 0 20px var(--d3-accent-glow), inset 0 1px 0 var(--d3-overlay-strong)' : 'none',
transition: 'all 0.18s cubic-bezier(0.16, 1, 0.3, 1)', transition: 'all 0.18s cubic-bezier(0.16, 1, 0.3, 1)',
'&:hover': { '&:hover': {
bgcolor: active ? 'rgba(59, 130, 246, 0.2)' : 'rgba(26, 38, 68, 0.5)', bgcolor: active ? 'var(--d3-accent-glow)' : 'var(--d3-bg-raised-soft)',
borderColor: active ? 'rgba(96, 165, 250, 0.5)' : C.border, borderColor: active ? 'var(--d3-accent-light)' : C.border,
color: C.bright, color: C.bright,
transform: 'translateX(3px)', transform: 'translateX(3px)',
}, },
@ -379,7 +379,7 @@ export function AdminSidebar({ identity }: {
<Box <Box
sx={{ sx={{
p: 2, p: 2,
bgcolor: 'rgba(10, 14, 28, 0.95)', bgcolor: 'var(--d3-bg-inset-soft)',
borderTop: `1px solid ${C.border}`, borderTop: `1px solid ${C.border}`,
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
@ -393,7 +393,7 @@ export function AdminSidebar({ identity }: {
p: 1.25, p: 1.25,
borderRadius: '12px', borderRadius: '12px',
border: `1px solid ${C.borderHl}`, border: `1px solid ${C.borderHl}`,
bgcolor: 'rgba(17, 26, 48, 0.5)', bgcolor: 'var(--d3-bg-card-soft)',
transition: 'border-color 0.15s ease', transition: 'border-color 0.15s ease',
'&:hover': { '&:hover': {
borderColor: C.accentLight, borderColor: C.accentLight,
@ -406,14 +406,14 @@ export function AdminSidebar({ identity }: {
width: 32, width: 32,
height: 32, height: 32,
borderRadius: '8px', borderRadius: '8px',
background: 'linear-gradient(135deg, #1d4ed8 0%, #3b82f6 100%)', background: 'linear-gradient(135deg, var(--d3-accent-dark) 0%, var(--d3-accent-main) 100%)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
fontWeight: 500, fontWeight: 500,
fontSize: '13px', fontSize: '13px',
color: '#ffffff', color: 'var(--d3-text-inverse)',
boxShadow: '0 0 12px rgba(59, 130, 246, 0.4)', boxShadow: '0 0 12px var(--d3-accent-glow)',
}} }}
> >
{identity.email.slice(0, 1).toUpperCase()} {identity.email.slice(0, 1).toUpperCase()}
@ -442,7 +442,7 @@ export function AdminSidebar({ identity }: {
transition: 'all 0.15s ease', transition: 'all 0.15s ease',
'&:hover': { '&:hover': {
color: C.red400, color: C.red400,
bgcolor: 'rgba(239, 68, 68, 0.12)', bgcolor: 'var(--d3-status-danger)',
}, },
}} }}
> >

View file

@ -20,7 +20,7 @@ export function LicenseIssuerButton(): React.ReactElement {
sx={{ sx={{
...primaryButtonSx, ...primaryButtonSx,
background: `linear-gradient(135deg, ${C.purple} 0%, ${C.accent} 100%)`, background: `linear-gradient(135deg, ${C.purple} 0%, ${C.accent} 100%)`,
boxShadow: '0 0 12px rgba(139, 92, 246, 0.4)' boxShadow: '0 0 12px var(--d3-tag-purple)'
}} }}
> >
Issue Ed25519 License Key Issue Ed25519 License Key

View file

@ -117,7 +117,7 @@ export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps)
bgcolor: C.app, bgcolor: C.app,
border: `1px solid ${C.borderHl}`, border: `1px solid ${C.borderHl}`,
borderRadius: '16px', borderRadius: '16px',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.8)', boxShadow: '0 20px 40px var(--d3-scrim)',
p: 1 p: 1
} }
}} }}
@ -208,7 +208,7 @@ export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps)
</Button> </Button>
{generatedKey && ( {generatedKey && (
<Box sx={{ mt: 1, p: 2, bgcolor: 'rgba(0, 0, 0, 0.4)', borderRadius: '10px', border: `1px solid ${C.borderStrong}` }}> <Box sx={{ mt: 1, p: 2, bgcolor: 'var(--d3-scrim)', borderRadius: '10px', border: `1px solid ${C.borderStrong}` }}>
{!auditRecorded && ( {!auditRecorded && (
<Alert severity="info" sx={{ fontFamily: FONT_MONO, fontSize: '11px', mb: 1.5 }}> <Alert severity="info" sx={{ fontFamily: FONT_MONO, fontSize: '11px', mb: 1.5 }}>
. . . .
@ -223,7 +223,7 @@ export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps)
fontSize: '11px', fontSize: '11px',
color: C.bright, color: C.bright,
p: 1.5, p: 1.5,
bgcolor: 'rgba(255, 255, 255, 0.05)', bgcolor: 'var(--d3-overlay-strong)',
borderRadius: '6px', borderRadius: '6px',
wordBreak: 'break-all', wordBreak: 'break-all',
userSelect: 'all', userSelect: 'all',