BuildersPut it in your productUse your own UI

Updated 27 Oct 2026View as Markdown

Use your own UI

Drives a Mindset agent from your own chat interface with the UI-less client, and draws its quick replies, cards and widgets.

Skills for your coding agent

Build our own chat panel for the billing-help agent with the UI-less client, in our app's style. Draw quick replies and capability cards as buttons, and show the summary for any other widget.

The UI-less client runs the same agent as the element, in the page, with no Mindset UI: you draw every message. It uses the same session endpoint as Put an agent on your page. The UI-less client is the full API.

html
<script type="module">
  import { createAgentConversation } from "https://eu.mindset.ai/sdk/mindset-agent-uiless.js";

  const chat = createAgentConversation({
    agent: "billing-help",
    getSession: async () => {
      const r = await fetch("/api/session");
      if (!r.ok) throw new Error("Could not start a session");
      return r.json();
    },
  });

  let reply = "";
  chat.on((event) => {
    if (event.type === "text_delta") reply += event.content;
    if (event.type === "complete") console.log("Agent:", reply);
    if (event.type === "run_error") console.error("Turn failed:", event.message);
  });

  try {
    await chat.send("What does my last invoice cover?");
  } catch {
    // The run_error event above already reported what happened.
  }
</script>

In a bundled app, import it at runtime and tell your bundler not to resolve the address. Create the conversation once (in React, guard it in a ref), because each createAgentConversation asks your backend for a session.

javascript
const UILESS_URL = "https://eu.mindset.ai/sdk/mindset-agent-uiless.js";

// Vite: /* @vite-ignore */   webpack: /* webpackIgnore: true */
const { createAgentConversation } = await import(/* @vite-ignore */ UILESS_URL);

Widgets#

A widget arrives as a widget-payload event during a turn, or as widget on a message in a restored transcript. kind, summary and toolCallId are public contract, and a message that showed a widget has its summary as its content.

kindWhat doc holdsWhat to draw
quick_replies{ replies: [{ label, value? }] }One button per reply. A tap sends value, or label when there is no value
capability_cards{ cards: [{ title, description, prompt }] }One card per entry. A tap sends prompt
chart, callout, table, card, badge, stat, widgetA widget tree. Not a public contractsummary, as plain text

This module draws text, quick replies, capability cards, and a summary for everything else:

html
<div id="log"></div>
<form id="composer">
  <input id="message" autocomplete="off" />
  <button type="submit">Send</button>
</form>

<script type="module">
  import { createAgentConversation } from "https://eu.mindset.ai/sdk/mindset-agent-uiless.js";

  const log = document.querySelector("#log");
  const form = document.querySelector("#composer");
  const input = document.querySelector("#message");

  const chat = createAgentConversation({
    agent: "billing-help",
    getSession: async () => {
      const r = await fetch("/api/session");
      if (!r.ok) throw new Error("Could not start a session");
      return r.json();
    },
  });

  function addLine(text, className) {
    const p = document.createElement("p");
    p.className = className;
    p.textContent = text;
    log.appendChild(p);
    return p;
  }

  function send(text) {
    addLine(text, "user");
    chat.send(text).catch(() => {});
  }

  function addButtons(items) {
    const row = document.createElement("div");
    for (const { label, text, detail } of items) {
      const button = document.createElement("button");
      button.type = "button";
      button.textContent = detail ? `${label}: ${detail}` : label;
      button.addEventListener("click", () => {
        row.querySelectorAll("button").forEach((b) => (b.disabled = true));
        send(text);
      });
      row.appendChild(button);
    }
    log.appendChild(row);
  }

  function drawWidget(kind, doc, summary) {
    if (kind === "quick_replies" && Array.isArray(doc?.replies)) {
      addButtons(doc.replies.map((r) => ({ label: r.label, text: (r.value ?? r.label).trim() })));
    } else if (kind === "capability_cards" && Array.isArray(doc?.cards)) {
      addButtons(doc.cards.map((c) => ({ label: c.title, detail: c.description, text: c.prompt })));
    } else if (summary) {
      addLine(summary, "widget-summary");
    }
  }

  let reply = null;

  chat.on((event) => {
    switch (event.type) {
      case "text_delta":
        reply ??= addLine("", "agent");
        reply.textContent += event.content;
        break;
      case "widget-payload":
        drawWidget(event.kind, event.doc, event.summary);
        break;
      case "complete":
      case "run_error":
        reply = null;
        break;
      default:
        break;
    }
  });

  form.addEventListener("submit", (e) => {
    e.preventDefault();
    const text = input.value.trim();
    if (!text) return;
    input.value = "";
    send(text);
  });
</script>

For a returning user's history, call drawWidget(m.widget.kind, m.widget.doc, m.widget.summary) for each message in history_settled that has a widget. Events reference has every field.

What you can't do yet#

  • Draw charts, tables, cards and authored widgets reliably. Nothing promises the shape of doc.
  • Run a scripted agent. A script's greeting, cards and phases appear only in the element.

If you need these, use the element, or have the agent answer in text for your integration.

See it working#

Live demoHalcyon · HalcyonA sleep app draws the agent’s breathing widget as its own orb, with no Mindset UI on screen. Live demoAsk your data · Northwind AnalyticsAsk sales questions and a dashboard draws the answers from what the agent’s functions return.