VoiceModeService ↔ 팝업 연동 구현 (Speakly RE 패턴)
- startSession: RecordingTip 표시 (recording 상태) - 오디오 레벨: sendAudioLevelToTip → 웨이브 바 애니메이션 - stopSession: RecordingTip → thinking 상태 전환 - completeSession: RecordingTip 숨김 + 삽입 실패 시 ResultPopup 표시 - cancelSession: RecordingTip 즉시 숨김 - handleError: RecordingTip에 에러 표시 → 3초 후 자동 숨김 → idle 복귀 - CLAUDE.md: 핵심 병목 + Speakly 파이프라인 패턴 문서화
This commit is contained in:
parent
9d5594e848
commit
18d5fef594
2 changed files with 73 additions and 2 deletions
|
|
@ -18,6 +18,13 @@ import { D3ROError, ErrorCode } from '@shared/errors'
|
|||
import { TIMING } from '@shared/constants'
|
||||
import { RecognitionState, AudioState } from '@shared/types'
|
||||
import type { VoiceMode, VoiceState } from '@shared/types'
|
||||
import {
|
||||
showRecordingTip,
|
||||
hideRecordingTip,
|
||||
updateRecordingTipState,
|
||||
sendAudioLevelToTip,
|
||||
showResultPopup,
|
||||
} from '../windows/WindowManager'
|
||||
|
||||
const logger = getLogger('VoiceModeService')
|
||||
|
||||
|
|
@ -187,6 +194,9 @@ class VoiceModeService extends EventEmitter {
|
|||
this.emit('session-started', { session: this._session })
|
||||
logger.info(`Session started: ${this._session.id} (mode: ${mode})`)
|
||||
|
||||
// Speakly 패턴: 녹음 시작 시 즉시 RecordingTip 표시
|
||||
showRecordingTip('recording')
|
||||
|
||||
// 이중 조건 플러시: STT 초기화 + 오디오 캡처를 병렬 시작
|
||||
const sttPromise = this._initSTT()
|
||||
const audioPromise = this._startAudio()
|
||||
|
|
@ -212,6 +222,9 @@ class VoiceModeService extends EventEmitter {
|
|||
// 오디오 캡처 중지
|
||||
await this._stopAudio()
|
||||
|
||||
// Speakly 패턴: 녹음 종료 후 thinking 상태로 전환
|
||||
updateRecordingTipState('thinking')
|
||||
|
||||
// 버퍼가 있으면 STT에 전달
|
||||
if (this._audioBuffer.length > 0 && this._sttReady) {
|
||||
await this._transcribe()
|
||||
|
|
@ -321,6 +334,8 @@ class VoiceModeService extends EventEmitter {
|
|||
|
||||
this._audioLevelHandler = (payload) => {
|
||||
this.emit('audio-level', { level: payload.level })
|
||||
// Speakly 패턴: 오디오 레벨을 RecordingTip 웨이브 바에 전달
|
||||
sendAudioLevelToTip(payload.level)
|
||||
}
|
||||
|
||||
audio.on('audio-data', this._audioDataHandler)
|
||||
|
|
@ -467,16 +482,26 @@ class VoiceModeService extends EventEmitter {
|
|||
const session = { ...this._session }
|
||||
logger.info(`Session completed: "${finalText.substring(0, 50)}${finalText.length > 50 ? '...' : ''}"`)
|
||||
|
||||
// Speakly 패턴: RecordingTip 숨김
|
||||
hideRecordingTip()
|
||||
|
||||
// 텍스트 삽입 (autoInsert 설정 확인)
|
||||
let insertSuccess = false
|
||||
if (configGet('autoInsert') && finalText.length > 0) {
|
||||
try {
|
||||
const insertMethod = configGet('insertMethod')
|
||||
await getTextInsertService().insertText(finalText, insertMethod)
|
||||
insertSuccess = true
|
||||
} catch (error) {
|
||||
logger.warn(`Text insert failed: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Speakly 패턴: 삽입 실패 시 ResultPopup에 텍스트 표시
|
||||
if (!insertSuccess && finalText.length > 0) {
|
||||
showResultPopup(finalText, 10000)
|
||||
}
|
||||
|
||||
this.emit('session-completed', { session, finalText })
|
||||
|
||||
// IDLE로 복귀
|
||||
|
|
@ -492,6 +517,9 @@ class VoiceModeService extends EventEmitter {
|
|||
const session = { ...this._session }
|
||||
logger.info(`Session cancelled: ${reason}`)
|
||||
|
||||
// Speakly 패턴: RecordingTip 즉시 숨김
|
||||
hideRecordingTip()
|
||||
|
||||
// 오디오 정리
|
||||
this._stopAudio()
|
||||
|
||||
|
|
@ -528,6 +556,12 @@ class VoiceModeService extends EventEmitter {
|
|||
|
||||
this._stopAudio()
|
||||
|
||||
// Speakly 패턴: RecordingTip에 에러 표시 → 3초 후 자동 숨김
|
||||
updateRecordingTipState('error', { errorMessage: error.message })
|
||||
setTimeout(() => {
|
||||
hideRecordingTip()
|
||||
}, 3000)
|
||||
|
||||
this.emit('error', { error, session: this._session ? { ...this._session } : null })
|
||||
this._resetToIdle()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue