Open source · React · MIT

Embeddable React AI chat widget

@anter/ai-chat-sdk

A drop-in React chat widget for any website. Stream conversations from any Anter agent — reasoning steps, tool calls, and rich artifacts included — and theme it to your brand with CSS variables. You wire your backend through one typed adapter; nothing else is hardcoded.

Key Features

One small dependency

A headless hook plus prebuilt widget and full-page chat UI. Streams Markdown, reasoning steps, tool calls, and artifacts out of the box.

Adapter, not lock-in

You implement one `ChatAdapter.sendMessage` that returns a stream. Auth, headers, and proxying stay in your control — secrets never ship to the browser.

Themeable by CSS variables

Drives off `--chat-*` custom properties and a no-reset stylesheet, so it inherits your brand without fighting your global CSS.

Install

Not on npm yet — install straight from the public repo. It builds itself on install, so no extra step is needed. Import the stylesheet once at your app entry; use styles-no-base.css to avoid global CSS resets and keep full control of your own cascade.

# Not published to npm yet — install from the public repo.
# The package builds itself on install.

# npm
npm install github:anter-ai/ai-chat-sdk

# pnpm
pnpm add github:anter-ai/ai-chat-sdk

# Pin to a commit for a reproducible install:
npm install github:anter-ai/ai-chat-sdk#<commit-sha>

# Load the widget styles once, at your app entry:
import "@anter/ai-chat-sdk/styles-no-base.css";

React AI Chat SDK Tutorial: How to Embed a Streaming Chat Widget

To embed a streaming AI chat widget into any React application, install @anter/ai-chat-sdk, import its CSS variable stylesheet once, and connect your backend using a single typed ChatAdapter implementation.

  • Backend-Agnostic Adapter: Your server manages authentication and API keys while streaming SSE chunks to the UI via ChatAdapter.sendMessage.
  • Theme Custom Properties: Customize colors and typography using --chat-* CSS variables without breaking your existing app styles.
  • Built-In Artifact & Reasoning Tracing: Automatically renders Markdown streams, tool call execution accordions, and sub-agent handoffs.

Quickstart Code

Implement a ChatAdapter that streams from your agent endpoint, then wrap your app in ChatProvider and drop in a ChatWidget.

1 · Adapter

import type { ChatAdapter, MessagePayload } from "@anter/ai-chat-sdk/types";

export class LandingChatAdapter implements ChatAdapter {
  constructor(private readonly agentId: string) {}

  private get streamPath(): string {
    return `/api/v1/external/agent-runner/agents/${this.agentId}/run-stream`;
  }

  async sendMessage(payload: MessagePayload): Promise<ReadableStream<Uint8Array>> {
    const res = await fetch(this.streamPath, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        message: payload.message,
        organizationId: payload.organizationId,
        // contextVariables flow through to the agent. Pass per-session identity
        // so the runner can scope memory/history to this conversation.
        contextVariables: {
          ...(payload.contextVariables ?? {}),
          userId: `chat-widget-${payload.sessionId}`,
          sessionId: payload.sessionId,
        },
      }),
    });
    if (!res.ok) throw new Error(`sendMessage failed: ${res.status}`);
    if (!res.body) throw new Error("sendMessage: missing response body");
    return res.body;
  }
}

2 · Provider + widget

import { ChatProvider, ChatWidget } from "@anter/ai-chat-sdk";
import { LandingChatAdapter } from "./lib/landing-adapter";

const organizationId = (import.meta.env.VITE_ORG_ID as string | undefined) ?? "anter";
const chatAgentId = import.meta.env.VITE_CHAT_AGENT_ID as string;
const adapter = new LandingChatAdapter(chatAgentId);

<ChatProvider
  organizationId={organizationId}
  adapter={adapter}
  config={{
    theme: "light",
    enableModelSelector: false,
    enableSlashCommands: false,
    enableCommandPalette: false,
    enableFileUpload: false,
    enableSlashFocusShortcut: false,
    enableArtifacts: true,
  }}
  strings={{
    composerPlaceholder: "Ask a question...",
    footerDisclaimer: "AI responses can contain mistakes.",
  }}
>
  {page}
  <ChatWidget position="bottom-right" title="Anter" />
</ChatProvider>
Keep credentials server-side. Point the adapter at a proxy path (e.g. /api/...) and inject x-project-id / x-api-key there — never in browser code.

Configuration

Pass a config object to ChatProvider to tune the experience. The "Landing value" column shows the choices used on this very site.

OptionLanding valueWhy usedOther options
enableModelSelectorfalseKeep model choice controlled by backend/flow configuration.Set `true` to expose model switching to end users.
enableSlashCommandsfalseSimplify consumer-facing UX.Set `true` for power-user workflows and command shortcuts.
enableCommandPalettefalseAvoid advanced command UI in public marketing chat.Enable for internal apps or operator consoles.
enableFileUploadfalseLimit public-surface risk and keep scope to text chat.Enable for document workflows once validation is in place.
enableSlashFocusShortcutfalsePrevent accidental shortcut conflicts on landing pages.Enable for app-like experiences with keyboard-heavy users.
enableArtifactstrueAllow rich response rendering with artifacts.Disable if your use case requires strict plain-text output only.

Theming

The widget renders against --chat-* CSS custom properties (accent, surfaces, borders, message text). Override them in your own stylesheet to match your design system — light and dark adapt automatically. Because the SDK ships its styling as raw, prefixed ais-* classes rather than relying on a Tailwind runtime, it drops cleanly into any host without a build-time theme step.

Guides & Where it's used