Skip to main content
Two React components ship from @waniwani/sdk/chat. Pick based on who runs the chat backend: The React components mount inline: they render wherever you place them in your layout, and you control the container. There is no launcher, floating bubble, or overlay panel in the React path. Those live in the embed script, as floating mode (a docked bar) and composer mode (an in-page input that opens the panel on send). To put the React chat in a sidebar or drawer of your own that opens from your buttons, see In a sidebar or drawer.
Platform feature. Requires a Waniwani project token (wwp_...). Get one at app.waniwani.ai or read more about the Platform.

Install

Add the stylesheet to your app once (e.g. in _app.tsx or your root layout):

WaniwaniChat

The hosted chat. Pass your project token and channel ID and it talks to app.waniwani.ai directly — no API route, no proxy, no backend work on your side.
The system prompt, welcome message, placeholder, suggestions, thread history and tool-call display are configured per channel in the dashboard, so business users can change them without a deploy. Theming has no dashboard equivalent — set it in code via overrides.appearance.

Sizing and placement

WaniwaniChat fills its parent. Put it wherever you want it and size the container:
To use it as a full-width page hero, give the wrapper the height you want and let the chat fill it. className is applied to the root element if you’d rather style it directly.

Per-page overrides

The dashboard is the source of truth. Use overrides only for a local tweak that doesn’t justify a separate channel:

Control the chat from your page

Pass a ref to drive the chat from anywhere else on the page: a call to action in your hero, a link in your navigation, a pricing row that asks about itself. The ref holds a ChatHandle.
Calls made before the chat has loaded its channel config are dropped, so wire the handle to a visitor’s click rather than calling it during the first render. On a page that the dashboard’s per-URL rules hide, the component renders nothing and the chat methods do nothing.

In a sidebar or drawer

The React handle has no open() or close(), because WaniwaniChat is on screen wherever you mount it. A sidebar, drawer or modal around it is your own component, and its open state lives in your code. A button anywhere on the page flips that state, and can send a first message through the ref in the same click:
The chat stays mounted while the sidebar is closed, hidden with CSS. Rendering it only when open is true would mount it on the very click that sends the question, before it has loaded, and that first message would be dropped. Keeping it mounted also means the visitor finds their conversation where they left it when they reopen the sidebar. If you’d rather use the Waniwani panel than build your own, load the embed script in floating or composer mode and call window.WaniWani.chat.open() from your React components. See Control the chat from your page.

Tracking from the host page

The same ChatHandle carries track and identify. identify takes two positional arguments, the user id first and optional traits second:
Both are present on WaniwaniChat only — the bare ChatEmbed primitive has no Waniwani credential to send events with. See Identify.

The page the visitor is on

From 0.24.1, WaniwaniChat sends the address of the current page with each message, and your agent is told which page the visitor is on. There is nothing to set up. The page the visitor is on shows what the agent gets and how to tell it what your pages are.

ChatEmbed

A bare-bones primitive for when you run the chat backend yourself. api is required and there is no default; pass any endpoint implementing the Vercel AI SDK chat protocol and the component handles the rest.
Reach for ChatEmbed only when self-hosting. If you’re using the Waniwani Platform, WaniwaniChat gives you the same UI plus dashboard configuration, visitor correlation, and tracking.
Both components accept a visitorId prop to override the anonymous id the widget generates with one you already track (a PostHog / Amplitude / Segment distinct id, your own cookie). Waniwani correlates its sessions and events to the same visitor your analytics sees, and your server-side MCP tools and flows read it back as context.waniwani.visitorId. Leave it unset to keep the auto-generated, localStorage-persisted id. visitorId accepts a string, or a resolver that returns one. The resolver may be sync or async, which is handy when your analytics SDK only exposes its id after it bootstraps:
A blank or failed result is ignored, so a not-ready id never wipes a good one. The value is read live on every request, so updating it applies to the next message. See Sessions for how the three correlation ids fit together.
For an expensive async resolver, pass a stable reference (useCallback) so it isn’t re-invoked on every render.

Attachments

ChatEmbed takes the documents module’s state as a prop, since it fetches no remote config of its own:
The composer offers a paperclip, a drop target and paste only while enabled is true, and the upload endpoint enforces the same flag. WaniwaniChat and the script embed read all of this from the dashboard, so they need none of it. See Documents for what an agent does with the files.

Authentication

headers goes out with every chat request, and document uploads reuse it, so one bearer usually covers both.
Set uploadHeaders when your upload endpoint takes a different credential than your chat endpoint. It is merged over headers on the document upload and discard calls, and leaves the chat request alone.
Uploads there go out with the upload token and still carry X-Tenant-Id. Only the names you repeat are replaced, and the match ignores case, so authorization overrides Authorization. Both land in the browser, so put only public credentials in them.

Theming

Both components take an appearance preset (light, dark, or auto) with optional per-property overrides layered on top:
auto follows the host’s prefers-color-scheme. variables accepts every ChatTheme token — colors, message-bubble radius/padding/max-width, base font size and line height. The full token and CSS-variable list lives on Theming & customization.

Deep customization

Two escape hatches when tokens aren’t enough:
  • appearance.assistantBubble — opt-in filled bubble for assistant replies (plain text by default), styled by assistantBubbleColor / assistantBubbleTextColor.
  • classNames — per-slot class overrides merged onto the widget’s own classes: root, header, message, userBubble, assistantBubble, input. Pass it directly on ChatEmbed, or as overrides.classNames on WaniwaniChat.
Recipes and the list of what remains non-themeable: Theming & customization.

Mirroring events into your analytics

The hosted WaniwaniChat component accepts an onEvent prop that fires on chat lifecycle events (messages, session start, errors, link clicks) so you can mirror widget activity into your own analytics — Segment, Amplitude, gtag — with the page’s identity attached automatically. ChatEmbed, the bring-your-own-backend primitive, does not expose it. See Widget events (onEvent).