{"record":{"id":"167463cccddd8005","repo":"moeru-ai/airi","slug":"failed-to-initialize-speech-provider-167463","errorCode":null,"errorMessage":"Failed to initialize speech provider","messagePattern":"Failed to initialize speech provider","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/stage-pages/src/pages/settings/providers/speech/openai-compatible-audio-speech.vue","lineNumber":111,"sourceCode":"  { deep: true, immediate: true },\n)\n\n// Check if API key is configured\nconst apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey)\n\n// Ensure provider config is initialized on mount\nonMounted(() => {\n  providers.value[providerId] ??= {}\n  // Defaults only when unset (null/undefined); empty strings are kept intentionally\n  providers.value[providerId].model ??= defaultModel\n  providers.value[providerId].voice ??= defaultVoice\n})\n\n// Generate speech with OpenAI-compatible parameters\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  // Get provider configuration\n  const providerConfig = providerStore.getProviderConfig(providerId)\n\n  // Use the reactive model computed property (not a local variable)\n  const modelToUse = modelId || model.value || defaultModel\n\n  return await speechStore.speech(\n    provider,\n    modelToUse,\n    input,\n    voiceId || voice.value || defaultVoice,\n    {\n      ...providerConfig,\n      ...defaultVoiceSettings,\n      speed: speed.value,\n    },","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-pages/src/pages/settings/providers/speech/openai-compatible-audio-speech.vue#L93-L129","documentation":"Thrown by the OpenAI-compatible audio speech playground when the provider factory resolves falsy. This provider targets arbitrary OpenAI-shaped endpoints, so its config needs an API key AND a base URL; notably the page's onMounted uses `??=` defaults that intentionally preserve empty strings, meaning a blank-but-present baseUrl or key is not auto-corrected and can reach the factory, which then returns `undefined` — exactly the case this guard converts into an explicit error.","triggerScenarios":"Clicking Generate with baseUrl or apiKey saved as empty strings (the `??=` defaults skip them); a self-hosted endpoint whose URL lacks scheme/path so the factory declines; pointing at an endpoint that is not OpenAI-compatible at construction time.","commonSituations":"Using local servers (LocalAI, vLLM, llama.cpp server) that were not started or moved ports; pasting a URL with trailing path issues; storing the key in one compatible-provider entry while generating from another; assuming defaults were applied when the fields were left blank on purpose (they are kept).","solutions":["Fill in both a non-empty base URL (absolute, with scheme) and API key (use a placeholder like 'sk-none' for keyless local servers) and save.","Verify the endpoint responds to an OpenAI-style models request before generating.","Reload the page after saving so `getProviderInstance` rebuilds the instance.","Check console for `Error creating provider instance for ...` for the construction failure detail.","Report it if a fully valid endpoint still yields a falsy instance."],"exampleFix":"// before\nonMounted(() => {\n  providers.value[providerId] ??= {}\n  providers.value[providerId].model ??= defaultModel\n  providers.value[providerId].voice ??= defaultVoice\n})\n\n// after (also default the connection fields, and validate before generate)\nonMounted(() => {\n  providers.value[providerId] ??= {}\n  providers.value[providerId].model ??= defaultModel\n  providers.value[providerId].voice ??= defaultVoice\n})\nconst canGenerate = computed(() => {\n  const c = providers.value[providerId]\n  return !!c?.baseUrl?.trim() && !!c?.apiKey?.trim()\n})\n// template: <button :disabled=\"!canGenerate\">Generate</button>","handlingStrategy":"validation","validationCode":"const canGenerate = computed(() => {\n  const c = providers.value[providerId]\n  return !!c?.baseUrl?.trim() && !!c?.apiKey?.trim()\n})\nif (!canGenerate.value)\n  throw new Error('Set both a base URL and an API key (placeholder allowed for keyless servers)')","typeGuard":"function isSpeechProvider(v: unknown): v is SpeechProvider<string> {\n  return typeof v === 'object' && v !== null && typeof (v as SpeechProvider<string>).speech === 'function'\n}","tryCatchPattern":"try { const provider = await providersStore.getProviderInstance<SpeechProvider<string>>(providerId) }\ncatch (error) { showError(errorMessageFrom(error)) }","preventionTips":["Remember the `??=` onMounted defaults intentionally keep empty strings — clear means cleared.","Health-check the custom endpoint with a models request before synthesis.","Use a placeholder API key for keyless local servers so the factory gets a non-empty string."],"tags":["speech","provider","initialization","api-key","base-url","openai-compatible"],"backgroundTag":"provider-instance-undefined","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}