// src/shared/constants.ts import type { LicenseTier } from './types' import { PLAN_QUOTA, type PlanQuotaFeature, type PlanQuotaPeriod } from './plan-catalog' /** 타이밍 상수 (Speakly 리버스엔지니어링 기반) */ export const TIMING = { /** 더블프레스 감지 간격 (ms) */ DOUBLE_PRESS_DURATION: 300, /** 최소 녹음 시간 (ms) — 이하 자동 취소 */ MIN_AUDIO_DURATION: 700, /** 녹음 후 STT 대기 시간 (ms) */ POST_RECORDING_WAIT: 4000, /** 녹음 후 STT 대기 (버퍼 있을 때) (ms) */ POST_RECORDING_WAIT_BUFFERED: 6000, /** STT 아이들 타임아웃 (ms) */ STT_IDLE_TIMEOUT: 30000, /** 절대 최대 대기 시간 (ms) */ ABSOLUTE_MAX_WAIT: 120000, /** 오디오 레벨 전송 간격 (ms) */ AUDIO_LEVEL_INTERVAL: 100, /** 서비스 종료 타임아웃 (ms) */ SERVICE_DESTROY_TIMEOUT: 3000, /** 사이드카 헬스체크 지연 (ms) */ SIDECAR_HEALTH_DELAY: 3000, /** LLM 요청 타임아웃 (ms) */ LLM_REQUEST_TIMEOUT: 30000 } as const /** 웨이브 바 상수 (RecordingTip) */ export const WAVE_BAR = { COUNT: 9, ANIMATION_INTERVAL: 100, /** 코사인 분포 가중치 (Speakly 패턴) */ COS_WEIGHTS: Array.from({ length: 9 }, (_, i) => Math.cos((i - 4) * (Math.PI / 9))) } as const /** 오디오 포맷 */ export const AUDIO_FORMAT = { SAMPLE_RATE: 16000, CHANNELS: 1, BIT_DEPTH: 16, BYTES_PER_SAMPLE: 2 } as const /** Premium 모델별 쿼터 표시 행. 값의 정본은 plan-catalog.ts `PLAN_QUOTA`(서버 quota.ts도 같은 값을 생성 사본으로 읽는다). */ export interface PremiumModelQuota { readonly model: PlanQuotaFeature readonly i18nKey: string readonly limit: number // -1 = 무제한 readonly period: PlanQuotaPeriod } const PREMIUM_MODEL_ROWS: readonly { model: PlanQuotaFeature; i18nKey: string }[] = [ { model: 'llm_haiku', i18nKey: 'license.modelHaiku' }, { model: 'llm_sonnet', i18nKey: 'license.modelSonnet' }, { model: 'llm_opus', i18nKey: 'license.modelOpus' }, ] function premiumModelLimits(tier: LicenseTier): readonly PremiumModelQuota[] { // limit 0(사용 불가) 모델은 표시하지 않는다 — Free 는 Haiku 한 줄만 남는다. return PREMIUM_MODEL_ROWS .map(({ model, i18nKey }) => ({ model, i18nKey, ...PLAN_QUOTA[tier][model] })) .filter((row) => row.limit !== 0) } export const PREMIUM_MODEL_LIMITS: Record = { free: premiumModelLimits('free'), pro: premiumModelLimits('pro'), pro_plus: premiumModelLimits('pro_plus'), team: premiumModelLimits('team'), enterprise: premiumModelLimits('enterprise'), } /** 윈도우 크기 */ export const WINDOW_SIZE = { MAIN: { width: 1104, height: 816 }, RECORDING_TIP: { width: 280, height: 80 }, RESULT_POPUP: { width: 400, height: 200 } } as const