AkurAI Build
Menu

Public repository / master

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>