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
119
src/main/services/ConfigService.ts
Normal file
119
src/main/services/ConfigService.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// src/main/services/ConfigService.ts
|
||||
// electron-store 기반 설정 관리. 설계서 02의 AppConfig 타입 사용.
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import type { AppConfig, ConfigChangedEvent } from '@shared/types'
|
||||
import { getLogger } from './LoggerService'
|
||||
|
||||
const logger = getLogger('ConfigService')
|
||||
|
||||
// electron-store v10은 ESM 전용이므로 동적 import 필요
|
||||
interface ElectronStore<T extends Record<string, unknown>> {
|
||||
get<K extends keyof T>(key: K): T[K]
|
||||
set<K extends keyof T>(key: K, value: T[K]): void
|
||||
store: T
|
||||
}
|
||||
|
||||
const CONFIG_DEFAULTS: AppConfig = {
|
||||
theme: 'auto',
|
||||
language: 'ko',
|
||||
closeToTray: true,
|
||||
autoLaunch: false,
|
||||
soundEnabled: true,
|
||||
selectedDeviceId: null,
|
||||
sttModelId: 'base',
|
||||
sttLanguage: 'auto',
|
||||
ttsVoiceId: null,
|
||||
ttsSpeed: 1.0,
|
||||
ollamaServerUrl: 'http://localhost:11434',
|
||||
llmModelId: null,
|
||||
defaultLLMAction: 'refine',
|
||||
dictationShortcut: {
|
||||
keyCode: 0xa5, // Right Alt
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
meta: false,
|
||||
displayLabel: 'Right Alt'
|
||||
},
|
||||
handsFreeShortcut: {
|
||||
keyCode: 0xa5,
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
meta: false,
|
||||
displayLabel: 'Right Alt (double)'
|
||||
},
|
||||
commandShortcut: {
|
||||
keyCode: 0xa5,
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
meta: false,
|
||||
displayLabel: 'Ctrl + Right Alt'
|
||||
},
|
||||
hotkeyEnabled: true,
|
||||
insertMethod: 'clipboard',
|
||||
autoInsert: true,
|
||||
maxHistoryEntries: 1000
|
||||
}
|
||||
|
||||
let store: ElectronStore<AppConfig> | null = null
|
||||
const emitter = new EventEmitter()
|
||||
|
||||
export async function initConfigService(): Promise<void> {
|
||||
const { default: Store } = await import('electron-store')
|
||||
store = new Store<AppConfig>({
|
||||
name: 'd3ro-voice-config',
|
||||
defaults: CONFIG_DEFAULTS
|
||||
})
|
||||
logger.info('ConfigService initialized')
|
||||
}
|
||||
|
||||
export function getConfigService(): ElectronStore<AppConfig> | null {
|
||||
return store
|
||||
}
|
||||
|
||||
export function configGet<K extends keyof AppConfig>(key: K): AppConfig[K] {
|
||||
if (!store) {
|
||||
logger.warn(`ConfigService not initialized, returning default for "${key}"`)
|
||||
return CONFIG_DEFAULTS[key]
|
||||
}
|
||||
return store.get(key)
|
||||
}
|
||||
|
||||
export function configSet<K extends keyof AppConfig>(key: K, value: AppConfig[K]): void {
|
||||
if (!store) {
|
||||
logger.warn(`ConfigService not initialized, cannot set "${key}"`)
|
||||
return
|
||||
}
|
||||
const previousValue = store.get(key)
|
||||
store.set(key, value)
|
||||
|
||||
const event: ConfigChangedEvent = {
|
||||
key,
|
||||
value,
|
||||
previousValue
|
||||
}
|
||||
emitter.emit('config-changed', event)
|
||||
logger.debug(`Config changed: ${key}`)
|
||||
}
|
||||
|
||||
export function configGetAll(): AppConfig {
|
||||
if (!store) return { ...CONFIG_DEFAULTS }
|
||||
return store.store
|
||||
}
|
||||
|
||||
export function configReset(key?: keyof AppConfig): void {
|
||||
if (!store) return
|
||||
if (key) {
|
||||
store.set(key, CONFIG_DEFAULTS[key])
|
||||
} else {
|
||||
store.store = { ...CONFIG_DEFAULTS }
|
||||
}
|
||||
}
|
||||
|
||||
export function onConfigChanged(callback: (event: ConfigChangedEvent) => void): () => void {
|
||||
emitter.on('config-changed', callback)
|
||||
return () => emitter.off('config-changed', callback)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue