문제 1: STT release 안 됨 → 진짜 원인은 sidecar import 실패 - 시스템 python3 (Python 3.13)에 numpy/faster-whisper 등 sidecar deps 없음 - 매번 spawn 직후 ModuleNotFoundError로 즉사 → /health 30s 타임아웃 - 사용자에게는 '핫키 release 안 됨'으로 보이지만, 실제로는 핫키는 정상, STT 응답이 없어 결과 팝업이 안 뜬 것 수정: - apps/desktop/sidecar/.venv/ 신규 (gitignored) - Python 3.13.13 venv - 최소 deps: faster-whisper 1.2.1, fastapi 0.135, uvicorn 0.44, python-multipart, numpy 2.4 - torch/pyannote는 lazy import이므로 화자 구분 쓸 때 추가 설치 - main/utils/paths.ts: getSidecarCommand가 venv python 우선 사용 - existsSync(.venv/bin/python3) → 그걸로 spawn - 없으면 시스템 python3 폴백 (기존 동작) - Windows는 .venv/Scripts/python.exe 문제 2: HotkeyRecordModal 칩에 'WIN' 그대로 표기 - getKeyName()이 KEY_DISPLAY_MAP (Windows 전용 매핑) 사용 - 91/92 → 'Win' 하드코딩 - format-hotkey.ts의 keyCodeToName/getPlatform export - HotkeyRecordModal.getKeyName이 keyCodeToName(platform) 호출 → macOS는 ⌘/⇧/⌃/⌥ - 'Shift + Win + 1 — 저장을 눌러주세요' 안내 텍스트도 macOS는 구분자 없이 결합
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
// apps/desktop/src/renderer/utils/format-hotkey.ts
|
|
// HotkeyBinding → 플랫폼별 표시 라벨 (segments + 결합 문자열).
|
|
// macOS: ⌘/⌥/⌃/⇧ + key, Windows/Linux: Ctrl/Alt/Win/Shift + key.
|
|
//
|
|
// 기존 binding.displayLabel은 키 녹화 시 만들어진 정적 문자열이라
|
|
// 플랫폼이 다르면 잘못 보임 — 이 헬퍼가 단일 진입점.
|
|
|
|
import type { HotkeyBinding } from '@d3ro/core/types'
|
|
|
|
export type Platform = 'darwin' | 'win32' | 'linux'
|
|
|
|
export function getPlatform(): Platform {
|
|
const p = (window as { electronAPI?: { platform?: string } }).electronAPI?.platform
|
|
if (p === 'darwin' || p === 'win32' || p === 'linux') return p
|
|
// 폴백: SSR/테스트 환경에서는 navigator.platform로 추정.
|
|
if (typeof navigator !== 'undefined' && /Mac/i.test(navigator.platform)) return 'darwin'
|
|
return 'win32'
|
|
}
|
|
|
|
// keyCode → 키명. Windows VK_* 기준 + 일부 macOS 분기.
|
|
export function keyCodeToName(keyCode: number, platform: Platform = getPlatform()): string {
|
|
// Modifier 표기 — 플랫폼별
|
|
if (platform === 'darwin') {
|
|
if (keyCode === 16 || keyCode === 160 || keyCode === 161) return '⇧'
|
|
if (keyCode === 17 || keyCode === 162 || keyCode === 163) return '⌃'
|
|
if (keyCode === 18 || keyCode === 164 || keyCode === 165) return '⌥'
|
|
if (keyCode === 91 || keyCode === 92) return '⌘'
|
|
} else {
|
|
if (keyCode === 16 || keyCode === 160 || keyCode === 161) return 'Shift'
|
|
if (keyCode === 17 || keyCode === 162 || keyCode === 163) return 'Ctrl'
|
|
if (keyCode === 18 || keyCode === 164 || keyCode === 165) return 'Alt'
|
|
if (keyCode === 91 || keyCode === 92) return 'Win'
|
|
}
|
|
|
|
// 공통 키
|
|
switch (keyCode) {
|
|
case 8: return 'Backspace'
|
|
case 9: return 'Tab'
|
|
case 13: return platform === 'darwin' ? '↩' : 'Enter'
|
|
case 19: return 'Pause'
|
|
case 20: return 'CapsLock'
|
|
case 27: return 'Esc'
|
|
case 32: return 'Space'
|
|
case 33: return 'PgUp'
|
|
case 34: return 'PgDn'
|
|
case 35: return 'End'
|
|
case 36: return 'Home'
|
|
case 37: return '←'
|
|
case 38: return '↑'
|
|
case 39: return '→'
|
|
case 40: return '↓'
|
|
case 45: return 'Insert'
|
|
case 46: return platform === 'darwin' ? '⌫' : 'Delete'
|
|
case 186: return ';'
|
|
case 187: return '='
|
|
case 188: return ','
|
|
case 189: return '-'
|
|
case 190: return '.'
|
|
case 191: return '/'
|
|
case 192: return '`'
|
|
case 219: return '['
|
|
case 220: return '\\'
|
|
case 221: return ']'
|
|
case 222: return "'"
|
|
default: break
|
|
}
|
|
|
|
// F1~F24
|
|
if (keyCode >= 112 && keyCode <= 135) return `F${keyCode - 111}`
|
|
// Numpad
|
|
if (keyCode >= 96 && keyCode <= 105) return `Num${keyCode - 96}`
|
|
// 알파벳/숫자
|
|
if ((keyCode >= 48 && keyCode <= 57) || (keyCode >= 65 && keyCode <= 90)) {
|
|
return String.fromCharCode(keyCode)
|
|
}
|
|
|
|
return `Key${keyCode}`
|
|
}
|
|
|
|
/**
|
|
* binding을 플랫폼별 segment 배열로 변환.
|
|
* 예) Mac: ['⌘', '⇧', '1'], Win: ['Win', 'Shift', '1']
|
|
*
|
|
* 순서:
|
|
* - macOS: ⌃ ⌥ ⇧ ⌘ + key (Apple 표준)
|
|
* - Win/Linux: Ctrl Win Alt Shift + key
|
|
*/
|
|
export function formatHotkeySegments(binding: HotkeyBinding | null): string[] {
|
|
if (!binding) return []
|
|
const platform = getPlatform()
|
|
const segments: string[] = []
|
|
|
|
if (platform === 'darwin') {
|
|
if (binding.ctrl) segments.push('⌃')
|
|
if (binding.alt) segments.push('⌥')
|
|
if (binding.shift) segments.push('⇧')
|
|
if (binding.meta) segments.push('⌘')
|
|
} else {
|
|
if (binding.ctrl) segments.push('Ctrl')
|
|
if (binding.meta) segments.push('Win')
|
|
if (binding.alt) segments.push('Alt')
|
|
if (binding.shift) segments.push('Shift')
|
|
}
|
|
|
|
segments.push(keyCodeToName(binding.keyCode, platform))
|
|
return segments
|
|
}
|
|
|
|
/**
|
|
* 사람이 읽기 위한 단일 문자열 형태.
|
|
* macOS: '⌘⇧1' (구분자 없음 — Apple 표준)
|
|
* Win/Linux: 'Ctrl + Shift + 1'
|
|
*/
|
|
export function formatHotkeyLabel(binding: HotkeyBinding | null): string {
|
|
const segments = formatHotkeySegments(binding)
|
|
if (segments.length === 0) return ''
|
|
const platform = getPlatform()
|
|
return platform === 'darwin' ? segments.join('') : segments.join(' + ')
|
|
}
|