@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context/ChatBotContext.d.ts.map +1 -1
- package/dist/context/ChatBotContext.js +134 -426
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/pendingTextToolRetry.d.ts +16 -0
- package/dist/pendingTextToolRetry.d.ts.map +1 -0
- package/dist/pendingTextToolRetry.js +30 -0
- package/dist/permissionState.d.ts +53 -0
- package/dist/permissionState.d.ts.map +1 -0
- package/dist/permissionState.js +217 -0
- package/dist/types/ChatBotTypes.d.ts +4 -3
- package/dist/types/ChatBotTypes.d.ts.map +1 -1
- package/dist/ui/ChatBotUI.d.ts.map +1 -1
- package/dist/ui/ChatBotUI.js +6 -4
- package/dist/ui/MessageList.d.ts.map +1 -1
- package/dist/ui/MessageList.js +23 -14
- package/dist/ui/PermissionForm.d.ts.map +1 -1
- package/dist/ui/PermissionForm.js +6 -9
- package/package.json +4 -4
- package/src/context/ChatBotContext.tsx +151 -460
- package/src/index.ts +5 -0
- package/src/pendingTextToolRetry.ts +39 -0
- package/src/types/ChatBotTypes.ts +5 -4
- package/src/ui/ChatBotUI.tsx +6 -4
- package/src/ui/MessageList.tsx +17 -8
- package/src/ui/PermissionForm.tsx +7 -12
|
@@ -25,12 +25,18 @@ import type {
|
|
|
25
25
|
Message,
|
|
26
26
|
ServicePermission,
|
|
27
27
|
} from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
28
|
+
import {
|
|
29
|
+
authorizeTextFrontendTool,
|
|
30
|
+
hasRecordedWalletVerification,
|
|
31
|
+
validateWalletSession,
|
|
32
|
+
} from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
28
33
|
import {
|
|
29
34
|
AIModel,
|
|
30
35
|
ChatBotContextType,
|
|
31
36
|
ChatBotProps,
|
|
32
37
|
PresetMessage
|
|
33
38
|
} from '../types/ChatBotTypes';
|
|
39
|
+
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
34
40
|
|
|
35
41
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
36
42
|
|
|
@@ -212,6 +218,22 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
212
218
|
const permissionsRef = React.useRef(permissions);
|
|
213
219
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
214
220
|
const signerRef = React.useRef(signer);
|
|
221
|
+
const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
|
|
222
|
+
const sendMessageRef = React.useRef<ChatBotContextType['sendMessage']>(async () => {});
|
|
223
|
+
const retryPendingTextTool = React.useCallback(() => {
|
|
224
|
+
const controller = pendingTextToolRetryRef.current;
|
|
225
|
+
return controller.retry(async (request) => {
|
|
226
|
+
// This attempt consumes the old request. A still-blocked preflight will
|
|
227
|
+
// record it again with the next required recovery step.
|
|
228
|
+
controller.clear();
|
|
229
|
+
await sendMessageRef.current(
|
|
230
|
+
request.content,
|
|
231
|
+
1,
|
|
232
|
+
request.isPresetMessage,
|
|
233
|
+
request.attachments,
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
}, []);
|
|
215
237
|
|
|
216
238
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
217
239
|
const getFinalCustomUsername = React.useCallback((username: string | undefined): string | undefined => {
|
|
@@ -996,7 +1018,9 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
996
1018
|
|
|
997
1019
|
// Refresh AIT
|
|
998
1020
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
999
|
-
|
|
1021
|
+
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1022
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
1023
|
+
if (!currentHitAddress) {
|
|
1000
1024
|
setAit(null);
|
|
1001
1025
|
setPermissions([]);
|
|
1002
1026
|
setWalletInfo(null);
|
|
@@ -1006,13 +1030,18 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1006
1030
|
|
|
1007
1031
|
setIsAITLoading(true);
|
|
1008
1032
|
try {
|
|
1033
|
+
let walletAllowsAITLookup = !requireWalletIDVVerification;
|
|
1034
|
+
|
|
1009
1035
|
// Get wallet info first - always try to get wallet info if we have a token
|
|
1010
|
-
if (
|
|
1036
|
+
if (currentToken) {
|
|
1011
1037
|
try {
|
|
1012
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address:
|
|
1038
|
+
const walletResponse = await nxtlinqApi.wallet.getWallet({ address: currentHitAddress }, currentToken);
|
|
1013
1039
|
if (!('error' in walletResponse)) {
|
|
1014
1040
|
setWalletInfo(walletResponse);
|
|
1041
|
+
walletAllowsAITLookup = !requireWalletIDVVerification
|
|
1042
|
+
|| hasRecordedWalletVerification(walletResponse);
|
|
1015
1043
|
} else {
|
|
1044
|
+
setWalletInfo(null);
|
|
1016
1045
|
// Check if the error is due to invalid/expired token
|
|
1017
1046
|
if (walletResponse.error.includes('Invalid or expired token')) {
|
|
1018
1047
|
console.log('Token appears to be invalid during wallet info fetch, clearing it');
|
|
@@ -1020,17 +1049,26 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1020
1049
|
}
|
|
1021
1050
|
}
|
|
1022
1051
|
} catch (error) {
|
|
1052
|
+
setWalletInfo(null);
|
|
1023
1053
|
console.error('Failed to fetch wallet info:', error);
|
|
1024
1054
|
}
|
|
1025
1055
|
}
|
|
1026
1056
|
|
|
1057
|
+
// AIT existence is not identity verification. In strict mode, stop before
|
|
1058
|
+
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
1059
|
+
if (currentToken && !walletAllowsAITLookup) {
|
|
1060
|
+
setAit(null);
|
|
1061
|
+
setPermissions([]);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1027
1065
|
// Only try to fetch AIT if we have a token
|
|
1028
|
-
if (
|
|
1066
|
+
if (currentToken) {
|
|
1029
1067
|
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
1030
1068
|
serviceId,
|
|
1031
|
-
controller:
|
|
1069
|
+
controller: currentHitAddress,
|
|
1032
1070
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
1033
|
-
},
|
|
1071
|
+
}, currentToken);
|
|
1034
1072
|
|
|
1035
1073
|
if ('error' in response) {
|
|
1036
1074
|
console.error('Failed to fetch AIT:', response.error);
|
|
@@ -1062,17 +1100,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1062
1100
|
const isNeedSignInWithWallet = React.useMemo(() => {
|
|
1063
1101
|
if (!hitAddress) return false;
|
|
1064
1102
|
if (!nxtlinqAITServiceAccessToken) return true;
|
|
1065
|
-
|
|
1066
|
-
try {
|
|
1067
|
-
const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
|
|
1068
|
-
const address = payload.address;
|
|
1069
|
-
if (address !== hitAddress) return true;
|
|
1070
|
-
|
|
1071
|
-
return false;
|
|
1072
|
-
} catch (error) {
|
|
1073
|
-
console.error('Error parsing token:', error);
|
|
1074
|
-
return true;
|
|
1075
|
-
}
|
|
1103
|
+
return !validateWalletSession(nxtlinqAITServiceAccessToken, hitAddress).valid;
|
|
1076
1104
|
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
1077
1105
|
|
|
1078
1106
|
// Connect wallet
|
|
@@ -1182,7 +1210,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1182
1210
|
}
|
|
1183
1211
|
}, [nxtlinqAITServiceAccessToken, refreshAIT, isNeedSignInWithWallet, requireWalletIDVVerification, customUsername, nxtlinqApi]);
|
|
1184
1212
|
|
|
1185
|
-
const signInWallet = async (autoShowSuccessMessage = true) => {
|
|
1213
|
+
const signInWallet = async (autoShowSuccessMessage = true): Promise<boolean> => {
|
|
1186
1214
|
// Use refs to get latest state values for consistency with hasPermission
|
|
1187
1215
|
const currentHitAddress = hitAddressRef.current;
|
|
1188
1216
|
const currentSigner = signerRef.current;
|
|
@@ -1215,7 +1243,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1215
1243
|
if (autoShowSuccessMessage) {
|
|
1216
1244
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1217
1245
|
}
|
|
1218
|
-
return;
|
|
1246
|
+
return false;
|
|
1219
1247
|
}
|
|
1220
1248
|
|
|
1221
1249
|
if (!signerToUse) {
|
|
@@ -1223,7 +1251,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1223
1251
|
if (autoShowSuccessMessage) {
|
|
1224
1252
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1225
1253
|
}
|
|
1226
|
-
return;
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1258
|
+
if (currentToken && validateWalletSession(currentToken, addressToUse).valid) {
|
|
1259
|
+
await refreshAIT();
|
|
1260
|
+
return true;
|
|
1227
1261
|
}
|
|
1228
1262
|
|
|
1229
1263
|
try {
|
|
@@ -1232,7 +1266,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1232
1266
|
if (autoShowSuccessMessage) {
|
|
1233
1267
|
showError(normalizeErrorForUser(nonceResponse.error));
|
|
1234
1268
|
}
|
|
1235
|
-
return;
|
|
1269
|
+
return false;
|
|
1236
1270
|
}
|
|
1237
1271
|
|
|
1238
1272
|
const payload = {
|
|
@@ -1253,10 +1287,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1253
1287
|
if (autoShowSuccessMessage) {
|
|
1254
1288
|
showError(normalizeErrorForUser(response.error));
|
|
1255
1289
|
}
|
|
1256
|
-
return;
|
|
1290
|
+
return false;
|
|
1257
1291
|
}
|
|
1258
1292
|
const { accessToken } = response;
|
|
1259
1293
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1294
|
+
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1260
1295
|
|
|
1261
1296
|
// Wait for state to update before refreshing AIT
|
|
1262
1297
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -1265,372 +1300,83 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1265
1300
|
if (autoShowSuccessMessage) {
|
|
1266
1301
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
1267
1302
|
}
|
|
1303
|
+
return true;
|
|
1268
1304
|
} catch (error) {
|
|
1269
1305
|
console.error('Failed to sign in:', error);
|
|
1270
1306
|
if (autoShowSuccessMessage) {
|
|
1271
1307
|
showError('Failed to sign in. Please try again.');
|
|
1272
1308
|
}
|
|
1309
|
+
return false;
|
|
1273
1310
|
}
|
|
1274
1311
|
};
|
|
1275
1312
|
|
|
1276
1313
|
// Check permissions
|
|
1277
|
-
const hasPermission = async (
|
|
1314
|
+
const hasPermission = async (
|
|
1315
|
+
requiredPermission: string,
|
|
1316
|
+
): Promise<boolean> => {
|
|
1278
1317
|
// Use refs to get latest state values
|
|
1279
1318
|
const currentHitAddress = hitAddressRef.current;
|
|
1280
|
-
const currentAit = aitRef.current;
|
|
1281
|
-
const currentPermissions = permissionsRef.current;
|
|
1282
1319
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1283
1320
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
await connectWallet(false); // Don't show sign-in message yet
|
|
1298
|
-
setIsAutoConnecting(false); // Clear auto-connecting state
|
|
1299
|
-
|
|
1300
|
-
// Show brief success message for auto-connect
|
|
1301
|
-
showSuccess(walletTextUtils.getWalletText('Auto wallet connection successful', serviceId));
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
onAutoConnect?.(); // Call callback if provided
|
|
1305
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1306
|
-
|
|
1307
|
-
// After auto connect, if not signed in, then auto sign-in
|
|
1308
|
-
const tokenAfterConnect = nxtlinqAITServiceAccessTokenRef.current;
|
|
1309
|
-
if (!tokenAfterConnect) {
|
|
1310
|
-
setIsAutoConnecting(true);
|
|
1311
|
-
await signInWallet(false);
|
|
1312
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1313
|
-
setIsAutoConnecting(false);
|
|
1314
|
-
showSuccess(walletTextUtils.getWalletText('Auto sign-in successful after wallet connect', serviceId));
|
|
1315
|
-
await refreshAIT();
|
|
1316
|
-
// Wait for AIT to be fully loaded with polling
|
|
1317
|
-
let attempts = 0;
|
|
1318
|
-
const maxAttempts = 5;
|
|
1319
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1320
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1321
|
-
attempts++;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
// If connection (and sign-in if needed) successful, continue with permission check
|
|
1326
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1327
|
-
return result;
|
|
1328
|
-
} catch (error) {
|
|
1329
|
-
console.error('Failed to auto-connect wallet:', error);
|
|
1330
|
-
setIsAutoConnecting(false); // Clear auto-connecting state on error
|
|
1331
|
-
return false;
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1335
|
-
return false;
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
if (!currentToken) {
|
|
1339
|
-
if (autoRetry) {
|
|
1340
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1341
|
-
setMessages(prev => [...prev, {
|
|
1342
|
-
id: Date.now().toString(),
|
|
1343
|
-
content: walletTextUtils.getWalletText('Please sign in with your HIT wallet to continue.', serviceId),
|
|
1344
|
-
role: 'assistant',
|
|
1345
|
-
timestamp: new Date().toISOString(),
|
|
1346
|
-
button: 'signIn'
|
|
1347
|
-
}]);
|
|
1348
|
-
|
|
1349
|
-
try {
|
|
1350
|
-
setIsAutoConnecting(true); // Mark as auto-signing
|
|
1351
|
-
await signInWallet(false); // Don't show success message yet
|
|
1352
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1353
|
-
setIsAutoConnecting(false); // Clear auto-signing state
|
|
1354
|
-
|
|
1355
|
-
// Show brief success message for auto-sign-in
|
|
1356
|
-
showSuccess('Auto sign-in successful');
|
|
1357
|
-
|
|
1358
|
-
// Ensure AIT is refreshed after sign-in
|
|
1359
|
-
await refreshAIT();
|
|
1360
|
-
|
|
1361
|
-
// Wait for AIT to be fully loaded with polling
|
|
1362
|
-
let attempts = 0;
|
|
1363
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1364
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1365
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1366
|
-
attempts++;
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// Only continue if AIT is actually loaded
|
|
1370
|
-
if (aitRef.current) {
|
|
1371
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1372
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1373
|
-
// If sign-in successful, continue with permission check
|
|
1374
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1375
|
-
return result;
|
|
1376
|
-
} else {
|
|
1377
|
-
return false;
|
|
1378
|
-
}
|
|
1379
|
-
} catch (error) {
|
|
1380
|
-
console.error('Failed to auto-sign-in wallet:', error);
|
|
1381
|
-
setIsAutoConnecting(false); // Clear auto-signing state on error
|
|
1382
|
-
return false;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1386
|
-
return false;
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
try {
|
|
1390
|
-
const payload = JSON.parse(atob(currentToken.split('.')[1]));
|
|
1391
|
-
const address = payload.address;
|
|
1392
|
-
if (address !== currentHitAddress) {
|
|
1393
|
-
setNxtlinqAITServiceAccessToken('');
|
|
1394
|
-
if (autoRetry) {
|
|
1395
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1396
|
-
setMessages(prev => [...prev, {
|
|
1397
|
-
id: Date.now().toString(),
|
|
1398
|
-
content: walletTextUtils.getWalletText('Wallet address mismatch. Please sign in with the correct wallet.', serviceId),
|
|
1399
|
-
role: 'assistant',
|
|
1400
|
-
timestamp: new Date().toISOString(),
|
|
1401
|
-
button: 'signIn'
|
|
1402
|
-
}]);
|
|
1403
|
-
|
|
1404
|
-
try {
|
|
1405
|
-
setIsAutoConnecting(true); // Mark as auto-signing
|
|
1406
|
-
await signInWallet(false); // Don't show success message yet
|
|
1407
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1408
|
-
setIsAutoConnecting(false); // Clear auto-signing state
|
|
1409
|
-
|
|
1410
|
-
// Show brief success message for auto-sign-in after address mismatch
|
|
1411
|
-
showSuccess('Auto sign-in successful after address mismatch');
|
|
1412
|
-
|
|
1413
|
-
// Ensure AIT is refreshed after sign-in
|
|
1414
|
-
await refreshAIT();
|
|
1415
|
-
|
|
1416
|
-
// Wait for AIT to be fully loaded with polling
|
|
1417
|
-
let attempts = 0;
|
|
1418
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1419
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1420
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1421
|
-
attempts++;
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
// Only continue if AIT is actually loaded
|
|
1425
|
-
if (aitRef.current) {
|
|
1426
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1427
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1428
|
-
// If sign-in successful, continue with permission check
|
|
1429
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1430
|
-
return result;
|
|
1431
|
-
} else {
|
|
1432
|
-
return false;
|
|
1433
|
-
}
|
|
1434
|
-
} catch (error) {
|
|
1435
|
-
console.error('Failed to auto-sign-in after address mismatch:', error);
|
|
1436
|
-
setIsAutoConnecting(false); // Clear auto-signing state on error
|
|
1437
|
-
return false;
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1441
|
-
return false;
|
|
1442
|
-
}
|
|
1443
|
-
} catch (error) {
|
|
1444
|
-
console.error('Error parsing token:', error);
|
|
1445
|
-
setNxtlinqAITServiceAccessToken('');
|
|
1446
|
-
if (autoRetry) {
|
|
1447
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1448
|
-
setMessages(prev => [...prev, {
|
|
1449
|
-
id: Date.now().toString(),
|
|
1450
|
-
content: walletTextUtils.getWalletText('Invalid wallet session. Please sign in again.', serviceId),
|
|
1451
|
-
role: 'assistant',
|
|
1452
|
-
timestamp: new Date().toISOString(),
|
|
1453
|
-
button: 'signIn'
|
|
1454
|
-
}]);
|
|
1455
|
-
|
|
1456
|
-
try {
|
|
1457
|
-
setIsAutoConnecting(true); // Mark as auto-signing
|
|
1458
|
-
await signInWallet(false); // Don't show success message yet
|
|
1459
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1460
|
-
setIsAutoConnecting(false); // Clear auto-signing state
|
|
1461
|
-
|
|
1462
|
-
// Show brief success message for auto-sign-in after token parse error
|
|
1463
|
-
showSuccess('Auto sign-in successful after token error');
|
|
1464
|
-
|
|
1465
|
-
// Ensure AIT is refreshed after sign-in
|
|
1466
|
-
await refreshAIT();
|
|
1467
|
-
|
|
1468
|
-
// Wait for AIT to be fully loaded with polling
|
|
1469
|
-
let attempts = 0;
|
|
1470
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1471
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1472
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1473
|
-
attempts++;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
// Only continue if AIT is actually loaded
|
|
1477
|
-
if (aitRef.current) {
|
|
1478
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1479
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1480
|
-
// If sign-in successful, continue with permission check
|
|
1481
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1482
|
-
return result;
|
|
1483
|
-
} else {
|
|
1484
|
-
return false;
|
|
1485
|
-
}
|
|
1486
|
-
} catch (signInError) {
|
|
1487
|
-
console.error('Failed to auto-sign-in after token parse error:', signInError);
|
|
1488
|
-
setIsAutoConnecting(false); // Clear auto-signing state on error
|
|
1489
|
-
return false;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1493
|
-
return false;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
if (!currentAit) {
|
|
1497
|
-
// Show loading message if AIT is still loading
|
|
1498
|
-
if (isAITLoading) {
|
|
1499
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1500
|
-
setMessages(prev => [...prev, {
|
|
1501
|
-
id: Date.now().toString(),
|
|
1502
|
-
content: walletTextUtils.getWalletText('Loading your wallet configuration... Please wait a moment.', serviceId),
|
|
1503
|
-
role: 'assistant',
|
|
1504
|
-
timestamp: new Date().toISOString()
|
|
1505
|
-
}]);
|
|
1506
|
-
return false;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// If AIT is not loaded but we have a token, try to refresh it once
|
|
1510
|
-
if (currentToken && !isAITLoading) {
|
|
1511
|
-
try {
|
|
1512
|
-
await refreshAIT();
|
|
1513
|
-
// Wait for AIT to be loaded with polling
|
|
1514
|
-
let attempts = 0;
|
|
1515
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1516
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1517
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1518
|
-
attempts++;
|
|
1519
|
-
}
|
|
1520
|
-
// Check again after refresh
|
|
1521
|
-
if (!aitRef.current) {
|
|
1522
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1523
|
-
setMessages(prev => [...prev, {
|
|
1524
|
-
id: Date.now().toString(),
|
|
1525
|
-
content: walletTextUtils.getWalletText('No AIT found for your wallet. Please click the settings button (⚙️) to configure your AIT permissions.', serviceId),
|
|
1526
|
-
role: 'assistant',
|
|
1527
|
-
timestamp: new Date().toISOString()
|
|
1528
|
-
}]);
|
|
1529
|
-
return false;
|
|
1530
|
-
}
|
|
1531
|
-
} catch (error) {
|
|
1532
|
-
console.error('Failed to refresh AIT during permission check:', error);
|
|
1533
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1534
|
-
setMessages(prev => [...prev, {
|
|
1535
|
-
id: Date.now().toString(),
|
|
1536
|
-
content: 'No AIT found for your wallet. Please click the settings button (⚙️) to configure your AIT permissions.',
|
|
1537
|
-
role: 'assistant',
|
|
1538
|
-
timestamp: new Date().toISOString()
|
|
1539
|
-
}]);
|
|
1540
|
-
return false;
|
|
1541
|
-
}
|
|
1542
|
-
} else {
|
|
1543
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1544
|
-
setMessages(prev => [...prev, {
|
|
1545
|
-
id: Date.now().toString(),
|
|
1546
|
-
content: 'No AIT found for your wallet. Please click the settings button (⚙️) to configure your AIT permissions.',
|
|
1547
|
-
role: 'assistant',
|
|
1548
|
-
timestamp: new Date().toISOString()
|
|
1549
|
-
}]);
|
|
1550
|
-
return false;
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
if (availablePermissions.length === 0) {
|
|
1555
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1556
|
-
setMessages(prev => [...prev, {
|
|
1557
|
-
id: Date.now().toString(),
|
|
1558
|
-
content: `No permissions available for your current identity provider. Please check your service configuration or contact support. Service ID: ${serviceId}, Permission Group: ${permissionGroup || 'None'}`,
|
|
1559
|
-
role: 'assistant',
|
|
1560
|
-
timestamp: new Date().toISOString()
|
|
1561
|
-
}]);
|
|
1562
|
-
return false;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
const checkToolPermissionResult = await nxtlinqApi.agent.checkToolPermission({
|
|
1321
|
+
const authorization = await authorizeTextFrontendTool({
|
|
1322
|
+
api: nxtlinqApi,
|
|
1323
|
+
serviceId,
|
|
1324
|
+
toolName: requiredPermission,
|
|
1325
|
+
snapshot: {
|
|
1326
|
+
walletAddress: currentHitAddress,
|
|
1327
|
+
walletToken: currentToken,
|
|
1328
|
+
// Legacy AIT issuance is unscoped (`externalId=''`). Do not impose a
|
|
1329
|
+
// wallet-address subject until issuance/migration supports it.
|
|
1330
|
+
externalId: undefined,
|
|
1331
|
+
requireWalletIDVVerification,
|
|
1332
|
+
loading: isAITLoading || isAutoConnecting,
|
|
1333
|
+
},
|
|
1566
1334
|
...authFields(),
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
})
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
}]);
|
|
1613
|
-
return false;
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
// User has AIT but hasn't enabled this permission → prompt to enable
|
|
1617
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1618
|
-
const permissionMsg = `You don't have the required AIT permission: ${requiredPermission}. Would you like to enable AIT permission?`;
|
|
1619
|
-
setMessages(prev => [
|
|
1620
|
-
...prev,
|
|
1621
|
-
{
|
|
1622
|
-
id: Date.now().toString(),
|
|
1623
|
-
content: permissionMsg,
|
|
1624
|
-
role: 'assistant',
|
|
1625
|
-
timestamp: new Date().toISOString(),
|
|
1626
|
-
button: 'enableAIT',
|
|
1627
|
-
metadata: { requiredPermission }
|
|
1628
|
-
}
|
|
1629
|
-
]);
|
|
1630
|
-
return false;
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
return true;
|
|
1335
|
+
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1336
|
+
? getFinalCustomUsername(customUsername)
|
|
1337
|
+
: undefined,
|
|
1338
|
+
});
|
|
1339
|
+
if (authorization.status === 'allowed') return true;
|
|
1340
|
+
|
|
1341
|
+
setIsLoading(false);
|
|
1342
|
+
const recovery: Message = {
|
|
1343
|
+
id: Date.now().toString(),
|
|
1344
|
+
content: 'Authorization is temporarily unavailable. Please try again.',
|
|
1345
|
+
role: 'assistant',
|
|
1346
|
+
timestamp: new Date().toISOString(),
|
|
1347
|
+
};
|
|
1348
|
+
switch (authorization.status) {
|
|
1349
|
+
case 'wallet_disconnected':
|
|
1350
|
+
recovery.content = walletTextUtils.getWalletText('Please connect your HIT wallet to continue.', serviceId);
|
|
1351
|
+
recovery.button = 'connectWallet';
|
|
1352
|
+
break;
|
|
1353
|
+
case 'wallet_signed_out':
|
|
1354
|
+
recovery.content = walletTextUtils.getWalletText('Please sign in with your HIT wallet to continue.', serviceId);
|
|
1355
|
+
recovery.button = 'signIn';
|
|
1356
|
+
break;
|
|
1357
|
+
case 'wallet_session_invalid':
|
|
1358
|
+
recovery.content = walletTextUtils.getWalletText('Invalid wallet session. Please sign in again.', serviceId);
|
|
1359
|
+
recovery.button = 'signIn';
|
|
1360
|
+
break;
|
|
1361
|
+
case 'wallet_verification_required':
|
|
1362
|
+
recovery.content = 'Please verify your wallet identity before using this tool.';
|
|
1363
|
+
recovery.button = 'verifyWallet';
|
|
1364
|
+
setIsPermissionFormOpen(true);
|
|
1365
|
+
setShowPermissionForm(true);
|
|
1366
|
+
break;
|
|
1367
|
+
case 'ait_not_found':
|
|
1368
|
+
recovery.content = 'No AIT found for your wallet. Please open settings to configure your AIT.';
|
|
1369
|
+
break;
|
|
1370
|
+
case 'permission_denied':
|
|
1371
|
+
recovery.content = `You don't have the required AIT permission: ${authorization.requiredPermission}. Would you like to enable AIT permission?`;
|
|
1372
|
+
recovery.button = 'enableAIT';
|
|
1373
|
+
recovery.metadata = { requiredPermission: authorization.requiredPermission };
|
|
1374
|
+
break;
|
|
1375
|
+
case 'authorization_unavailable':
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
setMessages(prev => [...prev, recovery]);
|
|
1379
|
+
return false;
|
|
1634
1380
|
};
|
|
1635
1381
|
|
|
1636
1382
|
// AI Model related functions
|
|
@@ -1684,8 +1430,17 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1684
1430
|
clientPipelineOverride?: Array<{ name: string; durationMs: number }>
|
|
1685
1431
|
): Promise<void> => {
|
|
1686
1432
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
1687
|
-
|
|
1433
|
+
// Recovery retries may run immediately after a completion handler calls
|
|
1434
|
+
// setIsLoading(false), before React has committed that state update.
|
|
1435
|
+
if (!hasContent || (retryCount === 0 && isLoading)) return;
|
|
1436
|
+
if (retryCount === 0) {
|
|
1437
|
+
// A new user turn supersedes any older interrupted tool request.
|
|
1438
|
+
pendingTextToolRetryRef.current.clear();
|
|
1439
|
+
}
|
|
1688
1440
|
setPendingAutoTts(null);
|
|
1441
|
+
const turnExternalId = typeof window !== 'undefined'
|
|
1442
|
+
? localStorage.getItem('walletAddress') || undefined
|
|
1443
|
+
: hitAddressRef.current || undefined;
|
|
1689
1444
|
|
|
1690
1445
|
const currentModel = getCurrentModel();
|
|
1691
1446
|
// Initialize with current model, will be updated with actual model from backend response
|
|
@@ -1764,7 +1519,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1764
1519
|
model: currentModel.value,
|
|
1765
1520
|
...authFields(),
|
|
1766
1521
|
pseudoId: pseudoId,
|
|
1767
|
-
externalId:
|
|
1522
|
+
externalId: turnExternalId,
|
|
1768
1523
|
customUserInfo,
|
|
1769
1524
|
customUsername,
|
|
1770
1525
|
message: content || (attachments && attachments.length > 0 ? `Uploaded ${attachments.length} file(s)` : ''),
|
|
@@ -1994,96 +1749,25 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1994
1749
|
|
|
1995
1750
|
let toolMsg = '';
|
|
1996
1751
|
if (onToolUse) {
|
|
1997
|
-
let wasAutoConnected = false;
|
|
1998
|
-
let wasAutoSignedIn = false;
|
|
1999
|
-
// Added: Mark if permission denied due to missing AIT permission
|
|
2000
|
-
let permissionDenied = false;
|
|
2001
|
-
// Use requiredPermission from response if available, otherwise fall back to toolUse.name
|
|
2002
|
-
const permissionToCheck = (response as any)?.requiredPermission || toolUse.name;
|
|
2003
|
-
|
|
2004
1752
|
const isToolAllowed = await hasPermission(
|
|
2005
|
-
|
|
2006
|
-
true,
|
|
2007
|
-
() => { wasAutoConnected = true },
|
|
2008
|
-
() => { wasAutoSignedIn = true }
|
|
1753
|
+
toolUse.name,
|
|
2009
1754
|
);
|
|
2010
1755
|
|
|
2011
|
-
//
|
|
2012
|
-
if (!isToolAllowed && !permissions.includes(permissionToCheck) && availablePermissions.map(p => p.label).includes(permissionToCheck)) {
|
|
2013
|
-
permissionDenied = true;
|
|
2014
|
-
}
|
|
1756
|
+
// Typed preflight already emitted the only permitted recovery UI.
|
|
2015
1757
|
if (!isToolAllowed) {
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
if (isSemiAutomaticMode) {
|
|
2023
|
-
setIsLoading(false);
|
|
2024
|
-
setMessages(prev => [...prev, {
|
|
2025
|
-
id: Date.now().toString(),
|
|
2026
|
-
content: 'Click button to continue using tool',
|
|
2027
|
-
role: 'assistant',
|
|
2028
|
-
timestamp: new Date().toISOString(),
|
|
2029
|
-
button: 'continue'
|
|
2030
|
-
}]);
|
|
2031
|
-
return;
|
|
2032
|
-
} else {
|
|
2033
|
-
// Only retry for auto-connect/auto-sign-in scenarios
|
|
2034
|
-
if (wasAutoConnected && retryCount < 1) {
|
|
2035
|
-
// Clear loading state and retry immediately
|
|
2036
|
-
setIsLoading(false);
|
|
2037
|
-
|
|
2038
|
-
// Check if wallet is already signed in
|
|
2039
|
-
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
2040
|
-
|
|
2041
|
-
if (!currentToken) {
|
|
2042
|
-
// If not signed in, directly retry the message without waiting for AIT
|
|
2043
|
-
setTimeout(() => {
|
|
2044
|
-
sendMessage(content, retryCount + 1, isPresetMessage);
|
|
2045
|
-
}, 2000);
|
|
2046
|
-
} else {
|
|
2047
|
-
// If already signed in, wait for AIT to be fully loaded before retrying
|
|
2048
|
-
setTimeout(async () => {
|
|
2049
|
-
// Wait for AIT to be loaded if needed
|
|
2050
|
-
if (!aitRef.current && nxtlinqAITServiceAccessTokenRef.current) {
|
|
2051
|
-
await refreshAIT();
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
// Wait for AIT to be fully loaded with polling
|
|
2055
|
-
let attempts = 0;
|
|
2056
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
2057
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
2058
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2059
|
-
attempts++;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
// Only retry if AIT is actually loaded
|
|
2063
|
-
if (aitRef.current) {
|
|
2064
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
2065
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
2066
|
-
sendMessage(content, retryCount + 1, isPresetMessage);
|
|
2067
|
-
}
|
|
2068
|
-
}, 2000);
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
return;
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
if (isSemiAutomaticMode && wasAutoSignedIn) {
|
|
1758
|
+
pendingTextToolRetryRef.current.set({
|
|
1759
|
+
content,
|
|
1760
|
+
isPresetMessage,
|
|
1761
|
+
attachments,
|
|
1762
|
+
});
|
|
2076
1763
|
setIsLoading(false);
|
|
2077
|
-
setMessages(prev => [...prev, {
|
|
2078
|
-
id: Date.now().toString(),
|
|
2079
|
-
content: 'Click button to continue using tool',
|
|
2080
|
-
role: 'assistant',
|
|
2081
|
-
timestamp: new Date().toISOString(),
|
|
2082
|
-
button: 'continue'
|
|
2083
|
-
}]);
|
|
2084
1764
|
return;
|
|
2085
1765
|
}
|
|
2086
1766
|
|
|
1767
|
+
// Authorization passed. Clear before invoking the host callback so
|
|
1768
|
+
// concurrent completion events cannot execute this tool twice.
|
|
1769
|
+
pendingTextToolRetryRef.current.clear();
|
|
1770
|
+
|
|
2087
1771
|
// Create streaming message for tool execution
|
|
2088
1772
|
const streamingMessageId = `streaming-${Date.now()}`;
|
|
2089
1773
|
const streamingMessage: Message = {
|
|
@@ -2393,6 +2077,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2393
2077
|
setIsLoading(false);
|
|
2394
2078
|
}
|
|
2395
2079
|
};
|
|
2080
|
+
sendMessageRef.current = sendMessage;
|
|
2396
2081
|
|
|
2397
2082
|
// Handle submit
|
|
2398
2083
|
const handleSubmit = async (e: React.FormEvent, attachments?: Attachment[]) => {
|
|
@@ -2947,6 +2632,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2947
2632
|
setShowPermissionForm(false);
|
|
2948
2633
|
setIsPermissionFormOpen(false);
|
|
2949
2634
|
await refreshAIT(true);
|
|
2635
|
+
await retryPendingTextTool();
|
|
2950
2636
|
} catch (error) {
|
|
2951
2637
|
console.error('Failed to generate AIT:', error);
|
|
2952
2638
|
setIsDisabled(false);
|
|
@@ -3025,7 +2711,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3025
2711
|
}
|
|
3026
2712
|
setIsLoading(false);
|
|
3027
2713
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
3028
|
-
refreshAIT();
|
|
2714
|
+
await refreshAIT();
|
|
2715
|
+
await retryPendingTextTool();
|
|
3029
2716
|
return;
|
|
3030
2717
|
}
|
|
3031
2718
|
showError(verifyWalletResponse.error);
|
|
@@ -3063,7 +2750,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3063
2750
|
}
|
|
3064
2751
|
setIsLoading(false);
|
|
3065
2752
|
showSuccess('Wallet verification completed successfully! Your wallet is now verified and ready to use.');
|
|
3066
|
-
refreshAIT();
|
|
2753
|
+
await refreshAIT();
|
|
2754
|
+
await retryPendingTextTool();
|
|
3067
2755
|
return;
|
|
3068
2756
|
} catch (error) {
|
|
3069
2757
|
let msg = 'Verification failed';
|
|
@@ -3103,6 +2791,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3103
2791
|
await refreshAIT();
|
|
3104
2792
|
setIsLoading(false);
|
|
3105
2793
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2794
|
+
await retryPendingTextTool();
|
|
3106
2795
|
return;
|
|
3107
2796
|
}
|
|
3108
2797
|
// Handle specific error messages
|
|
@@ -3118,6 +2807,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3118
2807
|
await refreshAIT();
|
|
3119
2808
|
setIsLoading(false);
|
|
3120
2809
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2810
|
+
await retryPendingTextTool();
|
|
3121
2811
|
return;
|
|
3122
2812
|
} catch (error) {
|
|
3123
2813
|
console.error('Custom wallet verification failed:', error);
|
|
@@ -3298,6 +2988,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3298
2988
|
// Functions
|
|
3299
2989
|
connectWallet,
|
|
3300
2990
|
signInWallet,
|
|
2991
|
+
retryPendingTextTool,
|
|
3301
2992
|
sendMessage,
|
|
3302
2993
|
handleSubmit,
|
|
3303
2994
|
handlePresetMessage,
|