diff --git a/CLAUDE.md b/CLAUDE.md index 1e5cf17..c8666f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,6 +58,22 @@ Diese drei Punkte sind der Grund, warum "die KI soll sagen, wenn sie scheitert" hier funktioniert: nicht weil das Modell Einsicht hätte, sondern weil eine externe, prüfbare Bedingung es erzwingt. +## Sichtbarkeit der LLM-Arbeit (Grundregel) + +So wie das System **sichtbar abbricht**, wenn es scheitert, muss es auch +**sichtbar zeigen**, wenn es arbeitet. Jede LLM-Aktion (die Kategorisierung) +wird in der Oberfläche als *laufende Aktivität* dargestellt: erkennbarer +Fortschritt (z. B. „LLM kategorisiert … Batch X/Y"), ein animierter Indikator, +und das auslösende Bedien-Element im Lade-Zustand. Eine LLM-Aktion darf **nie** +wie ein eingefrorenes, totes UI aussehen. + +Der Grund ist derselbe wie beim Abbruch: Transparenz. Der Nutzer muss jederzeit +zwischen „arbeitet gerade" und „hängt / ist fertig" unterscheiden können -- sonst +wirkt selbst ein korrekt laufender Prozess wie ein Defekt. Das gilt für die +Web-UI (animierter Status + Fortschrittsbalken) ebenso wie für die CLI (laufende +Status-/Batch-Zeile). Deterministische Schritte (Fetch) dürfen still sein; die +LLM-Schritte nicht. + ## Skills Das Projekt nutzt zwei Skills (in `.claude/skills/`), die exakt dem Schnitt diff --git a/src/angebote/web_static/index.html b/src/angebote/web_static/index.html index 5e41763..fab5974 100644 --- a/src/angebote/web_static/index.html +++ b/src/angebote/web_static/index.html @@ -162,6 +162,23 @@ } .pending { color:var(--muted); } + /* LLM-Arbeit SICHTBAR machen (Grundregel: zeig, dass das Modell arbeitet) */ + @keyframes llm-spin { to { transform:rotate(360deg); } } + @keyframes llm-shimmer { 0%{background-position:-220px 0} 100%{background-position:220px 0} } + .llm-busy { + display:flex; align-items:center; gap:var(--s4); margin-top:var(--s3); + padding:var(--s4); border-radius:var(--radius); + background:var(--brand-weak); border:1px solid #cfe3d9; + } + .llm-busy .spin { flex:none; width:20px; height:20px; border:2.5px solid #c2dccf; border-top-color:var(--brand); border-radius:50%; animation:llm-spin .8s linear infinite; } + .llm-busy .txt { flex:1; min-width:0; } + .llm-busy .txt .hl { font-weight:700; color:var(--brand-d); } + .llm-busy .txt small { display:block; color:var(--brand); opacity:.9; font-size:12px; margin-top:1px; } + .llm-busy .bar2 { height:7px; border-radius:4px; background:#cfe3d9; overflow:hidden; margin-top:7px; } + .llm-busy .bar2 > i { display:block; height:100%; background:var(--brand); border-radius:4px; transition:width .3s; } + .llm-busy.indet .bar2 > i { width:40%; background:linear-gradient(90deg,#cfe3d9,var(--brand),#cfe3d9); background-size:220px 100%; animation:llm-shimmer 1.1s linear infinite; } + @media (prefers-reduced-motion: reduce){ .llm-busy .spin{ animation:none; } .llm-busy.indet .bar2 > i{ animation:none; } } + /* Ergebnis (Stufe 2) */ #result { margin-top:var(--s8); } .summary { display:flex; flex-wrap:wrap; gap:var(--s2) var(--s6); align-items:baseline; color:var(--muted); font-size:14px; margin-bottom:var(--s4); } @@ -537,19 +554,37 @@ async function holeRohdaten(){ } // ----- Stufe 2: Kategorisieren ---------------------------------------------- +// Sichtbarer LLM-Arbeits-Indikator (Grundregel: zeig, dass das Modell arbeitet) +function llmBusy(titel, done, total, modell){ + const indet = !total; + const pct = indet ? 0 : Math.round(100 * done / total); + return `