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:
parent
36d77ca224
commit
a31f96bbb8
97 changed files with 11853 additions and 1143 deletions
|
|
@ -89,7 +89,31 @@ export enum ErrorCode {
|
|||
DictionaryExportFailed = 723,
|
||||
DictionaryImportInvalidFormat = 724,
|
||||
|
||||
// === Config (800-899) ===
|
||||
// === Phase 10: Memo Tags (730-739) ===
|
||||
MemoTagDuplicate = 730,
|
||||
MemoTagNotFound = 731,
|
||||
MemoExportFailed = 732,
|
||||
|
||||
// === Phase 10: Voice Commands (740-749) ===
|
||||
VoiceCommandMatchFailed = 740,
|
||||
VoiceCommandNotFound = 741,
|
||||
|
||||
// === Phase 10: Screen Context (750-759) ===
|
||||
ContextCaptureFailed = 750,
|
||||
ContextSelectedTextFailed = 751,
|
||||
|
||||
// === Phase 10: LLM Chain (760-769) ===
|
||||
ChainNotFound = 760,
|
||||
ChainExecutionFailed = 761,
|
||||
ChainStepFailed = 762,
|
||||
ChainCancelled = 763,
|
||||
|
||||
// === Phase 10: Live Caption (770-779) ===
|
||||
CaptionStartFailed = 770,
|
||||
CaptionAlreadyActive = 771,
|
||||
CaptionSTTFailed = 772,
|
||||
|
||||
// === Config (800-849) ===
|
||||
ConfigReadFailed = 800,
|
||||
ConfigWriteFailed = 801,
|
||||
ConfigInvalidValue = 802,
|
||||
|
|
@ -97,6 +121,18 @@ export enum ErrorCode {
|
|||
ConfigResetFailed = 804,
|
||||
ConfigMigrationFailed = 810,
|
||||
|
||||
// === License (850-869) ===
|
||||
LicenseKeyInvalid = 850,
|
||||
LicenseKeyExpired = 851,
|
||||
LicenseActivationFailed = 852,
|
||||
LicenseDeactivationFailed = 853,
|
||||
LicenseMachineIdMismatch = 854,
|
||||
LicenseOfflineGraceExpired = 855,
|
||||
LicenseVerificationFailed = 856,
|
||||
FeatureNotAvailable = 860,
|
||||
QuotaExceeded = 861,
|
||||
TierRequired = 862,
|
||||
|
||||
// === System / Window (900-999) ===
|
||||
WindowCreationFailed = 900,
|
||||
WindowNotFound = 901,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ export const IPC_CHANNELS = {
|
|||
SET_HANDS_FREE_SHORTCUT: 'hotkey:setHandsFreeShortcut',
|
||||
GET_COMMAND_SHORTCUT: 'hotkey:getCommandShortcut',
|
||||
SET_COMMAND_SHORTCUT: 'hotkey:setCommandShortcut',
|
||||
GET_CAPTION_SHORTCUT: 'hotkey:getCaptionShortcut',
|
||||
SET_CAPTION_SHORTCUT: 'hotkey:setCaptionShortcut',
|
||||
IS_ENABLED: 'hotkey:isEnabled',
|
||||
SET_ENABLED: 'hotkey:setEnabled',
|
||||
START_RECORDING: 'hotkey:startRecording',
|
||||
|
|
@ -159,7 +161,79 @@ export const IPC_CHANNELS = {
|
|||
GET_WEEKLY: 'stats:getWeekly',
|
||||
// Main → Renderer events
|
||||
UPDATED: 'stats:updated'
|
||||
}
|
||||
},
|
||||
|
||||
// ── Phase 10: Memo Tags (10.3) ──
|
||||
MEMO: {
|
||||
GET_TAGS: 'memo:getTags',
|
||||
ADD_TAG: 'memo:addTag',
|
||||
REMOVE_TAG: 'memo:removeTag',
|
||||
GET_ALL_TAGS: 'memo:getAllTags',
|
||||
SEARCH_BY_TAG: 'memo:searchByTag',
|
||||
EXPORT: 'memo:export',
|
||||
},
|
||||
|
||||
// ── Phase 10: Voice Commands (10.5) ──
|
||||
VOICE_COMMAND: {
|
||||
GET_ALL: 'voiceCommand:getAll',
|
||||
SET_KEYWORDS: 'voiceCommand:setKeywords',
|
||||
SET_ENABLED: 'voiceCommand:setEnabled',
|
||||
IS_ENABLED: 'voiceCommand:isEnabled',
|
||||
// Main → Renderer events
|
||||
MATCHED: 'voiceCommand:matched',
|
||||
},
|
||||
|
||||
// ── Phase 10: Screen Context (10.2) ──
|
||||
CONTEXT: {
|
||||
CAPTURE: 'context:capture',
|
||||
GET_CONFIG: 'context:getConfig',
|
||||
SET_ENABLED: 'context:setEnabled',
|
||||
IS_ENABLED: 'context:isEnabled',
|
||||
},
|
||||
|
||||
// ── Phase 10: LLM Chain (10.4) ──
|
||||
CHAIN: {
|
||||
GET_ALL: 'chain:getAll',
|
||||
CREATE: 'chain:create',
|
||||
UPDATE: 'chain:update',
|
||||
DELETE: 'chain:delete',
|
||||
EXECUTE: 'chain:execute',
|
||||
// Main → Renderer events
|
||||
PROGRESS: 'chain:progress',
|
||||
},
|
||||
|
||||
// ── Phase 10: Live Caption (10.1) ──
|
||||
CAPTION: {
|
||||
START: 'caption:start',
|
||||
STOP: 'caption:stop',
|
||||
GET_STATE: 'caption:getState',
|
||||
SET_CONFIG: 'caption:setConfig',
|
||||
GET_CONFIG: 'caption:getConfig',
|
||||
/** 렌더러 → 메인: 시스템 오디오 PCM 데이터 전달 */
|
||||
SYSTEM_AUDIO_DATA: 'caption:systemAudioData',
|
||||
// Main → Renderer events
|
||||
SEGMENT: 'caption:segment',
|
||||
DELTA: 'caption:delta',
|
||||
STATE_CHANGED: 'caption:stateChanged',
|
||||
SESSION_SAVED: 'caption:sessionSaved',
|
||||
/** 메인 → 렌더러: 시스템 오디오 캡처 시작/정지 요청 */
|
||||
START_SYSTEM_AUDIO: 'caption:startSystemAudio',
|
||||
STOP_SYSTEM_AUDIO: 'caption:stopSystemAudio',
|
||||
},
|
||||
|
||||
// ── Phase 11: License & Monetization ──
|
||||
LICENSE: {
|
||||
GET_INFO: 'license:getInfo',
|
||||
ACTIVATE: 'license:activate',
|
||||
DEACTIVATE: 'license:deactivate',
|
||||
CHECK_FEATURE: 'license:checkFeature',
|
||||
GET_USAGE: 'license:getUsage',
|
||||
GET_ALL_USAGE: 'license:getAllUsage',
|
||||
GET_TIER_COMPARISON: 'license:getTierComparison',
|
||||
// Main → Renderer events
|
||||
UPGRADE_PROMPT: 'license:upgradePrompt',
|
||||
TIER_CHANGED: 'license:tierChanged',
|
||||
},
|
||||
} as const
|
||||
|
||||
// 타입 유틸리티: 채널명 유니온 추출
|
||||
|
|
|
|||
199
src/shared/theme-vars.ts
Normal file
199
src/shared/theme-vars.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// src/shared/theme-vars.ts
|
||||
// 팝업 윈도우용 CSS 변수 맵.
|
||||
// main process (WindowManager)에서 insertCSS로 팝업에 테마를 주입할 때 사용.
|
||||
// renderer의 theme.ts RAW와 동기화 유지 필요.
|
||||
// Node.js/Electron main 환경에서 import 가능 (DOM API 없음).
|
||||
|
||||
export type PopupThemeKey = 'dark' | 'light' | 'nord' | 'solarized' | 'catppuccin' | 'dracula'
|
||||
|
||||
interface PopupThemeVars {
|
||||
/** 카드 배경 (팝업 본체) */
|
||||
'--d3-bg-card': string
|
||||
/** 앱 배경 (recording-tip 반투명 배경) */
|
||||
'--d3-bg-tip': string
|
||||
/** 주요 텍스트 */
|
||||
'--d3-text-primary': string
|
||||
/** 보조 텍스트 */
|
||||
'--d3-text-secondary': string
|
||||
/** 비활성 텍스트 (번호, 시간 등) */
|
||||
'--d3-text-inactive': string
|
||||
/** 힌트/최약 텍스트 */
|
||||
'--d3-text-muted': string
|
||||
/** dimLabel 텍스트 (command-popup 제목) */
|
||||
'--d3-text-dimLabel': string
|
||||
/** 기본 테두리 */
|
||||
'--d3-border-default': string
|
||||
/** 미묘한 테두리 (구분선, hover) */
|
||||
'--d3-border-subtle': string
|
||||
/** 강한 테두리 (progress background) */
|
||||
'--d3-border-strong': string
|
||||
/** 악센트 색상 (웨이브바, 선택 바) */
|
||||
'--d3-accent-main': string
|
||||
/** 악센트 dim (active 배경) */
|
||||
'--d3-accent-dim': string
|
||||
/** 팝업 박스 섀도 */
|
||||
'--d3-shadow-popup': string
|
||||
/** 결과 팝업 배경 (불투명) */
|
||||
'--d3-bg-result': string
|
||||
/** 결과 팝업 테두리 */
|
||||
'--d3-border-result': string
|
||||
/** 결과 팝업 텍스트 */
|
||||
'--d3-text-result': string
|
||||
/** 결과 팝업 버튼 색상 */
|
||||
'--d3-action-btn': string
|
||||
/** 결과 팝업 버튼 hover 배경 */
|
||||
'--d3-action-btn-hover-bg': string
|
||||
/** 결과 팝업 버튼 hover 색상 */
|
||||
'--d3-action-btn-hover': string
|
||||
}
|
||||
|
||||
const POPUP_THEME_VARS: Record<PopupThemeKey, PopupThemeVars> = {
|
||||
dark: {
|
||||
'--d3-bg-card': '#242427',
|
||||
'--d3-bg-tip': 'rgba(0,0,0,0.85)',
|
||||
'--d3-text-primary': 'rgba(255,255,255,0.87)',
|
||||
'--d3-text-secondary': 'rgba(255,255,255,0.6)',
|
||||
'--d3-text-inactive': 'rgba(255,255,255,0.3)',
|
||||
'--d3-text-muted': 'rgba(255,255,255,0.25)',
|
||||
'--d3-text-dimLabel': '#5c2615',
|
||||
'--d3-border-default': 'rgba(255,255,255,0.08)',
|
||||
'--d3-border-subtle': 'rgba(255,255,255,0.06)',
|
||||
'--d3-border-strong': 'rgba(255,255,255,0.15)',
|
||||
'--d3-accent-main': '#f25b29',
|
||||
'--d3-accent-dim': 'rgba(242,91,41,0.08)',
|
||||
'--d3-shadow-popup': '0 8px 32px rgba(0,0,0,0.5)',
|
||||
'--d3-bg-result': '#1e1e1e',
|
||||
'--d3-border-result': 'rgba(255,255,255,0.08)',
|
||||
'--d3-text-result': 'rgba(255,255,255,0.87)',
|
||||
'--d3-action-btn': 'rgba(255,255,255,0.4)',
|
||||
'--d3-action-btn-hover-bg': 'rgba(255,255,255,0.08)',
|
||||
'--d3-action-btn-hover': 'rgba(255,255,255,0.7)',
|
||||
},
|
||||
light: {
|
||||
'--d3-bg-card': '#ffffff',
|
||||
'--d3-bg-tip': 'rgba(255,255,255,0.92)',
|
||||
'--d3-text-primary': 'rgba(0,0,0,0.87)',
|
||||
'--d3-text-secondary': 'rgba(0,0,0,0.6)',
|
||||
'--d3-text-inactive': 'rgba(0,0,0,0.35)',
|
||||
'--d3-text-muted': 'rgba(0,0,0,0.25)',
|
||||
'--d3-text-dimLabel': '#b07040',
|
||||
'--d3-border-default': 'rgba(0,0,0,0.10)',
|
||||
'--d3-border-subtle': 'rgba(0,0,0,0.06)',
|
||||
'--d3-border-strong': 'rgba(0,0,0,0.14)',
|
||||
'--d3-accent-main': '#f25b29',
|
||||
'--d3-accent-dim': 'rgba(242,91,41,0.08)',
|
||||
'--d3-shadow-popup': '0 8px 32px rgba(0,0,0,0.12)',
|
||||
'--d3-bg-result': '#ffffff',
|
||||
'--d3-border-result': 'rgba(0,0,0,0.08)',
|
||||
'--d3-text-result': 'rgba(0,0,0,0.87)',
|
||||
'--d3-action-btn': 'rgba(0,0,0,0.4)',
|
||||
'--d3-action-btn-hover-bg': 'rgba(0,0,0,0.06)',
|
||||
'--d3-action-btn-hover': 'rgba(0,0,0,0.7)',
|
||||
},
|
||||
nord: {
|
||||
'--d3-bg-card': '#3b4252',
|
||||
'--d3-bg-tip': 'rgba(46,52,64,0.92)',
|
||||
'--d3-text-primary': 'rgba(236,239,244,0.87)',
|
||||
'--d3-text-secondary': 'rgba(216,222,233,0.6)',
|
||||
'--d3-text-inactive': 'rgba(216,222,233,0.35)',
|
||||
'--d3-text-muted': 'rgba(216,222,233,0.25)',
|
||||
'--d3-text-dimLabel': '#5e81ac',
|
||||
'--d3-border-default': 'rgba(216,222,233,0.10)',
|
||||
'--d3-border-subtle': 'rgba(216,222,233,0.06)',
|
||||
'--d3-border-strong': 'rgba(216,222,233,0.16)',
|
||||
'--d3-accent-main': '#88c0d0',
|
||||
'--d3-accent-dim': 'rgba(136,192,208,0.08)',
|
||||
'--d3-shadow-popup': '0 8px 32px rgba(0,0,0,0.5)',
|
||||
'--d3-bg-result': '#434c5e',
|
||||
'--d3-border-result': 'rgba(216,222,233,0.10)',
|
||||
'--d3-text-result': 'rgba(236,239,244,0.87)',
|
||||
'--d3-action-btn': 'rgba(216,222,233,0.4)',
|
||||
'--d3-action-btn-hover-bg': 'rgba(216,222,233,0.08)',
|
||||
'--d3-action-btn-hover': 'rgba(216,222,233,0.7)',
|
||||
},
|
||||
solarized: {
|
||||
'--d3-bg-card': '#073642',
|
||||
'--d3-bg-tip': 'rgba(0,43,54,0.92)',
|
||||
'--d3-text-primary': 'rgba(238,232,213,0.87)',
|
||||
'--d3-text-secondary': 'rgba(147,161,161,0.7)',
|
||||
'--d3-text-inactive': 'rgba(147,161,161,0.4)',
|
||||
'--d3-text-muted': 'rgba(147,161,161,0.25)',
|
||||
'--d3-text-dimLabel': '#7a6c2e',
|
||||
'--d3-border-default': 'rgba(131,148,150,0.13)',
|
||||
'--d3-border-subtle': 'rgba(131,148,150,0.08)',
|
||||
'--d3-border-strong': 'rgba(131,148,150,0.20)',
|
||||
'--d3-accent-main': '#b58900',
|
||||
'--d3-accent-dim': 'rgba(181,137,0,0.08)',
|
||||
'--d3-shadow-popup': '0 8px 32px rgba(0,0,0,0.5)',
|
||||
'--d3-bg-result': '#0d4250',
|
||||
'--d3-border-result': 'rgba(131,148,150,0.13)',
|
||||
'--d3-text-result': 'rgba(238,232,213,0.87)',
|
||||
'--d3-action-btn': 'rgba(147,161,161,0.4)',
|
||||
'--d3-action-btn-hover-bg': 'rgba(131,148,150,0.10)',
|
||||
'--d3-action-btn-hover': 'rgba(147,161,161,0.7)',
|
||||
},
|
||||
catppuccin: {
|
||||
'--d3-bg-card': '#313244',
|
||||
'--d3-bg-tip': 'rgba(30,30,46,0.92)',
|
||||
'--d3-text-primary': 'rgba(205,214,244,0.87)',
|
||||
'--d3-text-secondary': 'rgba(186,194,222,0.7)',
|
||||
'--d3-text-inactive': 'rgba(166,173,200,0.4)',
|
||||
'--d3-text-muted': 'rgba(166,173,200,0.25)',
|
||||
'--d3-text-dimLabel': '#585b70',
|
||||
'--d3-border-default': 'rgba(205,214,244,0.08)',
|
||||
'--d3-border-subtle': 'rgba(205,214,244,0.04)',
|
||||
'--d3-border-strong': 'rgba(205,214,244,0.14)',
|
||||
'--d3-accent-main': '#cba6f7',
|
||||
'--d3-accent-dim': 'rgba(203,166,247,0.08)',
|
||||
'--d3-shadow-popup': '0 8px 32px rgba(0,0,0,0.5)',
|
||||
'--d3-bg-result': '#3a3a54',
|
||||
'--d3-border-result': 'rgba(205,214,244,0.08)',
|
||||
'--d3-text-result': 'rgba(205,214,244,0.87)',
|
||||
'--d3-action-btn': 'rgba(166,173,200,0.4)',
|
||||
'--d3-action-btn-hover-bg': 'rgba(205,214,244,0.06)',
|
||||
'--d3-action-btn-hover': 'rgba(205,214,244,0.7)',
|
||||
},
|
||||
dracula: {
|
||||
'--d3-bg-card': '#44475a',
|
||||
'--d3-bg-tip': 'rgba(40,42,54,0.92)',
|
||||
'--d3-text-primary': 'rgba(248,248,242,0.87)',
|
||||
'--d3-text-secondary': 'rgba(169,176,208,0.7)',
|
||||
'--d3-text-inactive': 'rgba(169,176,208,0.4)',
|
||||
'--d3-text-muted': 'rgba(169,176,208,0.25)',
|
||||
'--d3-text-dimLabel': '#6272a4',
|
||||
'--d3-border-default': 'rgba(248,248,242,0.08)',
|
||||
'--d3-border-subtle': 'rgba(248,248,242,0.04)',
|
||||
'--d3-border-strong': 'rgba(248,248,242,0.14)',
|
||||
'--d3-accent-main': '#bd93f9',
|
||||
'--d3-accent-dim': 'rgba(189,147,249,0.08)',
|
||||
'--d3-shadow-popup': '0 8px 32px rgba(0,0,0,0.5)',
|
||||
'--d3-bg-result': '#4f5266',
|
||||
'--d3-border-result': 'rgba(248,248,242,0.08)',
|
||||
'--d3-text-result': 'rgba(248,248,242,0.87)',
|
||||
'--d3-action-btn': 'rgba(169,176,208,0.4)',
|
||||
'--d3-action-btn-hover-bg': 'rgba(248,248,242,0.06)',
|
||||
'--d3-action-btn-hover': 'rgba(248,248,242,0.7)',
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 팝업용 CSS 변수 맵 조회.
|
||||
* ThemeMode('system' | 'light' | 'dark') 또는 테마 키를 받아 해당 변수 맵 반환.
|
||||
* 'system' 또는 알 수 없는 값은 'dark'로 폴백.
|
||||
*/
|
||||
export function getPopupThemeVars(themeKey: string): PopupThemeVars {
|
||||
const key = themeKey as PopupThemeKey
|
||||
return POPUP_THEME_VARS[key] ?? POPUP_THEME_VARS.dark
|
||||
}
|
||||
|
||||
/**
|
||||
* 팝업 BrowserWindow에 insertCSS로 주입할 CSS 문자열 반환.
|
||||
* `:root { --d3-bg-card: #242427; ... }`
|
||||
*/
|
||||
export function buildPopupThemeCss(themeKey: string): string {
|
||||
const vars = getPopupThemeVars(themeKey)
|
||||
const declarations = Object.entries(vars)
|
||||
.map(([prop, value]) => ` ${prop}: ${value};`)
|
||||
.join('\n')
|
||||
return `:root {\n${declarations}\n}`
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue