feat: UI-Hinweis bei Cartesia + nicht unterstützter Sprache

Wählt der Nutzer Cartesia und eine feste Sprache, die Cartesia nicht
spricht (AR/IT/RU/PL …), erscheint ein Hinweis, dass automatisch die
lokale Piper-Stimme verwendet wird. Macht den tts_fallback sichtbar,
statt lautlos eine andere Stimme zu liefern.

- /api/config liefert neu available.cartesia_unsupported_languages
  (Single Source of Truth: _UNSUPPORTED_LANGS aus cartesia.py).
- app.js zeigt/aktualisiert den Hinweis bei Sprach-/Provider-Wechsel.
- Test: /api/config listet die nicht unterstützten Sprachen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 00:49:28 +02:00
commit a6866a6e7f
5 changed files with 44 additions and 1 deletions

View file

@ -9,6 +9,9 @@ const formEl = $("#prompt-form");
const micBtn = $("#mic");
const ttsSel = $("#tts");
const langSel = $("#lang-sel");
const cartesiaHint = $("#cartesia-lang-hint");
let cartesiaUnsupportedLangs = []; // von /api/config: Sprachen, die Cartesia nicht spricht
const CARTESIA_LANG_LABELS = { ar: "Arabisch", it: "Italienisch", ru: "Russisch", pl: "Polnisch", nl: "Niederländisch", sv: "Schwedisch", tr: "Türkisch" };
// "Flex" ist im Sprachmenü nur ein weiterer Wert: keine feste Sprache, sondern
// automatische Erkennung. Eine konkrete Sprache = Fix-Modus (Eingabe wird übersetzt).
@ -231,6 +234,7 @@ async function refreshCartesiaPreset() {
try {
const cfg = await fetch("/api/config", { cache: "no-store" }).then(r => r.json());
const available = cfg.available?.cartesia_available === true;
cartesiaUnsupportedLangs = cfg.available?.cartesia_unsupported_languages || [];
btn.disabled = !available;
btn.classList.toggle("opacity-50", !available);
btn.classList.toggle("cursor-not-allowed", !available);
@ -242,9 +246,28 @@ async function refreshCartesiaPreset() {
ttsSel.value = "piper";
syncPresetUI();
}
updateCartesiaLangHint();
} catch (e) { /* ignorieren */ }
}
// Zeigt einen Hinweis, wenn Cartesia gewählt ist, die feste Sprache aber nicht
// unterstützt wird — dann übernimmt automatisch die lokale Piper-Stimme (tts_fallback).
function updateCartesiaLangHint() {
if (!cartesiaHint) return;
const lang = langSel && langSel.value;
const show = ttsSel && ttsSel.value === "cartesia"
&& lang && lang !== "flex"
&& cartesiaUnsupportedLangs.includes(lang);
if (show) {
const label = CARTESIA_LANG_LABELS[lang] || lang.toUpperCase();
cartesiaHint.textContent =
`Hinweis: Cartesia spricht ${label} nicht — es wird automatisch die lokale Stimme (Piper) verwendet.`;
cartesiaHint.classList.remove("hidden");
} else {
cartesiaHint.classList.add("hidden");
}
}
// Graut "Cloud" (OpenRouter TTS) aus, wenn das TTS-Modell nicht verfügbar ist.
async function refreshOpenrouterTtsPreset() {
const btn = document.querySelector('.tts-preset[data-tts="openrouter"]');
@ -759,7 +782,7 @@ window.addEventListener("resize", scrollToBottom);
promptEl.addEventListener("focus", () => setTimeout(scrollToBottom, 300));
// Dropdown-Änderungen dauerhaft als Nutzer-Präferenz speichern.
if (langSel) langSel.addEventListener("change", () => savePrefs(langOverrides()));
if (langSel) langSel.addEventListener("change", () => { savePrefs(langOverrides()); updateCartesiaLangHint(); });
if (ttsSel) {
ttsSel.addEventListener("change", () => {
unlockTTS(); // Auswahl ist eine Nutzergeste -> Geräte-TTS gleich freischalten
@ -770,6 +793,7 @@ if (ttsSel) {
localStorage.removeItem(PLAYBACK_KEY);
savePrefs({ tts_provider: ttsSel.value || null }); // echter Provider -> Server-Pref
}
updateCartesiaLangHint();
});
}

View file

@ -298,6 +298,7 @@
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0"></span>
</button>
</div>
<p id="cartesia-lang-hint" class="hidden mt-2 px-1 text-xs text-amber-600 dark:text-amber-400"></p>
</div>