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:

  1. Hooks only -- wire useChatSession into your own markup.
  2. 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

PropTypeDescription
clientChatClientRequired. Created via createChatClient().
renderersRendererMapOptional. 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

FieldTypeDescription
messagesChatMessage[]Current message list, updated on every stream event.
statusSessionStatus'idle' / 'connecting' / 'ready' / 'error' / 'ended'.
errorError | nullLatest error, or null.
canSendbooleantrue when status is ready and not streaming.
isStreamingbooleantrue while the agent is responding.
inputChatInputStateManaged input state: { value, set, clear }.
handoffStatusHandoffStatus'none' / 'requested' / 'active' / 'completed'.
capabilitiesSessionCapabilitiesWhat the current session supports.
metadataSessionMetadata | nullSession metadata from the server.
send(text?, options?) => PromiseSend the current input value (or provided text).
sendText(text, options?) => PromiseSend a specific text string.
sendToolResponse(args) => PromiseRespond to a tool call from the agent.
attachFile(file: File) => AsyncIterableAttach and upload a file.
regenerate() => PromiseRe-generate the last agent response.
stop() => voidCancel the current streaming response.
reset() => PromiseReset the session and clear messages.
setApprovalHandler(handler) => voidRegister a callback for tool approval requests.
governanceSessionGovernanceFacade | nullSession-scoped data governance.
clientGovernanceChatGovernanceClient-wide governance handle.
createDebugBundle() => DebugBundle | nullSnapshot diagnostics for support.
sessionChatSession | nullThe 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

FieldTypeDescription
identityChatIdentityCurrent identity (type, identityId, claims).
upgradeToAuthenticated(input) => PromiseUpgrade from guest to authenticated.
signOut(options?) => PromiseSign out and revert to guest.
setClaims(claims) => PromiseSet 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

FieldTypeDescription
conversationsConversationDescriptor[]All conversations for the current identity.
identityIdstringThe identity owning these conversations.
create(input?) => PromiseCreate a new conversation.
touch(id, patch?) => PromiseUpdate a conversation's title or metadata.
remove(id) => PromiseDelete a conversation.
importRemote(descriptors) => PromiseImport conversations from an external source.

Hooks reference

All hooks are imported from gecx-chat/react unless noted.

HookReturnsWhat it does
useChatSession{ messages, status, send, input, error, attachments, voice, ... }The main hook — owns one ChatSession.
useChatClientChatClientThe shared client (multi-session).
useChatStoreThe reactive storeSubscribe 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?) => voidFires an *_impression analytics event when the ref enters the viewport.
useMemoryMemory query stateLong-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.
usePermissionManagerPermissionManagerThe 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.

ComponentPropsDescription
ChatSurfacebrand, title, status, children, actions?, className?Main chat container with header showing brand, title, and status.
MessageListmessages, emptyState?Renders the message transcript. Uses MessagePart for each part.
Composerinput, canSend, isStreaming, onSend, onStop?, placeholder?Text input with send button, stop button when streaming.
SuggestionBarsuggestions, onSelectQuick-reply chip buttons.
ToolApprovalSurfacetoolName, open, onApprove, onDenyModal dialog for tool permission requests.
HandoffBannerstatus, targetAgent?Status banner during agent handoff.
DebugDraweropen, childrenCollapsible panel for developer diagnostics.
MessagePartpartRenders a single ChatMessagePart. Checks the renderer registry first, then falls back to built-in defaults.
VoiceTogglevoicePush-to-talk button with mic-permission probe and inline remediation. From gecx-chat/react/voice.
VoiceComposervoiceSession, mode?Imperative voice composer for direct VoiceSession use. From gecx-chat/react/voice.
TranscriptDisplaymessagesRenders TranscriptPart history with interim/final styling. From gecx-chat/react/voice.
PermissionPromptcapability, onGranted?, copy?Default prompt for a single capability with bundled copy.
ComputerUseSurfacepartSandboxed iframe + action log + consent banner + per-action approval + Abort.
MemoryList(no props — reads from useMemory)Default memory drawer.
MetricsDashboardevents, window?Composite five-widget dashboard. From gecx-chat/dashboards.
DeflectionTrend, CsatDistribution, AhtHistogram, AgentAssistRate, GmvBreakdownevents, 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

Source: docs/guides/react-integration.md