Interactive Artifacts — Frontend API Contract¶
Feature: FEAT-EZ10MV · Interactive HTML Handler (extended from Infographic)
Status: Merged to dev (PR #962)
Audience: Frontend engineers building interactive artifact support in the navigator frontend.
This document is the authoritative contract for requesting and rendering Interactive Artifacts — self-contained HTML pages (dashboards, wizards, data grids, diagrams, reports) generated by the LLM using a curated catalog of vetted JavaScript libraries. It covers the conversation flow, tool payload schemas, artifact retrieval endpoints, content negotiation, download support, and the security model.
1. Interactive Artifacts vs Infographics¶
| Dimension | Infographic | Interactive Artifact |
|---|---|---|
| Output model | Block-based JSON (blocks[]) + rendered HTML |
Free-form self-contained HTML page |
| LLM task | Produce structured data blocks | Fill <!-- SLOT:* --> markers in an HTML skeleton |
| JavaScript | None (pure HTML/CSS) | Curated vetted libraries (ECharts, Mermaid, Grid.js, Stepper) |
| Dedicated generation endpoint | POST /api/v1/agents/infographic/{agent_id} |
None — uses standard agent chat |
| Artifact type | infographic |
interactive |
| Frontend rendering | Component-per-block OR iframe | Always iframe / <div> container OR signed public URL |
| Download support | ✗ | ✓ (?download=1) |
Interactive Artifacts are the right choice when the user needs an explorable, clickable page: filterable grids, multi-step wizards, Mermaid diagrams, ECharts dashboards. Infographics are better for static, print-quality summaries.
2. How Generation Works — Conversation Flow¶
Interactive Artifacts are generated via the standard agent chat endpoint. The frontend sends a natural-language request; the agent calls the interactive_render tool internally and returns an InteractiveRenderResult as its final output.
Frontend ai-parrot Agent
│ │
│── POST /api/v1/agents/chat/{agent_id} ──▶ │
│ { "message": "Build a Q4 sales dashboard" }│
│ │
│ │── [calls tool: interactive_list_templates]
│ │── [calls tool: interactive_render(template="dashboard", ...)]
│ │── [LLM fills scaffold slots with ECharts code]
│ │── [stores artifact, signs URL]
│ │
│◀── 200 { "type": "interactive", ──────────── │
│ "artifact_id": "...",
│ "html_url": "/api/v1/artifacts/public/...",
│ "html_inline": "<!DOCTYPE html>...",
│ "template_name": "dashboard",
│ "theme": "dark",
│ "libraries_used": ["echarts"],
│ "enhanced": true }
│
│── GET {html_url}?download=1 ──▶ (optional: save as file)
The agent detects from context which scaffold template fits the request and invokes the tool autonomously. The frontend does not need to call template/library endpoints directly — they exist only for UI pickers if needed.
3. Base URL & Auth¶
- Chat endpoint:
/api/v1/agents/chat/{agent_id}(standard agent talk; seeAgentTalkhandler) - Artifact retrieval (authenticated):
/api/v1/threads/{session_id}/artifacts/{artifact_id} - Artifact retrieval (public signed URL):
/api/v1/artifacts/public/{signature}/{artifact_id}.html - Auth: Chat and authenticated artifact endpoints require
Authorization: Bearer <token>+agent:chatPBAC permission. The public signed URL endpoint is unauthenticated (HMAC-verified). - Content-Type:
application/jsonon all request bodies.
4. Chat Endpoint — Triggering Generation¶
Use the standard agent chat endpoint with a message that describes the desired artifact. The agent's system prompt includes the interactive catalog index; it will call interactive_render automatically.
Request¶
{
"message": "Build a Q4 2025 sales dashboard showing revenue, unit sales, and monthly trend",
"session_id": "sess_abc123", // optional but recommended for continuity
"user_id": "user_42", // optional
"use_conversation_history": true, // optional, default false
"use_vector_context": false // optional, default true
// Any additional kwargs are forwarded to the bot; see AgentTalk for the full schema.
}
To hint the agent toward a specific template, theme, or library, include that preference in the message:
Response — Interactive Artifact Result¶
When the agent calls interactive_render, its response is an InteractiveRenderResult serialised as the agent's final output.
200 OK · Content-Type: application/json
{
"type": "interactive", // discriminates from other agent responses
"artifact_id": "interactive-a1b2c3d4e5f6",
"html_url": "/api/v1/artifacts/public/1750000000.AbCdEfGhIj/interactive-a1b2c3d4e5f6.html",
"html_inline": "<!DOCTYPE html>...", // full HTML if payload < 50 KB, else null
"template_name": "dashboard",
"theme": "dark",
"libraries_used": ["echarts"],
"enhanced": true // false = LLM enhance failed, deterministic skeleton used
}
Field reference:
| Field | Type | Always present | Description |
|---|---|---|---|
type |
"interactive" |
✓ | Response discriminator |
artifact_id |
string |
✓ | Stable artifact ID; use to construct fetch/download URLs |
html_url |
string |
✓ | HMAC-signed public URL valid for ~1 hour (see section 7) |
html_inline |
string \| null |
✓ | Full HTML document when artifact < 50 KB; otherwise null |
template_name |
string |
✓ | Scaffold template used (dashboard, wizard, diagram, grid, report) |
theme |
string \| null |
✓ | Theme applied (light or dark) |
libraries_used |
string[] |
✓ | JS libraries included in the page |
enhanced |
boolean |
✓ | true = LLM authored content; false = empty skeleton (fallback) |
enhanced: false is not an error — it means the LLM pass failed validation and the artifact contains a valid but empty skeleton. Show the artifact normally; the user can ask again with more context.
5. Artifact Retrieval¶
5.1 Authenticated Detail Fetch¶
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
format |
html | json |
— | Overrides Accept header |
download |
1 | true | yes |
— | Adds Content-Disposition: attachment |
Content-Type negotiation (same as infographic handler):
- Query param
?format=html/?format=json Acceptheader (text/html→ raw HTML;application/json→ JSON envelope)- Default:
application/json
JSON response (200 OK · application/json):
{
"artifact_id": "interactive-a1b2c3d4e5f6",
"artifact_type": "interactive",
"session_id": "sess_abc123",
"created_at": "2025-10-01T12:00:00Z",
"definition": {
"html": "<!DOCTYPE html>...",
"js_bundles": [
{
"name": "echarts",
"scope": "cdn",
"url": "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js",
"sri_hash": "sha384-<verified-hash>",
"css_url": null,
"css_sri_hash": null
}
],
"template": "dashboard",
"libraries": ["echarts"],
"theme": "dark"
},
"html_url": "/api/v1/artifacts/public/1750000000.AbCdEfGhIj/interactive-a1b2c3d4e5f6.html"
}
HTML response (200 OK · text/html; charset=utf-8):
Raw self-contained HTML with Content-Security-Policy header enforcing SRI allow-list. Safe to render in a sandboxed <iframe>.
Download (?download=1):
Same HTML response with added header:
5.2 Public Signed URL¶
The html_url in every InteractiveRenderResult is a pre-signed URL valid for approximately one hour. No authentication required.
URL structure:
expiry— Unix timestamp (seconds) after which the URL is rejectedhmac_base64url—HMAC-SHA256(secret, "{artifact_id}|{expiry}")encoded as URL-safe Base64artifact_id_html—{artifact_id}.html
Response: 200 OK · text/html; charset=utf-8 with full CSP + SRI headers.
Error cases:
| Status | When |
|---|---|
400 |
Malformed signature (cannot split on .) |
403 |
Signature expired or HMAC mismatch |
404 |
Artifact not found |
Recommended usage: embed as <iframe src="{html_url}" sandbox="allow-scripts"> for zero-auth public sharing; the URL can be passed to third-party consumers safely (it is time-limited and content-addressed).
6. Discovery Endpoints (Optional — for UI Pickers)¶
These endpoints allow the frontend to build template/library pickers. They are not required for basic generation flow.
6.1 GET /api/v1/agents/interactive/templates — List Templates¶
Note: Endpoint path is indicative; confirm with the server router if a dedicated
InteractiveTalkhandler is registered. Alternatively, prompt the agent with"list available interactive templates"and it will callinteractive_list_templatesinternally.
Response:
{
"templates": [
{
"name": "dashboard",
"description": "KPI cards + responsive grid of ECharts visualizations",
"slots": ["title", "subtitle", "kpis", "charts", "footer"],
"allowed_libraries": ["echarts"],
"default_theme": "light"
},
{
"name": "wizard",
"description": "Multi-step onboarding with stepper controls",
"slots": ["title", "subtitle", "steps"],
"allowed_libraries": ["stepper"],
"default_theme": "light"
},
{
"name": "diagram",
"description": "Single diagram + notes section",
"slots": ["title", "subtitle", "diagram", "notes"],
"allowed_libraries": ["mermaid"],
"default_theme": "light"
},
{
"name": "grid",
"description": "Sortable and searchable data grid",
"slots": ["title", "grid"],
"allowed_libraries": ["gridjs"],
"default_theme": "light"
},
{
"name": "report",
"description": "Prose + embedded ECharts visualizations",
"slots": ["title", "subtitle", "content", "footer"],
"allowed_libraries": ["echarts"],
"default_theme": "light"
}
]
}
6.2 GET /api/v1/agents/interactive/libraries — List Libraries¶
{
"libraries": [
{ "name": "echarts", "description": "Apache ECharts 5 — bar/line/pie/scatter/radar/gauge charts", "category": "chart", "scope": "cdn" },
{ "name": "mermaid", "description": "Mermaid.js — flowcharts, sequence, class, ER diagrams", "category": "diagram", "scope": "cdn" },
{ "name": "gridjs", "description": "Grid.js — sortable, searchable, paginated HTML table", "category": "grid", "scope": "cdn" },
{ "name": "stepper", "description": "Lightweight inline multi-step wizard (no external dep)", "category": "wizard", "scope": "inline" }
]
}
Library categories:
| Category | Use case |
|---|---|
chart |
Quantitative data visualizations (bar, line, pie, scatter…) |
diagram |
Structural / relational diagrams (flowchart, sequence, ER…) |
grid |
Tabular data with sort / search / pagination |
wizard |
Multi-step forms / onboarding flows |
7. Tool Payloads (for custom agent integrations)¶
If you are building a custom agent that calls the toolkit directly (not through natural language chat), use these tool call schemas.
7.1 interactive_list_templates¶
Input: none
Output: array of template descriptors (see section 6.1 shape).
7.2 interactive_list_libraries¶
Input: none
Output: array of library descriptors (see section 6.2 shape).
7.3 interactive_get_scaffold¶
Input:
Output: Full scaffold detail — HTML skeleton with <!-- SLOT:* --> markers visible, allowed libraries with usage snippets and TypeScript type references.
Errors:
| Code | Condition |
|---|---|
TEMPLATE_UNKNOWN |
template_name not in catalog |
7.4 interactive_render — Primary Tool¶
Input:
{
"template_name": "dashboard", // required — one of: dashboard, wizard, diagram, grid, report
"brief": "Q4 sales metrics with monthly revenue trend, top-5 products by volume, and YoY comparison",
// required in enhance mode; describes what each slot should contain
"libraries": ["echarts"], // optional; defaults to template's allowed libraries
"mode": "enhance", // "enhance" (LLM authors) | "deterministic" (empty skeleton)
"theme": "dark", // "light" | "dark" — defaults to template's default_theme
"title": "Q4 2025 Dashboard", // optional; sets <title> and document heading
"data_context": { // optional; source-of-truth data injected verbatim into LLM prompt
"q4_revenue": 2500000,
"products": [
{ "name": "Product A", "units": 12000 },
{ "name": "Product B", "units": 9500 }
],
"monthly": [780000, 920000, 800000]
}
}
Field rules:
| Field | Required | Constraints |
|---|---|---|
template_name |
✓ | Must exist in catalog |
brief |
✓ in enhance mode |
Non-empty; describes content per slot |
libraries |
✗ | Each name must exist AND be in the template's allowed_libraries |
mode |
✗ | "enhance" (default) or "deterministic" |
theme |
✗ | "light" or "dark" |
title |
✗ | Becomes <title> tag and top heading |
data_context |
✗ | Any JSON object; injected verbatim into the LLM enhance prompt |
Output: InteractiveRenderResult (see section 4 — Response field reference).
Validation errors:
| Code | Condition |
|---|---|
TEMPLATE_UNKNOWN |
template_name not in catalog |
LIBRARY_UNKNOWN |
A library in libraries[] is not in the catalog |
LIBRARY_NOT_ALLOWED |
A library in libraries[] is not in the template's allowed_libraries |
ENHANCE_BRIEF_MISSING |
mode="enhance" but brief is empty or missing |
ENHANCE_OUTPUT_INVALID |
LLM-produced HTML references a non-whitelisted external script/stylesheet (security fallback to skeleton) |
8. Built-in Scaffold Catalog¶
8.1 Templates¶
| Name | Slots | Allowed Libraries | Default Theme | Best for |
|---|---|---|---|---|
dashboard |
title, subtitle, kpis, charts, footer | echarts | light | KPI overviews, multi-chart pages |
wizard |
title, subtitle, steps | stepper | light | Multi-step forms, onboarding |
diagram |
title, subtitle, diagram, notes | mermaid | light | Flowcharts, sequence, ER diagrams |
grid |
title, grid | gridjs | light | Sortable data tables |
report |
title, subtitle, content, footer | echarts | light | Long-form prose with charts |
8.2 Libraries¶
| Name | CDN / Inline | Version | Category | Notes |
|---|---|---|---|---|
echarts |
CDN (jsdelivr) | 5.4.3 | chart | SRI-verified hash; bar/line/pie/scatter/radar/gauge |
mermaid |
CDN (jsdelivr) | 10.x | diagram | SRI placeholder — verify before production |
gridjs |
CDN + stylesheet | 6.x | grid | Requires companion CSS; SRI placeholder |
stepper |
Inline | — | wizard | Zero external dep; bundled directly in HTML |
8.3 Themes¶
| Name | --ip-bg |
--ip-text |
--ip-primary |
Use case |
|---|---|---|---|---|
light |
#ffffff |
#0f172a |
#6366f1 (indigo) |
Default; light backgrounds |
dark |
#0f172a |
#f1f5f9 |
#818cf8 (light indigo) |
Dark dashboards |
Both themes are implemented as CSS custom properties injected into <head>. The frontend can further override them by setting variables on the iframe container if embedding via srcdoc.
9. Security Model¶
All interactive artifacts are hardened against XSS and supply-chain attacks at two layers.
9.1 Subresource Integrity (SRI)¶
Every CDN library in the catalog carries a sri_hash field. The artifact HTML emits:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"
integrity="sha384-<hash>"
crossorigin="anonymous"></script>
The browser rejects the script if the hash does not match. A library with a placeholder hash (sha384-REGENERATEME) is flagged at startup with a WARNING log and must be updated before production use.
9.2 Content Security Policy¶
The public artifact endpoint (/api/v1/artifacts/public/…) sets a strict Content-Security-Policy header derived from the artifact's js_bundles list:
Content-Security-Policy:
default-src 'none';
script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net/npm/echarts@5.4.3/ ;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
connect-src 'none';
frame-ancestors 'self'
connect-src 'none' prevents the LLM-generated JavaScript from making outbound network calls. frame-ancestors 'self' allows embedding only from the same origin.
9.3 HTML Validation Gate¶
Before an LLM-enhanced artifact is persisted, a validator parses its <script src> and <link href> tags and rejects any URL not present in the SRI allow-list. If validation fails:
- The toolkit logs a
WARNINGsecurity event. - The empty deterministic skeleton is stored instead.
- The response carries
"enhanced": false.
Inline <script> and <style> blocks are always permitted (no hash required for inline).
9.4 Signed URLs¶
Public URLs use HMAC-SHA256(secret, "{artifact_id}|{expiry}"). The server validates the signature and rejects expired or tampered URLs with 403 Forbidden. URLs are valid for approximately one hour; use the authenticated endpoint for long-lived links.
10. Frontend Integration Flow¶
Minimal — Iframe Embed¶
The simplest integration: send a chat message and embed the signed URL.
// 1. Generate
const res = await fetch(`/api/v1/agents/chat/${agentId}`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: userQuery, session_id: sessionId }),
});
const data = await res.json();
// 2. If it's an interactive artifact, embed it
if (data.type === 'interactive') {
const iframe = document.createElement('iframe');
iframe.src = data.html_url;
iframe.sandbox = 'allow-scripts'; // needed for ECharts/Mermaid; do NOT add allow-same-origin
iframe.style.width = '100%';
iframe.style.height = '600px';
iframe.style.border = 'none';
container.appendChild(iframe);
}
Using html_inline (when available)¶
For artifacts under 50 KB, skip the second HTTP request:
if (data.type === 'interactive' && data.html_inline) {
const iframe = document.createElement('iframe');
iframe.srcdoc = data.html_inline;
iframe.sandbox = 'allow-scripts';
container.appendChild(iframe);
} else if (data.type === 'interactive') {
iframe.src = data.html_url; // fallback: fetch from signed URL
}
Download Button¶
const downloadUrl = `/api/v1/threads/${sessionId}/artifacts/${data.artifact_id}?format=html&download=1`;
downloadLink.href = downloadUrl;
downloadLink.download = `${data.template_name}-${data.artifact_id}.html`;
Or use the signed public URL (no auth required):
Full Integration Flow (Recommended)¶
1. Boot-time (optional):
GET /api/v1/agents/interactive/templates → populate template picker
GET /api/v1/agents/interactive/libraries → populate library picker
2. User submits request:
POST /api/v1/agents/chat/{agent_id}
{ "message": userQuery, "session_id": sessionId }
3. Response: check `data.type`
- "interactive" → render artifact (sections 4, 5)
- other types → handle as standard text/infographic response
4. Render:
- If html_inline present and page < 50KB: use srcdoc (no RTT)
- Otherwise: load html_url in <iframe>
- Always use sandbox="allow-scripts"
5. Action bar (optional):
- Download: GET {html_url}?download=1
- Refresh: re-send same message
- Share: copy html_url (valid ~1 hour; re-generate for long-lived share)
6. Error states:
- enhanced=false: show info banner "Artifact rendered with default layout"
- 403 on html_url: URL expired — re-call GET /artifacts/{id} to get fresh URL
- 404: artifact no longer in store
11. Recommended iframe Sandbox Policy¶
<iframe
src="{html_url}"
sandbox="allow-scripts"
referrerpolicy="no-referrer"
loading="lazy"
style="width:100%;height:600px;border:none;border-radius:8px;"
></iframe>
Do NOT add allow-same-origin — that would let the page's JavaScript escape the sandbox and access the parent origin's cookies and storage.
Do NOT add allow-forms or allow-modals unless the artifact is a wizard that explicitly needs form submission (discuss with backend before enabling).
The sandbox="allow-scripts" policy is sufficient for ECharts, Mermaid, and Grid.js. The stepper library is pure DOM manipulation and needs no special permissions.
12. Known Limitations (v1)¶
- No dedicated generation endpoint. Unlike Infographic, there is no
POST /api/v1/agents/interactive/{agent_id}shortcut. Generation always goes through the chat flow. - SRI placeholders.
mermaidandgridjscatalog entries carrysha384-REGENERATEMEplaceholder hashes. These must be verified and replaced before production deployment, or the browser will block those libraries. - Fixed catalog. Libraries and scaffold templates cannot be registered via API (no equivalent of
POST /infographic/templates). New entries require a code change in the catalog directory. - No streaming. Artifact generation is synchronous; the chat endpoint returns only after the full HTML is generated and stored.
- No PDF/PNG export. The artifact is HTML-only. Screenshot-to-image conversion is out of scope for v1.
- ~1-hour signed URL expiry. The
html_urlin the response expires. For long-lived embeds, use the authenticatedGET /api/v1/threads/{session_id}/artifacts/{artifact_id}?format=htmlendpoint instead. - Single theme per artifact. Theme is chosen at generation time; the HTML is baked. To switch themes, regenerate the artifact.
13. Source References¶
| Concern | File |
|---|---|
Data models (LibraryEntry, ScaffoldTemplate, InteractiveRenderResult) |
packages/ai-parrot/src/parrot/models/interactive.py |
| Toolkit + enhance pipeline | packages/ai-parrot/src/parrot/tools/interactive_toolkit.py |
Catalog registry + build_head() |
packages/ai-parrot/src/parrot/tools/interactive/catalog_registry.py |
| Scaffold templates (HTML skeletons + YAML metadata) | packages/ai-parrot/src/parrot/tools/interactive/catalog/templates/ |
| Library definitions (YAML frontmatter + source) | packages/ai-parrot/src/parrot/tools/interactive/catalog/libraries/ |
| Shared HTML SRI validator | packages/ai-parrot/src/parrot/tools/_enhance_html_check.py |
Bot enhance method (enhance_interactive) |
packages/ai-parrot/src/parrot/bots/abstract.py |
| System prompt injection constants | packages/ai-parrot/src/parrot/bots/prompts/__init__.py |
ArtifactType.INTERACTIVE enum |
packages/ai-parrot/src/parrot/storage/models.py |
| Artifact HTTP handler (retrieval + download) | packages/ai-parrot-server/src/parrot/handlers/artifacts.py |
| Route registration | app.py (lines 255-269) |
| Catalog tests | packages/ai-parrot/tests/test_interactive_catalog.py |
| Toolkit tests | packages/ai-parrot/tests/test_interactive_toolkit.py |
| E2E tests | packages/ai-parrot/tests/integration/test_interactive_e2e.py |
| Infographic API (for comparison) | docs/infographic_handler_api.md |