Files
mission-control-v2/frontend/src/lib/anzeige.ts
T
HitonabiandClaude Opus 5.5 177c9a311c
Ampel / ampel (push) Failing after 21s
boxwart: Modell-Radar sucht, testet nachts selbst und empfiehlt (Hirn und Coder)
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>
2026-09-23 22:29:38 +02:00

103 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Reine Anzeige-Logik der Cockpit-Seiten — getrennt von den Komponenten, damit sie ohne
// DOM testbar bleibt und Vites Hot-Reload die Komponenten-Dateien sauber tauschen kann.
import type { UpdateDetails } from "./abfragen"
import type { Modell, Start } from "./typen"
// --- Rundinstrument -----------------------------------------------------------------
// 240°-Bogen wie ein Zeigerinstrument, Mittelpunkt (100|95) in einer 200×150-Zeichnung.
const MX = 100
const MY = 95
const START = 210 // Grad, links unten
const SPANNE = 240
function punkt(grad: number, r: number): [number, number] {
const rad = (grad * Math.PI) / 180
return [MX + r * Math.cos(rad), MY - r * Math.sin(rad)]
}
const r2 = (n: number) => Math.round(n * 100) / 100
/** SVG-Pfad eines Bogens von Anteil `von` bis `bis` (0…1) auf Radius `r`. */
export function bogenPfad(von: number, bis: number, r: number): string {
const a = Math.max(0, Math.min(1, von))
const b = Math.max(a, Math.min(1, bis))
const [x0, y0] = punkt(START - SPANNE * a, r)
const [x1, y1] = punkt(START - SPANNE * b, r)
const gross = (b - a) * SPANNE > 180 ? 1 : 0
return `M ${r2(x0)} ${r2(y0)} A ${r} ${r} 0 ${gross} 1 ${r2(x1)} ${r2(y1)}`
}
/** Farbe der Temperatur: grün bis 70 °C, dann Bernstein, ab 85 °C rot. */
export function temperaturFarbe(grad: number | null | undefined) {
if (grad == null) return "var(--text-3)"
if (grad >= 85) return "var(--rot)"
if (grad >= 70) return "var(--bernstein)"
return "var(--gruen)"
}
// --- Hauptwarnleuchte ---------------------------------------------------------------
/** Was die Hauptwarnleuchte sagt. Der schweigende Wächter geht allem anderen vor. */
export function hauptleuchte(z: Start["zustand"]) {
if (!z.waechter_wach) {
return { art: "stumm" as const, oben: "Wächter", mitte: "SCHWEIGT", unten: "Seit über 5 Minuten kein Prüflauf" }
}
if (z.update_laeuft) {
return { art: "info" as const, oben: "Wartung", mitte: "UPDATE LÄUFT", unten: "Hinweise ruhen bis zum Ende" }
}
if (z.anzahl === 0) {
return { art: "ok" as const, oben: "Alles", mitte: "IN ORDNUNG", unten: "Keine offenen Hinweise" }
}
const mitte = `${z.anzahl} HINWEIS${z.anzahl === 1 ? "" : "E"}`
return z.stufe === "rot"
? { art: "rot" as const, oben: "Störung", mitte, unten: "Drücken zum Ansehen" }
: { art: "gelb" as const, oben: "Achtung", mitte, unten: "Drücken zum Ansehen" }
}
// --- Updates ------------------------------------------------------------------------
/** „Läuft“ und „Neu“ aus den Update-Details lesbar machen. */
export function versionen(id: string, d: UpdateDetails | undefined): { laeuft: string; neu: string | null } {
if (!d) return { laeuft: "…", neu: null }
if (id === "os") return { laeuft: "Ubuntu", neu: d.count ? `${d.count} Pakete` : null }
// Hermes kommt aus git: „Neu“ ist die Zahl der Änderungen seit dem eingespielten Stand.
if (id === "hermes") {
return {
laeuft: d.installed_version ? `v${d.installed_version}` : "–",
neu: d.behind ? `+${d.behind.toLocaleString("de-DE")} Änderungen` : null,
}
}
const pre = id === "engine" ? "b" : "v"
const laeuft = d.installed_build != null ? `${pre}${d.installed_build}` : "–"
const neuer = d.latest_build != null && d.installed_build != null && d.latest_build > d.installed_build
return { laeuft, neu: neuer ? `${pre}${d.latest_build}` : null }
}
// --- Modelle ------------------------------------------------------------------------
/** Rolle eines Modells aus seinen llama-swap-Aliasen. */
export function rolleVon(m: Modell): "hirn" | "coder" | null {
const a = m.aliases.map((x) => x.toLowerCase())
if (a.includes("hermes") || a.includes("fast")) return "hirn"
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
}