AI content components
Six Artifacts / content components from the tool-ui port — message drafts, social post cards, an item carousel, and a geo map. Each is a native ES module in site/frontend/ai/components/content/ that exports a single def object following the component contract. No runtime imports, no build step, zero dependencies beyond the existing design-system CSS.
Contract
Every component lives at site/frontend/ai/components/content/<name>.js and exports:
export const def = {
name, // string — unique kebab-case identifier
category: "Artifacts", // fixed
title, // human-readable label
match(payload), // returns true when this component owns the payload
render(payload, ctx), // returns a DOM node — pure, no side effects outside ctx
example, // a concrete payload object for previews / docs
};
ctx (optional, feature-detected) — the generative-UI runtime context:
ctx.onAction(name, data)— fire a named action back to the runtime.ctx.sendReceipt(obj)— send a structured receipt back to the assistant as tool output.
All components XSS-safe: user-supplied strings go through esc() before touching innerHTML. All components degrade gracefully when ctx is absent.
message_draft
An editable draft email card with Edit and Send/Approve actions.
Payload
| Field | Type | Required | Description | |---|---|---|---| | body | string | yes | Message body text. | | to | string | no | Recipient address. | | subject | string | no | Email subject line. |
Actions (ctx)
| Action | Fired when | |---|---| | draft:edit | Edit/Done button toggled. Data: { editing: boolean }. | | draft:send | Send button clicked. Data: { to, subject, body }. |
sendReceipt is called on Send with { type: "message_draft", action: "send", to, subject, body }.
Example
import { def } from "/ai/components/content/message_draft.js";
const node = def.render(def.example, ctx);
document.body.appendChild(node);
{
"to": "team@example.com",
"subject": "Sprint recap",
"body": "Hi team,\n\nHere's a quick recap of this week's sprint.\n\nBest,\nOli"
}
x_post
An X / Twitter-style post card — avatar, handle, text, and engagement stats.
Payload
| Field | Type | Required | Description | |---|---|---|---| | handle | string | yes | @handle (without @). | | name | string | no | Display name (defaults to handle). | | text | string | yes | Post body. | | avatar | string | no | Avatar image URL. | | stats | object | no | { likes?, reposts?, replies? } — all numbers. |
Example
{
"handle": "olibuijr",
"name": "Ólafur Búi",
"text": "Just shipped the AI component library — native ESM, zero deps. 🚀",
"stats": { "likes": 142, "reposts": 38, "replies": 17 }
}
linkedin_post
A LinkedIn-style post card — name, optional headline, post text, and reaction/comment counts.
Payload
| Field | Type | Required | Description | |---|---|---|---| | name | string | yes | Author display name. | | headline | string | no | Professional headline (role/company). | | text | string | yes | Post body. | | stats | object | no | { likes?, comments? } — all numbers. |
Example
{
"name": "Ólafur Búi Ólafsson",
"headline": "Founder at AkurAI · AI solutions for Icelandic businesses",
"text": "Excited to announce: AkurAI Framework ships a native-ESM AI component library. No React, no bundler.\n\n#AI #OpenSource",
"stats": { "likes": 310, "comments": 42 }
}
instagram_post
An Instagram-style media post card — avatar, image (or SVG placeholder), caption, and like count.
Payload
| Field | Type | Required | Description | |---|---|---|---| | user | string | yes | Username / @handle. | | image | string | no | Image URL. Omit or leave blank for an SVG placeholder — honest, no fake images. | | caption | string | no | Post caption text. | | likes | number | no | Like count. |
Example
{
"user": "akurai.is",
"image": "",
"caption": "Building AI tools for Icelandic businesses. 🇮🇸",
"likes": 874
}
item_carousel
A horizontal scroll-snap carousel with prev/next buttons. Items can be linked or plain cards. Images are optional (SVG placeholder if absent).
Payload
| Field | Type | Required | Description | |---|---|---|---| | items | array | yes | Non-empty list of item objects (see below). |
Item shape:
| Field | Type | Required | |---|---|---| | title | string | yes | | image | string | no — URL | | subtitle | string | no | | href | string | no — must start with https:// or / |
URLs that do not start with https:// or / are silently dropped to prevent injection.
Example
{
"items": [
{ "title": "AkurAI Framework", "subtitle": "Native ESM, zero deps", "href": "https://github.com/olibuijr/AkurAI-Framework" },
{ "title": "Streaming Chat", "subtitle": "SSE-based token streaming" },
{ "title": "Tool UI", "subtitle": "30+ generative components" }
]
}
geo_map
A lightweight map with no external library. Always renders a labelled coordinate panel and an SVG pin canvas. An optional tileUrl adds a static tile image grid from any OSM XYZ-scheme tile server (no key auto-fetching — you supply the URL template).
Payload
| Field | Type | Required | Description | |---|---|---|---| | lat | number | yes | Latitude (decimal degrees). | | lng | number | yes | Longitude (decimal degrees). | | zoom | number | no | Zoom level (default 13). Used for tile grid and pin positioning. | | markers | array | no | Additional pins: [{ lat, lng, label? }]. | | tileUrl | string | no | Tile URL template, e.g. "https://yourserver/{z}/{x}/{y}.png". Major tile providers (OSM, MapTiler, MapBox) require API keys — this field exists for self-hosted or pre-approved endpoints. Omit to show the honest SVG placeholder. |
Pin behaviour
The centre coordinate is always pinned. Any markers entries get secondary pins. Pin positions use Web Mercator projection, so they are proportionally accurate on the SVG canvas and on a real tile overlay.
Example
{
"lat": 65.6835,
"lng": -18.1002,
"zoom": 13,
"markers": [{ "lat": 65.684, "lng": -18.098, "label": "AkurAI HQ" }]
}
CSS
All component styles live under the tc-content-* prefix in site/frontend/styles.css (appended at the end). Components reference only the design-system tokens declared on :root — no hard-coded colours or radii. No other CSS files are touched.