@chainai/react 1.0.2 → 1.0.3
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/dist/components/ChainAIWidget.js +7 -6
- package/dist/hooks/useChat.d.ts +3 -0
- package/dist/hooks/useChat.js +47 -0
- package/dist/index.d.ts +1 -1
- package/dist/styles/default.css +1 -0
- package/dist/styles/widget.css +37 -0
- package/package.json +2 -2
- package/styles/default.css +1 -0
- package/styles/widget.css +37 -0
|
@@ -99,7 +99,7 @@ const ChainAIWidgetInner = ({ wallets, activeChain, branding = {}, dimensions =
|
|
|
99
99
|
const messagesEndRef = (0, react_1.useRef)(null);
|
|
100
100
|
const inputRef = (0, react_1.useRef)(null);
|
|
101
101
|
const { isReady } = (0, ChainAIProvider_1.useChainAI)();
|
|
102
|
-
const { messages, isLoading, sendMessage } = (0, useChat_1.useChat)();
|
|
102
|
+
const { messages, isLoading, loadingStage, loadingLabel, sendMessage } = (0, useChat_1.useChat)();
|
|
103
103
|
// Merge custom theme with defaults
|
|
104
104
|
const theme = {
|
|
105
105
|
...themes_1.defaultTheme,
|
|
@@ -231,12 +231,13 @@ const ChainAIWidgetInner = ({ wallets, activeChain, branding = {}, dimensions =
|
|
|
231
231
|
message.role === 'assistant' && (react_1.default.createElement(Avatar, { branding: branding, size: "small" })),
|
|
232
232
|
react_1.default.createElement("div", { className: "chain-ai-widget-message-bubble" },
|
|
233
233
|
react_1.default.createElement(ChatMessage_1.ChatMessage, { message: message, theme: theme })))))),
|
|
234
|
-
isLoading && (react_1.default.createElement("div", { className: "chain-ai-widget-message chain-ai-widget-message-assistant" },
|
|
234
|
+
isLoading && loadingStage !== 'idle' && loadingStage !== 'complete' && (react_1.default.createElement("div", { className: "chain-ai-widget-message chain-ai-widget-message-assistant" },
|
|
235
235
|
react_1.default.createElement(Avatar, { branding: branding, size: "small" }),
|
|
236
|
-
react_1.default.createElement("div", { className: "chain-ai-widget-
|
|
237
|
-
react_1.default.createElement("
|
|
238
|
-
react_1.default.createElement("span",
|
|
239
|
-
|
|
236
|
+
react_1.default.createElement("div", { className: "chain-ai-widget-loading-stage" },
|
|
237
|
+
react_1.default.createElement("div", { className: "chain-ai-widget-loading-spinner" }),
|
|
238
|
+
react_1.default.createElement("span", { className: "chain-ai-widget-loading-text" },
|
|
239
|
+
loadingLabel,
|
|
240
|
+
"...")))),
|
|
240
241
|
pendingAction && (react_1.default.createElement(ActionPrompt_1.ActionPrompt, { action: pendingAction.action, missing: pendingAction.missing, prompt: pendingAction.prompt, onSubmit: handleActionFieldSubmit, theme: theme })),
|
|
241
242
|
react_1.default.createElement("div", { ref: messagesEndRef })),
|
|
242
243
|
react_1.default.createElement("div", { className: "chain-ai-widget-input-area" },
|
package/dist/hooks/useChat.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* useChat hook - Manages chat messages and state
|
|
3
3
|
*/
|
|
4
4
|
import type { Message, WalletContext } from '@chainai/core';
|
|
5
|
+
export type LoadingStage = 'idle' | 'thinking' | 'analyzing' | 'generating' | 'complete';
|
|
5
6
|
export interface SendMessageOptions {
|
|
6
7
|
/** @deprecated Use walletContext instead */
|
|
7
8
|
userContext?: any;
|
|
@@ -11,6 +12,8 @@ export interface SendMessageOptions {
|
|
|
11
12
|
export interface UseChatReturn {
|
|
12
13
|
messages: Message[];
|
|
13
14
|
isLoading: boolean;
|
|
15
|
+
loadingStage: LoadingStage;
|
|
16
|
+
loadingLabel: string;
|
|
14
17
|
error: string | null;
|
|
15
18
|
/**
|
|
16
19
|
* Send a message to the AI
|
package/dist/hooks/useChat.js
CHANGED
|
@@ -6,11 +6,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.useChat = void 0;
|
|
7
7
|
const react_1 = require("react");
|
|
8
8
|
const ChainAIProvider_1 = require("../context/ChainAIProvider");
|
|
9
|
+
const LOADING_STAGES = [
|
|
10
|
+
{ stage: 'thinking', label: 'Thinking', duration: 600 },
|
|
11
|
+
{ stage: 'analyzing', label: 'Analyzing request', duration: 800 },
|
|
12
|
+
{ stage: 'generating', label: 'Generating response', duration: 0 }, // stays until response
|
|
13
|
+
];
|
|
9
14
|
const useChat = (conversationId) => {
|
|
10
15
|
const { client, isReady } = (0, ChainAIProvider_1.useChainAI)();
|
|
11
16
|
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
12
17
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
18
|
+
const [loadingStage, setLoadingStage] = (0, react_1.useState)('idle');
|
|
19
|
+
const [loadingLabel, setLoadingLabel] = (0, react_1.useState)('');
|
|
13
20
|
const [error, setError] = (0, react_1.useState)(null);
|
|
21
|
+
const stageTimeoutsRef = (0, react_1.useRef)([]);
|
|
22
|
+
const clearStageTimeouts = () => {
|
|
23
|
+
stageTimeoutsRef.current.forEach(clearTimeout);
|
|
24
|
+
stageTimeoutsRef.current = [];
|
|
25
|
+
};
|
|
26
|
+
const startLoadingStages = () => {
|
|
27
|
+
clearStageTimeouts();
|
|
28
|
+
let cumulativeDelay = 0;
|
|
29
|
+
LOADING_STAGES.forEach(({ stage, label, duration }) => {
|
|
30
|
+
const timeout = setTimeout(() => {
|
|
31
|
+
setLoadingStage(stage);
|
|
32
|
+
setLoadingLabel(label);
|
|
33
|
+
}, cumulativeDelay);
|
|
34
|
+
stageTimeoutsRef.current.push(timeout);
|
|
35
|
+
cumulativeDelay += duration;
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const stopLoadingStages = () => {
|
|
39
|
+
clearStageTimeouts();
|
|
40
|
+
setLoadingStage('complete');
|
|
41
|
+
setLoadingLabel('');
|
|
42
|
+
// Reset to idle after a brief moment
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
setLoadingStage('idle');
|
|
45
|
+
}, 100);
|
|
46
|
+
};
|
|
14
47
|
const sendMessage = (0, react_1.useCallback)(async (text, options) => {
|
|
15
48
|
if (!client || !isReady) {
|
|
16
49
|
setError('Client not ready');
|
|
@@ -26,8 +59,10 @@ const useChat = (conversationId) => {
|
|
|
26
59
|
setMessages((prev) => [...prev, userMessage]);
|
|
27
60
|
setIsLoading(true);
|
|
28
61
|
setError(null);
|
|
62
|
+
startLoadingStages();
|
|
29
63
|
try {
|
|
30
64
|
let assistantResponse = '';
|
|
65
|
+
let hasReceivedContent = false;
|
|
31
66
|
const assistantMessage = {
|
|
32
67
|
id: (Date.now() + 1).toString(),
|
|
33
68
|
role: 'assistant',
|
|
@@ -42,6 +77,11 @@ const useChat = (conversationId) => {
|
|
|
42
77
|
walletContext: options?.walletContext,
|
|
43
78
|
}, {
|
|
44
79
|
onChunk: (chunk) => {
|
|
80
|
+
// Once we receive content, stop showing loading stages
|
|
81
|
+
if (!hasReceivedContent && chunk.length > 0) {
|
|
82
|
+
hasReceivedContent = true;
|
|
83
|
+
stopLoadingStages();
|
|
84
|
+
}
|
|
45
85
|
assistantResponse += chunk;
|
|
46
86
|
setMessages((prev) => {
|
|
47
87
|
const updated = [...prev];
|
|
@@ -53,15 +93,18 @@ const useChat = (conversationId) => {
|
|
|
53
93
|
});
|
|
54
94
|
},
|
|
55
95
|
onDone: () => {
|
|
96
|
+
stopLoadingStages();
|
|
56
97
|
setIsLoading(false);
|
|
57
98
|
},
|
|
58
99
|
onError: (err) => {
|
|
100
|
+
stopLoadingStages();
|
|
59
101
|
setError(err);
|
|
60
102
|
setIsLoading(false);
|
|
61
103
|
},
|
|
62
104
|
});
|
|
63
105
|
}
|
|
64
106
|
catch (err) {
|
|
107
|
+
stopLoadingStages();
|
|
65
108
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
66
109
|
setIsLoading(false);
|
|
67
110
|
}
|
|
@@ -69,10 +112,14 @@ const useChat = (conversationId) => {
|
|
|
69
112
|
const clearMessages = (0, react_1.useCallback)(() => {
|
|
70
113
|
setMessages([]);
|
|
71
114
|
setError(null);
|
|
115
|
+
setLoadingStage('idle');
|
|
116
|
+
setLoadingLabel('');
|
|
72
117
|
}, []);
|
|
73
118
|
return {
|
|
74
119
|
messages,
|
|
75
120
|
isLoading,
|
|
121
|
+
loadingStage,
|
|
122
|
+
loadingLabel,
|
|
76
123
|
error,
|
|
77
124
|
sendMessage,
|
|
78
125
|
clearMessages,
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { ActionPromptProps } from './components/ActionPrompt';
|
|
|
18
18
|
export { ChainAIProvider, useChainAI } from './context/ChainAIProvider';
|
|
19
19
|
export type { ChainAIContextValue, ChainAIProviderProps } from './context/ChainAIProvider';
|
|
20
20
|
export { useChat } from './hooks/useChat';
|
|
21
|
-
export type { UseChatReturn } from './hooks/useChat';
|
|
21
|
+
export type { UseChatReturn, LoadingStage } from './hooks/useChat';
|
|
22
22
|
export { defaultTheme, darkTheme, lightTheme } from './styles/themes';
|
|
23
23
|
export type { Theme } from './styles/themes';
|
|
24
24
|
export type { ChainAIConfig, ResponseCustomization, ResponseStyle, ResponseLength, Message, WalletAddress, WalletContext, } from '@chainai/core';
|
package/dist/styles/default.css
CHANGED
package/dist/styles/widget.css
CHANGED
|
@@ -278,6 +278,43 @@
|
|
|
278
278
|
40% { transform: scale(1); opacity: 1; }
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/* Loading Stage Indicator */
|
|
282
|
+
.chain-ai-widget-loading-stage {
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 10px;
|
|
286
|
+
padding: 12px 16px;
|
|
287
|
+
background: var(--chain-ai-assistant-bg, #f3f4f6);
|
|
288
|
+
border-radius: 16px;
|
|
289
|
+
border-bottom-left-radius: 4px;
|
|
290
|
+
color: var(--chain-ai-assistant-text, #6b7280);
|
|
291
|
+
font-size: 13px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.chain-ai-widget-loading-spinner {
|
|
295
|
+
width: 16px;
|
|
296
|
+
height: 16px;
|
|
297
|
+
border: 2px solid var(--chain-ai-border, #e5e7eb);
|
|
298
|
+
border-top-color: var(--chain-ai-primary, #3b82f6);
|
|
299
|
+
border-radius: 50%;
|
|
300
|
+
animation: chain-ai-spin 0.8s linear infinite;
|
|
301
|
+
flex-shrink: 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@keyframes chain-ai-spin {
|
|
305
|
+
to { transform: rotate(360deg); }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.chain-ai-widget-loading-text {
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* Message content alignment */
|
|
314
|
+
.chain-ai-widget-message-bubble .chain-ai-message {
|
|
315
|
+
text-align: left;
|
|
316
|
+
}
|
|
317
|
+
|
|
281
318
|
/* Input Area */
|
|
282
319
|
.chain-ai-widget-input-area {
|
|
283
320
|
display: flex;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainai/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "React components for Chain AI - Embeddable blockchain AI assistant widget",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"author": "Chain AI",
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chainai/core": "^1.0.
|
|
47
|
+
"@chainai/core": "^1.0.3"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/react": "^18.3.3",
|
package/styles/default.css
CHANGED
package/styles/widget.css
CHANGED
|
@@ -278,6 +278,43 @@
|
|
|
278
278
|
40% { transform: scale(1); opacity: 1; }
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/* Loading Stage Indicator */
|
|
282
|
+
.chain-ai-widget-loading-stage {
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 10px;
|
|
286
|
+
padding: 12px 16px;
|
|
287
|
+
background: var(--chain-ai-assistant-bg, #f3f4f6);
|
|
288
|
+
border-radius: 16px;
|
|
289
|
+
border-bottom-left-radius: 4px;
|
|
290
|
+
color: var(--chain-ai-assistant-text, #6b7280);
|
|
291
|
+
font-size: 13px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.chain-ai-widget-loading-spinner {
|
|
295
|
+
width: 16px;
|
|
296
|
+
height: 16px;
|
|
297
|
+
border: 2px solid var(--chain-ai-border, #e5e7eb);
|
|
298
|
+
border-top-color: var(--chain-ai-primary, #3b82f6);
|
|
299
|
+
border-radius: 50%;
|
|
300
|
+
animation: chain-ai-spin 0.8s linear infinite;
|
|
301
|
+
flex-shrink: 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@keyframes chain-ai-spin {
|
|
305
|
+
to { transform: rotate(360deg); }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.chain-ai-widget-loading-text {
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* Message content alignment */
|
|
314
|
+
.chain-ai-widget-message-bubble .chain-ai-message {
|
|
315
|
+
text-align: left;
|
|
316
|
+
}
|
|
317
|
+
|
|
281
318
|
/* Input Area */
|
|
282
319
|
.chain-ai-widget-input-area {
|
|
283
320
|
display: flex;
|