커맨드 선택 팝업 (Ctrl+Shift+C): 커서 위치에서 빠른 명령어 전환

- command-popup: Vanilla JS 팝업 (히스토리 팝업과 동일 패턴)
  Arrow↑↓ 선택, Enter 적용, 1-5 직접 선택, ESC 닫기
- WindowManager: showCommandPopup/hideCommandPopup/sendKeyToCommandPopup
- bootstrap: Ctrl+Shift+C 단축키 등록 + command:selected IPC
- electron.vite.config: command-popup HTML 빌드 엔트리 추가
- 선택한 명령어가 즉시 activeInstructionId로 설정 → 다음 녹음에 적용
This commit is contained in:
Yun Chan 2026-04-05 13:26:59 +09:00
parent fea923d302
commit 1361b95a4d
6 changed files with 419 additions and 4 deletions

View file

@ -3,7 +3,7 @@
import { join } from 'path'
import { app, dialog, globalShortcut, ipcMain as ipcMainRef } from 'electron'
import { initLoggerService, getLogger } from './services/LoggerService'
import { initConfigService } from './services/ConfigService'
import { initConfigService, configGet, configSet } from './services/ConfigService'
import { getHotkeyService } from './services/HotkeyService'
import { getVoiceModeService } from './services/VoiceModeService'
import { getLocalLLMService } from './services/LocalLLMService'
@ -19,7 +19,11 @@ import {
showHistoryPopup,
hideHistoryPopup,
sendKeyToHistoryPopup,
isHistoryPopupVisible
isHistoryPopupVisible,
showCommandPopup,
hideCommandPopup,
sendKeyToCommandPopup,
isCommandPopupVisible,
} from './windows/WindowManager'
import { createTray } from './windows/TrayManager'
import { registerAllIpcHandlers } from './ipc'
@ -125,6 +129,20 @@ async function initPopupWindows(): Promise<void> {
}
})
// Ctrl+Shift+C → 커맨드 선택 팝업 토글
globalShortcut.register('Ctrl+Shift+C', () => {
if (isCommandPopupVisible()) {
hideCommandPopup()
unregisterPopupNavKeys()
} else {
const instructions = getCustomInstructionService().getAll()
const activeId = configGet('activeInstructionId' as keyof import('@shared/types').AppConfig) as unknown as string
showCommandPopup(instructions as unknown as Array<Record<string, unknown>>, activeId || null)
registerPopupNavKeys('command')
}
})
setupCommandPopupIPC()
}
// 히스토리 팝업 키 네비게이션 등록/해제
@ -138,12 +156,14 @@ const POPUP_NAV_KEYS: Array<{ accel: string; key: string }> = [
{ accel: '7', key: '7' }, { accel: '8', key: '8' }, { accel: '9', key: '9' },
]
function registerPopupNavKeys(): void {
function registerPopupNavKeys(target: 'history' | 'command' = 'history'): void {
for (const { accel, key } of POPUP_NAV_KEYS) {
try {
globalShortcut.register(accel, () => {
if (isHistoryPopupVisible()) {
if (target === 'history' && isHistoryPopupVisible()) {
sendKeyToHistoryPopup(key)
} else if (target === 'command' && isCommandPopupVisible()) {
sendKeyToCommandPopup(key)
}
})
} catch {
@ -242,3 +262,24 @@ function setupHistoryPopupIPC(): void {
unregisterPopupNavKeys()
})
}
// ── CommandPopup IPC 연동 ────────────────────────────
function setupCommandPopupIPC(): void {
// 명령어 선택 → 활성 명령어로 설정
ipcMainRef.on('command:selected', (_event: Electron.IpcMainEvent, data: { id: string; name: string }) => {
hideCommandPopup()
unregisterPopupNavKeys()
// ConfigService에 활성 명령어 저장
configSet('activeInstructionId' as keyof import('@shared/types').AppConfig, data.id as never)
configSet('defaultLLMAction', 'custom')
logger.info(`Active command set: ${data.name} (${data.id})`)
})
// 팝업 닫기
ipcMainRef.on('command:dismissed', () => {
hideCommandPopup()
unregisterPopupNavKeys()
})
}