@bytexbyte/nxtlinq-ai-agent-sdk 1.0.8 → 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.
@@ -0,0 +1,836 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { ethers } from 'ethers';
4
+ import { createNxtlinqApi } from '../api/nxtlinq-api';
5
+ import stringify from 'fast-json-stable-stringify';
6
+ import metakeepClient from '../core/metakeepClient';
7
+ const PermissionForm = ({ hitAddress, permissions, setPermissions, setIsDisabled, onClose, onSave, onConnectWallet, onSignIn, isNeedSignInWithWallet, walletInfo, onVerifyWallet }) => {
8
+ const availablePermissions = [
9
+ { id: 'setUserName', label: 'Set User Name' },
10
+ { id: 'navigateToPage', label: 'Navigate To Page' },
11
+ { id: 'addMember', label: 'Add Member' }
12
+ ];
13
+ const hasAccessToken = !!localStorage.getItem('nxtlinqAITServiceAccessToken');
14
+ const isWalletVerified = walletInfo?.id;
15
+ return (_jsxs("div", { style: {
16
+ backgroundColor: 'white',
17
+ padding: '24px',
18
+ borderRadius: '12px',
19
+ width: '480px',
20
+ maxWidth: '90%',
21
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
22
+ }, children: [_jsxs("div", { style: {
23
+ display: 'flex',
24
+ justifyContent: 'space-between',
25
+ alignItems: 'center',
26
+ marginBottom: '24px'
27
+ }, children: [_jsx("h3", { style: {
28
+ margin: 0,
29
+ fontSize: '20px',
30
+ fontWeight: '600',
31
+ color: '#1a1a1a'
32
+ }, children: "AIT Settings" }), _jsx("button", { onClick: onClose, style: {
33
+ background: 'none',
34
+ border: 'none',
35
+ fontSize: '24px',
36
+ cursor: 'pointer',
37
+ color: '#666',
38
+ padding: '4px',
39
+ display: 'flex',
40
+ alignItems: 'center',
41
+ justifyContent: 'center'
42
+ }, children: "\u00D7" })] }), !hitAddress ? (_jsxs("div", { style: { textAlign: 'center', padding: '32px 0' }, children: [_jsx("div", { style: {
43
+ width: '64px',
44
+ height: '64px',
45
+ margin: '0 auto 16px',
46
+ backgroundColor: '#f5f5f5',
47
+ borderRadius: '50%',
48
+ display: 'flex',
49
+ alignItems: 'center',
50
+ justifyContent: 'center'
51
+ }, children: _jsx("span", { style: { fontSize: '32px' }, children: "\uD83D\uDC5B" }) }), _jsx("p", { style: {
52
+ marginBottom: '24px',
53
+ fontSize: '16px',
54
+ color: '#666'
55
+ }, children: "Please connect your wallet first" }), _jsx("button", { onClick: onConnectWallet, style: {
56
+ padding: '12px 24px',
57
+ backgroundColor: '#007bff',
58
+ color: 'white',
59
+ border: 'none',
60
+ borderRadius: '8px',
61
+ cursor: 'pointer',
62
+ fontSize: '16px',
63
+ fontWeight: '500',
64
+ transition: 'background-color 0.2s'
65
+ }, onMouseOver: (e) => e.currentTarget.style.backgroundColor = '#0056b3', onMouseOut: (e) => e.currentTarget.style.backgroundColor = '#007bff', children: "Connect Wallet" })] })) : isNeedSignInWithWallet ? (_jsxs("div", { style: { textAlign: 'center', padding: '32px 0' }, children: [_jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: {
66
+ marginBottom: '12px',
67
+ fontSize: '16px',
68
+ color: '#666'
69
+ }, children: "Connected Wallet" }), _jsx("p", { style: {
70
+ wordBreak: 'break-all',
71
+ backgroundColor: '#f8f9fa',
72
+ padding: '12px',
73
+ borderRadius: '8px',
74
+ fontSize: '14px',
75
+ color: '#333',
76
+ border: '1px solid #e9ecef'
77
+ }, children: hitAddress })] }), _jsx("p", { style: {
78
+ marginBottom: '24px',
79
+ fontSize: '16px',
80
+ color: '#666'
81
+ }, children: "Please sign in to continue" }), _jsx("button", { onClick: onSignIn, style: {
82
+ padding: '12px 24px',
83
+ backgroundColor: '#007bff',
84
+ color: 'white',
85
+ border: 'none',
86
+ borderRadius: '8px',
87
+ cursor: 'pointer',
88
+ fontSize: '16px',
89
+ fontWeight: '500',
90
+ transition: 'background-color 0.2s'
91
+ }, onMouseOver: (e) => e.currentTarget.style.backgroundColor = '#0056b3', onMouseOut: (e) => e.currentTarget.style.backgroundColor = '#007bff', children: "Sign In" })] })) : !isWalletVerified ? (_jsxs("div", { style: { textAlign: 'center', padding: '32px 0' }, children: [_jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: {
92
+ marginBottom: '12px',
93
+ fontSize: '16px',
94
+ color: '#666'
95
+ }, children: "Connected Wallet" }), _jsx("p", { style: {
96
+ wordBreak: 'break-all',
97
+ backgroundColor: '#f8f9fa',
98
+ padding: '12px',
99
+ borderRadius: '8px',
100
+ fontSize: '14px',
101
+ color: '#333',
102
+ border: '1px solid #e9ecef'
103
+ }, children: hitAddress })] }), _jsx("p", { style: {
104
+ marginBottom: '24px',
105
+ fontSize: '16px',
106
+ color: '#666'
107
+ }, children: "Please verify your wallet to continue" }), _jsx("button", { onClick: onVerifyWallet, style: {
108
+ padding: '12px 24px',
109
+ backgroundColor: '#007bff',
110
+ color: 'white',
111
+ border: 'none',
112
+ borderRadius: '8px',
113
+ cursor: 'pointer',
114
+ fontSize: '16px',
115
+ fontWeight: '500',
116
+ transition: 'background-color 0.2s'
117
+ }, onMouseOver: (e) => e.currentTarget.style.backgroundColor = '#0056b3', onMouseOut: (e) => e.currentTarget.style.backgroundColor = '#007bff', children: "Verify your wallet" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: {
118
+ marginBottom: '12px',
119
+ fontSize: '16px',
120
+ color: '#666'
121
+ }, children: "Connected Wallet" }), _jsx("p", { style: {
122
+ wordBreak: 'break-all',
123
+ backgroundColor: '#f8f9fa',
124
+ padding: '12px',
125
+ borderRadius: '8px',
126
+ fontSize: '14px',
127
+ color: '#333',
128
+ border: '1px solid #e9ecef'
129
+ }, children: hitAddress })] }), _jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: {
130
+ marginBottom: '12px',
131
+ fontSize: '16px',
132
+ color: '#666'
133
+ }, children: "Permissions" }), _jsx("div", { style: {
134
+ backgroundColor: '#f8f9fa',
135
+ padding: '16px',
136
+ borderRadius: '8px',
137
+ border: '1px solid #e9ecef'
138
+ }, children: availablePermissions.map((permission) => (_jsx("div", { style: { marginBottom: '12px' }, children: _jsxs("label", { style: {
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ gap: '12px',
142
+ cursor: 'pointer',
143
+ padding: '8px',
144
+ borderRadius: '6px',
145
+ transition: 'background-color 0.2s'
146
+ }, onMouseOver: (e) => e.currentTarget.style.backgroundColor = '#e9ecef', onMouseOut: (e) => e.currentTarget.style.backgroundColor = 'transparent', children: [_jsx("input", { type: "checkbox", checked: permissions.includes(permission.id), onChange: () => {
147
+ const newPermissions = permissions.includes(permission.id)
148
+ ? permissions.filter(p => p !== permission.id)
149
+ : [...permissions, permission.id].sort();
150
+ setPermissions(newPermissions);
151
+ setIsDisabled(false);
152
+ }, style: {
153
+ margin: 0,
154
+ width: '18px',
155
+ height: '18px',
156
+ cursor: 'pointer'
157
+ } }), _jsx("span", { style: {
158
+ fontSize: '14px',
159
+ color: '#333'
160
+ }, children: permission.label })] }) }, permission.id))) })] }), _jsxs("div", { style: {
161
+ display: 'flex',
162
+ justifyContent: 'flex-end',
163
+ gap: '12px',
164
+ borderTop: '1px solid #e9ecef',
165
+ paddingTop: '24px'
166
+ }, children: [_jsx("button", { onClick: onClose, style: {
167
+ padding: '10px 20px',
168
+ backgroundColor: '#f8f9fa',
169
+ color: '#666',
170
+ border: '1px solid #dee2e6',
171
+ borderRadius: '8px',
172
+ cursor: 'pointer',
173
+ fontSize: '14px',
174
+ fontWeight: '500',
175
+ transition: 'all 0.2s'
176
+ }, onMouseOver: (e) => {
177
+ e.currentTarget.style.backgroundColor = '#e9ecef';
178
+ e.currentTarget.style.borderColor = '#ced4da';
179
+ }, onMouseOut: (e) => {
180
+ e.currentTarget.style.backgroundColor = '#f8f9fa';
181
+ e.currentTarget.style.borderColor = '#dee2e6';
182
+ }, children: "Cancel" }), _jsx("button", { onClick: onSave, disabled: permissions.length === 0, style: {
183
+ padding: '10px 20px',
184
+ backgroundColor: permissions.length === 0 ? '#e9ecef' : '#007bff',
185
+ color: 'white',
186
+ border: 'none',
187
+ borderRadius: '8px',
188
+ cursor: permissions.length === 0 ? 'not-allowed' : 'pointer',
189
+ fontSize: '14px',
190
+ fontWeight: '500',
191
+ transition: 'background-color 0.2s'
192
+ }, onMouseOver: (e) => {
193
+ if (permissions.length > 0) {
194
+ e.currentTarget.style.backgroundColor = '#0056b3';
195
+ }
196
+ }, onMouseOut: (e) => {
197
+ if (permissions.length > 0) {
198
+ e.currentTarget.style.backgroundColor = '#007bff';
199
+ }
200
+ }, children: "Save" })] })] }))] }));
201
+ };
202
+ export const ChatBot = ({ projectId, onMessage, onError, onToolUse, presetMessages = [], placeholder = 'Type a message...', className = '', maxRetries = 3, retryDelay = 1000, serviceId, apiKey, apiSecret, onVerifyWallet }) => {
203
+ const [messages, setMessages] = React.useState([]);
204
+ const [inputValue, setInputValue] = React.useState('');
205
+ const [isLoading, setIsLoading] = React.useState(false);
206
+ const [isOpen, setIsOpen] = React.useState(false);
207
+ const [hitAddress, setHitAddress] = React.useState(null);
208
+ const [ait, setAit] = React.useState(null);
209
+ const [permissions, setPermissions] = React.useState([]);
210
+ const [showPermissionForm, setShowPermissionForm] = React.useState(false);
211
+ const [success, setSuccess] = React.useState(false);
212
+ const messagesEndRef = React.useRef(null);
213
+ const [isDisabled, setIsDisabled] = React.useState(true);
214
+ const [signer, setSigner] = React.useState(null);
215
+ const [provider, setProvider] = React.useState(null);
216
+ const [accessToken, setAccessToken] = React.useState(null);
217
+ const [walletInfo, setWalletInfo] = React.useState(null);
218
+ const verifyWalletBtnRef = React.useRef(null);
219
+ const nxtlinqApi = React.useMemo(() => createNxtlinqApi(apiKey, apiSecret), [apiKey, apiSecret]);
220
+ const isNeedSignInWithWallet = React.useMemo(() => {
221
+ if (!hitAddress) {
222
+ console.log('isNeedSignInWithWallet: false (no hitAddress)');
223
+ return false;
224
+ }
225
+ const nxtlinqAITServiceAccessToken = localStorage.getItem('nxtlinqAITServiceAccessToken');
226
+ if (!nxtlinqAITServiceAccessToken) {
227
+ console.log('isNeedSignInWithWallet: true (no token)');
228
+ return true;
229
+ }
230
+ try {
231
+ // Check if the token is expired
232
+ const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
233
+ const exp = payload.exp * 1000; // Convert to milliseconds
234
+ const now = Date.now();
235
+ if (exp < now) {
236
+ console.log('isNeedSignInWithWallet: true (token expired)');
237
+ return true;
238
+ }
239
+ // Check is the token's payload has the same address as the wallet address
240
+ const address = payload.address;
241
+ if (address !== hitAddress) {
242
+ console.log('isNeedSignInWithWallet: true (address mismatch)');
243
+ return true;
244
+ }
245
+ console.log('isNeedSignInWithWallet: false (valid token)');
246
+ return false;
247
+ }
248
+ catch (error) {
249
+ console.error('Error parsing token:', error);
250
+ console.log('isNeedSignInWithWallet: true (token parse error)');
251
+ return true;
252
+ }
253
+ }, [hitAddress]);
254
+ const handleVerifySuccess = (address) => {
255
+ console.log('handleVerifySuccess called with address:', address);
256
+ localStorage.setItem(`wallet_verified_${address}`, 'true');
257
+ const getWalletInfo = async () => {
258
+ try {
259
+ const token = JSON.parse(localStorage.getItem('nxtlinqAITServiceAccessToken') || '');
260
+ const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
261
+ console.log('Wallet response:', walletResponse);
262
+ if (!('error' in walletResponse)) {
263
+ setWalletInfo(walletResponse);
264
+ const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
265
+ serviceId,
266
+ controller: address
267
+ }, token);
268
+ console.log('AIT response:', aitResponse);
269
+ if (!('error' in aitResponse)) {
270
+ setAit(aitResponse);
271
+ }
272
+ }
273
+ }
274
+ catch (error) {
275
+ console.error('Failed to update wallet info after verification:', error);
276
+ }
277
+ };
278
+ getWalletInfo();
279
+ };
280
+ React.useEffect(() => {
281
+ const getWalletInfo = async () => {
282
+ if (!hitAddress) {
283
+ return;
284
+ }
285
+ if (isNeedSignInWithWallet) {
286
+ return;
287
+ }
288
+ const getWalletResponse = await nxtlinqApi.wallet.getWallet({ address: hitAddress }, accessToken);
289
+ if ('error' in getWalletResponse) {
290
+ console.error(getWalletResponse.error);
291
+ return;
292
+ }
293
+ setWalletInfo(getWalletResponse);
294
+ };
295
+ getWalletInfo();
296
+ }, [hitAddress, isNeedSignInWithWallet]);
297
+ const handleVerifyWalletClick = async () => {
298
+ console.log('handleVerifyWalletClick called');
299
+ console.log('Current states:', {
300
+ hitAddress,
301
+ isNeedSignInWithWallet,
302
+ walletInfo,
303
+ signer: !!signer
304
+ });
305
+ if (!hitAddress) {
306
+ alert('Please connect your wallet first.');
307
+ return;
308
+ }
309
+ try {
310
+ if (onVerifyWallet) {
311
+ setIsLoading(true);
312
+ const result = await onVerifyWallet(hitAddress);
313
+ console.log('Verify wallet response:', result);
314
+ const { token } = result;
315
+ const address = hitAddress;
316
+ if (token && address) {
317
+ const payload = {
318
+ address: hitAddress,
319
+ token,
320
+ timestamp: Date.now(),
321
+ method: 'berifyme'
322
+ };
323
+ try {
324
+ const verifyWalletResponse = await nxtlinqApi.wallet.verifyWallet({ ...payload }, token);
325
+ if ('error' in verifyWalletResponse) {
326
+ if (verifyWalletResponse.error === 'Wallet already exists') {
327
+ // 已存在则直接获取钱包信息
328
+ const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
329
+ if (!('error' in walletResponse)) {
330
+ setWalletInfo(walletResponse);
331
+ const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({ serviceId, controller: address }, token);
332
+ if (!('error' in aitResponse)) {
333
+ setAit(aitResponse);
334
+ }
335
+ }
336
+ // 清理 URL 上的 token
337
+ if (typeof window !== 'undefined') {
338
+ try {
339
+ const url = new URL(window.location.href);
340
+ url.searchParams.delete('token');
341
+ window.history.replaceState({}, '', url.toString());
342
+ }
343
+ catch (e) {
344
+ console.error('Failed to clean URL:', e);
345
+ }
346
+ }
347
+ setIsLoading(false);
348
+ return { token, hitAddress: address };
349
+ }
350
+ alert(verifyWalletResponse.error);
351
+ setIsLoading(false);
352
+ return;
353
+ }
354
+ // 验证成功,获取钱包信息
355
+ const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
356
+ if (!('error' in walletResponse)) {
357
+ setWalletInfo(walletResponse);
358
+ const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({ serviceId, controller: address }, token);
359
+ if (!('error' in aitResponse)) {
360
+ setAit(aitResponse);
361
+ }
362
+ }
363
+ // 清理 URL 上的 token
364
+ if (typeof window !== 'undefined') {
365
+ try {
366
+ const url = new URL(window.location.href);
367
+ url.searchParams.delete('token');
368
+ window.history.replaceState({}, '', url.toString());
369
+ }
370
+ catch (e) {
371
+ console.error('Failed to clean URL:', e);
372
+ }
373
+ }
374
+ setIsLoading(false);
375
+ return { token, hitAddress: address };
376
+ }
377
+ catch (error) {
378
+ let msg = 'Verification failed';
379
+ if (typeof error === 'object' && error !== null && 'response' in error) {
380
+ // @ts-ignore
381
+ msg = error.response?.data?.error || error.message || msg;
382
+ }
383
+ else if (error instanceof Error) {
384
+ msg = error.message;
385
+ }
386
+ console.error('Wallet verification failed:', error);
387
+ alert(msg);
388
+ setIsLoading(false);
389
+ throw error;
390
+ }
391
+ }
392
+ setIsLoading(false);
393
+ return { token, hitAddress: address };
394
+ }
395
+ }
396
+ catch (error) {
397
+ console.error('Failed to verify wallet:', error);
398
+ setIsLoading(false);
399
+ alert('Failed to verify wallet. Please try again.');
400
+ throw error;
401
+ }
402
+ };
403
+ // 添加一个 useEffect 来处理 URL 参数
404
+ React.useEffect(() => {
405
+ if (typeof window !== 'undefined') {
406
+ try {
407
+ const urlParams = new URLSearchParams(window.location.search);
408
+ const token = urlParams.get('token');
409
+ if (token && hitAddress) {
410
+ handleVerifyWalletClick();
411
+ }
412
+ }
413
+ catch (e) {
414
+ console.error('Failed to get URL params:', e);
415
+ }
416
+ }
417
+ }, [hitAddress]);
418
+ React.useEffect(() => {
419
+ console.log('Wallet states changed:', {
420
+ hitAddress,
421
+ isNeedSignInWithWallet,
422
+ walletInfo,
423
+ signer: !!signer
424
+ });
425
+ }, [hitAddress, isNeedSignInWithWallet, walletInfo, signer]);
426
+ const scrollToBottom = () => {
427
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
428
+ };
429
+ React.useEffect(() => {
430
+ scrollToBottom();
431
+ }, [messages]);
432
+ React.useEffect(() => {
433
+ if (!hitAddress) {
434
+ setAit(null);
435
+ }
436
+ }, [hitAddress]);
437
+ React.useEffect(() => {
438
+ if (!ait) {
439
+ return;
440
+ }
441
+ const aitPermissions = ait.metadata?.permissions || [];
442
+ setPermissions(aitPermissions);
443
+ }, [ait]);
444
+ const connectWallet = React.useCallback(async () => {
445
+ if (typeof window === 'undefined') {
446
+ console.error('Web3 is not available in server-side rendering');
447
+ return;
448
+ }
449
+ try {
450
+ const web3Provider = await metakeepClient.ethereum;
451
+ if (!web3Provider) {
452
+ throw new Error('Web3 provider not available');
453
+ }
454
+ await web3Provider.enable();
455
+ const ethersProvider = new ethers.BrowserProvider(web3Provider);
456
+ const userSigner = await ethersProvider.getSigner();
457
+ const userAddress = await userSigner.getAddress();
458
+ localStorage.setItem('walletAddress', userAddress);
459
+ setHitAddress(userAddress);
460
+ setSigner(userSigner);
461
+ setProvider(ethersProvider);
462
+ return userAddress;
463
+ }
464
+ catch (error) {
465
+ console.error('Failed to connect wallet:', error);
466
+ localStorage.removeItem('walletAddress');
467
+ setHitAddress(null);
468
+ setSigner(null);
469
+ setProvider(null);
470
+ throw error;
471
+ }
472
+ }, []);
473
+ const signInWallet = async () => {
474
+ if (!hitAddress) {
475
+ alert('Please connect your wallet first.');
476
+ return;
477
+ }
478
+ if (!signer) {
479
+ alert('Please connect your wallet first.');
480
+ return;
481
+ }
482
+ try {
483
+ const nonceResponse = await nxtlinqApi.auth.getNonce({ address: hitAddress });
484
+ if ('error' in nonceResponse) {
485
+ alert(nonceResponse.error);
486
+ return;
487
+ }
488
+ const payload = {
489
+ address: hitAddress,
490
+ code: nonceResponse.code,
491
+ timestamp: nonceResponse.timestamp
492
+ };
493
+ const stringToSign = stringify(payload);
494
+ const signature = await signer.signMessage(stringToSign || '');
495
+ const response = await nxtlinqApi.auth.signIn({
496
+ ...payload,
497
+ signature
498
+ });
499
+ if ('error' in response) {
500
+ alert(response.error);
501
+ return;
502
+ }
503
+ const { accessToken } = response;
504
+ localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
505
+ setAccessToken(accessToken);
506
+ // 登录成功后,获取 AIT 信息
507
+ try {
508
+ const token = JSON.parse(localStorage.getItem('nxtlinqAITServiceAccessToken') || '');
509
+ const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({ serviceId, controller: hitAddress }, token);
510
+ if (!('error' in aitResponse)) {
511
+ setAit(aitResponse);
512
+ }
513
+ }
514
+ catch (error) {
515
+ console.error('Failed to get AIT:', error);
516
+ }
517
+ }
518
+ catch (error) {
519
+ console.error('Failed to sign in:', error);
520
+ alert('Failed to sign in. Please try again.');
521
+ }
522
+ };
523
+ const hasPermission = async (toolName) => {
524
+ if (!ait) {
525
+ setMessages(prev => [...prev, {
526
+ id: Date.now().toString(),
527
+ content: 'Please connect your HIT wallet and sign in to access permissions.',
528
+ role: 'assistant',
529
+ timestamp: new Date().toISOString(),
530
+ button: true
531
+ }]);
532
+ return false;
533
+ }
534
+ return permissions.includes(toolName);
535
+ };
536
+ const sendMessage = async (content, retryCount = 0) => {
537
+ try {
538
+ setIsLoading(true);
539
+ const response = await nxtlinqApi.agent.sendMessage({
540
+ message: content,
541
+ serviceId,
542
+ });
543
+ if ('error' in response) {
544
+ throw new Error(response.error);
545
+ }
546
+ const message = {
547
+ id: Date.now().toString(),
548
+ content: response.reply,
549
+ role: 'assistant',
550
+ timestamp: new Date().toISOString()
551
+ };
552
+ setMessages(prev => [...prev, message]);
553
+ onMessage?.(message);
554
+ return message;
555
+ }
556
+ catch (error) {
557
+ if (retryCount < maxRetries) {
558
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
559
+ return sendMessage(content, retryCount + 1);
560
+ }
561
+ throw error;
562
+ }
563
+ finally {
564
+ setIsLoading(false);
565
+ }
566
+ };
567
+ const handleSubmit = async (e) => {
568
+ e.preventDefault();
569
+ if (!inputValue.trim() || isLoading)
570
+ return;
571
+ const userMessage = {
572
+ id: Date.now().toString(),
573
+ content: inputValue,
574
+ role: 'user',
575
+ timestamp: new Date().toISOString()
576
+ };
577
+ setMessages(prev => [...prev, userMessage]);
578
+ setInputValue('');
579
+ setIsLoading(true);
580
+ try {
581
+ const response = await sendMessage(inputValue);
582
+ if (response.error) {
583
+ throw new Error(response.error);
584
+ }
585
+ setMessages(prev => [...prev, response]);
586
+ }
587
+ catch (error) {
588
+ console.error('发送消息失败:', error);
589
+ onError?.(error instanceof Error ? error : new Error('发送消息失败'));
590
+ setMessages(prev => [...prev, {
591
+ id: Date.now().toString(),
592
+ content: 'Sorry, there was an error processing your message. Please try again.',
593
+ role: 'assistant',
594
+ timestamp: new Date().toISOString()
595
+ }]);
596
+ }
597
+ finally {
598
+ setIsLoading(false);
599
+ }
600
+ };
601
+ const handlePresetMessage = (message) => {
602
+ if (message.autoSend) {
603
+ setMessages(prev => [...prev, {
604
+ id: Date.now().toString(),
605
+ content: message.text,
606
+ role: 'user',
607
+ timestamp: new Date().toISOString()
608
+ }]);
609
+ sendMessage(message.text);
610
+ }
611
+ else {
612
+ setInputValue(message.text);
613
+ }
614
+ };
615
+ const generateAndRegisterAIT = async () => {
616
+ if (!signer || !hitAddress)
617
+ return;
618
+ const timestamp = Math.floor(Date.now() / 1000);
619
+ const aitId = `did:polygon:ike-dashboard:${hitAddress}:${timestamp}`;
620
+ const metadata = {
621
+ model: 'gpt-4',
622
+ permissions,
623
+ issuedBy: hitAddress,
624
+ controller: hitAddress
625
+ };
626
+ const metadataStr = stringify(metadata);
627
+ const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr || ''));
628
+ // 1️⃣ Upload metadata to Pinata, get CID
629
+ const uploadResponse = await nxtlinqApi.metadata.createMetadata(metadata, accessToken || '');
630
+ if ('error' in uploadResponse) {
631
+ throw new Error(`Failed to upload metadata: ${uploadResponse.error}`);
632
+ }
633
+ const { metadataCid } = uploadResponse;
634
+ // 2️⃣ Sign the message
635
+ const messageHash = ethers.solidityPackedKeccak256(['string', 'address', 'string', 'bytes32', 'uint256'], [aitId, hitAddress, serviceId, metadataHash, timestamp]);
636
+ const signature = await signer.signMessage(ethers.getBytes(messageHash));
637
+ await nxtlinqApi.ait.createAIT({
638
+ aitId,
639
+ controller: hitAddress,
640
+ serviceId,
641
+ metadataHash,
642
+ metadataCid,
643
+ timestamp,
644
+ signature
645
+ }, accessToken || '');
646
+ const aitInfo = {
647
+ aitId,
648
+ controller: hitAddress,
649
+ metadata,
650
+ metadataHash,
651
+ metadataCid,
652
+ signature
653
+ };
654
+ setAit(aitInfo);
655
+ };
656
+ const savePermissions = async () => {
657
+ setIsLoading(true);
658
+ setIsDisabled(true);
659
+ try {
660
+ await generateAndRegisterAIT();
661
+ setIsLoading(false);
662
+ setSuccess(true);
663
+ setShowPermissionForm(false);
664
+ }
665
+ catch (error) {
666
+ console.error('Failed to generate AIT:', error);
667
+ setIsLoading(false);
668
+ setIsDisabled(false);
669
+ if (error instanceof Error) {
670
+ alert(error.message);
671
+ }
672
+ }
673
+ };
674
+ // 添加状态变化的监听
675
+ React.useEffect(() => {
676
+ console.log('States updated:', {
677
+ hitAddress,
678
+ isNeedSignInWithWallet,
679
+ walletInfo,
680
+ isOpen
681
+ });
682
+ }, [hitAddress, isNeedSignInWithWallet, walletInfo, isOpen]);
683
+ return (_jsxs("div", { style: {
684
+ position: 'fixed',
685
+ bottom: '20px',
686
+ right: '20px',
687
+ zIndex: 1000,
688
+ display: 'flex',
689
+ flexDirection: 'column',
690
+ alignItems: 'flex-end',
691
+ gap: '10px'
692
+ }, children: [isOpen && (_jsxs("div", { className: `nxtlinq-chatbot ${className}`, style: {
693
+ width: '350px',
694
+ height: '500px',
695
+ backgroundColor: 'white',
696
+ borderRadius: '10px',
697
+ boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
698
+ display: 'flex',
699
+ flexDirection: 'column',
700
+ overflow: 'hidden'
701
+ }, children: [_jsxs("div", { style: {
702
+ padding: '15px',
703
+ backgroundColor: '#007bff',
704
+ color: 'white',
705
+ borderRadius: '10px 10px 0 0',
706
+ display: 'flex',
707
+ justifyContent: 'space-between',
708
+ alignItems: 'center'
709
+ }, children: [_jsx("h3", { style: { margin: 0 }, children: "AI Agent" }), _jsxs("div", { style: { display: 'flex', gap: '10px' }, children: [_jsx("button", { onClick: () => setShowPermissionForm(true), style: {
710
+ background: 'none',
711
+ border: 'none',
712
+ color: 'white',
713
+ fontSize: '20px',
714
+ cursor: 'pointer',
715
+ padding: '0 5px'
716
+ }, children: "\u2699\uFE0F" }), _jsx("button", { onClick: () => setIsOpen(false), style: {
717
+ background: 'none',
718
+ border: 'none',
719
+ color: 'white',
720
+ fontSize: '24px',
721
+ cursor: 'pointer',
722
+ padding: '0 5px'
723
+ }, children: "\u00D7" })] })] }), _jsxs("div", { style: {
724
+ flex: 1,
725
+ overflowY: 'auto',
726
+ padding: '15px',
727
+ display: 'flex',
728
+ flexDirection: 'column',
729
+ gap: '10px'
730
+ }, children: [messages.map((message, index) => (_jsxs("div", { style: {
731
+ alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
732
+ backgroundColor: message.role === 'user' ? '#007bff' : '#f0f0f0',
733
+ color: message.role === 'user' ? 'white' : 'black',
734
+ padding: '8px 12px',
735
+ borderRadius: '15px',
736
+ maxWidth: '80%',
737
+ wordBreak: 'break-word'
738
+ }, children: [message.content, message.button && (_jsx("button", { onClick: connectWallet, disabled: !!hitAddress, style: {
739
+ display: 'block',
740
+ marginTop: '8px',
741
+ padding: '5px 10px',
742
+ backgroundColor: hitAddress ? '#ccc' : '#28a745',
743
+ color: 'white',
744
+ border: 'none',
745
+ borderRadius: '5px',
746
+ cursor: hitAddress ? 'not-allowed' : 'pointer'
747
+ }, children: hitAddress ? 'Connected' : 'Connect HIT wallet' }))] }, index))), isLoading && (_jsx("div", { style: {
748
+ alignSelf: 'flex-start',
749
+ backgroundColor: '#f0f0f0',
750
+ padding: '8px 12px',
751
+ borderRadius: '15px',
752
+ maxWidth: '80%'
753
+ }, children: "Thinking..." })), _jsx("div", { ref: messagesEndRef })] }), _jsxs("div", { style: {
754
+ padding: '10px',
755
+ borderTop: '1px solid #eee',
756
+ display: 'flex',
757
+ flexDirection: 'column',
758
+ gap: '10px'
759
+ }, children: [_jsx("div", { style: {
760
+ display: 'flex',
761
+ gap: '5px',
762
+ overflowX: 'auto',
763
+ padding: '5px 0'
764
+ }, children: presetMessages.map((message, index) => (_jsx("button", { onClick: () => handlePresetMessage(message), style: {
765
+ padding: '5px 10px',
766
+ backgroundColor: '#f0f0f0',
767
+ border: 'none',
768
+ borderRadius: '15px',
769
+ cursor: 'pointer',
770
+ whiteSpace: 'nowrap',
771
+ fontSize: '12px'
772
+ }, children: message.text }, index))) }), _jsxs("form", { onSubmit: handleSubmit, style: {
773
+ display: 'flex',
774
+ gap: '10px'
775
+ }, children: [_jsx("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), placeholder: placeholder, style: {
776
+ flex: 1,
777
+ padding: '8px 12px',
778
+ border: '1px solid #ddd',
779
+ borderRadius: '20px',
780
+ fontSize: '14px',
781
+ outline: 'none'
782
+ } }), _jsx("button", { type: "submit", disabled: isLoading, style: {
783
+ backgroundColor: '#007bff',
784
+ color: 'white',
785
+ border: 'none',
786
+ borderRadius: '20px',
787
+ padding: '8px 20px',
788
+ cursor: isLoading ? 'not-allowed' : 'pointer',
789
+ fontSize: '14px',
790
+ opacity: isLoading ? 0.7 : 1
791
+ }, children: "Send" })] })] })] })), _jsx("button", { onClick: () => setIsOpen(!isOpen), style: {
792
+ width: '120px',
793
+ height: '40px',
794
+ borderRadius: '20px',
795
+ backgroundColor: '#007bff',
796
+ color: '#ffffff',
797
+ border: 'none',
798
+ cursor: 'pointer',
799
+ display: 'flex',
800
+ alignItems: 'center',
801
+ justifyContent: 'center',
802
+ fontSize: '14px',
803
+ fontWeight: 'bold',
804
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
805
+ transition: 'all 0.3s ease-in-out',
806
+ padding: '0 20px'
807
+ }, children: "AI Agent" }), showPermissionForm && (_jsx("div", { style: {
808
+ position: 'fixed',
809
+ top: 0,
810
+ left: 0,
811
+ right: 0,
812
+ bottom: 0,
813
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
814
+ display: 'flex',
815
+ alignItems: 'center',
816
+ justifyContent: 'center',
817
+ zIndex: 2000
818
+ }, children: _jsx(PermissionForm, { hitAddress: hitAddress, permissions: permissions, setPermissions: setPermissions, setIsDisabled: setIsDisabled, onClose: () => setShowPermissionForm(false), onConnectWallet: connectWallet, onSignIn: signInWallet, onSave: savePermissions, isNeedSignInWithWallet: isNeedSignInWithWallet, walletInfo: walletInfo, onVerifyWallet: handleVerifyWalletClick }) })), success && (_jsxs("div", { style: {
819
+ position: 'fixed',
820
+ bottom: 100,
821
+ right: 40,
822
+ background: '#4caf50',
823
+ color: 'white',
824
+ padding: '12px 24px',
825
+ borderRadius: 8,
826
+ zIndex: 2000,
827
+ fontWeight: 600,
828
+ }, children: ["Saved successfully!", _jsx("button", { style: {
829
+ background: 'none',
830
+ border: 'none',
831
+ color: 'white',
832
+ marginLeft: 16,
833
+ fontSize: 18,
834
+ cursor: 'pointer'
835
+ }, onClick: () => setSuccess(false), children: "\u00D7" })] }))] }));
836
+ };