feat(V2-1a): Monorepo 구조 전환 — apps/desktop으로 V1 이동

- npm workspaces 루트 (apps/*, packages/*) 세팅
- V1 전체를 apps/desktop/으로 git mv (src, resources, tests, sidecar,
  scripts, electron.vite.config.ts, electron-builder.yml, vitest.config.ts,
  tsconfig.node.json, tsconfig.web.json)
- apps/desktop/package.json 신규 (name=@d3ro/desktop)
- productName: 'd3ro-voice' 명시 — app.getName()을 고정하여 userData 경로
  %APPDATA%\d3ro-voice\ 그대로 유지 (기존 DB/설정 연속성 보장)
- 루트 package.json을 workspace 루트로 재구성, 공통 devDep만 유지
  (typescript, eslint, prettier)
- turbo.json, tsconfig.base.json 추가 (Turborepo 자체 설치는 별도 sub-phase)
- memory/project_status.md 생성 (규칙 13)

검증:
- npm run typecheck 통과
- npm run build 통과 (electron-vite main+preload+renderer)
- npm run dev 실제 실행 → DB/핫키/Ollama 자동 실행 모두 정상
This commit is contained in:
yunchan8804 2026-04-08 14:04:41 +09:00
parent 3a160b9032
commit 45a580878a
178 changed files with 214 additions and 0 deletions

View file

@ -0,0 +1,131 @@
// 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'
},
captionShortcut: {
keyCode: 0xa5,
ctrl: true,
alt: false,
shift: true,
meta: false,
displayLabel: 'Ctrl + Shift + Right Alt'
},
hotkeyEnabled: true,
insertMethod: 'clipboard',
autoInsert: true,
maxHistoryEntries: 1000,
dictationEnabled: true,
agentModeEnabled: false,
handsFreeEnabled: false,
screenContextEnabled: false,
}
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)
}