@chainai/react 1.0.0 → 1.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/README.md +9 -9
- package/dist/components/ChainAIWidget.d.ts +22 -1
- package/dist/components/ChainAIWidget.js +14 -3
- package/dist/context/ChainAIProvider.d.ts +6 -1
- package/dist/context/ChainAIProvider.js +6 -0
- package/dist/hooks/useChat.d.ts +13 -2
- package/dist/hooks/useChat.js +3 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/styles/default.css +156 -0
- package/dist/styles/widget.css +531 -0
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @chainai/react
|
|
2
2
|
|
|
3
3
|
React components for Chain AI - Embeddable blockchain AI assistant.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @
|
|
8
|
+
npm install @chainai/react @chainai/core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { ChainAI } from '@
|
|
14
|
+
import { ChainAI } from '@chainai/react';
|
|
15
15
|
|
|
16
16
|
function App() {
|
|
17
17
|
return (
|
|
@@ -38,7 +38,7 @@ function App() {
|
|
|
38
38
|
### Standalone Component
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
|
-
import { ChainAI } from '@
|
|
41
|
+
import { ChainAI } from '@chainai/react';
|
|
42
42
|
|
|
43
43
|
function App() {
|
|
44
44
|
return (
|
|
@@ -61,7 +61,7 @@ function App() {
|
|
|
61
61
|
### Advanced Usage with Provider
|
|
62
62
|
|
|
63
63
|
```typescript
|
|
64
|
-
import { ChainAIProvider, useChainAI, useChat } from '@
|
|
64
|
+
import { ChainAIProvider, useChainAI, useChat } from '@chainai/react';
|
|
65
65
|
|
|
66
66
|
function App() {
|
|
67
67
|
return (
|
|
@@ -112,7 +112,7 @@ function MyCustomChat() {
|
|
|
112
112
|
### Built-in Themes
|
|
113
113
|
|
|
114
114
|
```typescript
|
|
115
|
-
import { ChainAI, darkTheme, lightTheme } from '@
|
|
115
|
+
import { ChainAI, darkTheme, lightTheme } from '@chainai/react';
|
|
116
116
|
|
|
117
117
|
// Dark theme
|
|
118
118
|
<ChainAI config={config} theme={darkTheme} />
|
|
@@ -124,7 +124,7 @@ import { ChainAI, darkTheme, lightTheme } from '@chain-ai/react';
|
|
|
124
124
|
### Custom Theme
|
|
125
125
|
|
|
126
126
|
```typescript
|
|
127
|
-
import { ChainAI, Theme } from '@
|
|
127
|
+
import { ChainAI, Theme } from '@chainai/react';
|
|
128
128
|
|
|
129
129
|
const customTheme: Theme = {
|
|
130
130
|
colors: {
|
|
@@ -187,7 +187,7 @@ import {
|
|
|
187
187
|
ChatHeader,
|
|
188
188
|
useChat,
|
|
189
189
|
defaultTheme,
|
|
190
|
-
} from '@
|
|
190
|
+
} from '@chainai/react';
|
|
191
191
|
|
|
192
192
|
function CustomLayout() {
|
|
193
193
|
const { messages, sendMessage, isLoading } = useChat();
|
|
@@ -219,7 +219,7 @@ import type {
|
|
|
219
219
|
Message,
|
|
220
220
|
ChainAIConfig,
|
|
221
221
|
ResponseCustomization,
|
|
222
|
-
} from '@
|
|
222
|
+
} from '@chainai/react';
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
## License
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Works in any context (web, extension, mobile)
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react';
|
|
11
|
-
import type { ChainAIConfig } from '@chainai/core';
|
|
11
|
+
import type { ChainAIConfig, WalletAddress } from '@chainai/core';
|
|
12
12
|
import type { Theme } from '../styles/themes';
|
|
13
13
|
import '../styles/widget.css';
|
|
14
14
|
/** Branding configuration for white-labeling */
|
|
@@ -55,6 +55,27 @@ export interface DimensionConfig {
|
|
|
55
55
|
}
|
|
56
56
|
export interface ChainAIWidgetProps {
|
|
57
57
|
config: ChainAIConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Connected wallet addresses - passed with every request so the AI knows
|
|
60
|
+
* the user's wallets for balance checks, transaction history, etc.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const { address } = useAccount(); // wagmi
|
|
65
|
+
* const { publicKey } = useWallet(); // solana
|
|
66
|
+
*
|
|
67
|
+
* <ChainAIWidget
|
|
68
|
+
* config={{ apiKey: 'xxx' }}
|
|
69
|
+
* wallets={[
|
|
70
|
+
* { chain: 'ethereum', address: address },
|
|
71
|
+
* { chain: 'solana', address: publicKey?.toString() },
|
|
72
|
+
* ]}
|
|
73
|
+
* />
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
wallets?: WalletAddress[];
|
|
77
|
+
/** Currently active chain (optional, for context) */
|
|
78
|
+
activeChain?: string;
|
|
58
79
|
/** Branding/white-label configuration */
|
|
59
80
|
branding?: BrandingConfig;
|
|
60
81
|
/** Dimension/size configuration */
|
|
@@ -92,7 +92,7 @@ const Logo = ({ branding }) => {
|
|
|
92
92
|
// Default: use avatar as logo
|
|
93
93
|
return react_1.default.createElement(Avatar, { branding: branding, size: "medium" });
|
|
94
94
|
};
|
|
95
|
-
const ChainAIWidgetInner = ({ branding = {}, dimensions = {}, defaultOpen = false, position = 'bottom-right', customPosition, theme: customTheme, className = '', placeholder = 'Ask me anything...', zIndex = 9999, onToggle, onMessage, }) => {
|
|
95
|
+
const ChainAIWidgetInner = ({ wallets, activeChain, branding = {}, dimensions = {}, defaultOpen = false, position = 'bottom-right', customPosition, theme: customTheme, className = '', placeholder = 'Ask me anything...', zIndex = 9999, onToggle, onMessage, }) => {
|
|
96
96
|
const [isOpen, setIsOpen] = (0, react_1.useState)(defaultOpen);
|
|
97
97
|
const [input, setInput] = (0, react_1.useState)('');
|
|
98
98
|
const [pendingAction, setPendingAction] = (0, react_1.useState)(null);
|
|
@@ -166,7 +166,13 @@ const ChainAIWidgetInner = ({ branding = {}, dimensions = {}, defaultOpen = fals
|
|
|
166
166
|
const text = input;
|
|
167
167
|
setInput('');
|
|
168
168
|
onMessage?.(text);
|
|
169
|
-
|
|
169
|
+
// Include wallet context with every request
|
|
170
|
+
const walletContext = wallets?.length ? {
|
|
171
|
+
wallets,
|
|
172
|
+
activeChain,
|
|
173
|
+
activeAddress: wallets.find(w => w.isPrimary)?.address || wallets[0]?.address,
|
|
174
|
+
} : undefined;
|
|
175
|
+
await sendMessage(text, { walletContext });
|
|
170
176
|
};
|
|
171
177
|
const handleKeyDown = (e) => {
|
|
172
178
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -175,7 +181,12 @@ const ChainAIWidgetInner = ({ branding = {}, dimensions = {}, defaultOpen = fals
|
|
|
175
181
|
}
|
|
176
182
|
};
|
|
177
183
|
const handleActionFieldSubmit = (field, value) => {
|
|
178
|
-
|
|
184
|
+
const walletContext = wallets?.length ? {
|
|
185
|
+
wallets,
|
|
186
|
+
activeChain,
|
|
187
|
+
activeAddress: wallets.find(w => w.isPrimary)?.address || wallets[0]?.address,
|
|
188
|
+
} : undefined;
|
|
189
|
+
sendMessage(`${field}: ${value}`, { walletContext });
|
|
179
190
|
setPendingAction(null);
|
|
180
191
|
};
|
|
181
192
|
// CSS custom properties for theming and dimensions
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import React, { ReactNode } from 'react';
|
|
5
5
|
import { ChainAIClient } from '@chainai/core';
|
|
6
|
-
import type { ChainAIConfig, ResponseCustomization } from '@chainai/core';
|
|
6
|
+
import type { ChainAIConfig, ResponseCustomization, WalletProvider } from '@chainai/core';
|
|
7
7
|
export interface ChainAIContextValue {
|
|
8
8
|
client: ChainAIClient | null;
|
|
9
9
|
isReady: boolean;
|
|
10
10
|
error: string | null;
|
|
11
11
|
customization: ResponseCustomization | null;
|
|
12
12
|
updateCustomization: (customization: Partial<ResponseCustomization>) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Set or update the wallet provider function
|
|
15
|
+
* This allows dynamic updates when wallets connect/disconnect
|
|
16
|
+
*/
|
|
17
|
+
setWalletProvider: (provider: WalletProvider) => void;
|
|
13
18
|
}
|
|
14
19
|
export interface ChainAIProviderProps {
|
|
15
20
|
config: ChainAIConfig;
|
|
@@ -75,12 +75,18 @@ const ChainAIProvider = ({ config, children }) => {
|
|
|
75
75
|
const updated = await client.getCustomization();
|
|
76
76
|
setCustomization(updated);
|
|
77
77
|
};
|
|
78
|
+
const setWalletProvider = (0, react_1.useCallback)((provider) => {
|
|
79
|
+
if (client) {
|
|
80
|
+
client.setWalletProvider(provider);
|
|
81
|
+
}
|
|
82
|
+
}, [client]);
|
|
78
83
|
const value = {
|
|
79
84
|
client,
|
|
80
85
|
isReady,
|
|
81
86
|
error,
|
|
82
87
|
customization,
|
|
83
88
|
updateCustomization,
|
|
89
|
+
setWalletProvider,
|
|
84
90
|
};
|
|
85
91
|
return react_1.default.createElement(ChainAIContext.Provider, { value: value }, children);
|
|
86
92
|
};
|
package/dist/hooks/useChat.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useChat hook - Manages chat messages and state
|
|
3
3
|
*/
|
|
4
|
-
import type { Message } from '@chainai/core';
|
|
4
|
+
import type { Message, WalletContext } from '@chainai/core';
|
|
5
|
+
export interface SendMessageOptions {
|
|
6
|
+
/** @deprecated Use walletContext instead */
|
|
7
|
+
userContext?: any;
|
|
8
|
+
/** Override wallet context for this specific request */
|
|
9
|
+
walletContext?: WalletContext;
|
|
10
|
+
}
|
|
5
11
|
export interface UseChatReturn {
|
|
6
12
|
messages: Message[];
|
|
7
13
|
isLoading: boolean;
|
|
8
14
|
error: string | null;
|
|
9
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Send a message to the AI
|
|
17
|
+
* Wallet context is automatically included from the walletProvider in config.
|
|
18
|
+
* You can optionally override it by passing walletContext in options.
|
|
19
|
+
*/
|
|
20
|
+
sendMessage: (text: string, options?: SendMessageOptions) => Promise<void>;
|
|
10
21
|
clearMessages: () => void;
|
|
11
22
|
}
|
|
12
23
|
export declare const useChat: (conversationId?: string) => UseChatReturn;
|
package/dist/hooks/useChat.js
CHANGED
|
@@ -11,7 +11,7 @@ const useChat = (conversationId) => {
|
|
|
11
11
|
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
12
12
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
13
13
|
const [error, setError] = (0, react_1.useState)(null);
|
|
14
|
-
const sendMessage = (0, react_1.useCallback)(async (text,
|
|
14
|
+
const sendMessage = (0, react_1.useCallback)(async (text, options) => {
|
|
15
15
|
if (!client || !isReady) {
|
|
16
16
|
setError('Client not ready');
|
|
17
17
|
return;
|
|
@@ -38,7 +38,8 @@ const useChat = (conversationId) => {
|
|
|
38
38
|
await client.streamChat({
|
|
39
39
|
text,
|
|
40
40
|
conversationId: conversationId || 'default',
|
|
41
|
-
userContext,
|
|
41
|
+
userContext: options?.userContext,
|
|
42
|
+
walletContext: options?.walletContext,
|
|
42
43
|
}, {
|
|
43
44
|
onChunk: (chunk) => {
|
|
44
45
|
assistantResponse += chunk;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @chainai/react - React components for Chain AI
|
|
3
3
|
*
|
|
4
4
|
* Embeddable blockchain AI assistant components
|
|
5
5
|
*/
|
|
@@ -21,4 +21,4 @@ export { useChat } from './hooks/useChat';
|
|
|
21
21
|
export type { UseChatReturn } from './hooks/useChat';
|
|
22
22
|
export { defaultTheme, darkTheme, lightTheme } from './styles/themes';
|
|
23
23
|
export type { Theme } from './styles/themes';
|
|
24
|
-
export type { ChainAIConfig, ResponseCustomization, ResponseStyle, ResponseLength, Message, } from '@chainai/core';
|
|
24
|
+
export type { ChainAIConfig, ResponseCustomization, ResponseStyle, ResponseLength, Message, WalletAddress, WalletContext, } from '@chainai/core';
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/* Chain AI Default Styles */
|
|
2
|
+
|
|
3
|
+
.chain-ai-container {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
border: 1px solid #e5e7eb;
|
|
7
|
+
border-radius: 8px;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.chain-ai-header {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
padding: 16px;
|
|
17
|
+
border-bottom: 1px solid;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.chain-ai-header-title {
|
|
21
|
+
margin: 0;
|
|
22
|
+
font-size: 18px;
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.chain-ai-header-close {
|
|
27
|
+
background: none;
|
|
28
|
+
border: none;
|
|
29
|
+
font-size: 24px;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
padding: 0;
|
|
32
|
+
width: 32px;
|
|
33
|
+
height: 32px;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.chain-ai-messages {
|
|
40
|
+
flex: 1;
|
|
41
|
+
overflow-y: auto;
|
|
42
|
+
padding: 16px;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 12px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.chain-ai-welcome {
|
|
49
|
+
text-align: center;
|
|
50
|
+
padding: 48px 16px;
|
|
51
|
+
color: #6b7280;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.chain-ai-welcome h2 {
|
|
55
|
+
margin: 0 0 8px 0;
|
|
56
|
+
font-size: 24px;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
color: inherit;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.chain-ai-welcome p {
|
|
62
|
+
margin: 0;
|
|
63
|
+
font-size: 16px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.chain-ai-message {
|
|
67
|
+
max-width: 80%;
|
|
68
|
+
padding: 12px 16px;
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
position: relative;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.chain-ai-message-user {
|
|
74
|
+
align-self: flex-end;
|
|
75
|
+
margin-left: auto;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.chain-ai-message-assistant {
|
|
79
|
+
align-self: flex-start;
|
|
80
|
+
margin-right: auto;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.chain-ai-message-content {
|
|
84
|
+
margin: 0;
|
|
85
|
+
line-height: 1.5;
|
|
86
|
+
white-space: pre-wrap;
|
|
87
|
+
word-wrap: break-word;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.chain-ai-message-timestamp {
|
|
91
|
+
font-size: 11px;
|
|
92
|
+
opacity: 0.7;
|
|
93
|
+
margin-top: 4px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.chain-ai-input-container {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 8px;
|
|
99
|
+
padding: 16px;
|
|
100
|
+
border-top: 1px solid;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.chain-ai-input {
|
|
104
|
+
flex: 1;
|
|
105
|
+
padding: 12px;
|
|
106
|
+
border: 1px solid #e5e7eb;
|
|
107
|
+
border-radius: 6px;
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
outline: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.chain-ai-input:focus {
|
|
113
|
+
border-color: #3b82f6;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.chain-ai-input:disabled {
|
|
117
|
+
opacity: 0.5;
|
|
118
|
+
cursor: not-allowed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.chain-ai-send-button {
|
|
122
|
+
padding: 12px 24px;
|
|
123
|
+
border: none;
|
|
124
|
+
border-radius: 6px;
|
|
125
|
+
font-size: 14px;
|
|
126
|
+
font-weight: 500;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
transition: opacity 0.2s;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.chain-ai-send-button:hover:not(:disabled) {
|
|
132
|
+
opacity: 0.9;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.chain-ai-send-button:disabled {
|
|
136
|
+
opacity: 0.5;
|
|
137
|
+
cursor: not-allowed;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.chain-ai-loading {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
height: 100%;
|
|
145
|
+
color: #6b7280;
|
|
146
|
+
font-size: 14px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.chain-ai-error {
|
|
150
|
+
padding: 12px;
|
|
151
|
+
background-color: #fee2e2;
|
|
152
|
+
color: #dc2626;
|
|
153
|
+
border-radius: 6px;
|
|
154
|
+
margin: 8px 0;
|
|
155
|
+
font-size: 14px;
|
|
156
|
+
}
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain AI Widget Styles
|
|
3
|
+
*
|
|
4
|
+
* CSS Variables for theming:
|
|
5
|
+
* --chain-ai-primary: Primary brand color
|
|
6
|
+
* --chain-ai-primary-text: Text on primary color
|
|
7
|
+
* --chain-ai-bg: Background color
|
|
8
|
+
* --chain-ai-text: Main text color
|
|
9
|
+
* --chain-ai-border: Border color
|
|
10
|
+
* --chain-ai-user-bg: User message background
|
|
11
|
+
* --chain-ai-user-text: User message text
|
|
12
|
+
* --chain-ai-assistant-bg: Assistant message background
|
|
13
|
+
* --chain-ai-assistant-text: Assistant message text
|
|
14
|
+
* --chain-ai-font: Font family
|
|
15
|
+
*
|
|
16
|
+
* CSS Variables for dimensions:
|
|
17
|
+
* --chain-ai-fab-size: FAB button size (default: 56px)
|
|
18
|
+
* --chain-ai-fab-radius: FAB border radius (default: 50%)
|
|
19
|
+
* --chain-ai-window-width: Chat window width (default: 380px)
|
|
20
|
+
* --chain-ai-window-height: Chat window height (default: 520px)
|
|
21
|
+
* --chain-ai-window-radius: Chat window border radius (default: 16px)
|
|
22
|
+
* --chain-ai-avatar-gradient: Avatar gradient background
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/* Reset and base */
|
|
26
|
+
.chain-ai-widget,
|
|
27
|
+
.chain-ai-widget * {
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
margin: 0;
|
|
30
|
+
padding: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.chain-ai-widget {
|
|
34
|
+
position: fixed;
|
|
35
|
+
font-family: var(--chain-ai-font, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
line-height: 1.5;
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Floating Action Button */
|
|
42
|
+
.chain-ai-widget-fab {
|
|
43
|
+
width: var(--chain-ai-fab-size, 56px);
|
|
44
|
+
height: var(--chain-ai-fab-size, 56px);
|
|
45
|
+
border-radius: var(--chain-ai-fab-radius, 50%);
|
|
46
|
+
border: none;
|
|
47
|
+
background: var(--chain-ai-primary, #3b82f6);
|
|
48
|
+
color: var(--chain-ai-primary-text, #ffffff);
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
54
|
+
transition: all 0.2s ease;
|
|
55
|
+
position: relative;
|
|
56
|
+
z-index: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.chain-ai-widget-fab:hover {
|
|
60
|
+
transform: scale(1.05);
|
|
61
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2), 0 3px 6px rgba(0, 0, 0, 0.1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.chain-ai-widget-fab:active {
|
|
65
|
+
transform: scale(0.95);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.chain-ai-widget-fab-open {
|
|
69
|
+
background: var(--chain-ai-assistant-bg, #f3f4f6);
|
|
70
|
+
color: var(--chain-ai-text, #1f2937);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Chat Window */
|
|
74
|
+
.chain-ai-widget-window {
|
|
75
|
+
position: absolute;
|
|
76
|
+
bottom: calc(var(--chain-ai-fab-size, 56px) + 14px);
|
|
77
|
+
right: 0;
|
|
78
|
+
width: var(--chain-ai-window-width, 380px);
|
|
79
|
+
max-width: calc(100vw - 40px);
|
|
80
|
+
height: var(--chain-ai-window-height, 520px);
|
|
81
|
+
max-height: calc(100vh - 100px);
|
|
82
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
83
|
+
border-radius: var(--chain-ai-window-radius, 16px);
|
|
84
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
animation: chain-ai-slide-up 0.2s ease;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keyframes chain-ai-slide-up {
|
|
92
|
+
from {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
transform: translateY(10px);
|
|
95
|
+
}
|
|
96
|
+
to {
|
|
97
|
+
opacity: 1;
|
|
98
|
+
transform: translateY(0);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Header */
|
|
103
|
+
.chain-ai-widget-header {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: space-between;
|
|
107
|
+
padding: 16px;
|
|
108
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
109
|
+
border-bottom: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.chain-ai-widget-header-info {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 12px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.chain-ai-widget-avatar {
|
|
119
|
+
width: 36px;
|
|
120
|
+
height: 36px;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
background: linear-gradient(135deg, var(--chain-ai-primary, #3b82f6), #8b5cf6);
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
color: white;
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.chain-ai-widget-title {
|
|
131
|
+
font-weight: 600;
|
|
132
|
+
font-size: 16px;
|
|
133
|
+
color: var(--chain-ai-text, #1f2937);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.chain-ai-widget-close {
|
|
137
|
+
width: 32px;
|
|
138
|
+
height: 32px;
|
|
139
|
+
border: none;
|
|
140
|
+
background: transparent;
|
|
141
|
+
border-radius: 8px;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: center;
|
|
146
|
+
color: var(--chain-ai-text, #6b7280);
|
|
147
|
+
transition: background 0.15s;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.chain-ai-widget-close:hover {
|
|
151
|
+
background: var(--chain-ai-assistant-bg, #f3f4f6);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Messages Area */
|
|
155
|
+
.chain-ai-widget-messages {
|
|
156
|
+
flex: 1;
|
|
157
|
+
overflow-y: auto;
|
|
158
|
+
padding: 16px;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 16px;
|
|
162
|
+
scroll-behavior: smooth;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.chain-ai-widget-messages::-webkit-scrollbar {
|
|
166
|
+
width: 6px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.chain-ai-widget-messages::-webkit-scrollbar-track {
|
|
170
|
+
background: transparent;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.chain-ai-widget-messages::-webkit-scrollbar-thumb {
|
|
174
|
+
background: var(--chain-ai-border, #e5e7eb);
|
|
175
|
+
border-radius: 3px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Welcome Screen */
|
|
179
|
+
.chain-ai-widget-welcome {
|
|
180
|
+
text-align: center;
|
|
181
|
+
padding: 32px 16px;
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-direction: column;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.chain-ai-widget-welcome .chain-ai-widget-avatar {
|
|
189
|
+
width: 48px;
|
|
190
|
+
height: 48px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.chain-ai-widget-welcome h3 {
|
|
194
|
+
font-size: 18px;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
color: var(--chain-ai-text, #1f2937);
|
|
197
|
+
margin: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.chain-ai-widget-welcome p {
|
|
201
|
+
color: #6b7280;
|
|
202
|
+
font-size: 14px;
|
|
203
|
+
max-width: 280px;
|
|
204
|
+
margin: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Message Bubbles */
|
|
208
|
+
.chain-ai-widget-message {
|
|
209
|
+
display: flex;
|
|
210
|
+
gap: 8px;
|
|
211
|
+
max-width: 90%;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.chain-ai-widget-message-user {
|
|
215
|
+
align-self: flex-end;
|
|
216
|
+
flex-direction: row-reverse;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.chain-ai-widget-message-assistant {
|
|
220
|
+
align-self: flex-start;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.chain-ai-widget-message-assistant .chain-ai-widget-avatar {
|
|
224
|
+
width: 28px;
|
|
225
|
+
height: 28px;
|
|
226
|
+
font-size: 12px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.chain-ai-widget-message-bubble {
|
|
230
|
+
flex: 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.chain-ai-widget-message-bubble .chain-ai-message {
|
|
234
|
+
max-width: 100%;
|
|
235
|
+
border-radius: 16px;
|
|
236
|
+
padding: 10px 14px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.chain-ai-widget-message-user .chain-ai-message {
|
|
240
|
+
background: var(--chain-ai-user-bg, #3b82f6);
|
|
241
|
+
color: var(--chain-ai-user-text, #ffffff);
|
|
242
|
+
border-bottom-right-radius: 4px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.chain-ai-widget-message-assistant .chain-ai-message {
|
|
246
|
+
background: var(--chain-ai-assistant-bg, #f3f4f6);
|
|
247
|
+
color: var(--chain-ai-assistant-text, #1f2937);
|
|
248
|
+
border-bottom-left-radius: 4px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.chain-ai-widget-message-bubble .chain-ai-message-timestamp {
|
|
252
|
+
display: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Typing Indicator */
|
|
256
|
+
.chain-ai-widget-typing {
|
|
257
|
+
display: flex;
|
|
258
|
+
gap: 4px;
|
|
259
|
+
padding: 12px 16px;
|
|
260
|
+
background: var(--chain-ai-assistant-bg, #f3f4f6);
|
|
261
|
+
border-radius: 16px;
|
|
262
|
+
border-bottom-left-radius: 4px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.chain-ai-widget-typing span {
|
|
266
|
+
width: 8px;
|
|
267
|
+
height: 8px;
|
|
268
|
+
background: #9ca3af;
|
|
269
|
+
border-radius: 50%;
|
|
270
|
+
animation: chain-ai-bounce 1.4s infinite ease-in-out both;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.chain-ai-widget-typing span:nth-child(1) { animation-delay: -0.32s; }
|
|
274
|
+
.chain-ai-widget-typing span:nth-child(2) { animation-delay: -0.16s; }
|
|
275
|
+
|
|
276
|
+
@keyframes chain-ai-bounce {
|
|
277
|
+
0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }
|
|
278
|
+
40% { transform: scale(1); opacity: 1; }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Input Area */
|
|
282
|
+
.chain-ai-widget-input-area {
|
|
283
|
+
display: flex;
|
|
284
|
+
gap: 8px;
|
|
285
|
+
padding: 12px 16px;
|
|
286
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
287
|
+
border-top: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.chain-ai-widget-input {
|
|
291
|
+
flex: 1;
|
|
292
|
+
padding: 10px 14px;
|
|
293
|
+
border: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
294
|
+
border-radius: 24px;
|
|
295
|
+
font-size: 14px;
|
|
296
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
297
|
+
color: var(--chain-ai-text, #1f2937);
|
|
298
|
+
outline: none;
|
|
299
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.chain-ai-widget-input:focus {
|
|
303
|
+
border-color: var(--chain-ai-primary, #3b82f6);
|
|
304
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.chain-ai-widget-input::placeholder {
|
|
308
|
+
color: #9ca3af;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.chain-ai-widget-input:disabled {
|
|
312
|
+
opacity: 0.6;
|
|
313
|
+
cursor: not-allowed;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.chain-ai-widget-send {
|
|
317
|
+
width: 40px;
|
|
318
|
+
height: 40px;
|
|
319
|
+
border: none;
|
|
320
|
+
border-radius: 50%;
|
|
321
|
+
background: var(--chain-ai-primary, #3b82f6);
|
|
322
|
+
color: var(--chain-ai-primary-text, #ffffff);
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
display: flex;
|
|
325
|
+
align-items: center;
|
|
326
|
+
justify-content: center;
|
|
327
|
+
transition: all 0.15s;
|
|
328
|
+
flex-shrink: 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.chain-ai-widget-send:hover:not(:disabled) {
|
|
332
|
+
transform: scale(1.05);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.chain-ai-widget-send:disabled {
|
|
336
|
+
opacity: 0.5;
|
|
337
|
+
cursor: not-allowed;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Action Prompt */
|
|
341
|
+
.chain-ai-action-prompt {
|
|
342
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
343
|
+
border: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
344
|
+
border-radius: 12px;
|
|
345
|
+
padding: 16px;
|
|
346
|
+
margin-top: 8px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.chain-ai-action-prompt-header {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
gap: 12px;
|
|
353
|
+
margin-bottom: 12px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.chain-ai-action-prompt-icon {
|
|
357
|
+
width: 36px;
|
|
358
|
+
height: 36px;
|
|
359
|
+
border-radius: 10px;
|
|
360
|
+
background: rgba(59, 130, 246, 0.1);
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
justify-content: center;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.chain-ai-action-prompt-title {
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.chain-ai-action-prompt-action {
|
|
372
|
+
font-weight: 600;
|
|
373
|
+
font-size: 12px;
|
|
374
|
+
letter-spacing: 0.5px;
|
|
375
|
+
color: var(--chain-ai-primary, #3b82f6);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.chain-ai-action-prompt-label {
|
|
379
|
+
font-size: 13px;
|
|
380
|
+
color: #6b7280;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.chain-ai-action-prompt-message {
|
|
384
|
+
font-size: 14px;
|
|
385
|
+
color: var(--chain-ai-text, #1f2937);
|
|
386
|
+
margin-bottom: 16px;
|
|
387
|
+
line-height: 1.5;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.chain-ai-action-prompt-form {
|
|
391
|
+
display: flex;
|
|
392
|
+
flex-direction: column;
|
|
393
|
+
gap: 12px;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.chain-ai-action-prompt-field {
|
|
397
|
+
display: flex;
|
|
398
|
+
flex-direction: column;
|
|
399
|
+
gap: 6px;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.chain-ai-action-prompt-field-label {
|
|
403
|
+
font-size: 12px;
|
|
404
|
+
font-weight: 500;
|
|
405
|
+
color: #6b7280;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.chain-ai-action-prompt-quick-options {
|
|
409
|
+
display: flex;
|
|
410
|
+
flex-wrap: wrap;
|
|
411
|
+
gap: 6px;
|
|
412
|
+
margin-bottom: 6px;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.chain-ai-action-prompt-quick-btn {
|
|
416
|
+
padding: 6px 12px;
|
|
417
|
+
border: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
418
|
+
border-radius: 16px;
|
|
419
|
+
background: transparent;
|
|
420
|
+
font-size: 12px;
|
|
421
|
+
cursor: pointer;
|
|
422
|
+
transition: all 0.15s;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.chain-ai-action-prompt-quick-btn:hover {
|
|
426
|
+
border-color: var(--chain-ai-primary, #3b82f6);
|
|
427
|
+
color: var(--chain-ai-primary, #3b82f6);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.chain-ai-action-prompt-input {
|
|
431
|
+
padding: 10px 12px;
|
|
432
|
+
border: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
433
|
+
border-radius: 8px;
|
|
434
|
+
font-size: 14px;
|
|
435
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
436
|
+
color: var(--chain-ai-text, #1f2937);
|
|
437
|
+
outline: none;
|
|
438
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.chain-ai-action-prompt-input:focus {
|
|
442
|
+
border-color: var(--chain-ai-primary, #3b82f6);
|
|
443
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.chain-ai-action-prompt-input::placeholder {
|
|
447
|
+
color: #9ca3af;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.chain-ai-action-prompt-submit {
|
|
451
|
+
padding: 12px 16px;
|
|
452
|
+
border: none;
|
|
453
|
+
border-radius: 8px;
|
|
454
|
+
font-size: 14px;
|
|
455
|
+
font-weight: 500;
|
|
456
|
+
cursor: pointer;
|
|
457
|
+
transition: all 0.15s;
|
|
458
|
+
margin-top: 4px;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.chain-ai-action-prompt-submit:hover:not(:disabled) {
|
|
462
|
+
opacity: 0.9;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.chain-ai-action-prompt-submit:disabled {
|
|
466
|
+
opacity: 0.5;
|
|
467
|
+
cursor: not-allowed;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* Dark mode support via data attribute or class */
|
|
471
|
+
.chain-ai-widget[data-theme="dark"],
|
|
472
|
+
.chain-ai-widget.dark {
|
|
473
|
+
--chain-ai-bg: #1f2937;
|
|
474
|
+
--chain-ai-text: #f9fafb;
|
|
475
|
+
--chain-ai-border: #374151;
|
|
476
|
+
--chain-ai-assistant-bg: #374151;
|
|
477
|
+
--chain-ai-assistant-text: #f9fafb;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* Mobile responsive */
|
|
481
|
+
@media (max-width: 480px) {
|
|
482
|
+
.chain-ai-widget-window {
|
|
483
|
+
width: calc(100vw - 20px);
|
|
484
|
+
height: calc(100vh - 100px);
|
|
485
|
+
bottom: calc(var(--chain-ai-fab-size, 56px) + 14px);
|
|
486
|
+
right: -10px;
|
|
487
|
+
border-radius: var(--chain-ai-window-radius, 12px);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* Logo in header */
|
|
492
|
+
.chain-ai-widget-logo {
|
|
493
|
+
display: flex;
|
|
494
|
+
align-items: center;
|
|
495
|
+
justify-content: center;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.chain-ai-widget-logo img {
|
|
499
|
+
max-height: 28px;
|
|
500
|
+
width: auto;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* Powered by badge */
|
|
504
|
+
.chain-ai-widget-powered-by {
|
|
505
|
+
padding: 8px 16px;
|
|
506
|
+
text-align: center;
|
|
507
|
+
font-size: 11px;
|
|
508
|
+
color: #9ca3af;
|
|
509
|
+
border-top: 1px solid var(--chain-ai-border, #e5e7eb);
|
|
510
|
+
background: var(--chain-ai-bg, #ffffff);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.chain-ai-widget-powered-by a {
|
|
514
|
+
color: var(--chain-ai-primary, #3b82f6);
|
|
515
|
+
text-decoration: none;
|
|
516
|
+
font-weight: 500;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.chain-ai-widget-powered-by a:hover {
|
|
520
|
+
text-decoration: underline;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* Custom header support */
|
|
524
|
+
.chain-ai-widget-header-custom {
|
|
525
|
+
padding: 12px 16px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* Custom welcome support */
|
|
529
|
+
.chain-ai-widget-welcome-custom {
|
|
530
|
+
padding: 16px;
|
|
531
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainai/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React components for Chain AI - Embeddable blockchain AI assistant widget",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.esm.js",
|
|
7
6
|
"types": "dist/index.d.ts",
|
|
8
7
|
"sideEffects": [
|
|
9
8
|
"**/*.css"
|
|
@@ -23,7 +22,7 @@
|
|
|
23
22
|
},
|
|
24
23
|
"scripts": {
|
|
25
24
|
"build": "tsc && npm run copy-styles",
|
|
26
|
-
"copy-styles": "mkdir -p styles && cp src/styles/*.css styles/",
|
|
25
|
+
"copy-styles": "mkdir -p styles && cp src/styles/*.css styles/ && mkdir -p dist/styles && cp src/styles/*.css dist/styles/",
|
|
27
26
|
"dev": "tsc --watch",
|
|
28
27
|
"test": "jest",
|
|
29
28
|
"lint": "eslint src --ext .ts,.tsx",
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
"author": "Chain AI",
|
|
46
45
|
"license": "MIT",
|
|
47
46
|
"dependencies": {
|
|
48
|
-
"@chainai/core": "^1.0.
|
|
47
|
+
"@chainai/core": "^1.0.2"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
50
|
"@types/react": "^18.3.3",
|