fix(voice): 받아쓰기 파이프라인 4버그 수정 + 실시간 부분 전사

- press가 STT 초기화를 await하며 action queue 점유 → release 수십 초 지연·
  유령 세션 반복 버그 수정 (initSTT fire-and-forget)
- 프리플라이트: STT 모델 미설치 시 즉시 에러 + 메인 UI 경고 + 온보딩 오픈
- 사이드카: 기동 중 프로세스 사망 시 30초 대기 없이 즉시 실패,
  restartCount 리셋, error 리스너 부재 미처리 예외 방지
- 실시간 부분 전사: 1.5s 간격 interim → RecordingTip에 말하는 내용 미리보기
- Ollama 미가용 후처리 스킵 시 warning 배너, voice:error 브로드캐스트 신설
This commit is contained in:
Yun Chan 2026-07-21 20:56:40 +09:00
parent e0a1864e60
commit fd46ac7b14
13 changed files with 280 additions and 16 deletions

View file

@ -173,6 +173,14 @@ const MODEL_CATALOG: STTModel[] = [
const logger = getLogger('LocalSTTService')
class LocalSTTService extends EventEmitter {
constructor() {
super()
// EventEmitter는 'error' 리스너가 없으면 emit 시 프로세스 예외를 던진다
// (실측: sidecar crash 루프 중 ERR_UNHANDLED_ERROR). 기본 sink로 방지 —
// 실제 로깅은 _emitError에서 수행.
this.on('error', () => { /* default sink */ })
}
private _state: STTState = STTState.Uninitialized
private _sidecarProcess: ChildProcess | null = null
private _port: number = SIDECAR_PORT
@ -637,6 +645,15 @@ class LocalSTTService extends EventEmitter {
const startTime = Date.now()
while (Date.now() - startTime < HEALTH_CHECK_TIMEOUT_MS) {
// 프로세스가 이미 죽었으면 30초 타임아웃을 기다리지 않고 즉시 실패
// (의존성 미설치 등 즉사 크래시가 30초×큐잉으로 증폭되는 문제 방지)
if (!this._sidecarProcess || this._sidecarProcess.exitCode !== null) {
throw new D3ROError(
ErrorCode.STTSidecarSpawnFailed,
'Sidecar process exited during startup — check Python environment/dependencies',
)
}
try {
const response = await fetch(`http://localhost:${this._port}/health`, {
signal: AbortSignal.timeout(2000),
@ -645,6 +662,7 @@ class LocalSTTService extends EventEmitter {
if (response.ok) {
const data = (await response.json()) as HealthResponse
this._gpuAccelerated = data.gpu
this._restartCount = 0
logger.info(
`Sidecar 헬스체크 성공: status=${data.status}, gpu=${data.gpu}`,
)