Phase 10~11 전체 구현: 킬러 피처 5종 + 수익화 시스템

Phase 10 킬러 피처:
- MemoService: 태그 CRUD + 마크다운 내보내기 (memo_tags DB)
- VoiceCommandService: 키워드→명령어 매칭, 프리셋 4종
- ScreenContextService: PowerShell 활성 윈도우 + Ctrl+C 선택 텍스트
- ChainService: LLM 명령어 순차 실행 파이프라인
- CaptionService: 6초 청크 연속 전사 + 시스템 오디오 루프백

VoiceModeService 파이프라인 통합:
- 녹음 시작 → 컨텍스트 캡처 → STT → 키워드 매칭 → LLM(체인/컨텍스트 주입) → 삽입

시스템 오디오 캡처:
- setDisplayMediaRequestHandler + audio: 'loopback' (IPC 브릿지)
- electron-audio-loopback 패키지 contextIsolation 호환 불가 → 직접 구현

Phase 11 수익화:
- LicenseService: Free/Pro/Pro+ 3티어, LemonSqueezy API
- Feature Gate: requireFeature/checkFeature/consumeFeature
- 일일 쿼터: Free dictation 20/일, LLM 10/일 (SQLite daily_usage)
- LicenseModal, ProBadge, UpgradePromptModal UI

디자인 보강:
- d3roTypo(13종), d3roShadow(10종), d3roRadius(7종) 토큰 시스템
- ScreenPanel, ButtonGroup DS 컴포넌트 신규
- PhosphorText 4→13종 변형, MetalDial conic-gradient 광택
- 공유 컴포넌트: EmptyStateCard, SearchInput, PageHeader, HistoryEntryCard

기타:
- 자막 핫키 SSOT 전체 연동 (Config→Hotkey→VoiceMode→Caption→Settings)
- StatusBar 자막 LED + 효과음, 자막 로딩 UI
- LLM 상태 이벤트 전파 수정 (폴링 제거 → onStatusChanged)
- 커맨드 팝업 "선택 해제" 항목 추가
This commit is contained in:
Yun Chan 2026-04-05 21:36:09 +09:00
parent 36d77ca224
commit a31f96bbb8
97 changed files with 11853 additions and 1143 deletions

View file

@ -5,7 +5,7 @@
// Common
// ============================================================
export type ThemeMode = 'light' | 'dark' | 'auto'
export type ThemeMode = 'light' | 'dark' | 'auto' | 'nord' | 'solarized' | 'catppuccin' | 'dracula'
export type VoiceMode = 'dictation' | 'hands-free'
@ -267,7 +267,7 @@ export interface LLMModel {
modifiedAt: string
}
export type LLMAction = 'refine' | 'translate' | 'summarize' | 'expand' | 'grammar' | 'custom'
export type LLMAction = 'refine' | 'translate' | 'summarize' | 'expand' | 'grammar' | 'custom' | 'chain'
export interface LLMProcessParams {
text: string
@ -336,7 +336,7 @@ export interface SetEnabledParams {
enabled: boolean
}
export type HotkeyAction = 'dictation' | 'hands-free' | 'command'
export type HotkeyAction = 'dictation' | 'hands-free' | 'command' | 'caption'
export interface HotkeyTriggeredEvent {
action: HotkeyAction
@ -370,6 +370,8 @@ export interface AppConfig {
dictationShortcut: HotkeyBinding
handsFreeShortcut: HotkeyBinding
commandShortcut: HotkeyBinding
/** 실시간 자막 토글 핫키 (Phase 10.1) */
captionShortcut: HotkeyBinding
hotkeyEnabled: boolean
insertMethod: 'clipboard' | 'keyboard'
autoInsert: boolean
@ -380,6 +382,8 @@ export interface AppConfig {
agentModeEnabled: boolean
/** 핸즈프리 모드 활성화 (토글) */
handsFreeEnabled: boolean
/** 스크린 컨텍스트 캡처 활성화 (Phase 10.2) */
screenContextEnabled: boolean
}
export interface ConfigGetParams {
@ -662,3 +666,284 @@ export interface WeeklyStats {
wordCount: number
sessionCount: number
}
// ============================================================
// Phase 10: Memo Tags (10.3)
// ============================================================
export interface MemoTag {
id: string
historyId: string
tag: string
createdAt: number
}
export interface AddTagParams {
historyId: string
tag: string
}
export interface RemoveTagParams {
historyId: string
tag: string
}
export interface GetTagsParams {
historyId: string
}
export interface SearchByTagParams {
tag: string
page: number
pageSize: number
}
export interface ExportMemoParams {
format: 'markdown'
tag?: string
from?: string
to?: string
}
export interface TagCount {
tag: string
count: number
}
// ============================================================
// Phase 10: Voice Commands (10.5)
// ============================================================
export type KeywordMatchMode = 'prefix' | 'suffix' | 'contains'
export interface VoiceCommandKeyword {
keyword: string
matchMode: KeywordMatchMode
}
export interface VoiceCommandRule {
id: string
instructionId: string
keywords: VoiceCommandKeyword[]
enabled: boolean
priority: number
}
export interface VoiceCommandMatch {
matched: boolean
ruleId: string | null
instructionId: string | null
/** 키워드 제거 후 남은 텍스트 */
cleanedText: string
/** 매칭된 키워드 */
matchedKeyword: string | null
}
export interface SetVoiceCommandKeywordsParams {
instructionId: string
keywords: VoiceCommandKeyword[]
}
export interface SetVoiceCommandEnabledParams {
enabled: boolean
}
// ============================================================
// Phase 10: Screen Context (10.2)
// ============================================================
export interface ScreenContext {
appName: string | null
windowTitle: string | null
selectedText: string | null
capturedAt: number
}
export interface CaptureContextResult {
context: ScreenContext
/** 선택 텍스트 캡처 시도 여부 */
selectedTextAttempted: boolean
}
// ============================================================
// Phase 10: LLM Chain (10.4)
// ============================================================
export interface ChainStep {
instructionId: string
/** 이전 단계 결과 사용 or 원본 텍스트 사용 */
inputSource: 'previous' | 'original'
}
export interface LLMChain {
id: string
name: string
steps: ChainStep[]
createdAt: number
updatedAt: number
}
export interface CreateChainParams {
name: string
steps: ChainStep[]
}
export interface UpdateChainParams {
id: string
name?: string
steps?: ChainStep[]
}
export interface DeleteChainParams {
id: string
}
export interface ExecuteChainParams {
chainId: string
text: string
}
export interface ChainProgress {
chainId: string
currentStep: number
totalSteps: number
stepName: string
intermediateText: string
}
export interface ChainExecutionResult {
chainId: string
finalText: string
steps: Array<{ instructionId: string; output: string; durationMs: number }>
totalDurationMs: number
}
// ============================================================
// Phase 10: Live Caption (10.1)
// ============================================================
export type CaptionState = 'inactive' | 'starting' | 'active' | 'stopping'
export interface CaptionSegment {
id: string
text: string
timestamp: number
isFinal: boolean
}
export type CaptionAudioSource = 'mic' | 'system' | 'both'
export interface CaptionConfig {
fontSize: number
opacity: number
maxLines: number
autoClearMs: number
/** 오디오 소스: 마이크 / 시스템 오디오 / 둘 다 */
audioSource: CaptionAudioSource
}
export interface CaptionSessionSummary {
sessionId: string
segments: CaptionSegment[]
startedAt: number
endedAt: number
totalDurationMs: number
}
// ============================================================
// Phase 11: License & Monetization
// ============================================================
/** 라이센스 티어 */
export type LicenseTier = 'free' | 'pro' | 'pro_plus'
/** 기능 게이팅 대상 */
export enum Feature {
// 쿼터 제한 기능 (Free에서 횟수 제한)
DICTATION = 'dictation',
LLM_PROCESS = 'llm_process',
// Pro 이상
HISTORY_UNLIMITED = 'history_unlimited',
HISTORY_EXPORT = 'history_export',
CUSTOM_INSTRUCTION_CREATE = 'custom_instruction_create',
LIVE_CAPTION = 'live_caption',
SCREEN_CONTEXT = 'screen_context',
VOICE_MEMO = 'voice_memo',
VOICE_COMMAND = 'voice_command',
LLM_CHAIN = 'llm_chain',
// Pro+ 이상
FILE_TRANSCRIPTION = 'file_transcription',
VOICE_CONVERSATION = 'voice_conversation',
DICTATION_TEMPLATE = 'dictation_template',
MEETING_SUMMARY = 'meeting_summary',
LOCAL_RAG = 'local_rag',
OS_AUTOMATION = 'os_automation',
}
/** 라이센스 정보 (electron-store에 저장) */
export interface LicenseInfo {
tier: LicenseTier
licenseKey: string | null
activatedAt: number | null
machineId: string
/** 마지막 온라인 검증 시각 */
lastVerifiedAt: number | null
/** 오프라인 유예 만료 (lastVerifiedAt + 30일) */
offlineGraceUntil: number | null
}
/** 일일 사용량 */
export interface DailyUsage {
date: string // 'YYYY-MM-DD'
feature: string
count: number
}
/** 쿼터 정보 */
export interface UsageQuota {
feature: Feature
used: number
limit: number // -1 = 무제한
remaining: number // -1 = 무제한
resetAt: string // 다음 리셋 시각 (내일 00:00) ISO 8601
}
/** 기능 접근 결과 */
export interface FeatureAccess {
allowed: boolean
reason: 'ok' | 'quota_exceeded' | 'tier_required' | 'license_expired'
requiredTier?: LicenseTier
quota?: UsageQuota
}
/** 업그레이드 유도 이벤트 */
export interface UpgradePromptEvent {
feature: Feature
reason: 'quota_exceeded' | 'tier_required'
currentTier: LicenseTier
requiredTier: LicenseTier
quota?: UsageQuota
}
/** 라이센스 활성화 파라미터 */
export interface ActivateLicenseParams {
licenseKey: string
}
/** 라이센스 활성화 결과 */
export interface ActivateLicenseResult {
success: boolean
tier: LicenseTier
message: string
}
/** 티어별 기능 비교 항목 */
export interface TierComparison {
feature: Feature
featureLabel: string
free: boolean | string
pro: boolean | string
proPlus: boolean | string
}