modelle: Aufraeumen auf Knopfdruck, geteilte Gewichte bleiben beim Loeschen liegen
Ampel / ampel (push) Failing after 21s

Neuer Bereich "Aufraeumen" auf der Modelle-Seite: Eintraege ohne heutige Rolle und Ordner, auf
die kein Eintrag zeigt, mit Groesse und Erklaerung (Rueckweg, Reserve, alte Drafts). Geloescht
wird nur auf Knopfdruck mit Rueckfrage. delete_model loescht keine Dateien mehr, die ein anderer
Eintrag nennt - seit den Bild-Zwillingen haette sonst das Loeschen eines Zwillings den Coder
mitgerissen. Der Projektor des Hirn-Zwillings liegt jetzt neben den Hirn-Gewichten, damit der
Original-Ordner loeschbar bleibt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-24 13:17:11 +02:00
co-authored by Claude Opus 5.5
parent 133a491aca
commit 42363229d4
16 changed files with 340 additions and 17 deletions
+14 -1
View File
@@ -8,7 +8,7 @@ import { api, post } from "./api"
import { melden } from "./meldungen"
import { stromVerbunden } from "./strom"
import type {
AktionsErgebnis, Job, Modell, Nutzung, Radar, Sicherung, Start,
AktionsErgebnis, AufraeumKandidat, Job, Modell, Nutzung, Radar, Sicherung, Start,
} from "./typen"
const takt = (ms: number) => () => (stromVerbunden() ? ms * 5 : ms)
@@ -146,6 +146,19 @@ export const useUpdateStarten = () =>
neuLaden: ["jobs", "start"],
})
export const useAufraeumen = () =>
useQuery({
queryKey: ["aufraeumen"],
queryFn: () => api<{ kandidaten: AufraeumKandidat[] }>("/api/modelle/aufraeumen"),
staleTime: 60_000,
})
export const useAufraeumenLoeschen = () =>
useAktion(
(k: AufraeumKandidat) => post<AktionsErgebnis>("/api/modelle/aufraeumen/loeschen", { art: k.art, name: k.name }),
{ erfolg: (_, e) => (e as AktionsErgebnis).text ?? "Gelöscht.", neuLaden: ["aufraeumen", "models", "start"] },
)
export const useFreigeben = () =>
useAktion(
(baustein: string) => post<AktionsErgebnis>(`/api/updates/festgehalten/${encodeURIComponent(baustein)}/freigeben`),
+8
View File
@@ -82,6 +82,14 @@ export interface Festgehalten {
grund: string
}
/** Etwas auf der Modell-Platte, das ungenutzt Platz belegt (Box-Wart, Aufräumen). */
export interface AufraeumKandidat {
art: "eintrag" | "ordner"
name: string
groesse_gb: number
hinweis: string
}
export interface AktionsErgebnis {
ok: boolean
text?: string
+63 -2
View File
@@ -5,11 +5,12 @@ 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,
useAufraeumen, useAufraeumenLoeschen, useModellLaden, useModelle, useNutzung, useRadar, useRadarAktion,
useRadarSuche, useStart,
} from "@/lib/abfragen"
import { messwert, RADAR_STATUS_TEXT, rolleVon } from "@/lib/anzeige"
import { useLetzteMetrik } from "@/lib/strom"
import type { Modell, Nutzung, RadarKandidat, RadarTest } from "@/lib/typen"
import type { AufraeumKandidat, Modell, Nutzung, RadarKandidat, RadarTest } from "@/lib/typen"
import { flugplanZeit, gb, gbText, zahl } from "@/lib/zeit"
import { ModellSuche } from "./ModellSuche"
@@ -273,6 +274,65 @@ function RadarPanel({ hirn, coder }: { hirn: string | undefined; coder: string |
)
}
/** Was ungenutzt Platz belegt. Gelöscht wird nur auf Knopfdruck, mit Rückfrage — Löschen ist endgültig. */
function AufraeumenPanel() {
const liste = useAufraeumen()
const loeschen = useAufraeumenLoeschen()
const [frage, setFrage] = useState<AufraeumKandidat | null>(null)
const kandidaten = liste.data?.kandidaten ?? []
const summe = kandidaten.reduce((s, k) => s + k.groesse_gb, 0)
if (liste.isError) return null
return (
<Panel
titel="Aufräumen"
rechts={kandidaten.length > 0
? <span className="ziffern text-sm text-text-2">bis zu {summe.toLocaleString("de-DE", { maximumFractionDigits: 0 })} GB frei</span>
: undefined}
>
<p className="-mt-2 text-sm text-text-2">
Modelle und Ordner, die gerade niemand nutzt. Nichts davon wird von selbst gelöscht, das entscheidest du je Eintrag.
</p>
{liste.isPending ? (
<p className="text-[15px] text-text-2">Wird gelesen …</p>
) : kandidaten.length === 0 ? (
<p className="text-[15px] text-text-2">Nichts aufzuräumen.</p>
) : (
<ul className="m-0 flex list-none flex-col p-0">
{kandidaten.map((k) => (
<li key={`${k.art}-${k.name}`} className="flex flex-wrap items-center justify-between gap-3 border-t border-linie py-3">
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span className="flex flex-wrap items-center gap-2">
<span className="ziffern text-sm break-all">{k.name}</span>
<Chip>{k.art === "eintrag" ? "Eintrag" : "Ordner"}</Chip>
</span>
<span className="text-sm text-text-2">{k.hinweis}</span>
</span>
<span className="flex items-center gap-3">
<span className="ziffern text-sm text-text-2">{k.groesse_gb.toLocaleString("de-DE")} GB</span>
<Button variant="gefahr" size="sm" disabled={loeschen.isPending} onClick={() => setFrage(k)}>
Löschen
</Button>
</span>
</li>
))}
</ul>
)}
<Bestaetigen
offen={frage !== null}
gefahr
titel={`${frage?.name ?? ""} löschen?`}
text={`${frage?.hinweis ?? ""} Es werden ${frage?.groesse_gb.toLocaleString("de-DE") ?? ""} GB frei. Das lässt sich nicht rückgängig machen — zurück ginge es nur mit einem neuen Download.`}
knopf="Endgültig löschen"
onNein={() => setFrage(null)}
onJa={() => {
if (frage) loeschen.mutate(frage)
setFrage(null)
}}
/>
</Panel>
)
}
export function ModelleSeite() {
const modelle = useModelle()
const nutzung = useNutzung()
@@ -324,6 +384,7 @@ export function ModelleSeite() {
))}
</ul>
</Panel>
<AufraeumenPanel />
<ModellSuche offen={suche} onSchliessen={() => setSuche(false)} />
</>
)