durchsicht: Rueckfragen passen aufs kleine Handy, ehrlichere Texte, Abbrechen nur wo es stoppt
Befunde der Schlussdurchsicht (Attrappe des Backends, 320 x 568 bis 1440 px):
- Rueckfragen: Bei 320 x 568 lagen "Installieren"/"Abbrechen" unter dem Bildschirmrand
und liessen sich nicht erreichen (Dialog 791 px hoch, kein Scrollen). Jetzt hoechstens
Bildschirmhoehe, der Text scrollt, die Knoepfe bleiben unten stehen. Gilt fuer alle.
- Installieren: Die Rueckfrage sagt jetzt, dass llama-swap beim Eintragen sofort neu laedt
(--watch-config): laufende Antworten von Lucy/OpenChamber brechen ab, mit Rolle nach dem
Download noch einmal. Der Ersatz-Hinweis nennt, dass das bisherige Modell "heavy" behaelt.
- Abbrechen nur noch bei Downloads (eigener Prozess). Updates und die Suche laufen als
Shell-Kette, ein Abbruch stoppt dort nicht sicher alles.
- Auftragstitel in den Worten der Oberflaeche ("Update Motor (llama.cpp)" statt
"Engine-Update (llama.cpp Vulkan)"); "Laeuft seit ... min" zaehlt weiter, auch wenn der
Auftrag nichts schreibt; Ueberschriftenebene passend zum Panel.
- Updates: "Alles jetzt aktualisieren" richtet sich wie die Zeilen nach den frischen
Details (vorher nur nach dem bis zu 10 min alten Start-Stand); "Nach Neuem suchen"
sperrt, solange eine Suche laeuft (zwei apt-get update stolpern ueber die Sperre);
Rueckfragetext sagt genau, was gesichert wird.
- Nach Auftragsende nur nachladen, was betroffen ist (Download: Modelle/Platte; sonst
Versionen/Verlauf/Sicherungen) - die Update-Pruefung fragt GitHub, apt und git.
- Radar "Uebernehmen"/"Verwerfen" und "Endgueltig loeschen": Ja-Knopf gesperrt, solange
die Aktion laeuft, und ein zweiter Klick waehrend des Schliessens loest nichts mehr aus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
83941847ab
commit
bc1106ddc8
@@ -5,18 +5,21 @@ import { Button } from "@/components/ui/button"
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
||||
import { useInstallieren } from "@/lib/abfragen"
|
||||
import { api } from "@/lib/api"
|
||||
import { rollenErsatzText } from "@/lib/wartung"
|
||||
import { installierenUnterbrechung, type RollenInhaber, rollenErsatzText } from "@/lib/wartung"
|
||||
import { gbText, zahl } from "@/lib/zeit"
|
||||
|
||||
interface Treffer { repo: string; downloads: number; likes: number }
|
||||
/** /api/hf/quants liefert heute nur die Namen. Kommen Größen dazu, zeigen Liste und Rückfrage sie an. */
|
||||
interface Quant { quant?: string; name?: string; size_bytes?: number; total_bytes?: number }
|
||||
|
||||
/** Rollen, die sich beim Installieren gleich vergeben lassen (Rollen-Alias → Name in der Oberfläche). */
|
||||
/**
|
||||
* Rollen, die sich beim Installieren gleich vergeben lassen (Rollen-Alias → Name in der Oberfläche).
|
||||
* Das Hirn steht hier (wie bisher) nicht zur Wahl; Hirn-Kandidaten testet das Radar nachts, bevor man sie übernimmt.
|
||||
*/
|
||||
const ROLLEN: Record<string, string> = { coder: "Coder" }
|
||||
|
||||
/** Wer eine Rolle heute innehat: Modellname und ob ein Bild-Zwilling daran hängt. */
|
||||
export type RollenStand = Record<string, { name: string; zwilling: boolean } | undefined>
|
||||
/** Wer die Rollen heute innehat (Schlüssel = Rollen-Alias). */
|
||||
export type RollenStand = Record<string, RollenInhaber | undefined>
|
||||
|
||||
/** Modelle selbst auf Hugging Face suchen und installieren — zusätzlich zum Radar. */
|
||||
export function ModellSuche({ offen, onSchliessen, rollenJetzt }: {
|
||||
@@ -188,6 +191,7 @@ export function ModellSuche({ offen, onSchliessen, rollenJetzt }: {
|
||||
{ersatz}
|
||||
</p>
|
||||
)}
|
||||
<p className="m-0 text-[15px] leading-relaxed text-text-2">{installierenUnterbrechung(!!rolle)}</p>
|
||||
</Bestaetigen>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
@@ -214,8 +214,10 @@ function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | unde
|
||||
? `${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"
|
||||
gesperrt={aktion.isPending}
|
||||
onNein={() => setFrage(null)}
|
||||
onJa={() => {
|
||||
if (frage !== "uebernehmen") return // zweiter Klick eines Doppelklicks, der Dialog schließt schon
|
||||
aktion.mutate({ id: k.id, aktion: "uebernehmen", name: k.name })
|
||||
setFrage(null)
|
||||
}}
|
||||
@@ -226,8 +228,10 @@ function Kandidat({ k, test, heute }: { k: RadarKandidat; test: RadarTest | unde
|
||||
titel={`${k.name} verwerfen?`}
|
||||
text="Die heruntergeladenen Dateien werden gelöscht, und das Radar testet dieses Modell nicht noch einmal."
|
||||
knopf="Verwerfen"
|
||||
gesperrt={aktion.isPending}
|
||||
onNein={() => setFrage(null)}
|
||||
onJa={() => {
|
||||
if (frage !== "verwerfen") return
|
||||
aktion.mutate({ id: k.id, aktion: "verwerfen", name: k.name })
|
||||
setFrage(null)
|
||||
}}
|
||||
@@ -353,6 +357,7 @@ function AufraeumenPanel() {
|
||||
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"
|
||||
gesperrt={loeschen.isPending}
|
||||
onNein={() => setFrage(null)}
|
||||
onJa={() => {
|
||||
if (frage) loeschen.mutate(frage)
|
||||
@@ -432,7 +437,11 @@ export function ModelleSeite() {
|
||||
<ModellSuche
|
||||
offen={suche}
|
||||
onSchliessen={() => setSuche(false)}
|
||||
rollenJetzt={{ coder: coder ? { name: coder.name, zwilling: !!coderZwilling } : undefined }}
|
||||
rollenJetzt={{
|
||||
coder: coder
|
||||
? { name: coder.name, zwilling: !!coderZwilling, weitere: coder.aliases.filter((a) => a.toLowerCase() !== "coder") }
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react"
|
||||
import type { UseQueryResult } from "@tanstack/react-query"
|
||||
import { ChevronRight } from "lucide-react"
|
||||
import { cn } from "cn"
|
||||
import { Auftraege } from "@/components/cockpit/Auftraege"
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
useUpdateStarten, useUpdateVerlauf, useZurueckspielen,
|
||||
} from "@/lib/abfragen"
|
||||
import { laeuftNoch } from "@/lib/anzeige"
|
||||
import { detailsHabenNeues, pruefFehlerText, versionen } from "@/lib/wartung"
|
||||
import { detailsHabenNeues, jobSuche, pruefFehlerText, versionen } from "@/lib/wartung"
|
||||
import type { Festgehalten, Sicherung, UpdateDetails } from "@/lib/typen"
|
||||
import { flugplanZeit } from "@/lib/zeit"
|
||||
|
||||
@@ -110,9 +111,12 @@ function zusammenfassung(summary: string | undefined): string | null {
|
||||
return s
|
||||
}
|
||||
|
||||
function BausteinZeile({ id, name, neuLautStart, fest, gesperrt, onStart, onFreigeben }: {
|
||||
function BausteinZeile({ id, name, details, hatUpdate, neuLautStart, fest, gesperrt, onStart, onFreigeben }: {
|
||||
id: string
|
||||
name: string
|
||||
details: UseQueryResult<UpdateDetails, Error>
|
||||
/** Gibt es Neues? Frische Details schlagen den gecachten Stand der Startseite (siehe UpdatesSeite). */
|
||||
hatUpdate: boolean
|
||||
/** Was /api/start über diesen Baustein sagt („3 Pakete“, „neuer Build“), undefined = nichts Neues. */
|
||||
neuLautStart: string | undefined
|
||||
fest: Festgehalten | undefined
|
||||
@@ -120,13 +124,10 @@ function BausteinZeile({ id, name, neuLautStart, fest, gesperrt, onStart, onFrei
|
||||
onStart: () => void
|
||||
onFreigeben: () => void
|
||||
}) {
|
||||
const details = useUpdateDetails(id)
|
||||
const d = details.data
|
||||
const laedt = details.isPending
|
||||
const fehler = details.isError ? details.error.message : d?.error
|
||||
const v = d ? versionen(id, d) : null
|
||||
// Frische Details schlagen den gecachten Stand der Startseite; wissen sie nichts, gilt der Start.
|
||||
const hatUpdate = detailsHabenNeues(id, d) ?? neuLautStart != null
|
||||
const neu = hatUpdate ? v?.neu ?? neuLautStart ?? null : null
|
||||
const summary = zusammenfassung(d?.summary)
|
||||
|
||||
@@ -269,12 +270,16 @@ export function UpdatesSeite() {
|
||||
|
||||
const neuLautStart = new Map((start.data?.updates.bausteine ?? []).map((b) => [b.id, b.neu]))
|
||||
const festgehalten = new Map((start.data?.updates.festgehalten ?? []).map((f) => [f.baustein, f]))
|
||||
// /api/start kennt nur einen bis zu zehn Minuten alten Stand. Die Details prüfen frisch — sagen
|
||||
// sie etwas, gilt das (für die Zeilen UND für „Alles jetzt aktualisieren“), sonst der Start.
|
||||
const details = useUpdateDetails(BAUSTEINE.map((b) => b.id))
|
||||
const hatNeues = BAUSTEINE.map((b, i) => detailsHabenNeues(b.id, details[i].data) ?? neuLautStart.has(b.id))
|
||||
const alleJobs = jobs.data?.jobs ?? []
|
||||
// Nur ein Update gleichzeitig (Riegel im Backend) — auch der Sonntags-Lauf zählt.
|
||||
const updateLaeuft =
|
||||
alleJobs.some((j) => j.group === "maintenance" && laeuftNoch(j)) || (start.data?.zustand.update_laeuft ?? false)
|
||||
// Die Suche hat im Backend (noch) keine Gruppe; sobald sie "pruefen" heißt, sperrt der Knopf auch hier.
|
||||
const suchtGerade = alleJobs.some((j) => j.group === "pruefen" && laeuftNoch(j))
|
||||
// Zwei Suchen gleichzeitig stolpern über die apt-Sperre — solange eine läuft, bleibt der Knopf zu.
|
||||
const suchtGerade = alleJobs.some((j) => jobSuche(j) && laeuftNoch(j))
|
||||
const gesperrt = updateLaeuft || updateStarten.isPending
|
||||
const zurueckMoeglich = sicherungen.data?.available ?? false
|
||||
|
||||
@@ -296,11 +301,13 @@ export function UpdatesSeite() {
|
||||
<span role="columnheader">Was sich ändert</span>
|
||||
<span role="columnheader" className="sr-only">Aktion</span>
|
||||
</div>
|
||||
{BAUSTEINE.map((b) => (
|
||||
{BAUSTEINE.map((b, i) => (
|
||||
<BausteinZeile
|
||||
key={b.id}
|
||||
id={b.id}
|
||||
name={b.name}
|
||||
details={details[i]}
|
||||
hatUpdate={hatNeues[i]}
|
||||
neuLautStart={neuLautStart.get(b.id)}
|
||||
fest={festgehalten.get(b.id)}
|
||||
gesperrt={gesperrt}
|
||||
@@ -371,9 +378,9 @@ export function UpdatesSeite() {
|
||||
onClick={() => updateStarten.mutate("pruefen")}
|
||||
disabled={updateStarten.isPending || suchtGerade}
|
||||
>
|
||||
Nach Neuem suchen
|
||||
{suchtGerade ? "Sucht …" : "Nach Neuem suchen"}
|
||||
</Button>
|
||||
<Button onClick={() => setFrage({ art: "alle" })} disabled={gesperrt || neuLautStart.size === 0}>
|
||||
<Button onClick={() => setFrage({ art: "alle" })} disabled={gesperrt || !hatNeues.some(Boolean)}>
|
||||
Alles jetzt aktualisieren
|
||||
</Button>
|
||||
</div>
|
||||
@@ -413,7 +420,7 @@ export function UpdatesSeite() {
|
||||
<Bestaetigen
|
||||
offen={frage?.art === "alle"}
|
||||
titel="Alles jetzt aktualisieren?"
|
||||
text="Motor, llama-swap, Hermes und Betriebssystem werden nacheinander aktualisiert, soweit es Neues gibt. Vorher wird gesichert, danach geprüft. Lucy ist dabei ein paar Minuten nicht erreichbar."
|
||||
text="Motor, llama-swap, Hermes und Betriebssystem werden nacheinander aktualisiert, soweit es Neues gibt, und danach geprüft. Motor und llama-swap holen bei Problemen ihren alten Stand von selbst zurück, vor Hermes wird gesichert. Lucy ist dabei ein paar Minuten nicht erreichbar."
|
||||
knopf="Aktualisieren"
|
||||
gesperrt={gesperrt}
|
||||
onNein={() => setFrage(null)}
|
||||
|
||||
Reference in New Issue
Block a user