Skip to content

AI Architectuur

ComponentStatusBestand
Brand Story (input)Gebouwdapps/dashboard/src/components/brand/story-tab.tsx
AI Hero Test (PoC)Gebouwdapps/api/src/routes/ai.ts · POST /ai/generate-hero
Per-Block RegeneratieGebouwdapps/api/src/routes/ai.ts · POST /ai/generate-block
Block SchemaGebouwdpackages/shared/src/ai/block-schema.ts
Per-Block UIGebouwdapps/dashboard/src/components/ai-block-generate.tsx
Full Page GeneratieGeplandZie Verbeterpunten
Conversatie ModusVisiondocs/vision/07-ai-content.md

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:

  1. Brand context — volledige BrandStory (bedrijfsinfo, doelgroep, tone of voice, missie/visie)
  2. Block schema — metadata, richtlijnen en voorbeeld voor het specifieke block type
  3. Block instellingen — layout, kolombreedte, achtergrond type, huidig aantal items (per block type)
  4. Bestaande content — titels, teksten, buttons, features, FAQ vragen (voor verfijnen vs vervangen)

Na generatie worden AI props slim gemerged met bestaande block props. Het principe: AI wijzigt alleen content, styling blijft onaangeroerd.

CategorieVeldenVoorbeeld
Teksttitle.text, subtitle.text, content.html”Uw tuin, ons vakmanschap”
Button labelsbuttons.items[].label, buttons.items[].href”Neem contact op” → “#contact”
Featuresfeatures.items[].icon, .title, .descriptionicon: “leaf”, title: “Tuinonderhoud”
FAQitems.items[].question, .answer”Hoe lang duurt een renovatie?”
Kolom tekstleft.text.html, right.text.htmlAlleen tekst-kolommen
Layout keuzeslayout, columns, maxWidth, alignmentcenter, 3, prose, left
CategorieVeldenReden
Spacingspacing.top, spacing.bottomVisuele layout
Achtergrondbackground (color, gradient, image, video)Ontwerp keuze
Hoekencorners.top, corners.bottomOntwerp keuze
Tekstkleur modustextColorModeAfgestemd op achtergrond
Button stylingstyle, color, colorToken, page_idBehoud bestaande kleuren
Button stijlbuttonStyle, buttonColor, buttonColorTokenBlock-level button settings
AfbeeldingenKolommen met type: 'image'Media koppeling

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)

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

De AI bepaalt automatisch of content verfijnd of vervangen moet worden, op basis van de prompt:

PromptGedragVoorbeeld
Aanpassing vragenVerfijnen — behoud structuur, pas aan wat gevraagd wordt”maak de titel korter”, “andere toon”, “voeg een punt toe”
Nieuw onderwerpVervangen — genereer volledig nieuwe content”maak hier een FAQ over verzendkosten”, “hero voor een kapper”
OnduidelijkVerfijnen — 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.”


Elk block type stuurt relevante layout-informatie mee zodat de AI de content aanpast aan de beschikbare ruimte:

BlockContext die de AI krijgt
heroLayout (center/left/right), achtergrond type (image/video → kortere tekst), huidig aantal buttons
featuresKolomindeling (2/3/4 → ideaal aantal items), huidig aantal items
ctaLayout (center/side-by-side → tekst lengte), achtergrond type
textTekstbreedte (prose/md/lg/full → leeslengte), uitlijning (center → kortere alinea’s)
faqHuidig aantal vragen (→ genereer vergelijkbaar aantal)
columnsGrid ratio (bijv. 8/12 + 4/12), kolom types (tekst/afbeelding), waarschuwing bij smalle kolommen
buttonsUitlijning, huidig aantal buttons

De AI system prompt bevat drie lagen:

Vast onderdeel van elke prompt. Bevat schrijfregels, cliché-lijst, verfijn/vervang instructie, output regels.

Per block type: beschrijving, richtlijnen, voorbeeld. Plus layout-context en bestaande content.

Gebouwd uit brand_profiles data door buildBrandContext():

BronVeldenPrompt 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_presetNaam + 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.30.3–0.50.5–0.7> 0.7
FormeelZeer informeel en losjesInformeel maar professioneelZakelijk maar benaderbaarFormeel en professioneel
SerieusSpeels en luchtig, humor magLicht en positiefSerieus maar niet zwaarSerieus en feitelijk
TechnischEenvoudig, vermijd vakjargonToegankelijk, leg termen uitVakkundig, jargon is okéTechnisch en vakspecifiek

MaatregelDoelImplementatie
sanitize()Prompt injection preventieStrip \r\n\u2028\u2029 + unicode control chars, max lengte per veld
Brand context max 3000 charsToken cost control.slice(0, 3000) voor insert in system prompt
Zod input validatieReject ongeldige requestssiteId: uuid, prompt: 5-2000 chars, blockType: enum
Zod output validatieReject ongeldige AI outputDiscriminated union per block type
IDOR checkVoorkom toegang tot andere sitesRLS + expliciete 404 als brand niet gevonden
Rate limitKosten beperken10 req/min per user (apart van algemene 100/min)
AbortControllerVoorkom hangende requests25s timeout op Anthropic API calls
Geen details naar clientSchema-discovery preventieZod errors niet in response, alleen generiek bericht
API key server-sideSecret beschermingANTHROPIC_API_KEY alleen in Worker env, nooit in frontend

ComponentTokensKosten (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.


  • 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
  • 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.