boxwart: Modell-Radar sucht, testet nachts selbst und empfiehlt (Hirn und Coder)
Ampel / ampel (push) Failing after 21s
Ampel / ampel (push) Failing after 21s
Suche aus Merkliste (deploy/radar-watchlist.json) und Hugging-Face-Entdeckung; nur Kandidaten mit Bild-Projektor, die neben das Warm-Set passen (<= 115 GB inkl. KV-Cache). Nachtlauf mc2-radar.timer 00:30, Tests nur bis 02:30 (um 03:00 kommt NerdQuiz), hoechstens ein neuer Kandidat pro Woche, Notbremse 02:35, RuntimeMaxSec als letzte Sicherung. Pruefstand als Modul (deploy/bench/pruefstand.py): Tempo, Werkzeuge, Deutsch/JSON bzw. Programmieraufgaben und Bild-Probe gegen das heutige Modell der Rolle. Durchgefallene werden geloescht, Bestandene gemeldet und erst nach "Uebernehmen" getauscht. Beim Uebernehmen wandern die Zweitrollen mit (fast beim Hirn, heavy beim Coder), und das Warm-Set des Stewards wird selbst umgestellt statt als Handgriff zu bleiben. Modell-Code im Pruefstand darf keine Prozesse starten (RLIMIT_NPROC=0). Radar steht im Flugplan und unter Waechter-Aufsicht; deploy.sh spielt seine Units ein. Oberflaeche: Vergleichswerte je Kandidat, Rueckfrage vor Uebernehmen und Verwerfen, Status auf Deutsch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4b226d5d22
commit
177c9a311c
@@ -83,3 +83,20 @@ export function rolleVon(m: Modell): "hirn" | "coder" | null {
|
||||
if (a.includes("coder")) return "coder"
|
||||
return null
|
||||
}
|
||||
|
||||
// --- Modell-Radar ---------------------------------------------------------------------
|
||||
|
||||
/** Status eines Radar-Kandidaten in Worten fürs Cockpit. */
|
||||
export const RADAR_STATUS_TEXT: Record<string, string> = {
|
||||
neu: "Neu", wartet: "Wartet", getestet: "Gemessen", bestanden: "Bestanden",
|
||||
durchgefallen: "Durchgefallen", uebernommen: "Übernommen", verworfen: "Verworfen",
|
||||
}
|
||||
|
||||
/** Messwert des Radar-Vergleichs lesbar machen (Zahlen deutsch, eine Nachkommastelle). */
|
||||
export function messwert(wert: unknown, einheit = ""): string {
|
||||
if (wert == null || wert === "") return "–"
|
||||
const text = typeof wert === "number"
|
||||
? wert.toLocaleString("de-DE", { maximumFractionDigits: wert < 10 ? 1 : 0 })
|
||||
: String(wert)
|
||||
return einheit ? `${text} ${einheit}` : text
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useState } from "react"
|
||||
import { Search } from "lucide-react"
|
||||
import { Bestaetigen } from "@/components/cockpit/Bestaetigen"
|
||||
import { Chip, type ChipArt, Zustandsfeld } from "@/components/cockpit/Chip"
|
||||
import { Panel } from "@/components/cockpit/Panel"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
useModellLaden, useModelle, useNutzung, useRadar, useRadarAktion, useRadarSuche, useStart,
|
||||
} from "@/lib/abfragen"
|
||||
import { rolleVon } from "@/lib/anzeige"
|
||||
import { messwert, RADAR_STATUS_TEXT, rolleVon } from "@/lib/anzeige"
|
||||
import { useLetzteMetrik } from "@/lib/strom"
|
||||
import type { Modell, Nutzung, RadarKandidat } from "@/lib/typen"
|
||||
import type { Modell, Nutzung, RadarKandidat, RadarTest } from "@/lib/typen"
|
||||
import { flugplanZeit, gb, gbText, zahl } from "@/lib/zeit"
|
||||
import { ModellSuche } from "./ModellSuche"
|
||||
|
||||
@@ -122,39 +123,94 @@ function NutzungsPanel({ nutzung }: { nutzung: Nutzung | undefined }) {
|
||||
const STATUS_ART: Record<string, ChipArt> = {
|
||||
bestanden: "gruen", durchgefallen: "rot", wartet: "cyan", neu: "cyan", getestet: "grau", uebernommen: "gruen", verworfen: "grau",
|
||||
}
|
||||
const URTEIL_FARBE: Record<string, string> = { besser: "text-gruen", schlechter: "text-rot-text", gleich: "text-text-2" }
|
||||
|
||||
function Kandidat({ k }: { k: RadarKandidat }) {
|
||||
type Vergleichszeile = { kandidat: unknown; baseline: unknown; einheit?: string; urteil?: string }
|
||||
|
||||
/** Die Messwerte hinter dem Urteil: Kandidat gegen das heutige Modell der Rolle. */
|
||||
function Vergleich({ test }: { test: RadarTest }) {
|
||||
const zeilen = Object.entries(test.vergleich ?? {}) as [string, Vergleichszeile][]
|
||||
if (zeilen.length === 0) return null
|
||||
return (
|
||||
<div role="table" aria-label="Messwerte gegen das heutige Modell" className="rounded-lg border border-linie bg-erhaben/50 px-3 py-2 text-sm">
|
||||
<div role="row" className="schild grid grid-cols-[minmax(0,1fr)_auto_auto] gap-x-4 pb-1 text-xs text-text-3">
|
||||
<span role="columnheader">Messwert</span>
|
||||
<span role="columnheader" className="text-right">Kandidat</span>
|
||||
<span role="columnheader" className="text-right">Heute</span>
|
||||
</div>
|
||||
{zeilen.map(([name, z]) => (
|
||||
<div key={name} role="row" className="grid grid-cols-[minmax(0,1fr)_auto_auto] gap-x-4 border-t border-linie py-1">
|
||||
<span role="rowheader" className="text-text-2">{name}</span>
|
||||
<span role="cell" className={`ziffern text-right ${URTEIL_FARBE[z.urteil ?? ""] ?? "text-text-2"}`}>
|
||||
{messwert(z.kandidat, z.einheit)}
|
||||
</span>
|
||||
<span role="cell" className="ziffern text-right text-text-3">{messwert(z.baseline, z.einheit)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | undefined; heute: string | undefined }) {
|
||||
const aktion = useRadarAktion()
|
||||
const offen = ["bestanden", "wartet", "neu"].includes(k.status)
|
||||
const [frage, setFrage] = useState<null | "uebernehmen" | "verwerfen">(null)
|
||||
const offen = ["bestanden", "wartet", "neu", "getestet"].includes(k.status)
|
||||
const hirn = k.rolle === "hirn"
|
||||
return (
|
||||
<li className="flex flex-col gap-2 border-t border-linie py-3.5">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="ziffern text-[15px] font-medium">{k.name}</span>
|
||||
<Chip art="cyan">{k.rolle === "hirn" ? "fürs Hirn" : "fürs Coden"}</Chip>
|
||||
<Chip art="cyan">{hirn ? "fürs Hirn" : "fürs Coden"}</Chip>
|
||||
<Chip art={k.eng ? "bernstein" : k.passt ? "gruen" : "rot"}>{k.eng ? "passt knapp" : k.passt ? "passt" : "passt nicht"}</Chip>
|
||||
<Chip art={STATUS_ART[k.status] ?? "grau"}>{k.status}</Chip>
|
||||
<Chip art={STATUS_ART[k.status] ?? "grau"}>{RADAR_STATUS_TEXT[k.status] ?? k.status}</Chip>
|
||||
</div>
|
||||
<p className="text-sm text-text-2">
|
||||
{k.groesse_gb != null ? `${k.groesse_gb.toLocaleString("de-DE")} GB. ` : ""}
|
||||
{k.begruendung}
|
||||
</p>
|
||||
{test && ["bestanden", "durchgefallen"].includes(k.status) && <Vergleich test={test} />}
|
||||
{offen && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{k.status === "bestanden" && (
|
||||
<Button size="sm" disabled={aktion.isPending} onClick={() => aktion.mutate({ id: k.id, aktion: "uebernehmen", name: k.name })}>
|
||||
<Button size="sm" disabled={aktion.isPending} onClick={() => setFrage("uebernehmen")}>
|
||||
Übernehmen
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" size="sm" disabled={aktion.isPending} onClick={() => aktion.mutate({ id: k.id, aktion: "verwerfen", name: k.name })}>
|
||||
<Button variant="outline" size="sm" disabled={aktion.isPending} onClick={() => setFrage("verwerfen")}>
|
||||
Verwerfen
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Bestaetigen
|
||||
offen={frage === "uebernehmen"}
|
||||
titel={`${k.name} übernehmen?`}
|
||||
text={hirn
|
||||
? `${k.name} wird Lucys Hirn${heute ? ` und löst ${heute} ab` : ""}. NerdQuiz und OpenChamber nutzen ab dann ebenfalls das neue Modell. Hermes startet dafür kurz neu, Lucy ist etwa eine Minute nicht erreichbar. Das alte Modell bleibt auf der Platte.`
|
||||
: `${k.name} wird der Coder${heute ? ` und löst ${heute} ab` : ""}. OpenChamber plant und baut ab dann damit. Das alte Modell bleibt auf der Platte.`}
|
||||
knopf="Übernehmen"
|
||||
onNein={() => setFrage(null)}
|
||||
onJa={() => {
|
||||
aktion.mutate({ id: k.id, aktion: "uebernehmen", name: k.name })
|
||||
setFrage(null)
|
||||
}}
|
||||
/>
|
||||
<Bestaetigen
|
||||
offen={frage === "verwerfen"}
|
||||
gefahr
|
||||
titel={`${k.name} verwerfen?`}
|
||||
text="Die heruntergeladenen Dateien werden gelöscht, und das Radar testet dieses Modell nicht noch einmal."
|
||||
knopf="Verwerfen"
|
||||
onNein={() => setFrage(null)}
|
||||
onJa={() => {
|
||||
aktion.mutate({ id: k.id, aktion: "verwerfen", name: k.name })
|
||||
setFrage(null)
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function RadarPanel() {
|
||||
function RadarPanel({ hirn, coder }: { hirn: string | undefined; coder: string | undefined }) {
|
||||
const radar = useRadar()
|
||||
const suche = useRadarSuche()
|
||||
if (radar.isError) {
|
||||
@@ -181,7 +237,14 @@ function RadarPanel() {
|
||||
<p className="text-[15px] text-text-2">Nichts Besseres in Sicht.</p>
|
||||
) : (
|
||||
<ul className="m-0 flex list-none flex-col p-0">
|
||||
{d.kandidaten.map((k) => <Kandidat key={k.id} k={k} />)}
|
||||
{d.kandidaten.map((k) => (
|
||||
<Kandidat
|
||||
key={k.id}
|
||||
k={k}
|
||||
test={d.tests.find((t) => t.kandidat === k.id)}
|
||||
heute={k.rolle === "hirn" ? hirn : coder}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{d && d.tests.length > 0 && (
|
||||
@@ -223,7 +286,7 @@ export function ModelleSeite() {
|
||||
</section>
|
||||
<div className="grid gap-5 md:gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.3fr)]">
|
||||
<NutzungsPanel nutzung={nutzung.data} />
|
||||
<RadarPanel />
|
||||
<RadarPanel hirn={hirn?.name} coder={coder?.name} />
|
||||
</div>
|
||||
<Panel
|
||||
titel="Weitere Einträge"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Warnpanel } from "@/components/cockpit/Warnpanel"
|
||||
import { Zaehlwerk } from "@/components/cockpit/Zaehlwerk"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRadar, useStart } from "@/lib/abfragen"
|
||||
import { temperaturFarbe } from "@/lib/anzeige"
|
||||
import { RADAR_STATUS_TEXT, temperaturFarbe } from "@/lib/anzeige"
|
||||
import { useLetzteMetrik } from "@/lib/strom"
|
||||
import type { Radar, Start } from "@/lib/typen"
|
||||
import { gb, gbText } from "@/lib/zeit"
|
||||
@@ -74,7 +74,7 @@ function RadarKasten({ radar }: { radar: Radar | undefined }) {
|
||||
<span className="flex flex-wrap items-center gap-2 text-[15px]">
|
||||
<span className="ziffern">{vorn.name}</span>
|
||||
<span className="text-text-2">fürs {vorn.rolle === "hirn" ? "Hirn" : "Coden"}</span>
|
||||
<Chip art={RADAR_ART[vorn.status as keyof typeof RADAR_ART] ?? "grau"}>{vorn.status}</Chip>
|
||||
<Chip art={RADAR_ART[vorn.status as keyof typeof RADAR_ART] ?? "grau"}>{RADAR_STATUS_TEXT[vorn.status] ?? vorn.status}</Chip>
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[15px] text-text-2">Nichts Besseres in Sicht.</span>
|
||||
|
||||
Reference in New Issue
Block a user