Build a page on the client
Build a web page of your own on @fxbot/sdk/client, against fxbot's server. The client is a typed wrapper for the server's HTTP API, with the stream reader and applyLine. It carries no credentials: keys go to the server once and never come back.
You need:
- fxbot's server, running: a release install, which answers on 127.0.0.1:8787, or
npm run serverin a checkout. - A project with the SDK as a workspace: steps 1 to 3 of Use the SDK in a project. A page needs no database or key of its own.
The steps use Vite, as fxbot's own page does. It compiles the client's TypeScript along with yours.
Install Vite:
npm install -D viteProxy
/apito fxbot's server, invite.config.ts:import { defineConfig } from "vite"; export default defineConfig({ server: { proxy: { "/api": { target: "http://127.0.0.1:8787" } }, }, });The server refuses a write whose
Origindoesn't match itsHost, and sends no CORS headers. So the page reaches it through a proxy on the page's own origin. Use the object form: Vite's string form,"/api": "http://127.0.0.1:8787", setschangeOrigin, which sends the server's own host, and every write fails.Write
index.html:<!doctype html> <meta charset="utf-8" /> <title>My bot</title> <pre id="log"></pre> <form><input name="text" autocomplete="off" /></form> <script type="module" src="/main.ts"></script>Write
main.ts. It shows the first bot's chat, and sends what you type:import { applyLine, createClient, type Turn } from "@fxbot/sdk/client"; const client = createClient(); // the page's own origin const log = document.querySelector("#log")!; const form = document.querySelector("form")!; const input = form.querySelector("input")!; const [bot] = await client.profiles.list(); let turns: Turn[] = []; form.addEventListener("submit", (event) => { event.preventDefault(); const text = input.value.trim(); input.value = ""; if (text) client.send(bot.id, { text, messageId: crypto.randomUUID() }).catch((error) => alert(error.message)); }); // Replays the chat, then each new line. It reconnects by itself after a drop. for await (const line of client.stream(bot.id)) { turns = applyLine(turns, line); log.textContent = turns.map((t) => `> ${t.text}\n${t.reply}${t.note ? `\n(${t.note})` : ""}`).join("\n\n"); }Start it, and open the address Vite prints:
npx vite
The page needs a bot. Make one in fxbot's own page, or from yours with client.profiles.create({ name, provider }).
What the client does
- Each call throws an
Errorwith the server's message, orCannot reach the server.when no answer comes. client.stream(id, { cursor, signal, onConnection })replays the chat fromcursor, 0 by default, then waits for new lines. After a drop, it asks again from the last cursor every 2 seconds, andonConnectionhears it go and come back. It hides lines from an older epoch, as libfx asks. Abortsignalto stop it.sendresolves once the server has stored the message. The reply comes on the stream.client.providers.add({ kind, apiKey })sends a key once. A listed provider says onlyhasKey.createClient({ onVersion })hears the server's version on every answer, so a page can notice a new release.
Every call is in The client, and the routes behind them in HTTP API.
In production
Build the page:
npx vite buildThen serve dist/ and /api from one origin, with a reverse proxy in front of both. It must pass the Host header on as the browser sent it, and pass the stream's lines on as they come, without buffering. fxbot's server has no login of its own, and the client sends no credentials, so whoever reaches the page can use all of fxbot through it. Where to put it: see Reach fxbot from other devices.
If it doesn't work
- Writes fail with
This request came from another site.The proxy changedHost, or the page calls the server from another origin. Use the object form in step 2, withoutchangeOrigin. - Every call says
Cannot reach the server.The proxy can't reach fxbot's server, or something answered with a page that isn't JSON. Check thatcurl http://127.0.0.1:8787/api/healthanswers. - A reply shows up all at once, at its end. A proxy holds the stream back. Turn buffering off for
/api/profiles/<id>/stream. - The chat stays empty. The bot has no chat until its first message. The stream asks again every 2 seconds, and shows it once you send one.