Skip to content

v1.1: AI-Engineering-Upgrade & Material-Pipeline#21

Merged
veniplex merged 4 commits into
mainfrom
claude/v1.1-ai-upgrade
Jul 18, 2026
Merged

v1.1: AI-Engineering-Upgrade & Material-Pipeline#21
veniplex merged 4 commits into
mainfrom
claude/v1.1-ai-upgrade

Conversation

@veniplex

Copy link
Copy Markdown
Owner

v1.1 — AI-Engineering-Review, Fixes & Ausbau

Stacked PR auf #20 (claude/app-review-functionality-usb0y6) — bitte #20 zuerst mergen; danach dieses PR gegen main retargeten (GitHub macht das beim Merge der Base automatisch). Version ist auf 1.1.0 angehoben. Die Merge-Konflikte von #20 mit main (tus-Upload-Refactor aus #19) wurden im Zuge dieser Arbeit bereits aufgelöst — #20 ist wieder mergeable.

Verifikation: Typecheck ✓, Lint ✓, 129 Tests ✓, Produktions-Build ✓.

Diagnose deiner gemeldeten Bugs (Upload / Öffnen / Verarbeitung)

Die Analyse fand keinen deterministischen Code-Defekt im Upload- und Öffnen-Pfad — der Code ist an den üblichen Fallstricken (Dateinamen-Encoding, Streaming, Proxy-Body-Limits) korrekt. Die wahrscheinlichsten Ursachen sind deployment-seitig, und genau dafür macht dieses PR sie sichtbar:

  1. Upload schlägt fehl → wahrscheinlichste Ursache: das Upload-Volume (UPLOAD_DIR) ist im Container nicht beschreibbar (klassisches Docker-Bind-Mount-Ownership-Problem; der Server gibt dann 500 „Upload failed"). Prüfen: /api/health liefert jetzt storage: up|down, und die Admin-Seite zeigt einen roten Warnbanner, wenn der Speicher nicht beschreibbar ist. Im Server-Log nach [upload] stream to disk failed (EACCES) suchen; im Container ls -la auf das gemountete data/uploads.
  2. Bestehende Materialien lassen sich nicht öffnen → wahrscheinlichste Ursache: Dateien, die vor dem UPLOAD_DIR-Fix (Commit a1bf02e) hochgeladen wurden, gingen bei Redeploys verloren; die DB-Zeilen zeigen ins Leere und die File-Route 404t. Prüfen: Netzwerk-Tab — 404 auf /api/materials/<id>/file bestätigt es. Der Viewer erklärt das jetzt verständlich („Datei fehlt auf dem Server — bitte neu hochladen") statt eines kryptischen pdf.js-Fehlers.
  3. Zusätzlich abgesichert: Der pdf.js-Worker wurde bisher über ein Bundler-Muster geladen, das unter Turbopack (Next-16-Default) nicht garantiert funktioniert — wäre er 404 gegangen, wäre jede PDF-Anzeige kaputt. Er wird jetzt beim Build nach public/ kopiert und von einer stabilen URL geladen.

Material-Verarbeitung: von unsichtbar zu beobachtbar

Der Kern deines „Verarbeitung wirkt unsauber"-Eindrucks: Die Pipeline pflegte intern präzise Status (pending → extracting → embedding → summarizing → ready/failed/skipped) samt Fehlertexten — aber das UI hat nichts davon angezeigt. Jetzt:

  • Status-Badges in der Materialliste (Spinner für aktive Phasen, rot mit Fehler-Tooltip für failed, neutral mit Begründung für skipped), mit Auto-Refresh solange etwas läuft
  • „Neu verarbeiten"-Aktion für fehlgeschlagene/übersprungene Materialien (idempotent)
  • Übersprungene Materialien nennen den Grund (kein Transkriptionsanbieter, Modell ohne Bildverständnis, kein extrahierbarer Text) statt still leer zu bleiben
  • Fehlgeschlagenes Job-Enqueue markiert das Material als failed statt so zu tun, als wäre alles gut; hängende „summarizing"-Spinner sind unmöglich gemacht
  • Hinweisbanner, wenn kein Embedding-Modell konfiguriert ist (Dateien waren „ready", aber unsichtbar für die AI-Suche)
  • Upload-Fehler unterscheiden jetzt Größe/Quota (413), Rate-Limit, Netzwerk und Serverfehler mit handlungsleitenden Meldungen

AI-Engineering-Review: kritische Befunde (gefixt)

  1. Embedding-Modellwechsel zerstörte RAG still (schwerwiegendster latenter Bug): Die Vektorsuche filtert auf das aktive Modell — nach einem Wechsel matchte nichts mehr, ohne Signal, ohne Recovery. Jetzt: Re-Embed-Backfill-Job (startet automatisch beim Modellwechsel, Admin-Card mit Warnzähler + Fortschritt, resumable, baut am Ende den ANN-Index neu).
  2. Keine Generation-Parameter gesetzt: Alle Aufrufe liefen auf Provider-Defaults (~temp 0.7–1.0). Karten/Quiz/Outline/Summaries/Plan laufen jetzt mit niedriger Temperatur (Treue zu den Quellen), Grading mit Temperatur 0 + Rubrik (identische Antworten kippen nicht mehr zwischen richtig/falsch — das floss vorher in Noten und Schwachstellenanalyse!), und der Live-Generierungspfad hat ein Output-Token-Budget wie der Batch-Pfad (verhinderte stilles JSON-Abschneiden → „failed" Topics).
  3. Volltextsuche ohne deutsches Stemming: 'simple'-Config → „Vorlesungen" fand „Vorlesung" nicht. Jetzt 'german' + websearch_to_tsquery (Migration baut Spalte + GIN-Index neu).
  4. Chat-Fehler wurden verschluckt: Provider-Fehler mitten im Stream zeigten nur Generisches, leere Assistant-Messages wurden persistiert. Jetzt kategorisiert (Key ungültig / Rate-Limit / generisch) und übersetzt; leere Antworten werden nicht gespeichert.
  5. API-Keys wurden nie validiert: Admin-Provider haben jetzt einen „Verbindung testen"-Button (1-Token-Live-Request), BYOK-Keys werden vor dem Speichern getestet.
  6. Vision/Transkription scheiterten still (Text-only-Modell, fehlender Whisper-Provider) → jetzt sichtbare Skip-Gründe (s.o.).
  7. Retrieval-Rauschen: Vektor-Treffer unterhalb eines konservativen Ähnlichkeits-Schwellwerts werden vor der RRF-Fusion verworfen (Nearest-Neighbour liefert sonst immer irgendetwas).
  8. Generierungs-Prompts mit Qualitätsrubriken (atomare Karten, plausible Distraktoren, Schwierigkeits-Mix, gleichverteilte richtige Positionen).

Neue AI-Features

  • Klickbare Quellen-Zitate im Chat[n]-Zitate im Text, dedupierte Quellen-Chips mit Link zum Material unter der Antwort (persistiert, überleben Reload)
  • Stop-Button & Antwort neu generieren — Regenerate wird serverseitig sauber abgewickelt (letzte Assistant-Nachricht wird entfernt; 409 wenn dort bereits ein bestätigtes Write-Tool ausgeführt wurde)
  • AI-generierte Konversationstitel statt 80-Zeichen-Abschnitt
  • Modus-Wechsel in laufender Konversation (war beim Erstellen fixiert)
  • „Im Chat fragen" auf jedem Material — Konversation mit Dokument-Scope, Suche nur in diesem Dokument
  • Schwachthemen → Ein-Klick-Generierung — die Fortschrittsanalyse bietet direkt „Quiz/Deck aus meinen Schwachthemen"
  • Spracheingabe im Chat (Whisper über OpenAI/Groq; Button erscheint nur, wenn verfügbar)
  • „Erklären" auf PDF-Markierungen — Text der markierten Region wird via pdf.js-Textlayer extrahiert und AI-erklärt
  • LLM-Reranking der Suche (Admin-Toggle, default aus, Fallback auf RRF)
  • Usage-Transparenz: User sehen Monatsverbrauch vs. Limit (kalendermonats-konsistent mit der Durchsetzung — vorher zeigte die Karte „30 Tage") + Aufschlüsselung nach Feature; Admins zusätzlich Breakdown nach Feature & Modell

Bewusst NICHT in v1.1 (dokumentierte Grenzen)

  • Exam-Mode „Quiz aus Musterklausur" — braucht ein eigenes UX-Konzept (Markieren von Musterklausuren), sonst wird es raten
  • Cross-Encoder-Reranking, token-basiertes Chunking, Attachments im Chat — sinnvolle nächste Ausbaustufen
  • Seiten-genaue Zitat-Links — Chunks tragen keine Seitennummer (Schema-Erweiterung + Re-Extraktion wäre nötig)

Migrationen

  • 0036 — FTS-Spalte auf 'german' (Tabellen-Rewrite + GIN-Neuaufbau, läuft in db:migrate)
  • 0037ai_conversation.material_id (Dokument-Chat)

npm run db:migrate wie üblich. Neue optionale Umgebung: keine.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR


Generated by Claude Code

claude added 4 commits July 18, 2026 08:58
…obust PDF worker

The processing pipeline maintained detailed per-material state
(extractionStatus/extractionError) that the UI never showed — a stuck or
failed material looked identical to a ready one, and several failure modes
were completely silent. This makes the whole pipeline observable and
recoverable:

- materials list shows pipeline badges (queued/extracting/indexing/
  summarizing spinner, failed with error tooltip, skipped with reason) and
  polls while anything is still processing
- new 'process again' action re-enqueues failed/skipped materials
  (processing is idempotent, so retries are safe)
- skipped materials now record WHY (no transcription provider, no vision
  model, no extractable text) instead of silently showing nothing
- a failed job enqueue marks the material failed instead of pretending the
  upload fully succeeded; summarization errors can no longer leave a
  material stuck on the 'summarizing' spinner forever
- material viewer detects a missing stored file (DB row outliving the file,
  e.g. uploads lost before the volume fix) and explains it — previously the
  PDF viewer surfaced a cryptic pdf.js error
- pdf.js worker is copied to public/ at build time and loaded from a stable
  same-origin URL — the bare-specifier new URL(...) pattern is not
  guaranteed to emit an asset under Turbopack, which would break ALL PDF
  rendering
- /api/health now write-probes the storage backend and the admin page warns
  when storage is unwritable (the most likely cause of failing uploads)
- upload errors are mapped to actionable messages (rate limit, network,
  server/storage) instead of raw statusText
- materials page hints when no embedding model is configured (files were
  'ready' but silently unsearchable)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
…obust PDF worker

The processing pipeline maintained detailed per-material state
(extractionStatus/extractionError) that the UI never showed — a stuck or
failed material looked identical to a ready one, and several failure modes
were completely silent. This makes the whole pipeline observable and
recoverable:

- materials list shows pipeline badges (queued/extracting/indexing/
  summarizing spinner, failed with error tooltip, skipped with reason) and
  polls while anything is still processing
- new 'process again' action re-enqueues failed/skipped materials
  (processing is idempotent, so retries are safe)
- skipped materials now record WHY (no transcription provider, no vision
  model, no extractable text) instead of silently showing nothing
- a failed job enqueue marks the material failed instead of pretending the
  upload fully succeeded; summarization errors can no longer leave a
  material stuck on the 'summarizing' spinner forever
- material viewer detects a missing stored file (DB row outliving the file,
  e.g. uploads lost before the volume fix) and explains it — previously the
  PDF viewer surfaced a cryptic pdf.js error
- pdf.js worker is copied to public/ at build time and loaded from a stable
  same-origin URL — the bare-specifier new URL(...) pattern is not
  guaranteed to emit an asset under Turbopack, which would break ALL PDF
  rendering
- /api/health now write-probes the storage backend and the admin page warns
  when storage is unwritable (the most likely cause of failing uploads)
- upload errors are mapped to actionable messages (rate limit, network,
  server/storage) instead of raw statusText
- materials page hints when no embedding model is configured (files were
  'ready' but silently unsearchable)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
…vider validation

Core AI-engineering fixes from the v1.1 review:

- re-embed backfill: changing the default embedding model used to silently
  orphan every existing chunk (vector search filters on the active model
  ref and matched nothing, with no signal and no recovery). A new
  reembed-materials job re-embeds stale materials (reusing stored text,
  resumable), removes old-model chunks only after success, rebuilds the
  ANN index at the end, auto-starts on model change and has an admin card
  with a stale-count warning and progress
- generation parameters: every SDK call ran on raw provider defaults.
  Central params now set low temperature for content generation
  (cards/questions/outline/summaries/plans), temperature 0 for free-text
  grading (identical answers no longer flip between correct/incorrect)
  and OCR, an explicit chat temperature, and an output-token budget on the
  live generation path matching the batch path (prevents silent mid-JSON
  truncation -> schema failures)
- grading uses an explicit rubric; card/question prompts get quality
  rubrics (atomic cards, plausible distractors, difficulty mix, evenly
  distributed correct positions)
- full-text search switches from the 'simple' config to 'german'
  (stemming + stop words; the app's content is German-first) and from
  plainto_tsquery to websearch_to_tsquery (never throws, supports
  phrases); migration rebuilds the generated column + GIN index
- retrieval noise cut: vector-only hits below a conservative cosine
  threshold are dropped before RRF fusion (nearest-neighbour always
  returns something, even when the corpus doesn't cover the query)
- chat stream errors are categorized (auth / rate-limit / generic) and
  shown as translated messages instead of being swallowed; failed streams
  no longer persist empty assistant messages
- provider/API-key validation: admin 'Test connection' button fires a
  one-token live request against the (unsaved) provider config, and BYOK
  keys are validated the same way before being stored
- admin usage view gains a per-feature/per-model breakdown
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
…, voice input

New AI features for v1.1:

- clickable source citations in chat: search results carry an index and
  materialId, the model cites [n] inline, and deduped source chips below
  the answer deep-link to the material (persisted via tool parts)
- stop button while streaming and one-click regenerate of the last answer;
  the server handles the AI SDK regenerate trigger by removing the trailing
  assistant rows (refused with 409 when an executed write tool is part of
  the answer — its side effect already exists)
- AI-generated conversation titles after the first exchange (the 80-char
  truncation stays as instant placeholder)
- tutor mode can now be switched mid-conversation (was fixed at creation)
- 'chat with this document': aiConversation.materialId scopes retrieval to
  one material; entry button on the material viewer
- weak-topics loop closed: the progress analysis returns a compact
  weak-topics list and offers one-click 'quiz/deck from my weak spots'
- voice input in chat (MediaRecorder -> Whisper via configured
  OpenAI/Groq provider; hidden when unavailable)
- 'Explain' on PDF highlights: extracts the highlighted region's text via
  the pdf.js text layer and shows an AI explanation in the annotation
  popover
- optional LLM re-ranking of hybrid-search candidates (admin toggle,
  best-effort with RRF fallback)
- user settings usage card now matches the enforced calendar-month window,
  shows utilization vs the monthly limit and a per-feature breakdown
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
Base automatically changed from claude/app-review-functionality-usb0y6 to main July 18, 2026 09:49
@veniplex veniplex added the release-candidate Creates a new release and deployment of docker image, once merged. label Jul 18, 2026
@veniplex
veniplex merged commit 01e175c into main Jul 18, 2026
1 of 2 checks passed
@veniplex
veniplex deleted the claude/v1.1-ai-upgrade branch July 18, 2026 09:50
github-actions Bot added a commit that referenced this pull request Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-candidate Creates a new release and deployment of docker image, once merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants