React Integration
Overview
The SDK's React adapter gives you hooks and pre-built components for building chat experiences. Everything imports from gecx-chat/react.
There are two ways to use it:
- Hooks only -- wire
useChatSessioninto your own markup. - Pre-built components -- drop in
ChatSurface,MessageList,Composer, and friends for a working UI out of the box.
Both approaches work together. Start with the pre-built components, then replace individual pieces as your design requires.
ChatProvider
ChatProvider is the root context provider. Wrap your app (or the chat subtree) with it so that hooks like useChatClient, useIdentity, and useConversations can access the client.
import { ChatProvider } from 'gecx-chat/react';
import { createChatClient } from 'gecx-chat';
const client = createChatClient({
auth: tokenEndpointAuth({ endpoint: '/api/gecx-chat-token' }),
});
function App() {
return (
<ChatProvider client={client}>
{/* your chat UI */}
</ChatProvider>
);
}
Props
| Prop | Type | Description |
|---|---|---|
client | ChatClient | Required. Created via createChatClient(). |
renderers | RendererMap | Optional. Custom part renderers (see Custom Renderers). |
ChatProvider is not required if you pass config or client directly to useChatSession. But it is required for useChatClient, useIdentity, and useConversations.
useChatSession
The main hook. It manages the full chat lifecycle: session creation, streaming, message state, errors, input, and cleanup.
import { useChatSession, MessagePart } from 'gecx-chat/react';
function SupportChat() {
const {
messages,
status,
error,
canSend,
isStreaming,
input,
sendText,
stop,
} = useChatSession();
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
{msg.parts.map((part) => (
<MessagePart key={part.id} part={part} />
))}
</div>
))}
{error && <p>{error.message}</p>}
<form onSubmit={(e) => { e.preventDefault(); sendText(input.value); }}>
<input
value={input.value}
onChange={(e) => input.set(e.currentTarget.value)}
disabled={!canSend}
/>
<button type="submit" disabled={!canSend}>Send</button>
{isStreaming && <button type="button" onClick={stop}>Stop</button>}
</form>
</div>
);
}
Options
Pass an options object to configure behavior:
const chat = useChatSession({
client, // optional -- uses ChatProvider context if omitted
config, // optional -- creates a client internally
autoStart: true, // default true; set false to defer session creation
onMessage: (msg) => console.log(msg),
onError: (err) => reportError(err),
onStatusChange: (status) => analytics.track(status),
});
Return value
| Field | Type | Description |
|---|---|---|
messages | ChatMessage[] | Current message list, updated on every stream event. |
status | SessionStatus | 'idle' / 'connecting' / 'ready' / 'error' / 'ended'. |
error | Error | null | Latest error, or null. |
canSend | boolean | true when status is ready and not streaming. |
isStreaming | boolean | true while the agent is responding. |
input | ChatInputState | Managed input state: { value, set, clear }. |
handoffStatus | HandoffStatus | 'none' / 'requested' / 'active' / 'completed'. |
capabilities | SessionCapabilities | What the current session supports. |
metadata | SessionMetadata | null | Session metadata from the server. |
send | (text?, options?) => Promise | Send the current input value (or provided text). |
sendText | (text, options?) => Promise | Send a specific text string. |
sendToolResponse | (args) => Promise | Respond to a tool call from the agent. |
attachFile | (file: File) => AsyncIterable | Attach and upload a file. |
regenerate | () => Promise | Re-generate the last agent response. |
stop | () => void | Cancel the current streaming response. |
reset | () => Promise | Reset the session and clear messages. |
setApprovalHandler | (handler) => void | Register a callback for tool approval requests. |
governance | SessionGovernanceFacade | null | Session-scoped data governance. |
clientGovernance | ChatGovernance | Client-wide governance handle. |
createDebugBundle | () => DebugBundle | null | Snapshot diagnostics for support. |
session | ChatSession | null | The underlying session object (advanced use). |
useChatClient
Access the ChatClient instance from ChatProvider context. Useful when you need to create sessions manually, inspect capabilities, or access identity and conversation APIs outside of useChatSession.
import { useChatClient } from 'gecx-chat/react';
function DebugPanel() {
const client = useChatClient();
const caps = client.getCapabilities();
return <pre>{JSON.stringify(caps, null, 2)}</pre>;
}
Throws if called outside a <ChatProvider>.
useIdentity
Access and manage the current user's identity state.
import { useIdentity } from 'gecx-chat/react';
function UserBadge() {
const { identity, upgradeToAuthenticated, signOut } = useIdentity();
if (identity.type === 'guest') {
return <button onClick={() => upgradeToAuthenticated({ jwt: token })}>Sign in</button>;
}
return (
<div>
<span>{identity.identityId}</span>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
Return value
| Field | Type | Description |
|---|---|---|
identity | ChatIdentity | Current identity (type, identityId, claims). |
upgradeToAuthenticated | (input) => Promise | Upgrade from guest to authenticated. |
signOut | (options?) => Promise | Sign out and revert to guest. |
setClaims | (claims) => Promise | Set custom identity claims. |
useConversations
List, create, and manage conversation history. Requires <ChatProvider>.
import { useConversations } from 'gecx-chat/react';
function ConversationList() {
const { conversations, create, remove } = useConversations();
return (
<ul>
{conversations.map((c) => (
<li key={c.id}>
{c.title ?? 'Untitled'}
<button onClick={() => remove(c.id)}>Delete</button>
</li>
))}
<button onClick={() => create()}>New conversation</button>
</ul>
);
}
Return value
| Field | Type | Description |
|---|---|---|
conversations | ConversationDescriptor[] | All conversations for the current identity. |
identityId | string | The identity owning these conversations. |
create | (input?) => Promise | Create a new conversation. |
touch | (id, patch?) => Promise | Update a conversation's title or metadata. |
remove | (id) => Promise | Delete a conversation. |
importRemote | (descriptors) => Promise | Import conversations from an external source. |
Hooks reference
All hooks are imported from gecx-chat/react unless noted.
| Hook | Returns | What it does |
|---|---|---|
useChatSession | { messages, status, send, input, error, attachments, voice, ... } | The main hook — owns one ChatSession. |
useChatClient | ChatClient | The shared client (multi-session). |
useChatStore | The reactive store | Subscribe to fine-grained store updates. |
useIdentity | { identity, signIn, signOut, upgrade } | Identity lifecycle. |
useConversations | { conversations, refresh, ... } | Conversation registry. |
useClientToken | { token, refresh, isLoading, error } | TTL-cached client token helper. |
useRecaptcha | { execute } | Imperative reCAPTCHA challenge. |
useRecaptchaToken | { token, refresh, isLoading, error } | TTL-cached reCAPTCHA token (default TTL 110 s). |
useImpressionRef | (ref, type, payload?) => void | Fires an *_impression analytics event when the ref enters the viewport. |
useMemory | Memory query state | Long-term memory read/write. |
useSentiment | { latest, history, byCategory } | Pure memoized selector over signal parts. |
useIntent | { latest, history, byIntent } | Pure memoized selector over intent signal parts. |
usePermission | { status, request, ... } | Drive one capability. |
usePermissionManager | PermissionManager | The full manager for multi-capability flows. |
useSentiment and useIntent example
Pure selectors over chat.messages that surface sentiment and intent signals streamed by the signal subsystem. They re-run on every messages update and memoize internally.
import { useChatSession, useSentiment, useIntent } from 'gecx-chat/react';
function SentimentAwareUI() {
const chat = useChatSession();
const { latest: sentiment, byCategory } = useSentiment(chat.messages);
const { byIntent } = useIntent(chat.messages);
const polarity = sentiment?.polarity ?? 'neutral';
return (
<div data-polarity={polarity}>
{byIntent.refund_request && <RefundCta />}
{byCategory.frustration?.score && byCategory.frustration.score > 0.7 && <CalmDownBanner />}
</div>
);
}
Both hooks accept { historyLimit } (default 50). See sentiment-and-intent.md for adapter configuration and escalation rules.
Pre-built components
These are unstyled, accessible building blocks. Add your own CSS or wrap them in styled wrappers.
| Component | Props | Description |
|---|---|---|
ChatSurface | brand, title, status, children, actions?, className? | Main chat container with header showing brand, title, and status. |
MessageList | messages, emptyState? | Renders the message transcript. Uses MessagePart for each part. |
Composer | input, canSend, isStreaming, onSend, onStop?, placeholder? | Text input with send button, stop button when streaming. |
SuggestionBar | suggestions, onSelect | Quick-reply chip buttons. |
ToolApprovalSurface | toolName, open, onApprove, onDeny | Modal dialog for tool permission requests. |
HandoffBanner | status, targetAgent? | Status banner during agent handoff. |
DebugDrawer | open, children | Collapsible panel for developer diagnostics. |
MessagePart | part | Renders a single ChatMessagePart. Checks the renderer registry first, then falls back to built-in defaults. |
VoiceToggle | voice | Push-to-talk button with mic-permission probe and inline remediation. From gecx-chat/react/voice. |
VoiceComposer | voiceSession, mode? | Imperative voice composer for direct VoiceSession use. From gecx-chat/react/voice. |
TranscriptDisplay | messages | Renders TranscriptPart history with interim/final styling. From gecx-chat/react/voice. |
PermissionPrompt | capability, onGranted?, copy? | Default prompt for a single capability with bundled copy. |
ComputerUseSurface | part | Sandboxed iframe + action log + consent banner + per-action approval + Abort. |
MemoryList | (no props — reads from useMemory) | Default memory drawer. |
MetricsDashboard | events, window? | Composite five-widget dashboard. From gecx-chat/dashboards. |
DeflectionTrend, CsatDistribution, AhtHistogram, AgentAssistRate, GmvBreakdown | events, window? | Individual dashboard widgets. From gecx-chat/dashboards. |
Wiring components to the hook
import {
ChatSurface,
MessageList,
Composer,
HandoffBanner,
} from 'gecx-chat/react';
import { useChatSession } from 'gecx-chat/react';
function Chat() {
const chat = useChatSession();
return (
<ChatSurface brand="Acme" title="Support" status={chat.status}>
<MessageList messages={chat.messages} emptyState="Ask us anything." />
{chat.handoffStatus !== 'none' && (
<HandoffBanner status={chat.handoffStatus} />
)}
<Composer
input={chat.input}
canSend={chat.canSend}
isStreaming={chat.isStreaming}
onSend={(text) => chat.sendText(text)}
onStop={chat.stop}
/>
</ChatSurface>
);
}
What's next
- Custom Renderers -- override how any message part type renders.
- Messages & Parts -- every part type and its shape.
- Generative UI -- let the agent produce dynamic UI surfaces.
- Voice Integration --
voice: 'auto'and the React voice surface. - Sentiment and Intent -- the signal hooks in practice.
- Memory --
useMemoryandMemoryList.
docs/guides/react-integration.md