/** * Provider-neutral voice-registration seam. * * The "auto voice" timbre-stability pattern — synthesize a voice-design once, * register it, then reference it by id — is not VoxCPM-specific. Any TTS * backend that can register/clone a voice (VoxCPM/vLLM-Omni today; ElevenLabs, * MiniMax, Doubao, … later) plugs in by implementing `VoiceRegistrationAdapter` * and registering it below. Server routes and the client orchestrator dispatch * by `providerId` and never name a concrete provider. */ import type { VoiceDesign } from '@/lib/audio/voice-design'; import { qwenVoiceCloneRegistrationAdapter } from '@/lib/audio/qwen-voice-clone-registration'; import { voxcpmVoiceRegistrationAdapter } from '@/lib/audio/voxcpm-registration'; /** Resolved backend connection for a registration call (server-injected for managed providers). */ export interface VoiceRegistrationConfig { baseUrl: string; apiKey?: string; model?: string; /** * `true` pins a client-supplied BYOK `baseUrl` to the strict public policy; * unset falls back to the process-wide `ALLOW_LOCAL_NETWORKS` behavior. */ publicOnly?: boolean; /** * `true` for a server-configured provider: its `baseUrl` may be on a local * network without ALLOW_LOCAL_NETWORKS. */ managed?: boolean; } export interface VoiceRegistrationAdapter { /** Provider wire name used for enrollment identity; omitted adapters send the name unchanged. */ normalizeRegistrationName?(name: string): string; /** Minimum reference duration in seconds; omitted adapters use the shared minimum. */ minReferenceDurationSeconds?: number; /** Whether registration is available for this provider given its options (e.g. backend kind). */ supportsRegistration(options?: Record): boolean; /** Whether the adapter can synthesize its own reference clip from a voice design. */ supportsBootstrapReferenceClip?: boolean; /** * The model this adapter's registration flow uses (enrollment target or the * synthesis model behind a bootstrap clip), resolved provider-side. * `clientModel` is a caller-supplied TTS model hint; the adapter decides * whether (and how) to honor it. `undefined` when registration is model-less. */ resolveRegistrationModel(clientModel?: string): string | undefined; /** Local saved-voice preflight only: no network work or model authorization. */ assertVoiceCompatible?(voiceId: string, configuredVoices?: readonly { id: string }[]): void; /** Whether `voiceId` is registered, or `unknown` when the lookup is inconclusive. */ voiceExists( cfg: VoiceRegistrationConfig, voiceId: string, signal?: AbortSignal, ): Promise; /** Register (or idempotently re-register) a reference clip under `voiceId`; returns the id. */ registerVoice( cfg: VoiceRegistrationConfig, params: { voiceId: string; referenceAudioBase64: string; mimeType?: string; refText?: string; }, signal?: AbortSignal, ): Promise; /** Delete a provider-side registered voice, when supported. */ deleteVoice?(cfg: VoiceRegistrationConfig, voiceId: string, signal?: AbortSignal): Promise; /** Synthesize the voice design once into a reference clip. */ bootstrapReferenceClip( cfg: VoiceRegistrationConfig, params: { design: VoiceDesign; language?: string }, signal?: AbortSignal, ): Promise<{ referenceAudioBase64: string; mimeType: string }>; } /** providerId → adapter. The only seam to touch when adding a provider. */ const VOICE_REGISTRATION_ADAPTERS: Record = { 'qwen-tts': qwenVoiceCloneRegistrationAdapter, 'voxcpm-tts': voxcpmVoiceRegistrationAdapter, }; export function getVoiceRegistrationAdapter( providerId: string, ): VoiceRegistrationAdapter | undefined { return VOICE_REGISTRATION_ADAPTERS[providerId]; } /** Whether this provider supports register-once/reference-by-id for the given options. */ export function supportsVoiceRegistration( providerId: string, options?: Record, ): boolean { return getVoiceRegistrationAdapter(providerId)?.supportsRegistration(options) ?? false; }