d3ro-voice/docs/phases/phase-5.md
Yun Chan e24bb8378c 초기 프로젝트 설정: 하네스 시스템 + 설계서 + RE 노하우
- CLAUDE.md: 프로젝트 규칙, 기술 스택, 코딩 규칙, 페이즈 로드맵
- .claude/settings.json: 권한, 강제 훅 (매 프롬프트 설계서 규칙 주입)
- .claude/skills/: implement-phase, review-phase, scaffold, test-commit, debug
- .claude/agents/: electron-architect, voice-pipeline-expert, ui-specialist
- docs/design/00-09: 마스터 아키텍처, 서비스 명세(16개), IPC(113채널),
  DB스키마, UI컴포넌트, 검증리포트, 외부엔진연동, 갭분석,
  VoiceMode패턴, 디자인시스템, 히스토리팝업
- docs/phases/1-7+3.5: 전체 구현 페이즈 문서
- docs/re-findings/: Speakly RE 노하우 5개 문서
2026-04-05 01:03:03 +09:00

148 lines
6.9 KiB
Markdown

# Phase 5: TTS + 히스토리/사전 DB
## 목표
TTS 엔진을 연동하여 텍스트를 음성으로 재생하고, 히스토리와 사전 DB를 완전 구현하여 사용자 데이터를 체계적으로 관리한다. Dashboard 통계 UI를 완성한다.
## 태스크
### 5.1 LocalTTSService 구현
- `src/main/services/LocalTTSService.ts`
- Piper-TTS 또는 Kokoro sidecar 프로세스 관리 (spawn/kill)
- 상태 머신: `Idle → Loading → Speaking → Idle` (서비스 명세 TTSState 참조)
- 싱글톤 + EventEmitter 패턴
- Sidecar 통신 프로토콜:
```
Main Process piper-tts sidecar
│── stdin: text ────────►│
│◄── stdout: WAV data ───│ (또는 PCM 스트리밍)
```
- 음성 모델 디렉토리 관리 (`userData/tts-models/`)
- TTSVoice 목록 조회: id, name, language, gender, sampleRate, downloaded
### 5.2 TTS 오디오 재생
- Node.js 측에서 PCM/WAV 데이터를 renderer로 전달
- renderer에서 Web Audio API로 재생
- IPC 채널:
- `tts:speak` — 텍스트 합성 및 재생 시작
- `tts:stop` — 재생 중지
- `tts:getVoices` — 사용 가능한 음성 목록
- `tts:audioChunk` (main→renderer) — 오디오 청크 스트리밍
- `tts:finished` (main→renderer) — 재생 완료
- TTSOptions: speed (0.5~2.0), format (pcm/wav)
### 5.3 TTS 재생 UI
- ResultPopupWindow에 재생/중지 버튼 추가
- 재생 아이콘 (▶) → 클릭 시 TTS 재생, 아이콘 중지(■)로 변경
- 재생 중 상태 표시 (파형 또는 진행 바)
- History UI 각 항목에 재생 버튼 추가
- ConfigService의 `tts.enabled` 설정에 따라 버튼 표시/숨김
### 5.4 HistoryService 완전 구현
- `src/main/services/HistoryService.ts`
- better-sqlite3 + drizzle-orm (DB 스키마: `03-db-and-ui.md` 참조)
- CRUD 메서드:
- `create(input: CreateHistoryInput): Promise<History>` — nanoid로 ID 생성
- `getById(id: string): Promise<History | null>`
- `getList(filter: HistoryFilter): Promise<{ entries: History[]; total: number }>` — 페이지네이션
- `update(id: string, data: Partial<History>): Promise<void>`
- `softDelete(id: string): Promise<void>` — deleted 플래그 설정
- `hardDelete(id: string): Promise<void>` — 물리 삭제
- `bulkDelete(ids: string[]): Promise<void>`
- 검색: originalText, polishedText에 대한 LIKE 검색, 날짜 범위, 모드, 상태 필터
- 통계 집계:
- `getStats(): Promise<HistoryStats>` — 총 항목, 총 녹음 시간, 언어별 통계 등
- stats 테이블 싱글턴 업데이트 (세션 완료 시 자동 갱신)
- 연속 사용 일수(streakDays) 계산
- 보존 정책:
- `cleanupOldEntries()`: retentionDays(30일) 초과 + softDelete된 항목 물리 삭제
- 앱 시작 시 + 24시간 주기로 실행
- maxEntries 초과 시 오래된 항목부터 softDelete
### 5.5 DictionaryService 완전 구현
- `src/main/services/DictionaryService.ts`
- better-sqlite3 + drizzle-orm (DB 스키마: `03-db-and-ui.md` 참조)
- CRUD 메서드:
- `add(word: string, pronunciation?: string, category?: string): Promise<Dictionary>`
- `update(id: string, data: Partial<Dictionary>): Promise<void>`
- `delete(id: string): Promise<void>`
- `getAll(filter?: DictionaryFilter): Promise<Dictionary[]>`
- `search(query: string): Promise<Dictionary[]>` — word 검색
- `incrementUsage(id: string): Promise<void>` — usageCount 증가 + lastUsedAt 갱신
- 카테고리 관리: `user`, `auto`, `technical`
- STT 연동: Whisper initialPrompt에 사전 단어 목록 주입
- 전사 시작 전 DictionaryService에서 상위 N개 단어 조회
- initialPrompt 형태: `"단어1, 단어2, 단어3"` (Whisper 컨텍스트 힌트)
- 사용 횟수 자동 갱신: 전사 결과에 사전 단어가 포함되면 incrementUsage 호출
### 5.6 History UI (React)
- `src/renderer/components/History.tsx`
- 목록 표시:
- MUI DataGrid 또는 커스텀 리스트
- 원본 텍스트, 다듬어진 텍스트, 모드, 상태, 날짜, 녹음 시간 표시
- 무한 스크롤 또는 페이지네이션 (기본 50개씩)
- 검색: 텍스트 검색 입력 + 필터 (날짜 범위, 모드, 언어)
- 항목별 액션:
- 복사 (원본/다듬은 텍스트)
- 재시도 (같은 텍스트를 다시 LLM 처리)
- TTS 재생 (Phase 5.3 연동)
- 삭제 (softDelete + 확인 다이얼로그)
- IPC 채널:
- `history:getList` — 목록 조회
- `history:getById` — 상세 조회
- `history:delete` — 삭제
- `history:bulkDelete` — 일괄 삭제
- `history:search` — 검색
- `history:getStats` — 통계
### 5.7 Dictionary UI (React)
- `src/renderer/components/Dictionary.tsx`
- 단어 목록: word, pronunciation, category, usageCount 표시
- 추가: 단어 + 발음 힌트 + 카테고리 입력 다이얼로그
- 편집: 인라인 편집 또는 모달
- 삭제: 확인 후 삭제
- 가져오기/내보내기:
- JSON 파일로 내보내기 (`[{ word, pronunciation, category }]`)
- JSON 파일에서 가져오기 (중복 word+category 시 스킵 또는 덮어쓰기 옵션)
- Electron dialog.showOpenDialog / dialog.showSaveDialog 사용
- IPC 채널:
- `dictionary:getAll` — 전체 목록
- `dictionary:add` — 추가
- `dictionary:update` — 수정
- `dictionary:delete` — 삭제
- `dictionary:import` — 파일에서 가져오기
- `dictionary:export` — 파일로 내보내기
### 5.8 Dashboard 통계 UI 완성
- `src/renderer/components/Dashboard.tsx`
- 통계 카드:
- 총 녹음 시간 (시:분:초 형식)
- 총 단어 수
- 총 세션 수
- 연속 사용 일수 (streakDays)
- 최근 7일 / 30일 활동 그래프 (간단한 바 차트, MUI 또는 커스텀 SVG)
- 최근 세션 목록 (5~10개, History UI로 이동 링크)
- 언어별/모드별 사용 비율 (파이 차트 또는 비율 바)
- stats 테이블에서 데이터 조회 + history 테이블에서 최근 데이터 집계
### 5.9 Drawer 네비게이션 업데이트
- Speakly 패턴: MUI Drawer (240px, permanent)
- 네비게이션 항목 추가: Dashboard, History, Dictionary
- 아이콘 + 텍스트 라벨
- 현재 라우트 하이라이트
## Speakly RE 참조
- HistoryService: SQLite 스키마 (history 테이블), CRUD 패턴, 검색 쿼리
- DictionaryService: 단어 사전 관리, 카테고리 분류
- RecordStatsService: 싱글턴 통계 테이블, 누적 집계 패턴
- App.js: MUI Drawer 네비게이션, 라우팅 패턴 (useState 기반)
- ResultPopup: 결과 표시 + 액션 버튼 패턴
## 완료 조건
- [ ] TTS로 텍스트 음성 재생 가능
- [ ] 재생/중지 버튼 UI 작동
- [ ] 히스토리 CRUD + 검색 + 페이지네이션 작동
- [ ] 히스토리 보존 정책 (30일 초과 자동 정리) 작동
- [ ] 사전 CRUD + 가져오기/내보내기 작동
- [ ] 사전 단어가 STT initialPrompt에 주입됨
- [ ] Dashboard 통계 카드 및 최근 세션 표시
- [ ] Drawer 네비게이션으로 각 화면 이동 가능