Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ ipython_config.py
# Docu

Coderr_Checklist.md
Coderr_CLAUDE.md
CLAUDE.md

# pyenv
# For a library or package, you might want to ignore these files since the code is
Expand Down
2 changes: 1 addition & 1 deletion assistant_app/knowledge/infrastruktur.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ die noch nie zurückgespielt wurde, ist keine Sicherung, sondern eine Annahme.

## Wie neue Versionen live gehen

Das Ausrollen passiert nicht von Hand, sondern über eine Pipeline in GitHub
Benjamin deployt nicht von Hand, sondern über eine CI/CD-Pipeline in GitHub
Actions. Bei jeder Änderung laufen zuerst die Prüfungen: Codeformatierung,
statische Analyse, automatische Tests, ein vollständiger Build. Erst wenn alles
davon durchläuft und die Änderung im Hauptzweig landet, wird ausgerollt.
Expand Down
38 changes: 38 additions & 0 deletions assistant_app/knowledge/projekt-bestellapp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
titel: BestellApp
quelle: Projekt BestellApp
---

## Was die BestellApp ist

Die BestellApp ist eine Bestelloberfläche nach dem Vorbild von Lieferdiensten
wie Lieferando, gebaut in reinem JavaScript mit HTML und CSS, ohne Framework.
Die Speisekarte einer Pizzeria mit Pizzen, Desserts und Getränken wird aus
einem JavaScript-Datenobjekt erzeugt.

Gerichte lassen sich in den Warenkorb legen, in der Menge ändern und wieder
entfernen. Zwischensumme, Lieferkosten und Gesamtsumme werden bei jeder
Änderung neu berechnet. Auf großen Bildschirmen steht der Warenkorb neben der
Speisekarte, auf dem Smartphone öffnet er sich als eigenes Dialogfenster. Nach
dem Bestellen erscheint eine Bestätigung, und der Warenkorb wird geleert.
Bestellt wird dabei nichts, es gibt kein Backend.

Die BestellApp ist eines der ersten Projekte, die Benjamin an der Developer
Akademie gebaut hat. Sie ist unter benjaminblarr.de/bestellapp erreichbar, der
Quelltext liegt öffentlich auf GitHub.

## Was Benjamin an der BestellApp über Frameworks gelernt hat

Der Zustand der BestellApp liegt in einfachen Variablen und Objekten. Jede
Änderung muss von Hand ins DOM zurückgeschrieben werden, in der richtigen
Reihenfolge und an jeder Stelle, an der derselbe Wert auftaucht.

Genau das ist hier die Schwierigkeit: Den Warenkorb gibt es zweimal, einmal
neben der Speisekarte für große Bildschirme und einmal im Dialog für das
Smartphone. Jeder Zähler, jeder Einzelpreis und jede Summe muss an beiden
Stellen nachgeführt werden. Wird eine Stelle vergessen, zeigen die beiden
Warenkörbe unterschiedliche Beträge.

Diesen Teil nimmt einem ein Framework wie Angular ab: Man ändert die Daten, und
die Oberfläche folgt von selbst. Weil Benjamin es vorher von Hand gemacht hat,
weiß er, welches Problem Datenbindung eigentlich löst.
36 changes: 36 additions & 0 deletions assistant_app/knowledge/projekt-book-store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
titel: Book-Store
quelle: Projekt Book-Store
---

## Was der Book-Store ist

Der Book-Store ist ein Buchkatalog mit Likes und Kommentaren, gebaut in reinem
JavaScript mit HTML und CSS, ohne Framework. Er zeigt neun Bücher mit Titel,
Autor, Erscheinungsjahr, Genre und Preis. Jedes Buch lässt sich liken und
kommentieren.

Likes und Kommentare bleiben nach dem Neuladen erhalten, weil sie im
LocalStorage des Browsers gespeichert werden. Einen Server gibt es nicht. Jeder
Besucher sieht deshalb nur seine eigenen Likes und Kommentare.

Der Book-Store ist das erste Projekt, das Benjamin an der Developer Akademie
gebaut hat. Er ist unter benjaminblarr.de/bookstore erreichbar, der Quelltext
liegt öffentlich auf GitHub.

## Wie der Book-Store Daten im Browser speichert

Der LocalStorage speichert nur Text. Der Book-Store wandelt deshalb nach jedem
Like und jedem Kommentar die gesamte Bücherliste in JSON um und legt sie unter
einem festen Schlüssel ab. Beim Laden der Seite wird sie wieder eingelesen und
ersetzt die Daten aus dem Quelltext. Ein Kommentar mit leerem Namen oder leerem
Text wird gar nicht erst gespeichert.

Die Daten liegen damit an zwei Orten: im Quelltext und im Browser. Das
funktioniert, solange sich die Bücherliste im Quelltext nicht ändert. Käme ein
neues Buch dazu, sähe ein Besucher mit gespeichertem Stand es nicht, weil sein
alter Stand den neuen überschreibt.

An diesem kleinen Projekt hat Benjamin gelernt, was es heißt, Zustand dauerhaft
zu speichern. Es ist dieselbe Frage, die in größeren Anwendungen eine Datenbank
beantwortet: Welcher Stand gilt, wenn es zwei gibt?
88 changes: 88 additions & 0 deletions assistant_app/knowledge/projekt-portfolio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
titel: Portfolio
quelle: Projekt Portfolio
---

## Was das Portfolio ist

Das Portfolio unter benjaminblarr.de ist Benjamins persönliche Website, eine
Single-Page-Anwendung mit Angular und TypeScript. Sie stellt ihn vor, zeigt
seine Technologien, eine Auswahl seiner Projekte mit Links zu den laufenden
Anwendungen, Stimmen von Teampartnern und ein Kontaktformular.

Die Seite ist zweisprachig, Deutsch und Englisch, und passt sich von großen
4K-Bildschirmen bis hinunter zu 320 Pixel breiten Smartphones an.

Das Design stammt von der Developer Akademie. Benjamins Arbeit ist die
Umsetzung: der Aufbau der Komponenten, das Layout auf allen Breiten, die
Übersetzung, das Formular mit dem Backend dahinter und die Auslieferung auf
seinen eigenen Server. Der Quelltext liegt öffentlich auf GitHub.

## Wie das Portfolio technisch aufgebaut ist

Das Portfolio nutzt Angular 21 mit Standalone-Komponenten, also ohne
NgModules, und SCSS ohne UI-Framework. Jeder Bereich der Seite ist eine eigene
Komponente, wiederverwendbare Bausteine wie der Knopf, das Kontaktformular und
das Projekt-Overlay liegen getrennt davon.

Die Texte der Seite stehen nicht in den Templates, sondern in je einer
Sprachdatei für Deutsch und Englisch.

Für den Knopf, das Kontaktformular und das Projekt-Overlay gibt es Unit-Tests
mit Vitest. Beim Formular wird dabei auch die Anfrage an den Server geprüft,
ohne dass ein echter Server laufen muss.

## Wie die Zweisprachigkeit des Portfolios funktioniert

Das Portfolio ist zweisprachig, Deutsch und Englisch. Die Internationalisierung,
kurz i18n, läuft über die Bibliothek ngx-translate.

Alle sichtbaren Texte stehen in zwei JSON-Dateien, eine pro Sprache, mit
denselben 143 Schlüsseln. Die Templates enthalten nur die Schlüssel, nicht die
Texte. Fest im Template stehen nur der Name, die Berufsbezeichnung und die
E-Mail-Adresse.

Die Sprachdateien werden zur Laufzeit geladen. Dadurch wechselt die Sprache
ohne Neuladen, und der Besucher bleibt an der Stelle der Seite, an der er
gerade war. Die gewählte Sprache merkt sich der Browser für den nächsten
Besuch. Fehlt in einer Sprache ein Text, erscheint die englische Fassung
statt eines leeren Feldes.

Beim Wechsel wird auch das Sprachattribut der Seite umgestellt. So liest ein
Screenreader englische Texte mit englischer Aussprache vor und nicht mit
deutscher.

Angular bringt mit @angular/localize eine eigene Lösung mit, die aber für jede
Sprache eine eigene Fassung der Seite baut. Für einen Sprachwechsel ohne
Neuladen passt ngx-translate besser.

## Wie das Portfolio deployt wird

Das Portfolio wird automatisch über GitHub Actions deployt. Bei jedem Pull
Request und jedem Push auf den Hauptzweig laufen Linting mit ESLint, die
Formatprüfung mit Prettier, die Tests und ein vollständiger Build. Alle
Prüfungen laufen auch dann weiter, wenn eine davon scheitert. So meldet ein
Durchlauf jedes Problem und nicht nur das erste.

Ausgerollt wird nur, was im Hauptzweig landet. Die Pipeline baut die Seite dann
neu und überträgt sie per rsync auf den Server. Dabei werden alte Dateien im
selben Durchgang entfernt und die Dateirechte direkt beim Schreiben gesetzt.

Nach dem Deployment prüft die Pipeline, ob die Seite erreichbar ist und ob die
Sprachdateien wirklich als JSON ankommen.

## Was Benjamin am Kontaktformular des Portfolios gelernt hat

Das Kontaktformular prüft die Eingaben zweimal: im Browser, damit der Besucher
sofort sieht, was fehlt, und auf dem Server, weil man sich auf den Browser nie
verlassen darf.

Einmal passten die beiden Prüfungen nicht zusammen. Der Server verlangte
mindestens zehn Zeichen in der Nachricht, das Formular im Browser nicht. Kurze
Nachrichten wurden also abgeschickt, vom Server abgelehnt, und der Besucher sah
nur eine allgemeine Fehlermeldung. Seitdem legt Benjamin bei jeder Änderung
beide Seiten nebeneinander und vergleicht sie Feld für Feld.

Die Fehlermeldungen des Servers zeigt das Formular bewusst nicht an. Sie gibt
es nur auf Deutsch, das Portfolio ist aber zweisprachig. Die Meldungen im
Formular kommen deshalb aus den Sprachdateien.
42 changes: 42 additions & 0 deletions assistant_app/knowledge/projekt-quizly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
titel: Quizly
quelle: Projekt Quizly
---

## Was Quizly ist

Quizly ist das Projekt, in dem Benjamin KI-Modelle in ein eigenes Backend
eingebunden hat. Das Backend mit Django und dem Django REST Framework macht aus
einem YouTube-Video ein Quiz. Der Nutzer gibt einen Link ein, das Backend lädt
die Tonspur mit yt-dlp herunter, wandelt sie mit dem Spracherkennungsmodell
Whisper in Text um und lässt Gemini über dessen API zehn Fragen mit je vier Antworten
schreiben.

Die Umwandlung in Text läuft lokal im Backend, der Ton geht also an keinen
fremden Dienst. Nur das fertige Transkript geht an Gemini. Welche Modelle
verwendet werden, lässt sich über die Konfiguration ändern, ohne den Code
anzufassen.

Die Anmeldung läuft über JWT in HttpOnly-Cookies. Beim Abmelden kommt der
Refresh-Token auf eine Sperrliste und kann nicht wiederverwendet werden.

Quizly ist ein Ausbildungsprojekt der Developer Akademie. Das Frontend war
vorgegeben, Benjamins Arbeit ist das Backend. Der Quelltext liegt öffentlich
auf GitHub.

## Wie Quizly die Antwort der KI absichert

Eine KI liefert nicht zuverlässig das Format, das man sich wünscht. Quizly
verlässt sich deshalb nicht auf den Text der Anfrage, sondern gibt Gemini ein
festes Antwortschema vor. Die Antwort kommt als JSON in genau der Struktur, die
das Backend erwartet.

Vor dem Speichern prüft Quizly zusätzlich, ob die richtige Antwort jeder Frage
wörtlich unter den vier Antwortmöglichkeiten steht. Ist das nicht der Fall,
bricht die Anfrage mit einem Fehler ab. Ohne diese Prüfung könnte ein Quiz
gespeichert werden, in dem eine Frage keine richtige Antwort hat, und das
würde erst ein Nutzer beim Spielen bemerken.

Die Tests rufen weder YouTube noch Whisper noch Gemini auf. Die ganze Kette ist
in den Tests ersetzt, damit sie schnell laufen, nichts kosten und jedes Mal
gleich ausgehen.
72 changes: 72 additions & 0 deletions assistant_app/knowledge/projekte-ueberblick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
titel: Projekte im Überblick
quelle: Projektübersicht
---

## Welche Projekte Benjamin gebaut hat und mit welchem Stack

Benjamins Projekte in der Reihenfolge, in der sie entstanden sind, jeweils mit
den wichtigsten Technologien:

- **Book-Store**: JavaScript, HTML, CSS, Speicherung im LocalStorage
- **BestellApp**: JavaScript, HTML, CSS
- **Pokédex**: JavaScript, Daten von der PokéAPI
- **El Pollo Loco**: JavaScript, objektorientiert, Zeichnen auf Canvas
- **Join**: Angular, TypeScript, SCSS, Supabase, im Team zu viert
- **Portfolio**: Angular, TypeScript, SCSS, zweisprachig
- **Cardelia**: Python, Django, Celery, Redis, Supabase
- **Coderr**: Python, Django REST Framework, PostgreSQL
- **Quizly**: Python, Django REST Framework, Whisper, Gemini
- **Videoflix**: Python, Django REST Framework, Redis, RQ, FFmpeg, Docker

Dazu kommt der Assistent auf dieser Seite: Django, PostgreSQL mit pgvector und
ein eigener Embedding-Dienst mit FastAPI.

Die Liste zeigt auch den Weg: Angefangen hat Benjamin mit reinem JavaScript
ohne Framework, dann kamen Angular und TypeScript, danach das Backend mit
Python und Django und zuletzt der Betrieb auf einem eigenen Server.

## Welche Projekte Benjamin mit Django und Python gebaut hat

Im Backend arbeitet Benjamin mit Python, Django und dem Django REST Framework.
Damit sind fünf Projekte entstanden:

- **Coderr** ist eine REST-API für eine Plattform, auf der Dienstleistungen
angeboten, bestellt und bewertet werden. Sie läuft auf seinem eigenen Server.
- **Quizly** macht aus einem YouTube-Video ein Quiz: Der Ton wird lokal mit
Whisper in Text umgewandelt, Gemini schreibt daraus zehn Fragen.
- **Videoflix** ist eine Videoplattform, die hochgeladene Videos im Hintergrund
in mehrere Auflösungen umwandelt. Das Projekt ist noch in Arbeit.
- **Cardelia** ist ein Kartenkatalog mit Sammlungsverwaltung, sein größtes und
einziges ohne Vorgaben entstandenes Projekt.
- **Der Assistent auf dieser Seite** sucht Antworten in einer Wissensbasis mit
PostgreSQL und pgvector.

Die ersten drei sind Ausbildungsprojekte der Developer Akademie mit
vorgegebenem Frontend. Benjamins Arbeit ist dort das Backend dahinter.

## Welche Frontend-Projekte Benjamin gebaut hat

Im Frontend hat Benjamin zuerst ohne Framework gearbeitet, in reinem
JavaScript mit HTML und CSS. So sind der Book-Store, die BestellApp, der
Pokédex und das Spiel El Pollo Loco entstanden. Das war die Vorgabe der
Developer Akademie und hat einen Sinn: Wer einmal jede Änderung von Hand ins
DOM geschrieben hat, versteht, was ein Framework später abnimmt.

Danach kamen Angular und TypeScript. Damit sind Join, ein Task-Manager im Team
zu viert, und sein Portfolio gebaut, beide mit SCSS und ohne UI-Framework wie
Bootstrap oder Angular Material.

Alle Frontend-Projekte sind öffentlich erreichbar und laufen auf Benjamins
eigenem Server.

## Warum im Portfolio nur vier Projekte stehen

Im Portfolio stehen vier Projekte: Join, El Pollo Loco, Cardelia und Coderr.
Der Pokédex, die BestellApp und der Book-Store laufen weiterhin und sind
erreichbar, werden dort aber nicht mehr gezeigt.

Das ist eine bewusste Entscheidung. Vier Projekte lesen sich als Auswahl, sechs
oder mehr als Sammlung. Kommt ein neues Projekt dazu, wird deshalb getauscht
und nicht angehängt. Die Auswahl soll zeigen, was Benjamin heute kann, und
nicht alles, was er je gebaut hat.
29 changes: 22 additions & 7 deletions assistant_app/retrieval_questions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{"question": "Welche Projekte hat Benjamin mit Django gebaut?", "expected": ["Was Coderr ist", "Wie Coderr aufgebaut ist", "Die Architektur von Cardelia", "Videoflix, eine Streaming-Plattform mit Hintergrundverarbeitung"]},
{"question": "Hat er schon mal selbst einen Server deployt?", "expected": ["Benjamin betreibt seine Projekte auf einem eigenen Server", "Wie neue Versionen live gehen"]},
{"question": "Wie deployt Benjamin seine Projekte?", "expected": ["Wie neue Versionen live gehen"]},
{"question": "Welche Projekte hat Benjamin mit Django gebaut?", "expected": ["Was Coderr ist", "Wie Coderr aufgebaut ist", "Die Architektur von Cardelia", "Videoflix, eine Streaming-Plattform mit Hintergrundverarbeitung", "Welche Projekte Benjamin mit Django und Python gebaut hat"]},
{"question": "Hat er schon mal selbst einen Server deployt?", "expected": ["Benjamin betreibt seine Projekte auf einem eigenen Server", "Wie neue Versionen live gehen", "Wie das Portfolio deployt wird"]},
{"question": "Wie deployt Benjamin seine Projekte?", "expected": ["Wie neue Versionen live gehen", "Wie das Portfolio deployt wird"]},
{"question": "Wie sichert er seinen Server ab?", "expected": ["Absicherung des Servers"]},
{"question": "Macht er Backups?", "expected": ["Datensicherung"]},
{"question": "Warum Nginx und Gunicorn?", "expected": ["Wie die Anwendungen ausgeliefert werden"]},
Expand All @@ -21,18 +21,33 @@
{"question": "Wie schützt das Kontaktformular vor Spam?", "expected": ["Das Kontaktformular des Portfolios läuft über Coderr"]},
{"question": "Wie arbeitest du mit KI?", "expected": ["Wie Benjamin mit KI arbeitet"]},
{"question": "Wie ist dieses System gebaut?", "expected": ["Der Assistent auf dieser Seite"]},
{"question": "Was für Projekte hast du bisher gebaut?", "expected": ["Welche Projekte nach Vorgabe entstanden sind und welche frei"]},
{"question": "Was für Projekte hast du bisher gebaut?", "expected": ["Welche Projekte nach Vorgabe entstanden sind und welche frei", "Welche Projekte Benjamin gebaut hat und mit welchem Stack"]},
{"question": "Was hast du bei Join gemacht?", "expected": ["Join war Teamarbeit zu viert", "Die schwierigste technische Stelle in Join", "Was Join ist"]},
{"question": "Wie deployst du deine Projekte?", "expected": ["Wie neue Versionen live gehen"]},
{"question": "Wie deployst du deine Projekte?", "expected": ["Wie neue Versionen live gehen", "Wie das Portfolio deployt wird"]},
{"question": "Wo wohnst du?", "expected": ["Wo Benjamin lebt und wie er arbeiten möchte"]},
{"question": "Arbeitest du mit Git und Pull Requests?", "expected": ["Wie Benjamin mit Git arbeitet"]},
{"question": "How does Benjamin deploy his projects?", "expected": ["Wie neue Versionen live gehen"]},
{"question": "How does Benjamin deploy his projects?", "expected": ["Wie neue Versionen live gehen", "Wie das Portfolio deployt wird"]},
{"question": "Does he run his own server?", "expected": ["Benjamin betreibt seine Projekte auf einem eigenen Server"]},
{"question": "Where does Benjamin live and is he open to remote work?", "expected": ["Wo Benjamin lebt und wie er arbeiten möchte"]},
{"question": "Why did Videoflix use JWT in cookies?", "expected": ["Warum Videoflix JWT verwendet und nicht Token-Authentifizierung"]},
{"question": "What was the hardest part of the Pokédex?", "expected": ["Wo die eigentliche Schwierigkeit im Pokédex lag"]},
{"question": "How does this assistant work?", "expected": ["Der Assistent auf dieser Seite"]},
{"question": "What did you build with Django?", "expected": ["Was Coderr ist", "Wie Coderr aufgebaut ist", "Die Architektur von Cardelia", "Videoflix, eine Streaming-Plattform mit Hintergrundverarbeitung"]},
{"question": "What did you build with Django?", "expected": ["Was Coderr ist", "Wie Coderr aufgebaut ist", "Die Architektur von Cardelia", "Videoflix, eine Streaming-Plattform mit Hintergrundverarbeitung", "Welche Projekte Benjamin mit Django und Python gebaut hat"]},
{"question": "Welche Projekte hat Benjamin gebaut und mit welchem Stack?", "expected": ["Welche Projekte Benjamin gebaut hat und mit welchem Stack"]},
{"question": "Which technologies has he worked with in his projects?", "expected": ["Welche Projekte Benjamin gebaut hat und mit welchem Stack", "Welche Projekte Benjamin mit Django und Python gebaut hat", "Welche Frontend-Projekte Benjamin gebaut hat"]},
{"question": "Was hat er mit Angular gebaut?", "expected": ["Welche Frontend-Projekte Benjamin gebaut hat", "Was Join ist", "Was das Portfolio ist", "Wie das Portfolio technisch aufgebaut ist"]},
{"question": "Warum stehen nicht alle Projekte im Portfolio?", "expected": ["Warum im Portfolio nur vier Projekte stehen"]},
{"question": "Wie ist dein Portfolio gebaut?", "expected": ["Was das Portfolio ist", "Wie das Portfolio technisch aufgebaut ist"]},
{"question": "Is the portfolio available in English?", "expected": ["Was das Portfolio ist", "Wie das Portfolio technisch aufgebaut ist", "Wie die Zweisprachigkeit des Portfolios funktioniert"]},
{"question": "Wie hat er die Mehrsprachigkeit im Portfolio umgesetzt?", "expected": ["Wie die Zweisprachigkeit des Portfolios funktioniert"]},
{"question": "How does the portfolio handle i18n?", "expected": ["Wie die Zweisprachigkeit des Portfolios funktioniert"]},
{"question": "Was ist die BestellApp?", "expected": ["Was die BestellApp ist"]},
{"question": "Warum hat er zuerst ohne Framework programmiert?", "expected": ["Was Benjamin an der BestellApp über Frameworks gelernt hat", "Welche Frontend-Projekte Benjamin gebaut hat"]},
{"question": "Was ist der Book-Store?", "expected": ["Was der Book-Store ist"]},
{"question": "How does the Book-Store keep likes after a reload?", "expected": ["Wie der Book-Store Daten im Browser speichert", "Was der Book-Store ist"]},
{"question": "Was ist Quizly?", "expected": ["Was Quizly ist"]},
{"question": "Hat Benjamin schon mal eine KI-API in ein Backend eingebaut?", "expected": ["Was Quizly ist", "Wie Quizly die Antwort der KI absichert"]},
{"question": "How does Quizly make sure the AI output is valid?", "expected": ["Wie Quizly die Antwort der KI absichert"]},
{"question": "Schreib mir ein Rezept für Lasagne.", "expected": []},
{"question": "Wie wird das Wetter morgen in Berlin?", "expected": []},
{"question": "Wer hat die Fußball-WM 2014 gewonnen?", "expected": []},
Expand Down
Loading