Embed on any page
Introduction
Section titled “Introduction”You will build the demo web app’s embed bundle and mount the complete chat into a <div> of any page, framework or not. The bundle carries its own copy of React and a stylesheet scoped to the element it mounts into.
What you need
Section titled “What you need”- The repository checked out, with dependencies installed.
- A server the page can reach, and a way to get the user’s token on the page.
1. Build the bundle
Section titled “1. Build the bundle”pnpm --filter @pilotoo/web build:embedThis writes apps/web/dist-embed/pilotoo-chat.js (an ES module) and pilotoo-chat.css. Copy both next to your page or to your CDN.
2. Mount it
Section titled “2. Mount it”<link rel="stylesheet" href="/pilotoo-chat.css" />
<div id="chat" style="height: 640px"></div>
<script type="module"> import { mountChat } from '/pilotoo-chat.js';
const chat = mountChat(document.getElementById('chat'), { apiUrl: 'https://assistant.example.com', credentials: () => window.session?.token, theme: 'system', onConversationChange: (id) => console.log('now reading', id), });
// later, for example when your page changes route: // chat.unmount();</script>The element needs a height. The chat fills it, and nothing sizes it for you.
3. Keep the user’s place
Section titled “3. Keep the user’s place”By default drafts, the theme choice and the last conversation live in memory and vanish on reload. Pass a storage to keep them. It is a KeyValueStorage: three async functions.
<script type="module"> import { mountChat } from '/pilotoo-chat.js';
const storage = { getItem: async (key) => localStorage.getItem(`assistant.${key}`), setItem: async (key, value) => localStorage.setItem(`assistant.${key}`, value), removeItem: async (key) => localStorage.removeItem(`assistant.${key}`), };
mountChat(document.getElementById('chat'), { apiUrl: 'https://assistant.example.com', credentials: () => window.session?.token, storage, conversationId: new URLSearchParams(location.search).get('chat') ?? undefined, });</script>4. Choose the theme
Section titled “4. Choose the theme”theme is 'light', 'dark' or 'system'. It is set as data-theme on the mount element, so it never touches the rest of your page. A theme you pass overrides a stored choice.
How it works
Section titled “How it works”- The element receives the class
pilotoo-chatwhile mounted. Every rule inpilotoo-chat.cssmatches only that class or its descendants, so your page styles do not leak in and the chat’s do not leak out. - Routing is in memory. The embed never reads or writes
location.hash. - It never writes your
localStorageunless you hand it astorage. unmount()removes the class anddata-themeand tears the chat down.- The embed ships without voice.
Options
Section titled “Options”| Option | Meaning |
|---|---|
apiUrl |
The server. Required. |
credentials |
() => token | undefined, read before every request and socket connection. |
conversationId |
The conversation to open on; the new-conversation screen otherwise. |
storage |
Where drafts, the theme choice and the last conversation live. Memory when omitted. |
onConversationChange |
Called with the conversation id, or undefined for the new-conversation screen. |
theme |
'light' | 'dark' | 'system'. |
Next steps
Section titled “Next steps”- Embedding: the guarantees in detail.
- Add chat to a React app: when you have React already, use the components directly.
- Match your brand: recolour the embed.