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

@ -3,7 +3,8 @@
import { useRef, useEffect, useCallback } from 'react'
import { Box } from '@mui/material'
import { d3roPalette, d3roFontMono } from '../../theme'
import { useTheme } from '@mui/material/styles'
import { d3roPalette, d3roFontMono, d3roShadow } from '../../theme'
// ── WebGL 유틸 ─────────────────────────────────────────
@ -112,6 +113,8 @@ interface CrtDisplayProps {
frequency?: number
/** 글리치 트리거 (변경 시 글리치 발생) */
glitchTrigger?: number
/** 실시간 오디오 레벨 (0.0~1.0) — 파형 진폭에 반영 */
audioLevel?: number
/** 오버레이 콘텐츠 (인광 텍스트 등) */
children?: React.ReactNode
/** 높이 (기본 280px) */
@ -122,9 +125,12 @@ export function CrtDisplay({
amplitude = 0.1,
frequency = 8.0,
glitchTrigger = 0,
audioLevel = 0,
children,
height = 280,
}: CrtDisplayProps): React.ReactElement {
const theme = useTheme()
const isLight = theme.palette.mode === 'light'
const canvasRef = useRef<HTMLCanvasElement>(null)
const glRef = useRef<{
gl: WebGLRenderingContext
@ -138,15 +144,20 @@ export function CrtDisplay({
const glitchRef = useRef(0)
const ampRef = useRef(amplitude)
const freqRef = useRef(frequency)
const audioLevelRef = useRef(audioLevel)
const currentAmpRef = useRef(amplitude)
const currentFreqRef = useRef(frequency)
// amplitude/frequency 변경 추적
// amplitude/frequency/audioLevel 변경 추적
useEffect(() => {
ampRef.current = amplitude
freqRef.current = frequency
}, [amplitude, frequency])
useEffect(() => {
audioLevelRef.current = audioLevel
}, [audioLevel])
// 글리치 트리거
useEffect(() => {
if (glitchTrigger > 0) {
@ -160,8 +171,11 @@ export function CrtDisplay({
const { gl, uTime, uGlitch, uAmp, uFreq } = ctx
// audioLevel → amplitude 반영: 기본 amplitude + 오디오 레벨로 증폭
const targetAmp = ampRef.current + audioLevelRef.current * 0.35
// Smoothing
currentAmpRef.current += (ampRef.current - currentAmpRef.current) * 0.1
currentAmpRef.current += (targetAmp - currentAmpRef.current) * 0.15
currentFreqRef.current += (freqRef.current - currentFreqRef.current) * 0.1
glitchRef.current *= 0.85
@ -221,7 +235,7 @@ export function CrtDisplay({
height,
bgcolor: d3roPalette.bg.crtBezel,
borderRadius: '8px',
boxShadow: 'inset 0 4px 12px rgba(0,0,0,0.9), inset 0 0 0 1px #000, 0 1px 1px rgba(255,255,255,0.1)',
boxShadow: d3roShadow.insetDeep,
overflow: 'hidden',
}}
>
@ -233,12 +247,19 @@ export function CrtDisplay({
borderRadius: '6px',
bgcolor: d3roPalette.bg.crtGlass,
overflow: 'hidden',
boxShadow: 'inset 0 0 20px rgba(0,0,0,0.8)',
boxShadow: d3roShadow.screenGlow,
}}
>
<canvas
ref={canvasRef}
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
// 라이트 모드에서 WebGL 셰이더(다크 전용)를 반전하여 밝은 배경에 어울리게 조정
...(isLight && { filter: 'invert(0.88) hue-rotate(180deg)', opacity: 0.9 }),
}}
/>
{/* Glass reflection */}
@ -246,7 +267,9 @@ export function CrtDisplay({
sx={{
position: 'absolute',
top: 0, left: 0, right: 0, bottom: '50%',
background: 'linear-gradient(180deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 100%)',
background: isLight
? 'linear-gradient(180deg, rgba(255,255,255,0.30) 0%, rgba(255,255,255,0) 100%)'
: 'linear-gradient(180deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 100%)',
pointerEvents: 'none',
zIndex: 10,
}}