feat(caption): stream live captions and polish finished lines in context
Replaces the fixed six-second batches with a streaming track per audio source: the uncommitted audio is re-recognised every second and sent as a partial with its agreed (stable) prefix, a short pause finalises the line, and long unbroken speech is committed at Whisper segment boundaries. Idle audio is trimmed so silence cannot produce invented sentences. Finished lines are corrected by the local model against the previous lines and replaced in place; edits that change too much are rejected. The behaviour can be switched off in Settings.
This commit is contained in:
parent
db8d9448a3
commit
39b8e7448e
28 changed files with 827 additions and 217 deletions
|
|
@ -481,6 +481,27 @@ export function SettingsModal({ open, initialTab = 0, onClose }: SettingsModalPr
|
|||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControlLabel
|
||||
sx={{ mr: 0, alignItems: 'flex-start' }}
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
checked={config.captionRefineEnabled !== false}
|
||||
onChange={(e) => updateConfig('captionRefineEnabled', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ pt: '2px' }}>
|
||||
<Typography sx={{ fontSize: d3roTypo.body.size, color: d3roPalette.text.primary }}>
|
||||
{t('settings.captionRefine')}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: d3roTypo.meta.size, color: d3roPalette.text.secondary }}>
|
||||
{t('settings.captionRefine.desc')}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
<Divider sx={{ borderColor: d3roPalette.border.subtle }} />
|
||||
|
||||
<Typography variant="overline" sx={{ color: d3roPalette.text.label, letterSpacing: '1.5px' }}>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@
|
|||
return
|
||||
}
|
||||
|
||||
// 첫 중간 결과가 오면 준비 안내를 걷는다 — 이제 실제로 듣고 있다.
|
||||
removeStatusLine()
|
||||
|
||||
if (!deltaLine) {
|
||||
deltaLine = document.createElement('div')
|
||||
deltaLine.className = 'caption-line delta'
|
||||
|
|
@ -82,7 +85,35 @@
|
|||
linesContainer.appendChild(deltaLine)
|
||||
}
|
||||
|
||||
deltaLine.textContent = data.text
|
||||
// 두 번 연속 같게 들린 앞부분은 또렷하게, 아직 바뀔 수 있는 뒷부분은 흐리게.
|
||||
var text = data.text || ''
|
||||
var stable = data.stable || ''
|
||||
if (stable && text.indexOf(stable) !== 0) stable = ''
|
||||
deltaLine.textContent = ''
|
||||
if (stable) deltaLine.appendChild(document.createTextNode(stable))
|
||||
var rest = text.slice(stable.length)
|
||||
if (rest) {
|
||||
var tentative = document.createElement('span')
|
||||
tentative.className = 'tentative'
|
||||
tentative.textContent = rest
|
||||
deltaLine.appendChild(tentative)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 문맥 다듬기로 고쳐진 줄을 제자리에서 바꾼다.
|
||||
* @param {{id: string, text: string}} update
|
||||
*/
|
||||
function updateSegment(update) {
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].id !== update.id) continue
|
||||
lines[i].el.textContent = update.text
|
||||
lines[i].el.classList.remove('refined')
|
||||
// 다시 그려야 애니메이션이 재시작된다
|
||||
void lines[i].el.offsetWidth
|
||||
lines[i].el.classList.add('refined')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -163,6 +194,11 @@
|
|||
updateDelta(data)
|
||||
})
|
||||
|
||||
// 문맥 다듬기 결과
|
||||
window.popupAPI.on('caption:segmentUpdated', function (update) {
|
||||
updateSegment(update)
|
||||
})
|
||||
|
||||
// 설정 업데이트
|
||||
window.popupAPI.on('caption:config', function (newConfig) {
|
||||
applyConfig(newConfig)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,21 @@ html, body {
|
|||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* 아직 바뀔 수 있는 뒷부분 (중간 결과) */
|
||||
.caption-line .tentative {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 문맥 다듬기로 고쳐진 줄 — 잠깐 강조 */
|
||||
.caption-line.refined {
|
||||
animation: refined-flash 0.9s ease-out;
|
||||
}
|
||||
|
||||
@keyframes refined-flash {
|
||||
0% { border-color: var(--d3-accent-main); box-shadow: 0 0 0 2px var(--d3-accent-glow-dim); }
|
||||
100% { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); }
|
||||
}
|
||||
|
||||
/* 로딩 중 표시 — 점멸 애니메이션 */
|
||||
.caption-line.loading {
|
||||
opacity: 0.6;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue