Fix: "Modelle finden" bevorzugt faehigste passende Modelle (kein Downgrade) + Rollen-Farben zentral

- discover.rank_runnable: rankt jetzt fit-level > params_b(desc) > downloads -> fuer die
  128GB-Box wird das faehigste passende (MoE-)Modell empfohlen statt kleiner Populaer-Modelle.
  Top-4-Anzeige nutzt dasselbe Ranking.
- maintenance.model_upgrades: schlaegt KEIN Downgrade mehr vor (rec.params_b >= installiert*0.95).
  Behebt: fast 35B-A3B -> 4B wurde faelschlich als Upgrade angeboten.
- Frontend: Rollen-Farb-Mapping zentralisiert in ModelBadges (ROLE_TONE/roleTone);
  Cockpit/RolesCard/ActiveModelsCard nutzen es statt eigener Duplikate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 02:35:21 +02:00
parent 38f0394166
commit 8bb4e11f31
11 changed files with 444 additions and 430 deletions
+12 -7
View File
@@ -47,12 +47,18 @@ def _fetch_author_models(author: str) -> list:
def rank_runnable(models: list[dict]) -> list[dict]:
"""EINE Quelle der Wahrheit fürs Ranking lauffähiger Modelle:
bestes Fit-Level zuerst (perfect < marginal), bei Gleichstand meistgeladen.
Zu große Modelle (too_tight) fliegen raus."""
"""EINE Quelle der Wahrheit fürs Ranking lauffähiger Modelle für DIESE Hardware:
1) nur was komfortabel passt (perfect vor marginal, too_tight fliegt raus),
2) das FÄHIGSTE zuerst — mehr Parameter = mehr Können (bei MoE bleibt es dank
aktiver-Param-Schätzung schnell),
3) bei Gleichstand das meistgeladene.
So bevorzugt die 128-GB-Box große (MoE-)Modelle statt kleiner Populär-Modelle —
und „Modelle finden" schlägt nie ein Downgrade vor (z.B. 35B-A3B → 4B)."""
return sorted(
[m for m in models if m["fit"]["level"] != "too_tight"],
key=lambda m: (_FIT_ORDER[m["fit"]["level"]], -int(m.get("downloads") or 0)),
key=lambda m: (_FIT_ORDER[m["fit"]["level"]],
-float(m.get("params_b") or 0.0),
-int(m.get("downloads") or 0)),
)
@@ -97,9 +103,8 @@ def refresh_discover(ram_gb: float) -> dict:
for c in CATEGORIES:
items = by_cat[c["role"]]
ranked = rank_runnable(items)
# Top 4 je Kategorie (für die Anzeige) — gerankt, dann nach Downloads aufgefüllt.
items.sort(key=lambda x: (x["fit"]["level"] != "too_tight", x["downloads"]), reverse=True)
top = items[:4]
# Top 4 je Kategorie = die best-gerankten (fähigstes-was-passt zuerst).
top = ranked[:4]
if top:
cats.append({
"role": c["role"], "title": c["title"], "icon": c["icon"],