Skip to content

Commit fd02337

Browse files
authored
Merge pull request #11 from deapi-ai/fix/tts-voice-lang-slug-resolve
Fix TTS voice/lang defaults using names instead of slugs
2 parents 2ecf2b5 + a3b2d19 commit fd02337

1 file changed

Lines changed: 48 additions & 16 deletions

File tree

src/components/EndpointForm.tsx

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
44
import { Loader2, CircleDollarSign, Play, ChevronRight, RotateCcw, Dices } from 'lucide-react';
5-
import { EndpointDefinition, EndpointParam, JsonValue } from '@/lib/types';
5+
import { EndpointDefinition, EndpointParam, JsonValue, DeApiModel } from '@/lib/types';
66
import { useModelsContext } from '@/components/ModelsContext';
77
import { ModelInfo } from '@/components/ModelInfo';
88
import { FormField } from '@/components/form/FormField';
@@ -47,6 +47,29 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }:
4747
const prevModelSlugRef = useRef<string | undefined>(undefined);
4848
const savedModelsRef = useRef<Record<string, string>>({});
4949

50+
// Resolve lang/voice default values (API returns names, selects use slugs)
51+
const resolveLangSlug = useCallback((value: string, model: DeApiModel | undefined): string => {
52+
if (!model?.languages) return value;
53+
const match = model.languages.find((l) => l.slug === value || l.name === value);
54+
return match ? match.slug : value;
55+
}, []);
56+
57+
const resolveVoiceSlug = useCallback((value: string, langSlug: string, model: DeApiModel | undefined): string => {
58+
if (!model?.languages) return value;
59+
const normalizedLangSlug = resolveLangSlug(langSlug, model);
60+
const lang = model.languages.find((l) => l.slug === normalizedLangSlug);
61+
if (lang) {
62+
const match = lang.voices.find((v) => v.slug === value || v.name === value);
63+
return match ? match.slug : value;
64+
}
65+
// Search all languages as fallback
66+
for (const l of model.languages) {
67+
const match = l.voices.find((v) => v.slug === value || v.name === value);
68+
if (match) return match.slug;
69+
}
70+
return value;
71+
}, [resolveLangSlug]);
72+
5073
// Get model defaults/limits/features from API data
5174
const modelDefaults =
5275
selectedModel?.info && !Array.isArray(selectedModel.info) ? selectedModel.info.defaults : undefined;
@@ -141,9 +164,14 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }:
141164
});
142165
// Skip negative_prompt defaults — API returns placeholder text like "Negative prompt"
143166
// which is not a useful default value. Users should fill this in themselves.
144-
// Auto-set lang/voice defaults for TTS
145-
if (defaults.lang !== undefined) newValues['lang'] = defaults.lang as string;
146-
if (defaults.voice !== undefined) newValues['voice'] = defaults.voice as string;
167+
// Auto-set lang/voice defaults for TTS (resolve names to slugs)
168+
if (defaults.lang !== undefined) {
169+
newValues['lang'] = resolveLangSlug(defaults.lang as string, selectedModel);
170+
}
171+
if (defaults.voice !== undefined) {
172+
const langSlug = (newValues['lang'] ?? '') as string;
173+
newValues['voice'] = resolveVoiceSlug(defaults.voice as string, langSlug, selectedModel);
174+
}
147175
if (defaults.format !== undefined) newValues['format'] = defaults.format as string;
148176
if (defaults.sample_rate !== undefined) newValues['sample_rate'] = defaults.sample_rate as number;
149177
return newValues;
@@ -161,7 +189,7 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }:
161189
});
162190
return newDisabled;
163191
});
164-
}, [selectedModelSlug, selectedModel]);
192+
}, [selectedModelSlug, selectedModel, resolveLangSlug, resolveVoiceSlug]);
165193

166194
const handleChange = useCallback((name: string, value: JsonValue) => {
167195
setValues((prev) => {
@@ -299,8 +327,14 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }:
299327
newValues[field] = defaults[field] as number;
300328
}
301329
});
302-
if (defaults.lang !== undefined) newValues['lang'] = defaults.lang as string;
303-
if (defaults.voice !== undefined) newValues['voice'] = defaults.voice as string;
330+
// Resolve names to slugs for TTS defaults
331+
if (defaults.lang !== undefined) {
332+
newValues['lang'] = resolveLangSlug(defaults.lang as string, selectedModel);
333+
}
334+
if (defaults.voice !== undefined) {
335+
const langSlug = (newValues['lang'] ?? '') as string;
336+
newValues['voice'] = resolveVoiceSlug(defaults.voice as string, langSlug, selectedModel);
337+
}
304338
if (defaults.format !== undefined) newValues['format'] = defaults.format as string;
305339
if (defaults.sample_rate !== undefined) newValues['sample_rate'] = defaults.sample_rate as number;
306340
return newValues;
@@ -317,7 +351,7 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }:
317351
});
318352
return newDisabled;
319353
});
320-
}, [modelDefaults, modelSupportsField]);
354+
}, [modelDefaults, modelSupportsField, selectedModel, resolveLangSlug, resolveVoiceSlug]);
321355

322356
// Get effective param with model limits/defaults applied
323357
const getEffectiveParam = useCallback((param: EndpointParam): EndpointParam => {
@@ -366,20 +400,18 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }:
366400

367401
if (paramName === 'voice' && selectedModel.languages) {
368402
const selectedLang = values['lang'] as string;
369-
const language = selectedModel.languages.find((l) => l.slug === selectedLang);
403+
// Also try matching by name (API defaults may use name instead of slug)
404+
const language = selectedModel.languages.find(
405+
(l) => l.slug === selectedLang || l.name === selectedLang
406+
);
370407
if (language) {
371408
return language.voices.map((v) => ({
372409
value: v.slug,
373410
label: `${v.name} (${v.gender === 'female' ? 'F' : 'M'})`,
374411
}));
375412
}
376-
// Fallback: show all voices grouped by language
377-
return selectedModel.languages.flatMap((l) =>
378-
l.voices.map((v) => ({
379-
value: v.slug,
380-
label: `${v.name} (${l.slug.toUpperCase()}, ${v.gender === 'female' ? 'F' : 'M'})`,
381-
}))
382-
);
413+
// No language selected yet — return empty (defaults effect will set lang shortly)
414+
return [];
383415
}
384416

385417
return undefined;

0 commit comments

Comments
 (0)