← unDrifter

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

EndpointDescription
-----------------------
GET /enrich/statusActive provider (ollama \openclaw), config (enabled, model/host or url, enrichWithVision). Use to show "AI: ready" or "AI: unavailable" in UIs.
GET /enrich/testPing active backend (Ollama or OpenClaw, 15s timeout). Verifies the model responds.
POST /enrich/zone-nameSuggest a short zone name (2–4 words) from content. Body: { children: ChildNode[], pageTitle?: string }. Returns { success, data?: { suggestedName }, error? }.
POST /enrich/variable-namesSuggest 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-typesSuggest 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/semanticsEnrich 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-structureClean 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-examplesReturns good/bad structure examples (user-submitted only; base corpus is read-only). Used for few-shot in clean-structure.
POST /enrich/structure-examplesSubmit 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/mosmeMOSME — 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/chatGwen 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-zoneCreate 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-replyCreate 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-evaluateOpenClaw 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 /logsServer 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.

If the file is missing, the server uses an in-code default. Edit the JSON to extend or change allowed names.

---

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

Plugin

---

Planned

Future work (not implemented; design only):

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

Run Ollama and pull a model: ollama run <your-tag>.

OpenClaw (alternative backend)

UnDrifter supports two OpenClaw modes:

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"