feat(tts/stt): add API key support for OpenAI-compatible custom providers (#1361)
* feat(tts/stt): add API key support for OpenAI-compatible custom providers ## Problem Custom (OpenAI-compatible) TTS/STT provider in Voice Settings has no way to pass an API key or bearer token. Many self-hosted or third-party compatible servers require authentication, making them unreachable from OpenChamber. The server-side TTS route already accepts an `apiKey` parameter, but the frontend never sends it. The STT route hardcodes `'not-required'`. ## Implementation - Add `openaiCompatibleApiKey` to Zustand config store, persisted to localStorage - Add API Key input field in VoiceSettings.tsx under the custom provider section - Wire `openaiCompatibleApiKey` through useServerTTS to the TTS backend - Add `apiKey` field to AudioStreamConfig for STT, forwarded as X-API-Key header - Update server STT route to accept and forward X-API-Key to transcribeAudio - Update stt.js to use client-provided apiKey before falling back to env var ## Files changed - packages/ui/src/stores/useConfigStore.ts - packages/ui/src/components/sections/openchamber/VoiceSettings.tsx - packages/ui/src/hooks/useServerTTS.ts - packages/ui/src/hooks/useBrowserVoice.ts - packages/ui/src/lib/voice/audioStreamService.ts - packages/web/server/lib/tts/routes.js - packages/web/server/lib/tts/stt.js * feat(tts/stt): add separate API key support for custom TTS and STT providers ## Problem Custom (OpenAI-compatible) TTS and STT providers in Voice Settings have no way to pass API keys. Many self-hosted or third-party compatible servers require authentication, making them unreachable from OpenChamber Desktop (Electron). ## Implementation - Add `openaiCompatibleApiKey` for TTS (persisted to localStorage, passed in JSON body) - Add `sttApiKey` for STT (persisted to localStorage, passed via Authorization: Bearer header) - Two independent keys: TTS and STT are configured separately - STT authentication follows OpenAI standard (Authorization: Bearer <token>) - TTS authentication follows existing pattern (apiKey in JSON body) - Backend STT route extracts bearer token from Authorization header - Backend STT service prefers client-provided key over OPENAI_API_KEY env var ## Fixes - Fixed P1: ConfigStore interface now declares setOpenaiCompatibleApiKey setter - STT API key is only forwarded when sttProvider === 'server' (not leaked to other providers) ## Files changed (7) - packages/ui/src/stores/useConfigStore.ts - packages/ui/src/components/sections/openchamber/VoiceSettings.tsx - packages/ui/src/hooks/useServerTTS.ts - packages/ui/src/hooks/useBrowserVoice.ts - packages/ui/src/lib/voice/audioStreamService.ts - packages/web/server/lib/tts/routes.js - packages/web/server/lib/tts/stt.js * fix: refresh server STT callback when API key changes --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
e16097b05d
commit
06526767a2
@@ -39,6 +39,8 @@ export interface AudioStreamConfig {
|
||||
* Default: 1500
|
||||
*/
|
||||
silenceHoldMs?: number;
|
||||
/** Optional API key for the STT server. */
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
// How often (ms) the VAD samples the analyser
|
||||
@@ -69,6 +71,7 @@ class AudioStreamService {
|
||||
language: '',
|
||||
silenceThresholdDb: -45,
|
||||
silenceHoldMs: 1500,
|
||||
apiKey: '',
|
||||
};
|
||||
|
||||
/** Update service configuration. Can be called before or after startListening. */
|
||||
@@ -77,8 +80,10 @@ class AudioStreamService {
|
||||
silenceThresholdDb: -45,
|
||||
silenceHoldMs: 1500,
|
||||
language: '',
|
||||
apiKey: '',
|
||||
...config,
|
||||
};
|
||||
this.cfg.apiKey = config.apiKey ?? '';
|
||||
}
|
||||
|
||||
/** Whether the browser supports the required APIs. */
|
||||
@@ -336,6 +341,9 @@ class AudioStreamService {
|
||||
'X-Base-URL': this.cfg.baseURL,
|
||||
'X-Model': this.cfg.model,
|
||||
};
|
||||
if (this.cfg.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.cfg.apiKey}`;
|
||||
}
|
||||
if (this.cfg.language) {
|
||||
headers['X-Language'] = this.cfg.language;
|
||||
} else if (this.lang && this.lang !== 'auto') {
|
||||
|
||||
Reference in New Issue
Block a user