Show all STT/TTS backends with ✓/✗ status in conversation dropdowns

Replaces the filter-to-available-only approach with full lists that
include unavailable backends (disabled, marked ✗) so users can see
what's broken. Also extends STT retry fallback to cover HTTP 500 from
wrong model names (fixes faster-whisper CTranslate2 CUDA build issue).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-05-28 01:59:36 +02:00
parent ed24ff4f8f
commit 838445ba07
2 changed files with 36 additions and 9 deletions

View File

@ -1974,7 +1974,7 @@ def _transcribe_audio(src: Path, settings: dict, backend: str = "configured") ->
headers=hdrs,
timeout=_STT_REQUEST_TIMEOUT,
)
if resp.status_code in {400, 404, 422} and model != "whisper-1":
if resp.status_code in {400, 404, 422, 500} and model != "whisper-1":
with src.open("rb") as f:
resp = requests.post(
f"{stt_url}/v1/audio/transcriptions",

View File

@ -8227,12 +8227,27 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
// ── Populate STT backends ────────────────────────────────────────────────
async function loadConvSttBackends() {
if (!sttSel) return;
const prev = sttSel.value;
try {
const d = await fetch('/api/stt-backends').then(r => r.json());
const avail = (d.backends || []).filter(b => b.available);
sttSel.innerHTML = avail.length
? avail.map(b => `<option value="${escHtml(b.id)}">${escHtml(b.label)}</option>`).join('')
: '<option value="configured">Default (configured)</option>';
const all = d.backends || [];
if (!all.length) {
sttSel.innerHTML = '<option value="configured">Default (configured)</option>';
return;
}
sttSel.innerHTML = all.map(b => {
const icon = b.available ? '✓' : '✗';
const label = `${icon} ${escHtml(b.label)}`;
const disabled = !b.available;
return `<option value="${escHtml(b.id)}"${disabled ? ' disabled' : ''}>${label}</option>`;
}).join('');
// Restore prev selection or pick first available
const opt = sttSel.querySelector(`option[value="${CSS.escape(prev)}"]`);
if (opt && !opt.disabled) { sttSel.value = prev; }
else {
const first = sttSel.querySelector('option:not([disabled])');
if (first) sttSel.value = first.value;
}
} catch(_) {
sttSel.innerHTML = '<option value="configured">Default (configured)</option>';
}
@ -8241,10 +8256,22 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
// ── Populate TTS backends (reuse global _ttsBackends) ───────────────────
function populateConvTtsBackends() {
if (!ttsBkSel) return;
const avail = availableTtsBackends();
ttsBkSel.innerHTML = avail.length
? avail.map(b => `<option value="${escHtml(b.id)}">${escHtml(b.label)}</option>`).join('')
: '<option value="">No TTS backend available</option>';
const prev = ttsBkSel.value;
const all = window._ttsBackends || [];
if (!all.length) {
ttsBkSel.innerHTML = '<option value="">No TTS backend available</option>';
return;
}
ttsBkSel.innerHTML = all.map(b => {
const icon = b.available ? '✓' : '✗';
return `<option value="${escHtml(b.id)}"${!b.available ? ' disabled' : ''}>${icon} ${escHtml(b.label)}</option>`;
}).join('');
const opt = ttsBkSel.querySelector(`option[value="${CSS.escape(prev)}"]`);
if (opt && !opt.disabled) { ttsBkSel.value = prev; }
else {
const first = ttsBkSel.querySelector('option:not([disabled])');
if (first) ttsBkSel.value = first.value;
}
}
// ── Fetch LLM models ─────────────────────────────────────────────────────