Phase 5 구현: SQLite DB + History/Dictionary 서비스 + UI

- DB: better-sqlite3 + drizzle-orm (history/dictionary/stats, WAL 모드)
- HistoryService: CRUD + 검색 + 통계 + 30일 보존 정책, 세션 완료 시 자동 저장
- DictionaryService: CRUD + 검색 + 사용 횟수 추적 + STT 프롬프트 힌트
- HistoryPage: 목록 + 검색 + 삭제 + 복사 + 페이지네이션
- DictionaryPage: 목록 + 검색 + 추가 다이얼로그 + 삭제
- DashboardPage: 실데이터 통계 (총/오늘 세션, 시간, 단어수, 연속일수)
- IPC: history/dictionary/stats 핸들러 + preload API
- Bootstrap: DB 초기화 (critical) + 이력 자동 저장 연동
This commit is contained in:
Yun Chan 2026-04-05 02:32:53 +09:00
parent 4a5cf6c819
commit 291e2a29d0
17 changed files with 3111 additions and 35 deletions

View file

@ -6,6 +6,8 @@ import { initConfigService } from './services/ConfigService'
import { getHotkeyService } from './services/HotkeyService'
import { getVoiceModeService } from './services/VoiceModeService'
import { getLocalLLMService } from './services/LocalLLMService'
import { getHistoryService } from './services/HistoryService'
import { initDatabase } from './db'
import {
createMainWindow,
preloadPopupWindows,
@ -30,6 +32,7 @@ export async function bootstrap(): Promise<void> {
const steps: BootstrapStep[] = [
{ name: 'logger', critical: false, fn: initLogger },
{ name: 'config', critical: false, fn: initConfig },
{ name: 'database', critical: true, fn: initDB },
{ name: 'create-windows', critical: true, fn: createWindows },
{ name: 'tray', critical: false, fn: initTray },
{ name: 'ipc-handlers', critical: true, fn: initIpcHandlers },
@ -65,6 +68,10 @@ async function initConfig(): Promise<void> {
initConfigService()
}
async function initDB(): Promise<void> {
initDatabase()
}
async function createWindows(): Promise<void> {
createMainWindow()
}
@ -106,11 +113,26 @@ async function initVoiceMode(): Promise<void> {
}
})
voiceMode.on('session-completed', ({ finalText }) => {
voiceMode.on('session-completed', ({ session, finalText }) => {
hideRecordingTip()
if (finalText.length > 0) {
showResultPopup(finalText)
}
// 이력 저장
try {
const wordCount = finalText.split(/\s+/).filter((w) => w.length > 0).length
getHistoryService().create({
originalText: session.transcription || finalText,
polishedText: session.processedText,
mode: session.mode === 'hands-free' ? 'dictation' : session.mode,
status: 'completed',
duration: (Date.now() - session.startedAt) / 1000,
wordCount
})
} catch (error) {
logger.warn(`Failed to save history: ${error instanceof Error ? error.message : String(error)}`)
}
})
voiceMode.on('session-cancelled', () => {