feat(2.0): W2+W3 — HF-Link/Suche + Modell-Verwaltungs-UX
W2: install akzeptiert HF-URL ODER org/repo (normalize_repo); GET /api/hf/
search + /api/hf/quants; Frontend AddModel-Panel (URL+Quant-Dropdown+freie
Suche) im Discover-Tab. W3: POST /api/models/{id}/role + /ctx; Installiert-
Tab mit Rollen-Select (fast/heavy/coder/...), ctx-Edit, Loeschen → LLM
tauschen per Klick.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -70,17 +70,29 @@ class InstallReq(BaseModel):
|
||||
hf_token: str | None = None
|
||||
|
||||
|
||||
@router.get("/hf/search")
|
||||
def hf_search(q: str) -> dict:
|
||||
return {"results": hf.search(q)}
|
||||
|
||||
|
||||
@router.get("/hf/quants")
|
||||
def hf_quants(repo: str) -> dict:
|
||||
repo = hf.normalize_repo(repo)
|
||||
return {"repo": repo, "quants": hf.list_quants(repo)}
|
||||
|
||||
|
||||
@router.post("/models/install")
|
||||
def install(req: InstallReq) -> dict:
|
||||
"""Lädt ein Modell von HuggingFace (Hintergrund-Job) UND trägt es sofort in
|
||||
llama-swap ein (cmd + Rolle-Alias). llama-swap (-watch-config) lädt es, sobald
|
||||
die Datei da ist. Split-GGUFs werden komplett geladen, registriert wird der
|
||||
erste Teil (-00001-of-…)."""
|
||||
info = hf.resolve_gguf(req.repo, req.quant)
|
||||
erste Teil (-00001-of-…). Akzeptiert volle HF-URL ODER org/repo."""
|
||||
repo = hf.normalize_repo(req.repo)
|
||||
info = hf.resolve_gguf(repo, req.quant)
|
||||
if not info["first"]:
|
||||
raise HTTPException(404, f"Keine GGUF-Datei für Quant '{req.quant}' in {req.repo} gefunden.")
|
||||
raise HTTPException(404, f"Keine GGUF-Datei für Quant '{req.quant}' in {repo} gefunden.")
|
||||
|
||||
subdir = req.repo.split("/")[-1]
|
||||
subdir = repo.split("/")[-1]
|
||||
target = MODELS_DIR / subdir
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
model_path = str(target / info["first"])
|
||||
@@ -89,7 +101,7 @@ def install(req: InstallReq) -> dict:
|
||||
ctx = req.ctx
|
||||
if ctx is None:
|
||||
ram = _ram_gb()
|
||||
ctx = max_ctx_for(extract_params_b(req.repo), req.quant, ram)
|
||||
ctx = max_ctx_for(extract_params_b(repo), req.quant, ram)
|
||||
|
||||
# Sofort registrieren (Datei kommt gleich) — robust gegen -watch-config.
|
||||
try:
|
||||
@@ -99,7 +111,7 @@ def install(req: InstallReq) -> dict:
|
||||
raise HTTPException(500, str(exc))
|
||||
|
||||
# Download-Job: alle GGUF-Teile (+ mmproj) per --include holen.
|
||||
args = [hf.hf_bin(), "download", req.repo]
|
||||
args = [hf.hf_bin(), "download", repo]
|
||||
for f in info["files"]:
|
||||
args.append(f)
|
||||
if info["mmproj"]:
|
||||
@@ -124,6 +136,28 @@ def cancel(job_id: str) -> dict:
|
||||
return {"ok": jobengine.cancel_job(job_id)}
|
||||
|
||||
|
||||
class RoleReq(BaseModel):
|
||||
role: str | None = None
|
||||
|
||||
|
||||
@router.post("/models/{model_id}/role")
|
||||
def set_model_role(model_id: str, body: RoleReq) -> dict:
|
||||
if not llamaswap.set_role(model_id, body.role):
|
||||
raise HTTPException(404, "Modell nicht gefunden")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class CtxReq(BaseModel):
|
||||
ctx: int
|
||||
|
||||
|
||||
@router.post("/models/{model_id}/ctx")
|
||||
def set_model_ctx(model_id: str, body: CtxReq) -> dict:
|
||||
if not llamaswap.set_ctx(model_id, body.ctx):
|
||||
raise HTTPException(404, "Modell nicht gefunden")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.delete("/models/{model_id}")
|
||||
def delete(model_id: str) -> dict:
|
||||
if not llamaswap.delete_model(model_id):
|
||||
|
||||
Reference in New Issue
Block a user