@thg-agentic/chatbot-sdk-react-native
v0.2.5
Published
React Native SDK for the THG AI Shopping Assistant — exports a single <Chatbot /> component that host apps can mount however they like.
Readme
@thg-agentic/chatbot-sdk-react-native
React Native SDK for the THG AI Shopping Assistant. Exports a single
<Chatbot /> component that you mount wherever makes sense in your
app — full screen, modal, drawer, bottom sheet.
Install
npm install @thg-agentic/chatbot-sdk-react-nativeRequired peer dependencies:
npm install react@>=18 react-native@>=0.72 react-native-svgSSE streaming on React Native: the SDK uses
fetch+ReadableStreamfor AG-UI streaming. Bare RN 0.72+ supports this on iOS, but Android can need a polyfill. Installreact-native-polyfill-globalsand addimport 'react-native-polyfill-globals/auto';to your app entry if you see "response.body is undefined" errors.
Optional peers (per feature)
| Feature | Peer dependency | What you get |
|---|---|---|
| Persistent session/conversation across launches | @react-native-async-storage/async-storage (async) or react-native-mmkv (sync) | Conversation history survives app restart |
| Voice-to-text | @react-native-voice/voice | Mic button + live transcript |
| Image upload | expo-image-picker (preferred) or react-native-image-picker | Attach button + base64 upload |
| iOS Health activity connector | react-native-health | Lets the agent request a consent-gated Apple Health activity summary for fitness-informed Myprotein guidance |
If a peer isn't installed, the corresponding feature is silently hidden — no errors, no setup gymnastics.
iOS Health activity
The Health connector is opt-in at three layers:
- The backend
/v1/configcapability must returnhealthActivity: truefor the channel. - The native app must install and configure
react-native-healthwith the required iOS HealthKit entitlement andNSHealthShareUsageDescription. - The native app must pass a
health.readActivitycallback. The SDK declaresget_health_activityonly when both the backend capability and callback are present.
import { Chatbot } from '@thg-agentic/chatbot-sdk-react-native';
import {
createAppleHealthKitActivityProvider,
requestAppleHealthKitActivityAuthorization,
} from '@thg-agentic/chatbot-sdk-react-native/health/ios';
const health = createAppleHealthKitActivityProvider({
defaultLookbackDays: 14,
includeManuallyAdded: false,
});
// Recommended from a user action, e.g. a "Connect Apple Health" button.
await requestAppleHealthKitActivityAuthorization();
<Chatbot
config={{
channel: 'myprotein',
locale: 'en-GB',
apiUrl: 'https://api.thg.dev/agentic-commerce/shopping-assistant/ag-ui',
apiKey: '<per-storefront-key>',
health,
}}
/>;The adapter asks HealthKit for read access to step count, active energy,
walking/running distance, workouts, and sleep analysis. It returns a bounded
aggregate snapshot rather than raw samples, and partial permission/read failures
are reported in the snapshot so the agent can explain what is missing.
If the host does not preflight authorization, the adapter still calls HealthKit
authorization when the agent first invokes get_health_activity; preflighting
just makes the consent sheet explicit and easier to test.
iOS does not provide a supported deep link to an app's Health data-type permissions. If a user needs to review or change access after the first prompt, guide them to Health app > profile picture > Apps > your app, or Settings > Health > Data Access & Devices > your app.
Use
import { useEffect, useState } from 'react';
import { Chatbot } from '@thg-agentic/chatbot-sdk-react-native';
import type { StorageAdapter } from '@thg-agentic/chatbot-sdk-react-native';
import { createAsyncStorageAdapter } from '@thg-agentic/chatbot-sdk-react-native/storage/async-storage';
export function ChatScreen({ navigation }) {
const [storage, setStorage] = useState<StorageAdapter | null>(null);
useEffect(() => {
const adapter = createAsyncStorageAdapter();
let mounted = true;
adapter.ready().then(() => {
if (mounted) setStorage(adapter);
});
return () => { mounted = false; };
}, []);
if (!storage) return null;
return (
<Chatbot
config={{
channel: 'myprotein',
locale: 'en-GB',
apiUrl: 'https://api.thg.dev/agentic-commerce/shopping-assistant/ag-ui',
apiKey: '<per-storefront-key>',
theme: {
primaryColor: '#003942',
accentColor: '#003942',
},
content: {
headerTitle: 'Fuel Coach',
welcomeMessages: ['Hi! Ask me about anything from our range.'],
},
features: {
showSuggestions: true,
showFeedback: true,
imageUpload: true,
// In production this is overlaid from /v1/config; shown here only
// for local mocks that bypass the config bootstrap.
healthActivity: true,
},
health: createAppleHealthKitActivityProvider(),
}}
storage={storage}
onSettingsPress={() => navigation.navigate('Personalisation')}
onProductPress={(product) => navigation.navigate('PDP', { sku: product.sku })}
onError={(code, message) => console.warn('[Chatbot]', code, message)}
/>
);
}Props
| Prop | Type | Notes |
|---|---|---|
| config | ChatbotConfig | Required. Same shape as the web SDK. |
| storage | StorageAdapter | Optional. Defaults to in-memory (resets on reload). |
| onProductPress | (p: CarouselProduct) => void | Override default product navigation. Without an override, the SDK opens http(s) product URLs with Linking.openURL; relative catalog paths are resolved against config.siteUrl. |
| onError | (code, message) => void | Fires for every chat-stream error. |
| onSettingsPress | () => void | Optional. Renders a header control that lets the host open its own memory/connectors/personalisation screen. |
| onMemoryConsentChange | (detail: MemoryConsentChangedDetail) => void | Required (with onSettingsPress) to show the in-chat memory opt-in card — the SDK won't offer an opt-in it can't persist or that the user can't reverse. Fires on accept/decline so the host persists the decision; the SDK reflects it for the session (badge + per-request memory_settings). |
| style | ViewStyle | Applied to the outer container. |
The ChatbotConfig shape covers theme colors, content/i18n strings,
feature flags, analytics callbacks, and session/customer identifiers.
See @thg/chatbot-sdk-core's types.ts for the full interface.
Theming (multi-brand)
The UI is fully theme-driven — there are no brand colours baked into the
components. Every brand (Myprotein, Lookfantastic, Cult Beauty, …) ships the same
SDK and restyles it entirely through config.theme:
| Token | Drives |
|---|---|
| primaryColor | header title + icons, filled CTAs ("Turn on memory", send button when active), suggestion-chip outline, settings toggles |
| primaryTextColor | text/icon colour on primaryColor fills |
| accentColor | the sparkle mark, the active input-pill border, the voice waveform, review stars |
| textColor | body + answer text |
| textMutedColor | disclaimer, placeholder, review counts, struck-through RRP |
| surfaceColor | raised neutral fills — user bubble, input pill, header icon circles, "memory on" badge (defaults to light grey; set it for dark brands) |
| backgroundColor | chat background |
| borderColor | dividers, card outlines |
| errorColor | discount text + (with priceFormat: 'default') the "(N% off)" tag |
| radius | base corner radius |
| sparklesSvg | optional override for the header/suggestions sparkle glyph |
Anything left unset falls back to a neutral default, so a minimal { primaryColor,
accentColor } already produces a coherent look. The example app ships three brand
presets (config.ts) selectable with EXPO_PUBLIC_CHATBOT_BRAND=lookfantastic to
demonstrate that the identical screens restyle per brand.
Internationalisation
i18n works out of the box. All user-facing copy is resolved through
@thg/chatbot-sdk-core's label() against the locale pack chosen by
config.locale (31 built-in languages, including the cross-session memory copy).
Pass locale: 'fr-FR' (etc.) and the header, memory consent card, suggestions,
feedback, errors and thinking states all render translated — no per-brand string
tables required. Host overrides go in config.content (merged on top of the
locale pack), so a brand can sharpen a few strings (e.g. the memory examples)
without losing translations for everything else.
Storage adapters
The SDK uses a small, synchronous StorageAdapter contract:
interface StorageAdapter {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}Three built-in factories:
import { InMemoryStorageAdapter } from '@thg-agentic/chatbot-sdk-react-native';
import { createAsyncStorageAdapter } from '@thg-agentic/chatbot-sdk-react-native/storage/async-storage';
import { createMMKVStorageAdapter } from '@thg-agentic/chatbot-sdk-react-native/storage/mmkv';InMemoryStorageAdapter— default. Conversation resets on reload.createAsyncStorageAdapter()— async backend, sync façade via an in-memory cache pre-warmed at construction. Callawait adapter.ready()before mounting<Chatbot />so the first render reads the persisted session and conversation.createMMKVStorageAdapter()— fully synchronous, recommended if you're already using MMKV in your app.
You can also implement StorageAdapter against your own store
(SecureStore, Keychain, etc.).
What's not in this package
The web SDK has FAB triggers, popup/sidebar layouts, drag/resize, and a
launcher hub for multiple assistants. None of that is here — the host
app owns presentation. If you need a multi-assistant entry point, build
your own screen that decides which assistantId to pass.
Example app
A working Expo example lives at ./example. After
pnpm install at the repo root:
cd packages/chatbot-sdk/react-native/example
pnpm ios # or: pnpm android, pnpm web, pnpm startMetro is set up for the monorepo, so edits to this package (and to
@thg/chatbot-sdk-core) hot-reload without any extra build step. See
example/README.md for backend wiring and notes
on which features run in Expo Go vs. need a dev build.
Local development
This package lives in the THG chatbot-adk monorepo. From the repo root:
pnpm install
pnpm --filter @thg-agentic/chatbot-sdk-react-native typecheck
pnpm --filter @thg-agentic/chatbot-sdk-react-native test
pnpm --filter @thg-agentic/chatbot-sdk-react-native lintThe package's main entry resolves to ./src/index.ts (via the example's Metro
config) so linked monorepo consumers (e.g. the Expo example app under
./example) see live TypeScript edits without a build step.
The web SDK's mock API server at
packages/web/scripts/dev-server.js exposes POST /v1/chat for local
prototyping, but the AG-UI streaming endpoint isn't mocked there — use
a real dev backend or write a lightweight SSE mock for now.
