diff --git a/routers/models.py b/routers/models.py index 18ef8f9..4259af4 100644 --- a/routers/models.py +++ b/routers/models.py @@ -98,11 +98,15 @@ def status(): if "--mmproj" in cmd: caps.append("Bild") + # "incomplete" = Rolle existiert in der config, hat aber kein Modell (-m) hinterlegt + # (z.B. von provision.sh angelegter Platzhalter). Ehrlich kennzeichnen statt "bereit". + incomplete = not (m_path and m_path.group(1)) configured[name] = { "name": name, "ttl": spec.get("ttl", cfg.get("globalTTL", 0)), "cmd": cmd, "state": "idle", + "incomplete": incomplete, "port": None, "meta": { "ctx": ctx, @@ -157,12 +161,29 @@ def download(req: DownloadReq): return {"job_id": job_id, "expected_path": str(target / req.file)} +def _augment_vision(cmd: str, model_path: str) -> str: + """Liegt im selben Ordner ein mmproj-Projektor, --mmproj + --jinja ergaenzen — + Vision-Modelle brauchen das in llama.cpp (siehe CLAUDE.md). No-op, wenn schon gesetzt + oder kein Projektor da.""" + if "--mmproj" in cmd: + return cmd + try: + d = os.path.dirname(model_path) + for f in sorted(os.listdir(d)): + if "mmproj" in f.lower() and f.lower().endswith(".gguf"): + return cmd.rstrip() + f" --mmproj {os.path.join(d, f)} --jinja" + except OSError: + pass + return cmd + + @router.post("/register") def register(req: RegisterReq): # Bewusst KEIN exists()-Check: beim frischen Download läuft der hf-Job noch, die Datei kommt # erst gleich. Eintrag jetzt schon schreiben → llama-swap (-watch-config) lädt, sobald sie da ist. cfg = read_config() cmd = CMD_TEMPLATE.replace("{model}", req.model_path).replace("{ctx}", str(req.ctx)) + cmd = _augment_vision(cmd, req.model_path) cfg["models"][req.alias] = { "cmd": LiteralScalarString(cmd + "\n"), "ttl": req.ttl if req.ttl is not None else DEFAULT_TTL, diff --git a/static/js/panels/models.js b/static/js/panels/models.js index fe7543b..c7fcf3f 100644 --- a/static/js/panels/models.js +++ b/static/js/panels/models.js @@ -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 `