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:
Hitonabi
2026-09-24 17:44:40 +02:00
co-authored by Claude Opus 5.5
parent 83941847ab
commit bc1106ddc8
10 changed files with 188 additions and 62 deletions
+22 -4
View File
@@ -1,4 +1,4 @@
import { useState } from "react"
import { useEffect, useState } from "react"
import { cn } from "cn"
import { Button } from "@/components/ui/button"
import { laeuftNoch } from "@/lib/anzeige"
@@ -22,14 +22,30 @@ const BALKEN: Record<JobZustand, string> = {
const zeitVon = (sekunden: number) => flugplanZeit(new Date(sekunden * 1000).toISOString())
/**
* Die Uhrzeit in Sekunden, alle 30 s neu, solange `aktiv`. Ohne sie bliebe „Läuft seit 5 min“ stehen,
* wenn ein Auftrag eine Weile nichts schreibt: Die Karte zeichnet sonst nur neu, wenn sich der Auftrag ändert.
*/
function useJetzt(aktiv: boolean) {
const [jetzt, setJetzt] = useState(() => Date.now() / 1000)
useEffect(() => {
if (!aktiv) return
const t = window.setInterval(() => setJetzt(Date.now() / 1000), 30_000)
return () => window.clearInterval(t)
}, [aktiv])
return jetzt
}
/**
* Ein Hintergrund-Auftrag als Karte: Titel und Zustand, bei Downloads der Fortschritt, bei Fehlern
* die letzte Meldung, auf Wunsch das Protokoll. Abbrechen nur, wo es wirklich stoppt (`abbrechbar`).
* `ebene` ist die Überschriftenebene (unter einem Panel-Titel h3, unter einer Zwischenüberschrift h4).
*/
export function JobKarte({ job, onAbbrechen, abbrechenGesperrt }: {
export function JobKarte({ job, onAbbrechen, abbrechenGesperrt, ebene = 4 }: {
job: Job
onAbbrechen?: (job: Job) => void
abbrechenGesperrt?: boolean
ebene?: 3 | 4
}) {
const [frage, setFrage] = useState(false)
const [protokoll, setProtokoll] = useState(false)
@@ -38,13 +54,15 @@ export function JobKarte({ job, onAbbrechen, abbrechenGesperrt }: {
const fortschritt = fortschrittText(job)
const meldung = letzteMeldung(job)
const laeuft = laeuftNoch(job)
const jetzt = useJetzt(laeuft)
const darfAbbrechen = !!onAbbrechen && abbrechbar(job)
const protokollZeilen = (job.log ?? []).slice(-12)
const Titel = ebene === 3 ? "h3" : "h4"
return (
<article aria-label={`Auftrag: ${titel}`} className="flex flex-col gap-2.5 rounded-xl border border-linie bg-erhaben/50 p-3.5">
<div className="flex items-start justify-between gap-3">
<h4 className="m-0 min-w-0 text-[15px] leading-snug font-semibold break-words">{titel}</h4>
<Titel className="m-0 min-w-0 text-[15px] leading-snug font-semibold break-words">{titel}</Titel>
<Chip art={zustand.art}>{zustand.text}</Chip>
</div>
@@ -66,7 +84,7 @@ export function JobKarte({ job, onAbbrechen, abbrechenGesperrt }: {
<p className="ziffern m-0 text-sm text-text-2">
{laeuft
? fortschritt ?? laufText(job)
? fortschritt ?? laufText(job, jetzt)
: `${zustand.text} · ${zeitVon(job.finished_at ?? job.started_at)}`}
</p>