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:
@@ -47,12 +47,18 @@ def _fetch_author_models(author: str) -> list:
|
|||||||
|
|
||||||
|
|
||||||
def rank_runnable(models: list[dict]) -> list[dict]:
|
def rank_runnable(models: list[dict]) -> list[dict]:
|
||||||
"""EINE Quelle der Wahrheit fürs Ranking lauffähiger Modelle:
|
"""EINE Quelle der Wahrheit fürs Ranking lauffähiger Modelle für DIESE Hardware:
|
||||||
bestes Fit-Level zuerst (perfect < marginal), bei Gleichstand meistgeladen.
|
1) nur was komfortabel passt (perfect vor marginal, too_tight fliegt raus),
|
||||||
Zu große Modelle (too_tight) fliegen 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(
|
return sorted(
|
||||||
[m for m in models if m["fit"]["level"] != "too_tight"],
|
[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:
|
for c in CATEGORIES:
|
||||||
items = by_cat[c["role"]]
|
items = by_cat[c["role"]]
|
||||||
ranked = rank_runnable(items)
|
ranked = rank_runnable(items)
|
||||||
# Top 4 je Kategorie (für die Anzeige) — gerankt, dann nach Downloads aufgefüllt.
|
# Top 4 je Kategorie = die best-gerankten (fähigstes-was-passt zuerst).
|
||||||
items.sort(key=lambda x: (x["fit"]["level"] != "too_tight", x["downloads"]), reverse=True)
|
top = ranked[:4]
|
||||||
top = items[:4]
|
|
||||||
if top:
|
if top:
|
||||||
cats.append({
|
cats.append({
|
||||||
"role": c["role"], "title": c["title"], "icon": c["icon"],
|
"role": c["role"], "title": c["title"], "icon": c["icon"],
|
||||||
|
|||||||
@@ -164,6 +164,12 @@ def model_upgrades() -> list[dict]:
|
|||||||
|
|
||||||
installed = llamaswap.list_models()
|
installed = llamaswap.list_models()
|
||||||
active_roles = {m["role"] for m in installed if m.get("role")}
|
active_roles = {m["role"] for m in installed if m.get("role")}
|
||||||
|
# Größtes installiertes Modell je Rolle (params_b) — Basis für „kein Downgrade".
|
||||||
|
inst_params: dict[str, float] = {}
|
||||||
|
for m in installed:
|
||||||
|
r, pb = m.get("role"), (m.get("capabilities") or {}).get("params_b")
|
||||||
|
if r and pb:
|
||||||
|
inst_params[r] = max(inst_params.get(r, 0.0), float(pb))
|
||||||
|
|
||||||
cmds = " ".join(str(s.get("cmd", "")).lower()
|
cmds = " ".join(str(s.get("cmd", "")).lower()
|
||||||
for s in (llamaswap.read_config().get("models") or {}).values())
|
for s in (llamaswap.read_config().get("models") or {}).values())
|
||||||
@@ -178,6 +184,14 @@ def model_upgrades() -> list[dict]:
|
|||||||
rec = c.get("recommended")
|
rec = c.get("recommended")
|
||||||
if not rec:
|
if not rec:
|
||||||
continue
|
continue
|
||||||
|
# KEIN Downgrade: Empfehlung nur, wenn sie mind. so „groß"/fähig ist wie das
|
||||||
|
# installierte Modell der Rolle (verhindert z.B. fast 35B-A3B → 4B).
|
||||||
|
rec_model = next((x for x in c.get("models", []) if x.get("repo") == rec), None)
|
||||||
|
rec_params = float((rec_model or {}).get("params_b") or 0.0)
|
||||||
|
cur = inst_params.get(role, 0.0)
|
||||||
|
if cur and rec_params and rec_params < cur * 0.95:
|
||||||
|
continue
|
||||||
|
|
||||||
base = rec.split("/")[-1].lower()
|
base = rec.split("/")[-1].lower()
|
||||||
stem = base[:-5] if base.endswith("-gguf") else base
|
stem = base[:-5] if base.endswith("-gguf") else base
|
||||||
if base in cmds or (stem and stem in cmds):
|
if base in cmds or (stem and stem in cmds):
|
||||||
|
|||||||
-1
File diff suppressed because one or more lines are too long
-395
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+395
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
|||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>Mission Control 2.0</title>
|
<title>Mission Control 2.0</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CrPS-3Fn.js"></script>
|
<script type="module" crossorigin src="/assets/index-oijhNb41.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CjZVMvhL.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Cx3ZKkHL.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -3,15 +3,7 @@ import { Activity, Zap, Moon } from "lucide-react"
|
|||||||
import { useModels, useTokenStats } from "@/lib/queries"
|
import { useModels, useTokenStats } from "@/lib/queries"
|
||||||
import { fmtSize } from "@/lib/format"
|
import { fmtSize } from "@/lib/format"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { roleTone } from "@/components/models/ModelBadges"
|
||||||
const ROLE_TONE: Record<string, string> = {
|
|
||||||
fast: "bg-cyan-500/15 text-cyan-400 border-cyan-500/25",
|
|
||||||
heavy: "bg-amber-500/15 text-amber-400 border-amber-500/25",
|
|
||||||
coder: "bg-violet-500/15 text-violet-400 border-violet-500/25",
|
|
||||||
vision: "bg-pink-500/15 text-pink-400 border-pink-500/25",
|
|
||||||
scout: "bg-teal-500/15 text-teal-400 border-teal-500/25",
|
|
||||||
hermes: "bg-indigo-500/15 text-indigo-400 border-indigo-500/25",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ActiveModelsCard() {
|
export function ActiveModelsCard() {
|
||||||
const { data } = useModels(2_000)
|
const { data } = useModels(2_000)
|
||||||
@@ -65,7 +57,7 @@ export function ActiveModelsCard() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
{m.role && (
|
{m.role && (
|
||||||
<span className={cn("text-[8px] font-semibold uppercase px-1.5 py-0.5 rounded font-mono border tracking-wider shrink-0",
|
<span className={cn("text-[8px] font-semibold uppercase px-1.5 py-0.5 rounded font-mono border tracking-wider shrink-0",
|
||||||
ROLE_TONE[m.role] || "bg-slate-500/15 text-slate-300 border-slate-500/25")}>
|
roleTone(m.role))}>
|
||||||
{m.role}
|
{m.role}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Layers } from "lucide-react"
|
import { Layers } from "lucide-react"
|
||||||
import { useModels } from "@/lib/queries"
|
import { useModels } from "@/lib/queries"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { ROLES, roleTone } from "@/components/models/ModelBadges"
|
||||||
const ROLES = ["fast", "heavy", "coder", "vision", "scout"]
|
|
||||||
|
|
||||||
export function RolesCard() {
|
export function RolesCard() {
|
||||||
const { data } = useModels(3_000)
|
const { data } = useModels(3_000)
|
||||||
@@ -37,12 +36,7 @@ export function RolesCard() {
|
|||||||
<div className="min-w-0 flex-1 mr-2">
|
<div className="min-w-0 flex-1 mr-2">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<span className={cn("text-[9px] font-semibold uppercase px-1.5 py-0.5 rounded font-mono border tracking-wider shrink-0",
|
<span className={cn("text-[9px] font-semibold uppercase px-1.5 py-0.5 rounded font-mono border tracking-wider shrink-0",
|
||||||
role === "fast" ? "bg-cyan-500/15 text-cyan-400 border-cyan-500/25" :
|
roleTone(role))}>
|
||||||
role === "heavy" ? "bg-amber-500/15 text-amber-400 border-amber-500/25" :
|
|
||||||
role === "coder" ? "bg-violet-500/15 text-violet-400 border-violet-500/25" :
|
|
||||||
role === "vision" ? "bg-pink-500/15 text-pink-400 border-pink-500/25" :
|
|
||||||
"bg-teal-500/15 text-teal-400 border-teal-500/25"
|
|
||||||
)}>
|
|
||||||
{role}
|
{role}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ import { cn } from "@/lib/utils"
|
|||||||
// Die 5 kanonischen Serving-Rollen (identisch zu Backend sources.py/llamaswap.ROLE_IDS).
|
// Die 5 kanonischen Serving-Rollen (identisch zu Backend sources.py/llamaswap.ROLE_IDS).
|
||||||
export const ROLES = ["fast", "heavy", "coder", "vision", "scout"]
|
export const ROLES = ["fast", "heavy", "coder", "vision", "scout"]
|
||||||
|
|
||||||
|
// Rolle → Farbton. EINE Quelle der Wahrheit für alle Rollen-Badges (Cockpit, RolesCard,
|
||||||
|
// ActiveModelsCard, …). `hermes` = Agent-Hirn (taucht als geladenes Modell auf).
|
||||||
|
export const ROLE_TONE: Record<string, string> = {
|
||||||
|
fast: "bg-cyan-500/15 text-cyan-400 border-cyan-500/25",
|
||||||
|
heavy: "bg-amber-500/15 text-amber-400 border-amber-500/25",
|
||||||
|
coder: "bg-violet-500/15 text-violet-400 border-violet-500/25",
|
||||||
|
vision: "bg-pink-500/15 text-pink-400 border-pink-500/25",
|
||||||
|
scout: "bg-teal-500/15 text-teal-400 border-teal-500/25",
|
||||||
|
hermes: "bg-indigo-500/15 text-indigo-400 border-indigo-500/25",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const roleTone = (role?: string | null) =>
|
||||||
|
(role && ROLE_TONE[role]) || "bg-slate-500/15 text-slate-300 border-slate-500/25"
|
||||||
|
|
||||||
export function FitBadge({ fit }: { fit: Fit }) {
|
export function FitBadge({ fit }: { fit: Fit }) {
|
||||||
const tone = {
|
const tone = {
|
||||||
perfect: "bg-emerald-500/15 text-emerald-400 border border-emerald-500/20",
|
perfect: "bg-emerald-500/15 text-emerald-400 border border-emerald-500/20",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useDialog } from "@/lib/useDialog"
|
|||||||
import { CapsChips } from "@/components/CapsChips"
|
import { CapsChips } from "@/components/CapsChips"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { fmtSize, fmtCtx } from "@/lib/format"
|
import { fmtSize, fmtCtx } from "@/lib/format"
|
||||||
import { getBrandInfo, ROLES } from "@/components/models/ModelBadges"
|
import { getBrandInfo, ROLES, roleTone } from "@/components/models/ModelBadges"
|
||||||
import { SpecDraftModal } from "@/components/models/SpecDraftModal"
|
import { SpecDraftModal } from "@/components/models/SpecDraftModal"
|
||||||
|
|
||||||
export function Cockpit() {
|
export function Cockpit() {
|
||||||
@@ -611,12 +611,7 @@ export function Cockpit() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className={cn("text-[9px] font-bold uppercase tracking-wider font-mono px-1.5 py-0.5 rounded border",
|
<span className={cn("text-[9px] font-bold uppercase tracking-wider font-mono px-1.5 py-0.5 rounded border",
|
||||||
role === "fast" ? "bg-cyan-500/10 text-cyan-400 border-cyan-500/20" :
|
roleTone(role))}>{role}</span>
|
||||||
role === "heavy" ? "bg-amber-500/10 text-amber-400 border-amber-500/20" :
|
|
||||||
role === "coder" ? "bg-violet-500/10 text-violet-400 border-violet-500/20" :
|
|
||||||
role === "vision" ? "bg-pink-500/10 text-pink-400 border-pink-500/20" :
|
|
||||||
"bg-teal-500/10 text-teal-400 border-teal-500/20"
|
|
||||||
)}>{role}</span>
|
|
||||||
{isWarm && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />}
|
{isWarm && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user