{"record":{"id":"1f3d92cfb1135226","repo":"moeru-ai/airi","slug":"no-voice-selected-pick-a-character-in-the-speech","errorCode":null,"errorMessage":"No voice selected. Pick a character in the speech settings.","messagePattern":"No voice selected\\. Pick a character in the speech settings\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/providers/voicevox/define.ts","lineNumber":211,"sourceCode":"  })\n}\n\n/**\n * Reads the segment text and the style id out of the OpenAI-shaped body that\n * `generateSpeech` builds.\n *\n * `requestBody` in `@xsai/shared` passes that object through `objCamelToSnake`.\n * Only single-word keys survive unchanged, and `input` and `voice` are two of\n * them. A key of more than one word arrives renamed. The synthesis parameters\n * therefore come from the provider configuration, not from this body.\n */\nfunction readSpeechRequest(init: RequestInit | undefined): { input: string, voice: string } {\n  if (!init?.body || typeof init.body !== 'string')\n    throw new Error('Invalid speech request body')\n\n  const body = JSON.parse(init.body) as { input?: string, voice?: string }\n  if (!body.voice)\n    throw new Error('No voice selected. Pick a character in the speech settings.')\n\n  return { input: body.input ?? '', voice: body.voice }\n}\n\nfunction toVoiceInfo(providerId: string, speakerName: string, style: { id: number, name: string }): VoiceInfo {\n  return {\n    id: String(style.id),\n    languages: [{ code: 'ja', title: 'Japanese' }],\n    name: `${speakerName} / ${style.name}`,\n    provider: providerId,\n  }\n}\n","sourceCodeStart":193,"sourceCodeEnd":224,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/libs/providers/providers/voicevox/define.ts#L193-L224","documentation":"The VOICEVOX provider validates the incoming speech request body before proxying it to the engine. If the parsed JSON body has no `voice` field (empty string, missing, or null), readSpeechRequest throws this error because the engine needs a speaker/style id to synthesize audio. The library throws it early so the user gets an actionable message instead of an opaque engine-side 400.","triggerScenarios":"Calling the provider's speech endpoint (fetch to the provider route) with a JSON body where `voice` is absent, empty string, or null; e.g. `{ \"input\": \"hello\" }` with no voice field.","commonSituations":"The user never picked a character/style in the speech settings so the stored voice id is empty; a settings migration or fresh install left the voice field unset; custom UI code invoking the TTS route omits `voice`.","solutions":["Open the speech settings and select a character (speaker/style) so a voice id is persisted.","Verify the settings store actually saves the selected voice id (check providers/voicevox settings state after selection).","When calling the provider programmatically, always include `voice` in the JSON body: `{ input, voice }`.","Add UI-level validation that disables the speak action until a voice is selected."],"exampleFix":"// before\nawait fetch('/api/voicevox/speech', { method: 'POST', body: JSON.stringify({ input: 'hi' }) })\n// after\nawait fetch('/api/voicevox/speech', { method: 'POST', body: JSON.stringify({ input: 'hi', voice: selectedVoiceId }) })","handlingStrategy":"validation","validationCode":"if (!selectedVoiceId) {\n  openSpeechSettings()\n  return\n}\nawait sendSpeech({ input, voice: selectedVoiceId })","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always read the voice id from the settings store immediately before sending a speech request.","Disable the speak action in the UI until a character/style is selected.","Persist the voice selection and validate it on app startup."],"tags":["validation","tts","missing-parameter","voicevox"],"backgroundTag":"missing-required-argument","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}