SegOps AIDocs

Visual Landing Page Builder (M20)

What It Does#

The Visual Landing Page Builder lets non-technical users create branded landing pages through a two-panel interface: an AI chat agent on the left assembles and edits page blocks in response to natural-language instructions; a drag-and-drop canvas on the right shows a live preview of the block composition. Pages can be delivered via a hosted public URL (with secure server-side event tracking) or exported as TSX, HTML, or JSON for self-hosting.

The system builds on the M19b GeneratedPage foundation and adds a full block-based composition model, brand design system management, a reusable page template library, and external product feed ingestion.


Builder UI#

Route: /ai-reach/pages/[id]/builder

The builder is a two-panel layout:

PanelContents
Left — AI ChatStreaming chat with the page builder agent. Natural-language instructions are translated into block operations on the canvas.
Right — CanvasLive block preview. Blocks can be reordered via drag-and-drop (@dnd-kit). Clicking a block opens its property editor.

Additional panels accessible from the builder toolbar:

  • Design System — preview and apply the active brand kit (colors, fonts, spacing tokens)
  • Export — download the page as TSX, HTML with inline CSS, or raw JSON block data

Block operations from chat#

The AI agent can:

  • Add blocks to the page (emit_block)
  • Update block content or properties (update_block)
  • Suggest a template based on the page's query context (suggest_template)
  • Set the page title (set_page_title)
  • Pull products from the PIM or an external feed (fetch_products)

New Page Wizard#

Route: /ai-reach/pages/new

A 3-step wizard for creating a new page:

  1. Choose a starting point — blank canvas, a saved template, or a query-driven auto-generate
  2. Select product source — PIM catalog, product segment, or external product feed
  3. Configure delivery — hosted (delivery app) or API / self-hosted

AI Agent Workflow#

The builder agent is a LangGraph ReAct loop running at POST /api/ai-pages/pages/<id>/build/. It streams results as Server-Sent Events.

Tools#

ToolDescription
fetch_productsRetrieve products from the tenant PIM or a named external feed
fetch_templateLoad a saved page template by ID
list_templatesList available templates for the tenant
fetch_design_systemLoad the active brand design system
fetch_page_contextLoad the current page state (existing blocks, title, query)
emit_blockAppend a new block to the page
update_blockModify an existing block's content or configuration
suggest_templatePick the best matching template for the current query context
set_page_titleSet or update the page title

SSE event types#

data: {"type": "thinking",  "content": "Fetching products for running shoes..."}
data: {"type": "block",     "block": {"type": "hero", "id": "b1", "props": {...}}}
data: {"type": "text",      "content": "Added a hero block with your top product."}
data: {"type": "done"}

Block Types#

The block registry defines 6 canonical block types. Each block has typed props enforced by the registry.

Block TypePurpose
heroFull-width hero with headline, subheadline, CTA button, and optional background image
product_gridGrid of product cards with image, name, price, and link. Supports 2–4 column layouts.
text_sectionRich text block with heading and body copy
cta_bannerCall-to-action strip with headline and button
feature_listIcon + label + description list for product or brand features
testimonialSingle testimonial with quote, author name, and optional avatar

Design System#

Route: /ai-reach/pages/design-system

The design system stores a tenant's brand kit as CSS design tokens. Tokens are injected at render time in both the builder preview and the hosted delivery app.

FieldDescription
primary_colorPrimary brand color (hex)
secondary_colorSecondary/accent color (hex)
background_colorDefault page background (hex)
text_colorBody text color (hex)
heading_fontHeading font family name
body_fontBody font family name
border_radiusButton and card border radius (CSS value)
spacing_unitBase spacing unit in px
custom_cssFree-form CSS appended after generated tokens

Vision extraction#

POST /api/ai-pages/design-system/extract/ accepts an image URL (screenshot, brand asset, or homepage URL). A Claude vision call extracts color palette, font suggestions, and style notes from the image and streams the results as SSE events. The extracted values are pre-populated into the design system form for review before saving.

data: {"type": "extracted", "field": "primary_color", "value": "#1E40AF"}
data: {"type": "extracted", "field": "heading_font",  "value": "Inter"}
data: {"type": "done"}

Page Templates#

Route: /ai-reach/pages/templates

Templates capture a block composition as a reusable starting point. A template stores:

  • Template name and description
  • Ordered block structure (block types + default props)
  • Suggested use case tags

Templates are created from existing pages ("Save as template" in the builder) or via the template library UI. The AI agent's suggest_template tool ranks available templates by cosine similarity to the current page query.


External Product Feeds#

Route: /ai-reach/pages/feeds (admin); API at /api/ai-pages/feeds/

External product feeds let you pull product data from outside the PIM — useful for partner catalogs, staging environments, or Shopify storefronts.

FieldDescription
nameDisplay name for the feed
urlFeed endpoint URL (JSON or XML)
formatjson or xml
field_mapJSON mapping from source fields to canonical product fields
last_synced_atTimestamp of last successful sync

Syncing fetches the remote feed, applies the field map, and upserts products into a feed-scoped staging area (separate from the PIM catalog). The builder's fetch_products tool can target a specific feed by name.

Sync can be triggered manually via POST /api/ai-pages/feeds/<id>/sync/ or on a scheduled Celery beat task (fetch_external_feed).


Export Formats#

POST /api/ai-pages/pages/<id>/export/ queues an async export_page Celery task. Poll status via GET /api/ai-pages/pages/<id>/export/.

FormatDescription
tsxReact component file. Each block renders as a typed component. A <script> tracking snippet is injected automatically.
htmlStatic HTML with inline CSS from design tokens. Tracking snippet auto-injected before </body>.
jsonRaw block array as JSON. No tracking injection — use for headless rendering pipelines.

The tracking snippet in TSX/HTML exports calls the /api/track proxy endpoint using a short-lived JWT page token. The tenant API key is never embedded in the export.


Delivery Methods#

Hosted (delivery app)#

Pages configured with delivery_method: hosted are served by the standalone apps/landing-page-delivery/ Next.js app (port 3002). This app is separate from the control plane.

Public URL pattern: /<tenant_slug>/<page_slug>

The delivery app:

  • Renders blocks server-side using public block renderer components
  • Injects CSS design tokens from the page's design_snapshot
  • Generates a 5-minute JWT page token (signed with DELIVERY_SIGNING_SECRET)
  • Embeds JSON-LD structured data
  • Serves a PageTracker client island for event tracking

API / self-hosted#

Pages with delivery_method: api are fetched via GET /api/ai-pages/public/pages/<tenant>/<slug>/ and rendered by the client. The response includes the blocks array and design_snapshot for client-side rendering.


Secure Event Tracking (Delivery App)#

The delivery app tracks user behavior without exposing the tenant API key to the browser.

Architecture#

Browser
  └── PageTracker (client island)
        └── POST /api/track  (delivery app proxy, same origin)
              └── verifies JWT page token
              └── rate limits (100 req / IP / min)
              └── forwards event via server-side tenant API key
                    └── POST /api/track/  (control plane)

Events tracked#

EventWhen firedExtra fields
page_viewedOn page mountpage_id, tenant_slug, page_slug
block_viewedIntersection Observer ≥50% viewport overlap maintained for dwell thresholdblock_id, block_type, dwell_ms
block_clickedClick on any element inside a blockblock_id, block_type, element

JWT page token#

Generated server-side at render time. Payload:

json
{
  "page_id": 42,
  "tenant_slug": "acme",
  "iat": 1715000000,
  "exp": 1715000300
}

The /api/track proxy verifies the token signature and expiry before forwarding. Expired tokens return 401. The signing secret (DELIVERY_SIGNING_SECRET) is never sent to the browser.


Billing Gate#

The WithinPageLimit permission class blocks page creation when the tenant's published page count reaches the plan limit. Attempts to create beyond the limit return 403 with:

json
{ "detail": "Page limit reached for your current plan." }

Plan limits are defined in the billing configuration and checked on every POST /api/ai-pages/pages/.


Bulk Job Tracking#

Bulk page generation operations (e.g., generating all pages from active queries) run as BulkGenerationJob records.

GET /api/ai-pages/jobs/ — list all bulk jobs for the tenant.

GET /api/ai-pages/jobs/<id>/ — detail with total, completed, failed, and per-page status.

The landing pages list view (/ai-reach/pages) shows a progress bar for any active bulk job.


Data Model Extensions#

GeneratedPage fields added in M20:

FieldTypeNotes
blocksJSONFieldOrdered array of block objects [{id, type, props}]
builder_statusCharFielddraft, building, ready, error
templateFK PageTemplateNullable — template this page was created from
product_sourceCharFieldpim, segment, or feed
delivery_methodCharFieldhosted or api
design_snapshotJSONFieldSnapshot of the design system at publish time
bulk_jobFK BulkGenerationJobNullable — job that created this page

Celery Tasks#

TaskDescription
apps.ai_pages.tasks.generate_page(page_id)Original M19b task — semantic search + Claude Haiku intro copy. Still used for query-driven pages without a builder session.
apps.ai_pages.tasks.bulk_generate_pages_v2(tenant_id, job_id)Creates pages for all active queries under a BulkGenerationJob, updates progress.
apps.ai_pages.tasks.export_page(page_id, format)Renders page to TSX / HTML / JSON, injects tracking snippet, stores result on PageExport.
apps.ai_pages.tasks.fetch_external_feed(feed_id)Fetches and syncs a single ExternalProductFeed.

Evals#

The builder agent is evaluated with an LLM-as-judge pipeline.

  • Dataset: apps/evals/page_builder_golden_dataset.json — 10 test cases covering hero-only, product grid, full page, and brand extraction scenarios
  • Scoring dimensions: helpfulness, accuracy, tool_use, conciseness, plus block_quality (structural pass/fail: correct block types, valid props, no duplicate block IDs)

Run evals locally:

bash
docker compose exec api python manage.py run_page_builder_evals --tenant-slug retailcorp

Dependencies#

  • M19a — embedding pipeline must be running for fetch_products semantic search
  • M19b — GeneratedPage model baseline
  • M13 — PIM product catalog (primary product source)
  • Active AIQueries for query-driven page generation