@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
|
@@ -5,6 +5,8 @@ import * as React from 'react';
|
|
|
5
5
|
import { flushSync } from 'react-dom';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
import { createNxtlinqApi, setApiHosts, synthesizeSpeechToBuffer, streamSpeechToAudioContext, useLocalStorage, useSessionStorage, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, } from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
|
8
|
+
import { authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
9
|
+
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
8
10
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
9
11
|
const ChatBotContext = React.createContext(undefined);
|
|
10
12
|
export const useChatBot = () => {
|
|
@@ -143,6 +145,17 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
143
145
|
const permissionsRef = React.useRef(permissions);
|
|
144
146
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
145
147
|
const signerRef = React.useRef(signer);
|
|
148
|
+
const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
|
|
149
|
+
const sendMessageRef = React.useRef(async () => { });
|
|
150
|
+
const retryPendingTextTool = React.useCallback(() => {
|
|
151
|
+
const controller = pendingTextToolRetryRef.current;
|
|
152
|
+
return controller.retry(async (request) => {
|
|
153
|
+
// This attempt consumes the old request. A still-blocked preflight will
|
|
154
|
+
// record it again with the next required recovery step.
|
|
155
|
+
controller.clear();
|
|
156
|
+
await sendMessageRef.current(request.content, 1, request.isPresetMessage, request.attachments);
|
|
157
|
+
});
|
|
158
|
+
}, []);
|
|
146
159
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
147
160
|
const getFinalCustomUsername = React.useCallback((username) => {
|
|
148
161
|
if (!username)
|
|
@@ -859,7 +872,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
859
872
|
};
|
|
860
873
|
// Refresh AIT
|
|
861
874
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
862
|
-
|
|
875
|
+
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
876
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
877
|
+
if (!currentHitAddress) {
|
|
863
878
|
setAit(null);
|
|
864
879
|
setPermissions([]);
|
|
865
880
|
setWalletInfo(null);
|
|
@@ -868,14 +883,18 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
868
883
|
}
|
|
869
884
|
setIsAITLoading(true);
|
|
870
885
|
try {
|
|
886
|
+
let walletAllowsAITLookup = !requireWalletIDVVerification;
|
|
871
887
|
// Get wallet info first - always try to get wallet info if we have a token
|
|
872
|
-
if (
|
|
888
|
+
if (currentToken) {
|
|
873
889
|
try {
|
|
874
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address:
|
|
890
|
+
const walletResponse = await nxtlinqApi.wallet.getWallet({ address: currentHitAddress }, currentToken);
|
|
875
891
|
if (!('error' in walletResponse)) {
|
|
876
892
|
setWalletInfo(walletResponse);
|
|
893
|
+
walletAllowsAITLookup = !requireWalletIDVVerification
|
|
894
|
+
|| hasRecordedWalletVerification(walletResponse);
|
|
877
895
|
}
|
|
878
896
|
else {
|
|
897
|
+
setWalletInfo(null);
|
|
879
898
|
// Check if the error is due to invalid/expired token
|
|
880
899
|
if (walletResponse.error.includes('Invalid or expired token')) {
|
|
881
900
|
console.log('Token appears to be invalid during wallet info fetch, clearing it');
|
|
@@ -884,16 +903,24 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
884
903
|
}
|
|
885
904
|
}
|
|
886
905
|
catch (error) {
|
|
906
|
+
setWalletInfo(null);
|
|
887
907
|
console.error('Failed to fetch wallet info:', error);
|
|
888
908
|
}
|
|
889
909
|
}
|
|
910
|
+
// AIT existence is not identity verification. In strict mode, stop before
|
|
911
|
+
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
912
|
+
if (currentToken && !walletAllowsAITLookup) {
|
|
913
|
+
setAit(null);
|
|
914
|
+
setPermissions([]);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
890
917
|
// Only try to fetch AIT if we have a token
|
|
891
|
-
if (
|
|
918
|
+
if (currentToken) {
|
|
892
919
|
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
893
920
|
serviceId,
|
|
894
|
-
controller:
|
|
921
|
+
controller: currentHitAddress,
|
|
895
922
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
896
|
-
},
|
|
923
|
+
}, currentToken);
|
|
897
924
|
if ('error' in response) {
|
|
898
925
|
console.error('Failed to fetch AIT:', response.error);
|
|
899
926
|
setAit(null);
|
|
@@ -927,17 +954,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
927
954
|
return false;
|
|
928
955
|
if (!nxtlinqAITServiceAccessToken)
|
|
929
956
|
return true;
|
|
930
|
-
|
|
931
|
-
const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
|
|
932
|
-
const address = payload.address;
|
|
933
|
-
if (address !== hitAddress)
|
|
934
|
-
return true;
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
catch (error) {
|
|
938
|
-
console.error('Error parsing token:', error);
|
|
939
|
-
return true;
|
|
940
|
-
}
|
|
957
|
+
return !validateWalletSession(nxtlinqAITServiceAccessToken, hitAddress).valid;
|
|
941
958
|
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
942
959
|
// Connect wallet
|
|
943
960
|
const connectWallet = React.useCallback(async (autoShowSignInMessage = true) => {
|
|
@@ -1067,14 +1084,19 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1067
1084
|
if (autoShowSuccessMessage) {
|
|
1068
1085
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1069
1086
|
}
|
|
1070
|
-
return;
|
|
1087
|
+
return false;
|
|
1071
1088
|
}
|
|
1072
1089
|
if (!signerToUse) {
|
|
1073
1090
|
console.log('No signer available, returning early');
|
|
1074
1091
|
if (autoShowSuccessMessage) {
|
|
1075
1092
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1076
1093
|
}
|
|
1077
|
-
return;
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1097
|
+
if (currentToken && validateWalletSession(currentToken, addressToUse).valid) {
|
|
1098
|
+
await refreshAIT();
|
|
1099
|
+
return true;
|
|
1078
1100
|
}
|
|
1079
1101
|
try {
|
|
1080
1102
|
const nonceResponse = await nxtlinqApi.auth.getNonce({ address: addressToUse });
|
|
@@ -1082,7 +1104,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1082
1104
|
if (autoShowSuccessMessage) {
|
|
1083
1105
|
showError(normalizeErrorForUser(nonceResponse.error));
|
|
1084
1106
|
}
|
|
1085
|
-
return;
|
|
1107
|
+
return false;
|
|
1086
1108
|
}
|
|
1087
1109
|
const payload = {
|
|
1088
1110
|
address: addressToUse,
|
|
@@ -1099,357 +1121,91 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1099
1121
|
if (autoShowSuccessMessage) {
|
|
1100
1122
|
showError(normalizeErrorForUser(response.error));
|
|
1101
1123
|
}
|
|
1102
|
-
return;
|
|
1124
|
+
return false;
|
|
1103
1125
|
}
|
|
1104
1126
|
const { accessToken } = response;
|
|
1105
1127
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1128
|
+
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1106
1129
|
// Wait for state to update before refreshing AIT
|
|
1107
1130
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1108
1131
|
await refreshAIT();
|
|
1109
1132
|
if (autoShowSuccessMessage) {
|
|
1110
1133
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
1111
1134
|
}
|
|
1135
|
+
return true;
|
|
1112
1136
|
}
|
|
1113
1137
|
catch (error) {
|
|
1114
1138
|
console.error('Failed to sign in:', error);
|
|
1115
1139
|
if (autoShowSuccessMessage) {
|
|
1116
1140
|
showError('Failed to sign in. Please try again.');
|
|
1117
1141
|
}
|
|
1142
|
+
return false;
|
|
1118
1143
|
}
|
|
1119
1144
|
};
|
|
1120
1145
|
// Check permissions
|
|
1121
|
-
const hasPermission = async (requiredPermission
|
|
1146
|
+
const hasPermission = async (requiredPermission) => {
|
|
1122
1147
|
// Use refs to get latest state values
|
|
1123
1148
|
const currentHitAddress = hitAddressRef.current;
|
|
1124
|
-
const currentAit = aitRef.current;
|
|
1125
|
-
const currentPermissions = permissionsRef.current;
|
|
1126
1149
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
setIsAutoConnecting(false); // Clear auto-connecting state
|
|
1141
|
-
// Show brief success message for auto-connect
|
|
1142
|
-
showSuccess(walletTextUtils.getWalletText('Auto wallet connection successful', serviceId));
|
|
1143
|
-
onAutoConnect?.(); // Call callback if provided
|
|
1144
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1145
|
-
// After auto connect, if not signed in, then auto sign-in
|
|
1146
|
-
const tokenAfterConnect = nxtlinqAITServiceAccessTokenRef.current;
|
|
1147
|
-
if (!tokenAfterConnect) {
|
|
1148
|
-
setIsAutoConnecting(true);
|
|
1149
|
-
await signInWallet(false);
|
|
1150
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1151
|
-
setIsAutoConnecting(false);
|
|
1152
|
-
showSuccess(walletTextUtils.getWalletText('Auto sign-in successful after wallet connect', serviceId));
|
|
1153
|
-
await refreshAIT();
|
|
1154
|
-
// Wait for AIT to be fully loaded with polling
|
|
1155
|
-
let attempts = 0;
|
|
1156
|
-
const maxAttempts = 5;
|
|
1157
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1158
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1159
|
-
attempts++;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
// If connection (and sign-in if needed) successful, continue with permission check
|
|
1163
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1164
|
-
return result;
|
|
1165
|
-
}
|
|
1166
|
-
catch (error) {
|
|
1167
|
-
console.error('Failed to auto-connect wallet:', error);
|
|
1168
|
-
setIsAutoConnecting(false); // Clear auto-connecting state on error
|
|
1169
|
-
return false;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1173
|
-
return false;
|
|
1174
|
-
}
|
|
1175
|
-
if (!currentToken) {
|
|
1176
|
-
if (autoRetry) {
|
|
1177
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1178
|
-
setMessages(prev => [...prev, {
|
|
1179
|
-
id: Date.now().toString(),
|
|
1180
|
-
content: walletTextUtils.getWalletText('Please sign in with your HIT wallet to continue.', serviceId),
|
|
1181
|
-
role: 'assistant',
|
|
1182
|
-
timestamp: new Date().toISOString(),
|
|
1183
|
-
button: 'signIn'
|
|
1184
|
-
}]);
|
|
1185
|
-
try {
|
|
1186
|
-
setIsAutoConnecting(true); // Mark as auto-signing
|
|
1187
|
-
await signInWallet(false); // Don't show success message yet
|
|
1188
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1189
|
-
setIsAutoConnecting(false); // Clear auto-signing state
|
|
1190
|
-
// Show brief success message for auto-sign-in
|
|
1191
|
-
showSuccess('Auto sign-in successful');
|
|
1192
|
-
// Ensure AIT is refreshed after sign-in
|
|
1193
|
-
await refreshAIT();
|
|
1194
|
-
// Wait for AIT to be fully loaded with polling
|
|
1195
|
-
let attempts = 0;
|
|
1196
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1197
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1198
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1199
|
-
attempts++;
|
|
1200
|
-
}
|
|
1201
|
-
// Only continue if AIT is actually loaded
|
|
1202
|
-
if (aitRef.current) {
|
|
1203
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1204
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1205
|
-
// If sign-in successful, continue with permission check
|
|
1206
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1207
|
-
return result;
|
|
1208
|
-
}
|
|
1209
|
-
else {
|
|
1210
|
-
return false;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
catch (error) {
|
|
1214
|
-
console.error('Failed to auto-sign-in wallet:', error);
|
|
1215
|
-
setIsAutoConnecting(false); // Clear auto-signing state on error
|
|
1216
|
-
return false;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1220
|
-
return false;
|
|
1221
|
-
}
|
|
1222
|
-
try {
|
|
1223
|
-
const payload = JSON.parse(atob(currentToken.split('.')[1]));
|
|
1224
|
-
const address = payload.address;
|
|
1225
|
-
if (address !== currentHitAddress) {
|
|
1226
|
-
setNxtlinqAITServiceAccessToken('');
|
|
1227
|
-
if (autoRetry) {
|
|
1228
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1229
|
-
setMessages(prev => [...prev, {
|
|
1230
|
-
id: Date.now().toString(),
|
|
1231
|
-
content: walletTextUtils.getWalletText('Wallet address mismatch. Please sign in with the correct wallet.', serviceId),
|
|
1232
|
-
role: 'assistant',
|
|
1233
|
-
timestamp: new Date().toISOString(),
|
|
1234
|
-
button: 'signIn'
|
|
1235
|
-
}]);
|
|
1236
|
-
try {
|
|
1237
|
-
setIsAutoConnecting(true); // Mark as auto-signing
|
|
1238
|
-
await signInWallet(false); // Don't show success message yet
|
|
1239
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1240
|
-
setIsAutoConnecting(false); // Clear auto-signing state
|
|
1241
|
-
// Show brief success message for auto-sign-in after address mismatch
|
|
1242
|
-
showSuccess('Auto sign-in successful after address mismatch');
|
|
1243
|
-
// Ensure AIT is refreshed after sign-in
|
|
1244
|
-
await refreshAIT();
|
|
1245
|
-
// Wait for AIT to be fully loaded with polling
|
|
1246
|
-
let attempts = 0;
|
|
1247
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1248
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1249
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1250
|
-
attempts++;
|
|
1251
|
-
}
|
|
1252
|
-
// Only continue if AIT is actually loaded
|
|
1253
|
-
if (aitRef.current) {
|
|
1254
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1255
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1256
|
-
// If sign-in successful, continue with permission check
|
|
1257
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1258
|
-
return result;
|
|
1259
|
-
}
|
|
1260
|
-
else {
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
catch (error) {
|
|
1265
|
-
console.error('Failed to auto-sign-in after address mismatch:', error);
|
|
1266
|
-
setIsAutoConnecting(false); // Clear auto-signing state on error
|
|
1267
|
-
return false;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1271
|
-
return false;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
catch (error) {
|
|
1275
|
-
console.error('Error parsing token:', error);
|
|
1276
|
-
setNxtlinqAITServiceAccessToken('');
|
|
1277
|
-
if (autoRetry) {
|
|
1278
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1279
|
-
setMessages(prev => [...prev, {
|
|
1280
|
-
id: Date.now().toString(),
|
|
1281
|
-
content: walletTextUtils.getWalletText('Invalid wallet session. Please sign in again.', serviceId),
|
|
1282
|
-
role: 'assistant',
|
|
1283
|
-
timestamp: new Date().toISOString(),
|
|
1284
|
-
button: 'signIn'
|
|
1285
|
-
}]);
|
|
1286
|
-
try {
|
|
1287
|
-
setIsAutoConnecting(true); // Mark as auto-signing
|
|
1288
|
-
await signInWallet(false); // Don't show success message yet
|
|
1289
|
-
onAutoSignIn?.(); // Call callback if provided
|
|
1290
|
-
setIsAutoConnecting(false); // Clear auto-signing state
|
|
1291
|
-
// Show brief success message for auto-sign-in after token parse error
|
|
1292
|
-
showSuccess('Auto sign-in successful after token error');
|
|
1293
|
-
// Ensure AIT is refreshed after sign-in
|
|
1294
|
-
await refreshAIT();
|
|
1295
|
-
// Wait for AIT to be fully loaded with polling
|
|
1296
|
-
let attempts = 0;
|
|
1297
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1298
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1299
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1300
|
-
attempts++;
|
|
1301
|
-
}
|
|
1302
|
-
// Only continue if AIT is actually loaded
|
|
1303
|
-
if (aitRef.current) {
|
|
1304
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1305
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1306
|
-
// If sign-in successful, continue with permission check
|
|
1307
|
-
const result = await hasPermission(requiredPermission, false);
|
|
1308
|
-
return result;
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
return false;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
catch (signInError) {
|
|
1315
|
-
console.error('Failed to auto-sign-in after token parse error:', signInError);
|
|
1316
|
-
setIsAutoConnecting(false); // Clear auto-signing state on error
|
|
1317
|
-
return false;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
// If autoRetry is false, don't show message again, just return false
|
|
1321
|
-
return false;
|
|
1322
|
-
}
|
|
1323
|
-
if (!currentAit) {
|
|
1324
|
-
// Show loading message if AIT is still loading
|
|
1325
|
-
if (isAITLoading) {
|
|
1326
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1327
|
-
setMessages(prev => [...prev, {
|
|
1328
|
-
id: Date.now().toString(),
|
|
1329
|
-
content: walletTextUtils.getWalletText('Loading your wallet configuration... Please wait a moment.', serviceId),
|
|
1330
|
-
role: 'assistant',
|
|
1331
|
-
timestamp: new Date().toISOString()
|
|
1332
|
-
}]);
|
|
1333
|
-
return false;
|
|
1334
|
-
}
|
|
1335
|
-
// If AIT is not loaded but we have a token, try to refresh it once
|
|
1336
|
-
if (currentToken && !isAITLoading) {
|
|
1337
|
-
try {
|
|
1338
|
-
await refreshAIT();
|
|
1339
|
-
// Wait for AIT to be loaded with polling
|
|
1340
|
-
let attempts = 0;
|
|
1341
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1342
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1343
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1344
|
-
attempts++;
|
|
1345
|
-
}
|
|
1346
|
-
// Check again after refresh
|
|
1347
|
-
if (!aitRef.current) {
|
|
1348
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1349
|
-
setMessages(prev => [...prev, {
|
|
1350
|
-
id: Date.now().toString(),
|
|
1351
|
-
content: walletTextUtils.getWalletText('No AIT found for your wallet. Please click the settings button (⚙️) to configure your AIT permissions.', serviceId),
|
|
1352
|
-
role: 'assistant',
|
|
1353
|
-
timestamp: new Date().toISOString()
|
|
1354
|
-
}]);
|
|
1355
|
-
return false;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
catch (error) {
|
|
1359
|
-
console.error('Failed to refresh AIT during permission check:', error);
|
|
1360
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1361
|
-
setMessages(prev => [...prev, {
|
|
1362
|
-
id: Date.now().toString(),
|
|
1363
|
-
content: 'No AIT found for your wallet. Please click the settings button (⚙️) to configure your AIT permissions.',
|
|
1364
|
-
role: 'assistant',
|
|
1365
|
-
timestamp: new Date().toISOString()
|
|
1366
|
-
}]);
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
else {
|
|
1371
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1372
|
-
setMessages(prev => [...prev, {
|
|
1373
|
-
id: Date.now().toString(),
|
|
1374
|
-
content: 'No AIT found for your wallet. Please click the settings button (⚙️) to configure your AIT permissions.',
|
|
1375
|
-
role: 'assistant',
|
|
1376
|
-
timestamp: new Date().toISOString()
|
|
1377
|
-
}]);
|
|
1378
|
-
return false;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
if (availablePermissions.length === 0) {
|
|
1382
|
-
setIsLoading(false); // Stop thinking before showing message
|
|
1383
|
-
setMessages(prev => [...prev, {
|
|
1384
|
-
id: Date.now().toString(),
|
|
1385
|
-
content: `No permissions available for your current identity provider. Please check your service configuration or contact support. Service ID: ${serviceId}, Permission Group: ${permissionGroup || 'None'}`,
|
|
1386
|
-
role: 'assistant',
|
|
1387
|
-
timestamp: new Date().toISOString()
|
|
1388
|
-
}]);
|
|
1389
|
-
return false;
|
|
1390
|
-
}
|
|
1391
|
-
const checkToolPermissionResult = await nxtlinqApi.agent.checkToolPermission({
|
|
1150
|
+
const authorization = await authorizeTextFrontendTool({
|
|
1151
|
+
api: nxtlinqApi,
|
|
1152
|
+
serviceId,
|
|
1153
|
+
toolName: requiredPermission,
|
|
1154
|
+
snapshot: {
|
|
1155
|
+
walletAddress: currentHitAddress,
|
|
1156
|
+
walletToken: currentToken,
|
|
1157
|
+
// Legacy AIT issuance is unscoped (`externalId=''`). Do not impose a
|
|
1158
|
+
// wallet-address subject until issuance/migration supports it.
|
|
1159
|
+
externalId: undefined,
|
|
1160
|
+
requireWalletIDVVerification,
|
|
1161
|
+
loading: isAITLoading || isAutoConnecting,
|
|
1162
|
+
},
|
|
1392
1163
|
...authFields(),
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1164
|
+
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1165
|
+
? getFinalCustomUsername(customUsername)
|
|
1166
|
+
: undefined,
|
|
1396
1167
|
});
|
|
1397
|
-
if ('
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
const permissionMsg = `You don't have the required AIT permission: ${requiredPermission}. Would you like to enable AIT permission?`;
|
|
1439
|
-
setMessages(prev => [
|
|
1440
|
-
...prev,
|
|
1441
|
-
{
|
|
1442
|
-
id: Date.now().toString(),
|
|
1443
|
-
content: permissionMsg,
|
|
1444
|
-
role: 'assistant',
|
|
1445
|
-
timestamp: new Date().toISOString(),
|
|
1446
|
-
button: 'enableAIT',
|
|
1447
|
-
metadata: { requiredPermission }
|
|
1448
|
-
}
|
|
1449
|
-
]);
|
|
1450
|
-
return false;
|
|
1451
|
-
}
|
|
1452
|
-
return true;
|
|
1168
|
+
if (authorization.status === 'allowed')
|
|
1169
|
+
return true;
|
|
1170
|
+
setIsLoading(false);
|
|
1171
|
+
const recovery = {
|
|
1172
|
+
id: Date.now().toString(),
|
|
1173
|
+
content: 'Authorization is temporarily unavailable. Please try again.',
|
|
1174
|
+
role: 'assistant',
|
|
1175
|
+
timestamp: new Date().toISOString(),
|
|
1176
|
+
};
|
|
1177
|
+
switch (authorization.status) {
|
|
1178
|
+
case 'wallet_disconnected':
|
|
1179
|
+
recovery.content = walletTextUtils.getWalletText('Please connect your HIT wallet to continue.', serviceId);
|
|
1180
|
+
recovery.button = 'connectWallet';
|
|
1181
|
+
break;
|
|
1182
|
+
case 'wallet_signed_out':
|
|
1183
|
+
recovery.content = walletTextUtils.getWalletText('Please sign in with your HIT wallet to continue.', serviceId);
|
|
1184
|
+
recovery.button = 'signIn';
|
|
1185
|
+
break;
|
|
1186
|
+
case 'wallet_session_invalid':
|
|
1187
|
+
recovery.content = walletTextUtils.getWalletText('Invalid wallet session. Please sign in again.', serviceId);
|
|
1188
|
+
recovery.button = 'signIn';
|
|
1189
|
+
break;
|
|
1190
|
+
case 'wallet_verification_required':
|
|
1191
|
+
recovery.content = 'Please verify your wallet identity before using this tool.';
|
|
1192
|
+
recovery.button = 'verifyWallet';
|
|
1193
|
+
setIsPermissionFormOpen(true);
|
|
1194
|
+
setShowPermissionForm(true);
|
|
1195
|
+
break;
|
|
1196
|
+
case 'ait_not_found':
|
|
1197
|
+
recovery.content = 'No AIT found for your wallet. Please open settings to configure your AIT.';
|
|
1198
|
+
break;
|
|
1199
|
+
case 'permission_denied':
|
|
1200
|
+
recovery.content = `You don't have the required AIT permission: ${authorization.requiredPermission}. Would you like to enable AIT permission?`;
|
|
1201
|
+
recovery.button = 'enableAIT';
|
|
1202
|
+
recovery.metadata = { requiredPermission: authorization.requiredPermission };
|
|
1203
|
+
break;
|
|
1204
|
+
case 'authorization_unavailable':
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
setMessages(prev => [...prev, recovery]);
|
|
1208
|
+
return false;
|
|
1453
1209
|
};
|
|
1454
1210
|
// AI Model related functions
|
|
1455
1211
|
const handleModelChange = React.useCallback((modelIndex) => {
|
|
@@ -1488,9 +1244,18 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1488
1244
|
// Updated sendMessage function to support different AI models and attachments
|
|
1489
1245
|
const sendMessage = async (content, retryCount = 0, isPresetMessage = false, attachments, clientPipelineOverride) => {
|
|
1490
1246
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
1491
|
-
|
|
1247
|
+
// Recovery retries may run immediately after a completion handler calls
|
|
1248
|
+
// setIsLoading(false), before React has committed that state update.
|
|
1249
|
+
if (!hasContent || (retryCount === 0 && isLoading))
|
|
1492
1250
|
return;
|
|
1251
|
+
if (retryCount === 0) {
|
|
1252
|
+
// A new user turn supersedes any older interrupted tool request.
|
|
1253
|
+
pendingTextToolRetryRef.current.clear();
|
|
1254
|
+
}
|
|
1493
1255
|
setPendingAutoTts(null);
|
|
1256
|
+
const turnExternalId = typeof window !== 'undefined'
|
|
1257
|
+
? localStorage.getItem('walletAddress') || undefined
|
|
1258
|
+
: hitAddressRef.current || undefined;
|
|
1494
1259
|
const currentModel = getCurrentModel();
|
|
1495
1260
|
// Initialize with current model, will be updated with actual model from backend response
|
|
1496
1261
|
let actualModelUsed = currentModel.value;
|
|
@@ -1562,7 +1327,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1562
1327
|
model: currentModel.value,
|
|
1563
1328
|
...authFields(),
|
|
1564
1329
|
pseudoId: pseudoId,
|
|
1565
|
-
externalId:
|
|
1330
|
+
externalId: turnExternalId,
|
|
1566
1331
|
customUserInfo,
|
|
1567
1332
|
customUsername,
|
|
1568
1333
|
message: content || (attachments && attachments.length > 0 ? `Uploaded ${attachments.length} file(s)` : ''),
|
|
@@ -1776,84 +1541,20 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1776
1541
|
const toolUse = response.toolCall.toolUse;
|
|
1777
1542
|
let toolMsg = '';
|
|
1778
1543
|
if (onToolUse) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
// Added: Mark if permission denied due to missing AIT permission
|
|
1782
|
-
let permissionDenied = false;
|
|
1783
|
-
// Use requiredPermission from response if available, otherwise fall back to toolUse.name
|
|
1784
|
-
const permissionToCheck = response?.requiredPermission || toolUse.name;
|
|
1785
|
-
const isToolAllowed = await hasPermission(permissionToCheck, true, () => { wasAutoConnected = true; }, () => { wasAutoSignedIn = true; });
|
|
1786
|
-
// If currentPermissions does not include permissionToCheck and availablePermissionLabels includes permissionToCheck, it means AIT permission is missing
|
|
1787
|
-
if (!isToolAllowed && !permissions.includes(permissionToCheck) && availablePermissions.map(p => p.label).includes(permissionToCheck)) {
|
|
1788
|
-
permissionDenied = true;
|
|
1789
|
-
}
|
|
1544
|
+
const isToolAllowed = await hasPermission(toolUse.name);
|
|
1545
|
+
// Typed preflight already emitted the only permitted recovery UI.
|
|
1790
1546
|
if (!isToolAllowed) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1796
|
-
if (isSemiAutomaticMode) {
|
|
1797
|
-
setIsLoading(false);
|
|
1798
|
-
setMessages(prev => [...prev, {
|
|
1799
|
-
id: Date.now().toString(),
|
|
1800
|
-
content: 'Click button to continue using tool',
|
|
1801
|
-
role: 'assistant',
|
|
1802
|
-
timestamp: new Date().toISOString(),
|
|
1803
|
-
button: 'continue'
|
|
1804
|
-
}]);
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
else {
|
|
1808
|
-
// Only retry for auto-connect/auto-sign-in scenarios
|
|
1809
|
-
if (wasAutoConnected && retryCount < 1) {
|
|
1810
|
-
// Clear loading state and retry immediately
|
|
1811
|
-
setIsLoading(false);
|
|
1812
|
-
// Check if wallet is already signed in
|
|
1813
|
-
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1814
|
-
if (!currentToken) {
|
|
1815
|
-
// If not signed in, directly retry the message without waiting for AIT
|
|
1816
|
-
setTimeout(() => {
|
|
1817
|
-
sendMessage(content, retryCount + 1, isPresetMessage);
|
|
1818
|
-
}, 2000);
|
|
1819
|
-
}
|
|
1820
|
-
else {
|
|
1821
|
-
// If already signed in, wait for AIT to be fully loaded before retrying
|
|
1822
|
-
setTimeout(async () => {
|
|
1823
|
-
// Wait for AIT to be loaded if needed
|
|
1824
|
-
if (!aitRef.current && nxtlinqAITServiceAccessTokenRef.current) {
|
|
1825
|
-
await refreshAIT();
|
|
1826
|
-
}
|
|
1827
|
-
// Wait for AIT to be fully loaded with polling
|
|
1828
|
-
let attempts = 0;
|
|
1829
|
-
const maxAttempts = 5; // Wait up to 10 seconds (5 * 2000ms)
|
|
1830
|
-
while (!aitRef.current && attempts < maxAttempts) {
|
|
1831
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1832
|
-
attempts++;
|
|
1833
|
-
}
|
|
1834
|
-
// Only retry if AIT is actually loaded
|
|
1835
|
-
if (aitRef.current) {
|
|
1836
|
-
// Wait a bit more to ensure permissions are also loaded
|
|
1837
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
1838
|
-
sendMessage(content, retryCount + 1, isPresetMessage);
|
|
1839
|
-
}
|
|
1840
|
-
}, 2000);
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
if (isSemiAutomaticMode && wasAutoSignedIn) {
|
|
1547
|
+
pendingTextToolRetryRef.current.set({
|
|
1548
|
+
content,
|
|
1549
|
+
isPresetMessage,
|
|
1550
|
+
attachments,
|
|
1551
|
+
});
|
|
1847
1552
|
setIsLoading(false);
|
|
1848
|
-
setMessages(prev => [...prev, {
|
|
1849
|
-
id: Date.now().toString(),
|
|
1850
|
-
content: 'Click button to continue using tool',
|
|
1851
|
-
role: 'assistant',
|
|
1852
|
-
timestamp: new Date().toISOString(),
|
|
1853
|
-
button: 'continue'
|
|
1854
|
-
}]);
|
|
1855
1553
|
return;
|
|
1856
1554
|
}
|
|
1555
|
+
// Authorization passed. Clear before invoking the host callback so
|
|
1556
|
+
// concurrent completion events cannot execute this tool twice.
|
|
1557
|
+
pendingTextToolRetryRef.current.clear();
|
|
1857
1558
|
// Create streaming message for tool execution
|
|
1858
1559
|
const streamingMessageId = `streaming-${Date.now()}`;
|
|
1859
1560
|
const streamingMessage = {
|
|
@@ -2146,6 +1847,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2146
1847
|
setIsLoading(false);
|
|
2147
1848
|
}
|
|
2148
1849
|
};
|
|
1850
|
+
sendMessageRef.current = sendMessage;
|
|
2149
1851
|
// Handle submit
|
|
2150
1852
|
const handleSubmit = async (e, attachments) => {
|
|
2151
1853
|
e.preventDefault();
|
|
@@ -2635,6 +2337,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2635
2337
|
setShowPermissionForm(false);
|
|
2636
2338
|
setIsPermissionFormOpen(false);
|
|
2637
2339
|
await refreshAIT(true);
|
|
2340
|
+
await retryPendingTextTool();
|
|
2638
2341
|
}
|
|
2639
2342
|
catch (error) {
|
|
2640
2343
|
console.error('Failed to generate AIT:', error);
|
|
@@ -2713,7 +2416,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2713
2416
|
}
|
|
2714
2417
|
setIsLoading(false);
|
|
2715
2418
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2716
|
-
refreshAIT();
|
|
2419
|
+
await refreshAIT();
|
|
2420
|
+
await retryPendingTextTool();
|
|
2717
2421
|
return;
|
|
2718
2422
|
}
|
|
2719
2423
|
showError(verifyWalletResponse.error);
|
|
@@ -2753,7 +2457,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2753
2457
|
}
|
|
2754
2458
|
setIsLoading(false);
|
|
2755
2459
|
showSuccess('Wallet verification completed successfully! Your wallet is now verified and ready to use.');
|
|
2756
|
-
refreshAIT();
|
|
2460
|
+
await refreshAIT();
|
|
2461
|
+
await retryPendingTextTool();
|
|
2757
2462
|
return;
|
|
2758
2463
|
}
|
|
2759
2464
|
catch (error) {
|
|
@@ -2793,6 +2498,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2793
2498
|
await refreshAIT();
|
|
2794
2499
|
setIsLoading(false);
|
|
2795
2500
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2501
|
+
await retryPendingTextTool();
|
|
2796
2502
|
return;
|
|
2797
2503
|
}
|
|
2798
2504
|
// Handle specific error messages
|
|
@@ -2809,6 +2515,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2809
2515
|
await refreshAIT();
|
|
2810
2516
|
setIsLoading(false);
|
|
2811
2517
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2518
|
+
await retryPendingTextTool();
|
|
2812
2519
|
return;
|
|
2813
2520
|
}
|
|
2814
2521
|
catch (error) {
|
|
@@ -2977,6 +2684,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2977
2684
|
// Functions
|
|
2978
2685
|
connectWallet,
|
|
2979
2686
|
signInWallet,
|
|
2687
|
+
retryPendingTextTool,
|
|
2980
2688
|
sendMessage,
|
|
2981
2689
|
handleSubmit,
|
|
2982
2690
|
handlePresetMessage,
|