| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- import type { TTSProviderId } from '@/lib/audio/types';
- import type { AgentConfig } from '@/lib/orchestration/registry/types';
- import { TTS_PROVIDERS } from '@/lib/audio/constants';
- export interface ResolvedVoice {
- providerId: TTSProviderId;
- modelId?: string;
- voiceId: string;
- }
- /**
- * Resolve the TTS provider + voice for an agent.
- * 1. If agent has voiceConfig and the voice is still valid, use it
- * 2. Otherwise, use the first available provider + deterministic voice by index
- */
- export function resolveAgentVoice(
- agent: AgentConfig,
- agentIndex: number,
- availableProviders: ProviderWithVoices[],
- ): ResolvedVoice {
- // Agent-specific config
- if (agent.voiceConfig) {
- // Browser-native voices are dynamic (not in static registry), so skip validation
- if (agent.voiceConfig.providerId === 'browser-native-tts') {
- return {
- providerId: agent.voiceConfig.providerId,
- modelId: agent.voiceConfig.modelId,
- voiceId: agent.voiceConfig.voiceId,
- };
- }
- const list = getServerVoiceList(agent.voiceConfig.providerId);
- if (list.includes(agent.voiceConfig.voiceId)) {
- return {
- providerId: agent.voiceConfig.providerId,
- modelId: agent.voiceConfig.modelId,
- voiceId: agent.voiceConfig.voiceId,
- };
- }
- }
- // Fallback: first available provider, deterministic voice
- if (availableProviders.length > 0) {
- const first = availableProviders[0];
- return {
- providerId: first.providerId,
- voiceId: first.voices[agentIndex % first.voices.length].id,
- };
- }
- return { providerId: 'browser-native-tts', voiceId: 'default' };
- }
- /**
- * Get the list of voice IDs for a TTS provider.
- * For browser-native-tts, returns empty (browser voices are dynamic).
- */
- export function getServerVoiceList(providerId: TTSProviderId): string[] {
- if (providerId === 'browser-native-tts') return [];
- const provider = TTS_PROVIDERS[providerId];
- if (!provider) return [];
- return provider.voices.map((v) => v.id);
- }
- export interface ModelVoiceGroup {
- modelId: string;
- modelName: string;
- voices: Array<{ id: string; name: string }>;
- }
- export interface ProviderWithVoices {
- providerId: TTSProviderId;
- providerName: string;
- voices: Array<{ id: string; name: string }>; // keep for backward compat
- modelGroups: ModelVoiceGroup[]; // voices grouped by model
- }
- /**
- * Get all available providers and their voices for the voice picker UI.
- * A provider is available if it has an API key or is server-configured.
- * Browser-native-tts is excluded (no static voice list).
- */
- export function getAvailableProvidersWithVoices(
- ttsProvidersConfig: Record<
- string,
- { apiKey?: string; enabled?: boolean; isServerConfigured?: boolean }
- >,
- ): ProviderWithVoices[] {
- const result: ProviderWithVoices[] = [];
- for (const [id, config] of Object.entries(TTS_PROVIDERS)) {
- const providerId = id as TTSProviderId;
- if (providerId === 'browser-native-tts') continue;
- if (config.voices.length === 0) continue;
- const providerConfig = ttsProvidersConfig[providerId];
- const hasApiKey = providerConfig?.apiKey && providerConfig.apiKey.trim().length > 0;
- const isServerConfigured = providerConfig?.isServerConfigured === true;
- if (hasApiKey || isServerConfigured) {
- const allVoices = config.voices.map((v) => ({ id: v.id, name: v.name }));
- // Build model groups
- const modelGroups: ModelVoiceGroup[] = [];
- if (config.models.length > 0) {
- for (const model of config.models) {
- const compatibleVoices = config.voices
- .filter((v) => !v.compatibleModels || v.compatibleModels.includes(model.id))
- .map((v) => ({ id: v.id, name: v.name }));
- modelGroups.push({
- modelId: model.id,
- modelName: model.name,
- voices: compatibleVoices,
- });
- }
- } else {
- // Provider has no model concept (Azure, Browser Native, Doubao)
- modelGroups.push({
- modelId: '',
- modelName: config.name,
- voices: allVoices,
- });
- }
- result.push({
- providerId,
- providerName: config.name,
- voices: allVoices,
- modelGroups,
- });
- }
- }
- return result;
- }
- /**
- * Find a voice display name across all providers.
- */
- export function findVoiceDisplayName(providerId: TTSProviderId, voiceId: string): string {
- const provider = TTS_PROVIDERS[providerId];
- if (!provider) return voiceId;
- const voice = provider.voices.find((v) => v.id === voiceId);
- return voice?.name ?? voiceId;
- }
|