{"record":{"id":"e45617ff9678ab69","repo":"moeru-ai/airi","slug":"voice-clone-model-requires-a-base64-audio-sample-i","errorCode":null,"errorMessage":"Voice clone model requires a base64 audio sample in data URI format.","messagePattern":"Voice clone model requires a base64 audio sample in data URI format\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-pages/src/pages/settings/providers/speech/mimo-audio-speech.vue","lineNumber":141,"sourceCode":"})\n\nasync function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean, modelId?: string) {\n  const provider = await providersStore.getProviderInstance<SpeechProvider<string>>(providerId)\n  if (!provider) {\n    throw new Error('Failed to initialize speech provider')\n  }\n\n  const providerConfig = providerStore.getProviderConfig(providerId)\n  const modelToUse = modelId || model.value || defaultModel\n  const requestConfig = {\n    ...providerConfig,\n    ...defaultVoiceSettings,\n    stylePrompt: stylePrompt.value,\n    voiceSample: voiceSample.value,\n  }\n\n  if (modelToUse === 'mimo-v2.5-tts-voiceclone' && !voiceSample.value.trim()) {\n    throw new Error('Voice clone model requires a base64 audio sample in data URI format.')\n  }\n\n  const voiceToUse = modelToUse === 'mimo-v2.5-tts-voiceclone'\n    ? voiceSample.value.trim()\n    : voiceId || (config.value?.voice || defaultVoice)\n\n  return await speechStore.speech(\n    provider,\n    modelToUse,\n    input,\n    voiceToUse,\n    requestConfig,\n  )\n}\n\nconst {\n  isValidating,\n  isValid,","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-pages/src/pages/settings/providers/speech/mimo-audio-speech.vue#L123-L159","documentation":"Guard in the MiMo speech playground: selecting the `mimo-v2.5-tts-voiceclone` model switches the voice source from a catalogue voice id to a user-provided cloned-voice sample, which must be base64 audio embedded as a data URI (`voiceSample`). The code checks `!voiceSample.value.trim()` for exactly that model id and throws this validation error before calling `speechStore.speech`, so no network request is wasted on a request the backend would reject.","triggerScenarios":"Choosing `mimo-v2.5-tts-voiceclone` in the model dropdown and pressing Generate without having recorded/pasted a voice sample; voice sample field cleared after switching models; sample containing only whitespace; pasting raw base64 without the `data:audio/...;base64,` prefix (passes the blank check but fails downstream) — this specific message, however, is only the empty-sample case.","commonSituations":"Users assume voice cloning works from the text prompt or `stylePrompt` alone; switching from a normal TTS model where voice selection comes from the voice list; UI flows that forget to surface the sample input for the clone model.","solutions":["Record or paste a base64 data-URI audio sample into the voice sample field before generating with the voiceclone model.","Or switch the model selector back to a non-clone MiMo TTS model and pick a normal voice id.","Ensure the sample is a full data URI (`data:audio/webm;base64,...` or similar), not bare base64, to avoid the next failure downstream.","If cloning is unintended, clear the stored model so it falls back to the default non-clone model."],"exampleFix":"// before\nif (modelToUse === 'mimo-v2.5-tts-voiceclone' && !voiceSample.value.trim())\n  throw new Error('Voice clone model requires a base64 audio sample in data URI format.')\n\n// after (surface it in the UI instead of only throwing at generate time)\nconst voiceCloneReady = computed(() => modelToUse.value !== 'mimo-v2.5-tts-voiceclone' || !!voiceSample.value.trim())\n// template: <button :disabled=\"!voiceCloneReady\">Generate</button>\nif (modelToUse === 'mimo-v2.5-tts-voiceclone' && !voiceSample.value.trim())\n  throw new Error('Voice clone model requires a base64 audio sample in data URI format.')","handlingStrategy":"validation","validationCode":"const isVoiceCloneModel = (id: string) => id === 'mimo-v2.5-tts-voiceclone'\nconst isDataUri = (s: string) => /^data:audio\\/[a-z0-9.+-]+;base64,\\S+$/i.test(s.trim())\nconst voiceCloneReady = computed(() =>\n  !isVoiceCloneModel(model.value) || isDataUri(voiceSample.value),\n)\n// template: <button :disabled=\"!voiceCloneReady\">Generate</button>","typeGuard":"function isVoiceCloneRequestReady(modelId: string, sample: string): boolean {\n  return modelId !== 'mimo-v2.5-tts-voiceclone' || isDataUri(sample)\n}","tryCatchPattern":"try { return await speechStore.speech(provider, modelToUse, input, voiceToUse, requestConfig) }\ncatch (error) {\n  // keep the thrown validation message user-facing: it explains exactly what to provide\n  showError(errorMessageFrom(error))\n}","preventionTips":["Show the voice-sample input only for the voiceclone model and mark it required.","Validate data-URI shape on input, not at generate time.","Default the model to a non-clone variant when no sample exists."],"tags":["speech","voice-clone","validation","base64","mimo"],"backgroundTag":"voice-clone-missing-audio-sample","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}