Build a Stable Caption UI
Consume StableStream events without manufacturing caption jitter.
How to consume realtime transcription events and build a caption UI that doesn't jitter.
The problem with naive approaches
A naive live caption UI might try to update text on every event. With partial-text systems, this causes visible jitter as words are repeatedly replaced.
LansonAI's StableStream eliminates this: each conversation.item.input_audio_transcription.completed event contains final, stable text.
Recommended render strategy
Append-only display
const captions = document.getElementById('captions');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'conversation.item.input_audio_transcription.completed') {
// Append directly — text is final, no replacement needed
const p = document.createElement('p');
p.dataset.utterance = data.utterance_index;
p.textContent = data.text;
captions.appendChild(p);
captions.scrollTop = captions.scrollHeight;
}
};
With optional status indicators
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'input_audio_buffer.speech_started') {
showIndicator(data.utterance_index, 'listening');
}
if (data.type === 'input_audio_buffer.speech_stopped') {
showIndicator(data.utterance_index, 'processing');
}
if (data.type === 'conversation.item.input_audio_transcription.completed') {
hideIndicator(data.utterance_index);
appendCaption(data.utterance_index, data.text);
}
};
What you do NOT need to do
- ❌ No partial → final text replacement mapping
- ❌ No waiting for "stabilization" (received text is already stable)
- ❌ No text rollback or undo
- ❌ No debounce or jitter elimination
- ❌ No re-rendering of previously shown text
Scrolling behavior
For long sessions, manage scroll behavior:
function appendCaption(index, text) {
const p = document.createElement('p');
p.textContent = text;
// Auto-scroll if user is near bottom
const isNearBottom =
captions.scrollHeight - captions.scrollTop - captions.clientHeight < 100;
captions.appendChild(p);
if (isNearBottom) {
captions.scrollTop = captions.scrollHeight;
}
}
Styling for readability
#captions p {
margin: 0.25em 0;
padding: 0.25em 0.5em;
border-radius: 4px;
transition: opacity 0.2s;
}
Related
- StableStream — the stability contract
- Stable vs. Partial Text — concept
- Browser Live Captions — full browser setup
