feat: leere Rollen ehrlich anzeigen + Vision-cmd automatisch (mmproj/jinja)

- status(): Flag "incomplete" fuer Rollen ohne Modell (-m), z.B. provision.sh-
  Platzhalter. Wurden bisher irrefuehrend als "bereit / ? . 8K . ?" gezeigt.
- Modelle-Tab: leere Rollen klar als "kein Modell hinterlegt / leer" + Aktion
  "Modell zuweisen" (-> Cookbook); nicht im Schnelltest-Dropdown waehlbar
- register(): erkennt einen mmproj-Projektor im selben Ordner und ergaenzt
  --mmproj + --jinja automatisch (Vision braucht das in llama.cpp)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-21 18:38:16 +02:00
parent f44da4b8e2
commit 45ab32955a
2 changed files with 39 additions and 1 deletions
+18 -1
View File
@@ -81,6 +81,19 @@ function onStatus(s) {
const sel = $("#chat-model"), cur = sel.value; sel.innerHTML = "";
tb.innerHTML = ALL.map(m => {
if (m.incomplete) {
// Leere Rolle (Platzhalter ohne Modell-Datei) — ehrlich kennzeichnen statt „bereit".
return `<tr style="opacity:.9">
<td class="mid" style="font-weight:500">${esc(m.name)}<div class="li-sub mono-sm" style="color:var(--warn)">kein Modell hinterlegt</div></td>
<td>—</td>
<td><span class="mono-sm text-mut">leere Rolle</span></td>
<td><span class="badge">leer</span></td>
<td class="port">—</td>
<td style="text-align:right;white-space:nowrap">
<button class="ghost" data-assign="${esc(m.name)}">Modell zuweisen</button>
<button class="ghost del" data-del="${esc(m.name)}">Löschen</button>
</td></tr>`;
}
const fn = m.meta?.filename ? `<div class="li-sub mono-sm">${esc(m.meta.filename)}</div>` : "";
return `<tr>
<td class="mid" style="font-weight:500">${esc(m.name)}${fn}</td>
@@ -94,12 +107,16 @@ function onStatus(s) {
<button class="ghost del" data-del="${esc(m.name)}">Löschen</button>
</td></tr>`;
}).join("");
for (const m of ALL) sel.insertAdjacentHTML("beforeend", `<option>${esc(m.name)}</option>`);
for (const m of ALL) if (!m.incomplete) sel.insertAdjacentHTML("beforeend", `<option>${esc(m.name)}</option>`);
if (cur) sel.value = cur;
tb.querySelectorAll("[data-unload]").forEach(b => b.addEventListener("click", () => unloadOne(b.getAttribute("data-unload"))));
tb.querySelectorAll("[data-cfg]").forEach(b => b.addEventListener("click", () => openConfig(b.getAttribute("data-cfg"))));
tb.querySelectorAll("[data-del]").forEach(b => b.addEventListener("click", () => deleteModel(b.getAttribute("data-del"))));
tb.querySelectorAll("[data-assign]").forEach(b => b.addEventListener("click", () => {
toast("Lade im Cookbook ein Modell und setze den Alias auf: " + b.getAttribute("data-assign"));
document.querySelector(".nav-item[data-view='cookbook']")?.click();
}));
}
async function deleteModel(m) {