Chat
The chat is the conversation for an AI assistant or a support desk. Answers stream in as they arrive and are written in Markdown, with highlighted code that copies in one click, tables, sources cited by number and the steps the assistant took. Files go with a message; suggestions start one. Stop an answer, ask again, edit and resend, rate it, and save the whole conversation as Markdown, text or JSON.
It holds no model and sends nothing anywhere. You give it a handler that asks your own
service — your server, or a model you host — and return the reply as text, a stream or a
fetch response; the chat does the rest. Answers are sanitised before they are shown, and
cannot load anything unless you allow images.
A support assistant
"Nova" answers from this page with a stand-in handler that streams its answers a few characters at a time, as a model would. Ask about refunds for sources and a tool step, about the API for code, or about plans for a table. Press the send button while an answer streams to stop it.
Get the code
<script src="/assets/bmx-components.min.js"></script> <bmx-chat id="ct-assistant" lang="en-GB" label="Support assistant" heading="How can I help?" description="Ask about orders, refunds, the API or our plans." assistant-name="Nova" suggestions="How do refunds work?,Show me the API in TypeScript,Compare the plans,Where is my order?" attachments accept="image/*,.pdf,.txt" style="inline-size: 100%; --bmx-chat-height: 34rem" ></bmx-chat>
A support desk, with no handler
Without a handler the chat is driven by your page: it hears bmxChatSend and writes replies
with startReply, appendToMessage and finishReply. Here a person
on the desk answers each message after a moment; the buttons write as the desk.
Get the code
<script src="/assets/bmx-components.min.js"></script> <bmx-chat id="ct-desk" lang="en-GB" label="Order help" user-name="Ravi" style="--bmx-chat-height: 26rem"></bmx-chat>
Your own service
The handler is a function. Return text, a promise of text, an async generator, a
ReadableStream or a fetch response, and the chat streams it. Stop aborts the
signal it is given.
const chat = document.querySelector('bmx-chat');
// A server that streams plain text.
chat.handler = ({ messages, signal }) =>
fetch('/api/chat', { method: 'POST', body: JSON.stringify({ messages }), signal });
// Or one that streams server-sent events: read them and yield the text.
chat.handler = async function* ({ messages, signal, update }) {
const res = await fetch('/api/chat/stream', { method: 'POST', body: JSON.stringify({ messages }), signal });
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
for (let buffer = ''; ; ) {
const { value, done } = await reader.read();
if (done) return;
buffer += value;
const events = buffer.split('\n\n');
buffer = events.pop();
for (const event of events) {
const data = JSON.parse(event.replace(/^data: /, ''));
if (data.sources) update({ sources: data.sources });
if (data.text) yield data.text;
}
}
};