Phase 1+2 구현: Electron 뼈대 + STT/핫키/오케스트레이터
Phase 1: - 프로젝트 초기화 (TypeScript strict, electron-vite, ESLint, Prettier) - shared 타입 (ipc-channels 113채널, types, errors, constants) - 메인 프로세스 뼈대 (bootstrap, lifecycle, 단일 인스턴스) - LoggerService, ConfigService (electron-store ESM dynamic import) - React 19 + MUI 7 Dashboard, 시스템 트레이 Phase 2: - AudioCaptureService (node-record-lpcm16, PCM16 16kHz mono) - HotkeyService (uiohook-napi, 더블프레스, holdMode/toggleMode) - LocalSTTService (faster-whisper Python sidecar, 이중 조건 플러시) - VoiceModeService 오케스트레이터 (이중 상태머신, Action Queue) - Python sidecar (FastAPI: health/load/transcribe/shutdown) - IPC 핸들러 (voice, stt, hotkey) + Preload API 확장
This commit is contained in:
parent
e24bb8378c
commit
1d152d01a1
46 changed files with 10828 additions and 4 deletions
58
src/shared/constants.ts
Normal file
58
src/shared/constants.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// src/shared/constants.ts
|
||||
|
||||
/** 타이밍 상수 (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
|
||||
|
||||
/** 윈도우 크기 */
|
||||
export const WINDOW_SIZE = {
|
||||
MAIN: { width: 1104, height: 816 },
|
||||
RECORDING_TIP: { width: 280, height: 80 },
|
||||
RESULT_POPUP: { width: 400, height: 200 }
|
||||
} as const
|
||||
162
src/shared/errors.ts
Normal file
162
src/shared/errors.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// src/shared/errors.ts
|
||||
|
||||
export enum ErrorCode {
|
||||
// === Success ===
|
||||
Success = 0,
|
||||
|
||||
// === STT (100-199) ===
|
||||
STTEngineNotInstalled = 100,
|
||||
STTModelNotFound = 101,
|
||||
STTModelNotLoaded = 102,
|
||||
STTModelLoadFailed = 103,
|
||||
STTModelDownloadFailed = 104,
|
||||
STTModelDownloadCancelled = 105,
|
||||
STTTranscriptionFailed = 110,
|
||||
STTTranscriptionTimeout = 111,
|
||||
STTTranscriptionCancelled = 112,
|
||||
STTNoAudioData = 113,
|
||||
STTAudioTooShort = 114,
|
||||
STTLanguageNotSupported = 120,
|
||||
STTSidecarSpawnFailed = 130,
|
||||
STTSidecarCrashed = 131,
|
||||
STTSidecarCommunicationFailed = 132,
|
||||
STTGPUNotAvailable = 140,
|
||||
|
||||
// === TTS (200-299) ===
|
||||
TTSEngineNotInstalled = 200,
|
||||
TTSVoiceNotFound = 201,
|
||||
TTSVoiceNotLoaded = 202,
|
||||
TTSVoiceLoadFailed = 203,
|
||||
TTSVoiceDownloadFailed = 204,
|
||||
TTSSynthesisFailed = 210,
|
||||
TTSPlaybackFailed = 211,
|
||||
TTSPlaybackInterrupted = 212,
|
||||
TTSTextTooLong = 220,
|
||||
TTSTextEmpty = 221,
|
||||
|
||||
// === LLM / Ollama (300-399) ===
|
||||
LLMServerUnreachable = 300,
|
||||
LLMServerConnectionFailed = 301,
|
||||
LLMServerTimeout = 302,
|
||||
LLMModelNotFound = 310,
|
||||
LLMModelNotLoaded = 311,
|
||||
LLMModelLoadFailed = 312,
|
||||
LLMModelPullFailed = 313,
|
||||
LLMModelPullCancelled = 314,
|
||||
LLMProcessingFailed = 320,
|
||||
LLMProcessingTimeout = 321,
|
||||
LLMProcessingCancelled = 322,
|
||||
LLMResponseParseFailed = 323,
|
||||
LLMInvalidAction = 330,
|
||||
LLMPromptTooLong = 331,
|
||||
|
||||
// === Audio (400-499) ===
|
||||
AudioDeviceNotFound = 400,
|
||||
AudioDeviceAccessDenied = 401,
|
||||
AudioDeviceBusy = 402,
|
||||
AudioCaptureStartFailed = 410,
|
||||
AudioCaptureStopFailed = 411,
|
||||
AudioCaptureFailed = 412,
|
||||
AudioNoPermission = 420,
|
||||
AudioStreamError = 430,
|
||||
AudioBufferOverflow = 431,
|
||||
|
||||
// === Hotkey (500-599) ===
|
||||
HotkeyRegistrationFailed = 500,
|
||||
HotkeyConflict = 501,
|
||||
HotkeySystemReserved = 502,
|
||||
HotkeyHookInitFailed = 510,
|
||||
HotkeyHookCrashed = 511,
|
||||
|
||||
// === TextInsert (600-699) ===
|
||||
TextInsertFailed = 600,
|
||||
TextInsertClipboardSaveFailed = 601,
|
||||
TextInsertClipboardRestoreFailed = 602,
|
||||
TextInsertKeySimulationFailed = 603,
|
||||
TextInsertNoActiveWindow = 610,
|
||||
TextInsertTargetAppNotResponding = 611,
|
||||
|
||||
// === History / Dictionary / DB (700-799) ===
|
||||
DBOpenFailed = 700,
|
||||
DBMigrationFailed = 701,
|
||||
DBQueryFailed = 702,
|
||||
DBWriteFailed = 703,
|
||||
HistoryNotFound = 710,
|
||||
HistoryExportFailed = 711,
|
||||
DictionaryNotFound = 720,
|
||||
DictionaryDuplicate = 721,
|
||||
DictionaryImportFailed = 722,
|
||||
DictionaryExportFailed = 723,
|
||||
DictionaryImportInvalidFormat = 724,
|
||||
|
||||
// === Config (800-899) ===
|
||||
ConfigReadFailed = 800,
|
||||
ConfigWriteFailed = 801,
|
||||
ConfigInvalidValue = 802,
|
||||
ConfigKeyNotFound = 803,
|
||||
ConfigResetFailed = 804,
|
||||
ConfigMigrationFailed = 810,
|
||||
|
||||
// === System / Window (900-999) ===
|
||||
WindowCreationFailed = 900,
|
||||
WindowNotFound = 901,
|
||||
TrayCreationFailed = 910,
|
||||
NotificationFailed = 920,
|
||||
PermissionDenied = 930,
|
||||
ExternalOpenFailed = 940,
|
||||
SoundPlayFailed = 950,
|
||||
AppAlreadyRunning = 960,
|
||||
UnknownError = 999
|
||||
}
|
||||
|
||||
/**
|
||||
* D3RO-VOICE 표준 에러 객체 (Speakly NXError 패턴)
|
||||
*/
|
||||
export class D3ROError extends Error {
|
||||
readonly code: ErrorCode
|
||||
readonly details?: Record<string, unknown>
|
||||
|
||||
constructor(code: ErrorCode, message: string, details?: Record<string, unknown>) {
|
||||
super(message)
|
||||
this.name = 'D3ROError'
|
||||
this.code = code
|
||||
this.details = details
|
||||
}
|
||||
|
||||
toJSON(): D3ROErrorJSON {
|
||||
return {
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
details: this.details
|
||||
}
|
||||
}
|
||||
|
||||
static fromJSON(json: D3ROErrorJSON): D3ROError {
|
||||
return new D3ROError(json.code, json.message, json.details)
|
||||
}
|
||||
}
|
||||
|
||||
export interface D3ROErrorJSON {
|
||||
code: ErrorCode
|
||||
message: string
|
||||
details?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* IPC 핸들러에서 사용하는 표준 응답 래퍼.
|
||||
*/
|
||||
export type IPCResult<T> =
|
||||
| { success: true; data: T }
|
||||
| { success: false; error: D3ROErrorJSON }
|
||||
|
||||
export function ipcSuccess<T>(data: T): IPCResult<T> {
|
||||
return { success: true, data }
|
||||
}
|
||||
|
||||
export function ipcError<T>(
|
||||
code: ErrorCode,
|
||||
message: string,
|
||||
details?: Record<string, unknown>
|
||||
): IPCResult<T> {
|
||||
return { success: false, error: { code, message, details } }
|
||||
}
|
||||
172
src/shared/ipc-channels.ts
Normal file
172
src/shared/ipc-channels.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
// src/shared/ipc-channels.ts
|
||||
// IPC 채널명 중앙 정의 — 모든 채널명은 이 파일에서만 정의한다.
|
||||
|
||||
export const IPC_CHANNELS = {
|
||||
VOICE: {
|
||||
START_RECORDING: 'voice:startRecording',
|
||||
STOP_RECORDING: 'voice:stopRecording',
|
||||
CANCEL_RECORDING: 'voice:cancelRecording',
|
||||
GET_STATE: 'voice:getState',
|
||||
SET_MODE: 'voice:setMode',
|
||||
GET_MODE: 'voice:getMode',
|
||||
// Main → Renderer events
|
||||
STATE_CHANGED: 'voice:stateChanged',
|
||||
TRANSCRIPTION_DELTA: 'voice:transcriptionDelta',
|
||||
TRANSCRIPTION_COMPLETE: 'voice:transcriptionComplete',
|
||||
ERROR: 'voice:error',
|
||||
AUDIO_LEVEL: 'voice:audioLevel'
|
||||
},
|
||||
|
||||
AUDIO: {
|
||||
GET_DEVICES: 'audio:getDevices',
|
||||
GET_SELECTED_DEVICE: 'audio:getSelectedDevice',
|
||||
SET_SELECTED_DEVICE: 'audio:setSelectedDevice',
|
||||
TEST_DEVICE: 'audio:testDevice',
|
||||
// Main → Renderer events
|
||||
DEVICE_CHANGED: 'audio:deviceChanged'
|
||||
},
|
||||
|
||||
STT: {
|
||||
GET_STATUS: 'stt:getStatus',
|
||||
GET_MODELS: 'stt:getModels',
|
||||
GET_ACTIVE_MODEL: 'stt:getActiveModel',
|
||||
SET_MODEL: 'stt:setModel',
|
||||
DOWNLOAD_MODEL: 'stt:downloadModel',
|
||||
CANCEL_DOWNLOAD: 'stt:cancelDownload',
|
||||
GET_LANGUAGE: 'stt:getLanguage',
|
||||
SET_LANGUAGE: 'stt:setLanguage',
|
||||
// Main → Renderer events
|
||||
STATUS_CHANGED: 'stt:statusChanged',
|
||||
DOWNLOAD_PROGRESS: 'stt:downloadProgress'
|
||||
},
|
||||
|
||||
TTS: {
|
||||
SPEAK: 'tts:speak',
|
||||
STOP: 'tts:stop',
|
||||
GET_VOICES: 'tts:getVoices',
|
||||
GET_ACTIVE_VOICE: 'tts:getActiveVoice',
|
||||
SET_VOICE: 'tts:setVoice',
|
||||
GET_STATUS: 'tts:getStatus',
|
||||
DOWNLOAD_VOICE: 'tts:downloadVoice',
|
||||
// Main → Renderer events
|
||||
STATUS_CHANGED: 'tts:statusChanged',
|
||||
SPEAKING_STATE_CHANGED: 'tts:speakingStateChanged'
|
||||
},
|
||||
|
||||
LLM: {
|
||||
GET_STATUS: 'llm:getStatus',
|
||||
GET_MODELS: 'llm:getModels',
|
||||
GET_ACTIVE_MODEL: 'llm:getActiveModel',
|
||||
SET_MODEL: 'llm:setModel',
|
||||
PROCESS: 'llm:process',
|
||||
CANCEL_PROCESS: 'llm:cancelProcess',
|
||||
GET_SERVER_URL: 'llm:getServerUrl',
|
||||
SET_SERVER_URL: 'llm:setServerUrl',
|
||||
PULL_MODEL: 'llm:pullModel',
|
||||
// Main → Renderer events
|
||||
STATUS_CHANGED: 'llm:statusChanged',
|
||||
PROCESS_PROGRESS: 'llm:processProgress',
|
||||
PULL_PROGRESS: 'llm:pullProgress'
|
||||
},
|
||||
|
||||
HOTKEY: {
|
||||
GET_DICTATION_SHORTCUT: 'hotkey:getDictationShortcut',
|
||||
SET_DICTATION_SHORTCUT: 'hotkey:setDictationShortcut',
|
||||
GET_HANDS_FREE_SHORTCUT: 'hotkey:getHandsFreeShortcut',
|
||||
SET_HANDS_FREE_SHORTCUT: 'hotkey:setHandsFreeShortcut',
|
||||
GET_COMMAND_SHORTCUT: 'hotkey:getCommandShortcut',
|
||||
SET_COMMAND_SHORTCUT: 'hotkey:setCommandShortcut',
|
||||
IS_ENABLED: 'hotkey:isEnabled',
|
||||
SET_ENABLED: 'hotkey:setEnabled',
|
||||
START_RECORDING: 'hotkey:startRecording',
|
||||
STOP_RECORDING: 'hotkey:stopRecording',
|
||||
// Main → Renderer events
|
||||
TRIGGERED: 'hotkey:triggered',
|
||||
RECORDING_RESULT: 'hotkey:recordingResult'
|
||||
},
|
||||
|
||||
CONFIG: {
|
||||
GET: 'config:get',
|
||||
SET: 'config:set',
|
||||
GET_ALL: 'config:getAll',
|
||||
RESET: 'config:reset',
|
||||
GET_THEME: 'config:getTheme',
|
||||
SET_THEME: 'config:setTheme',
|
||||
GET_LANGUAGE: 'config:getLanguage',
|
||||
SET_LANGUAGE: 'config:setLanguage',
|
||||
GET_AUTO_LAUNCH: 'config:getAutoLaunch',
|
||||
SET_AUTO_LAUNCH: 'config:setAutoLaunch',
|
||||
GET_CLOSE_TO_TRAY: 'config:getCloseToTray',
|
||||
SET_CLOSE_TO_TRAY: 'config:setCloseToTray',
|
||||
// Main → Renderer events
|
||||
CHANGED: 'config:changed'
|
||||
},
|
||||
|
||||
HISTORY: {
|
||||
GET_ALL: 'history:getAll',
|
||||
GET_BY_ID: 'history:getById',
|
||||
DELETE: 'history:delete',
|
||||
DELETE_ALL: 'history:deleteAll',
|
||||
SEARCH: 'history:search',
|
||||
EXPORT: 'history:export',
|
||||
// Main → Renderer events
|
||||
ADDED: 'history:added'
|
||||
},
|
||||
|
||||
DICTIONARY: {
|
||||
GET_ALL: 'dictionary:getAll',
|
||||
ADD: 'dictionary:add',
|
||||
UPDATE: 'dictionary:update',
|
||||
DELETE: 'dictionary:delete',
|
||||
IMPORT: 'dictionary:import',
|
||||
EXPORT: 'dictionary:export',
|
||||
SEARCH: 'dictionary:search'
|
||||
},
|
||||
|
||||
WINDOW: {
|
||||
MINIMIZE: 'window:minimize',
|
||||
MAXIMIZE: 'window:maximize',
|
||||
CLOSE: 'window:close',
|
||||
IS_MAXIMIZED: 'window:isMaximized',
|
||||
SHOW_RECORDING_TIP: 'window:showRecordingTip',
|
||||
HIDE_RECORDING_TIP: 'window:hideRecordingTip',
|
||||
SHOW_RESULT_POPUP: 'window:showResultPopup',
|
||||
HIDE_RESULT_POPUP: 'window:hideResultPopup',
|
||||
TIP_MEASURED: 'window:tipMeasured',
|
||||
// Main → Renderer events
|
||||
TIP_STATE_CHANGED: 'window:tipStateChanged',
|
||||
TIP_PREPARE: 'window:tipPrepare',
|
||||
TIP_SHOW: 'window:tipShow'
|
||||
},
|
||||
|
||||
SYSTEM: {
|
||||
GET_PLATFORM: 'system:getPlatform',
|
||||
GET_VERSION: 'system:getVersion',
|
||||
CHECK_MIC_PERMISSION: 'system:checkMicPermission',
|
||||
REQUEST_MIC_PERMISSION: 'system:requestMicPermission',
|
||||
SHOW_NOTIFICATION: 'system:showNotification',
|
||||
OPEN_EXTERNAL: 'system:openExternal',
|
||||
GET_ACTIVE_APP: 'system:getActiveApp',
|
||||
INSERT_TEXT: 'system:insertText',
|
||||
PLAY_SOUND: 'system:playSound',
|
||||
SET_SOUND_ENABLED: 'system:setSoundEnabled',
|
||||
IS_SOUND_ENABLED: 'system:isSoundEnabled'
|
||||
},
|
||||
|
||||
STATS: {
|
||||
GET_SUMMARY: 'stats:getSummary',
|
||||
GET_DAILY: 'stats:getDaily',
|
||||
GET_WEEKLY: 'stats:getWeekly',
|
||||
// Main → Renderer events
|
||||
UPDATED: 'stats:updated'
|
||||
}
|
||||
} as const
|
||||
|
||||
// 타입 유틸리티: 채널명 유니온 추출
|
||||
type NestedValues<T> = T extends Record<string, infer V>
|
||||
? V extends string
|
||||
? V
|
||||
: NestedValues<V>
|
||||
: never
|
||||
|
||||
export type IPCChannel = NestedValues<typeof IPC_CHANNELS>
|
||||
658
src/shared/types.ts
Normal file
658
src/shared/types.ts
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
// src/shared/types.ts
|
||||
// 모든 IPC 파라미터/반환 타입 정의
|
||||
|
||||
// ============================================================
|
||||
// Common
|
||||
// ============================================================
|
||||
|
||||
export type ThemeMode = 'light' | 'dark' | 'auto'
|
||||
|
||||
export type VoiceMode = 'dictation' | 'hands-free'
|
||||
|
||||
export type PermissionStatus = 'granted' | 'denied' | 'unknown'
|
||||
|
||||
// ============================================================
|
||||
// Voice (음성 오케스트레이션)
|
||||
// ============================================================
|
||||
|
||||
export enum RecognitionState {
|
||||
IDLE = 'idle',
|
||||
PREPARING = 'preparing',
|
||||
CONNECTING = 'connecting',
|
||||
READY = 'ready',
|
||||
RECOGNIZING = 'recognizing',
|
||||
COMPLETED = 'completed',
|
||||
CANCELLED = 'cancelled',
|
||||
ERROR = 'error',
|
||||
DESTROYED = 'destroyed'
|
||||
}
|
||||
|
||||
export enum AudioState {
|
||||
IDLE = 'idle',
|
||||
INITIALIZING = 'initializing',
|
||||
STREAMING = 'streaming',
|
||||
STOPPED = 'stopped'
|
||||
}
|
||||
|
||||
export interface VoiceState {
|
||||
recognitionState: RecognitionState
|
||||
audioState: AudioState
|
||||
mode: VoiceMode
|
||||
sessionId: string | null
|
||||
recordingStartedAt: number | null
|
||||
}
|
||||
|
||||
export interface StartRecordingParams {
|
||||
sessionId?: string
|
||||
deviceId?: string
|
||||
}
|
||||
|
||||
export interface StartRecordingResult {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface StopRecordingParams {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface StopRecordingResult {
|
||||
sessionId: string
|
||||
text: string
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
export interface CancelRecordingParams {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface SetVoiceModeParams {
|
||||
mode: VoiceMode
|
||||
}
|
||||
|
||||
// Voice events (Main → Renderer)
|
||||
|
||||
export interface VoiceStateChangedEvent {
|
||||
previousState: RecognitionState
|
||||
currentState: RecognitionState
|
||||
audioState: AudioState
|
||||
sessionId: string | null
|
||||
}
|
||||
|
||||
export interface TranscriptionDeltaEvent {
|
||||
sessionId: string
|
||||
text: string
|
||||
delta: string
|
||||
isFinal: boolean
|
||||
}
|
||||
|
||||
export interface TranscriptionCompleteEvent {
|
||||
sessionId: string
|
||||
text: string
|
||||
durationMs: number
|
||||
language: string
|
||||
}
|
||||
|
||||
export interface VoiceErrorEvent {
|
||||
sessionId: string | null
|
||||
errorCode: number
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface AudioLevelEvent {
|
||||
level: number
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Audio (디바이스 & 캡처)
|
||||
// ============================================================
|
||||
|
||||
export interface AudioDevice {
|
||||
deviceId: string
|
||||
label: string
|
||||
isDefault: boolean
|
||||
}
|
||||
|
||||
export interface SetDeviceParams {
|
||||
deviceId: string
|
||||
}
|
||||
|
||||
export interface TestDeviceParams {
|
||||
deviceId: string
|
||||
durationMs?: number
|
||||
}
|
||||
|
||||
export interface TestDeviceResult {
|
||||
averageLevel: number
|
||||
peakLevel: number
|
||||
hasAudio: boolean
|
||||
}
|
||||
|
||||
export interface AudioDeviceChangedEvent {
|
||||
devices: AudioDevice[]
|
||||
type: 'added' | 'removed' | 'default-changed'
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// STT (로컬 Whisper)
|
||||
// ============================================================
|
||||
|
||||
export enum STTEngineState {
|
||||
NOT_INSTALLED = 'not-installed',
|
||||
DOWNLOADING = 'downloading',
|
||||
LOADING = 'loading',
|
||||
READY = 'ready',
|
||||
PROCESSING = 'processing',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
export interface STTStatus {
|
||||
engineState: STTEngineState
|
||||
activeModel: string | null
|
||||
engineVersion: string | null
|
||||
gpuAccelerated: boolean
|
||||
}
|
||||
|
||||
export interface STTModel {
|
||||
id: string
|
||||
name: string
|
||||
sizeBytes: number
|
||||
downloaded: boolean
|
||||
languages: string[]
|
||||
accuracy: number
|
||||
speed: number
|
||||
}
|
||||
|
||||
export interface SetSTTModelParams {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export interface DownloadModelParams {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export interface SetSTTLanguageParams {
|
||||
language: string
|
||||
}
|
||||
|
||||
export interface STTStatusChangedEvent {
|
||||
status: STTStatus
|
||||
}
|
||||
|
||||
export interface DownloadProgressEvent {
|
||||
modelId: string
|
||||
percent: number
|
||||
downloadedBytes: number
|
||||
totalBytes: number
|
||||
bytesPerSecond: number
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// TTS (로컬 TTS)
|
||||
// ============================================================
|
||||
|
||||
export enum TTSEngineState {
|
||||
NOT_INSTALLED = 'not-installed',
|
||||
LOADING = 'loading',
|
||||
READY = 'ready',
|
||||
SPEAKING = 'speaking',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
export interface TTSStatus {
|
||||
engineState: TTSEngineState
|
||||
activeVoice: string | null
|
||||
engineVersion: string | null
|
||||
}
|
||||
|
||||
export interface TTSVoice {
|
||||
id: string
|
||||
name: string
|
||||
language: string
|
||||
gender: 'male' | 'female' | 'neutral'
|
||||
downloaded: boolean
|
||||
sizeBytes: number
|
||||
}
|
||||
|
||||
export interface TTSSpeakParams {
|
||||
text: string
|
||||
voiceId?: string
|
||||
speed?: number
|
||||
}
|
||||
|
||||
export interface TTSSpeakResult {
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
export interface SetTTSVoiceParams {
|
||||
voiceId: string
|
||||
}
|
||||
|
||||
export interface DownloadVoiceParams {
|
||||
voiceId: string
|
||||
}
|
||||
|
||||
export interface TTSStatusChangedEvent {
|
||||
status: TTSStatus
|
||||
}
|
||||
|
||||
export interface SpeakingStateChangedEvent {
|
||||
isSpeaking: boolean
|
||||
text: string
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LLM (Ollama)
|
||||
// ============================================================
|
||||
|
||||
export enum LLMConnectionState {
|
||||
DISCONNECTED = 'disconnected',
|
||||
CONNECTING = 'connecting',
|
||||
CONNECTED = 'connected',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
export interface LLMStatus {
|
||||
connectionState: LLMConnectionState
|
||||
serverUrl: string
|
||||
activeModel: string | null
|
||||
serverVersion: string | null
|
||||
}
|
||||
|
||||
export interface LLMModel {
|
||||
id: string
|
||||
name: string
|
||||
sizeBytes: number
|
||||
parameterSize: string
|
||||
quantization: string
|
||||
modifiedAt: string
|
||||
}
|
||||
|
||||
export type LLMAction = 'refine' | 'translate' | 'summarize' | 'expand' | 'grammar' | 'custom'
|
||||
|
||||
export interface LLMProcessParams {
|
||||
text: string
|
||||
action: LLMAction
|
||||
targetLanguage?: string
|
||||
customPrompt?: string
|
||||
modelId?: string
|
||||
}
|
||||
|
||||
export interface LLMProcessResult {
|
||||
originalText: string
|
||||
processedText: string
|
||||
action: LLMAction
|
||||
processingTimeMs: number
|
||||
tokenCount: number
|
||||
}
|
||||
|
||||
export interface SetLLMModelParams {
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export interface SetServerUrlParams {
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface PullModelParams {
|
||||
modelName: string
|
||||
}
|
||||
|
||||
export interface LLMStatusChangedEvent {
|
||||
status: LLMStatus
|
||||
}
|
||||
|
||||
export interface LLMProcessProgressEvent {
|
||||
text: string
|
||||
token: string
|
||||
done: boolean
|
||||
}
|
||||
|
||||
export interface LLMPullProgressEvent {
|
||||
modelName: string
|
||||
status: string
|
||||
percent: number
|
||||
downloadedBytes: number
|
||||
totalBytes: number
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Hotkey (핫키)
|
||||
// ============================================================
|
||||
|
||||
export interface HotkeyBinding {
|
||||
keyCode: number
|
||||
ctrl: boolean
|
||||
alt: boolean
|
||||
shift: boolean
|
||||
meta: boolean
|
||||
displayLabel: string
|
||||
}
|
||||
|
||||
export interface SetHotkeyParams {
|
||||
binding: HotkeyBinding
|
||||
}
|
||||
|
||||
export interface SetEnabledParams {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export type HotkeyAction = 'dictation' | 'hands-free' | 'command'
|
||||
|
||||
export interface HotkeyTriggeredEvent {
|
||||
action: HotkeyAction
|
||||
type: 'pressed' | 'released'
|
||||
isDoublePress: boolean
|
||||
}
|
||||
|
||||
export interface HotkeyRecordingResultEvent {
|
||||
binding: HotkeyBinding | null
|
||||
conflictReason: string | null
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Config (설정)
|
||||
// ============================================================
|
||||
|
||||
export interface AppConfig {
|
||||
theme: ThemeMode
|
||||
language: string
|
||||
closeToTray: boolean
|
||||
autoLaunch: boolean
|
||||
soundEnabled: boolean
|
||||
selectedDeviceId: string | null
|
||||
sttModelId: string
|
||||
sttLanguage: string
|
||||
ttsVoiceId: string | null
|
||||
ttsSpeed: number
|
||||
ollamaServerUrl: string
|
||||
llmModelId: string | null
|
||||
defaultLLMAction: LLMAction
|
||||
dictationShortcut: HotkeyBinding
|
||||
handsFreeShortcut: HotkeyBinding
|
||||
commandShortcut: HotkeyBinding
|
||||
hotkeyEnabled: boolean
|
||||
insertMethod: 'clipboard' | 'keyboard'
|
||||
autoInsert: boolean
|
||||
maxHistoryEntries: number
|
||||
}
|
||||
|
||||
export interface ConfigGetParams {
|
||||
key: keyof AppConfig
|
||||
}
|
||||
|
||||
export interface ConfigSetParams {
|
||||
key: keyof AppConfig
|
||||
value: AppConfig[keyof AppConfig]
|
||||
}
|
||||
|
||||
export interface ConfigResetParams {
|
||||
key?: keyof AppConfig
|
||||
}
|
||||
|
||||
export interface SetThemeParams {
|
||||
theme: ThemeMode
|
||||
}
|
||||
|
||||
export interface SetLanguageParams {
|
||||
language: string
|
||||
}
|
||||
|
||||
export interface SetAutoLaunchParams {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface SetCloseToTrayParams {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface ConfigChangedEvent {
|
||||
key: keyof AppConfig
|
||||
value: AppConfig[keyof AppConfig]
|
||||
previousValue: AppConfig[keyof AppConfig]
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// History (히스토리)
|
||||
// ============================================================
|
||||
|
||||
export interface HistoryEntry {
|
||||
id: string
|
||||
originalText: string
|
||||
polishedText: string | null
|
||||
focusedApp: string | null
|
||||
focusedAppName: string | null
|
||||
focusedAppWindowTitle: string | null
|
||||
mode: 'dictation' | 'translate' | 'command'
|
||||
status: 'completed' | 'cancelled' | 'error'
|
||||
errorCode: string | null
|
||||
audioLocalPath: string | null
|
||||
duration: number
|
||||
detectedLanguage: string | null
|
||||
micDevice: string | null
|
||||
wordCount: number
|
||||
sttModel: string | null
|
||||
llmModel: string | null
|
||||
sttLatencyMs: number | null
|
||||
llmLatencyMs: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
appVersion: string
|
||||
}
|
||||
|
||||
export interface HistoryQueryParams {
|
||||
page: number
|
||||
pageSize: number
|
||||
sortBy?: 'createdAt' | 'durationMs' | 'wordCount'
|
||||
sortOrder?: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
export interface HistoryPage {
|
||||
entries: HistoryEntry[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
totalPages: number
|
||||
}
|
||||
|
||||
export interface HistoryGetByIdParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface HistoryDeleteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface HistorySearchParams {
|
||||
query: string
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export interface HistoryExportParams {
|
||||
format: 'json' | 'csv'
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Dictionary (사전)
|
||||
// ============================================================
|
||||
|
||||
export interface DictionaryEntry {
|
||||
id: string
|
||||
word: string
|
||||
pronunciation: string | null
|
||||
category: 'user' | 'auto' | 'technical'
|
||||
usageCount: number
|
||||
lastUsedAt: number | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
export interface DictionaryQueryParams {
|
||||
page: number
|
||||
pageSize: number
|
||||
sortBy?: 'word' | 'category' | 'usageCount' | 'createdAt'
|
||||
sortOrder?: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
export interface DictionaryPage {
|
||||
entries: DictionaryEntry[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
totalPages: number
|
||||
}
|
||||
|
||||
export interface DictionaryAddParams {
|
||||
word: string
|
||||
pronunciation?: string
|
||||
category?: 'user' | 'auto' | 'technical'
|
||||
}
|
||||
|
||||
export interface DictionaryUpdateParams {
|
||||
id: string
|
||||
word?: string
|
||||
pronunciation?: string
|
||||
category?: 'user' | 'auto' | 'technical'
|
||||
}
|
||||
|
||||
export interface DictionaryDeleteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface DictionaryImportParams {
|
||||
filePath: string
|
||||
format: 'json' | 'csv'
|
||||
}
|
||||
|
||||
export interface DictionaryImportResult {
|
||||
imported: number
|
||||
skipped: number
|
||||
errors: number
|
||||
}
|
||||
|
||||
export interface DictionaryExportParams {
|
||||
format: 'json' | 'csv'
|
||||
}
|
||||
|
||||
export interface DictionarySearchParams {
|
||||
query: string
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Window (윈도우 제어)
|
||||
// ============================================================
|
||||
|
||||
export type RecordingTipState = 'opening' | 'recording' | 'thinking' | 'result' | 'error'
|
||||
|
||||
export interface ShowRecordingTipParams {
|
||||
state: RecordingTipState
|
||||
text?: string
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
export interface ShowResultPopupParams {
|
||||
text: string
|
||||
autoHideMs?: number
|
||||
}
|
||||
|
||||
export interface TipMeasuredParams {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface TipStateChangedEvent {
|
||||
state: RecordingTipState
|
||||
text?: string
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
export interface TipPrepareEvent {
|
||||
state: RecordingTipState
|
||||
text?: string
|
||||
}
|
||||
|
||||
export interface TipShowEvent {
|
||||
state: RecordingTipState
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// System (시스템)
|
||||
// ============================================================
|
||||
|
||||
export interface ActiveAppInfo {
|
||||
name: string
|
||||
title: string
|
||||
pid: number
|
||||
}
|
||||
|
||||
export interface ShowNotificationParams {
|
||||
title: string
|
||||
body: string
|
||||
type?: 'info' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
export interface OpenExternalParams {
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface InsertTextParams {
|
||||
text: string
|
||||
method?: 'clipboard' | 'keyboard'
|
||||
}
|
||||
|
||||
export interface InsertTextResult {
|
||||
success: boolean
|
||||
insertedLength: number
|
||||
}
|
||||
|
||||
export type SoundEffect =
|
||||
| 'recording-start'
|
||||
| 'recording-stop'
|
||||
| 'transcription-complete'
|
||||
| 'error'
|
||||
| 'notification'
|
||||
|
||||
export interface PlaySoundParams {
|
||||
sound: SoundEffect
|
||||
}
|
||||
|
||||
export interface SetSoundEnabledParams {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Stats (통계)
|
||||
// ============================================================
|
||||
|
||||
export interface StatsSummary {
|
||||
totalRecordingTimeMs: number
|
||||
totalWordCount: number
|
||||
totalSessionCount: number
|
||||
todayRecordingTimeMs: number
|
||||
todayWordCount: number
|
||||
todaySessionCount: number
|
||||
streakDays: number
|
||||
}
|
||||
|
||||
export interface StatsQueryParams {
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
|
||||
export interface DailyStats {
|
||||
date: string
|
||||
recordingTimeMs: number
|
||||
wordCount: number
|
||||
sessionCount: number
|
||||
}
|
||||
|
||||
export interface WeeklyStats {
|
||||
weekStart: string
|
||||
recordingTimeMs: number
|
||||
wordCount: number
|
||||
sessionCount: number
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue