* Voice UX: simple speak toggle, onboarding pick, chat voice change Replace Kokoro-branded settings with a Kann sprechen / Spricht nicht toggle, onboarding voice personalization, and local chat commands for voice changes. German voice presets (Eva, Bernd) surface as friendly names; Kokoro stays under the hood via KOKORO_BASE_URL. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix import order in voice onboarding and settings Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
import type { VoiceProvider } from "@rakazo/adapter-kit";
|
|
import { CartesiaVoiceProvider } from "./cartesia-voice.js";
|
|
import { ElevenLabsVoiceProvider } from "./elevenlabs-voice.js";
|
|
import { FishAudioVoiceProvider } from "./fish-audio-voice.js";
|
|
import { KokoroVoiceProvider } from "./kokoro-voice.js";
|
|
import { OpenAIVoiceProvider } from "./openai-voice.js";
|
|
import { SCRIPTED_VOICE_CATALOG_ENTRY, ScriptedVoiceProvider } from "./scripted-voice.js";
|
|
|
|
export const VOICE_CATALOG = [
|
|
{
|
|
id: "kokoro",
|
|
name: "Local voice",
|
|
description: "Free local speech on your server.",
|
|
transcribe: false,
|
|
},
|
|
{
|
|
id: "elevenlabs",
|
|
name: "ElevenLabs",
|
|
description: "Highest quality and cloning. Flash v2.5 for conversational calls.",
|
|
transcribe: true,
|
|
},
|
|
{
|
|
id: "openai",
|
|
name: "OpenAI",
|
|
description: "Simple TTS plus Whisper-class transcription. Reuse an OpenAI key.",
|
|
transcribe: true,
|
|
},
|
|
{
|
|
id: "cartesia",
|
|
name: "Cartesia",
|
|
description: "Lowest-latency Sonic voices for interruptible calls.",
|
|
transcribe: false,
|
|
},
|
|
{
|
|
id: "fish-audio",
|
|
name: "Fish Audio",
|
|
description: "Voice models, cloning, and expressive speech with optional transcription.",
|
|
transcribe: true,
|
|
},
|
|
] as const;
|
|
|
|
export { SCRIPTED_VOICE_CATALOG_ENTRY };
|
|
|
|
export type HostedVoiceProviderId = (typeof VOICE_CATALOG)[number]["id"];
|
|
export type VoiceProviderId = HostedVoiceProviderId | "scripted";
|
|
|
|
export function scriptedVoiceEnabled() {
|
|
return process.env.AGENT_RUNTIME === "scripted";
|
|
}
|
|
|
|
export function listVoiceCatalog() {
|
|
return scriptedVoiceEnabled()
|
|
? [...VOICE_CATALOG, SCRIPTED_VOICE_CATALOG_ENTRY]
|
|
: [...VOICE_CATALOG];
|
|
}
|
|
|
|
export function voiceCatalogEntry(id: string) {
|
|
if (id === SCRIPTED_VOICE_CATALOG_ENTRY.id) return SCRIPTED_VOICE_CATALOG_ENTRY;
|
|
return VOICE_CATALOG.find((entry) => entry.id === id);
|
|
}
|
|
|
|
export function isVoiceProviderId(value: string): value is VoiceProviderId {
|
|
if (value === "scripted") return scriptedVoiceEnabled();
|
|
return VOICE_CATALOG.some((entry) => entry.id === value);
|
|
}
|
|
|
|
/** Construct the provider adapter selected by a persisted voice credential. */
|
|
export function createVoiceProvider(kind: string): VoiceProvider {
|
|
switch (kind) {
|
|
case "kokoro":
|
|
return new KokoroVoiceProvider();
|
|
case "elevenlabs":
|
|
return new ElevenLabsVoiceProvider();
|
|
case "openai":
|
|
return new OpenAIVoiceProvider();
|
|
case "cartesia":
|
|
return new CartesiaVoiceProvider();
|
|
case "fish-audio":
|
|
return new FishAudioVoiceProvider();
|
|
case "scripted":
|
|
if (!scriptedVoiceEnabled()) break;
|
|
return new ScriptedVoiceProvider();
|
|
default:
|
|
break;
|
|
}
|
|
throw new Error(
|
|
`Unknown voice provider "${kind}". Use kokoro | elevenlabs | openai | cartesia | fish-audio.`,
|
|
);
|
|
}
|
|
|
|
export class NoVoiceConfigured extends Error {
|
|
readonly reason: "key" | "voice";
|
|
|
|
constructor(reason: "key" | "voice") {
|
|
super(
|
|
reason === "key"
|
|
? "Add a voice provider key in Voice settings to turn on speaking."
|
|
: "Pick a voice in Voice settings.",
|
|
);
|
|
this.reason = reason;
|
|
this.name = "NoVoiceConfigured";
|
|
}
|
|
}
|
|
|
|
export const MAX_SPEAK_CHARS = 2000;
|
|
export const MAX_TRANSCRIBE_BYTES = 8 * 1024 * 1024;
|