AI Architectuur
Status
Section titled “Status”| Component | Status | Bestand |
|---|---|---|
| Brand Story (input) | Gebouwd | apps/dashboard/src/components/brand/story-tab.tsx |
| AI Hero Test (PoC) | Gebouwd | apps/api/src/routes/ai.ts · POST /ai/generate-hero |
| Per-Block Regeneratie | Gebouwd | apps/api/src/routes/ai.ts · POST /ai/generate-block |
| Block Schema | Gebouwd | packages/shared/src/ai/block-schema.ts |
| Per-Block UI | Gebouwd | apps/dashboard/src/components/ai-block-generate.tsx |
| Full Page Generatie | Gepland | Zie Verbeterpunten |
| Conversatie Modus | Vision | docs/vision/07-ai-content.md |
Dataflow
Section titled “Dataflow”Per-Block Generatie (huidige implementatie)
Section titled “Per-Block Generatie (huidige implementatie)”┌─────────────┐ POST /ai/generate-block ┌──────────────┐│ Dashboard │ ─────────────────────────────→ │ API Worker ││ │ { siteId, blockType, │ ││ ai-block- │ intent, blockContext } │ 1. Auth check││ generate.tsx│ │ 2. RLS query ││ │ │ ↓ ││ │ │ brand_profiles││ │ │ ↓ ││ │ │ buildBrand ││ │ │ Context() ││ │ │ ↓ ││ │ │ buildBlock ││ │ │ SystemPrompt()││ │ │ + existing ││ │ │ content ││ │ │ + block ││ │ │ context ││ │ │ ↓ ││ │ { block, usage } │ Claude API ││ smart merge │ ←───────────────────────────── │ (tool_use) ││ + enrichment│ │ + Zod validate│└─────────────┘ └──────────────┘Wat er meegestuurd wordt per request:
- Brand context — volledige BrandStory (bedrijfsinfo, doelgroep, tone of voice, missie/visie)
- Block schema — metadata, richtlijnen en voorbeeld voor het specifieke block type
- Block instellingen — layout, kolombreedte, achtergrond type, huidig aantal items (per block type)
- Bestaande content — titels, teksten, buttons, features, FAQ vragen (voor verfijnen vs vervangen)
Smart Merge Strategie
Section titled “Smart Merge Strategie”Na generatie worden AI props slim gemerged met bestaande block props. Het principe: AI wijzigt alleen content, styling blijft onaangeroerd.
Wat de AI WEL wijzigt
Section titled “Wat de AI WEL wijzigt”| Categorie | Velden | Voorbeeld |
|---|---|---|
| Tekst | title.text, subtitle.text, content.html | ”Uw tuin, ons vakmanschap” |
| Button labels | buttons.items[].label, buttons.items[].href | ”Neem contact op” → “#contact” |
| Features | features.items[].icon, .title, .description | icon: “leaf”, title: “Tuinonderhoud” |
| FAQ | items.items[].question, .answer | ”Hoe lang duurt een renovatie?” |
| Kolom tekst | left.text.html, right.text.html | Alleen tekst-kolommen |
| Layout keuzes | layout, columns, maxWidth, alignment | center, 3, prose, left |
Wat de AI NIET wijzigt (altijd behouden)
Section titled “Wat de AI NIET wijzigt (altijd behouden)”| Categorie | Velden | Reden |
|---|---|---|
| Spacing | spacing.top, spacing.bottom | Visuele layout |
| Achtergrond | background (color, gradient, image, video) | Ontwerp keuze |
| Hoeken | corners.top, corners.bottom | Ontwerp keuze |
| Tekstkleur modus | textColorMode | Afgestemd op achtergrond |
| Button styling | style, color, colorToken, page_id | Behoud bestaande kleuren |
| Button stijl | buttonStyle, buttonColor, buttonColorToken | Block-level button settings |
| Afbeeldingen | Kolommen met type: 'image' | Media koppeling |
Button Merge
Section titled “Button Merge”Buttons worden per positie gemerged:
- Button 1 bestaat → AI mag label/href wijzigen, style/color/colorToken komen van bestaande button
- Button 2 is nieuw (AI voegt toe) → krijgt default:
style: 'solid',color: '#6e61e7',colorToken: 'primary' - AI laat button weg → button verdwijnt (AI beslist dat hij niet nodig is)
Enrichment na Merge
Section titled “Enrichment na Merge”Alle AI-gegenereerde items worden verrijkt met ontbrekende velden:
- Buttons:
id,color,colorToken,style(defaults als niet van bestaand) - Feature items:
id - FAQ items:
id - Null items worden gefilterd uit arrays
Verfijnen vs Vervangen
Section titled “Verfijnen vs Vervangen”De AI bepaalt automatisch of content verfijnd of vervangen moet worden, op basis van de prompt:
| Prompt | Gedrag | Voorbeeld |
|---|---|---|
| Aanpassing vragen | Verfijnen — behoud structuur, pas aan wat gevraagd wordt | ”maak de titel korter”, “andere toon”, “voeg een punt toe” |
| Nieuw onderwerp | Vervangen — genereer volledig nieuwe content | ”maak hier een FAQ over verzendkosten”, “hero voor een kapper” |
| Onduidelijk | Verfijnen — behoud wat werkt, verbeter wat gevraagd wordt | ”verbeter dit block” |
Dit werkt doordat de AI de bestaande content ziet in de prompt (## Huidige Content) en de instructie krijgt:
“Bij twijfel: verfijn. Behoud wat werkt, verbeter wat gevraagd wordt.”
Per-Block Style Context
Section titled “Per-Block Style Context”Elk block type stuurt relevante layout-informatie mee zodat de AI de content aanpast aan de beschikbare ruimte:
| Block | Context die de AI krijgt |
|---|---|
| hero | Layout (center/left/right), achtergrond type (image/video → kortere tekst), huidig aantal buttons |
| features | Kolomindeling (2/3/4 → ideaal aantal items), huidig aantal items |
| cta | Layout (center/side-by-side → tekst lengte), achtergrond type |
| text | Tekstbreedte (prose/md/lg/full → leeslengte), uitlijning (center → kortere alinea’s) |
| faq | Huidig aantal vragen (→ genereer vergelijkbaar aantal) |
| columns | Grid ratio (bijv. 8/12 + 4/12), kolom types (tekst/afbeelding), waarschuwing bij smalle kolommen |
| buttons | Uitlijning, huidig aantal buttons |
Brand Context Opbouw
Section titled “Brand Context Opbouw”De AI system prompt bevat drie lagen:
1. Statische regels
Section titled “1. Statische regels”Vast onderdeel van elke prompt. Bevat schrijfregels, cliché-lijst, verfijn/vervang instructie, output regels.
2. Block schema + context
Section titled “2. Block schema + context”Per block type: beschrijving, richtlijnen, voorbeeld. Plus layout-context en bestaande content.
3. Brand context (dynamisch per site)
Section titled “3. Brand context (dynamisch per site)”Gebouwd uit brand_profiles data door buildBrandContext():
| Bron | Velden | Prompt Sectie |
|---|---|---|
onboarding_data (BrandStory) | companyName, slogan, description, industry, foundedYear | ## Brand Context |
onboarding_data (BrandStory) | targetAudience, businessType, region, differentiator, CTA’s | ## Brand Context |
onboarding_data (BrandStory) | mission, vision, coreValues | ## Brand Context |
onboarding_data (BrandStory) | addressForm, toneFormal/Serious/Technical, toneDescription | ## Tone of Voice |
mood_preset | Naam + tone beschrijving | ## Tone of Voice (fallback) |
onboarding_data (BrandStory) | forbiddenWords | ## Verboden Woorden |
onboarding_data (BrandStory) | email, phone, address | ## Contactgegevens |
Prioriteit: Expliciete BrandStory velden overschrijven mood preset.
Lege velden: Worden overgeslagen. Alleen ingevulde velden komen in de prompt.
Tone sliders: Vertaald naar concrete schrijfinstructies:
| Slider | < 0.3 | 0.3–0.5 | 0.5–0.7 | > 0.7 |
|---|---|---|---|---|
| Formeel | Zeer informeel en losjes | Informeel maar professioneel | Zakelijk maar benaderbaar | Formeel en professioneel |
| Serieus | Speels en luchtig, humor mag | Licht en positief | Serieus maar niet zwaar | Serieus en feitelijk |
| Technisch | Eenvoudig, vermijd vakjargon | Toegankelijk, leg termen uit | Vakkundig, jargon is oké | Technisch en vakspecifiek |
Security
Section titled “Security”| Maatregel | Doel | Implementatie |
|---|---|---|
sanitize() | Prompt injection preventie | Strip \r\n\u2028\u2029 + unicode control chars, max lengte per veld |
| Brand context max 3000 chars | Token cost control | .slice(0, 3000) voor insert in system prompt |
| Zod input validatie | Reject ongeldige requests | siteId: uuid, prompt: 5-2000 chars, blockType: enum |
| Zod output validatie | Reject ongeldige AI output | Discriminated union per block type |
| IDOR check | Voorkom toegang tot andere sites | RLS + expliciete 404 als brand niet gevonden |
| Rate limit | Kosten beperken | 10 req/min per user (apart van algemene 100/min) |
| AbortController | Voorkom hangende requests | 25s timeout op Anthropic API calls |
| Geen details naar client | Schema-discovery preventie | Zod errors niet in response, alleen generiek bericht |
| API key server-side | Secret bescherming | ANTHROPIC_API_KEY alleen in Worker env, nooit in frontend |
Kosten
Section titled “Kosten”| Component | Tokens | Kosten (Sonnet) |
|---|---|---|
| System prompt (regels + block schema) | ~2.000 | ~$0.006 |
| Brand context + bestaande content | ~500-800 | ~$0.002 |
| User prompt | ~50 | ~$0.0002 |
| Input totaal | ~2.800 | ~$0.009 |
| Output (per-block) | ~300-600 | ~$0.008 |
| Output (hero: 3 varianten) | ~300 | ~$0.005 |
| Totaal per-block | ~3.400 | ~$0.017 |
| Totaal hero varianten | ~3.100 | ~$0.014 |
Model is configureerbaar via AI_MODEL env var. Default: claude-sonnet-4-20250514.
Toekomstige Architectuur
Section titled “Toekomstige Architectuur”Full Page Generatie (volgende stap)
Section titled “Full Page Generatie (volgende stap)”- Hergebruikt: block schema, brand context, Zod validatie, enrichment
- Nieuw:
aiGeneratePageSchema(al gebouwd), paginastructuur patronen in prompt,POST /ai/generate-page - UI: modal via toolbar met “Vervang pagina” / “Voeg toe” keuze
Conversatie Modus
Section titled “Conversatie Modus”- Hergebruikt: alles + multi-turn messages
- Nieuw: conversatie state management, chat UI
- Beschreven in:
docs/vision/07-ai-content.md
Alle toekomstige features hergebruiken dezelfde fundamenten: block schema, Zod validatie, smart merge, enrichment, brand context, tone mapping.
Gerelateerde Documentatie
Section titled “Gerelateerde Documentatie”- AI Block Schema — Velden, voorbeelden en verbeterpunten per block type
- Functionaliteit — Brand Story — Story tab UI en velden
- Functionaliteit — AI Content Generatie — Feature beschrijving
- Data Architectuur — brand_profiles — BrandStory velden in de database
- Vision — AI Content Generatie — Volledige toekomstvisie (5 fasen)
- Technische Stack — API Routes — AI endpoints