Phase 3 구현: 텍스트 삽입 + RecordingTip/ResultPopup + Settings

- TextInsertService: clipboard save→set→Ctrl+V→restore (@nut-tree-fork/nut-js)
- RecordingTip 팝업: 9개 웨이브바 cos분포, thinking 점근수렴, 2-phase 리사이즈
- ResultPopup 팝업: 복사 버튼, auto-close, 마우스 호버 유지, 다크모드
- WindowManager: 팝업 프리로딩, 커서 위치 표시, 멀티모니터 보정
- Settings 모달: General/Audio/STT/LLM 탭
- VoiceModeService: 전사 완료 시 자동 텍스트 삽입 + 팝업 연동
- electron-vite: 팝업 HTML 멀티 엔트리 + popup preload 빌드
This commit is contained in:
Yun Chan 2026-04-05 02:11:58 +09:00
parent 1d152d01a1
commit 517210af2f
16 changed files with 1366 additions and 10 deletions

View file

@ -5,7 +5,15 @@ import { initLoggerService, getLogger } from './services/LoggerService'
import { initConfigService } from './services/ConfigService'
import { getHotkeyService } from './services/HotkeyService'
import { getVoiceModeService } from './services/VoiceModeService'
import { createMainWindow } from './windows/WindowManager'
import {
createMainWindow,
preloadPopupWindows,
showRecordingTip,
hideRecordingTip,
updateRecordingTipState,
sendAudioLevelToTip,
showResultPopup
} from './windows/WindowManager'
import { createTray } from './windows/TrayManager'
import { registerAllIpcHandlers } from './ipc'
@ -24,6 +32,7 @@ export async function bootstrap(): Promise<void> {
{ name: 'create-windows', critical: true, fn: createWindows },
{ name: 'tray', critical: false, fn: initTray },
{ name: 'ipc-handlers', critical: true, fn: initIpcHandlers },
{ name: 'popup-preload', critical: false, fn: initPopupWindows },
{ name: 'hotkey', critical: false, fn: initHotkey },
{ name: 'voice-mode', critical: false, fn: initVoiceMode }
]
@ -72,7 +81,42 @@ async function initHotkey(): Promise<void> {
hotkey.start()
}
async function initPopupWindows(): Promise<void> {
preloadPopupWindows()
}
async function initVoiceMode(): Promise<void> {
const voiceMode = getVoiceModeService()
voiceMode.connectHotkey()
// RecordingTip 연동: 세션 시작/종료 시 팝업 표시/숨김
voiceMode.on('session-started', () => {
showRecordingTip('recording')
})
voiceMode.on('audio-level', ({ level }) => {
sendAudioLevelToTip(level)
})
voiceMode.on('recognition-state-changed', ({ current }) => {
if (current === 'recognizing') {
updateRecordingTipState('thinking')
}
})
voiceMode.on('session-completed', ({ finalText }) => {
hideRecordingTip()
if (finalText.length > 0) {
showResultPopup(finalText)
}
})
voiceMode.on('session-cancelled', () => {
hideRecordingTip()
})
voiceMode.on('error', ({ error }) => {
updateRecordingTipState('error', { errorMessage: error.message })
setTimeout(() => hideRecordingTip(), 3000)
})
}