초기 프로젝트 설정: 하네스 시스템 + 설계서 + 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개 문서
This commit is contained in:
commit
e24bb8378c
35 changed files with 11452 additions and 0 deletions
59
docs/phases/phase-1.md
Normal file
59
docs/phases/phase-1.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Phase 1: 프로젝트 초기화 + Electron 뼈대 + 마이크 캡처
|
||||
|
||||
## 목표
|
||||
프로젝트 기본 구조를 세우고, Electron 앱이 실행되며, 마이크에서 오디오를 캡처할 수 있는 상태까지.
|
||||
|
||||
## 태스크
|
||||
|
||||
### 1.1 프로젝트 초기화
|
||||
- `npm init` + package.json 설정
|
||||
- TypeScript 설정 (tsconfig.json, strict mode)
|
||||
- Vite + Electron 개발 환경 설정 (electron-vite 또는 vite-plugin-electron)
|
||||
- ESLint + Prettier 설정
|
||||
- vitest 테스트 환경 설정
|
||||
- .gitignore, README.md
|
||||
|
||||
### 1.2 Electron 기본 구조
|
||||
- `src/main/index.ts` — 앱 진입점
|
||||
- Speakly 패턴: app.whenReady → 서비스 초기화 → createWindow → IPC 등록
|
||||
- 단일 인스턴스 잠금
|
||||
- before-quit / will-quit 리소스 정리
|
||||
- `src/preload/index.ts` — contextBridge 기본 구조
|
||||
- `src/renderer/main.tsx` — React 앱 진입점
|
||||
- `src/shared/ipc-channels.ts` — IPC 채널 중앙 정의
|
||||
- `src/shared/types.ts` — 공통 타입
|
||||
- `src/shared/errors.ts` — 에러 코드 체계 (Speakly NXError 패턴)
|
||||
|
||||
### 1.3 메인 윈도우
|
||||
- BrowserWindow 생성 (1104x816, hiddenInset titleBar)
|
||||
- contextIsolation: true, nodeIntegration: false
|
||||
- 기본 React App 컴포넌트 (빈 대시보드)
|
||||
|
||||
### 1.4 시스템 트레이
|
||||
- Tray 아이콘 + 메뉴 (표시/숨기기, 종료)
|
||||
- Windows: closeToTray 옵션
|
||||
|
||||
### 1.5 마이크 캡처 서비스
|
||||
- `src/main/services/AudioCaptureService.ts`
|
||||
- Web Audio API 또는 node-record-lpcm16
|
||||
- 24kHz mono PCM16 (또는 16kHz for Whisper)
|
||||
- noiseSuppression: false, echoCancellation: false
|
||||
- EventEmitter 패턴: 'audio-data', 'audio-level', 'started', 'stopped'
|
||||
- IPC: `audio:startCapture`, `audio:stopCapture`, `audio:getDevices`
|
||||
|
||||
### 1.6 오디오 디바이스 관리
|
||||
- 마이크 디바이스 목록 조회
|
||||
- 디바이스 변경 감지
|
||||
- 선택된 디바이스 저장 (electron-store)
|
||||
|
||||
## Speakly RE 참조
|
||||
- NativeHelper.dll MicrophoneCapture: WASAPI 이벤트 구동, 24kHz mono
|
||||
- AudioService: EventEmitter 패턴, 구독자 레퍼런스 카운팅
|
||||
- main/index.js: 22단계 초기화 순서
|
||||
|
||||
## 완료 조건
|
||||
- [ ] `npm run dev` 로 Electron 앱 실행됨
|
||||
- [ ] 마이크에서 오디오 데이터 캡처 가능
|
||||
- [ ] 시스템 트레이에 아이콘 표시
|
||||
- [ ] `npm run typecheck` 통과
|
||||
- [ ] `npm run test` 통과
|
||||
46
docs/phases/phase-2.md
Normal file
46
docs/phases/phase-2.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Phase 2: 로컬 STT 연동 (Whisper) + 핫키
|
||||
|
||||
## 목표
|
||||
마이크 캡처된 오디오를 로컬 Whisper 모델로 전사하고, 글로벌 핫키로 녹음을 제어할 수 있는 상태.
|
||||
|
||||
## 태스크
|
||||
|
||||
### 2.1 LocalSTTService
|
||||
- `src/main/services/LocalSTTService.ts`
|
||||
- faster-whisper Python sidecar 관리 (spawn/kill)
|
||||
- 또는 whisper.cpp Node addon 직접 호출
|
||||
- PCM 16kHz mono → Whisper → 텍스트
|
||||
- Speakly 패턴: 이중 조건 플러시 (모델 로딩 + 오디오 버퍼링 병렬)
|
||||
- EventEmitter: 'transcription-delta', 'transcription-complete'
|
||||
|
||||
### 2.2 VoiceModeService (오케스트레이터)
|
||||
- `src/main/services/VoiceModeService.ts`
|
||||
- Speakly 패턴 적용:
|
||||
- RecognitionState 상태 머신 (IDLE→PREPARING→READY→RECOGNIZING→COMPLETED)
|
||||
- AudioState 별도 추적
|
||||
- _isInTerminalState() 체크
|
||||
- errorEmitted 플래그
|
||||
- 오디오 무손실 버퍼링 (STT 준비 전 버퍼)
|
||||
- 모드: dictation (hold-to-talk), hands-free (toggle)
|
||||
|
||||
### 2.3 글로벌 핫키
|
||||
- uiohook-napi로 글로벌 키보드 후킹
|
||||
- 기본 트리거: Right Alt (Windows)
|
||||
- Speakly 패턴: pressed/released 이벤트, 더블프레스 감지 (300ms), 최소 700ms
|
||||
- HotkeyService + HotkeyConfig
|
||||
|
||||
### 2.4 IPC 채널 추가
|
||||
- `voice:startRecording`, `voice:stopRecording`
|
||||
- `stt:getStatus`, `stt:getModels`
|
||||
- `hotkey:getDictationShortcut`, `hotkey:setDictationShortcut`
|
||||
|
||||
## Speakly RE 참조
|
||||
- VoiceRecognitionService: 상태 머신, 이중 조건 플러시, 재연결 3계층
|
||||
- VoiceModeService: 오케스트레이션, 모드별 핫키 처리
|
||||
- HotkeyConfig: 키코드 맵, 시스템 예약 단축키 블랙리스트
|
||||
|
||||
## 완료 조건
|
||||
- [ ] 핫키로 녹음 시작/종료 가능
|
||||
- [ ] Whisper로 한국어 음성 전사 작동
|
||||
- [ ] 상태 머신 정상 전이
|
||||
- [ ] 오디오 버퍼링/플러시 정상 작동
|
||||
54
docs/phases/phase-3.5.md
Normal file
54
docs/phases/phase-3.5.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Phase 3.5: 커서 위치 히스토리 팝업
|
||||
|
||||
## 목표
|
||||
핫키(Ctrl+Shift+V)로 커서 근처에 최근 전사 히스토리 팝업을 띄우고, Arrow 키로 선택하여 즉시 붙여넣기.
|
||||
Speakly에 없는 D3RO-VOICE 고유 편의 기능.
|
||||
|
||||
## 전제 조건
|
||||
Phase 3 완료 (TextInsertService, HistoryService, HotkeyService, WindowManager 모두 동작)
|
||||
|
||||
## 태스크
|
||||
|
||||
### 3.5.1 HistoryPopupWindow (Vanilla JS)
|
||||
- 별도 HTML 엔트리포인트 (`src/renderer/popups/history-popup/`)
|
||||
- 08-design-system.md 준수: 다크 카드(#242427), 앰버 악센트(#f25b29), 22px radius
|
||||
- 최근 10건 표시, 텍스트 50자 truncate, 상대 시간 표시
|
||||
- 선택 항목: 좌측 앰버 바 + hover 배경
|
||||
- 하단 키보드 힌트 (↑↓ SELECT / ⏎ PASTE / ESC ×)
|
||||
- 등장/퇴장 애니메이션 (0.15s ease-out / 0.1s ease-in)
|
||||
|
||||
### 3.5.2 위치 계산
|
||||
- mouse.getPosition()으로 커서 좌표
|
||||
- 기본: 커서 위에 표시, 화면 밖이면 아래로
|
||||
- 멀티모니터: screen.getDisplayNearestPoint()
|
||||
|
||||
### 3.5.3 글로벌 키 인터셉트
|
||||
- HotkeyService에 historyPopupMode 추가
|
||||
- 팝업 열림 중: Arrow↑↓, Enter, Escape, 1-9 키 소비
|
||||
- focusable: false로 활성 앱 포커스 유지
|
||||
|
||||
### 3.5.4 IPC 채널 추가
|
||||
- `history:showPopup`, `history:hidePopup`
|
||||
- `history:showItems` (send), `history:selectItem` (send)
|
||||
- `history:itemSelected` (on), `history:popupDismissed` (on)
|
||||
|
||||
### 3.5.5 설정 연동
|
||||
- ConfigService에 historyPopup 섹션 추가 (hotkey, maxItems, autoCloseMs)
|
||||
- Settings UI에 핫키 설정 추가
|
||||
|
||||
## Speakly RE 참조
|
||||
- ResultPopupWindow: Vanilla JS 팝업 패턴, 프리로딩, 2-phase 리사이즈
|
||||
- AskGensparkWindow: 키보드 인터랙션 (Enter/Escape)
|
||||
- HotkeyService: 키 인터셉트 모드 (keyRecordingMode 패턴 응용)
|
||||
|
||||
## 설계 문서 참조
|
||||
- `docs/design/09-history-popup.md` — 전체 설계
|
||||
- `docs/design/08-design-system.md` — 디자인 시스템
|
||||
|
||||
## 완료 조건
|
||||
- [ ] Ctrl+Shift+V로 커서 위에 팝업 표시
|
||||
- [ ] Arrow↑↓로 항목 선택, Enter로 붙여넣기
|
||||
- [ ] 숫자 1-9로 직접 선택+삽입
|
||||
- [ ] Escape로 닫기
|
||||
- [ ] 활성 앱 포커스 유지 (focusable: false)
|
||||
- [ ] 디자인 시스템 준수 (앰버 악센트, 다크 카드)
|
||||
49
docs/phases/phase-3.md
Normal file
49
docs/phases/phase-3.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Phase 3: 텍스트 삽입 + 기본 UI
|
||||
|
||||
## 목표
|
||||
전사된 텍스트를 활성 앱에 삽입하고, 녹음 상태/결과를 표시하는 UI 구축.
|
||||
|
||||
## 태스크
|
||||
|
||||
### 3.1 TextInsertService
|
||||
- `src/main/services/TextInsertService.ts`
|
||||
- Speakly ClipboardPaste 패턴:
|
||||
1. 클립보드 전체 백업
|
||||
2. 텍스트를 클립보드에 설정
|
||||
3. @nut-tree/nut-js로 Ctrl+V 시뮬레이션
|
||||
4. 클립보드 복원
|
||||
- 삽입 검증 (옵션)
|
||||
|
||||
### 3.2 RecordingTipWindow (Vanilla JS 팝업)
|
||||
- 녹음 상태 표시 (opening → recording → thinking → result/error)
|
||||
- 9개 웨이브 바 애니메이션 (Speakly 패턴)
|
||||
- 2-phase 리사이즈
|
||||
- 프리로딩 방식
|
||||
|
||||
### 3.3 ResultPopupWindow (Vanilla JS 팝업)
|
||||
- 전사 결과 텍스트 표시
|
||||
- 복사 버튼, 닫기 버튼
|
||||
- 자동 숨김 + 마우스 호버 시 유지
|
||||
|
||||
### 3.4 Dashboard (React)
|
||||
- 통계: 총 시간, 단어 수, 세션 수
|
||||
- 최근 세션 목록
|
||||
- Speakly 패턴: MUI Drawer(240px) + Content Area
|
||||
|
||||
### 3.5 Settings (React Modal)
|
||||
- 언어, 테마(light/dark/auto), 핫키 설정
|
||||
- 마이크 선택 + 테스트
|
||||
- Ollama 서버 URL 설정
|
||||
|
||||
## Speakly RE 참조
|
||||
- ClipboardPaste 의사코드: save→set→SendInput(Ctrl+V)→restore
|
||||
- RecordingTip: wave-bar cos 분포, thinking 점근 수렴
|
||||
- ResultPopup: requestAnimationFrame 높이 측정, mouseenter/leave
|
||||
- App.js: MUI 테마, Drawer 네비게이션, 라우팅 패턴
|
||||
- Settings: HotkeyRecordModal, 마이크 선택 UI
|
||||
|
||||
## 완료 조건
|
||||
- [ ] 전사된 텍스트가 활성 앱(메모장 등)에 삽입됨
|
||||
- [ ] RecordingTip 웨이브 애니메이션 작동
|
||||
- [ ] Dashboard에 통계 표시
|
||||
- [ ] Settings에서 핫키/마이크 변경 가능
|
||||
115
docs/phases/phase-4.md
Normal file
115
docs/phases/phase-4.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Phase 4: Ollama LLM 연동 (텍스트 다듬기, 번역)
|
||||
|
||||
## 목표
|
||||
Ollama REST API를 통해 로컬 LLM과 연동하여, 전사된 텍스트를 다듬기(polishing)하거나 번역(translate)할 수 있는 상태. 스트리밍 응답을 UI에 실시간 표시하고, 커스텀 명령어의 기본 구조를 갖춘다.
|
||||
|
||||
## 태스크
|
||||
|
||||
### 4.1 LocalLLMService 구현
|
||||
- `src/main/services/LocalLLMService.ts`
|
||||
- Ollama REST API 연동 (`/api/generate`, `/api/chat`, `/api/tags`)
|
||||
- 싱글톤 + EventEmitter 패턴
|
||||
- 상태 머신: `Unavailable → Available → Generating → Available` (서비스 명세 LLMState 참조)
|
||||
- Ollama 가용성 폴링 (5초 간격, `OLLAMA_CONFIG.pollIntervalMs`)
|
||||
- `availability-changed` 이벤트로 UI에 상태 전파
|
||||
- ConfigService에서 `llm.serverUrl`, `llm.defaultModel` 읽기
|
||||
|
||||
### 4.2 비스트리밍 생성 (`generate`)
|
||||
- `POST /api/generate` 호출 (stream: false)
|
||||
- GenerateOptions 지원: model, temperature, maxTokens, topP, topK, systemPrompt
|
||||
- GenerateResult 반환: text, model, promptTokens, completionTokens, totalDuration
|
||||
- 타임아웃 처리 (AbortController, 기본 120초)
|
||||
|
||||
### 4.3 스트리밍 생성 (`stream`)
|
||||
- `POST /api/generate` (stream: true) → NDJSON 파싱
|
||||
- fetch + ReadableStream으로 청크 처리
|
||||
- `token` 이벤트를 통해 점진적 토큰 전달
|
||||
- AbortController로 중간 취소 지원
|
||||
- 버퍼 관리: 불완전한 JSON 라인 처리
|
||||
|
||||
### 4.4 텍스트 다듬기 모드 (Polishing)
|
||||
- PostProcessCommand `{ type: 'polish', style?: 'formal' | 'casual' }` 처리
|
||||
- 시스템 프롬프트 설계:
|
||||
```
|
||||
[formal] 다음 음성 전사 텍스트를 자연스럽고 격식 있는 문어체로 다듬어주세요.
|
||||
원래 의미를 유지하면서 문법 오류를 수정하고, 불필요한 반복이나 필러를 제거하세요.
|
||||
다듬어진 텍스트만 출력하세요. 설명이나 부가 문구를 붙이지 마세요.
|
||||
[casual] 다음 음성 전사 텍스트를 자연스러운 구어체로 다듬어주세요.
|
||||
원래 의미와 톤을 유지하면서 문법 오류만 수정하세요.
|
||||
다듬어진 텍스트만 출력하세요.
|
||||
```
|
||||
- VoiceModeService의 Processing 상태에서 호출
|
||||
|
||||
### 4.5 번역 모드 (Translate)
|
||||
- PostProcessCommand `{ type: 'translate', targetLanguage: string }` 처리
|
||||
- 시스템 프롬프트 설계:
|
||||
```
|
||||
다음 텍스트를 {targetLanguage}로 번역해주세요.
|
||||
자연스럽고 정확한 번역만 출력하세요. 원문이나 설명을 붙이지 마세요.
|
||||
```
|
||||
- 감지 언어 → 대상 언어 자동 전환 (한국어 감지 시 영어로, 영어 감지 시 한국어로)
|
||||
- ConfigService에서 기본 대상 언어 설정 지원
|
||||
|
||||
### 4.6 커스텀 명령어 기본 구조
|
||||
- PostProcessCommand `{ type: 'custom', prompt: string }` 처리
|
||||
- 사용자가 직접 시스템 프롬프트를 지정하여 LLM에 전달
|
||||
- CustomInstruction 인터페이스 초기 정의:
|
||||
```typescript
|
||||
interface CustomInstruction {
|
||||
id: string;
|
||||
name: string;
|
||||
prompt: string;
|
||||
isBuiltin: boolean;
|
||||
createdAt: number;
|
||||
}
|
||||
```
|
||||
- Phase 6에서 완전 구현 (CRUD, 프리셋, 핫키 바인딩)
|
||||
|
||||
### 4.7 스트리밍 응답 UI 표시
|
||||
- RecordingTipWindow에 `processing` 상태 추가
|
||||
- Thinking 프로그레스 바 (점근 수렴 패턴: `min(95, (1 - 1/(1+1.5*t)) * 100)%`)
|
||||
- 스트리밍 토큰이 도착하면 텍스트 실시간 표시
|
||||
- ResultPopupWindow에 스트리밍 텍스트 영역 추가
|
||||
- 토큰 도착 시 점진적 텍스트 추가
|
||||
- 완료 시 복사 버튼 활성화
|
||||
- IPC 채널:
|
||||
- `llm:streamToken` (main→renderer, 스트리밍 토큰)
|
||||
- `llm:streamComplete` (main→renderer, 생성 완료)
|
||||
- `llm:streamError` (main→renderer, 에러)
|
||||
|
||||
### 4.8 VoiceModeService LLM 통합
|
||||
- RecognitionState.Processing 상태에서 LLM 호출
|
||||
- 전사 완료 → postProcessCommand에 따라 분기:
|
||||
- `none`: 바로 텍스트 삽입
|
||||
- `polish` / `translate` / `custom`: LLM 스트리밍 호출 → 완료 후 텍스트 삽입
|
||||
- `processing-update` 이벤트를 통해 UI에 진행 상황 전달
|
||||
- LLM 미가용 시 postProcess를 `none`으로 폴백 + 사용자 알림
|
||||
|
||||
### 4.9 에러 처리
|
||||
- Ollama 서버 미실행: `LLM_CONNECTION_FAILED` 에러, UI에 "Ollama가 실행 중이 아닙니다" 표시
|
||||
- 모델 미설치: `LLM_MODEL_NOT_FOUND` 에러, UI에 모델 설치 안내 표시
|
||||
- 생성 실패/타임아웃: `LLM_GENERATION_FAILED`, 원본 텍스트로 폴백 옵션 제공
|
||||
- 네트워크 에러: 재시도 없이 즉시 에러 표시 (로컬이므로 재시도 불필요)
|
||||
|
||||
### 4.10 IPC 채널 추가
|
||||
- `llm:getStatus` — LLM 가용 상태 조회
|
||||
- `llm:getModels` — 설치된 Ollama 모델 목록
|
||||
- `llm:generate` — 비스트리밍 텍스트 생성
|
||||
- `llm:stream` — 스트리밍 텍스트 생성 시작
|
||||
- `llm:cancelStream` — 스트리밍 취소
|
||||
- `llm:checkConnection` — Ollama 연결 테스트
|
||||
|
||||
## Speakly RE 참조
|
||||
- GensparkService: 클라우드 LLM 호출 패턴 (D3RO는 로컬 Ollama로 대체)
|
||||
- CustomInstructionService: 명령어 구조, 프롬프트 템플릿
|
||||
- VoiceModeService: Processing 상태 전이, 후처리 파이프라인
|
||||
- RecordingTip: thinking 상태 프로그레스 바, 점근 수렴 패턴
|
||||
|
||||
## 완료 조건
|
||||
- [ ] Ollama 가용성 자동 감지 및 UI 표시
|
||||
- [ ] 전사된 텍스트를 다듬기(polish) 모드로 LLM 처리 가능
|
||||
- [ ] 전사된 텍스트를 번역(translate) 모드로 LLM 처리 가능
|
||||
- [ ] 커스텀 프롬프트로 자유 LLM 처리 가능
|
||||
- [ ] 스트리밍 토큰이 UI에 실시간 표시됨
|
||||
- [ ] Ollama 미실행/모델 미설치 시 적절한 에러 메시지 표시
|
||||
- [ ] LLM 미가용 시 원본 텍스트 삽입으로 폴백
|
||||
148
docs/phases/phase-5.md
Normal file
148
docs/phases/phase-5.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# 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 네비게이션으로 각 화면 이동 가능
|
||||
183
docs/phases/phase-6.md
Normal file
183
docs/phases/phase-6.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# Phase 6: 커스텀 명령어 + 설정 UI 고도화
|
||||
|
||||
## 목표
|
||||
사용자 정의 LLM 명령어 시스템을 완전 구현하고, 설정 UI를 고도화하여 STT/LLM/TTS 모델 선택, 온보딩, 다국어(i18n)를 지원한다.
|
||||
|
||||
## 태스크
|
||||
|
||||
### 6.1 CustomInstructionService 완전 구현
|
||||
- `src/main/services/CustomInstructionService.ts`
|
||||
- electron-store에 명령어 목록 저장 (DB 아닌 설정 파일)
|
||||
- CustomInstruction 타입:
|
||||
```typescript
|
||||
interface CustomInstruction {
|
||||
id: string; // nanoid
|
||||
name: string; // 표시 이름 (예: "번역 (한→영)")
|
||||
description: string; // 설명
|
||||
prompt: string; // 시스템 프롬프트 템플릿
|
||||
icon: string; // MUI 아이콘 이름 또는 이모지
|
||||
isBuiltin: boolean; // 프리셋 여부 (삭제 불가)
|
||||
hotkeyId: string | null; // 바인딩된 핫키 ID (null이면 미설정)
|
||||
order: number; // 표시 순서
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
```
|
||||
- CRUD 메서드:
|
||||
- `getAll(): CustomInstruction[]`
|
||||
- `getById(id: string): CustomInstruction | null`
|
||||
- `create(input: CreateCustomInstructionInput): CustomInstruction`
|
||||
- `update(id: string, data: Partial<CustomInstruction>): void`
|
||||
- `delete(id: string): void` — isBuiltin은 삭제 불가
|
||||
- `reorder(ids: string[]): void` — 순서 변경
|
||||
- `resetBuiltins(): void` — 프리셋을 기본값으로 초기화
|
||||
|
||||
### 6.2 프리셋 명령어 5개
|
||||
- 앱 최초 실행 시 자동 생성 (isBuiltin: true)
|
||||
|
||||
1. **번역** (`translate`)
|
||||
```
|
||||
다음 텍스트를 {{targetLanguage}}로 번역해주세요.
|
||||
자연스럽고 정확한 번역만 출력하세요.
|
||||
```
|
||||
|
||||
2. **요약** (`summarize`)
|
||||
```
|
||||
다음 텍스트의 핵심 내용을 3줄 이내로 요약해주세요.
|
||||
요약문만 출력하세요.
|
||||
```
|
||||
|
||||
3. **전문 리라이트** (`formal-rewrite`)
|
||||
```
|
||||
다음 텍스트를 격식 있는 비즈니스 문체로 다시 작성해주세요.
|
||||
원래 의미를 유지하면서 전문적인 톤으로 변환하세요.
|
||||
다시 작성된 텍스트만 출력하세요.
|
||||
```
|
||||
|
||||
4. **코드 설명** (`explain-code`)
|
||||
```
|
||||
다음 코드를 한국어로 설명해주세요.
|
||||
각 부분이 무엇을 하는지 간결하게 설명하세요.
|
||||
```
|
||||
|
||||
5. **자유 프롬프트** (`free-prompt`)
|
||||
```
|
||||
{{userPrompt}}
|
||||
```
|
||||
- 사용자가 매번 프롬프트를 직접 입력하는 특수 모드
|
||||
- UI에서 프롬프트 입력 필드 표시
|
||||
|
||||
### 6.3 사용자 정의 명령어 CRUD UI
|
||||
- `src/renderer/components/CustomCommands.tsx`
|
||||
- 명령어 목록: 이름, 설명, 프리셋 여부 표시
|
||||
- 추가: 이름, 설명, 프롬프트 템플릿 입력 다이얼로그
|
||||
- 프롬프트 템플릿에 `{{text}}` 변수 자동 삽입 안내
|
||||
- 프롬프트 미리보기 (예시 텍스트로 치환 결과 표시)
|
||||
- 편집: 프리셋은 프롬프트만 수정 가능, 사용자 정의는 전체 수정 가능
|
||||
- 삭제: 프리셋은 삭제 불가 (리셋만 가능), 사용자 정의는 확인 후 삭제
|
||||
- 드래그 앤 드롭으로 순서 변경
|
||||
- IPC 채널:
|
||||
- `customInstruction:getAll`
|
||||
- `customInstruction:create`
|
||||
- `customInstruction:update`
|
||||
- `customInstruction:delete`
|
||||
- `customInstruction:reorder`
|
||||
|
||||
### 6.4 핫키 바인딩 (명령어별)
|
||||
- 각 커스텀 명령어에 개별 핫키 설정 가능
|
||||
- HotkeyService에 동적 핫키 등록/해제 연동
|
||||
- 핫키 녹화 UI (Speakly HotkeyRecordModal 참조):
|
||||
- 모달에서 키 조합 누르면 감지 → 표시 → 저장
|
||||
- 시스템 예약 키 블랙리스트 (Ctrl+C, Ctrl+V, Alt+F4 등)
|
||||
- 중복 핫키 충돌 감지 + 경고
|
||||
- 명령어별 핫키 → VoiceModeService에 postProcess 자동 설정
|
||||
- 핫키 감지 시: 해당 명령어의 prompt로 postProcess 설정 후 녹음 시작
|
||||
|
||||
### 6.5 설정 UI 고도화
|
||||
- `src/renderer/components/Settings.tsx` — MUI Modal 기반
|
||||
- 섹션별 탭 구성:
|
||||
|
||||
**일반 탭:**
|
||||
- 테마 선택 (light/dark/system)
|
||||
- 시작 시 최소화
|
||||
- 닫기 시 트레이로 최소화
|
||||
|
||||
**음성 입력(STT) 탭:**
|
||||
- STT 모델 선택 드롭다운 (base, small, medium, large-v3)
|
||||
- 모델별 크기, 정확도 설명 표시
|
||||
- 미다운로드 모델 표시 + 다운로드 버튼 (선택)
|
||||
- 기본 언어 선택 (auto, ko, en, ja, zh 등)
|
||||
- VAD 필터 토글
|
||||
- 초기 프롬프트 입력
|
||||
- 마이크 디바이스 선택 + 테스트 (Phase 3에서 이관)
|
||||
|
||||
**LLM 탭:**
|
||||
- Ollama 서버 URL 입력 + 연결 테스트 버튼
|
||||
- 기본 LLM 모델 선택 드롭다운 (Ollama에서 설치된 모델 목록 조회)
|
||||
- 기본 온도, 최대 토큰 설정
|
||||
- 기본 후처리 명령 선택
|
||||
|
||||
**TTS 탭:**
|
||||
- TTS 활성화/비활성화 토글
|
||||
- 기본 음성 선택 (설치된 음성 목록)
|
||||
- 말하기 속도 슬라이더 (0.5x ~ 2.0x)
|
||||
- 미리듣기 버튼
|
||||
|
||||
**핫키 탭:**
|
||||
- Dictation 모드 핫키 설정
|
||||
- Hands-free 모드 핫키 설정
|
||||
- 커스텀 명령어별 핫키 설정 (6.4 연동)
|
||||
- 핫키 녹화 모달
|
||||
|
||||
### 6.6 온보딩 플로우 (첫 실행 안내)
|
||||
- 앱 최초 실행 감지: ConfigService에 `onboarding.completed` 플래그
|
||||
- 4단계 온보딩 위저드:
|
||||
1. **환영**: 앱 소개, 주요 기능 설명
|
||||
2. **마이크 설정**: 디바이스 선택 + 테스트 녹음
|
||||
3. **핫키 설정**: 기본 핫키 안내 + 커스텀 설정
|
||||
4. **Ollama 설정**: Ollama 설치 안내 + 연결 테스트, 모델 선택
|
||||
- 건너뛰기 가능, 설정에서 다시 실행 가능
|
||||
- Speakly FlowOnboarding 참조: 단계별 UI, 진행 표시기
|
||||
|
||||
### 6.7 i18n (한국어/영어)
|
||||
- `src/shared/i18n/` 디렉토리
|
||||
- 리소스 파일 구조:
|
||||
```
|
||||
i18n/
|
||||
├── ko.json # 한국어 (기본)
|
||||
└── en.json # 영어
|
||||
```
|
||||
- 간단한 i18n 유틸리티 (라이브러리 없이 직접 구현):
|
||||
```typescript
|
||||
type I18nKey = string;
|
||||
function t(key: I18nKey, params?: Record<string, string>): string;
|
||||
function setLocale(locale: 'ko' | 'en'): void;
|
||||
function getLocale(): string;
|
||||
```
|
||||
- React 컨텍스트: `I18nProvider` + `useI18n()` 훅
|
||||
- ConfigService `ui.language` 연동
|
||||
- 번역 대상: UI 라벨, 에러 메시지, 프리셋 명령어 이름/설명, 온보딩 텍스트
|
||||
- Speakly I18nService 참조: 키-값 리소스 파일, 런타임 언어 전환
|
||||
|
||||
### 6.8 IPC 채널 추가
|
||||
- `customInstruction:getAll`, `customInstruction:create`, `customInstruction:update`, `customInstruction:delete`, `customInstruction:reorder`
|
||||
- `config:getSection`, `config:setSection`, `config:resetSection`
|
||||
- `i18n:getLocale`, `i18n:setLocale`, `i18n:getTranslations`
|
||||
- `onboarding:getStatus`, `onboarding:complete`
|
||||
|
||||
## Speakly RE 참조
|
||||
- CustomInstructionConfigService: 명령어 CRUD, 프리셋 관리, electron-store 저장
|
||||
- HotkeyConfig: 키코드 맵, 시스템 예약 단축키 블랙리스트, HotkeyRecordModal
|
||||
- FlowOnboarding: 단계별 온보딩 UI, 진행 표시, 건너뛰기
|
||||
- I18nService: 키-값 리소스 파일, 런타임 언어 전환, React 컨텍스트 연동
|
||||
- Settings: 섹션별 탭 UI, 디바이스 선택, 모델 선택
|
||||
|
||||
## 완료 조건
|
||||
- [ ] 프리셋 명령어 5개 기본 제공
|
||||
- [ ] 사용자 정의 명령어 추가/편집/삭제/순서변경 가능
|
||||
- [ ] 명령어별 핫키 바인딩 및 핫키로 녹음+명령어 실행 가능
|
||||
- [ ] 설정에서 STT 모델, LLM 모델, TTS 음성 선택 가능
|
||||
- [ ] Ollama 연결 테스트 작동
|
||||
- [ ] 온보딩 위저드가 첫 실행 시 표시됨
|
||||
- [ ] 한국어/영어 UI 전환 가능
|
||||
- [ ] 모든 UI 텍스트가 i18n 리소스에서 로드됨
|
||||
191
docs/phases/phase-7.md
Normal file
191
docs/phases/phase-7.md
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# Phase 7: 테스트 + 빌드 + 배포
|
||||
|
||||
## 목표
|
||||
테스트 커버리지를 확보하고, 프로덕션 빌드/패키징 파이프라인을 구축하여 Windows 설치 파일을 생성한다. CI/CD를 설정하여 자동 빌드 및 배포를 가능하게 한다.
|
||||
|
||||
## 태스크
|
||||
|
||||
### 7.1 테스트 전략 수립
|
||||
- 3계층 테스트 피라미드:
|
||||
- **단위 테스트** (vitest): 서비스 로직, 유틸리티, 상태 머신
|
||||
- **통합 테스트** (vitest): IPC 핸들러, DB 쿼리, 서비스 간 상호작용
|
||||
- **E2E 테스트** (Playwright + electron): 전체 사용자 시나리오
|
||||
- 커버리지 목표: 단위 70%+, 통합 50%+, E2E 핵심 플로우
|
||||
|
||||
### 7.2 단위 테스트 — 최우선 대상
|
||||
- **VoiceModeService 상태 머신**:
|
||||
- 모든 RecognitionState 전이 경로 테스트
|
||||
- 모든 AudioState 전이 경로 테스트
|
||||
- 잘못된 전이 시도 시 에러 처리
|
||||
- 이중 조건 플러시 (sttReady + audioBuffer) 시나리오
|
||||
- 타이밍 상수 검증 (minAudioDurationMs, doublePressMs)
|
||||
- 세션 취소/타임아웃 시나리오
|
||||
- **LocalSTTService**:
|
||||
- sidecar 프로토콜 파싱 (JSON stdout)
|
||||
- 이중 조건 플러시 로직
|
||||
- 모델 로딩 상태 전이
|
||||
- 에러 전파 (sidecar crash, transcription fail)
|
||||
- **LocalLLMService**:
|
||||
- Ollama API 응답 파싱
|
||||
- NDJSON 스트리밍 파싱 (불완전 라인 처리 포함)
|
||||
- 가용성 폴링 로직
|
||||
- 에러 코드별 처리 (연결 실패, 모델 미설치)
|
||||
- **HotkeyService**:
|
||||
- 키코드 매칭 + 수정자 키 조합
|
||||
- 더블프레스 감지 (300ms 윈도우)
|
||||
- holdMode vs toggleMode 동작
|
||||
- **TextInsertService**:
|
||||
- 클립보드 백업/복원 흐름
|
||||
- 삽입 실패 시 클립보드 복원 보장
|
||||
- **HistoryService / DictionaryService**:
|
||||
- CRUD 메서드 (in-memory SQLite 사용)
|
||||
- 검색/필터 쿼리
|
||||
- 보존 정책 (cleanup)
|
||||
- 통계 집계 정확성
|
||||
- **CustomInstructionService**:
|
||||
- CRUD + 프리셋 보호 (삭제 불가)
|
||||
- 순서 변경
|
||||
- 프롬프트 템플릿 변수 치환
|
||||
|
||||
### 7.3 통합 테스트
|
||||
- **IPC 핸들러 테스트**:
|
||||
- 각 IPC 채널이 올바른 서비스 메서드를 호출하는지 검증
|
||||
- 인자 검증 + 에러 응답 형식
|
||||
- preload bridge ↔ main handler 매핑 정합성
|
||||
- **DB 쿼리 통합 테스트**:
|
||||
- drizzle-orm 쿼리가 실제 SQLite에서 올바르게 실행되는지 검증
|
||||
- 마이그레이션 적용 후 스키마 일관성
|
||||
- 동시 접근 시나리오 (WAL 모드)
|
||||
- **서비스 간 상호작용**:
|
||||
- VoiceModeService → AudioCaptureService → LocalSTTService → LocalLLMService → TextInsertService 파이프라인
|
||||
- ConfigService 변경 → 서비스 반영 (설정 핫리로드)
|
||||
|
||||
### 7.4 E2E 테스트 (Playwright)
|
||||
- `@playwright/test` + `electron` fixture 사용
|
||||
- 핵심 시나리오:
|
||||
1. 앱 실행 → 메인 윈도우 표시
|
||||
2. Dashboard 통계 로드
|
||||
3. Settings 열기 → 설정 변경 → 저장
|
||||
4. History 목록 표시 → 검색 → 삭제
|
||||
5. Dictionary 추가 → 편집 → 삭제
|
||||
6. 온보딩 플로우 완주 (첫 실행 시뮬레이션)
|
||||
- 외부 의존성 모킹:
|
||||
- Ollama API: MSW(Mock Service Worker) 또는 로컬 HTTP 서버
|
||||
- faster-whisper sidecar: 모킹된 stdout 응답
|
||||
- piper-tts sidecar: 모킹된 WAV 출력
|
||||
|
||||
### 7.5 테스트 유틸리티
|
||||
- `tests/helpers/`:
|
||||
- `createTestDb()` — in-memory SQLite + 스키마 적용
|
||||
- `mockElectronStore()` — electron-store 모킹
|
||||
- `mockSidecar()` — sidecar stdout/stdin 모킹
|
||||
- `createMockAudioBuffer()` — 테스트용 PCM16 버퍼 생성
|
||||
- `waitForState()` — 상태 머신 전이 대기 유틸
|
||||
- vitest 설정:
|
||||
- `vitest.config.ts`: 경로 별칭, 환경변수, 타임아웃
|
||||
- `vitest.workspace.ts`: unit / integration / e2e 워크스페이스 분리
|
||||
|
||||
### 7.6 electron-vite 빌드 설정
|
||||
- `electron.vite.config.ts`:
|
||||
- main: TypeScript → CJS, externals (better-sqlite3, uiohook-napi, @nut-tree/nut-js)
|
||||
- preload: TypeScript → CJS, contextIsolation 호환
|
||||
- renderer: React + MUI → 번들 (코드 스플리팅, 트리 셰이킹)
|
||||
- 환경 분리: `MAIN_VITE_*`, `RENDERER_VITE_*`
|
||||
- native 모듈 빌드: electron-rebuild 또는 prebuild-install
|
||||
- 팝업 HTML 복사: recording-tip.html, result-popup.html → output
|
||||
|
||||
### 7.7 electron-builder 패키징
|
||||
- `electron-builder.yml` 설정:
|
||||
```yaml
|
||||
appId: com.d3ro.voice
|
||||
productName: D3RO Voice
|
||||
directories:
|
||||
output: release
|
||||
win:
|
||||
target:
|
||||
- target: nsis
|
||||
arch: [x64]
|
||||
icon: resources/icon.ico
|
||||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
createDesktopShortcut: true
|
||||
createStartMenuShortcut: true
|
||||
extraResources:
|
||||
- from: sidecar/
|
||||
to: sidecar/
|
||||
filter: ["**/*"]
|
||||
```
|
||||
- sidecar 번들링:
|
||||
- faster-whisper Python 환경 (embedded Python 또는 PyInstaller 빌드)
|
||||
- piper-tts 바이너리 + 음성 모델
|
||||
- Whisper 모델 파일 (선택: 초기 번들 또는 첫 실행 시 다운로드)
|
||||
- native 모듈 리빌드: better-sqlite3, uiohook-napi, @nut-tree/nut-js
|
||||
- asar 설정: native 모듈은 asar 외부 (`asarUnpack`)
|
||||
|
||||
### 7.8 자동 업데이트 (electron-updater, 선택)
|
||||
- `electron-updater` 라이브러리 통합
|
||||
- GitHub Releases를 업데이트 서버로 사용
|
||||
- 업데이트 체크: 앱 시작 시 + 24시간 주기
|
||||
- 업데이트 플로우:
|
||||
1. `autoUpdater.checkForUpdates()`
|
||||
2. 업데이트 발견 시 알림 (강제 아님)
|
||||
3. 사용자 승인 시 백그라운드 다운로드
|
||||
4. 다운로드 완료 → 재시작 안내
|
||||
- 버전 관리: semver, `package.json` version 필드
|
||||
|
||||
### 7.9 CI/CD (GitHub Actions)
|
||||
- `.github/workflows/ci.yml`:
|
||||
```yaml
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
lint-and-typecheck:
|
||||
- npm run lint
|
||||
- npm run typecheck
|
||||
unit-test:
|
||||
- npm run test:unit
|
||||
integration-test:
|
||||
- npm run test:integration
|
||||
build:
|
||||
- npm run build
|
||||
- 빌드 산출물 아티팩트 업로드
|
||||
```
|
||||
- `.github/workflows/release.yml`:
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
jobs:
|
||||
build-and-release:
|
||||
- npm run build
|
||||
- electron-builder --win --x64
|
||||
- GitHub Release 생성 + 설치파일 업로드
|
||||
```
|
||||
- Windows 러너 사용 (`runs-on: windows-latest`)
|
||||
- 캐시: node_modules, electron 바이너리
|
||||
|
||||
### 7.10 코드 서명 (선택)
|
||||
- Windows: Authenticode 서명 (EV 코드 서명 인증서)
|
||||
- electron-builder 설정:
|
||||
```yaml
|
||||
win:
|
||||
sign: ./scripts/sign.js
|
||||
signingHashAlgorithms: [sha256]
|
||||
```
|
||||
- CI에서 서명: GitHub Secrets에 인증서 저장
|
||||
- 서명 없이도 실행 가능 (SmartScreen 경고 표시)
|
||||
|
||||
## Speakly RE 참조
|
||||
- Speakly 패키징: electron-builder, NSIS 인스톨러, Squirrel.Windows 기반 업데이트
|
||||
- Speakly 빌드: NativeHelper.dll → extraResources, asar 외부 배치
|
||||
- Speakly CI: 자동 빌드/배포 파이프라인 (D3RO는 GitHub Actions로 대체)
|
||||
|
||||
## 완료 조건
|
||||
- [ ] `npm run test:unit` — VoiceModeService, HistoryService 등 핵심 서비스 테스트 통과
|
||||
- [ ] `npm run test:integration` — IPC 핸들러, DB 쿼리 테스트 통과
|
||||
- [ ] `npm run test:e2e` — 핵심 사용자 시나리오 테스트 통과
|
||||
- [ ] `npm run build` — 프로덕션 빌드 성공
|
||||
- [ ] electron-builder로 Windows NSIS 인스톨러 생성
|
||||
- [ ] 인스톨러로 설치 → 실행 → 기본 기능 작동 확인
|
||||
- [ ] GitHub Actions CI 파이프라인 작동 (lint, typecheck, test, build)
|
||||
- [ ] (선택) 자동 업데이트 체크 작동
|
||||
Loading…
Add table
Add a link
Reference in a new issue