The widget keeps its state in a Zustand store inside the bundle. There is no published hook export today — useAppmintChat and useChatStore are not pulled out of the chat-client repo because there is no npm package. To drive your own UI off the same data, you have two practical options:
- Custom events. Listen for events the bundle dispatches on
window. Lightweight, works from any framework. - Reach into the bundle. After
AppmintChat.initruns, the bundle's store is available onwindow. Read it directly. More fragile — internal shapes can change.
Both patterns are sketches; for production usage, prefer option 1 plus your own state management.
Earlier docs showed import { useAppmintChat } from '@appmint/chat'. That import does not work — there is no @appmint/chat package, and the bundle does not export hooks. The patterns below are the actual ways to integrate today.
Window event listeners
The simplest cross-framework integration. Listen for the events the bundle dispatches:
window.addEventListener('appmint-chat:message-sent', (e: any) => {
analytics.track('chat.sent', { content: e.detail.content });
});
window.addEventListener('appmint-chat:message-received', (e: any) => {
analytics.track('chat.received', { from: e.detail.from });
});
Confirm the exact event names against chat-client/src/ — the bundle dispatches a small set of appmint-chat:* events on visitor activity.
Reading the store directly
After init runs, the bundle attaches its store factory under window. From there you can subscribe to changes the same way Zustand stores normally work — but treat this as an internal API: shape changes on every release.
// After init has run:
const store = (window as any).__appmintChatStore;
if (store) {
const unsubscribe = store.subscribe((state: any) => {
if (state.messages.length !== lastSeen) { /* react */ }
});
}
The exact key the bundle attaches to window is not documented as public surface — confirm in chat-client/src/index.tsx for the build you've deployed before relying on it.
What's in the store
The store is defined in src/chat-store.ts (ChatStoreState). The most useful fields:
| Field | Type | Description |
|---|---|---|
| messages | ChatMessage[] | Every message in the active conversation. Each entry has |
| sendMessage | (message: string, files?: any[]) => void | Send a message as the current visitor. Files are an array of |
| isTyping | boolean | True while a remote participant (agent or AI) is composing a reply. |
| aiStreamingMessages | Record<string, AiStreamingState> | Partial AI replies as they stream in. Keyed by message id. Each value has |
| aiWaitingForResponse | boolean | True between the moment the visitor sends a message and the first AI streaming chunk. |
| presenceMap | Record<string, PresenceEntry> |
|
| conversationStatus | Record<string, ConvoStatus> | Per-conversation status updates pushed by the server (assigned, transferred, closed, archived). |
| assignedAgent | AgentAssignment | null | Set when a queue request resolves to a specific agent. Includes |
| queueStatus | QueueStatus | null | While waiting in the agent queue: |
| user | object | null | The current visitor's customer record (after registration / signin). |
| token | string | null | The visitor's chat-scoped JWT. |
| myEmail | string | null | Convenience copy of |
| isChatOpen | boolean | Whether the floating panel is expanded. Toggle via |
| cleanup | () => void | Tear down the socket and reset state. Call before unmounting if you're remounting the widget with new props. |
Socket events the bundle handles
The widget subscribes to these on the AppEngine ChatGateway socket (defined in src/components/chat-socket.tsx and the on* actions in chat-store.ts):
message— new chat messagetyping/stop-typing— typing indicator from the other sidepresence— participant online/away/offlinestatus— conversation status (assigned / closed / transferred)chat-assigned/chat-transferred/agent-changed— agent routingmessage-status-update— read receipts and delivery confirmationsai-stream-start/ai-stream-chunk/ai-stream-tool-use/ai-stream-tool-result/ai-stream-end/ai-stream-error— AI streaming eventsengage— proactive notices (toast outside the panel)broadcast-start/broadcast-end— agent screen/camera broadcasts
Driving the widget from outside
To open the panel or pre-fill a message from outside the bundle, dispatch a custom event the bundle listens for, or reach into the store as described above. The recommended path (works without depending on internal store shape):
<button onclick="window.AppmintChat?.openPanel?.()">Help</button>
Confirm the helper names against chat-client/src/index.tsx — the bundle exposes a small set of imperative methods on window.AppmintChat for outside-the-panel control. Anything not exposed there requires forking the bundle.
Lifecycle
The widget mounts a single socket per page. Be careful with React Strict Mode in development — re-mounting the wrapper component will tear down and reconnect the socket during the double-mount cycle. Production builds aren't affected.
If you re-init the widget with a different orgId / configId, the existing socket is cleaned up automatically when the bundle observes the change.
When to use which integration path
- Window events / store reads — building UI outside the floating panel (a sidebar, a notification bell, a custom dashboard).
chat-configrecord edits — branding, welcome copy, AI assistants, availability windows. No code change needed.- Forking the bundle — replacing parts of the panel UI (no slot API today; see Slots).