orchid-ai 2.0.0 → 2.0.1
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/package.json +2 -1
- package/src/hooks/useOrchidAiChat.js +135 -0
- package/src/index.d.ts +124 -0
- package/src/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orchid-ai",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -0,0 +1,135 @@
|
|
|
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) => 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
|
+
*
|
|
27
|
+
* @returns {{ messages, loading, statusText, sendMessage, clearMessages }}
|
|
28
|
+
*/
|
|
29
|
+
export function useOrchidAiChat({ endpoint, buildBody, getHeaders, showStatus = true }) {
|
|
30
|
+
const [messages, setMessages] = useState([]);
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
const [statusText, setStatusText] = useState('');
|
|
33
|
+
|
|
34
|
+
// Track messages in a ref so sendMessage always reads the latest without needing
|
|
35
|
+
// messages in its dependency array (avoids capturing stale history).
|
|
36
|
+
const messagesRef = useRef([]);
|
|
37
|
+
|
|
38
|
+
// Keep latest callbacks in refs so sendMessage identity stays stable regardless
|
|
39
|
+
// of whether the parent re-creates buildBody/getHeaders each render.
|
|
40
|
+
const buildBodyRef = useRef(buildBody);
|
|
41
|
+
const getHeadersRef = useRef(getHeaders);
|
|
42
|
+
buildBodyRef.current = buildBody;
|
|
43
|
+
getHeadersRef.current = getHeaders;
|
|
44
|
+
|
|
45
|
+
const addMessage = useCallback((msg) => {
|
|
46
|
+
setMessages((prev) => {
|
|
47
|
+
const next = [...prev, msg];
|
|
48
|
+
messagesRef.current = next;
|
|
49
|
+
return next;
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const sendMessage = useCallback(
|
|
54
|
+
async (userMessage) => {
|
|
55
|
+
const history = messagesRef.current.slice();
|
|
56
|
+
addMessage({ role: 'user', content: userMessage });
|
|
57
|
+
setLoading(true);
|
|
58
|
+
setStatusText('');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(endpoint, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
...getHeadersRef.current?.(),
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify(buildBodyRef.current(userMessage, history)),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
71
|
+
|
|
72
|
+
if (contentType.includes('text/event-stream')) {
|
|
73
|
+
const reader = response.body.getReader();
|
|
74
|
+
const decoder = new TextDecoder();
|
|
75
|
+
let buffer = '';
|
|
76
|
+
|
|
77
|
+
while (true) {
|
|
78
|
+
const { done, value } = await reader.read();
|
|
79
|
+
if (done) break;
|
|
80
|
+
buffer += decoder.decode(value, { stream: true });
|
|
81
|
+
const lines = buffer.split('\n');
|
|
82
|
+
buffer = lines.pop() ?? '';
|
|
83
|
+
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
if (!line.startsWith('data: ')) continue;
|
|
86
|
+
try {
|
|
87
|
+
const event = JSON.parse(line.slice(6));
|
|
88
|
+
if (event.type === 'status' && showStatus) {
|
|
89
|
+
setStatusText(event.text);
|
|
90
|
+
} else if (event.type === 'done') {
|
|
91
|
+
addMessage({
|
|
92
|
+
role: 'assistant',
|
|
93
|
+
content: event.response,
|
|
94
|
+
truncated: event.truncated,
|
|
95
|
+
});
|
|
96
|
+
} else if (event.type === 'error') {
|
|
97
|
+
addMessage({
|
|
98
|
+
role: 'assistant',
|
|
99
|
+
content: event.error || 'Something went wrong.',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// ignore malformed SSE lines
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
// Plain JSON fallback for non-streaming backends
|
|
109
|
+
const data = await response.json();
|
|
110
|
+
if (response.ok) {
|
|
111
|
+
addMessage({ role: 'assistant', content: data.response, truncated: data.truncated });
|
|
112
|
+
} else {
|
|
113
|
+
addMessage({ role: 'assistant', content: data.error || 'Something went wrong.' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
addMessage({
|
|
118
|
+
role: 'assistant',
|
|
119
|
+
content: 'Network error. Please check your connection and try again.',
|
|
120
|
+
});
|
|
121
|
+
} finally {
|
|
122
|
+
setLoading(false);
|
|
123
|
+
setStatusText('');
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[endpoint, showStatus, addMessage]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const clearMessages = useCallback(() => {
|
|
130
|
+
setMessages([]);
|
|
131
|
+
messagesRef.current = [];
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
return { messages, loading, statusText, sendMessage, clearMessages };
|
|
135
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
export interface OrchidAiChatOptions {
|
|
43
|
+
/** POST endpoint that accepts the chat message */
|
|
44
|
+
endpoint: string;
|
|
45
|
+
/** Builds the request body from the user message and current history */
|
|
46
|
+
buildBody: (userMessage: string, history: ChatMessage[]) => Record<string, unknown>;
|
|
47
|
+
/** Returns extra request headers (e.g. CSRF token) */
|
|
48
|
+
getHeaders?: () => Record<string, string>;
|
|
49
|
+
/**
|
|
50
|
+
* Whether to display status messages during processing.
|
|
51
|
+
* Set to false to suppress all status text. Defaults to true.
|
|
52
|
+
*/
|
|
53
|
+
showStatus?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface OrchidAiChatState {
|
|
57
|
+
messages: ChatMessage[];
|
|
58
|
+
loading: boolean;
|
|
59
|
+
/** Live status text emitted by the server (e.g. "Thinking", "Compiling response") */
|
|
60
|
+
statusText: string;
|
|
61
|
+
sendMessage: (userMessage: string) => Promise<void>;
|
|
62
|
+
clearMessages: () => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useOrchidAiChat(options: OrchidAiChatOptions): OrchidAiChatState;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Default status message strings used by the Orchid AI backend services.
|
|
69
|
+
* Import these in server-side code to keep client and server labels in sync,
|
|
70
|
+
* or to override individual values per app.
|
|
71
|
+
*/
|
|
72
|
+
export const ORCHID_AI_DEFAULT_STATUS: {
|
|
73
|
+
readonly thinking: string;
|
|
74
|
+
readonly compilingResponse: string;
|
|
75
|
+
readonly lookingUpData: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ─── Visualizations ──────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export const AiVisualization: React.FC<{ data: unknown }>;
|
|
81
|
+
export const BarChart: React.FC<{ data: unknown }>;
|
|
82
|
+
export const LineChart: React.FC<{ data: unknown }>;
|
|
83
|
+
export const DotChart: React.FC<{ data: unknown }>;
|
|
84
|
+
export const ScatterPlot: React.FC<{ data: unknown }>;
|
|
85
|
+
export const HistogramChart: React.FC<{ data: unknown }>;
|
|
86
|
+
export const StackedBarChart: React.FC<{ data: unknown }>;
|
|
87
|
+
export const GroupedBarChart: React.FC<{ data: unknown }>;
|
|
88
|
+
export const DataTable: React.FC<{ data: unknown }>;
|
|
89
|
+
export const StatCards: React.FC<{ data: unknown }>;
|
|
90
|
+
export const Timeline: React.FC<{ data: unknown }>;
|
|
91
|
+
|
|
92
|
+
// ─── Chart schema utilities ───────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
export const CHART_BLOCK_LANGUAGE: string;
|
|
95
|
+
export const LEGACY_CHART_BLOCK_LANGUAGE: string;
|
|
96
|
+
export function isChartMarkdownLanguage(lang: string): boolean;
|
|
97
|
+
|
|
98
|
+
export const DOT_CHART_TYPE: string;
|
|
99
|
+
export const TABLE_TYPE: string;
|
|
100
|
+
export const BAR_CHART_TYPE: string;
|
|
101
|
+
export const STAT_CARDS_TYPE: string;
|
|
102
|
+
export const TIMELINE_TYPE: string;
|
|
103
|
+
export const LINE_CHART_TYPE: string;
|
|
104
|
+
export const STACKED_BAR_CHART_TYPE: string;
|
|
105
|
+
export const GROUPED_BAR_CHART_TYPE: string;
|
|
106
|
+
export const HISTOGRAM_TYPE: string;
|
|
107
|
+
export const SCATTER_PLOT_TYPE: string;
|
|
108
|
+
|
|
109
|
+
export function validateBarChartPayload(data: unknown): boolean;
|
|
110
|
+
export function validateLineChartPayload(data: unknown): boolean;
|
|
111
|
+
export function validateStackedBarChartPayload(data: unknown): boolean;
|
|
112
|
+
export function validateGroupedBarChartPayload(data: unknown): boolean;
|
|
113
|
+
export function validateHistogramPayload(data: unknown): boolean;
|
|
114
|
+
export function validateScatterPlotPayload(data: unknown): boolean;
|
|
115
|
+
export function validateStatCardsPayload(data: unknown): boolean;
|
|
116
|
+
export function validateTimelinePayload(data: unknown): boolean;
|
|
117
|
+
export function validateTablePayload(data: unknown): boolean;
|
|
118
|
+
|
|
119
|
+
export function parseChartBlock(block: string): unknown;
|
|
120
|
+
export function resolveChartBlock(block: string): unknown;
|
|
121
|
+
|
|
122
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
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
|
|