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

@ -10,9 +10,13 @@
<div id="root">
<div id="container" class="recording-tip">
<!-- recording 상태 -->
<div id="recording-view" class="view">
<div id="wave-bars" class="wave-bars"></div>
<span id="duration-text" class="duration">0:00</span>
<div id="recording-view" class="view view-recording">
<div class="recording-row">
<div id="wave-bars" class="wave-bars"></div>
<span id="duration-text" class="duration">0:00</span>
</div>
<!-- 실시간 부분 전사 미리보기 -->
<div id="partial-text" class="partial-text hidden"></div>
</div>
<!-- thinking 상태 -->

View file

@ -30,6 +30,7 @@
var durationText = document.getElementById('duration-text')
var progressBar = document.getElementById('progress-bar')
var errorText = document.getElementById('error-text')
var partialText = document.getElementById('partial-text')
// ── 상태 ─────────────────────────────────────────────
var bars = []
@ -110,6 +111,10 @@
durationText.textContent = '0:00'
currentHeights.fill(MIN_HEIGHT)
// 이전 세션의 부분 전사 리셋
partialText.textContent = ''
partialText.classList.add('hidden')
animInterval = setInterval(updateBars, UPDATE_INTERVAL)
durationInterval = setInterval(updateDuration, 1000)
}
@ -166,6 +171,17 @@
audioLevel = data.level || 0
})
// 실시간 부분 전사 — 녹음 중 말하는 내용 미리보기
window.popupAPI.on('voice:partialTranscript', function (data) {
if (currentState !== 'recording') return
var text = (data && data.text) || ''
if (text.length === 0) return
partialText.textContent = text
partialText.classList.remove('hidden')
// 항상 끝부분(최근 발화)이 보이도록 스크롤
partialText.scrollTop = partialText.scrollHeight
})
// 상태 변경 (showRecordingTip + updateRecordingTipState 양쪽에서 사용)
window.popupAPI.on('window:tipStateChanged', function (data) {
var state = data.state

View file

@ -133,3 +133,37 @@ body {
.view.hidden {
display: none;
}
/* ── 실시간 부분 전사 (recording 상태 하단) ─────────────── */
.view-recording {
flex-direction: column;
align-items: stretch;
gap: 6px;
width: 288px;
}
.recording-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.partial-text {
color: var(--d3-text-primary);
font-size: 12px;
line-height: 1.45;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
text-align: left;
max-height: 35px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
border-top: 1px solid var(--d3-border-default);
padding-top: 5px;
}
.partial-text.hidden {
display: none;
}