- backend/coach: routing zu Sonnet (default) / Haiku / Groq Llama je nach sessionData.llmProvider. sort:latency für Anthropic-Modelle (-30..58% TTFB). - frontend: LlmProviderToggle (Sonnet/Haiku/Groq pills), llmProvider.ts Storage-Helper. sosStream.ts schickt llmProvider im /sos-session-Body. - bench: SosTtsBenchmark sammelt Marker (req->session, lyra-ttfb, lyra-done, tts-fired/headers/body/file, audio-loaded, first-audio); Output als console.table. - ops: backend/scripts/llm-bench.sh + Python-Variante für realistic SOS-Prompt. - speak-cartesia + speak-elevenlabs Endpoints (waren ungetracked, jetzt mit drin).
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
// SOS-TTS-Provider mit AsyncStorage-Persist + Listener-Pattern.
|
|
// Live-Switch im SOS-Screen: Hook holt aktuelle Wahl + reagiert auf Änderungen
|
|
// während des Mounts. Endpoint-Path wird beim Erzeugen der TTS-Queue gelesen.
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
export type TtsProvider = 'openai' | 'gemini' | 'google-cloud' | 'elevenlabs' | 'cartesia';
|
|
|
|
const STORAGE_KEY = 'rebreak-sos-tts-provider';
|
|
const DEFAULT_PROVIDER: TtsProvider = 'openai';
|
|
|
|
export const TTS_PROVIDER_LABEL: Record<TtsProvider, string> = {
|
|
openai: 'OpenAI',
|
|
gemini: 'Gemini',
|
|
'google-cloud': 'Cloud',
|
|
elevenlabs: 'ElevenLabs',
|
|
cartesia: 'Cartesia',
|
|
};
|
|
|
|
export const TTS_PROVIDER_ENDPOINT: Record<TtsProvider, string> = {
|
|
openai: '/api/coach/speak-openai',
|
|
gemini: '/api/coach/speak-gemini',
|
|
'google-cloud': '/api/coach/speak-google',
|
|
elevenlabs: '/api/coach/speak-elevenlabs',
|
|
cartesia: '/api/coach/speak-cartesia',
|
|
};
|
|
|
|
const listeners = new Set<(p: TtsProvider) => void>();
|
|
let cached: TtsProvider | null = null;
|
|
|
|
export async function loadTtsProvider(): Promise<TtsProvider> {
|
|
if (cached) return cached;
|
|
const raw = await AsyncStorage.getItem(STORAGE_KEY).catch(() => null);
|
|
cached =
|
|
raw === 'gemini' || raw === 'google-cloud' || raw === 'elevenlabs' || raw === 'cartesia'
|
|
? raw
|
|
: DEFAULT_PROVIDER;
|
|
return cached;
|
|
}
|
|
|
|
export async function setTtsProvider(p: TtsProvider): Promise<void> {
|
|
cached = p;
|
|
await AsyncStorage.setItem(STORAGE_KEY, p).catch(() => {});
|
|
for (const cb of listeners) cb(p);
|
|
}
|
|
|
|
export function endpointForProvider(p: TtsProvider): string {
|
|
return TTS_PROVIDER_ENDPOINT[p];
|
|
}
|
|
|
|
/** Always-fresh read of the current provider — module-level `cached` is updated
|
|
* synchronously inside `setTtsProvider` BEFORE listeners fire, so reading this
|
|
* inside any async callback sidesteps React's state-update / useRef-update lag. */
|
|
export function currentProvider(): TtsProvider {
|
|
return cached ?? DEFAULT_PROVIDER;
|
|
}
|
|
|
|
export function useTtsProvider(): [TtsProvider, (p: TtsProvider) => Promise<void>] {
|
|
const [p, setP] = useState<TtsProvider>(cached ?? DEFAULT_PROVIDER);
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
loadTtsProvider().then((v) => { if (mounted) setP(v); });
|
|
const cb = (v: TtsProvider) => { if (mounted) setP(v); };
|
|
listeners.add(cb);
|
|
return () => { mounted = false; listeners.delete(cb); };
|
|
}, []);
|
|
return [p, setTtsProvider];
|
|
}
|