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:
@@ -66,6 +66,7 @@ function PipelineSettings() {
|
|||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const [rows, setRows] = useState({}); // language → settings
|
const [rows, setRows] = useState({}); // language → settings
|
||||||
|
const [voices, setVoices] = useState(null); // null = lädt/nicht verfügbar
|
||||||
const [saving, setSaving] = useState(null);
|
const [saving, setSaving] = useState(null);
|
||||||
const [msg, setMsg] = useState(null);
|
const [msg, setMsg] = useState(null);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@@ -78,7 +79,10 @@ export default function Settings() {
|
|||||||
setRows(map);
|
setRows(map);
|
||||||
} catch (e) { setError(e.message); }
|
} catch (e) { setError(e.message); }
|
||||||
}
|
}
|
||||||
useEffect(() => { load(); }, []);
|
useEffect(() => {
|
||||||
|
load();
|
||||||
|
apiFetch('/tts-settings/voices/available').then(setVoices).catch(() => setVoices(null));
|
||||||
|
}, []);
|
||||||
|
|
||||||
function update(lang, patch) {
|
function update(lang, patch) {
|
||||||
setRows(r => ({ ...r, [lang]: { ...(r[lang] || { language: 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">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<label className="text-sm">
|
<label className="text-sm">
|
||||||
<span className="block text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Voice-ID</span>
|
<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 })}
|
<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" />
|
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>
|
||||||
<label className="text-sm">
|
<label className="text-sm">
|
||||||
<span className="block text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Geschwindigkeit</span>
|
<span className="block text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Geschwindigkeit</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user