@bytexbyte/nxtlinq-ai-agent-sdk 1.0.7 → 1.0.9
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/api/nxtlinq-api.d.ts +3 -0
- package/dist/api/nxtlinq-api.d.ts.map +1 -0
- package/dist/api/nxtlinq-api.js +126 -0
- package/dist/components/ChatBot.d.ts +49 -0
- package/dist/components/ChatBot.d.ts.map +1 -0
- package/dist/components/ChatBot.js +836 -0
- package/dist/core/metakeepClient.d.ts +4 -0
- package/dist/core/metakeepClient.d.ts.map +1 -0
- package/dist/core/metakeepClient.js +11 -0
- package/dist/hooks/useNxtlinqAIT.d.ts +14 -0
- package/dist/hooks/useNxtlinqAIT.d.ts.map +1 -0
- package/dist/hooks/useNxtlinqAIT.js +98 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types/ait-api.d.ts +127 -0
- package/dist/types/ait-api.d.ts.map +1 -0
- package/dist/types/ait-api.js +1 -0
- package/package.json +5 -5
- package/src/api/nxtlinq-api.ts +0 -131
- package/src/components/ChatBot.tsx +0 -1192
- package/src/core/metakeepClient.ts +0 -13
- package/src/hooks/useNxtlinqAIT.ts +0 -99
- package/src/index.ts +0 -449
- package/src/types/ait-api.ts +0 -103
- package/src/types/window.d.ts +0 -9
- package/tsconfig.json +0 -25
- package/tsup.config.ts +0 -15
|
@@ -1,1192 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { ethers } from 'ethers';
|
|
3
|
-
import { createNxtlinqApi } from '../api/nxtlinq-api';
|
|
4
|
-
import stringify from 'fast-json-stable-stringify';
|
|
5
|
-
import metakeepClient from '../core/metakeepClient';
|
|
6
|
-
|
|
7
|
-
export interface Message {
|
|
8
|
-
id: string;
|
|
9
|
-
content: string;
|
|
10
|
-
role: 'user' | 'assistant';
|
|
11
|
-
timestamp: string;
|
|
12
|
-
button?: boolean;
|
|
13
|
-
error?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface PresetMessage {
|
|
17
|
-
text: string;
|
|
18
|
-
autoSend?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ToolUse {
|
|
22
|
-
name: string;
|
|
23
|
-
input: Record<string, any>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface AITMetadata {
|
|
27
|
-
model: string;
|
|
28
|
-
permissions: string[];
|
|
29
|
-
issuedBy: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface AIT {
|
|
33
|
-
aitId: string;
|
|
34
|
-
controller: string;
|
|
35
|
-
metadata: AITMetadata;
|
|
36
|
-
metadataHash: string;
|
|
37
|
-
metadataCid: string;
|
|
38
|
-
signature: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ChatBotProps {
|
|
42
|
-
projectId?: string;
|
|
43
|
-
onMessage?: (message: Message) => void;
|
|
44
|
-
onError?: (error: Error) => void;
|
|
45
|
-
onToolUse?: (toolUse: ToolUse) => Promise<Message | void>;
|
|
46
|
-
presetMessages?: PresetMessage[];
|
|
47
|
-
placeholder?: string;
|
|
48
|
-
className?: string;
|
|
49
|
-
maxRetries?: number;
|
|
50
|
-
retryDelay?: number;
|
|
51
|
-
serviceId: string;
|
|
52
|
-
apiKey: string;
|
|
53
|
-
apiSecret: string;
|
|
54
|
-
onVerifyWallet?: (address: string) => Promise<{
|
|
55
|
-
token: string;
|
|
56
|
-
}>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const PermissionForm: React.FC<{
|
|
60
|
-
hitAddress: string | null;
|
|
61
|
-
permissions: string[];
|
|
62
|
-
setPermissions: (permissions: string[]) => void;
|
|
63
|
-
setIsDisabled: (disabled: boolean) => void;
|
|
64
|
-
onClose: () => void;
|
|
65
|
-
onSave: () => void;
|
|
66
|
-
onConnectWallet: () => void;
|
|
67
|
-
onSignIn: () => void;
|
|
68
|
-
isNeedSignInWithWallet: boolean;
|
|
69
|
-
walletInfo: any;
|
|
70
|
-
onVerifyWallet: () => void;
|
|
71
|
-
}> = ({ hitAddress, permissions, setPermissions, setIsDisabled, onClose, onSave, onConnectWallet, onSignIn, isNeedSignInWithWallet, walletInfo, onVerifyWallet }) => {
|
|
72
|
-
const availablePermissions = [
|
|
73
|
-
{ id: 'setUserName', label: 'Set User Name' },
|
|
74
|
-
{ id: 'navigateToPage', label: 'Navigate To Page' },
|
|
75
|
-
{ id: 'addMember', label: 'Add Member' }
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
const hasAccessToken = !!localStorage.getItem('nxtlinqAITServiceAccessToken');
|
|
79
|
-
const isWalletVerified = walletInfo?.id;
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<div style={{
|
|
83
|
-
backgroundColor: 'white',
|
|
84
|
-
padding: '24px',
|
|
85
|
-
borderRadius: '12px',
|
|
86
|
-
width: '480px',
|
|
87
|
-
maxWidth: '90%',
|
|
88
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
|
89
|
-
}}>
|
|
90
|
-
<div style={{
|
|
91
|
-
display: 'flex',
|
|
92
|
-
justifyContent: 'space-between',
|
|
93
|
-
alignItems: 'center',
|
|
94
|
-
marginBottom: '24px'
|
|
95
|
-
}}>
|
|
96
|
-
<h3 style={{
|
|
97
|
-
margin: 0,
|
|
98
|
-
fontSize: '20px',
|
|
99
|
-
fontWeight: '600',
|
|
100
|
-
color: '#1a1a1a'
|
|
101
|
-
}}>AIT Settings</h3>
|
|
102
|
-
<button
|
|
103
|
-
onClick={onClose}
|
|
104
|
-
style={{
|
|
105
|
-
background: 'none',
|
|
106
|
-
border: 'none',
|
|
107
|
-
fontSize: '24px',
|
|
108
|
-
cursor: 'pointer',
|
|
109
|
-
color: '#666',
|
|
110
|
-
padding: '4px',
|
|
111
|
-
display: 'flex',
|
|
112
|
-
alignItems: 'center',
|
|
113
|
-
justifyContent: 'center'
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
×
|
|
117
|
-
</button>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
{!hitAddress ? (
|
|
121
|
-
<div style={{ textAlign: 'center', padding: '32px 0' }}>
|
|
122
|
-
<div style={{
|
|
123
|
-
width: '64px',
|
|
124
|
-
height: '64px',
|
|
125
|
-
margin: '0 auto 16px',
|
|
126
|
-
backgroundColor: '#f5f5f5',
|
|
127
|
-
borderRadius: '50%',
|
|
128
|
-
display: 'flex',
|
|
129
|
-
alignItems: 'center',
|
|
130
|
-
justifyContent: 'center'
|
|
131
|
-
}}>
|
|
132
|
-
<span style={{ fontSize: '32px' }}>👛</span>
|
|
133
|
-
</div>
|
|
134
|
-
<p style={{
|
|
135
|
-
marginBottom: '24px',
|
|
136
|
-
fontSize: '16px',
|
|
137
|
-
color: '#666'
|
|
138
|
-
}}>Please connect your wallet first</p>
|
|
139
|
-
<button
|
|
140
|
-
onClick={onConnectWallet}
|
|
141
|
-
style={{
|
|
142
|
-
padding: '12px 24px',
|
|
143
|
-
backgroundColor: '#007bff',
|
|
144
|
-
color: 'white',
|
|
145
|
-
border: 'none',
|
|
146
|
-
borderRadius: '8px',
|
|
147
|
-
cursor: 'pointer',
|
|
148
|
-
fontSize: '16px',
|
|
149
|
-
fontWeight: '500',
|
|
150
|
-
transition: 'background-color 0.2s'
|
|
151
|
-
}}
|
|
152
|
-
onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#0056b3'}
|
|
153
|
-
onMouseOut={(e) => e.currentTarget.style.backgroundColor = '#007bff'}
|
|
154
|
-
>
|
|
155
|
-
Connect Wallet
|
|
156
|
-
</button>
|
|
157
|
-
</div>
|
|
158
|
-
) : isNeedSignInWithWallet ? (
|
|
159
|
-
<div style={{ textAlign: 'center', padding: '32px 0' }}>
|
|
160
|
-
<div style={{ marginBottom: '24px' }}>
|
|
161
|
-
<h4 style={{
|
|
162
|
-
marginBottom: '12px',
|
|
163
|
-
fontSize: '16px',
|
|
164
|
-
color: '#666'
|
|
165
|
-
}}>Connected Wallet</h4>
|
|
166
|
-
<p style={{
|
|
167
|
-
wordBreak: 'break-all',
|
|
168
|
-
backgroundColor: '#f8f9fa',
|
|
169
|
-
padding: '12px',
|
|
170
|
-
borderRadius: '8px',
|
|
171
|
-
fontSize: '14px',
|
|
172
|
-
color: '#333',
|
|
173
|
-
border: '1px solid #e9ecef'
|
|
174
|
-
}}>
|
|
175
|
-
{hitAddress}
|
|
176
|
-
</p>
|
|
177
|
-
</div>
|
|
178
|
-
<p style={{
|
|
179
|
-
marginBottom: '24px',
|
|
180
|
-
fontSize: '16px',
|
|
181
|
-
color: '#666'
|
|
182
|
-
}}>Please sign in to continue</p>
|
|
183
|
-
<button
|
|
184
|
-
onClick={onSignIn}
|
|
185
|
-
style={{
|
|
186
|
-
padding: '12px 24px',
|
|
187
|
-
backgroundColor: '#007bff',
|
|
188
|
-
color: 'white',
|
|
189
|
-
border: 'none',
|
|
190
|
-
borderRadius: '8px',
|
|
191
|
-
cursor: 'pointer',
|
|
192
|
-
fontSize: '16px',
|
|
193
|
-
fontWeight: '500',
|
|
194
|
-
transition: 'background-color 0.2s'
|
|
195
|
-
}}
|
|
196
|
-
onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#0056b3'}
|
|
197
|
-
onMouseOut={(e) => e.currentTarget.style.backgroundColor = '#007bff'}
|
|
198
|
-
>
|
|
199
|
-
Sign In
|
|
200
|
-
</button>
|
|
201
|
-
</div>
|
|
202
|
-
) : !isWalletVerified ? (
|
|
203
|
-
<div style={{ textAlign: 'center', padding: '32px 0' }}>
|
|
204
|
-
<div style={{ marginBottom: '24px' }}>
|
|
205
|
-
<h4 style={{
|
|
206
|
-
marginBottom: '12px',
|
|
207
|
-
fontSize: '16px',
|
|
208
|
-
color: '#666'
|
|
209
|
-
}}>Connected Wallet</h4>
|
|
210
|
-
<p style={{
|
|
211
|
-
wordBreak: 'break-all',
|
|
212
|
-
backgroundColor: '#f8f9fa',
|
|
213
|
-
padding: '12px',
|
|
214
|
-
borderRadius: '8px',
|
|
215
|
-
fontSize: '14px',
|
|
216
|
-
color: '#333',
|
|
217
|
-
border: '1px solid #e9ecef'
|
|
218
|
-
}}>
|
|
219
|
-
{hitAddress}
|
|
220
|
-
</p>
|
|
221
|
-
</div>
|
|
222
|
-
<p style={{
|
|
223
|
-
marginBottom: '24px',
|
|
224
|
-
fontSize: '16px',
|
|
225
|
-
color: '#666'
|
|
226
|
-
}}>Please verify your wallet to continue</p>
|
|
227
|
-
<button
|
|
228
|
-
onClick={onVerifyWallet}
|
|
229
|
-
style={{
|
|
230
|
-
padding: '12px 24px',
|
|
231
|
-
backgroundColor: '#007bff',
|
|
232
|
-
color: 'white',
|
|
233
|
-
border: 'none',
|
|
234
|
-
borderRadius: '8px',
|
|
235
|
-
cursor: 'pointer',
|
|
236
|
-
fontSize: '16px',
|
|
237
|
-
fontWeight: '500',
|
|
238
|
-
transition: 'background-color 0.2s'
|
|
239
|
-
}}
|
|
240
|
-
onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#0056b3'}
|
|
241
|
-
onMouseOut={(e) => e.currentTarget.style.backgroundColor = '#007bff'}
|
|
242
|
-
>
|
|
243
|
-
Verify your wallet
|
|
244
|
-
</button>
|
|
245
|
-
</div>
|
|
246
|
-
) : (
|
|
247
|
-
<>
|
|
248
|
-
<div style={{ marginBottom: '24px' }}>
|
|
249
|
-
<h4 style={{
|
|
250
|
-
marginBottom: '12px',
|
|
251
|
-
fontSize: '16px',
|
|
252
|
-
color: '#666'
|
|
253
|
-
}}>Connected Wallet</h4>
|
|
254
|
-
<p style={{
|
|
255
|
-
wordBreak: 'break-all',
|
|
256
|
-
backgroundColor: '#f8f9fa',
|
|
257
|
-
padding: '12px',
|
|
258
|
-
borderRadius: '8px',
|
|
259
|
-
fontSize: '14px',
|
|
260
|
-
color: '#333',
|
|
261
|
-
border: '1px solid #e9ecef'
|
|
262
|
-
}}>
|
|
263
|
-
{hitAddress}
|
|
264
|
-
</p>
|
|
265
|
-
</div>
|
|
266
|
-
<div style={{ marginBottom: '24px' }}>
|
|
267
|
-
<h4 style={{
|
|
268
|
-
marginBottom: '12px',
|
|
269
|
-
fontSize: '16px',
|
|
270
|
-
color: '#666'
|
|
271
|
-
}}>Permissions</h4>
|
|
272
|
-
<div style={{
|
|
273
|
-
backgroundColor: '#f8f9fa',
|
|
274
|
-
padding: '16px',
|
|
275
|
-
borderRadius: '8px',
|
|
276
|
-
border: '1px solid #e9ecef'
|
|
277
|
-
}}>
|
|
278
|
-
{availablePermissions.map((permission) => (
|
|
279
|
-
<div key={permission.id} style={{ marginBottom: '12px' }}>
|
|
280
|
-
<label style={{
|
|
281
|
-
display: 'flex',
|
|
282
|
-
alignItems: 'center',
|
|
283
|
-
gap: '12px',
|
|
284
|
-
cursor: 'pointer',
|
|
285
|
-
padding: '8px',
|
|
286
|
-
borderRadius: '6px',
|
|
287
|
-
transition: 'background-color 0.2s'
|
|
288
|
-
}}
|
|
289
|
-
onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#e9ecef'}
|
|
290
|
-
onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
|
|
291
|
-
>
|
|
292
|
-
<input
|
|
293
|
-
type="checkbox"
|
|
294
|
-
checked={permissions.includes(permission.id)}
|
|
295
|
-
onChange={() => {
|
|
296
|
-
const newPermissions = permissions.includes(permission.id)
|
|
297
|
-
? permissions.filter(p => p !== permission.id)
|
|
298
|
-
: [...permissions, permission.id].sort();
|
|
299
|
-
setPermissions(newPermissions);
|
|
300
|
-
setIsDisabled(false);
|
|
301
|
-
}}
|
|
302
|
-
style={{
|
|
303
|
-
margin: 0,
|
|
304
|
-
width: '18px',
|
|
305
|
-
height: '18px',
|
|
306
|
-
cursor: 'pointer'
|
|
307
|
-
}}
|
|
308
|
-
/>
|
|
309
|
-
<span style={{
|
|
310
|
-
fontSize: '14px',
|
|
311
|
-
color: '#333'
|
|
312
|
-
}}>{permission.label}</span>
|
|
313
|
-
</label>
|
|
314
|
-
</div>
|
|
315
|
-
))}
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
<div style={{
|
|
319
|
-
display: 'flex',
|
|
320
|
-
justifyContent: 'flex-end',
|
|
321
|
-
gap: '12px',
|
|
322
|
-
borderTop: '1px solid #e9ecef',
|
|
323
|
-
paddingTop: '24px'
|
|
324
|
-
}}>
|
|
325
|
-
<button
|
|
326
|
-
onClick={onClose}
|
|
327
|
-
style={{
|
|
328
|
-
padding: '10px 20px',
|
|
329
|
-
backgroundColor: '#f8f9fa',
|
|
330
|
-
color: '#666',
|
|
331
|
-
border: '1px solid #dee2e6',
|
|
332
|
-
borderRadius: '8px',
|
|
333
|
-
cursor: 'pointer',
|
|
334
|
-
fontSize: '14px',
|
|
335
|
-
fontWeight: '500',
|
|
336
|
-
transition: 'all 0.2s'
|
|
337
|
-
}}
|
|
338
|
-
onMouseOver={(e) => {
|
|
339
|
-
e.currentTarget.style.backgroundColor = '#e9ecef';
|
|
340
|
-
e.currentTarget.style.borderColor = '#ced4da';
|
|
341
|
-
}}
|
|
342
|
-
onMouseOut={(e) => {
|
|
343
|
-
e.currentTarget.style.backgroundColor = '#f8f9fa';
|
|
344
|
-
e.currentTarget.style.borderColor = '#dee2e6';
|
|
345
|
-
}}
|
|
346
|
-
>
|
|
347
|
-
Cancel
|
|
348
|
-
</button>
|
|
349
|
-
<button
|
|
350
|
-
onClick={onSave}
|
|
351
|
-
disabled={permissions.length === 0}
|
|
352
|
-
style={{
|
|
353
|
-
padding: '10px 20px',
|
|
354
|
-
backgroundColor: permissions.length === 0 ? '#e9ecef' : '#007bff',
|
|
355
|
-
color: 'white',
|
|
356
|
-
border: 'none',
|
|
357
|
-
borderRadius: '8px',
|
|
358
|
-
cursor: permissions.length === 0 ? 'not-allowed' : 'pointer',
|
|
359
|
-
fontSize: '14px',
|
|
360
|
-
fontWeight: '500',
|
|
361
|
-
transition: 'background-color 0.2s'
|
|
362
|
-
}}
|
|
363
|
-
onMouseOver={(e) => {
|
|
364
|
-
if (permissions.length > 0) {
|
|
365
|
-
e.currentTarget.style.backgroundColor = '#0056b3';
|
|
366
|
-
}
|
|
367
|
-
}}
|
|
368
|
-
onMouseOut={(e) => {
|
|
369
|
-
if (permissions.length > 0) {
|
|
370
|
-
e.currentTarget.style.backgroundColor = '#007bff';
|
|
371
|
-
}
|
|
372
|
-
}}
|
|
373
|
-
>
|
|
374
|
-
Save
|
|
375
|
-
</button>
|
|
376
|
-
</div>
|
|
377
|
-
</>
|
|
378
|
-
)}
|
|
379
|
-
</div>
|
|
380
|
-
);
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
export const ChatBot: React.FC<ChatBotProps> = ({
|
|
384
|
-
projectId,
|
|
385
|
-
onMessage,
|
|
386
|
-
onError,
|
|
387
|
-
onToolUse,
|
|
388
|
-
presetMessages = [],
|
|
389
|
-
placeholder = 'Type a message...',
|
|
390
|
-
className = '',
|
|
391
|
-
maxRetries = 3,
|
|
392
|
-
retryDelay = 1000,
|
|
393
|
-
serviceId,
|
|
394
|
-
apiKey,
|
|
395
|
-
apiSecret,
|
|
396
|
-
onVerifyWallet
|
|
397
|
-
}) => {
|
|
398
|
-
const [messages, setMessages] = React.useState<Message[]>([]);
|
|
399
|
-
const [inputValue, setInputValue] = React.useState('');
|
|
400
|
-
const [isLoading, setIsLoading] = React.useState(false);
|
|
401
|
-
const [isOpen, setIsOpen] = React.useState(false);
|
|
402
|
-
const [hitAddress, setHitAddress] = React.useState<string | null>(null);
|
|
403
|
-
const [ait, setAit] = React.useState<AIT | null>(null);
|
|
404
|
-
const [permissions, setPermissions] = React.useState<string[]>([]);
|
|
405
|
-
const [showPermissionForm, setShowPermissionForm] = React.useState(false);
|
|
406
|
-
const [success, setSuccess] = React.useState(false);
|
|
407
|
-
const messagesEndRef = React.useRef<HTMLDivElement>(null);
|
|
408
|
-
const [isDisabled, setIsDisabled] = React.useState(true);
|
|
409
|
-
const [signer, setSigner] = React.useState<ethers.Signer | null>(null);
|
|
410
|
-
const [provider, setProvider] = React.useState<ethers.BrowserProvider | null>(null);
|
|
411
|
-
const [accessToken, setAccessToken] = React.useState<string | null>(null);
|
|
412
|
-
const [walletInfo, setWalletInfo] = React.useState<any>(null);
|
|
413
|
-
const verifyWalletBtnRef = React.useRef<HTMLButtonElement>(null);
|
|
414
|
-
|
|
415
|
-
const nxtlinqApi = React.useMemo(() => createNxtlinqApi(apiKey, apiSecret), [apiKey, apiSecret]);
|
|
416
|
-
|
|
417
|
-
const isNeedSignInWithWallet = React.useMemo(() => {
|
|
418
|
-
if (!hitAddress) {
|
|
419
|
-
console.log('isNeedSignInWithWallet: false (no hitAddress)');
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const nxtlinqAITServiceAccessToken = localStorage.getItem('nxtlinqAITServiceAccessToken');
|
|
424
|
-
if (!nxtlinqAITServiceAccessToken) {
|
|
425
|
-
console.log('isNeedSignInWithWallet: true (no token)');
|
|
426
|
-
return true;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
try {
|
|
430
|
-
// Check if the token is expired
|
|
431
|
-
const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
|
|
432
|
-
const exp = payload.exp * 1000; // Convert to milliseconds
|
|
433
|
-
const now = Date.now();
|
|
434
|
-
if (exp < now) {
|
|
435
|
-
console.log('isNeedSignInWithWallet: true (token expired)');
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Check is the token's payload has the same address as the wallet address
|
|
440
|
-
const address = payload.address;
|
|
441
|
-
if (address !== hitAddress) {
|
|
442
|
-
console.log('isNeedSignInWithWallet: true (address mismatch)');
|
|
443
|
-
return true;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
console.log('isNeedSignInWithWallet: false (valid token)');
|
|
447
|
-
return false;
|
|
448
|
-
} catch (error) {
|
|
449
|
-
console.error('Error parsing token:', error);
|
|
450
|
-
console.log('isNeedSignInWithWallet: true (token parse error)');
|
|
451
|
-
return true;
|
|
452
|
-
}
|
|
453
|
-
}, [hitAddress]);
|
|
454
|
-
|
|
455
|
-
const handleVerifySuccess = (address: string) => {
|
|
456
|
-
console.log('handleVerifySuccess called with address:', address);
|
|
457
|
-
localStorage.setItem(`wallet_verified_${address}`, 'true');
|
|
458
|
-
const getWalletInfo = async () => {
|
|
459
|
-
try {
|
|
460
|
-
const token = JSON.parse(localStorage.getItem('nxtlinqAITServiceAccessToken') || '');
|
|
461
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
462
|
-
console.log('Wallet response:', walletResponse);
|
|
463
|
-
if (!('error' in walletResponse)) {
|
|
464
|
-
setWalletInfo(walletResponse);
|
|
465
|
-
const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
466
|
-
serviceId,
|
|
467
|
-
controller: address
|
|
468
|
-
}, token);
|
|
469
|
-
console.log('AIT response:', aitResponse);
|
|
470
|
-
if (!('error' in aitResponse)) {
|
|
471
|
-
setAit(aitResponse);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
} catch (error) {
|
|
475
|
-
console.error('Failed to update wallet info after verification:', error);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
getWalletInfo();
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
React.useEffect(() => {
|
|
482
|
-
const getWalletInfo = async () => {
|
|
483
|
-
if (!hitAddress) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
if (isNeedSignInWithWallet) {
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
const getWalletResponse = await nxtlinqApi.wallet.getWallet({ address: hitAddress }, accessToken as string);
|
|
490
|
-
if ('error' in getWalletResponse) {
|
|
491
|
-
console.error(getWalletResponse.error);
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
setWalletInfo(getWalletResponse);
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
getWalletInfo();
|
|
498
|
-
}, [hitAddress, isNeedSignInWithWallet]);
|
|
499
|
-
|
|
500
|
-
const handleVerifyWalletClick = async () => {
|
|
501
|
-
console.log('handleVerifyWalletClick called');
|
|
502
|
-
console.log('Current states:', {
|
|
503
|
-
hitAddress,
|
|
504
|
-
isNeedSignInWithWallet,
|
|
505
|
-
walletInfo,
|
|
506
|
-
signer: !!signer
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
if (!hitAddress) {
|
|
510
|
-
alert('Please connect your wallet first.');
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
try {
|
|
515
|
-
if (onVerifyWallet) {
|
|
516
|
-
setIsLoading(true);
|
|
517
|
-
const result = await onVerifyWallet(hitAddress);
|
|
518
|
-
console.log('Verify wallet response:', result);
|
|
519
|
-
const { token } = result;
|
|
520
|
-
const address = hitAddress;
|
|
521
|
-
if (token && address) {
|
|
522
|
-
const payload = {
|
|
523
|
-
address: hitAddress,
|
|
524
|
-
token,
|
|
525
|
-
timestamp: Date.now(),
|
|
526
|
-
method: 'berifyme'
|
|
527
|
-
};
|
|
528
|
-
try {
|
|
529
|
-
const verifyWalletResponse = await nxtlinqApi.wallet.verifyWallet({ ...payload }, token);
|
|
530
|
-
if ('error' in verifyWalletResponse) {
|
|
531
|
-
if (verifyWalletResponse.error === 'Wallet already exists') {
|
|
532
|
-
// 已存在则直接获取钱包信息
|
|
533
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
534
|
-
if (!('error' in walletResponse)) {
|
|
535
|
-
setWalletInfo(walletResponse);
|
|
536
|
-
const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({ serviceId, controller: address }, token);
|
|
537
|
-
if (!('error' in aitResponse)) {
|
|
538
|
-
setAit(aitResponse);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
// 清理 URL 上的 token
|
|
542
|
-
if (typeof window !== 'undefined') {
|
|
543
|
-
try {
|
|
544
|
-
const url = new URL(window.location.href);
|
|
545
|
-
url.searchParams.delete('token');
|
|
546
|
-
window.history.replaceState({}, '', url.toString());
|
|
547
|
-
} catch (e) {
|
|
548
|
-
console.error('Failed to clean URL:', e);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
setIsLoading(false);
|
|
552
|
-
return { token, hitAddress: address };
|
|
553
|
-
}
|
|
554
|
-
alert(verifyWalletResponse.error);
|
|
555
|
-
setIsLoading(false);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
// 验证成功,获取钱包信息
|
|
559
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
560
|
-
if (!('error' in walletResponse)) {
|
|
561
|
-
setWalletInfo(walletResponse);
|
|
562
|
-
const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({ serviceId, controller: address }, token);
|
|
563
|
-
if (!('error' in aitResponse)) {
|
|
564
|
-
setAit(aitResponse);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// 清理 URL 上的 token
|
|
568
|
-
if (typeof window !== 'undefined') {
|
|
569
|
-
try {
|
|
570
|
-
const url = new URL(window.location.href);
|
|
571
|
-
url.searchParams.delete('token');
|
|
572
|
-
window.history.replaceState({}, '', url.toString());
|
|
573
|
-
} catch (e) {
|
|
574
|
-
console.error('Failed to clean URL:', e);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
setIsLoading(false);
|
|
578
|
-
return { token, hitAddress: address };
|
|
579
|
-
} catch (error) {
|
|
580
|
-
let msg = 'Verification failed';
|
|
581
|
-
if (typeof error === 'object' && error !== null && 'response' in error) {
|
|
582
|
-
// @ts-ignore
|
|
583
|
-
msg = error.response?.data?.error || error.message || msg;
|
|
584
|
-
} else if (error instanceof Error) {
|
|
585
|
-
msg = error.message;
|
|
586
|
-
}
|
|
587
|
-
console.error('Wallet verification failed:', error);
|
|
588
|
-
alert(msg);
|
|
589
|
-
setIsLoading(false);
|
|
590
|
-
throw error;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
setIsLoading(false);
|
|
594
|
-
return { token, hitAddress: address };
|
|
595
|
-
}
|
|
596
|
-
} catch (error) {
|
|
597
|
-
console.error('Failed to verify wallet:', error);
|
|
598
|
-
setIsLoading(false);
|
|
599
|
-
alert('Failed to verify wallet. Please try again.');
|
|
600
|
-
throw error;
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
// 添加一个 useEffect 来处理 URL 参数
|
|
605
|
-
React.useEffect(() => {
|
|
606
|
-
if (typeof window !== 'undefined') {
|
|
607
|
-
try {
|
|
608
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
609
|
-
const token = urlParams.get('token');
|
|
610
|
-
if (token && hitAddress) {
|
|
611
|
-
handleVerifyWalletClick();
|
|
612
|
-
}
|
|
613
|
-
} catch (e) {
|
|
614
|
-
console.error('Failed to get URL params:', e);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}, [hitAddress]);
|
|
618
|
-
|
|
619
|
-
React.useEffect(() => {
|
|
620
|
-
console.log('Wallet states changed:', {
|
|
621
|
-
hitAddress,
|
|
622
|
-
isNeedSignInWithWallet,
|
|
623
|
-
walletInfo,
|
|
624
|
-
signer: !!signer
|
|
625
|
-
});
|
|
626
|
-
}, [hitAddress, isNeedSignInWithWallet, walletInfo, signer]);
|
|
627
|
-
|
|
628
|
-
const scrollToBottom = () => {
|
|
629
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
React.useEffect(() => {
|
|
633
|
-
scrollToBottom();
|
|
634
|
-
}, [messages]);
|
|
635
|
-
|
|
636
|
-
React.useEffect(() => {
|
|
637
|
-
if (!hitAddress) {
|
|
638
|
-
setAit(null);
|
|
639
|
-
}
|
|
640
|
-
}, [hitAddress]);
|
|
641
|
-
|
|
642
|
-
React.useEffect(() => {
|
|
643
|
-
if (!ait) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const aitPermissions = ait.metadata?.permissions || [];
|
|
648
|
-
setPermissions(aitPermissions);
|
|
649
|
-
}, [ait]);
|
|
650
|
-
|
|
651
|
-
const connectWallet = React.useCallback(async () => {
|
|
652
|
-
if (typeof window === 'undefined') {
|
|
653
|
-
console.error('Web3 is not available in server-side rendering');
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
try {
|
|
658
|
-
const web3Provider = await metakeepClient.ethereum;
|
|
659
|
-
if (!web3Provider) {
|
|
660
|
-
throw new Error('Web3 provider not available');
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
await web3Provider.enable();
|
|
664
|
-
const ethersProvider = new ethers.BrowserProvider(web3Provider);
|
|
665
|
-
|
|
666
|
-
const userSigner = await ethersProvider.getSigner();
|
|
667
|
-
const userAddress = await userSigner.getAddress();
|
|
668
|
-
|
|
669
|
-
localStorage.setItem('walletAddress', userAddress);
|
|
670
|
-
|
|
671
|
-
setHitAddress(userAddress);
|
|
672
|
-
setSigner(userSigner);
|
|
673
|
-
setProvider(ethersProvider);
|
|
674
|
-
|
|
675
|
-
return userAddress;
|
|
676
|
-
} catch (error) {
|
|
677
|
-
console.error('Failed to connect wallet:', error);
|
|
678
|
-
localStorage.removeItem('walletAddress');
|
|
679
|
-
setHitAddress(null);
|
|
680
|
-
setSigner(null);
|
|
681
|
-
setProvider(null);
|
|
682
|
-
throw error;
|
|
683
|
-
}
|
|
684
|
-
}, []);
|
|
685
|
-
|
|
686
|
-
const signInWallet = async () => {
|
|
687
|
-
if (!hitAddress) {
|
|
688
|
-
alert('Please connect your wallet first.');
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
if (!signer) {
|
|
693
|
-
alert('Please connect your wallet first.');
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
try {
|
|
698
|
-
const nonceResponse = await nxtlinqApi.auth.getNonce({ address: hitAddress });
|
|
699
|
-
if ('error' in nonceResponse) {
|
|
700
|
-
alert(nonceResponse.error);
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
const payload = {
|
|
705
|
-
address: hitAddress,
|
|
706
|
-
code: nonceResponse.code,
|
|
707
|
-
timestamp: nonceResponse.timestamp
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
const stringToSign = stringify(payload);
|
|
711
|
-
const signature = await signer.signMessage(stringToSign || '');
|
|
712
|
-
|
|
713
|
-
const response = await nxtlinqApi.auth.signIn({
|
|
714
|
-
...payload,
|
|
715
|
-
signature
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
if ('error' in response) {
|
|
719
|
-
alert(response.error);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
const { accessToken } = response;
|
|
723
|
-
localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
|
|
724
|
-
setAccessToken(accessToken);
|
|
725
|
-
|
|
726
|
-
// 登录成功后,获取 AIT 信息
|
|
727
|
-
try {
|
|
728
|
-
const token = JSON.parse(localStorage.getItem('nxtlinqAITServiceAccessToken') || '');
|
|
729
|
-
const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController(
|
|
730
|
-
{ serviceId, controller: hitAddress },
|
|
731
|
-
token
|
|
732
|
-
);
|
|
733
|
-
if (!('error' in aitResponse)) {
|
|
734
|
-
setAit(aitResponse);
|
|
735
|
-
}
|
|
736
|
-
} catch (error) {
|
|
737
|
-
console.error('Failed to get AIT:', error);
|
|
738
|
-
}
|
|
739
|
-
} catch (error) {
|
|
740
|
-
console.error('Failed to sign in:', error);
|
|
741
|
-
alert('Failed to sign in. Please try again.');
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
|
|
745
|
-
const hasPermission = async (toolName: string) => {
|
|
746
|
-
if (!ait) {
|
|
747
|
-
setMessages(prev => [...prev, {
|
|
748
|
-
id: Date.now().toString(),
|
|
749
|
-
content: 'Please connect your HIT wallet and sign in to access permissions.',
|
|
750
|
-
role: 'assistant',
|
|
751
|
-
timestamp: new Date().toISOString(),
|
|
752
|
-
button: true
|
|
753
|
-
}]);
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
return permissions.includes(toolName);
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
const sendMessage = async (content: string, retryCount = 0): Promise<Message> => {
|
|
760
|
-
try {
|
|
761
|
-
setIsLoading(true);
|
|
762
|
-
const response = await nxtlinqApi.agent.sendMessage({
|
|
763
|
-
message: content,
|
|
764
|
-
serviceId,
|
|
765
|
-
});
|
|
766
|
-
|
|
767
|
-
if ('error' in response) {
|
|
768
|
-
throw new Error(response.error);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
const message: Message = {
|
|
772
|
-
id: Date.now().toString(),
|
|
773
|
-
content: response.reply,
|
|
774
|
-
role: 'assistant',
|
|
775
|
-
timestamp: new Date().toISOString()
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
setMessages(prev => [...prev, message]);
|
|
779
|
-
onMessage?.(message);
|
|
780
|
-
return message;
|
|
781
|
-
} catch (error) {
|
|
782
|
-
if (retryCount < maxRetries) {
|
|
783
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
784
|
-
return sendMessage(content, retryCount + 1);
|
|
785
|
-
}
|
|
786
|
-
throw error;
|
|
787
|
-
} finally {
|
|
788
|
-
setIsLoading(false);
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
793
|
-
e.preventDefault();
|
|
794
|
-
if (!inputValue.trim() || isLoading) return;
|
|
795
|
-
|
|
796
|
-
const userMessage: Message = {
|
|
797
|
-
id: Date.now().toString(),
|
|
798
|
-
content: inputValue,
|
|
799
|
-
role: 'user',
|
|
800
|
-
timestamp: new Date().toISOString()
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
setMessages(prev => [...prev, userMessage]);
|
|
804
|
-
setInputValue('');
|
|
805
|
-
setIsLoading(true);
|
|
806
|
-
|
|
807
|
-
try {
|
|
808
|
-
const response = await sendMessage(inputValue);
|
|
809
|
-
if (response.error) {
|
|
810
|
-
throw new Error(response.error);
|
|
811
|
-
}
|
|
812
|
-
setMessages(prev => [...prev, response]);
|
|
813
|
-
} catch (error) {
|
|
814
|
-
console.error('发送消息失败:', error);
|
|
815
|
-
onError?.(error instanceof Error ? error : new Error('发送消息失败'));
|
|
816
|
-
setMessages(prev => [...prev, {
|
|
817
|
-
id: Date.now().toString(),
|
|
818
|
-
content: 'Sorry, there was an error processing your message. Please try again.',
|
|
819
|
-
role: 'assistant',
|
|
820
|
-
timestamp: new Date().toISOString()
|
|
821
|
-
}]);
|
|
822
|
-
} finally {
|
|
823
|
-
setIsLoading(false);
|
|
824
|
-
}
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
const handlePresetMessage = (message: PresetMessage) => {
|
|
828
|
-
if (message.autoSend) {
|
|
829
|
-
setMessages(prev => [...prev, {
|
|
830
|
-
id: Date.now().toString(),
|
|
831
|
-
content: message.text,
|
|
832
|
-
role: 'user',
|
|
833
|
-
timestamp: new Date().toISOString()
|
|
834
|
-
}]);
|
|
835
|
-
sendMessage(message.text);
|
|
836
|
-
} else {
|
|
837
|
-
setInputValue(message.text);
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
const generateAndRegisterAIT = async () => {
|
|
842
|
-
if (!signer || !hitAddress) return;
|
|
843
|
-
|
|
844
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
845
|
-
const aitId = `did:polygon:ike-dashboard:${hitAddress}:${timestamp}`;
|
|
846
|
-
|
|
847
|
-
const metadata = {
|
|
848
|
-
model: 'gpt-4',
|
|
849
|
-
permissions,
|
|
850
|
-
issuedBy: hitAddress,
|
|
851
|
-
controller: hitAddress
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
const metadataStr = stringify(metadata);
|
|
855
|
-
const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr || ''));
|
|
856
|
-
|
|
857
|
-
// 1️⃣ Upload metadata to Pinata, get CID
|
|
858
|
-
const uploadResponse = await nxtlinqApi.metadata.createMetadata(metadata, accessToken || '');
|
|
859
|
-
if ('error' in uploadResponse) {
|
|
860
|
-
throw new Error(`Failed to upload metadata: ${uploadResponse.error}`);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
const { metadataCid } = uploadResponse;
|
|
864
|
-
|
|
865
|
-
// 2️⃣ Sign the message
|
|
866
|
-
const messageHash = ethers.solidityPackedKeccak256(
|
|
867
|
-
['string', 'address', 'string', 'bytes32', 'uint256'],
|
|
868
|
-
[aitId, hitAddress, serviceId, metadataHash, timestamp]
|
|
869
|
-
);
|
|
870
|
-
|
|
871
|
-
const signature = await signer.signMessage(ethers.getBytes(messageHash));
|
|
872
|
-
|
|
873
|
-
await nxtlinqApi.ait.createAIT({
|
|
874
|
-
aitId,
|
|
875
|
-
controller: hitAddress,
|
|
876
|
-
serviceId,
|
|
877
|
-
metadataHash,
|
|
878
|
-
metadataCid,
|
|
879
|
-
timestamp,
|
|
880
|
-
signature
|
|
881
|
-
}, accessToken || '');
|
|
882
|
-
|
|
883
|
-
const aitInfo = {
|
|
884
|
-
aitId,
|
|
885
|
-
controller: hitAddress,
|
|
886
|
-
metadata,
|
|
887
|
-
metadataHash,
|
|
888
|
-
metadataCid,
|
|
889
|
-
signature
|
|
890
|
-
};
|
|
891
|
-
|
|
892
|
-
setAit(aitInfo);
|
|
893
|
-
};
|
|
894
|
-
|
|
895
|
-
const savePermissions = async () => {
|
|
896
|
-
setIsLoading(true);
|
|
897
|
-
setIsDisabled(true);
|
|
898
|
-
try {
|
|
899
|
-
await generateAndRegisterAIT();
|
|
900
|
-
setIsLoading(false);
|
|
901
|
-
setSuccess(true);
|
|
902
|
-
setShowPermissionForm(false);
|
|
903
|
-
} catch (error) {
|
|
904
|
-
console.error('Failed to generate AIT:', error);
|
|
905
|
-
setIsLoading(false);
|
|
906
|
-
setIsDisabled(false);
|
|
907
|
-
if (error instanceof Error) {
|
|
908
|
-
alert(error.message);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
// 添加状态变化的监听
|
|
914
|
-
React.useEffect(() => {
|
|
915
|
-
console.log('States updated:', {
|
|
916
|
-
hitAddress,
|
|
917
|
-
isNeedSignInWithWallet,
|
|
918
|
-
walletInfo,
|
|
919
|
-
isOpen
|
|
920
|
-
});
|
|
921
|
-
}, [hitAddress, isNeedSignInWithWallet, walletInfo, isOpen]);
|
|
922
|
-
|
|
923
|
-
return (
|
|
924
|
-
<div style={{
|
|
925
|
-
position: 'fixed',
|
|
926
|
-
bottom: '20px',
|
|
927
|
-
right: '20px',
|
|
928
|
-
zIndex: 1000,
|
|
929
|
-
display: 'flex',
|
|
930
|
-
flexDirection: 'column',
|
|
931
|
-
alignItems: 'flex-end',
|
|
932
|
-
gap: '10px'
|
|
933
|
-
}}>
|
|
934
|
-
{isOpen && (
|
|
935
|
-
<div className={`nxtlinq-chatbot ${className}`} style={{
|
|
936
|
-
width: '350px',
|
|
937
|
-
height: '500px',
|
|
938
|
-
backgroundColor: 'white',
|
|
939
|
-
borderRadius: '10px',
|
|
940
|
-
boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
|
|
941
|
-
display: 'flex',
|
|
942
|
-
flexDirection: 'column',
|
|
943
|
-
overflow: 'hidden'
|
|
944
|
-
}}>
|
|
945
|
-
<div style={{
|
|
946
|
-
padding: '15px',
|
|
947
|
-
backgroundColor: '#007bff',
|
|
948
|
-
color: 'white',
|
|
949
|
-
borderRadius: '10px 10px 0 0',
|
|
950
|
-
display: 'flex',
|
|
951
|
-
justifyContent: 'space-between',
|
|
952
|
-
alignItems: 'center'
|
|
953
|
-
}}>
|
|
954
|
-
<h3 style={{ margin: 0 }}>AI Agent</h3>
|
|
955
|
-
<div style={{ display: 'flex', gap: '10px' }}>
|
|
956
|
-
<button
|
|
957
|
-
onClick={() => setShowPermissionForm(true)}
|
|
958
|
-
style={{
|
|
959
|
-
background: 'none',
|
|
960
|
-
border: 'none',
|
|
961
|
-
color: 'white',
|
|
962
|
-
fontSize: '20px',
|
|
963
|
-
cursor: 'pointer',
|
|
964
|
-
padding: '0 5px'
|
|
965
|
-
}}
|
|
966
|
-
>
|
|
967
|
-
⚙️
|
|
968
|
-
</button>
|
|
969
|
-
<button
|
|
970
|
-
onClick={() => setIsOpen(false)}
|
|
971
|
-
style={{
|
|
972
|
-
background: 'none',
|
|
973
|
-
border: 'none',
|
|
974
|
-
color: 'white',
|
|
975
|
-
fontSize: '24px',
|
|
976
|
-
cursor: 'pointer',
|
|
977
|
-
padding: '0 5px'
|
|
978
|
-
}}
|
|
979
|
-
>
|
|
980
|
-
×
|
|
981
|
-
</button>
|
|
982
|
-
</div>
|
|
983
|
-
</div>
|
|
984
|
-
|
|
985
|
-
<div style={{
|
|
986
|
-
flex: 1,
|
|
987
|
-
overflowY: 'auto',
|
|
988
|
-
padding: '15px',
|
|
989
|
-
display: 'flex',
|
|
990
|
-
flexDirection: 'column',
|
|
991
|
-
gap: '10px'
|
|
992
|
-
}}>
|
|
993
|
-
{messages.map((message, index) => (
|
|
994
|
-
<div
|
|
995
|
-
key={index}
|
|
996
|
-
style={{
|
|
997
|
-
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
998
|
-
backgroundColor: message.role === 'user' ? '#007bff' : '#f0f0f0',
|
|
999
|
-
color: message.role === 'user' ? 'white' : 'black',
|
|
1000
|
-
padding: '8px 12px',
|
|
1001
|
-
borderRadius: '15px',
|
|
1002
|
-
maxWidth: '80%',
|
|
1003
|
-
wordBreak: 'break-word'
|
|
1004
|
-
}}
|
|
1005
|
-
>
|
|
1006
|
-
{message.content}
|
|
1007
|
-
{message.button && (
|
|
1008
|
-
<button
|
|
1009
|
-
onClick={connectWallet}
|
|
1010
|
-
disabled={!!hitAddress}
|
|
1011
|
-
style={{
|
|
1012
|
-
display: 'block',
|
|
1013
|
-
marginTop: '8px',
|
|
1014
|
-
padding: '5px 10px',
|
|
1015
|
-
backgroundColor: hitAddress ? '#ccc' : '#28a745',
|
|
1016
|
-
color: 'white',
|
|
1017
|
-
border: 'none',
|
|
1018
|
-
borderRadius: '5px',
|
|
1019
|
-
cursor: hitAddress ? 'not-allowed' : 'pointer'
|
|
1020
|
-
}}
|
|
1021
|
-
>
|
|
1022
|
-
{hitAddress ? 'Connected' : 'Connect HIT wallet'}
|
|
1023
|
-
</button>
|
|
1024
|
-
)}
|
|
1025
|
-
</div>
|
|
1026
|
-
))}
|
|
1027
|
-
{isLoading && (
|
|
1028
|
-
<div style={{
|
|
1029
|
-
alignSelf: 'flex-start',
|
|
1030
|
-
backgroundColor: '#f0f0f0',
|
|
1031
|
-
padding: '8px 12px',
|
|
1032
|
-
borderRadius: '15px',
|
|
1033
|
-
maxWidth: '80%'
|
|
1034
|
-
}}>
|
|
1035
|
-
Thinking...
|
|
1036
|
-
</div>
|
|
1037
|
-
)}
|
|
1038
|
-
<div ref={messagesEndRef} />
|
|
1039
|
-
</div>
|
|
1040
|
-
|
|
1041
|
-
<div style={{
|
|
1042
|
-
padding: '10px',
|
|
1043
|
-
borderTop: '1px solid #eee',
|
|
1044
|
-
display: 'flex',
|
|
1045
|
-
flexDirection: 'column',
|
|
1046
|
-
gap: '10px'
|
|
1047
|
-
}}>
|
|
1048
|
-
<div style={{
|
|
1049
|
-
display: 'flex',
|
|
1050
|
-
gap: '5px',
|
|
1051
|
-
overflowX: 'auto',
|
|
1052
|
-
padding: '5px 0'
|
|
1053
|
-
}}>
|
|
1054
|
-
{presetMessages.map((message, index) => (
|
|
1055
|
-
<button
|
|
1056
|
-
key={index}
|
|
1057
|
-
onClick={() => handlePresetMessage(message)}
|
|
1058
|
-
style={{
|
|
1059
|
-
padding: '5px 10px',
|
|
1060
|
-
backgroundColor: '#f0f0f0',
|
|
1061
|
-
border: 'none',
|
|
1062
|
-
borderRadius: '15px',
|
|
1063
|
-
cursor: 'pointer',
|
|
1064
|
-
whiteSpace: 'nowrap',
|
|
1065
|
-
fontSize: '12px'
|
|
1066
|
-
}}
|
|
1067
|
-
>
|
|
1068
|
-
{message.text}
|
|
1069
|
-
</button>
|
|
1070
|
-
))}
|
|
1071
|
-
</div>
|
|
1072
|
-
|
|
1073
|
-
<form onSubmit={handleSubmit} style={{
|
|
1074
|
-
display: 'flex',
|
|
1075
|
-
gap: '10px'
|
|
1076
|
-
}}>
|
|
1077
|
-
<input
|
|
1078
|
-
type="text"
|
|
1079
|
-
value={inputValue}
|
|
1080
|
-
onChange={(e) => setInputValue(e.target.value)}
|
|
1081
|
-
placeholder={placeholder}
|
|
1082
|
-
style={{
|
|
1083
|
-
flex: 1,
|
|
1084
|
-
padding: '8px 12px',
|
|
1085
|
-
border: '1px solid #ddd',
|
|
1086
|
-
borderRadius: '20px',
|
|
1087
|
-
fontSize: '14px',
|
|
1088
|
-
outline: 'none'
|
|
1089
|
-
}}
|
|
1090
|
-
/>
|
|
1091
|
-
<button
|
|
1092
|
-
type="submit"
|
|
1093
|
-
disabled={isLoading}
|
|
1094
|
-
style={{
|
|
1095
|
-
backgroundColor: '#007bff',
|
|
1096
|
-
color: 'white',
|
|
1097
|
-
border: 'none',
|
|
1098
|
-
borderRadius: '20px',
|
|
1099
|
-
padding: '8px 20px',
|
|
1100
|
-
cursor: isLoading ? 'not-allowed' : 'pointer',
|
|
1101
|
-
fontSize: '14px',
|
|
1102
|
-
opacity: isLoading ? 0.7 : 1
|
|
1103
|
-
}}
|
|
1104
|
-
>
|
|
1105
|
-
Send
|
|
1106
|
-
</button>
|
|
1107
|
-
</form>
|
|
1108
|
-
</div>
|
|
1109
|
-
</div>
|
|
1110
|
-
)}
|
|
1111
|
-
|
|
1112
|
-
<button
|
|
1113
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
1114
|
-
style={{
|
|
1115
|
-
width: '120px',
|
|
1116
|
-
height: '40px',
|
|
1117
|
-
borderRadius: '20px',
|
|
1118
|
-
backgroundColor: '#007bff',
|
|
1119
|
-
color: '#ffffff',
|
|
1120
|
-
border: 'none',
|
|
1121
|
-
cursor: 'pointer',
|
|
1122
|
-
display: 'flex',
|
|
1123
|
-
alignItems: 'center',
|
|
1124
|
-
justifyContent: 'center',
|
|
1125
|
-
fontSize: '14px',
|
|
1126
|
-
fontWeight: 'bold',
|
|
1127
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
1128
|
-
transition: 'all 0.3s ease-in-out',
|
|
1129
|
-
padding: '0 20px'
|
|
1130
|
-
}}
|
|
1131
|
-
>
|
|
1132
|
-
AI Agent
|
|
1133
|
-
</button>
|
|
1134
|
-
|
|
1135
|
-
{showPermissionForm && (
|
|
1136
|
-
<div style={{
|
|
1137
|
-
position: 'fixed',
|
|
1138
|
-
top: 0,
|
|
1139
|
-
left: 0,
|
|
1140
|
-
right: 0,
|
|
1141
|
-
bottom: 0,
|
|
1142
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
1143
|
-
display: 'flex',
|
|
1144
|
-
alignItems: 'center',
|
|
1145
|
-
justifyContent: 'center',
|
|
1146
|
-
zIndex: 2000
|
|
1147
|
-
}}>
|
|
1148
|
-
<PermissionForm
|
|
1149
|
-
hitAddress={hitAddress}
|
|
1150
|
-
permissions={permissions}
|
|
1151
|
-
setPermissions={setPermissions}
|
|
1152
|
-
setIsDisabled={setIsDisabled}
|
|
1153
|
-
onClose={() => setShowPermissionForm(false)}
|
|
1154
|
-
onConnectWallet={connectWallet}
|
|
1155
|
-
onSignIn={signInWallet}
|
|
1156
|
-
onSave={savePermissions}
|
|
1157
|
-
isNeedSignInWithWallet={isNeedSignInWithWallet}
|
|
1158
|
-
walletInfo={walletInfo}
|
|
1159
|
-
onVerifyWallet={handleVerifyWalletClick}
|
|
1160
|
-
/>
|
|
1161
|
-
</div>
|
|
1162
|
-
)}
|
|
1163
|
-
|
|
1164
|
-
{success && (
|
|
1165
|
-
<div style={{
|
|
1166
|
-
position: 'fixed',
|
|
1167
|
-
bottom: 100,
|
|
1168
|
-
right: 40,
|
|
1169
|
-
background: '#4caf50',
|
|
1170
|
-
color: 'white',
|
|
1171
|
-
padding: '12px 24px',
|
|
1172
|
-
borderRadius: 8,
|
|
1173
|
-
zIndex: 2000,
|
|
1174
|
-
fontWeight: 600,
|
|
1175
|
-
}}>
|
|
1176
|
-
Saved successfully!
|
|
1177
|
-
<button
|
|
1178
|
-
style={{
|
|
1179
|
-
background: 'none',
|
|
1180
|
-
border: 'none',
|
|
1181
|
-
color: 'white',
|
|
1182
|
-
marginLeft: 16,
|
|
1183
|
-
fontSize: 18,
|
|
1184
|
-
cursor: 'pointer'
|
|
1185
|
-
}}
|
|
1186
|
-
onClick={() => setSuccess(false)}
|
|
1187
|
-
>×</button>
|
|
1188
|
-
</div>
|
|
1189
|
-
)}
|
|
1190
|
-
</div>
|
|
1191
|
-
);
|
|
1192
|
-
};
|