jamiepine/voicebox · warning · Error
captures.noTranscriptError
Error message
captures.noTranscriptError
What it means
Thrown by the 'Play as' mutation in CapturesTab when a capture has no text. It reads `capture.transcript_refined || capture.transcript_raw`, trims, and throws the i18n key captures.noTranscriptError if empty before calling apiClient.generateSpeech(). This prevents sending an empty string to the TTS backend. The message is a translation key, not user-facing prose; the onError handler turns other failures into a destructive toast.
Source
Thrown at app/src/components/CapturesTab/CapturesTab.tsx:265
profiles?.[0] ||
null;
const playAsVoiceId = playAsVoice?.id ?? null;
const deleteMutation = useMutation({
mutationFn: async (captureId: string) => apiClient.deleteCapture(captureId),
onSuccess: () => {
setDeleteDialogOpen(false);
queryClient.invalidateQueries({ queryKey: ['captures'] });
},
onError: (err: Error) => {
toast({ title: t('captures.toast.deleteFailed'), description: err.message, variant: 'destructive' });
},
});
const playAsMutation = useMutation({
mutationFn: async ({ capture, voice }: { capture: CaptureResponse; voice: VoiceProfileResponse }) => {
const text = capture.transcript_refined || capture.transcript_raw;
if (!text.trim()) throw new Error(t('captures.noTranscriptError'));
const language = (capture.language || voice.language) as LanguageCode;
// Preset profiles (Kokoro etc.) reject the qwen default — honor the
// profile's stored engine preference. Cloned profiles without an
// override fall through to whatever the backend picks.
const engine = voice.default_engine as
| 'qwen' | 'qwen_custom_voice' | 'luxtts' | 'chatterbox'
| 'chatterbox_turbo' | 'tada' | 'kokoro'
| undefined;
return apiClient.generateSpeech({
profile_id: voice.id,
text,
language,
engine,
});
},
onSuccess: (result) => {
// /generate is queue-based — it returns a generating row with an empty
// audio_path. Hand the id to the global SSE handler which pollsView on GitHub (pinned to 51f49dea19)
Solutions
- Disable the 'Play as' button when (transcript_refined || transcript_raw).trim() is empty.
- Wait for transcription to settle before enabling playback actions (check capture status field).
- Show an inline empty-state message on the capture explaining it has no transcript yet.
Example fix
// before
<button onClick={() => playAsMutation.mutate({ capture, voice })}>Play</button>
// after
const hasTranscript = !!(capture.transcript_refined || capture.transcript_raw || '').trim();
<button disabled={!hasTranscript} onClick={() => playAsMutation.mutate({ capture, voice })}>Play</button>; Defensive patterns
Strategy: validation
Validate before calling
function captureHasTranscript(c: CaptureResponse): boolean {
return !!(c.transcript_refined || c.transcript_raw || '').trim();
}
// Disable Play-as when false:
const canPlay = captureHasTranscript(capture) && !!voice; Try / catch
const text = (capture.transcript_refined || capture.transcript_raw || '').trim();
if (!text) {
toast({ title: t('captures.noTranscriptError') });
return;
}
try {
await apiClient.generateSpeech({ profile_id: voice.id, text, language });
} catch (e) {
toast({ title: t('captures.toast.deleteFailed'), description: (e as Error).message, variant: 'destructive' });
} Prevention
- Disable Play-as until a non-empty transcript exists.
- Wait for transcription status to settle before enabling playback.
- Keep the throw, but pair it with a disabled button so it rarely fires.
When it happens
Trigger: User clicks 'Play as <voice>' on a capture whose transcript_refined and transcript_raw are both empty or whitespace-only. Common for captures that failed transcription or are still processing.
Common situations: A capture finished recording but transcription hasn't completed (transcript fields still empty). Transcription returned an empty result. A capture was imported without a transcript.
Related errors
- No profile selected
- HTTP ${res.status}
- Failed to fetch model info: ${response.status}
- e instanceof Error ? e.message : errorMessage
- Model ${model_size} is not downloaded yet. Use /generate to
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/8cc9c131d25d67de.
Report an issue: GitHub.