feat(3c): OpenRouter-Modelle live & selbst-aktualisierend statt erfunden

- Neuer Endpunkt GET /api/admin/openrouter/models (10 Min gecacht) liefert
  die echte Modell-Liste von OpenRouter (live 339 Modelle).
- Konfiguration: openrouter_llm_model/-tts_model nutzen jetzt ein
  <datalist> aus dieser Live-Liste (tippen/auswählen) — die fest
  verdrahteten, teils erfundenen Namen (z.B. gemini-3.1-flash-lite) sind weg.
- Test mit gemocktem httpx. app.js v=48. 220 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 05:01:05 +02:00
commit 2bec4e72db
4 changed files with 75 additions and 3 deletions

View file

@ -1706,8 +1706,8 @@ const FIELD_META = {
default_tts_provider: { ui: "combo", opts: ["openrouter","piper","chatterbox"], test: "tts" },
default_language: { ui: "combo", opts: ["de","en","fr","es","it","pt","pl","ar"],
labels: { de:"Deutsch",en:"Englisch",fr:"Französisch",es:"Spanisch",it:"Italienisch",pt:"Portugiesisch",pl:"Polnisch",ar:"Arabisch" } },
openrouter_llm_model: { ui: "combo", opts: ["google/gemini-3.1-flash-lite","openai/gpt-4.1-mini","google/gemini-2.5-flash","anthropic/claude-haiku-4-5-20251001","meta-llama/llama-3.3-70b-instruct"], test: "llm" },
openrouter_tts_model: { ui: "combo", opts: ["google/gemini-3.1-flash-tts-preview","openai/gpt-4o-mini-tts"], test: "tts", testProvider: "openrouter" },
openrouter_llm_model: { ui: "datalist", test: "llm" },
openrouter_tts_model: { ui: "datalist", test: "tts", testProvider: "openrouter" },
openrouter_tts_voice: { ui: "combo", opts: ["Zephyr","Puck","Charon","Kore","Fenrir","Leda","Orus","Aoede","Callirrhoe","Enceladus","Algieba","Despina","Achernar"], test: "tts", testProvider: "openrouter" },
piper_voice: { ui: "combo", opts: ["de_DE-thorsten-high","de_DE-kerstin-low","en_US-lessac-high","en_US-amy-medium","en_US-ryan-high","en_GB-cori-high","fr_FR-siwis-medium","fr_FR-tom-medium","es_ES-sharvard-medium","it_IT-paola-medium","it_IT-riccardo-x_low","pt_BR-faber-medium","pl_PL-gosia-medium","pl_PL-darkman-medium","ar_JO-kareem-medium","ru_RU-irina-medium","ru_RU-ruslan-medium","zh_CN-huayan-medium"], test: "tts", testProvider: "piper" },
local_llm_system_prompt: { ui: "textarea", test: "llm" },
@ -1774,6 +1774,20 @@ async function loadSettings() {
section.appendChild(body);
container.appendChild(section);
});
populateModelDatalists();
}
// Füllt alle Modell-<datalist>s mit der Live-Liste von OpenRouter (einmal gecacht).
let _orModels = null;
async function populateModelDatalists() {
const lists = document.querySelectorAll("datalist[data-or-models]");
if (!lists.length) return;
if (_orModels === null) {
const res = await adminFetch("/api/admin/openrouter/models");
_orModels = (res && res.models) || [];
}
const opts = _orModels.map((m) => `<option value="${escHtml(m)}"></option>`).join("");
lists.forEach((dl) => { dl.innerHTML = opts; });
}
function _buildControl(item, meta) {
@ -1807,6 +1821,14 @@ function _buildControl(item, meta) {
</div>`;
}
if (meta.ui === "datalist") {
// Live-Modell-Liste (OpenRouter) per <datalist>: tippen/auswählen, immer gültige Namen.
return `
<input id="${id}" list="${id}-list" type="text" value="${escHtml(val)}" placeholder="Modell eingeben oder wählen"
class="${cls} flex-1" />
<datalist id="${id}-list" data-or-models></datalist>`;
}
if (meta.ui === "range") {
return `
<div class="flex-1 flex items-center gap-3">