Dokumentation

Technische Dokumentation der Pammys-Produktsuche: Architektur, n8n-Aufbau, Matching-Logik, Daten-Contract, Dashboard und Abgabe. Dieselbe Doku liegt als README.md im Repository.

Überblick

Die Anwendung nimmt freie Texteingaben entgegen (z. B. snowboots nightfall 38, auch mit Tippfehlern) und erkennt entweder genau eine Produktvariante oder stellt eine gezielte Rückfrage nach Größe, Farbe oder Modell. Die Antwort formuliert anschließend eine KI in vier Sprachen.

Sie besteht aus einem n8n-Workflow für Verarbeitung und Produkterkennung (im Code, ohne KI) und einem React-Dashboard (Next.js) für Eingabe, Ergebnisanzeige mit Bildern und Verlauf.

Besonderheit: Modell, Farbe und Größe stehen gemeinsam in einem Titelfeld, z. B. Snowboots 2.0 - Nightfall - 38, und müssen erst daraus gelöst und robust abgeglichen werden.

Architektur & Datenfluss

Das Dashboard spricht n8n nie direkt an, sondern über zwei serverseitige Next.js-Proxy-Routes (/api/ask, /api/history). Das hält die Webhook-URLs auf dem Server und vermeidet CORS.

Browser (Dashboard)
Produktsuche
POST /api/ask
{ query, locale }
Next.js Proxy-Route
versteckt Webhook-URL, kein CORS
n8n: Webhook (POST)
Get products (Data Table)
Match (Code Node, KI-frei)
einer von 4 Fällen + results
AI Agent (Anthropic)
message { de, en, es, fr }
Respond (Merge)
HTTP-Antwort an das Dashboard
Log to Sheet (Google Sheets, eine Zeile pro Anfrage)
Verlauf
GET /api/history
kein Body
Next.js Proxy-Route
n8n: Webhook
GET /pammys-history
Google Sheets (Read)
Respond
JSON-Array aller Zeilen

Eine Anfrage durchläuft folgende Schritte:

  1. Der Nutzer gibt im Dashboard einen freien Text ein.
  2. Das Dashboard ruft /api/ask auf, die Route reicht { query, locale } an den n8n-Webhook weiter.
  3. Get products lädt die Data Table, Match (Code Node) zerlegt die Titel und entscheidet genau einen von vier Fällen.
  4. Der AI Agent formuliert daraus die Antwort in vier Sprachen.
  5. Respond schickt die HTTP-Antwort und schreibt parallel eine Zeile nach Google Sheets.
  6. Der Verlauf wird über einen zweiten Workflow (/api/history) ausgelesen.

Tech-Stack

  • Dashboard: Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS v4, shadcn/ui, next-intl, motion.
  • Automation: n8n (self-hosted), Data Table, Code Node, AI Agent mit Anthropic claude-haiku-4-5, Google Sheets.
  • Bindeglied: zwei n8n-Webhooks (POST für die Suche, GET für den Verlauf) über Next.js-Routes.

Lokales Setup

Voraussetzungen: Node.js 22+, pnpm (npm/yarn gehen auch), Zugriff auf die aktive n8n-Instanz.

pnpm install
# .env.local anlegen, Variablen siehe unten
pnpm dev                    # http://localhost:3000
pnpm build && pnpm start    # Produktions-Build
pnpm lint                   # ESLint

Ohne gesetzte Webhook-Variablen liefern die Proxy-Routes kontrollierte Fehlerzustände statt eines Absturzes.

Environment-Variablen

In .env.local (nicht eingecheckt). Beide Variablen werden ausschließlich serverseitig gelesen.

  • N8N_WEBHOOK_URL (POST): Hauptworkflow für die Produktsuche, erhält { query, locale }.
  • N8N_HISTORY_URL (GET): Verlaufs-Workflow, liefert alle Sheet-Zeilen als JSON-Array.

Datenmodell

Grundlage ist die CSV data/Pammys Products.csv mit 890 Zeilen und den Spalten status, product_title, image_url. Diese werden in die Data Table pammys_products übernommen; die Erkennung läuft auf deren Basis.

Die automatische id (1 bis 890) dient als Produkt-ID. Modell, Farbe und Größe entstehen zur Laufzeit durch Splitten des Titels am Trenner - :

"Snowboots 2.0 - Nightfall - 38"
   ->  model = "Snowboots 2.0"
       color = "Nightfall"
       size  = "38"

Größen können auch Bereiche sein (z. B. 40/41).

Matching-Logik

Die komplette Logik (Matching, Entscheidung, Optionen) passiert im Code, nicht in der KI. Das macht sie deterministisch und testbar. Kernbausteine:

  • Normalisierung: Kleinschreibung, Akzente entfernt, ß zu ss, Sonderzeichen raus.
  • Tippfehler-Toleranz: Levenshtein, Treffer ab Ähnlichkeit 0.8 (snowbots trifft Snowboots).
  • N-Gramme: benachbarte Tokens werden zusammengezogen, sodass snow boots auf Snowboots matcht.
  • Farb-Synonyme: Deutsch und Englisch werden vereinheitlicht (schwarz = black).
  • Größen-Bereiche: eine Eingabe 40 trifft auch die Range 40/41.

Je Modell wird ein Score gebildet. Ein Modell gilt als eindeutig, wenn sein Score hoch genug ist und klar über dem zweitbesten liegt; sonst folgt eine Modell-Rückfrage. Danach wird analog über Farbe und Größe entschieden. Zusätzlich liefert der Node immer bis zu vier Produkte mit image_url in results, damit das Dashboard immer Bilder zeigen kann.

Entscheidungslogik & Contract

Request an den Webhook: POST mit Body { query, locale }. Die Antwort ist genau einer von vier Fällen, jeweils ergänzt um results (bis zu vier Produkte mit Bild) und message mit den vier Sprachen.

A) Eindeutiger Match

Genau eine Variante wurde eindeutig identifiziert.

{
  "type": "match",
  "product": { "id": 524, "title": "Snowboots 2.0 - Nightfall - 38", "image_url": "..." },
  "context": { "model": "Snowboots 2.0", "color": "Nightfall", "size": "38" },
  "results": [ { "id": 524, "title": "...", "model": "...", "color": "...", "size": "38", "image_url": "..." } ],
  "message": { "de": "...", "en": "...", "es": "...", "fr": "..." }
}

B) Größe fehlt (missing_size)

Modell und Farbe sind klar, es existieren aber mehrere Größen.

{
  "type": "question",
  "question_key": "missing_size",
  "options": ["36", "37", "38", "39", "40", "41", "42", "43"],
  "context": {
    "model": "Snowboots 2.0",
    "color": "Nightfall",
    "matched_candidates": [ { "id": 522, "title": "Snowboots 2.0 - Nightfall - 36", "image_url": "..." } ]
  },
  "results": [ ... ],
  "message": { "de": "...", "en": "...", "es": "...", "fr": "..." }
}

C) Farbe fehlt (missing_color)

Das Modell ist klar, es existieren aber mehrere Farben.

{
  "type": "question",
  "question_key": "missing_color",
  "options": ["Beige", "Hellblau", "Rosa", "Schwarz", "Weiss"],
  "context": {
    "model": "Belt 2025",
    "matched_candidates": [ { "color": "Beige", "image_url": "..." } ]
  },
  "results": [ ... ],
  "message": { "de": "...", "en": "...", "es": "...", "fr": "..." }
}

D) Modell unklar (ambiguous_model)

Kein Modell eindeutig, oder mehrere passen ähnlich gut.

{
  "type": "question",
  "question_key": "ambiguous_model",
  "options": [ { "model": "Snowboots 2.0", "score": 0.82 } ],
  "context": { "top_candidates": [ { "id": 1, "title": "...", "image_url": "..." } ] },
  "results": [ ... ],
  "message": { "de": "...", "en": "...", "es": "...", "fr": "..." }
}

Sonderfall „nichts erkannt“

Bei einer vagen Eingabe wie asdf qwer kommt ambiguous_model mit leeren options, ohne context.model und mit leeren top_candidates. Der Agent benennt kein Produkt, das Dashboard zeigt einen neutralen Zustand statt erfundener Treffer.

AI-Agent

Der Agent ist ausschließlich für die Sprache zuständig, nicht für die Logik. Er erhält eine reduzierte Projektion (type, question_key, options, product, context) und gibt eine Antwort als { de, en, es, fr } zurück. Die Liste results sieht er bewusst nicht, damit er bei nicht erkannter Eingabe keine Produktnamen zur Hand hat.

Verbindliche Formulierungsregeln pro Fall:

  • match: kurze Bestätigung mit dem vollständigen product.title.
  • missing_size: Frage nach der Größe, nennt Modell und Farbe, listet die Optionen.
  • missing_color: Frage nach der Farbe, nennt das Modell, listet die Optionen.
  • ambiguous_model: Frage nach dem Modell, nennt die Modelle aus options.

Außerdem: nichts erfinden, nur Werte aus dem JSON verwenden, keine neuen Optionen, Namen unverändert lassen, kurzer freundlicher Ton, keine Gedankenstriche.

Logging & Verlauf

Jede Anfrage wird als Zeile in die Google-Sheets-Tabelle „Pammys Produktsuche - Verlauf“ geschrieben (Service-Account). Spalten:

timestamp, query, type, question_key, status, reply,
product_id, product_title, model, color, size,
results_json, raw_json

results_json und raw_json enthalten die vollständigen Strukturen als JSON-String, sodass die Detailansicht Kandidaten samt Bildern rekonstruieren kann.

Ein zweiter Workflow „Pammys History“ liefert den Verlauf: Webhook (GET /pammys-history)Google Sheets (Read)Respond.

Dashboard

1. Produkteingabe

  • Eingabefeld für eine freie Produktbeschreibung, schrittweiser Ablauf.
  • Anzeige des erkannten Produkts bzw. der Rückfrage des Agenten in der gewählten Sprache.

2. Produktanzeige mit Bildern

  • Produktbilder aus image_url in allen Fällen, auch wenn nur das Modell oder mehrere Kandidaten erkannt wurden.
  • Mehrere Kandidaten erscheinen übersichtlich als Grid neben- und untereinander.
  • Beim eindeutigen Treffer zusätzlich Produkttitel, Modell, Farbe und Größe.
  • Rückfragen verfeinern die Suche: Die Auswahl wird an die Query angehängt und neu gefragt.

3. Chat- / Eingabeverlauf

  • Liste aller Anfragen unter /historie, neueste zuerst, je Eintrag mit Datum, Uhrzeit, Preview und Status.
  • Einträge sind anklickbar.
  • Die Detailseite /historie/[id] zeigt das vollständige Ergebnis: gefundenes Produkt bzw. Kandidaten, jeweils mit Bild.

Mehrsprachigkeit

Das Dashboard unterstützt Deutsch, Englisch, Spanisch und Französisch über next-intl. Die UI-Texte liegen in messages/, diese Doku in src/lib/doc-content.ts, die Sprache wird im Header umgeschaltet. Auch die Antwort des Agenten folgt der UI-Sprache, da er alle vier Sprachen liefert und das Frontend die passende auswählt.

Tests

Die Matching-Logik des Code Nodes ist 1:1 als lokaler Harness gegen die echte CSV ausführbar, ohne laufendes n8n:

node scripts/match-harness.mjs

Abgedeckt sind die vier Fälle, Tippfehler (snowbots nightfal 38), getrennt geschriebene Modellnamen (snow boots nightfall 38) sowie der „nichts erkannt“-Fall.

Projektstruktur

src/app/page.tsx                 Einstieg -> SearchFlow
src/app/api/ask/route.ts         POST-Proxy auf N8N_WEBHOOK_URL
src/app/api/history/route.ts     GET-Proxy auf N8N_HISTORY_URL
src/app/historie/page.tsx        Verlaufsliste
src/app/historie/[id]/page.tsx   Verlaufs-Detailansicht
src/app/dokumentation/page.tsx   Diese Dokumentationsseite
src/components/search-flow.tsx   Zustandsmaschine der vier Faelle
src/components/match-result.tsx  Anzeige des eindeutigen Treffers
src/components/product-grid.tsx  Kandidaten-Grid mit Bildern
src/lib/history.ts               Mapping der Sheet-Zeilen auf Verlaufseintraege
src/lib/doc-content.ts           Inhalt dieser Seite (DE/EN/ES/FR)
src/i18n/, messages/             Mehrsprachigkeit DE/EN/ES/FR
data/Pammys Products.csv         Quelle der Data Table (890 Zeilen)
scripts/match-harness.mjs        Lokaler Test der Match-Logik gegen die CSV
n8n/                             Workflow-JSON-Exporte (Haupt + History)

Abgabe

  • Privates GitHub-Repository mit Leserecht für lp@dieseo.de.
  • n8n-Workflows als JSON-Export im Ordner n8n/ (Hauptworkflow und History-Workflow).
  • Google Sheet „Pammys Produktsuche - Verlauf“ mit mindestens Betrachterzugriff für lp@dieseo.de:
Google Sheet öffnen