radar: „Jetzt testen“ – Kandidaten auf Knopfdruck prüfen, wenn der Speicher reicht
User 25.09.: „Warum kann ich Modell-Tests nicht selbst anstoßen, sondern muss immer auf die Box warten?“ Nachts testet das Radar weiter selbst. Tagsüber jetzt auch per Knopf – aber nur, wenn Kandidat, Warm-Set UND der heutige Coder zusammen unter die 115-GB-Grenze passen (der Test-Wächter stoppt den Kandidaten erst bis zu 10 s, nachdem der Coder zu laden beginnt), und nicht 00:00–03:30. Sonst steht der Grund beim Kandidaten. Test als Auftrag (radar_lauf.py --test), höchstens 2 h, dieselben Leitplanken wie nachts; jedes Ergebnis kommt als Meldung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c31ef827fc
commit
44f33c31d3
@@ -259,6 +259,13 @@ export const useRadarAktion = () =>
|
||||
},
|
||||
)
|
||||
|
||||
/** „Jetzt testen“ (seit 25.09.2026): der Test läuft als Auftrag (Gruppe „radar“), das Ergebnis kommt als Meldung. */
|
||||
export const useRadarTest = () =>
|
||||
useAktion(({ id }: { id: string; name: string }) => post<AktionsErgebnis>(`/api/radar/${encodeURIComponent(id)}/testen`), {
|
||||
erfolg: (v, e) => (e as AktionsErgebnis).text ?? `Der Test von ${v.name} läuft.`,
|
||||
neuLaden: ["radar", "jobs"],
|
||||
})
|
||||
|
||||
/** „Jetzt suchen“: Die Box startet die Suche als Auftrag (Gruppe „radar“) und antwortet sofort. */
|
||||
export const useRadarSuche = () =>
|
||||
useAktion(() => post<AktionsErgebnis>("/api/radar/suche"), {
|
||||
|
||||
@@ -205,6 +205,9 @@ export interface RadarKandidat {
|
||||
quelle: string
|
||||
status: RadarStatus
|
||||
begruendung: string
|
||||
/** „Jetzt testen“ geht (seit 25.09.2026) — sonst steht in testbar_grund, warum nicht (null: gar nicht mehr). */
|
||||
testbar_jetzt?: boolean
|
||||
testbar_grund?: string | null
|
||||
}
|
||||
|
||||
export interface RadarTest {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"
|
||||
import { post } from "@/lib/api"
|
||||
import type { RadarKandidat } from "@/lib/typen"
|
||||
import { Kandidat } from "./Modelle"
|
||||
|
||||
vi.mock("@/lib/api", async (original) => ({ ...(await original<typeof import("@/lib/api")>()), post: vi.fn() }))
|
||||
|
||||
// Wie am 25.09.2026: Ornith passt tagsüber samt Coder, Flash-Next (84,6 GB) nur nachts.
|
||||
const ornith: RadarKandidat = {
|
||||
id: "ornith", name: "Ornith-1.5-35B-A3B", rolle: "hirn", repo: "org/ornith", datei: "o.gguf", groesse_gb: 22.6,
|
||||
passt: true, eng: false, quelle: "watchlist", status: "neu", begruendung: "Aus der Merkliste.",
|
||||
testbar_jetzt: true, testbar_grund: null,
|
||||
}
|
||||
|
||||
function zeigen(k: RadarKandidat) {
|
||||
return render(
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
<ul><Kandidat k={k} test={undefined} heute="Qwen3.6-35B-A3B" /></ul>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
describe("Radar: Jetzt testen (seit 25.09.2026)", () => {
|
||||
it("fragt nach und startet den Test", async () => {
|
||||
vi.mocked(post).mockResolvedValue({ ok: true, text: "Der Test läuft." })
|
||||
zeigen(ornith)
|
||||
fireEvent.click(screen.getByRole("button", { name: "Jetzt testen" }))
|
||||
const dialog = screen.getByRole("dialog")
|
||||
expect(within(dialog).getByText(/Lucy und NerdQuiz etwas langsamer/)).toBeInTheDocument()
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Jetzt testen" }))
|
||||
await waitFor(() => expect(post).toHaveBeenCalledWith("/api/radar/ornith/testen"))
|
||||
})
|
||||
|
||||
it("sagt, warum es gerade nicht geht", () => {
|
||||
zeigen({ ...ornith, id: "flash", name: "Qwen3.8-Flash-Next", status: "wartet", testbar_jetzt: false,
|
||||
testbar_grund: "Zu groß für einen Test tagsüber: braucht ~84,6 GB, neben Warm-Set und Coder sind 59 GB frei." })
|
||||
expect(screen.queryByRole("button", { name: "Jetzt testen" })).not.toBeInTheDocument()
|
||||
expect(screen.getByText(/Zu groß für einen Test tagsüber/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -9,7 +9,7 @@ import { Panel } from "@/components/cockpit/Panel"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
useAltreste, useAltrestLoeschen, useAufraeumen, useAufraeumenLoeschen, useJobs, useModellLaden, useModelle,
|
||||
useNutzung, useRadar, useRadarAktion, useRadarSuche, useStart,
|
||||
useNutzung, useRadar, useRadarAktion, useRadarSuche, useRadarTest, useStart,
|
||||
} from "@/lib/abfragen"
|
||||
import { laeuftNoch, messwert, RADAR_STATUS_TEXT, rolleVon } from "@/lib/anzeige"
|
||||
import { ApiFehler } from "@/lib/api"
|
||||
@@ -177,9 +177,10 @@ function Vergleich({ test }: { test: RadarTest }) {
|
||||
)
|
||||
}
|
||||
|
||||
function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | undefined; heute: string | undefined }) {
|
||||
export function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | undefined; heute: string | undefined }) {
|
||||
const aktion = useRadarAktion()
|
||||
const [frage, setFrage] = useState<null | "uebernehmen" | "verwerfen">(null)
|
||||
const testen = useRadarTest()
|
||||
const [frage, setFrage] = useState<null | "uebernehmen" | "verwerfen" | "testen">(null)
|
||||
const offen = ["bestanden", "wartet", "neu", "getestet"].includes(k.status)
|
||||
const hirn = k.rolle === "hirn"
|
||||
return (
|
||||
@@ -197,8 +198,16 @@ function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | unde
|
||||
{k.begruendung}
|
||||
</p>
|
||||
{test && ["bestanden", "durchgefallen"].includes(k.status) && <Vergleich test={test} />}
|
||||
{k.testbar_grund && !k.begruendung.startsWith("Läuft gerade") && (
|
||||
<p className="text-xs text-text-3">{k.testbar_grund}</p>
|
||||
)}
|
||||
{offen && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{k.testbar_jetzt && (
|
||||
<Button variant="info" size="sm" disabled={testen.isPending} onClick={() => setFrage("testen")}>
|
||||
Jetzt testen
|
||||
</Button>
|
||||
)}
|
||||
{k.status === "bestanden" && (
|
||||
<Button size="sm" disabled={aktion.isPending} onClick={() => setFrage("uebernehmen")}>
|
||||
Übernehmen
|
||||
@@ -224,6 +233,19 @@ function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | unde
|
||||
setFrage(null)
|
||||
}}
|
||||
/>
|
||||
<Bestaetigen
|
||||
offen={frage === "testen"}
|
||||
titel={`${k.name} jetzt testen?`}
|
||||
text={`Das Radar lädt ${k.name}${k.groesse_gb != null ? ` (${k.groesse_gb.toLocaleString("de-DE")} GB)` : ""} herunter, falls nötig, misst das heutige Modell der Rolle und dann den Kandidaten. Das dauert je nach Download eine halbe bis zwei Stunden. Solange antworten Lucy und NerdQuiz etwas langsamer; ein geladener Coder wird dafür entladen, und wird er währenddessen gebraucht, bricht der Test ab. Das Ergebnis kommt als Meldung; fällt der Kandidat durch, werden seine Dateien gelöscht.`}
|
||||
knopf="Jetzt testen"
|
||||
gesperrt={testen.isPending}
|
||||
onNein={() => setFrage(null)}
|
||||
onJa={() => {
|
||||
if (frage !== "testen") return
|
||||
testen.mutate({ id: k.id, name: k.name })
|
||||
setFrage(null)
|
||||
}}
|
||||
/>
|
||||
<Bestaetigen
|
||||
offen={frage === "verwerfen"}
|
||||
gefahr
|
||||
@@ -286,7 +308,8 @@ function RadarPanel({ hirn, coder }: { hirn: string | undefined; coder: string |
|
||||
}
|
||||
>
|
||||
<p className="-mt-2 text-sm text-text-2">
|
||||
Testet nachts zwischen 00:30 und 02:30 selbst, höchstens einen Kandidaten pro Woche.
|
||||
Testet nachts zwischen 00:30 und 02:30 selbst, höchstens einen Kandidaten pro Woche. Tagsüber geht es mit
|
||||
„Jetzt testen“, wenn der Kandidat samt Coder in den Speicher passt.
|
||||
{d?.naechster_test ? ` Nächster Test: ${flugplanZeit(d.naechster_test)}.` : ""}
|
||||
{sucht ? " Sucht gerade bei Hugging Face. Das dauert ein paar Minuten; den Stand zeigt der Auftrag oben unter „Aufträge“." : ""}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user