Skip to content
Pilotoo
v0.1

Embed on any page

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.

  • 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.
Terminal window
pnpm --filter @pilotoo/web build:embed

This 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.

index.html
<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.

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>

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.

  • The element receives the class pilotoo-chat while mounted. Every rule in pilotoo-chat.css matches 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 localStorage unless you hand it a storage.
  • unmount() removes the class and data-theme and tears the chat down.
  • The embed ships without voice.
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'.