orchid-ai
v2.3.1
Published
Shared Orchid AI chat UI and visualization components
Readme
orchid-ai
Shared Orchid AI chat UI and visualization components. A source-distributed React component library — no compilation step; the consuming app's bundler handles JSX.
Publishing
npm version patch # or minor / major
npm publishInstallation
npm install orchid-aiImport the stylesheet once in your app entry point:
import 'orchid-ai/orchid-ai.css';Peer dependencies
Your app must provide:
react >= 18
react-dom >= 18
react-markdown >= 9
remark-gfm >= 4
html2canvas >= 1.4 (for chart PNG export)Quick start
import { ChatWindow, ChatInput, useOrchidAiChat } from 'orchid-ai';
import 'orchid-ai/orchid-ai.css';
export default function App() {
const { messages, loading, statusText, sendMessage } = useOrchidAiChat({
endpoint: '/api/ai/chat',
buildBody: (userMessage, history) => ({ message: userMessage, history }),
getHeaders: () => ({ 'X-CSRF-Token': getCsrfToken() }),
});
return (
<div className="ai-chat-container">
<ChatWindow
messages={messages}
loading={loading}
statusText={statusText}
aiEnabled={true}
organisationName="Acme Ltd"
/>
<ChatInput onSend={sendMessage} disabled={loading} />
</div>
);
}useOrchidAiChat
const { messages, loading, statusText, sendMessage, clearMessages } =
useOrchidAiChat(options);Options
| Option | Type | Default | Description |
|-------------------|------------------------------------------------------------------|---------|-----------------------------------------------------------------|
| endpoint | string | — | POST URL the hook fetches on each message |
| buildBody | (userMessage, history, sendOptions?) => object | — | Builds the JSON request body |
| getHeaders | () => Record<string, string> | — | Returns extra headers (e.g. CSRF token) |
| showStatus | boolean | true | Set false to suppress the live status text entirely |
| initialMessages | ChatMessage[] | [] | Seed the transcript (e.g. from localStorage) |
Returns
| Key | Type | Description |
|-----------------|----------------------------------------------|----------------------------------------------------------|
| messages | ChatMessage[] | Full transcript including streaming assistant messages |
| loading | boolean | True while a request is in flight |
| statusText | string | Latest status event text (e.g. "Looking up data") |
| sendMessage | (text: string, opts?: SendOptions) => void | Send a user message |
| clearMessages | () => void | Reset the transcript |
ChatMessage shape
{
role: 'user' | 'assistant';
content: string;
truncated?: boolean;
isStreaming?: boolean;
processTrace?: { items: Array<{ type: 'status' | 'text'; value: string }>; defaultCollapsed?: boolean };
processInterimLive?: string;
queryContext?: Record<string, unknown>;
}Components
<ChatWindow>
Renders the full message list, empty state, and typing indicator. Wrap it with a <div className="ai-chat-container"> (sets layout and font).
| Prop | Type | Default | Description |
|-----------------------|-----------------|----------------------|----------------------------------------------------------------------------------------------------|
| messages | ChatMessage[] | — | From useOrchidAiChat |
| loading | boolean | — | Shows typing indicator when true and no streaming message is present |
| statusText | string | — | Live status shown above the typing indicator |
| aiEnabled | boolean | — | Shows a disabled state with unavailableMessage when false |
| appName | string | "Hermes Chat" | Used in the disabled state heading and as the PDF export filename prefix |
| organisationName | string | — | Shown in the empty-state description ("Ask about ...") |
| unavailableMessage | string | — | Overrides the default "needs an API key" copy when aiEnabled is false |
| emptyDescription | string | — | Overrides the default empty-state paragraph |
| suggestions | string[] | 3 built-in prompts | Clickable suggestion chips shown in the empty state |
| suggestionsDisabled | boolean | false | Renders suggestion chips as non-interactive (e.g. while loading) |
| onSuggestionClick | (text) => void| — | Called when a suggestion chip is clicked |
| showProcessTracePanel| boolean | true | Set false to show statuses inline next to the typing dots instead of in the collapsible panel |
| showQuerySummary | boolean | false | Shows a collapsed "Filters used" disclosure on messages that have queryContext |
<ChatInput>
The text input bar with auto-growing textarea and send button.
| Prop | Type | Description |
|-------------------|-----------------|-----------------------------------------------------------------------|
| onSend | (text) => void| Called with trimmed text when the user submits |
| disabled | boolean | Disables the textarea and send button |
| disabledReason | string | When set, shows a clickable overlay and updates the placeholder text |
| onDisabledClick | () => void | Called when the user taps the disabled overlay |
<Message>
Renders a single message bubble. Used internally by ChatWindow; import directly if you need a custom layout.
| Prop | Type | Description |
|------------------------|-----------------|---------------------------------------------------------|
| role | 'user' \| 'assistant' | — |
| content | string | Markdown for assistant; plain text for user |
| truncated | boolean | Shows a cut-off warning |
| exportPrefix | string | Filename prefix for PDF download (default orchid-ai) |
| isStreaming | boolean | Enables streaming placeholders for open code fences |
| streamingStatusText | string | Status text shown above streaming content |
| processTrace | object | See ChatMessage.processTrace |
| processInterimLive | string | Live interim preamble (streaming only) |
| showProcessTracePanel| boolean | Default true |
| queryContext | object | Filters to display when showQuerySummary is true |
| showQuerySummary | boolean | Default false |
Server-side: SSE protocol
The hook handles both streaming (Content-Type: text/event-stream) and plain JSON responses. For streaming, emit newline-delimited data: events:
data: {"type":"status","text":"Looking up data"}\n\n
data: {"type":"delta","text":"Here are the "}\n\n
data: {"type":"delta","text":"results..."}\n\n
data: {"type":"done","response":"Here are the results...","truncated":false}\n\nEvent types
| type | Required fields | Optional fields |
|----------|------------------------------|------------------------------|
| status | text: string | — |
| delta | text: string | — |
| done | response: string | truncated: boolean, queryContext: object |
| error | error: string | — |
Status labels that trigger the Working panel
The collapsible "Working" panel appears when statuses matching these patterns are received:
Looking up…Found N…Searching the web…Searching knowledge base…
The special status "Compiling response" (exported as ORCHID_AI_SSE_STATUS_CLEAR_STREAM) signals the boundary between interim tool preamble and the final answer — the collector flushes its interim buffer and begins accumulating the reply.
Use the exported constants to keep server and client in sync:
import { ORCHID_AI_DEFAULT_STATUS, ORCHID_AI_SSE_STATUS_CLEAR_STREAM } from 'orchid-ai';
// ORCHID_AI_DEFAULT_STATUS.thinking → 'Thinking'
// ORCHID_AI_DEFAULT_STATUS.lookingUpData → 'Looking up data'
// ORCHID_AI_DEFAULT_STATUS.compilingResponse → 'Compiling response'
// ORCHID_AI_SSE_STATUS_CLEAR_STREAM → 'Compiling response'Query context
Include queryContext on the done event to let users see what filters the AI applied:
res.write(`data: ${JSON.stringify({
type: 'done',
response: finalText,
queryContext: {
customerId: 123,
status: 'active',
dateFrom: '2024-01-01',
},
})}\n\n`);Enable display with <ChatWindow showQuerySummary={true} />. Keys are automatically converted from camelCase to Title Case ("customerId" → "Customer ID").
AI response title
The assistant can set the PDF export filename by embedding an HTML comment anywhere in its reply:
<!-- title: Monthly Shipment Summary -->The comment is stripped from the rendered content and used as the PDF title slug.
Visualizations
The AI embeds charts using a fenced code block with language orchid-ai-chart (legacy alias hemiq-chart is still parsed):
```orchid-ai-chart
{
"type": "bar_chart",
"title": "Shipments by carrier",
"bars": [
{ "label": "FedEx", "value": 42 },
{ "label": "DHL", "value": 31 }
]
}
```The type field determines which component renders. All supported types:
| type | Component | Key fields |
|---------------------|--------------------|--------------------------------------------------------------|
| bar_chart | BarChart | bars: [{ label, value }] |
| line_chart | LineChart | xAxis (label + categories), yAxis (label), series |
| stacked_bar_chart | StackedBarChart | Same as line_chart |
| grouped_bar_chart | GroupedBarChart | Same as line_chart |
| dot_chart | DotChart | series[].points with numeric x, categorical y |
| histogram | HistogramChart | bins: [{ start, end, value }] or { range, count } |
| scatter_plot | ScatterPlot | Standard numeric axes + series |
| stat_cards | StatCards | cards: [{ label, value, unit?, subtitle?, trend? }] |
| table | DataTable | columns + rows |
| timeline | Timeline | items: [{ label, start, end }] (ISO 8601 dates) |
Charts are downloadable as PNG (via html2canvas). Each chart block is validated against its schema on render — invalid JSON or schema errors show an error card instead of crashing.
System prompt constant
Import ORCHID_AI_VISUALIZATION_INSTRUCTIONS to inject the chart format instructions into your AI system prompt:
import { ORCHID_AI_VISUALIZATION_INSTRUCTIONS } from 'orchid-ai';
const systemPrompt = `You are a helpful assistant. ${ORCHID_AI_VISUALIZATION_INSTRUCTIONS}`;Using visualization components standalone
Each chart component can be used outside the chat context:
import { BarChart } from 'orchid-ai';
import 'orchid-ai/orchid-ai.css';
<BarChart chart={{
type: 'bar_chart',
title: 'Revenue by month',
bars: [
{ label: 'Jan', value: 120000 },
{ label: 'Feb', value: 98000 },
],
}} />