@bytexbyte/nxtlinq-ai-agent-sdk 1.0.8 → 1.1.0

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