arizuko › howto › Embed chat (SDK)
embed chat with the SDK
The plain chat link (put a chat link on
your site) drops a ready-made page at
/chat/<token>/ with zero code. When you want the
conversation inside your own UI — your fonts, your layout, your
message bubbles — use arizuko-client.js, the
browser SDK that wraps the same token surface.
/chat/<token>/ POST + SSE endpoints the built-in
page uses — see reference ›
route tokens for the wire shapes and
concepts › tokens for the
bearer model.
get a token
Mint a web: chat token for the folder you want to talk to:
arizuko token issue acme chat
# https://acme.fiu.wtf/chat/Yp3v...Q2/
The token is the bit after /chat/. Treat it as a
shareable bearer credential — anyone holding it can chat as an
anonymous visitor against that folder's agent.
load the SDK
Pull arizuko-client.js from the same host that serves
the docs and chat. It exposes one global, Arizuko.
<script src="https://acme.fiu.wtf/assets/arizuko-client.js"></script>
connect, send, stream
Three calls carry the whole conversation:
Arizuko.connect(token)— resolves to a session withnameandfolder.session.send(content)— posts a message, resolves to{ turn_id }.session.stream(turn_id, handlers)— subscribes to the SSE stream for that turn:onMessage,onStatus,onDone,onError.
complete sample
A self-contained chat page — header, scrolling thread, input
box — built on the three calls above. Paste your token (or pass
?token=<t> in the URL), open the file in a browser,
and you have a working chat client styled however you like.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>chat-sdk sample</title>
<script src="https://acme.fiu.wtf/assets/arizuko-client.js"></script>
<style>
body { font: 14px/1.5 -apple-system, sans-serif; display: flex; flex-direction: column; height: 100dvh; }
#thread { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: .5rem; }
.msg { max-width: 75%; padding: .55rem .85rem; border-radius: 12px; white-space: pre-wrap; }
.msg.user { align-self: flex-end; background: #4ade80; color: #0a0a0a; }
.msg.assistant { align-self: flex-start; border: 1px solid #222; }
.msg.status { align-self: center; color: #666; font-style: italic; font-size: .85em; }
footer form { display: flex; gap: .5rem; padding: .6rem 1rem; border-top: 1px solid #222; }
footer input { flex: 1; padding: .5rem .7rem; }
</style>
</head>
<body>
<header><span id="name">connecting…</span> <span id="folder"></span></header>
<div id="thread"></div>
<footer><form id="f"><input id="m" placeholder="type a message…" autofocus /><button id="b" disabled>send</button></form></footer>
<script>
// Paste your chat token here, or override via ?token=<t> in the URL.
const TOKEN = new URLSearchParams(location.search).get('token') || 'REPLACE_WITH_CHAT_TOKEN';
const thread = document.getElementById('thread');
const btn = document.getElementById('b');
const input = document.getElementById('m');
function add(role, content) {
const d = document.createElement('div');
d.className = 'msg ' + role;
d.textContent = content;
thread.appendChild(d);
d.scrollIntoView({ behavior: 'smooth' });
return d;
}
(async () => {
try {
const slink = await Arizuko.connect(TOKEN);
document.getElementById('name').textContent = slink.name;
document.getElementById('folder').textContent = slink.folder;
btn.disabled = false;
document.getElementById('f').onsubmit = async (e) => {
e.preventDefault();
const content = input.value.trim();
if (!content) return;
input.value = '';
btn.disabled = true;
add('user', content);
try {
const turn = await slink.send(content);
const typing = add('status', 'thinking…');
slink.stream(turn.turn_id, {
onMessage: (f) => { typing.remove(); add('assistant', f.content); },
onStatus: (f) => { typing.textContent = f.content; },
onDone: (_) => { typing.remove(); },
onError: (err) => { typing.textContent = 'stream error'; console.error(err); },
});
} catch (err) {
add('status', 'error: ' + err.message);
} finally {
btn.disabled = false;
input.focus();
}
};
} catch (err) {
document.getElementById('name').textContent = 'connect failed';
add('status', err.message);
}
})();
</script>
</body>
</html>
reconnection
stream() handles dropped connections the same way the
built-in page does: it reconnects with Last-Event-Id,
the server replays missed frames, and the turn resumes live. You
don't manage the EventSource yourself.
go deeper
- how-to: put a chat link on your site — the zero-code version of this page.
- reference/tokens — the
/chat/<token>/POST + SSE surface the SDK wraps. - concepts/tokens — the bearer model and JID encoding.
- components/webd — the daemon that hosts
/chat/*.