// 처리 머신 보드 / 상태 스트립 / 드로어 공용 표시 헬퍼. // 상태 표현은 dot + 칩 (이모지 금지 원칙) — 토큰 클래스만 사용. import type { MachineState } from '$lib/types/queue'; /** 머신 상태 한글 라벨 */ export const MACHINE_STATE_LABEL: Record = { active: '가동', deferred: '보류', idle: '대기', }; /** 상태 dot 색 — 가동=success / 보류=warning / 대기=faint */ export function machineDotClass(state: MachineState): string { if (state === 'active') return 'bg-success'; if (state === 'deferred') return 'bg-warning'; return 'bg-faint'; } /** 상태 칩 톤 — 가동=accent / 보류=warn / 대기=dim */ export function machineChipClass(state: MachineState): string { if (state === 'active') return 'bg-accent/10 text-accent'; if (state === 'deferred') return 'bg-warning/10 text-warning'; return 'bg-surface-hover text-faint'; } /** 처리율 표기 — 정수는 그대로, 소수는 한 자리 */ export function formatRate(n: number): string { return Number.isInteger(n) ? n.toLocaleString() : n.toFixed(1); } /** ETA 분 → "약 N분/N시간 후 소진 예상" (추정치 — title 로 명시는 호출부 책임) */ export function etaPhrase(minutes: number): string { if (minutes < 60) return `약 ${Math.max(1, Math.round(minutes))}분 후 소진 예상`; const hours = minutes / 60; const text = hours >= 10 ? String(Math.round(hours)) : String(Math.round(hours * 10) / 10); return `약 ${text}시간 후 소진 예상`; } /** ETA 분 → 칩용 짧은 표기 ("약 12분" / "약 4.6시간" / 48h+ = "약 5.5일") */ export function etaShort(minutes: number): string { if (minutes < 60) return `약 ${Math.max(1, Math.round(minutes))}분`; const hours = minutes / 60; if (hours >= 48) { const days = hours / 24; return `약 ${days >= 10 ? Math.round(days) : Math.round(days * 10) / 10}일`; } const text = hours >= 10 ? String(Math.round(hours)) : String(Math.round(hours * 10) / 10); return `약 ${text}시간`; } /** 경과 초 → "N분 전 / N시간 전 / N일 전" */ export function formatAgeSec(sec: number): string { if (sec < 3600) return `${Math.max(1, Math.round(sec / 60))}분 전`; if (sec < 86400) return `${Math.round(sec / 3600)}시간 전`; return `${Math.round(sec / 86400)}일 전`; } /* ─── 흐름 보드 정적 매핑 (plan ds-board-engines-1) ─────────────────────────── * stage → 흐름 노드 / 엔진(모델) / 소속 머신. API 는 머신 label 과 단계 사실만 * 주고(raw 모델명 노출 금지 계약), 엔진·모델 표기는 여기 단일 지점이 책임진다. * ★ 모델/엔진 교체 시 이 블록 1곳만 수정 (예: 맥미니 모델 스왑). */ export type FlowMachine = 'nas' | 'macmini'; export interface FlowNodeDef { key: string; /** 노드 표시명 */ label: string; /** 합산할 stage 키 (다중 = 같은 엔진 공유) */ stages: string[]; machine: FlowMachine; /** 엔진/모델 표시명 (FE 정적 — 모델 교체 시 여기 수정) */ engine: string; /** 보조 표기 (서비스/워커명) */ sub: string; } /** 메인 흐름 (문서 진행 순서). 뉴스 등 소스별 스킵 경로는 그림에 안 그림 — 단순화 한계. */ export const FLOW_NODES: FlowNodeDef[] = [ { key: 'extract', label: '추출', stages: ['extract'], machine: 'nas', engine: 'kordoc', sub: 'kordoc' }, { key: 'markdown', label: '마크다운', stages: ['markdown'], machine: 'nas', engine: 'Marker', sub: 'marker-service' }, { key: 'classify', label: '분류', stages: ['classify'], machine: 'macmini', engine: 'Qwen3.6-27B', sub: 'classify + triage' }, { key: 'summarize', label: '요약', stages: ['summarize'], machine: 'macmini', engine: 'Qwen3.6-27B', sub: 'summarize' }, { key: 'chunkembed', label: '청크 · 임베딩', stages: ['chunk', 'embed'], machine: 'nas', engine: 'bge-m3 (맥미니 콜)', sub: 'embed worker' }, { key: 'deep', label: '심층분석', stages: ['deep_summary'], machine: 'macmini', engine: 'Qwen3.6-27B', sub: 'deep_summary' }, ]; /** 보조 노드 — 메인 흐름 밖 (활동 있을 때만 보조 라인에 표시) */ export const AUX_NODES: FlowNodeDef[] = [ { key: 'fulltext', label: '전문 수집', stages: ['fulltext'], machine: 'nas', engine: 'Playwright', sub: 'playwright-fetcher' }, { key: 'stt', label: '전사', stages: ['stt'], machine: 'nas', engine: 'Whisper', sub: 'stt-service' }, { key: 'util', label: '미리보기 · 썸네일', stages: ['preview', 'thumbnail'], machine: 'nas', engine: '유틸', sub: 'ffmpeg' }, ]; /** 머신 스트립 메타 — 모델 표기 단일 지점 (2026-07-02 컷오버: 나스+맥미니 2노드) */ export const MACHINE_META: Record = { nas: { label: '나스', model: 'DS 본체 Docker · 특화 엔진' }, macmini: { label: '맥미니', model: 'Qwen3.6-27B-6bit · bge-m3 · 24/7' }, }; /** 흐름 보드 단계 라벨 (드로어/상세 행 표기) */ export const FLOW_STAGE_LABEL: Record = { extract: '추출', classify: '분류', summarize: '요약', embed: '임베딩', chunk: '청크', preview: '미리보기', stt: '전사', thumbnail: '썸네일', deep_summary: '심층분석', markdown: '마크다운', fulltext: '전문', }; export function flowStageLabel(stage: string): string { return FLOW_STAGE_LABEL[stage] ?? stage; }