@tagit/ai-client-react
v1.2.0
Published
Self-contained React chat widget and hooks for TagIt AI client
Readme
@tagit/ai-client-react
React integration layer for tagIt AI.
Use this package when you want a self-contained chat experience in a React app. It builds on @tagit/ai-client-core, which owns the client engine, orchestrator transport, state, and collector emission. The core package README is the source of truth for those engine details.
What Is tagIt?
tagIt is the platform for building governed AI experiences. It gives teams a way to define AI use cases, connect models, optionally attach MCP servers, and shape the orchestration logic that drives the overall experience.
What Is the AI Orchestrator?
The AI Orchestrator is the runtime layer that executes a use case. It combines the configured model, optional tools and MCP servers, conversation context, and policy behavior to produce the response flow for a specific scenario.
Why These Packages Exist
The @tagit/ai-client-core and @tagit/ai-client-react packages are the client-side pieces of that system. They connect your app to a tagIt account and a configured orchestrator so you can embed AI experiences in a product or website.
You need a tagIt account and at least one configured use case before these packages can drive a real experience. MCP servers are optional, but they can extend the orchestrator with tools and external capabilities.
To learn more, visit tagit.live.
This README is the source of truth for integrating the React package.
What This Package Provides
AIClientProviderfor React contextuseAIClient,useConversation,useClientEvents, anduseAgentsChatWidgetfor a full embeddable chat shellChatConversationfor a lighter conversation surface- the widget stylesheet for direct import
Provider Contract
AIClientProvider is the only required wrapper.
| Field | Type | Required | What it does | Example |
|---|---|---:|---|---|
| client | AIClient | yes | The live client instance used by hooks and widgets below the provider. | client={client} |
| children | ReactNode | yes | The React subtree that receives AI client context. | <ChatWidget /> |
<AIClientProvider client={client}>
<ChatWidget />
</AIClientProvider>Public Imports
Supported public imports:
@tagit/ai-client-core@tagit/ai-client-core/config@tagit/ai-client-core/types@tagit/ai-client-react@tagit/ai-client-react/chat-widget@tagit/ai-client-react/chat-widget.css@tagit/ai-client-react/hooks@tagit/ai-client-react/provider@tagit/ai-client-react/conversation
Do not import from dist/* or from internal source paths.
Install
npm install @tagit/ai-client-core @tagit/ai-client-reactMinimal Working Example
This is the smallest known-good React embed.
import { AIClient, createDefaultConfig } from '@tagit/ai-client-core';
import { AIClientProvider, ChatWidget } from '@tagit/ai-client-react';
import '@tagit/ai-client-react/chat-widget.css';
const client = new AIClient(
createDefaultConfig('your-use-case-id', 'https://your-orchestrator.example.com', {
user: {
entityId: 'anonymous-or-authenticated-user-id',
entityidType: 'internal_user_id',
entityidAlgo: 'native',
entityidOwner: 'your-service',
},
conversation: {
conversationId: 'optional-durable-conversation-id',
title: 'Optional conversation title',
memoryMaxTurns: 6,
},
collector: {
enabled: false,
},
}),
);
export default function App() {
return (
<AIClientProvider client={client}>
<ChatWidget />
</AIClientProvider>
);
}If that exact example does not render the default empty state and footer, verify the package install and runtime config before customizing anything.
Core Concepts
@tagit/ai-client-core
This is the headless engine:
- orchestrator config and transport
- conversation state
- SSE streaming
- retry and timeout handling
- collector emission
@tagit/ai-client-react
This is the React shell:
- provider + hooks
- stock chat widget
- inline or overlay rendering
- shell styling and theming
Treat the core package as the engine and the React package as the UI layer on top.
Widget Modes
ChatWidget supports two layout APIs:
presentation, the preferred API for new integrationsmode, the legacy API retained for existing integrations
Use presentation when embedding the widget into production applications:
presentation="overlay"for floating chat over host contentpresentation="sidePanel"for a viewport-docked panel where the host reserves side spacepresentation="inline"for embedded layouts inside the host page
The older mode="overlay" and mode="inline" props continue to work.
For legacy inline mode, there are two common host patterns:
dockMode="page"for a page-anchored raildockMode="viewport"for a viewport-docked rail that stays fixed to the browser edge
Use presentation="sidePanel" for new viewport sidebar integrations. The SDK keeps the panel pinned to the browser edge, owns resize math, and automatically switches to mobile fullscreen at the configured breakpoint. The host should only reserve page space from the controlled open and width values.
Choosing a Presentation
| Use case | Recommended presentation | Host responsibility |
|---|---|---|
| Marketing site, support bubble, simple app assistant | presentation="overlay" | None, unless you want a backdrop or controlled open state. |
| Application copilot that should push content aside | presentation="sidePanel" | Reserve side space from open and width; let the SDK render and resize the panel. |
| Fully embedded chat region inside a page layout | presentation="inline" | Provide the parent container size. |
| Existing integrations already using mode | Keep mode for now | Migrate to presentation when you next touch layout code. |
What The SDK Owns
The React package owns:
- panel shell rendering
- viewport docking
- resize handle behavior
- mobile fullscreen takeover
- tab positioning
- close and new-chat controls
- stock conversation body
The host application should not wrap the widget in invisible resize frames or constrain the panel with max-width: 100% when using presentation="sidePanel". Those patterns can break bidirectional resize math.
What The Host Owns
The host application owns:
- the
AIClientProviderand configured core client - whether the panel is controlled or uncontrolled
- reserved page space for
presentation="sidePanel" - theme/color values when the default palette is not enough
- application-specific quick actions, page context, or custom callbacks
Overlay Example
<ChatWidget
presentation="overlay"
side="right"
title="tagIt Assistant"
showBackdrop
closeOnOutsideClick
/>Side-Panel Example
const [open, setOpen] = useState(false);
const [width, setWidth] = useState('420px');
return (
<div
style={{
minHeight: '100dvh',
paddingRight: open ? width : 0,
transition: 'padding-right 180ms ease',
}}
>
<main>
<AppContent />
</main>
<ChatWidget
presentation="sidePanel"
side="right"
open={open}
onOpenChange={setOpen}
width={width}
onWidthChange={setWidth}
minWidthPx={360}
maxWidthPx={720}
mobileBreakpointPx={768}
mobilePresentation="fullscreen"
title="Tacky"
/>
</div>
);For a left-side panel, reserve paddingLeft instead of paddingRight.
Side-Panel Integration Checklist
- Use controlled
openandonOpenChange. - Store
widthas a string, for example'420px'. - Pass the same
widthto the widget and to the host reserved space. - Keep
minWidthPxandmaxWidthPxaligned with your application layout. - Use
mobilePresentation="fullscreen"unless your mobile layout has a strong reason not to. - Do not put the widget inside a parent that clips overflow or limits width when using
presentation="sidePanel". - Do not implement a separate resize handle in the host application.
Legacy Page-Rail Inline Example
<ChatWidget
mode="inline"
dockMode="page"
side="right"
open={true}
title="tagIt Assistant"
tabLabel="AI Client"
/>Legacy Viewport-Docked Inline Example
<ChatWidget
mode="inline"
dockMode="viewport"
side="right"
open={isOpen}
onOpenChange={setIsOpen}
title="tagIt Assistant"
tabLabel="AI Client"
tabHeightPercent={18}
width={440}
minWidthPx={360}
maxWidthPx={440}
/>Prefer the new side-panel example for new application copilot integrations. The legacy viewport-docked inline mode remains available for existing apps that already use it.
Widget Configuration Dictionary
This section documents the public ChatWidget props that matter for integrations. If a field is omitted, the widget uses its built-in default.
Layout and shell fields
| Field | Type / values | Default | What it does | Example |
|---|---|---:|---|---|
| presentation | 'overlay' \| 'sidePanel' \| 'inline' | derived from mode | Preferred layout API for new integrations. sidePanel is viewport-docked and designed for host-reserved side space. | presentation="sidePanel" |
| mode | 'overlay' \| 'inline' | overlay | Chooses between floating chat and embedded chat. | mode="inline" |
| dockMode | 'page' \| 'viewport' | page | Controls how inline mode is docked. page is a normal page rail. viewport pins the rail to the browser edge. | dockMode="viewport" |
| side | 'left' \| 'right' | right | Chooses which side the tab and panel live on. | side="right" |
| open | boolean | uncontrolled | Controlled open state. Use when the host needs to manage layout changes. | open={isOpen} |
| defaultOpen | boolean | false | Uncontrolled initial open state. | defaultOpen={true} |
| onOpenChange | (open: boolean) => void | undefined | Callback fired when the widget opens or closes. | onOpenChange={setIsOpen} |
| width | number \| string | 420 | Sets the panel width. Numbers are treated as pixels. | width={440} or width="32vw" |
| resizable | boolean | true | Enables or disables the drag resize handle. | resizable={false} |
| minWidthPx | number | 280 | Minimum panel width in pixels when resizing. | minWidthPx={360} |
| maxWidthPx | number | undefined | Maximum panel width in pixels when resizing. | maxWidthPx={520} |
| mobileBreakpointPx | number | 720 | Width breakpoint where the widget switches to mobile behavior. | mobileBreakpointPx={768} |
| mobilePresentation | 'fullscreen' \| 'default' | fullscreen | Controls mobile behavior. fullscreen makes the panel a 100vw x 100dvh takeover and disables resize. | mobilePresentation="fullscreen" |
| tabHeightPercent | number from 0 to 100 | 40 | Positions the tab vertically. 100 is near the top, 0 near the bottom. | tabHeightPercent={18} |
| className | string | undefined | Adds host styling hooks to the widget root. | className="product-rail" |
| children | ReactNode | undefined | Replaces the stock conversation body when intentionally supplied. | <CustomConversation /> |
Identity and labeling fields
| Field | Type / values | Default | What it does | Example |
|---|---|---:|---|---|
| title | string | Ask TagIt | Panel header title. | title="tagIt Assistant" |
| titleIconUrl | string \| null | undefined | Optional header icon URL. | titleIconUrl="/icons/agent.png" |
| tabLabel | string \| null | Chat | Text label on the tab. Set to null for icon-only mode. | tabLabel="AI Client" |
| tabIcon | ReactNode | 'AI' | Fallback tab icon/content when no icon URL is provided. | tabIcon={<Sparkles />} |
| tabIconUrl | string \| null | undefined | Optional image URL for the tab icon. | tabIconUrl="/icons/chat.png" |
| tabTooltipOpen | string \| null | derived | Tooltip shown when the widget is closed. | tabTooltipOpen="Open AI Client" |
| tabTooltipClose | string \| null | derived | Tooltip shown when the widget is open. | tabTooltipClose="Collapse AI Client" |
Theme and color fields
| Field | Type / values | Default | What it does | Example |
|---|---|---:|---|---|
| theme | 'light' \| 'dark' \| 'system' | system | Chooses the widget theme. system follows the OS preference. | theme="dark" |
| colorScheme | ChatColorScheme | undefined | Single theme palette for the widget. | { bg: '#0b1220', primary: '#60a5fa' } |
| colorSchemes | Partial<Record<'light' \| 'dark', ChatColorScheme>> | undefined | Separate palettes for light and dark themes. | { light: {...}, dark: {...} } |
Visibility and actions
| Field | Type / values | Default | What it does | Example |
|---|---|---:|---|---|
| showBackdrop | boolean | false | Shows the overlay scrim in overlay mode. | showBackdrop |
| closeOnOutsideClick | boolean | false | Allows clicking the backdrop to close the widget in overlay mode. | closeOnOutsideClick |
| onWidthChange | (width: string) => void | undefined | Called when the user resizes the panel. | onWidthChange={setWidth} |
| onNewChat | () => void | undefined | Overrides the default new-chat action. | onNewChat={handleNewChat} |
Open / Close Control
ChatWidget supports both controlled and uncontrolled open state.
open+onOpenChangefor controlled usagedefaultOpenfor uncontrolled usage
Use controlled state when the host page needs to:
- reserve layout space only while the panel is open
- coordinate open state with surrounding page layout
- keep the tab at the browser edge in a viewport-docked integration
Example: controlled side panel
const [isOpen, setIsOpen] = useState(true);
const [width, setWidth] = useState('420px');
<ChatWidget
presentation="sidePanel"
side="right"
open={isOpen}
onOpenChange={setIsOpen}
width={width}
onWidthChange={setWidth}
title="tagIt Assistant"
tabLabel="AI Client"
/>Mobile Behavior
By default, the widget switches to fullscreen when the viewport is at or below mobileBreakpointPx.
Fullscreen mobile behavior means:
- the panel uses
100vwand100dvh - the panel is anchored to the viewport instead of the page
- resize is disabled
- the tab becomes a floating button near the lower-right edge
- the host should not reserve side-panel layout space for mobile
If your host reserves side-panel space, clear that reservation at the same breakpoint:
const isMobile = useMediaQuery('(max-width: 768px)');
<div style={{ paddingRight: open && !isMobile ? width : 0 }}>
<AppContent />
<ChatWidget
presentation="sidePanel"
open={open}
onOpenChange={setOpen}
width={width}
onWidthChange={setWidth}
mobileBreakpointPx={768}
/>
</div>Troubleshooting Layout
| Symptom | Likely cause | Fix |
|---|---|---|
| Panel opens but leaves a blank rail after closing | Host always reserves width instead of checking open. | Reserve side space only when open is true and not mobile. |
| Panel can shrink but not grow | Parent container is constraining width. | Use presentation="sidePanel" and avoid parent max-width: 100% constraints around the widget. |
| Panel scrolls with the page | The host is using an embedded/inline layout when it wanted a viewport panel. | Use presentation="sidePanel" or presentation="overlay". |
| Mobile inherits desktop side-panel behavior | Host is not clearing reserved side space at the mobile breakpoint. | Use mobilePresentation="fullscreen" and clear host padding/margin on mobile. |
| Backdrop shows in a side panel | presentation="overlay" is being used. | Use presentation="sidePanel" for app copilots that push content aside. |
Conversation Action Options
The useConversation() hook exposes sendMessage(), retry(), cancel(), clearHistory(), and reEditMessage(). The request option bag belongs to the core client and is documented in @tagit/ai-client-core.
Conversation History UI
When conversationHistory.enabled is configured on the core client, the stock React UI adds first-class history controls:
- the widget header shows a conversation history button
- the empty state shows up to five recent conversations
- users can load a previous conversation
- users can rename conversations
- users can archive conversations
- the New Chat button starts a new durable conversation instead of only clearing local state
- after New Chat, the empty-state recent conversation list refreshes from the configured history transport
const client = new AIClient(
createDefaultConfig('uc-platform-assistant-v2', 'https://ai.tagit.live', {
user: {
entityId: currentUser.uid,
userId: currentUser.uid,
},
conversationHistory: {
enabled: true,
recentLimit: 5,
surface: window.location.pathname,
source: 'platform',
},
})
);For custom shells, use useConversationHistory() to render your own list, picker, rename action, archive action, or recent-conversation empty state.
const history = useConversationHistory({ limit: 5 });
await history.loadConversation(conversationId);
await history.renameConversation(conversationId, 'Campaign planning');
await history.archiveConversation(conversationId);
await history.startNewConversation('New chat');Multi-Agent UI
When the core AIClient is configured with multiple host-authorized agents, the stock ChatWidget shows the active agent name and logo in the header. The identity is selectable only when more than one agent is available.
The built-in Agent Picker:
- lists the host-supplied agent names, logos, descriptions, and active state
- uses the same overlay panel pattern as Conversation History
- confirms before replacing a non-empty active chat
- preserves an unsent composer draft when the user cancels
- starts a fresh local conversation when the user confirms an agent switch
- scopes Conversation History to the selected agent's
useCaseId
The host app owns the agent list, default selection, route/page/global policy, and runtime variables. The SDK only renders the supplied agents and performs the switch safely.
const client = new AIClient(
createDefaultConfig('uc-platform-help', 'https://ai.tagit.live', {
user: { entityId: currentUser.uid, userId: currentUser.uid },
conversationHistory: { enabled: true, surface: window.location.pathname },
agents: [
{ useCaseId: 'uc-platform-help', name: 'Platform Help' },
{ useCaseId: 'uc-dashboard-insights', name: 'Dashboard Insights' },
],
defaultUseCaseId: 'uc-dashboard-insights',
resolveRequestOptions(agent) {
return {
surface: window.location.pathname,
runtimeVariables: {
'page.path': window.location.pathname,
'agent.name': agent.name,
},
};
},
}),
);Custom shells can use useAgents() to render their own selector.
ChatConversation
Use ChatConversation when you want a lighter conversation surface and you are already handling the shell yourself.
import { AIClientProvider, ChatConversation } from '@tagit/ai-client-react';
export default function App() {
return (
<AIClientProvider client={client}>
<ChatConversation />
</AIClientProvider>
);
}Use ChatWidget when you want the full built-in shell, header, tab, and sizing controls.
Custom Conversation Body
Only pass children to ChatWidget if you intentionally want to replace the built-in conversation body.
<ChatWidget mode="overlay" side="right" title="tagIt Assistant">
<CustomConversation />
</ChatWidget>If you do not pass children, the stock conversation UI is rendered automatically.
Configuration
The React package owns the widget shell, provider, hooks, and layout guidance. The core config, request, event, and collector contracts live in @tagit/ai-client-core.
Use the core README for:
createDefaultConfig()- shared
userandconversationconfig - orchestrator config
- collector config
- UI config
sendMessage()/retry()option bags- conversation state
- client events
- orchestrator request shape
- collector envelope shape
See packages/ai-client-core/README.md.
Starter Harness
The starter app in apps/starter is the local demo and shell playground.
Use it to test:
- overlay mode
- inline page-rail mode
- inline viewport-docked mode
- theming and color schemes
- tab labels and tooltips
- open/close behavior
- sizing and resize behavior
- request-level
runtimeVariablesagainst a real/v2/chatuse case - one-agent and multi-agent configuration
- agent switching, confirmation cancellation, scoped history, and host-resolved runtime variables
The starter is not the distributable package.
Recommended Integration Patterns
Simple Embed
Use this when you want the fastest possible install:
- create one
AIClient - provide
user.entityIdso V2 memory and collector events share the same user identity - optionally provide
conversation.conversationIdwhen you want a durable conversation across reloads - wrap the app with
AIClientProvider - render
<ChatWidget /> - import
@tagit/ai-client-react/chat-widget.css
V2 Contract Notes
This beta client targets the orchestrator V2 contract only. The core client appends /v2/chat, sends the current user message, and lets the orchestrator rehydrate recent conversation memory from conversation.conversationId and user.userId ?? user.entityId.
Checkpoint controls are not part of the first React beta. Workflow metadata is still captured in client state and events, so host apps can inspect workflowId, workflowStatus, and checkpointPrompt if a workflow use case returns them.
Viewport-Docked Sidebar
Use this when the chat should feel like a persistent product rail:
- keep the widget fixed to the browser edge
- reserve page width only when open
- let the tab collapse back to the browser edge
- keep the host page scroll independent of the rail
This pattern pairs best with:
mode="inline"dockMode="viewport"- controlled
openstate - host-page padding or content width reservation that only applies while the panel is open
<div className={`page-shell ${isOpen ? 'page-shell-chat-open' : ''}`}>
<main>...</main>
<ChatWidget
mode="inline"
dockMode="viewport"
side="right"
open={isOpen}
onOpenChange={setIsOpen}
/>
</div>Custom Shell
Use useConversation() when the host app wants to render its own shell and only consume conversation state and actions.
Troubleshooting
| Symptom | Likely cause | Check |
|---|---|---|
| Header/tab only, empty shell | stale package cache or old bundle | clear node_modules/.vite, restart dev server, confirm children is not being passed |
| No styling | CSS not imported | verify @tagit/ai-client-react/chat-widget.css import |
| Missing body after passing custom JSX | children overrides stock body | remove children to restore the stock widget |
| No messages sent | invalid orchestrator config | verify orchestrator.baseUrl and useCaseId, confirm baseUrl does not include /v2/chat |
| V2 request is rejected for missing user | missing shared identity | set user.entityId; optionally set user.userId when it should differ from the entity id |
| Runtime import error | unsupported import path | use only documented public exports |
| Collector events do not appear | collector config incomplete or disabled | confirm collector.enabled, collector.baseUrl, and collector.tagId |
| Inline mode looks cramped | panel width too small | pass a wider width or switch to dockMode="viewport" with host-side reserved space |
| Viewport-docked mode takes over the whole page | host layout is stretching the widget root | keep the docked widget fixed to the viewport edge and reserve page width only in the host shell |
