import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@shared/components/ui/button'; import { Card, CardContent } from '@shared/components/ui/card'; import { Badge } from '@shared/components/ui/badge'; import { PageContainer, PageHeader } from '@shared/components/layout'; import { getModelStatusIntent, getQualityGateIntent, getExperimentIntent, MODEL_STATUSES, QUALITY_GATE_STATUSES, EXPERIMENT_STATUSES } from '@shared/constants/modelDeploymentStatuses'; import { getStatusIntent } from '@shared/constants/statusIntent'; import { Cpu, Brain, Database, GitBranch, Activity, RefreshCw, Server, Shield, FileText, Settings, Layers, Globe, Lock, BarChart3, Code, Play, Square, Rocket, Zap, FlaskConical, Search, ChevronRight, CheckCircle, XCircle, AlertTriangle, Eye, Terminal, MessageSquare, Send, Bot, User, } from 'lucide-react'; /* * SFR-18: 기계학습 운영 기능 (MLOps) * SFR-19: 대규모 언어모델(LLM) 운영 기능 (LLMOps) * * WING AI 플랫폼 운영 대시보드 반영: * ① 운영 대시보드 ② Experiment Studio ③ Model Registry * ④ Deploy Center ⑤ API Playground ⑥ LLMOps (학습/HPS/로그/워커/LLM테스트) * ⑦ 플랫폼 관리 */ type Tab = 'dashboard' | 'experiment' | 'registry' | 'deploy' | 'api' | 'llmops' | 'platform'; type LLMSubTab = 'train' | 'hps' | 'log' | 'worker' | 'llmtest'; // ─── 대시보드 KPI ────────────────── const DASH_KPI = [ { label: '고위험 (HIGH)', value: 8, sub: '↑3 전일비', color: '#ef4444', icon: AlertTriangle }, { label: '중위험 (MED)', value: 16, sub: '↓2 전일비', color: '#f59e0b', icon: Eye }, { label: '배포 중 모델', value: 3, sub: '최신 v2.1.0', color: '#10b981', icon: Rocket }, { label: '진행 중 실험', value: 5, sub: '2건 완료 대기', color: '#3b82f6', icon: FlaskConical }, { label: '등록 모델', value: 12, sub: '2건 승인 대기', color: '#8b5cf6', icon: GitBranch }, ]; // ─── 실험 데이터 ────────────────── const EXPERIMENTS = [ { id: 'EXP-042', name: 'LSTM 서해A1 야간', template: 'AIS 위험도 예측', status: 'running', progress: 67, epoch: '34/50', f1: 0.891, time: '2h 14m' }, { id: 'EXP-041', name: 'GNN 환적탐지 v3', template: '불법환적 네트워크', status: 'running', progress: 45, epoch: '23/50', f1: 0.823, time: '1h 50m' }, { id: 'EXP-040', name: 'Transformer 궤적', template: '궤적 이상탐지', status: 'done', progress: 100, epoch: '50/50', f1: 0.912, time: '4h 30m' }, { id: 'EXP-039', name: 'RF 어구분류 v2', template: '어구 자동분류', status: 'done', progress: 100, epoch: '100/100', f1: 0.876, time: '1h 12m' }, { id: 'EXP-038', name: 'CNN 위성영상', template: '선박 탐지(SAR)', status: 'fail', progress: 23, epoch: '12/50', f1: 0, time: '0h 45m' }, ]; const TEMPLATES = [ { name: 'AIS 위험도 예측', icon: AlertTriangle, desc: 'LSTM+CNN 시계열 위험도' }, { name: '궤적 이상탐지', icon: Activity, desc: 'Transformer 기반 경로 예측' }, { name: '불법환적 네트워크', icon: Layers, desc: 'GNN 관계 분석' }, { name: '어구 자동분류', icon: Search, desc: 'RF/XGBoost 앙상블' }, { name: '선박 탐지(SAR)', icon: Globe, desc: 'CNN 위성영상 분석' }, { name: 'Dark Vessel', icon: Eye, desc: 'SAR+RF 융합 탐지' }, ]; // ─── 모델 레지스트리 ────────────────── const MODELS = [ { name: '불법조업 위험도 예측', ver: 'v2.1.0', status: 'DEPLOYED', accuracy: 93.2, f1: 92.3, recall: 91.5, precision: 93.1, falseAlarm: 7.8, gates: ['데이터검증', '성능검증', '편향검사', 'A/B테스트', '보안감사'], gateStatus: ['pass', 'pass', 'pass', 'pass', 'pass'] }, { name: '경비함정 경로추천', ver: 'v1.5.2', status: 'DEPLOYED', accuracy: 89.7, f1: 88.4, recall: 87.2, precision: 89.6, falseAlarm: 10.3, gates: ['데이터검증', '성능검증', '편향검사', 'A/B테스트', '보안감사'], gateStatus: ['pass', 'pass', 'pass', 'pass', 'pass'] }, { name: '불법어선 어망탐지', ver: 'v1.2.0', status: 'DEPLOYED', accuracy: 87.5, f1: 86.1, recall: 85.0, precision: 87.2, falseAlarm: 12.5, gates: ['데이터검증', '성능검증', '편향검사', 'A/B테스트', '보안감사'], gateStatus: ['pass', 'pass', 'pass', 'run', 'pend'] }, { name: 'Transformer 궤적', ver: 'v0.9.0', status: 'APPROVED', accuracy: 91.2, f1: 90.5, recall: 89.8, precision: 91.2, falseAlarm: 8.8, gates: ['데이터검증', '성능검증', '편향검사', 'A/B테스트', '보안감사'], gateStatus: ['pass', 'pass', 'pass', 'pend', 'pend'] }, { name: 'GNN 환적탐지 v3', ver: 'v0.3.0', status: 'TESTING', accuracy: 82.3, f1: 80.1, recall: 78.5, precision: 81.8, falseAlarm: 17.7, gates: ['데이터검증', '성능검증', '편향검사', 'A/B테스트', '보안감사'], gateStatus: ['pass', 'run', 'pend', 'pend', 'pend'] }, { name: 'CNN 위성영상', ver: 'v0.1.0', status: 'DRAFT', accuracy: 0, f1: 0, recall: 0, precision: 0, falseAlarm: 0, gates: ['데이터검증', '성능검증', '편향검사', 'A/B테스트', '보안감사'], gateStatus: ['pend', 'pend', 'pend', 'pend', 'pend'] }, ]; // ─── 배포 센터 ────────────────── const DEPLOYS = [ { model: '불법조업 위험도', ver: 'v2.1.0', endpoint: '/v1/infer/risk', traffic: 80, latency: '23ms', falseAlarm: '7.8%', rps: 142, status: '정상', date: '04-01' }, { model: '불법조업 위험도', ver: 'v2.0.3', endpoint: '/v1/infer/risk', traffic: 20, latency: '25ms', falseAlarm: '9.9%', rps: 36, status: '카나리', date: '03-28' }, { model: '경비함정 경로추천', ver: 'v1.5.2', endpoint: '/v1/infer/patrol', traffic: 100, latency: '45ms', falseAlarm: '10.3%', rps: 28, status: '정상', date: '03-15' }, { model: '불법어선 어망탐지', ver: 'v1.2.0', endpoint: '/v1/infer/gear', traffic: 100, latency: '31ms', falseAlarm: '12.5%', rps: 15, status: '정상', date: '03-01' }, ]; // ─── LLMOps 데이터 ────────────────── const LLM_MODELS = [ { name: 'Llama-3-8B', icon: Brain, sub: 'Meta 오픈소스' }, { name: 'Qwen-2-7B', icon: Brain, sub: 'Alibaba' }, { name: 'SOLAR-10.7B', icon: Brain, sub: 'Upstage' }, { name: 'Gemma-2-9B', icon: Brain, sub: 'Google' }, { name: 'Phi-3-mini', icon: Brain, sub: 'Microsoft 3.8B' }, { name: 'Custom Upload', icon: GitBranch, sub: '직접 업로드' }, ]; const TRAIN_JOBS = [ { id: 'TRN-018', model: 'Llama-3-8B', status: 'running', progress: 72, elapsed: '3h 28m' }, { id: 'TRN-017', model: 'Qwen-2-7B', status: 'done', progress: 100, elapsed: '5h 12m' }, { id: 'TRN-016', model: 'SOLAR-10.7B', status: 'done', progress: 100, elapsed: '8h 45m' }, { id: 'TRN-015', model: 'Llama-3-8B', status: 'fail', progress: 34, elapsed: '1h 20m' }, ]; const HPS_TRIALS = [ { trial: 1, lr: '3.2e-4', batch: 64, dropout: 0.2, hidden: 256, f1: 0.891, best: false }, { trial: 2, lr: '1.0e-3', batch: 32, dropout: 0.3, hidden: 128, f1: 0.867, best: false }, { trial: 3, lr: '5.5e-4', batch: 64, dropout: 0.1, hidden: 256, f1: 0.912, best: true }, { trial: 4, lr: '2.1e-4', batch: 128, dropout: 0.2, hidden: 512, f1: 0.903, best: false }, { trial: 5, lr: '8.0e-4', batch: 32, dropout: 0.4, hidden: 128, f1: 0.845, best: false }, ]; const WORKERS = [ { name: 'infer-risk-prod-01', model: '위험도 v2.1.0', gpu: 'Blackwell x2', status: 'ok', rps: 142, latency: '23ms', vram: '78%' }, { name: 'infer-risk-canary-01', model: '위험도 v2.0.3', gpu: 'Blackwell x1', status: 'ok', rps: 36, latency: '25ms', vram: '45%' }, { name: 'infer-patrol-01', model: '경로추천 v1.5.2', gpu: 'Blackwell x1', status: 'ok', rps: 28, latency: '45ms', vram: '52%' }, { name: 'llm-serving-01', model: '해경LLM v1.0', gpu: 'H200 x2', status: 'ok', rps: 12, latency: '820ms', vram: '85%' }, { name: 'llm-serving-02', model: '법령QA v1.0', gpu: 'Blackwell x1', status: 'warn', rps: 8, latency: '1.2s', vram: '92%' }, ]; // ─── 메인 컴포넌트 ────────────────── export function MLOpsPage() { const { t } = useTranslation('ai'); const { t: tc } = useTranslation('common'); const [tab, setTab] = useState('dashboard'); const [llmSub, setLlmSub] = useState('train'); const [selectedTmpl, setSelectedTmpl] = useState(0); const [selectedLLM, setSelectedLLM] = useState(0); return ( {/* 탭 */}
{([ { key: 'dashboard' as Tab, icon: BarChart3, label: '대시보드' }, { key: 'experiment' as Tab, icon: FlaskConical, label: 'Experiment Studio' }, { key: 'registry' as Tab, icon: GitBranch, label: 'Model Registry' }, { key: 'deploy' as Tab, icon: Rocket, label: 'Deploy Center' }, { key: 'api' as Tab, icon: Zap, label: 'API Playground' }, { key: 'llmops' as Tab, icon: Brain, label: 'LLMOps' }, { key: 'platform' as Tab, icon: Settings, label: '플랫폼 관리' }, ]).map(t => ( ))}
{/* ── ① 대시보드 ── */} {tab === 'dashboard' && (
{DASH_KPI.map(k => (
{k.value}
{k.label}
{k.sub}
))}
배포 모델 현황
{MODELS.filter(m => m.status === 'DEPLOYED').map(m => (
DEPLOYED {m.name} {m.ver} F1 {m.f1}%
))}
진행 중 실험
{EXPERIMENTS.filter(e => e.status === 'running').map(e => (
실행중 {e.name}
{e.progress}%
))}
)} {/* ── ② Experiment Studio ── */} {tab === 'experiment' && (
실험 템플릿 선택
{TEMPLATES.map((t, i) => (
setSelectedTmpl(i)} className={`p-3 rounded-lg text-center cursor-pointer transition-colors ${selectedTmpl === i ? 'bg-blue-600/15 border border-blue-500/30' : 'bg-surface-overlay border border-transparent hover:border-border'}`}>
{t.name}
{t.desc}
))}
실험 목록
{EXPERIMENTS.map(e => (
{e.id} {e.name} {e.status}
{e.epoch} {e.time} {e.f1 > 0 && F1 {e.f1}}
))}
)} {/* ── ③ Model Registry ── */} {tab === 'registry' && (
{MODELS.map(m => (
{m.name}
{m.ver}
{MODEL_STATUSES[m.status as keyof typeof MODEL_STATUSES]?.fallback.ko ?? m.status}
{/* 성능 지표 */} {m.accuracy > 0 && (
{[['Acc', m.accuracy], ['F1', m.f1], ['Recall', m.recall], ['Prec', m.precision], ['FPR', m.falseAlarm]].map(([l, v]) => (
{v as number}%
{l as string}
))}
)} {/* 게이트 */}
Quality Gates
{m.gates.map((g, i) => ( {g} ))}
))}
)} {/* ── ④ Deploy Center ── */} {tab === 'deploy' && (
{['모델명', '버전', 'Endpoint', '트래픽%', '지연p95', '오탐율', 'RPS', '상태', '배포일'].map(h => ( ))} {DEPLOYS.map((d, i) => ( ))}
{h}
{d.model} {d.ver} {d.endpoint}
{d.traffic}%
{d.latency} {d.falseAlarm} {d.rps} {d.status} {d.date}
카나리 / A·B 테스트
위험도 v2.1.0 (80%) ↔ v2.0.3 (20%)
v2.1.0 80%
v2.0.3 20%
승인 대기 → 배포 가능
{MODELS.filter(m => m.status === 'APPROVED').map(m => (
{m.name} {m.ver}
))}
)} {/* ── ⑤ API Playground ── */} {tab === 'api' && (
REQUEST BODY (JSON)