AI Features (Ollama / OpenClaw / unDrifter)
Optional local LLM integration for zone naming, semantics enrichment, and clean-structure. Requires the unDrifter server with either Ollama (OLLAMA_ENABLED=true, Ollama running, e.g. ollama run <your-tag> matching OLLAMA_MODEL) or OpenClaw (OPENCLAW_ENABLED=true, OPENCLAW_URL set). All enrich endpoints use the active backend.
---
Server API
| Endpoint | Description | |
| ---------- | ------------- | |
GET /enrich/status | Active provider (ollama \ | openclaw), config (enabled, model/host or url, enrichWithVision). Use to show "AI: ready" or "AI: unavailable" in UIs. |
GET /enrich/test | Ping active backend (Ollama or OpenClaw, 15s timeout). Verifies the model responds. | |
POST /enrich/zone-name | Suggest a short zone name (2–4 words) from content. Body: { children: ChildNode[], pageTitle?: string }. Returns { success, data?: { suggestedName }, error? }. | |
POST /enrich/variable-names | Suggest variable name parts (e.g. hero_headline, card_title) for items. Body: { items: [{ zoneName, child }] }. Returns { success, data?: { names: string[] }, error? }. Same order as items. | |
POST /enrich/block-types | Suggest content block type (h1, p, list, link, etc.) from descriptions. Body: { blocks: [{ name?, text }] }. Returns { success, data?: { types: string[] }, error? }. Same order as blocks. | |
POST /enrich/semantics | Enrich children with role, component, annotation (for selectors like hero>p). Body: { children: ChildNode[], screenshot?: string, enrichWithVision?: boolean, zoneId?: string, instanceId?: string, forceReEnrich?: boolean }. When enrichWithVision is true and a screenshot is present, uses vision model. When forceReEnrich is true, always runs the LLM (do not skip when most children already have script semantics), so AI-inferred names (e.g. date-picker, main-nav) can override script labels. Returns { success, data: { children } }. | |
POST /enrich/clean-structure | Clean structure and dedupe: atomic design levels, semantic component names (from vocabulary), flatten redundant groups, merge/remove duplicate nodes. Body: { children: ChildNode[] }. Returns { success, data: { children, changes? } }. Prompt is built from semantic vocabulary (data/semantic-vocabulary.json) and merged examples (base corpus + user examples). | |
GET /enrich/structure-examples | Returns good/bad structure examples (user-submitted only; base corpus is read-only). Used for few-shot in clean-structure. | |
POST /enrich/structure-examples | Submit a before/after pair as good or bad. Body: { kind: "good" \ | "bad", childrenBefore: ChildNode[], childrenAfter: ChildNode[] }. Stored in server/data/structure-examples.json (up to 10 good, 5 bad). Merged with base corpus when building the clean-structure prompt. |
POST /enrich/chat/mosme | MOSME — Dual-expert dialogue (Ollama only; two model env vars). Body: { message: string, rounds?: number, expertLead?: string, expertPartner?: string }. Experts: copywriter, frontend, architect, sales, information_architecture, visual_designer. Omit both expert fields for hub defaults (IA + Architect); expertPartner without expertLead is 400. Returns { success, data: { transcript, rounds, expertLead, expertPartner } }; each assistant turn includes speakerLabel and expertId. | |
POST /enrich/chat | Gwen Chat v2 — Single-turn chat with Gwen (design-systems expert). Body: { message: string, image?: string }. When image is present (base64 or data URL), uses vision model to break down the design into structure only (regions, components, slots, atomic levels); no copy extraction. Text-only path uses a teaching system prompt (atomic design, vocabulary). Returns { success, data: { reply } } or 503 if vision requested but no vision model configured. | |
POST /enrich/create-zone | Create a zone from a natural-language description (no scrape). Body: { description: string, zoneName?: string }. Gwen returns JSON (zoneName + children); server creates a zone with source.url: "conversation" and appends to CopyDoc. | |
POST /enrich/create-zone-from-reply | Create a zone from Gwen's reply text. Body: { replyContent: string, instanceId?: string }. Server parses reply for a JSON object with zoneName (or name) and children (same shape as create-zone); creates the zone and returns { success, data: { zone } }. Returns 422 with "No valid zone JSON found in reply" if none found. | |
POST /enrich/figma-evaluate | OpenClaw only. Ask OpenClaw to evaluate the current Figma file (design-system, tokens, accessibility). Body: { fileKey: string, fileName?: string }. Optional query: timeoutMs (default 90000). Returns 501 when provider is not OpenClaw. Install the Figma Design Toolkit skill in OpenClaw for full analysis. | |
GET /logs | Server log buffer (last 500 lines). Used by the landing page Logs tab for debugging. Returns { lines: { t, level, msg }[], maxLines: 500 }. |
---
Landing page (https://localhost:3001)
The root route serves a single-page UI with: Home, Purpose, Zones, Docs, API, Logs. Logs fetches GET /logs and shows the in-memory server log (console output); optional auto-refresh every 2s. Gwen Chat v2 (design-systems expert): optional image (upload or paste) for "How'd they make this?" — Gwen breaks down the design into structure only (no copy extraction). Suggested prompts (concept chips) above the input; Create zone from Gwen's reply calls POST /enrich/create-zone-from-reply with the last Gwen reply; each Gwen message has Copy full and, when the reply contains zone JSON, Create zone from this. Vision requires OLLAMA_VISION_MODEL (or equivalent) in server config.
---
Vocabulary and base corpus (clean-structure)
Clean-structure uses a semantic vocabulary and merged good/bad examples so the model stays consistent and learnable.
- Vocabulary —
server/data/semantic-vocabulary.json(committed). Defines: topLevel: semantic names for top-level groups (hero, intro, banner, section, card, nav, footer)slots: allowed names for children inside a group (headline, body, cta, list, image, caption, label)nestedGroups: names for groups inside another group (block, list-group)atomicLevels: atom, molecule, organism, template, pagecomponents: intent-based component names (date-picker, main-nav, account-nav, search-bar, breadcrumb, tabs, accordion, modal) used in semantics prompts for reusable, intent-recognizable naming
- Base corpus —
server/data/base-structure-corpus.json(committed, read-only). Same shape as structure-examples:{ "good": [], "bad": [] }with{ "input": string, "output": string }entries. Seed examples here are always included first in the clean-structure few-shot prompt. - User examples —
server/data/structure-examples.json(user-submitted via Feed Gwen good/bad). Merged with base corpus:[...baseCorpus.good, ...userExamples.good](and same for bad), then capped. So base examples are preferred; user examples add per-user patterns.
---
AI-inferred naming
Naming (role, component, annotation) is inferred by AI when enrichment is used, yielding reusable patterns recognizable by intent (e.g. date-picker, main-nav, account-nav) instead of script-only labels from class names and container selectors. The semantics prompt injects the semantic vocabulary (topLevel + components) and instructs the model to prefer those names when they fit and to use intent-based kebab-case for other UI patterns. Script semantics from extraction are treated as hints; clients can send forceReEnrich: true so the LLM always runs and can override script labels. The extension can run semantics on capture when "AI enrichment on capture" is on and a screenshot is available (after clean-structure), so new zones get AI-inferred names at intake.
---
Implemented
Extension
- Describe (Gwen) — On the Add zone? crop confirmation modal, Describe (Gwen) captures a screenshot of the crop and calls
POST /enrich/chatwith a vision prompt (same Gwen path as the landing page). Requires a vision model on the server (OLLAMA_VISION_MODELor OpenClaw with vision). Does not add a zone; use Add Zone to capture as usual. - Zone name suggestion — When adding a zone (draw/confirm), extension can call
/enrich/zone-nameand pre-fill a suggested name (if AI enrichment is enabled). - Structure naming at intake — When "AI enrichment" is on and you add a zone, the extension calls
POST /enrich/clean-structure, then when a screenshot is available also callsPOST /enrich/semanticswith the screenshot andforceReEnrich: true. New zones get optimal structure naming plus AI-inferred semantics (e.g. date-picker, main-nav) at capture. Same clean-structure logic as the plugin's "Clean with AI" in the Structure tab. - Semantics on capture — When "AI enrichment on capture" is on and a screenshot was taken, the extension sends children + screenshot to
POST /enrich/semantics(via ENRICH_SEMANTICS) so zones get intent-based component/role names from the vision model when configured.
Plugin
- AI status — Plugin fetches
GET /enrich/statusand shows "AI: ✓ ready" or "AI: unavailable" so users know whether Suggest name / Enrich will work. - Suggest zone name — In "Start from blank", a "Suggest name" button sends the current content blocks to
/enrich/zone-nameand fills the zone name field with the suggested name (requires at least one block with name + text). - AI variable names — When creating variables (Vars button), the plugin calls
POST /enrich/variable-namesand uses returned names (e.g. hero_headline, card_title); falls back to heuristic if Ollama is disabled. - Enrich zone from Figma — Each zone in the outline has an "Enrich" button: sends that zone's children to
POST /enrich/semanticswithforceReEnrich: trueso the LLM always runs and AI-inferred names override script labels, thenPUT /zones/:idwith updated children. Refreshes outline so semantics show in the tree. Enrich with vision (Settings) sendsenrichWithVision: trueandzoneIdso the server loads the zone screenshot and uses the vision model for semantics when a vision-capable tag is configured (multimodalOLLAMA_MODEL, orOLLAMA_VISION_MODEL). - Suggest block types (Blank) — In "Start from blank", "Suggest types" sends each block's description to
POST /enrich/block-typesand fills the name/type field (h1, p, list, link, etc.). Requires at least one block with description. - Search by semantics — "Search by path" uses enriched
role/component: single-word queries (e.g.hero,cta,card) match children by semantics; paths likecard>headingorhero>p(1)recurse into groups and match by container + type/semantics. Uses.includes()so "hero" matches "hero-banner", etc. - Structure view — "Structure" tab: expandable/collapsible semantic tree (parent-child) for a selected zone. Clean with AI calls
POST /enrich/clean-structure(uses semantic vocabulary and merged base + user examples). Apply saves the cleaned children (includingsemantics.atomicLevel) to the zone. Discard reverts. Manual edit — each node has an "Edit" button to set component name and atomic level (then Apply). Feed Gwen (good) / Feed Gwen (bad) — submit before/after as a user example (stored instructure-examples.json; merged with base corpus for few-shot). Top-level groups start expanded; chevrons expand/collapse nodes. - Evaluate Figma file (OpenClaw) — In Settings, when the server uses OpenClaw (
provider: "openclaw"), a section Figma file evaluation (OpenClaw) appears. Click Evaluate file to send the current document's file key and name toPOST /enrich/figma-evaluate. OpenClaw (with the Figma Design Toolkit skill installed) can analyze the file for design-system, tokens, and accessibility. The report is shown in the plugin. The file must be saved to have a file key.
---
Planned
Future work (not implemented; design only):
- Dual-provider or routed enrich — e.g. Gwen chat → OpenClaw while
POST /enrich/chat/mosmeandPOST /enrich/chat/streamstill use Ollama when both are configured. Would require explicit routing inserver/src/enrich-provider.tsand endpoint-level rules inserver/src/index.tsinstead of today’s single globalgetEnrichProvider(). - OpenClaw streaming — If the OpenClaw gateway exposes an SSE or token stream compatible with the landing Gwen UI, implement a stream client parallel to
enrichChatStream(today returns 501 for OpenClaw).
Product / ops: A “managed OpenClaw service” on unDrifter is BYO gateway today (OPENCLAW_URL to your Droplet, homelab, or tunneled host). Hosting the gateway next to the app on DigitalOcean is the same env vars with a private or authenticated URL—see OPENCLAW_DROPLET.md. Per-user gateway URLs would need new auth, storage, and UI.
---
Configuration
In server/.env. Use one of the two backends (Ollama or OpenClaw). If both are enabled, OpenClaw takes precedence.
Ollama
OLLAMA_ENABLED=trueOLLAMA_HOST=http://localhost:11434OLLAMA_MODEL=<tag>(must matchollama listafterollama pull <tag>)- Optional:
OLLAMA_VISION_MODEL(defaults toOLLAMA_MODEL); used for semantics with screenshot and for Gwen chat-with-image ("How'd they make this?"). Set when using vision in either flow.OLLAMA_ENRICH_WITH_VISION=trueenables semantics-with-screenshot;OLLAMA_TIMEOUT_MSfor long runs.
Run Ollama and pull a model: ollama run <your-tag>.
OpenClaw (alternative backend)
UnDrifter supports two OpenClaw modes:
- Ollama-assisted install (Ollama 0.17+) — Ollama can install and configure OpenClaw, including cloud models (e.g.
kimi-k2.5:cloud). See the Ollama blog: The simplest and fastest way to setup OpenClaw. That flow still leaves OpenClaw + Gateway running on your machine; unDrifter only needs the gateway’s Chat Completions HTTP endpoint reachable atOPENCLAW_URL(same env vars as below). Cloud billing is through Ollama’s cloud model pricing, not a separate “OpenClaw API” on ollama.com. - Current OpenClaw Gateway (recommended) — OpenClaw runs a gateway (default port 18789) and can expose an OpenAI-compatible Chat Completions endpoint. Set
OPENCLAW_ENABLED=true,OPENCLAW_URL=http://localhost:18789,OPENCLAW_USE_CHAT_COMPLETIONS=true, andOPENCLAW_GATEWAY_TOKEN(orOPENCLAW_GATEWAY_PASSWORD). Enable in Gateway config:gateway.http.endpoints.chatCompletions.enabled: true.GET /enrich/statusreturnsopenClaw.useChatCompletions: true. - Legacy / custom skill server — A server that exposes e.g.
POST /skill/enrich-chatwith{ messages }and returns{ content }. LeaveOPENCLAW_USE_CHAT_COMPLETIONSunset; setOPENCLAW_URL(e.g.http://localhost:3002) and optionallyOPENCLAW_SKILL_CHAT,OPENCLAW_SKILL_VISION,OPENCLAW_SKILL_FIGMA.
Env checklist (gateway mode): OPENCLAW_ENABLED=true, OPENCLAW_URL (no trailing slash), OPENCLAW_USE_CHAT_COMPLETIONS=true, and gateway auth if enabled. Optional: OPENCLAW_TIMEOUT_MS.
Verify after restart:
bash
curl -sk https://localhost:3001/enrich/status | jq '.data.provider, .data.openClaw'
curl -sk https://localhost:3001/enrich/test | jq '.success'
Expect provider "openclaw" and success: true on test.
Limitations while provider is openclaw: POST /enrich/chat/stream (Gwen SSE) and POST /enrich/chat/mosme are implemented for Ollama only; the landing UI falls back to non-streaming chat, and MOSME returns 501. Use Ollama as the enrich provider if you need those paths. POST /enrich/figma-evaluate is OpenClaw-only.
For Figma file evaluation with the Gateway, install the Figma Design Toolkit skill; with a custom server, set OPENCLAW_SKILL_FIGMA. GET /enrich/status returns provider: "openclaw" and openClaw: { enabled, url, timeoutMs, useChatCompletions }; GET /enrich/test pings the active backend.
Troubleshooting "Enrich failed"
- Enrichment is disabled — Enable Ollama:
OLLAMA_ENABLED=trueinserver/.env, or OpenClaw:OPENCLAW_ENABLED=trueandOPENCLAW_URL. Restart the server. - Ollama not running — Start Ollama and pull a model:
ollama run <your-tag>. CheckGET /enrich/statusandGET /enrich/testfrom the landing page orcurl -sk https://localhost:3001/enrich/test. - Wrong model —
OLLAMA_MODELmust match an installed tag (ollama list). If the error says "model not found", setOLLAMA_MODELto a tag you have. - Timeout — Large zones or slow hardware can hit the timeout. Increase
OLLAMA_TIMEOUT_MS(default 60000) orOPENCLAW_TIMEOUT_MSinserver/.env. - Server not reachable — Extension and plugin call
https://localhost:3001. Ensure the server is running and nothing else is using port 3001. - Extension "AI structure naming" — Uses
POST /enrich/clean-structure. If it fails, the zone is still added with un-enriched content. Enable Settings → Show trace to see the exact error (or it will auto-open when enrich fails). - Clean with AI (Gwen / OpenClaw) — If you use OpenClaw as the backend, ensure the gateway is running and Chat Completions is enabled (
OPENCLAW_USE_CHAT_COMPLETIONS=true,gateway.http.endpoints.chatCompletions.enabled: true). On failure, the plugin shows the server error; open the landing page Logs tab (https://localhost:3001→ Logs) to see the full server log (e.g.clean-structure: Backend erroror exception stack).