name: ls-generative-ui description: "LiteSuite Generative UI — render interactive widgets inline in Frontier Chat / Sentinel Chat. Use prompt_widget when you need the user's input to continue (confirmations, forms, picks). Use render_widget for fire-and-forget displays (charts, stat cards, dashboards). Triggers on 'render widget', 'prompt widget', 'ls-generative-ui', 'show me a chart', 'ask the user', 'confirm before', 'show a form', or whenever a visual or interactive answer is clearer than prose."
Generative UI
Render interactive widgets directly inside the chat conversation. Two tools, three bands.
Choose the right tool
| Tool | Behavior | Use when |
|---|---|---|
prompt_widget |
Blocks until user interacts. Returns {action, data} as tool result. |
Confirmations, forms, picks. You need the user's input to continue. |
render_widget |
Fire-and-forget. Returns immediately. Pure inline display. | Charts, stat cards, status grids, dashboards. No response needed. |
Choose the right band
| Band | When to use |
|---|---|
catalog |
Data fits a named genui.* component exactly. Try this first. |
specs |
Multi-component layout (grids, tabs, dashboards). |
html |
Custom interactivity, charts (Chart.js/D3), unique visuals. |
prompt_widget — required fields
When calling prompt_widget, you MUST provide:
type—"catalog"|"html"|"specs"requestId— unique identifier per call. Generate"widget-{8 random hex chars}"(e.g."widget-7f3a9e2c"). Must be unique per call.agentId— your agent identifier (session UUID or"sentinel-chat"if unknown)
Plus the band-specific fields below.
Response shape
When the user interacts, prompt_widget returns:
{
"requestId": "widget-7f3a9e2c",
"action": "click" | "submit" | "dismiss",
"data": { /* depends on the widget */ },
"timestamp": 1779449619556
}
Catalog component reference
Interactive (use with prompt_widget)
genui.Button — single click resolves with {action:"click", data:{label, buttonId?}}
{ "label": "Confirm", "variant": "primary" }
genui.FormField — auto-wraps with Submit/Cancel; Submit resolves with {action:"submit", data:{fields:{[name]:value}}}
{ "name": "email", "type": "text", "label": "Your email", "placeholder": "you@example.com" }
Types: "text", "textarea", "number", "select", "checkbox", "radio". For select/radio pass options: [{label, value}, ...].
genui.Select — dropdown; same form-wrapped resolution as FormField
{ "options": [{ "label": "A", "value": "a" }], "value": "a" }
Display (use with render_widget)
| Component | Props |
|---|---|
genui.StatCard |
{title, value, trend?, icon?} |
genui.MetricRow |
{metrics: [{label, value}, ...]} |
genui.DataTable |
{columns: ["A","B"], rows: [["x","y"]]} |
genui.ProgressRing |
{value, max, label?} |
genui.TLDR |
{text} |
genui.KeyTakeaways |
{items: [...]} |
genui.ExecutiveSummary |
{title, sections: [{heading, body}]} |
genui.StepCard |
{step, title, description} |
genui.CodeBlock |
{code, language} |
genui.CalloutCard |
{type:"info"|"warning"|"error"|"success", title, message} |
genui.LinkCard |
{url, title, description?} |
genui.ToolCard |
{name, description, status?} |
genui.BookCard |
{title, author, description?} |
genui.StatusIndicator |
{status:"online"|"offline"|"warning", label} |
genui.CategoryBadge |
{category, color} |
Layout (use with specs band)
genui.Section ({title, children}), genui.Grid ({columns, children}), genui.Tabs ({tabs:[{label, content}]}), genui.Accordion ({items:[{title, content}]}).
HTML band
Write content as a self-contained snippet — no <!DOCTYPE>, no <html>, no <head>, no <body>. The iframe provides them.
Pre-loaded globals available in every HTML widget:
Chart— Chart.js v4 (new Chart(canvas, {...}))d3— D3 v7
The iframe runs sandbox="allow-scripts" with no parent-DOM access, no same-origin.
Interactive HTML — postMessage protocol
For prompt_widget html-band, your script MUST post a response to resolve the tool:
window.parent.postMessage(
{
type: "widget-response", // exact string required
action: "click", // or "submit", "dismiss", anything you choose
data: {
/* your data */
},
},
"*",
);
The type: "widget-response" discriminator is required. Anything else is ignored. Your action + data flow back as the tool result.
CDN allowlist
For libraries beyond Chart.js/D3, any https: script is allowed (sandbox is the boundary). Recommended sources: cdn.jsdelivr.net, cdnjs.cloudflare.com, unpkg.com. External API calls (fetch/XHR) are blocked by CSP — keep widgets self-contained.
Worked examples
1. Confirm action (prompt_widget catalog)
{
"type": "catalog",
"component": "genui.Button",
"props": { "label": "Ship it", "variant": "primary" },
"title": "Deploy confirmation",
"requestId": "widget-btn-7f3a9e2c",
"agentId": "{your-id}"
}
2. Pick from a list (prompt_widget catalog form)
{
"type": "catalog",
"component": "genui.FormField",
"props": {
"name": "model",
"label": "Pick a model",
"type": "select",
"options": [
{ "label": "Opus", "value": "opus" },
{ "label": "Sonnet", "value": "sonnet" },
{ "label": "Haiku", "value": "haiku" }
]
},
"title": "Model selection",
"requestId": "widget-pick-a1b2c3d4",
"agentId": "{your-id}"
}
3. Display a chart (render_widget html)
{
"type": "html",
"title": "Weekly Signups",
"content": "<canvas id='c' style='max-height:380px'></canvas><script>new Chart(document.getElementById('c'),{type:'bar',data:{labels:['Mon','Tue','Wed','Thu','Fri'],datasets:[{label:'Signups',data:[42,58,35,71,90],backgroundColor:'#d4a853'}]},options:{responsive:true,plugins:{legend:{labels:{color:'#e8e0d4'}}},scales:{x:{ticks:{color:'#94a3b8'}},y:{ticks:{color:'#94a3b8'}}}}})</script>"
}
4. Custom interactive HTML (prompt_widget html)
{
"type": "html",
"content": "<button id='b' style='padding:10px 20px;background:#7c3aed;color:white;border:0;border-radius:6px;cursor:pointer'>Click me</button><script>document.getElementById('b').addEventListener('click',function(){window.parent.postMessage({type:'widget-response',action:'click',data:{x:42}},'*');});</script>",
"title": "Custom HTML widget",
"requestId": "widget-html-9e2c7f3a",
"agentId": "{your-id}"
}
5. Dashboard layout (render_widget specs)
{
"type": "specs",
"title": "System Health",
"specs": [
{
"component": "genui.Grid",
"props": { "columns": 2 },
"children": [
{
"component": "genui.StatCard",
"props": { "title": "CPU", "value": "34%", "trend": "-2%" }
},
{
"component": "genui.StatCard",
"props": { "title": "Memory", "value": "6.1 GB", "trend": "+0.4 GB" }
}
]
},
{
"component": "genui.ToolCard",
"props": { "name": "Redis", "description": "Cache layer", "status": "healthy" }
}
]
}
Best practices
- Catalog first. Catalog components are theme-matched. Reach for
htmlonly when the design isn't expressible as catalog/specs. - Don't over-widget. A single clear widget beats five cluttered ones. Use
genui.Gridorspecsto group. - Pair tool to intent. Use
render_widgetfor displays you don't need a response to. Useprompt_widgetwhen you'll actually use the user's answer. - Always set a
title— it gives the widget a header and makes the conversation easier to reference. - HTML must be self-contained. No external API calls. All styles + scripts inline.
- One round-trip per
prompt_widget. The tool resolves on the first user response. For multi-step interaction, chain multipleprompt_widgetcalls.