Menu
Public repository / f1f33c69567a554b6ce011822661d3289c1e9c77
AkurAI-Framework / site/frontend/runtime-demo.html
Latest change ea85a4bfaa729fcd081e9ff622de7180d94a97b1 - integrate AI components: real component manifest (27), wire /chat + /ai-components + /ai-runtime routes, AI docs sidebar + nav, changelogs, landing alert; AI batch track done by Ólafur Búi Ólafsson
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Generative-UI runtime — AkurAI/Framework</title>
<meta
name="description"
content="Live demo of the tool-call → component runtime: registry dispatch, JSON fail-safe, inline approval round-trip, reasoning toggle, suggestion chips, ThreadList and branch picker."
/>
<link rel="stylesheet" href="/styles.css" />
<style>
/* toolui- scoped demo-only layout helpers (no design-system overrides). */
.toolui-demo-grid { display: grid; gap: 2.5rem; margin: 2.5rem 0 5rem; }
.toolui-demo-out { margin-top: 1rem; }
.toolui-demo-sidebar { max-width: 260px; }
</style>
</head>
<body>
<main class="container">
<header class="page-head">
<span class="eyebrow">Live demo</span>
<h1>Generative-UI runtime</h1>
<p class="lede">
The engine behind <code>tool-ui</code>: tool-call payloads are matched
to registered components and rendered to the DOM. Pure native ESM, zero
dependencies. Open DevTools → Console for <code>[toolui]</code> and
<code>[runtime]</code> breadcrumbs. <a href="/docs/generative-ui">Read the docs →</a>
</p>
</header>
<div class="toolui-demo-grid">
<!-- 1. Matched render -->
<section>
<h2>1 · Matched render</h2>
<p>A <code>weather</code> payload is dispatched and the registered weather component renders it.</p>
<button class="btn btn-primary" id="demo-match">Render weather payload</button>
<div class="toolui-demo-out" id="out-match"></div>
</section>
<!-- 2. JSON fail-safe fallback -->
<section>
<h2>2 · JSON fail-safe</h2>
<p>An unrecognized payload matches nothing, so the dispatcher falls back to a pretty-printed JSON card — the UI never crashes.</p>
<button class="btn btn-primary" id="demo-fallback">Render unknown payload</button>
<div class="toolui-demo-out" id="out-fallback"></div>
</section>
<!-- 3. Approval round-trip -->
<section>
<h2>3 · Approval round-trip</h2>
<p>An <code>approval</code> payload renders Approve/Reject. Choosing one resolves a promise and fires a receipt (POST attempted + <code>toolui:receipt</code> event).</p>
<button class="btn btn-primary" id="demo-approval">Render approval</button>
<div class="toolui-demo-out" id="out-approval"></div>
<p class="small muted toolui-demo-out" id="approval-status">Receipt log: —</p>
</section>
<!-- 4. Reasoning toggle -->
<section>
<h2>4 · Reasoning toggle</h2>
<p>A collapsible reasoning block for an assistant turn.</p>
<div class="toolui-demo-out" id="out-reasoning"></div>
</section>
<!-- 5. Suggestion chips -->
<section>
<h2>5 · Suggestion chips</h2>
<p>Tappable chips fill the composer below and emit <code>suggestion:pick</code>.</p>
<div class="toolui-demo-out" id="out-suggestions"></div>
<form id="composer-form" class="toolui-demo-out" style="display:flex; gap:8px;">
<input id="composer" type="text" style="flex:1" placeholder="Pick a suggestion…" />
<button class="btn btn-secondary" type="submit">Send</button>
</form>
</section>
<!-- 6. ThreadList + branch picker -->
<section class="split">
<div>
<h2>6 · ThreadList</h2>
<p>Switch conversations (in-memory + localStorage). Emits <code>thread:select</code>.</p>
<div class="toolui-demo-sidebar toolui-demo-out" id="out-threads"></div>
<p class="small muted" id="thread-status">Selected: —</p>
</div>
<div>
<h2>Branch picker</h2>
<p>Step through alternate message versions. Emits <code>branch:change</code>.</p>
<div class="toolui-demo-out" id="out-branch"></div>
<p class="small muted" id="branch-status">Branch: —</p>
</div>
</section>
</div>
</main>
<script type="module">
import Runtime, {
registerAll,
renderToolPayload,
renderApproval,
createReasoning,
createSuggestions,
createThreadList,
createBranchPicker,
} from "/ai/runtime/index.js";
// ── Register 2 tiny inline example components ────────────────────────────
registerAll([
{
name: "weather-card",
category: "data",
title: "Weather card",
match: (p) => p && p.type === "weather",
render: (p) => {
const card = document.createElement("div");
card.className = "card";
card.innerHTML =
'<h3>' + (p.city || "Somewhere") + "</h3>" +
'<p class="muted">' + (p.temp ?? "?") + "°C · " + (p.summary || "") + "</p>";
return card;
},
example: { type: "weather", city: "Akureyri", temp: 4, summary: "Light snow" },
},
{
name: "metric-card",
category: "data",
title: "Metric card",
match: (p) => p && p.type === "metric",
render: (p) => {
const card = document.createElement("div");
card.className = "card";
card.innerHTML =
'<h3>' + (p.label || "Metric") + "</h3>" +
'<p style="font-size:28px;margin-top:4px;">' + (p.value ?? "—") + "</p>";
return card;
},
example: { type: "metric", label: "Active users", value: 1280 },
},
]);
const $ = (id) => document.getElementById(id);
// 1. Matched render
$("demo-match").addEventListener("click", () => {
const out = $("out-match");
out.textContent = "";
out.appendChild(renderToolPayload({ type: "weather", city: "Akureyri", temp: 4, summary: "Light snow" }));
});
// 2. JSON fail-safe
$("demo-fallback").addEventListener("click", () => {
const out = $("out-fallback");
out.textContent = "";
out.appendChild(renderToolPayload({ type: "unknown-thing", foo: 1, nested: { a: [1, 2, 3] } }));
});
// 3. Approval round-trip
document.addEventListener("toolui:receipt", (e) => {
$("approval-status").textContent = "Receipt log: " + JSON.stringify(e.detail);
});
$("demo-approval").addEventListener("click", () => {
const out = $("out-approval");
out.textContent = "";
const { element, promise } = renderApproval(
{
title: "Delete 3 records?",
message: "This permanently removes the selected rows.",
toolCallId: "call_demo_1",
},
Runtime.makeReceiptCtx()
);
out.appendChild(element);
promise.then((r) => console.log("[demo] approval resolved:", r));
});
// 4. Reasoning toggle
$("out-reasoning").appendChild(
createReasoning({
summary: "Show reasoning",
text: "Step 1: parse the request.\nStep 2: select the weather tool.\nStep 3: format the result as a card.",
}).element
);
// 5. Suggestion chips
$("out-suggestions").appendChild(
createSuggestions(
["Tell me more", "Summarize this", { text: "Send now", submit: true }],
{ composer: $("composer"), form: $("composer-form") }
).element
);
$("composer-form").addEventListener("submit", (e) => {
e.preventDefault();
console.log("[demo] composer submit:", $("composer").value);
});
// 6. ThreadList
const threads = createThreadList({
threads: [
{ id: "t1", title: "Weather questions" },
{ id: "t2", title: "Budget planning" },
],
storageKey: "toolui:demo-threads",
onSelect: (t) => ($("thread-status").textContent = "Selected: " + t.title),
});
$("out-threads").appendChild(threads.element);
// Branch picker
const branch = createBranchPicker({
total: 3,
index: 0,
onChange: (i, n) => ($("branch-status").textContent = `Branch: ${i + 1} / ${n}`),
});
$("out-branch").appendChild(branch.element);
console.log("[demo] runtime-demo initialized", Runtime.getRegistered().map((d) => d.name));
</script>
</body>
</html>