feat: Voice-Auswahl aus ElevenLabs-Account in den TTS-Einstellungen

Dropdown mit den Account-Stimmen (GET /tts-settings/voices/available)
plus Warnung, wenn die gespeicherte Voice-ID nicht im Account existiert
— so fällt eine ungültige Stimme (Ursache der fehlenden sv-Audios)
sofort auf statt still fehlzuschlagen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:04:13 +02:00
parent 2cec5bc362
commit af00d3323d

View File

@@ -66,6 +66,7 @@ function PipelineSettings() {
export default function Settings() {
const [rows, setRows] = useState({}); // language → settings
const [voices, setVoices] = useState(null); // null = lädt/nicht verfügbar
const [saving, setSaving] = useState(null);
const [msg, setMsg] = useState(null);
const [error, setError] = useState(null);
@@ -78,7 +79,10 @@ export default function Settings() {
setRows(map);
} catch (e) { setError(e.message); }
}
useEffect(() => { load(); }, []);
useEffect(() => {
load();
apiFetch('/tts-settings/voices/available').then(setVoices).catch(() => setVoices(null));
}, []);
function update(lang, patch) {
setRows(r => ({ ...r, [lang]: { ...(r[lang] || { language: lang }), ...patch } }));
@@ -132,8 +136,25 @@ export default function Settings() {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<label className="text-sm">
<span className="block text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Voice-ID</span>
{voices?.length > 0 && (
<select value={voices.some(v => v.voice_id === s.voice_id) ? s.voice_id : ''}
onChange={e => e.target.value && update(lang.code, { voice_id: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-1.5 text-sm mb-1 focus:outline-none focus:ring-2 focus:ring-indigo-400">
<option value=""> Stimme aus dem Account wählen </option>
{voices.map(v => (
<option key={v.voice_id} value={v.voice_id}>
{v.name}{v.labels?.accent ? ` (${v.labels.accent})` : ''}
</option>
))}
</select>
)}
<input value={s.voice_id || ''} onChange={e => update(lang.code, { voice_id: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-1.5 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-indigo-400" />
{voices?.length > 0 && s.voice_id && !voices.some(v => v.voice_id === s.voice_id) && (
<span className="block text-xs text-red-500 mt-1">
Diese Voice-ID existiert nicht im ElevenLabs-Account Audio-Generierung schlägt fehl.
</span>
)}
</label>
<label className="text-sm">
<span className="block text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Geschwindigkeit</span>