orchid-ai 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/orchid-ai.css CHANGED
@@ -244,6 +244,9 @@
244
244
  border: 1px solid #e5e7eb;
245
245
  border-radius: 12px;
246
246
  padding: 16px 20px;
247
+ width: 100%;
248
+ max-width: 420px;
249
+ box-sizing: border-box;
247
250
  }
248
251
 
249
252
  .ai-chat-suggestions span {
@@ -260,6 +263,7 @@
260
263
  display: flex;
261
264
  flex-direction: column;
262
265
  gap: 8px;
266
+ padding: 0;
263
267
  }
264
268
 
265
269
  .ai-chat-suggestions li {
@@ -271,6 +275,8 @@
271
275
  border-radius: 8px;
272
276
  cursor: pointer;
273
277
  transition: background 0.15s, border-color 0.15s, color 0.15s;
278
+ width: 100%;
279
+ box-sizing: border-box;
274
280
  }
275
281
 
276
282
  .ai-chat-suggestions li:hover {
@@ -279,6 +285,17 @@
279
285
  color: #1eaaf1;
280
286
  }
281
287
 
288
+ .ai-chat-suggestions--disabled li {
289
+ opacity: 0.45;
290
+ cursor: not-allowed;
291
+ }
292
+
293
+ .ai-chat-suggestions--disabled li:hover {
294
+ background: #f9fafb;
295
+ border-color: #e5e7eb;
296
+ color: #4b5563;
297
+ }
298
+
282
299
  /* ── Messages ── */
283
300
 
284
301
  .ai-chat-message {
@@ -347,6 +364,16 @@
347
364
  border-bottom-right-radius: 4px;
348
365
  }
349
366
 
367
+ .ai-chat-bubble.user .ai-chat-user-link {
368
+ color: #e0f2fe;
369
+ text-decoration: underline;
370
+ word-break: break-all;
371
+ }
372
+
373
+ .ai-chat-bubble.user .ai-chat-user-link:hover {
374
+ color: #ffffff;
375
+ }
376
+
350
377
  .ai-chat-bubble.assistant {
351
378
  position: relative;
352
379
  background: #ffffff;
@@ -1917,47 +1944,61 @@
1917
1944
  /* ── Print (Single Response) ── */
1918
1945
 
1919
1946
  @media print {
1920
- @page {
1921
- margin: 12mm;
1947
+ @page { margin: 12mm; }
1948
+
1949
+ /*
1950
+ * visibility:hidden on body allows #ai-cortex-print-section descendants to
1951
+ * override with visibility:visible — this is a CSS guarantee that display:none
1952
+ * does NOT offer (Chrome's print engine ignores the specificity override for
1953
+ * display:none !important on body > *).
1954
+ *
1955
+ * Siblings are collapsed to height:0 so no blank space precedes the section.
1956
+ * position:absolute (not fixed) allows content to paginate across pages.
1957
+ */
1958
+ body.ai-chat-printing {
1959
+ visibility: hidden !important;
1960
+ position: relative !important;
1922
1961
  }
1923
1962
 
1924
- /* Hide everything on the page; show only the isolated print section. */
1925
- body.ai-chat-printing > * {
1926
- display: none !important;
1963
+ body.ai-chat-printing > *:not(#ai-cortex-print-section) {
1964
+ height: 0 !important;
1965
+ overflow: hidden !important;
1927
1966
  }
1928
1967
 
1929
1968
  body.ai-chat-printing #ai-cortex-print-section {
1969
+ visibility: visible !important;
1930
1970
  display: block !important;
1971
+ position: absolute !important;
1972
+ top: 0 !important;
1973
+ left: 0 !important;
1974
+ width: 100% !important;
1975
+ padding: 0 !important;
1976
+ background: #ffffff;
1977
+ color: #1f2937;
1978
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1979
+ font-size: 14px;
1980
+ line-height: 1.6;
1931
1981
  }
1932
1982
 
1933
- /*
1934
- * Kill all CSS animations and transitions inside the print section.
1935
- * Browsers reset animations during the print reflow, catching elements at
1936
- * their `from` keyframe (opacity: 0, scale: 0) rather than their final state.
1937
- * Disabling animations lets elements fall back to their base CSS styles,
1938
- * which are always the fully-visible final appearance.
1939
- *
1940
- * print-color-adjust: exact forces Chrome to render background colours and
1941
- * images — without it Chrome strips inline background fills (e.g. dot colours
1942
- * on scatter/dot charts) and the dots appear as white outlines only.
1943
- */
1944
1983
  body.ai-chat-printing #ai-cortex-print-section * {
1984
+ visibility: visible !important;
1945
1985
  animation: none !important;
1946
1986
  transition: none !important;
1987
+ /* Kill any opacity:0 left by a stopped animation */
1988
+ opacity: 1 !important;
1947
1989
  -webkit-print-color-adjust: exact !important;
1948
1990
  print-color-adjust: exact !important;
1949
1991
  }
1950
1992
 
1951
- /* Clean up the bubble for print */
1952
1993
  body.ai-chat-printing #ai-cortex-print-section .ai-chat-bubble.assistant {
1953
- max-width: none;
1954
- width: 100%;
1955
- border: none;
1956
- border-radius: 0;
1957
- padding: 0;
1958
- background: #ffffff;
1959
- color: #111827;
1960
- box-shadow: none;
1994
+ max-width: none !important;
1995
+ width: 100% !important;
1996
+ border: none !important;
1997
+ border-radius: 0 !important;
1998
+ padding: 0 !important;
1999
+ background: #ffffff !important;
2000
+ color: #1f2937 !important;
2001
+ box-shadow: none !important;
1961
2002
  }
1962
2003
  }
1963
2004
 
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "orchid-ai",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Shared Orchid AI chat UI and visualization components",
5
5
  "main": "src/index.js",
6
+ "types": "src/index.d.ts",
6
7
  "exports": {
7
8
  ".": "./src/index.js",
8
9
  "./orchid-ai.css": "./orchid-ai.css",
@@ -1,8 +1,14 @@
1
1
  import React, { useRef, useEffect } from 'react';
2
2
  import Message from './Message';
3
3
 
4
+ const DEFAULT_SUGGESTIONS = [
5
+ 'Give me brief tips for navigating iLink.',
6
+ 'What should I check before starting a dispatch?',
7
+ 'How do I narrow down a search in Hermes command search (⌘K)?',
8
+ ];
9
+
4
10
  /**
5
- * Hermes chat window (Markdown + optional orchid-ai-chart fenced blocks).
11
+ * Orchid AI chat window (Markdown + optional orchid-ai-chart fenced blocks).
6
12
  */
7
13
  export default function ChatWindow({
8
14
  messages,
@@ -11,7 +17,13 @@ export default function ChatWindow({
11
17
  onSuggestionClick,
12
18
  aiEnabled,
13
19
  organisationName,
20
+ appName = "Hermes Chat",
21
+ unavailableMessage,
22
+ emptyDescription,
23
+ suggestions = DEFAULT_SUGGESTIONS,
24
+ suggestionsDisabled = false,
14
25
  }) {
26
+ const exportPrefix = appName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
15
27
  const bottomRef = useRef(null);
16
28
 
17
29
  useEffect(() => {
@@ -20,6 +32,8 @@ export default function ChatWindow({
20
32
 
21
33
  const renderEmptyState = () => {
22
34
  if (!aiEnabled) {
35
+ const msg = unavailableMessage ??
36
+ `${appName} needs an Anthropic API key on the server. Contact your administrator if this persists.`;
23
37
  return (
24
38
  <div className="ai-chat-empty">
25
39
  <div className="ai-chat-empty-icon">
@@ -37,16 +51,14 @@ export default function ChatWindow({
37
51
  <line x1="4.93" y1="4.93" x2="19.07" y2="19.07" />
38
52
  </svg>
39
53
  </div>
40
- <h2>Hermes Chat unavailable</h2>
41
- <p>
42
- Hermes Chat needs an Anthropic API key on the server and an active organisation. Contact your administrator
43
- if this persists.
44
- </p>
54
+ <h2>{appName} unavailable</h2>
55
+ <p>{msg}</p>
45
56
  </div>
46
57
  );
47
58
  }
48
59
 
49
60
  const scope = organisationName || 'your organisation';
61
+ const description = emptyDescription ?? `Ask about ${scope} — shipments, schedules, data insights, or what to explore next.`;
50
62
 
51
63
  return (
52
64
  <div className="ai-chat-empty">
@@ -67,21 +79,23 @@ export default function ChatWindow({
67
79
  </svg>
68
80
  </div>
69
81
  <h2>How can I help?</h2>
70
- <p>{`Ask about ${scope} — jobs, routes, schedules, reminders, or what to explore next.`}</p>
71
- <div className="ai-chat-suggestions">
72
- <span>Try asking:</span>
73
- <ul>
74
- <li onClick={() => onSuggestionClick('Give me brief tips for navigating iLink.')}>
75
- Give me brief tips for navigating iLink.
76
- </li>
77
- <li onClick={() => onSuggestionClick('What should I check before starting a dispatch?')}>
78
- What should I check before starting a dispatch?
79
- </li>
80
- <li onClick={() => onSuggestionClick('How do I narrow down a search in Hermes command search (⌘K)?')}>
81
- How do I narrow down a search in Hermes command search (⌘K)?
82
- </li>
83
- </ul>
84
- </div>
82
+ <p>{description}</p>
83
+ {suggestions.length > 0 && (
84
+ <div className={`ai-chat-suggestions${suggestionsDisabled ? ' ai-chat-suggestions--disabled' : ''}`}>
85
+ <span>Try asking:</span>
86
+ <ul>
87
+ {suggestions.map((s) => (
88
+ <li
89
+ key={s}
90
+ onClick={suggestionsDisabled ? undefined : () => onSuggestionClick(s)}
91
+ aria-disabled={suggestionsDisabled || undefined}
92
+ >
93
+ {s}
94
+ </li>
95
+ ))}
96
+ </ul>
97
+ </div>
98
+ )}
85
99
  </div>
86
100
  );
87
101
  };
@@ -90,7 +104,7 @@ export default function ChatWindow({
90
104
  <div className="ai-chat-window">
91
105
  {messages?.length === 0 && !loading && renderEmptyState()}
92
106
  {(messages ?? []).map((msg, i) => (
93
- <Message key={i} role={msg.role} content={msg.content} truncated={msg.truncated} />
107
+ <Message key={i} role={msg.role} content={msg.content} truncated={msg.truncated} exportPrefix={exportPrefix} />
94
108
  ))}
95
109
  {loading && (
96
110
  <div className="ai-chat-message assistant">
@@ -9,7 +9,46 @@ const IS_DEV = process.env.NODE_ENV === "development";
9
9
 
10
10
  const TITLE_RE = /<!--\s*title:\s*([^-][^>]*?)\s*-->/i;
11
11
 
12
- export default function Message({ role, content, truncated }) {
12
+ /** Split on http(s) URLs for lightweight linkify in user bubbles (plain text, not full markdown). */
13
+ const URL_INLINE_RE = /(https?:\/\/[^\s<>`]+)/gi;
14
+
15
+ function trimTrailingUrlPunctuation(href) {
16
+ return href.replace(/[),.;:!?'"\]}>]+$/g, "");
17
+ }
18
+
19
+ function linkifyUserLine(line) {
20
+ const parts = String(line).split(URL_INLINE_RE);
21
+ return parts.map((part, i) => {
22
+ if (part === "") return null;
23
+ if (/^https?:\/\//i.test(part)) {
24
+ const href = trimTrailingUrlPunctuation(part);
25
+ return (
26
+ <a
27
+ key={i}
28
+ href={href}
29
+ target="_blank"
30
+ rel="noopener noreferrer"
31
+ className="ai-chat-user-link"
32
+ >
33
+ {part}
34
+ </a>
35
+ );
36
+ }
37
+ return <React.Fragment key={i}>{part}</React.Fragment>;
38
+ });
39
+ }
40
+
41
+ function UserBubbleContent({ content }) {
42
+ const lines = String(content).split("\n");
43
+ return lines.map((line, li) => (
44
+ <React.Fragment key={li}>
45
+ {li > 0 ? <br /> : null}
46
+ {linkifyUserLine(line)}
47
+ </React.Fragment>
48
+ ));
49
+ }
50
+
51
+ export default function Message({ role, content, truncated, exportPrefix = "orchid-ai" }) {
13
52
  const isUser = role === "user";
14
53
  const [copied, setCopied] = useState(false);
15
54
  const [isPrinting, setIsPrinting] = useState(false);
@@ -56,23 +95,21 @@ export default function Message({ role, content, truncated }) {
56
95
  const pad = (v) => String(v).padStart(2, "0");
57
96
  const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}`;
58
97
 
98
+ setIsPrinting(true);
99
+
100
+ const clone = bubbleContent.cloneNode(true);
101
+ clone.querySelectorAll(".ai-chart-export-actions, .ai-chat-message-actions").forEach((n) => n.remove());
102
+
59
103
  setIsPrinting(true);
60
104
  const previousTitle = document.title;
61
- document.title = `orchid-ai-${slug}-${timestamp}`;
105
+ document.title = `${exportPrefix}-${slug}-${timestamp}`;
62
106
 
63
- // Clone the bubble content into an isolated print section at the body root.
64
- // This keeps the layout clean (no surrounding app chrome) while the CSS
65
- // animation: none rule ensures chart elements render at their final visible
66
- // state rather than being caught mid-animation by the print reflow.
67
107
  let printSection = document.getElementById("ai-cortex-print-section");
68
108
  if (!printSection) {
69
109
  printSection = document.createElement("div");
70
110
  printSection.id = "ai-cortex-print-section";
71
111
  document.body.appendChild(printSection);
72
112
  }
73
-
74
- const clone = bubbleContent.cloneNode(true);
75
- clone.querySelectorAll(".ai-chart-export-actions, .ai-chat-message-actions").forEach((n) => n.remove());
76
113
  printSection.innerHTML = "";
77
114
  printSection.appendChild(clone);
78
115
  document.body.classList.add("ai-chat-printing");
@@ -147,6 +184,16 @@ export default function Message({ role, content, truncated }) {
147
184
  </code>
148
185
  );
149
186
  },
187
+ a({ href, children, ...props }) {
188
+ if (typeof href !== "string" || !/^https?:\/\//i.test(href)) {
189
+ return <span {...props}>{children}</span>;
190
+ }
191
+ return (
192
+ <a {...props} href={href} target="_blank" rel="noopener noreferrer">
193
+ {children}
194
+ </a>
195
+ );
196
+ },
150
197
  };
151
198
 
152
199
  return (
@@ -157,12 +204,7 @@ export default function Message({ role, content, truncated }) {
157
204
  <div className={`ai-chat-bubble ${role}`}>
158
205
  <div className="ai-chat-message-content">
159
206
  {isUser ? (
160
- content.split("\n").map((line, i) => (
161
- <React.Fragment key={i}>
162
- {line}
163
- {i < content.split("\n").length - 1 && <br />}
164
- </React.Fragment>
165
- ))
207
+ <UserBubbleContent content={content} />
166
208
  ) : (
167
209
  <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>{renderContent}</ReactMarkdown>
168
210
  )}
@@ -0,0 +1,136 @@
1
+ import { useState, useCallback, useRef } from 'react';
2
+
3
+ /**
4
+ * Default status message strings. Export these so server-side code can import
5
+ * and reference the same values, making it easy to keep client and server in sync
6
+ * or swap them out per-app.
7
+ *
8
+ * Set showStatus: false on the hook to disable status display entirely.
9
+ */
10
+ export const ORCHID_AI_DEFAULT_STATUS = {
11
+ thinking: 'Thinking',
12
+ compilingResponse: 'Compiling response',
13
+ lookingUpData: 'Looking up data',
14
+ };
15
+
16
+ /**
17
+ * React hook for Orchid AI chat. Handles SSE streaming when the server responds
18
+ * with Content-Type: text/event-stream, falling back to plain JSON for non-streaming
19
+ * backends.
20
+ *
21
+ * @param {object} opts
22
+ * @param {string} opts.endpoint - POST endpoint URL
23
+ * @param {(userMessage: string, history: Array, sendOptions?: object) => object} opts.buildBody - builds the request body
24
+ * @param {() => object} [opts.getHeaders] - returns extra request headers (e.g. CSRF token)
25
+ * @param {boolean} [opts.showStatus=true] - set false to suppress status text entirely
26
+ * @param {Array} [opts.initialMessages=[]] - seed the conversation (e.g. from localStorage)
27
+ *
28
+ * @returns {{ messages, loading, statusText, sendMessage, clearMessages }}
29
+ */
30
+ export function useOrchidAiChat({ endpoint, buildBody, getHeaders, showStatus = true, initialMessages = [] }) {
31
+ const [messages, setMessages] = useState(initialMessages);
32
+ const [loading, setLoading] = useState(false);
33
+ const [statusText, setStatusText] = useState('');
34
+
35
+ // Track messages in a ref so sendMessage always reads the latest without needing
36
+ // messages in its dependency array (avoids capturing stale history).
37
+ const messagesRef = useRef(initialMessages);
38
+
39
+ // Keep latest callbacks in refs so sendMessage identity stays stable regardless
40
+ // of whether the parent re-creates buildBody/getHeaders each render.
41
+ const buildBodyRef = useRef(buildBody);
42
+ const getHeadersRef = useRef(getHeaders);
43
+ buildBodyRef.current = buildBody;
44
+ getHeadersRef.current = getHeaders;
45
+
46
+ const addMessage = useCallback((msg) => {
47
+ setMessages((prev) => {
48
+ const next = [...prev, msg];
49
+ messagesRef.current = next;
50
+ return next;
51
+ });
52
+ }, []);
53
+
54
+ const sendMessage = useCallback(
55
+ async (userMessage, sendOptions = {}) => {
56
+ const history = messagesRef.current.slice();
57
+ addMessage({ role: 'user', content: userMessage });
58
+ setLoading(true);
59
+ setStatusText('');
60
+
61
+ try {
62
+ const response = await fetch(endpoint, {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/json',
66
+ ...getHeadersRef.current?.(),
67
+ },
68
+ body: JSON.stringify(buildBodyRef.current(userMessage, history, sendOptions)),
69
+ });
70
+
71
+ const contentType = response.headers.get('content-type') ?? '';
72
+
73
+ if (contentType.includes('text/event-stream')) {
74
+ const reader = response.body.getReader();
75
+ const decoder = new TextDecoder();
76
+ let buffer = '';
77
+
78
+ while (true) {
79
+ const { done, value } = await reader.read();
80
+ if (done) break;
81
+ buffer += decoder.decode(value, { stream: true });
82
+ const lines = buffer.split('\n');
83
+ buffer = lines.pop() ?? '';
84
+
85
+ for (const line of lines) {
86
+ if (!line.startsWith('data: ')) continue;
87
+ try {
88
+ const event = JSON.parse(line.slice(6));
89
+ if (event.type === 'status' && showStatus) {
90
+ setStatusText(event.text);
91
+ } else if (event.type === 'done') {
92
+ addMessage({
93
+ role: 'assistant',
94
+ content: event.response,
95
+ truncated: event.truncated,
96
+ });
97
+ } else if (event.type === 'error') {
98
+ addMessage({
99
+ role: 'assistant',
100
+ content: event.error || 'Something went wrong.',
101
+ });
102
+ }
103
+ } catch {
104
+ // ignore malformed SSE lines
105
+ }
106
+ }
107
+ }
108
+ } else {
109
+ // Plain JSON fallback for non-streaming backends
110
+ const data = await response.json();
111
+ if (response.ok) {
112
+ addMessage({ role: 'assistant', content: data.response, truncated: data.truncated });
113
+ } else {
114
+ addMessage({ role: 'assistant', content: data.error || 'Something went wrong.' });
115
+ }
116
+ }
117
+ } catch {
118
+ addMessage({
119
+ role: 'assistant',
120
+ content: 'Network error. Please check your connection and try again.',
121
+ });
122
+ } finally {
123
+ setLoading(false);
124
+ setStatusText('');
125
+ }
126
+ },
127
+ [endpoint, showStatus, addMessage]
128
+ );
129
+
130
+ const clearMessages = useCallback(() => {
131
+ setMessages([]);
132
+ messagesRef.current = [];
133
+ }, []);
134
+
135
+ return { messages, loading, statusText, sendMessage, clearMessages };
136
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ import * as React from 'react';
2
+
3
+ // ─── Chat UI ────────────────────────────────────────────────────────────────
4
+
5
+ export interface ChatMessage {
6
+ role: 'user' | 'assistant';
7
+ content: string;
8
+ truncated?: boolean;
9
+ }
10
+
11
+ export interface ChatWindowProps {
12
+ messages: ChatMessage[];
13
+ loading?: boolean;
14
+ statusText?: string;
15
+ onSuggestionClick?: (text: string) => void;
16
+ aiEnabled?: boolean;
17
+ organisationName?: string;
18
+ /** Any extra props are forwarded to the root element */
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ export interface ChatInputProps {
23
+ onSend: (text: string) => void;
24
+ disabled?: boolean;
25
+ disabledReason?: string;
26
+ onDisabledClick?: () => void;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ export interface MessageProps {
31
+ role: 'user' | 'assistant';
32
+ content: string;
33
+ truncated?: boolean;
34
+ }
35
+
36
+ export const ChatWindow: React.FC<ChatWindowProps>;
37
+ export const ChatInput: React.FC<ChatInputProps>;
38
+ export const Message: React.FC<MessageProps>;
39
+
40
+ // ─── useOrchidAiChat ─────────────────────────────────────────────────────────
41
+
42
+ /** Optional flags merged into the request by `buildBody` (e.g. web research for Hermes). */
43
+ export interface OrchidAiChatSendOptions {
44
+ allowWebResearch?: boolean;
45
+ urlsToCheck?: string[];
46
+ }
47
+
48
+ export interface OrchidAiChatOptions {
49
+ /** POST endpoint that accepts the chat message */
50
+ endpoint: string;
51
+ /**
52
+ * Builds the request body from the user message, history, and per-send options.
53
+ * The third argument is only present when callers use `sendMessage(text, options)`.
54
+ */
55
+ buildBody: (
56
+ userMessage: string,
57
+ history: ChatMessage[],
58
+ sendOptions?: OrchidAiChatSendOptions
59
+ ) => Record<string, unknown>;
60
+ /** Returns extra request headers (e.g. CSRF token) */
61
+ getHeaders?: () => Record<string, string>;
62
+ /**
63
+ * Whether to display status messages during processing.
64
+ * Set to false to suppress all status text. Defaults to true.
65
+ */
66
+ showStatus?: boolean;
67
+ /** Initial transcript when mounting the hook */
68
+ initialMessages?: ChatMessage[];
69
+ }
70
+
71
+ export interface OrchidAiChatState {
72
+ messages: ChatMessage[];
73
+ loading: boolean;
74
+ /** Live status text emitted by the server (e.g. "Thinking", "Compiling response") */
75
+ statusText: string;
76
+ sendMessage: (userMessage: string, sendOptions?: OrchidAiChatSendOptions) => Promise<void>;
77
+ clearMessages: () => void;
78
+ }
79
+
80
+ export function useOrchidAiChat(options: OrchidAiChatOptions): OrchidAiChatState;
81
+
82
+ /**
83
+ * Default status message strings used by the Orchid AI backend services.
84
+ * Import these in server-side code to keep client and server labels in sync,
85
+ * or to override individual values per app.
86
+ */
87
+ export const ORCHID_AI_DEFAULT_STATUS: {
88
+ readonly thinking: string;
89
+ readonly compilingResponse: string;
90
+ readonly lookingUpData: string;
91
+ };
92
+
93
+ // ─── Visualizations ──────────────────────────────────────────────────────────
94
+
95
+ export const AiVisualization: React.FC<{ data: unknown }>;
96
+ export const BarChart: React.FC<{ data: unknown }>;
97
+ export const LineChart: React.FC<{ data: unknown }>;
98
+ export const DotChart: React.FC<{ data: unknown }>;
99
+ export const ScatterPlot: React.FC<{ data: unknown }>;
100
+ export const HistogramChart: React.FC<{ data: unknown }>;
101
+ export const StackedBarChart: React.FC<{ data: unknown }>;
102
+ export const GroupedBarChart: React.FC<{ data: unknown }>;
103
+ export const DataTable: React.FC<{ data: unknown }>;
104
+ export const StatCards: React.FC<{ data: unknown }>;
105
+ export const Timeline: React.FC<{ data: unknown }>;
106
+
107
+ // ─── Chart schema utilities ───────────────────────────────────────────────────
108
+
109
+ export const CHART_BLOCK_LANGUAGE: string;
110
+ export const LEGACY_CHART_BLOCK_LANGUAGE: string;
111
+ export function isChartMarkdownLanguage(lang: string): boolean;
112
+
113
+ export const DOT_CHART_TYPE: string;
114
+ export const TABLE_TYPE: string;
115
+ export const BAR_CHART_TYPE: string;
116
+ export const STAT_CARDS_TYPE: string;
117
+ export const TIMELINE_TYPE: string;
118
+ export const LINE_CHART_TYPE: string;
119
+ export const STACKED_BAR_CHART_TYPE: string;
120
+ export const GROUPED_BAR_CHART_TYPE: string;
121
+ export const HISTOGRAM_TYPE: string;
122
+ export const SCATTER_PLOT_TYPE: string;
123
+
124
+ export function validateBarChartPayload(data: unknown): boolean;
125
+ export function validateLineChartPayload(data: unknown): boolean;
126
+ export function validateStackedBarChartPayload(data: unknown): boolean;
127
+ export function validateGroupedBarChartPayload(data: unknown): boolean;
128
+ export function validateHistogramPayload(data: unknown): boolean;
129
+ export function validateScatterPlotPayload(data: unknown): boolean;
130
+ export function validateStatCardsPayload(data: unknown): boolean;
131
+ export function validateTimelinePayload(data: unknown): boolean;
132
+ export function validateTablePayload(data: unknown): boolean;
133
+
134
+ export function parseChartBlock(block: string): unknown;
135
+ export function resolveChartBlock(block: string): unknown;
136
+
137
+ // ─── Constants ───────────────────────────────────────────────────────────────
138
+
139
+ export const ORCHID_AI_VISUALIZATION_INSTRUCTIONS: string;
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Chat UI
2
2
  export { default as ChatWindow } from './components/ChatWindow';
3
+ export { useOrchidAiChat, ORCHID_AI_DEFAULT_STATUS } from './hooks/useOrchidAiChat';
3
4
  export { default as ChatInput } from './components/ChatInput';
4
5
  export { default as Message } from './components/Message';
5
6