@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.2 → 0.4.4
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/authorizationLoadingState.d.ts +9 -0
- package/dist/authorizationLoadingState.d.ts.map +1 -0
- package/dist/authorizationLoadingState.js +15 -0
- package/dist/context/ChatBotContext.d.ts.map +1 -1
- package/dist/context/ChatBotContext.js +96 -28
- package/dist/pendingTextToolRetry.d.ts +16 -0
- package/dist/pendingTextToolRetry.d.ts.map +1 -0
- package/dist/pendingTextToolRetry.js +30 -0
- package/dist/types/ChatBotTypes.d.ts +4 -3
- package/dist/types/ChatBotTypes.d.ts.map +1 -1
- package/dist/ui/MessageList.d.ts.map +1 -1
- package/dist/ui/MessageList.js +13 -9
- package/package.json +1 -1
- package/src/authorizationLoadingState.ts +21 -0
- package/src/context/ChatBotContext.tsx +108 -28
- package/src/pendingTextToolRetry.ts +39 -0
- package/src/types/ChatBotTypes.ts +5 -4
- package/src/ui/MessageList.tsx +11 -8
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class AuthorizationLoadingState {
|
|
2
|
+
private aitLoading;
|
|
3
|
+
private autoConnecting;
|
|
4
|
+
constructor(aitLoading?: boolean, autoConnecting?: boolean);
|
|
5
|
+
setAITLoading(value: boolean): void;
|
|
6
|
+
setAutoConnecting(value: boolean): void;
|
|
7
|
+
isLoading(): boolean;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=authorizationLoadingState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorizationLoadingState.d.ts","sourceRoot":"","sources":["../src/authorizationLoadingState.ts"],"names":[],"mappings":"AAAA,qBAAa,yBAAyB;IACpC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,cAAc,CAAU;gBAEpB,UAAU,UAAQ,EAAE,cAAc,UAAQ;IAKtD,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAInC,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvC,SAAS,IAAI,OAAO;CAGrB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class AuthorizationLoadingState {
|
|
2
|
+
constructor(aitLoading = false, autoConnecting = false) {
|
|
3
|
+
this.aitLoading = aitLoading;
|
|
4
|
+
this.autoConnecting = autoConnecting;
|
|
5
|
+
}
|
|
6
|
+
setAITLoading(value) {
|
|
7
|
+
this.aitLoading = value;
|
|
8
|
+
}
|
|
9
|
+
setAutoConnecting(value) {
|
|
10
|
+
this.autoConnecting = value;
|
|
11
|
+
}
|
|
12
|
+
isLoading() {
|
|
13
|
+
return this.aitLoading || this.autoConnecting;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA8B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAw+FlD,CAAC"}
|
|
@@ -5,7 +5,9 @@ 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, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
8
|
+
import { authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
9
|
+
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
10
|
+
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
9
11
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
10
12
|
const ChatBotContext = React.createContext(undefined);
|
|
11
13
|
export const useChatBot = () => {
|
|
@@ -144,6 +146,18 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
144
146
|
const permissionsRef = React.useRef(permissions);
|
|
145
147
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
146
148
|
const signerRef = React.useRef(signer);
|
|
149
|
+
const authorizationLoadingStateRef = React.useRef(new AuthorizationLoadingState(isAITLoading, isAutoConnecting));
|
|
150
|
+
const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
|
|
151
|
+
const sendMessageRef = React.useRef(async () => { });
|
|
152
|
+
const retryPendingTextTool = React.useCallback(() => {
|
|
153
|
+
const controller = pendingTextToolRetryRef.current;
|
|
154
|
+
return controller.retry(async (request) => {
|
|
155
|
+
// This attempt consumes the old request. A still-blocked preflight will
|
|
156
|
+
// record it again with the next required recovery step.
|
|
157
|
+
controller.clear();
|
|
158
|
+
await sendMessageRef.current(request.content, 1, request.isPresetMessage, request.attachments);
|
|
159
|
+
});
|
|
160
|
+
}, []);
|
|
147
161
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
148
162
|
const getFinalCustomUsername = React.useCallback((username) => {
|
|
149
163
|
if (!username)
|
|
@@ -471,6 +485,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
471
485
|
React.useEffect(() => {
|
|
472
486
|
permissionsRef.current = permissions;
|
|
473
487
|
}, [permissions]);
|
|
488
|
+
React.useEffect(() => {
|
|
489
|
+
authorizationLoadingStateRef.current.setAITLoading(isAITLoading);
|
|
490
|
+
}, [isAITLoading]);
|
|
491
|
+
React.useEffect(() => {
|
|
492
|
+
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
493
|
+
}, [isAutoConnecting]);
|
|
474
494
|
React.useEffect(() => {
|
|
475
495
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
476
496
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -860,20 +880,26 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
860
880
|
};
|
|
861
881
|
// Refresh AIT
|
|
862
882
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
863
|
-
|
|
883
|
+
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
884
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
885
|
+
if (!currentHitAddress) {
|
|
886
|
+
aitRef.current = null;
|
|
887
|
+
permissionsRef.current = [];
|
|
864
888
|
setAit(null);
|
|
865
889
|
setPermissions([]);
|
|
866
890
|
setWalletInfo(null);
|
|
891
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
867
892
|
setIsAITLoading(false);
|
|
868
893
|
return;
|
|
869
894
|
}
|
|
895
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
870
896
|
setIsAITLoading(true);
|
|
871
897
|
try {
|
|
872
898
|
let walletAllowsAITLookup = !requireWalletIDVVerification;
|
|
873
899
|
// Get wallet info first - always try to get wallet info if we have a token
|
|
874
|
-
if (
|
|
900
|
+
if (currentToken) {
|
|
875
901
|
try {
|
|
876
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address:
|
|
902
|
+
const walletResponse = await nxtlinqApi.wallet.getWallet({ address: currentHitAddress }, currentToken);
|
|
877
903
|
if (!('error' in walletResponse)) {
|
|
878
904
|
setWalletInfo(walletResponse);
|
|
879
905
|
walletAllowsAITLookup = !requireWalletIDVVerification
|
|
@@ -895,42 +921,55 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
895
921
|
}
|
|
896
922
|
// AIT existence is not identity verification. In strict mode, stop before
|
|
897
923
|
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
898
|
-
if (
|
|
924
|
+
if (currentToken && !walletAllowsAITLookup) {
|
|
925
|
+
aitRef.current = null;
|
|
926
|
+
permissionsRef.current = [];
|
|
899
927
|
setAit(null);
|
|
900
928
|
setPermissions([]);
|
|
901
929
|
return;
|
|
902
930
|
}
|
|
903
931
|
// Only try to fetch AIT if we have a token
|
|
904
|
-
if (
|
|
932
|
+
if (currentToken) {
|
|
905
933
|
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
906
934
|
serviceId,
|
|
907
|
-
controller:
|
|
935
|
+
controller: currentHitAddress,
|
|
908
936
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
909
|
-
},
|
|
937
|
+
}, currentToken);
|
|
910
938
|
if ('error' in response) {
|
|
911
939
|
console.error('Failed to fetch AIT:', response.error);
|
|
940
|
+
aitRef.current = null;
|
|
941
|
+
permissionsRef.current = [];
|
|
912
942
|
setAit(null);
|
|
913
943
|
setPermissions([]);
|
|
914
944
|
return;
|
|
915
945
|
}
|
|
946
|
+
aitRef.current = response;
|
|
916
947
|
setAit(response);
|
|
917
948
|
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
918
949
|
const newPermissions = response.metadata?.permissions || [];
|
|
950
|
+
permissionsRef.current = newPermissions;
|
|
919
951
|
setPermissions(newPermissions);
|
|
920
952
|
}
|
|
921
953
|
}
|
|
922
954
|
else {
|
|
923
955
|
// No token available, clear AIT data
|
|
956
|
+
aitRef.current = null;
|
|
957
|
+
permissionsRef.current = [];
|
|
924
958
|
setAit(null);
|
|
925
959
|
setPermissions([]);
|
|
926
960
|
}
|
|
927
961
|
}
|
|
928
962
|
catch (error) {
|
|
929
963
|
console.error('Failed to fetch AIT:', error);
|
|
964
|
+
aitRef.current = null;
|
|
965
|
+
permissionsRef.current = [];
|
|
930
966
|
setAit(null);
|
|
931
967
|
setPermissions([]);
|
|
932
968
|
}
|
|
933
969
|
finally {
|
|
970
|
+
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
971
|
+
// React may not commit setState until the immediate pending-tool retry.
|
|
972
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
934
973
|
setIsAITLoading(false);
|
|
935
974
|
}
|
|
936
975
|
};
|
|
@@ -940,17 +979,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
940
979
|
return false;
|
|
941
980
|
if (!nxtlinqAITServiceAccessToken)
|
|
942
981
|
return true;
|
|
943
|
-
|
|
944
|
-
const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
|
|
945
|
-
const address = payload.address;
|
|
946
|
-
if (address !== hitAddress)
|
|
947
|
-
return true;
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
catch (error) {
|
|
951
|
-
console.error('Error parsing token:', error);
|
|
952
|
-
return true;
|
|
953
|
-
}
|
|
982
|
+
return !validateWalletSession(nxtlinqAITServiceAccessToken, hitAddress).valid;
|
|
954
983
|
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
955
984
|
// Connect wallet
|
|
956
985
|
const connectWallet = React.useCallback(async (autoShowSignInMessage = true) => {
|
|
@@ -1080,14 +1109,19 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1080
1109
|
if (autoShowSuccessMessage) {
|
|
1081
1110
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1082
1111
|
}
|
|
1083
|
-
return;
|
|
1112
|
+
return false;
|
|
1084
1113
|
}
|
|
1085
1114
|
if (!signerToUse) {
|
|
1086
1115
|
console.log('No signer available, returning early');
|
|
1087
1116
|
if (autoShowSuccessMessage) {
|
|
1088
1117
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1089
1118
|
}
|
|
1090
|
-
return;
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1122
|
+
if (currentToken && validateWalletSession(currentToken, addressToUse).valid) {
|
|
1123
|
+
await refreshAIT();
|
|
1124
|
+
return true;
|
|
1091
1125
|
}
|
|
1092
1126
|
try {
|
|
1093
1127
|
const nonceResponse = await nxtlinqApi.auth.getNonce({ address: addressToUse });
|
|
@@ -1095,7 +1129,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1095
1129
|
if (autoShowSuccessMessage) {
|
|
1096
1130
|
showError(normalizeErrorForUser(nonceResponse.error));
|
|
1097
1131
|
}
|
|
1098
|
-
return;
|
|
1132
|
+
return false;
|
|
1099
1133
|
}
|
|
1100
1134
|
const payload = {
|
|
1101
1135
|
address: addressToUse,
|
|
@@ -1112,22 +1146,25 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1112
1146
|
if (autoShowSuccessMessage) {
|
|
1113
1147
|
showError(normalizeErrorForUser(response.error));
|
|
1114
1148
|
}
|
|
1115
|
-
return;
|
|
1149
|
+
return false;
|
|
1116
1150
|
}
|
|
1117
1151
|
const { accessToken } = response;
|
|
1118
1152
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1153
|
+
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1119
1154
|
// Wait for state to update before refreshing AIT
|
|
1120
1155
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1121
1156
|
await refreshAIT();
|
|
1122
1157
|
if (autoShowSuccessMessage) {
|
|
1123
1158
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
1124
1159
|
}
|
|
1160
|
+
return true;
|
|
1125
1161
|
}
|
|
1126
1162
|
catch (error) {
|
|
1127
1163
|
console.error('Failed to sign in:', error);
|
|
1128
1164
|
if (autoShowSuccessMessage) {
|
|
1129
1165
|
showError('Failed to sign in. Please try again.');
|
|
1130
1166
|
}
|
|
1167
|
+
return false;
|
|
1131
1168
|
}
|
|
1132
1169
|
};
|
|
1133
1170
|
// Check permissions
|
|
@@ -1146,7 +1183,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1146
1183
|
// wallet-address subject until issuance/migration supports it.
|
|
1147
1184
|
externalId: undefined,
|
|
1148
1185
|
requireWalletIDVVerification,
|
|
1149
|
-
|
|
1186
|
+
// Use synchronously maintained refs so a retry immediately following
|
|
1187
|
+
// refreshAIT() does not observe the previous React render's loading state.
|
|
1188
|
+
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1150
1189
|
},
|
|
1151
1190
|
...authFields(),
|
|
1152
1191
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
@@ -1178,6 +1217,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1178
1217
|
case 'wallet_verification_required':
|
|
1179
1218
|
recovery.content = 'Please verify your wallet identity before using this tool.';
|
|
1180
1219
|
recovery.button = 'verifyWallet';
|
|
1220
|
+
setIsPermissionFormOpen(true);
|
|
1221
|
+
setShowPermissionForm(true);
|
|
1181
1222
|
break;
|
|
1182
1223
|
case 'ait_not_found':
|
|
1183
1224
|
recovery.content = 'No AIT found for your wallet. Please open settings to configure your AIT.';
|
|
@@ -1230,8 +1271,14 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1230
1271
|
// Updated sendMessage function to support different AI models and attachments
|
|
1231
1272
|
const sendMessage = async (content, retryCount = 0, isPresetMessage = false, attachments, clientPipelineOverride) => {
|
|
1232
1273
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
1233
|
-
|
|
1274
|
+
// Recovery retries may run immediately after a completion handler calls
|
|
1275
|
+
// setIsLoading(false), before React has committed that state update.
|
|
1276
|
+
if (!hasContent || (retryCount === 0 && isLoading))
|
|
1234
1277
|
return;
|
|
1278
|
+
if (retryCount === 0) {
|
|
1279
|
+
// A new user turn supersedes any older interrupted tool request.
|
|
1280
|
+
pendingTextToolRetryRef.current.clear();
|
|
1281
|
+
}
|
|
1235
1282
|
setPendingAutoTts(null);
|
|
1236
1283
|
const turnExternalId = typeof window !== 'undefined'
|
|
1237
1284
|
? localStorage.getItem('walletAddress') || undefined
|
|
@@ -1524,9 +1571,17 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1524
1571
|
const isToolAllowed = await hasPermission(toolUse.name);
|
|
1525
1572
|
// Typed preflight already emitted the only permitted recovery UI.
|
|
1526
1573
|
if (!isToolAllowed) {
|
|
1574
|
+
pendingTextToolRetryRef.current.set({
|
|
1575
|
+
content,
|
|
1576
|
+
isPresetMessage,
|
|
1577
|
+
attachments,
|
|
1578
|
+
});
|
|
1527
1579
|
setIsLoading(false);
|
|
1528
1580
|
return;
|
|
1529
1581
|
}
|
|
1582
|
+
// Authorization passed. Clear before invoking the host callback so
|
|
1583
|
+
// concurrent completion events cannot execute this tool twice.
|
|
1584
|
+
pendingTextToolRetryRef.current.clear();
|
|
1530
1585
|
// Create streaming message for tool execution
|
|
1531
1586
|
const streamingMessageId = `streaming-${Date.now()}`;
|
|
1532
1587
|
const streamingMessage = {
|
|
@@ -1819,6 +1874,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1819
1874
|
setIsLoading(false);
|
|
1820
1875
|
}
|
|
1821
1876
|
};
|
|
1877
|
+
sendMessageRef.current = sendMessage;
|
|
1822
1878
|
// Handle submit
|
|
1823
1879
|
const handleSubmit = async (e, attachments) => {
|
|
1824
1880
|
e.preventDefault();
|
|
@@ -2194,8 +2250,11 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2194
2250
|
metadataHash,
|
|
2195
2251
|
metadataCid,
|
|
2196
2252
|
};
|
|
2253
|
+
const nextPermissions = newPermissions || permissions;
|
|
2254
|
+
aitRef.current = aitInfo;
|
|
2255
|
+
permissionsRef.current = nextPermissions;
|
|
2197
2256
|
setAit(aitInfo);
|
|
2198
|
-
setPermissions(
|
|
2257
|
+
setPermissions(nextPermissions);
|
|
2199
2258
|
};
|
|
2200
2259
|
// Auto enable AIT permission
|
|
2201
2260
|
const enableAIT = async (toolName) => {
|
|
@@ -2308,6 +2367,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2308
2367
|
setShowPermissionForm(false);
|
|
2309
2368
|
setIsPermissionFormOpen(false);
|
|
2310
2369
|
await refreshAIT(true);
|
|
2370
|
+
await retryPendingTextTool();
|
|
2311
2371
|
}
|
|
2312
2372
|
catch (error) {
|
|
2313
2373
|
console.error('Failed to generate AIT:', error);
|
|
@@ -2364,6 +2424,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2364
2424
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2365
2425
|
}, token);
|
|
2366
2426
|
if (!('error' in aitResponse)) {
|
|
2427
|
+
aitRef.current = aitResponse;
|
|
2367
2428
|
setAit(aitResponse);
|
|
2368
2429
|
}
|
|
2369
2430
|
}
|
|
@@ -2386,7 +2447,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2386
2447
|
}
|
|
2387
2448
|
setIsLoading(false);
|
|
2388
2449
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2389
|
-
refreshAIT();
|
|
2450
|
+
await refreshAIT();
|
|
2451
|
+
await retryPendingTextTool();
|
|
2390
2452
|
return;
|
|
2391
2453
|
}
|
|
2392
2454
|
showError(verifyWalletResponse.error);
|
|
@@ -2404,6 +2466,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2404
2466
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2405
2467
|
}, token);
|
|
2406
2468
|
if (!('error' in aitResponse)) {
|
|
2469
|
+
aitRef.current = aitResponse;
|
|
2407
2470
|
setAit(aitResponse);
|
|
2408
2471
|
}
|
|
2409
2472
|
}
|
|
@@ -2426,7 +2489,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2426
2489
|
}
|
|
2427
2490
|
setIsLoading(false);
|
|
2428
2491
|
showSuccess('Wallet verification completed successfully! Your wallet is now verified and ready to use.');
|
|
2429
|
-
refreshAIT();
|
|
2492
|
+
await refreshAIT();
|
|
2493
|
+
await retryPendingTextTool();
|
|
2430
2494
|
return;
|
|
2431
2495
|
}
|
|
2432
2496
|
catch (error) {
|
|
@@ -2466,6 +2530,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2466
2530
|
await refreshAIT();
|
|
2467
2531
|
setIsLoading(false);
|
|
2468
2532
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2533
|
+
await retryPendingTextTool();
|
|
2469
2534
|
return;
|
|
2470
2535
|
}
|
|
2471
2536
|
// Handle specific error messages
|
|
@@ -2482,6 +2547,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2482
2547
|
await refreshAIT();
|
|
2483
2548
|
setIsLoading(false);
|
|
2484
2549
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2550
|
+
await retryPendingTextTool();
|
|
2485
2551
|
return;
|
|
2486
2552
|
}
|
|
2487
2553
|
catch (error) {
|
|
@@ -2518,6 +2584,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2518
2584
|
// Set loading state when permission form opens
|
|
2519
2585
|
React.useEffect(() => {
|
|
2520
2586
|
if (isPermissionFormOpen && hitAddress) {
|
|
2587
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
2521
2588
|
setIsAITLoading(true);
|
|
2522
2589
|
}
|
|
2523
2590
|
}, [isPermissionFormOpen, hitAddress]);
|
|
@@ -2650,6 +2717,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2650
2717
|
// Functions
|
|
2651
2718
|
connectWallet,
|
|
2652
2719
|
signInWallet,
|
|
2720
|
+
retryPendingTextTool,
|
|
2653
2721
|
sendMessage,
|
|
2654
2722
|
handleSubmit,
|
|
2655
2723
|
handlePresetMessage,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Attachment } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
export type PendingTextToolRequest = {
|
|
3
|
+
content: string;
|
|
4
|
+
isPresetMessage: boolean;
|
|
5
|
+
attachments?: Attachment[];
|
|
6
|
+
};
|
|
7
|
+
/** Keeps one interrupted text-tool request and serializes all resume attempts. */
|
|
8
|
+
export declare class PendingTextToolRetryController {
|
|
9
|
+
private pending;
|
|
10
|
+
private inFlight;
|
|
11
|
+
set(request: PendingTextToolRequest): void;
|
|
12
|
+
clear(): void;
|
|
13
|
+
hasPending(): boolean;
|
|
14
|
+
retry(execute: (request: PendingTextToolRequest) => Promise<void>): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=pendingTextToolRetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pendingTextToolRetry.d.ts","sourceRoot":"","sources":["../src/pendingTextToolRetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAE/E,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B,CAAC;AAEF,kFAAkF;AAClF,qBAAa,8BAA8B;IACzC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,QAAQ,CAAiC;IAEjD,GAAG,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAI1C,KAAK,IAAI,IAAI;IAIb,UAAU,IAAI,OAAO;IAIrB,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAarF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Keeps one interrupted text-tool request and serializes all resume attempts. */
|
|
2
|
+
export class PendingTextToolRetryController {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.pending = null;
|
|
5
|
+
this.inFlight = null;
|
|
6
|
+
}
|
|
7
|
+
set(request) {
|
|
8
|
+
this.pending = request;
|
|
9
|
+
}
|
|
10
|
+
clear() {
|
|
11
|
+
this.pending = null;
|
|
12
|
+
}
|
|
13
|
+
hasPending() {
|
|
14
|
+
return this.pending !== null;
|
|
15
|
+
}
|
|
16
|
+
retry(execute) {
|
|
17
|
+
if (this.inFlight)
|
|
18
|
+
return this.inFlight;
|
|
19
|
+
if (!this.pending)
|
|
20
|
+
return Promise.resolve(false);
|
|
21
|
+
const request = this.pending;
|
|
22
|
+
this.inFlight = (async () => {
|
|
23
|
+
await execute(request);
|
|
24
|
+
return true;
|
|
25
|
+
})().finally(() => {
|
|
26
|
+
this.inFlight = null;
|
|
27
|
+
});
|
|
28
|
+
return this.inFlight;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -134,8 +134,9 @@ export interface ChatBotContextType {
|
|
|
134
134
|
setAutoSendEnabled: (enabled: boolean) => void;
|
|
135
135
|
setTextToSpeechEnabled: (enabled: boolean) => void;
|
|
136
136
|
connectWallet: (autoShowSignInMessage?: boolean) => Promise<string | false | undefined>;
|
|
137
|
-
signInWallet: (autoShowSuccessMessage?: boolean) => Promise<
|
|
138
|
-
|
|
137
|
+
signInWallet: (autoShowSuccessMessage?: boolean) => Promise<boolean>;
|
|
138
|
+
retryPendingTextTool: () => Promise<boolean>;
|
|
139
|
+
sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
|
|
139
140
|
handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
|
|
140
141
|
handlePresetMessage: (message: PresetMessage) => void;
|
|
141
142
|
savePermissions: (newPermissions?: string[]) => Promise<void>;
|
|
@@ -165,7 +166,7 @@ export interface ChatBotContextType {
|
|
|
165
166
|
interruptVoice: () => void;
|
|
166
167
|
onSave: (newPermissions?: string[]) => Promise<void>;
|
|
167
168
|
onConnectWallet: () => Promise<string | false | undefined>;
|
|
168
|
-
onSignIn: () => Promise<
|
|
169
|
+
onSignIn: () => Promise<boolean>;
|
|
169
170
|
isNeedSignInWithWallet: boolean;
|
|
170
171
|
onVerifyWallet: (method: 'berifyme' | 'custom') => Promise<void>;
|
|
171
172
|
serviceId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatBotTypes.d.ts","sourceRoot":"","sources":["../../src/types/ChatBotTypes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ChatBotTypes.d.ts","sourceRoot":"","sources":["../../src/types/ChatBotTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuT/B,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuT/B,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EA4c/B,CAAC"}
|
package/dist/ui/MessageList.js
CHANGED
|
@@ -231,7 +231,7 @@ const PiiStatusHeader = React.memo(({ message }) => {
|
|
|
231
231
|
return (_jsxs("div", { css: piiHeaderRow, children: [_jsx(ShieldIcon, {}), _jsx("span", { children: "\u2713 Protected" })] }));
|
|
232
232
|
});
|
|
233
233
|
export const MessageList = () => {
|
|
234
|
-
const { messages, isLoading, isTtsProcessing, requiresGesture, retryTtsWithGesture, connectWallet, signInWallet, hitAddress, isAutoConnecting, isNeedSignInWithWallet, enableAIT, isAITLoading, isAITEnabling, sendMessage, permissions, availableModels, serviceId, piiDisplayMode, setShowPermissionForm, setIsPermissionFormOpen, } = useChatBot();
|
|
234
|
+
const { messages, isLoading, isTtsProcessing, requiresGesture, retryTtsWithGesture, connectWallet, signInWallet, retryPendingTextTool, hitAddress, isAutoConnecting, isNeedSignInWithWallet, enableAIT, isAITLoading, isAITEnabling, sendMessage, permissions, availableModels, serviceId, piiDisplayMode, setShowPermissionForm, setIsPermissionFormOpen, } = useChatBot();
|
|
235
235
|
const messagesEndRef = React.useRef(null);
|
|
236
236
|
const scrollToBottom = () => {
|
|
237
237
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
@@ -247,22 +247,26 @@ export const MessageList = () => {
|
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
if (buttonType === 'connectWallet') {
|
|
250
|
-
connectWallet(
|
|
250
|
+
const connected = await connectWallet(false);
|
|
251
|
+
if (connected) {
|
|
252
|
+
// Continue the interrupted flow immediately. This opens the wallet
|
|
253
|
+
// signature UI instead of leaving the user on a stale connect prompt.
|
|
254
|
+
const signedIn = await signInWallet(true);
|
|
255
|
+
if (signedIn)
|
|
256
|
+
await retryPendingTextTool();
|
|
257
|
+
}
|
|
251
258
|
}
|
|
252
259
|
else if (buttonType === 'signIn') {
|
|
253
|
-
signInWallet(true);
|
|
260
|
+
const signedIn = await signInWallet(true);
|
|
261
|
+
if (signedIn)
|
|
262
|
+
await retryPendingTextTool();
|
|
254
263
|
}
|
|
255
264
|
else if (buttonType === 'enableAIT') {
|
|
256
265
|
const requiredPermission = message.metadata?.requiredPermission;
|
|
257
266
|
if (requiredPermission) {
|
|
258
267
|
const success = await enableAIT(requiredPermission);
|
|
259
268
|
if (success) {
|
|
260
|
-
|
|
261
|
-
const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
|
|
262
|
-
if (lastUserMsg && lastUserMsg.content) {
|
|
263
|
-
// Re-execute the previous user message (retryCount=1 to skip duplicate PII log)
|
|
264
|
-
await sendMessage(lastUserMsg.content, 1);
|
|
265
|
-
}
|
|
269
|
+
await retryPendingTextTool();
|
|
266
270
|
}
|
|
267
271
|
}
|
|
268
272
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class AuthorizationLoadingState {
|
|
2
|
+
private aitLoading: boolean;
|
|
3
|
+
private autoConnecting: boolean;
|
|
4
|
+
|
|
5
|
+
constructor(aitLoading = false, autoConnecting = false) {
|
|
6
|
+
this.aitLoading = aitLoading;
|
|
7
|
+
this.autoConnecting = autoConnecting;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
setAITLoading(value: boolean): void {
|
|
11
|
+
this.aitLoading = value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setAutoConnecting(value: boolean): void {
|
|
15
|
+
this.autoConnecting = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
isLoading(): boolean {
|
|
19
|
+
return this.aitLoading || this.autoConnecting;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
import {
|
|
29
29
|
authorizeTextFrontendTool,
|
|
30
30
|
hasRecordedWalletVerification,
|
|
31
|
+
validateWalletSession,
|
|
31
32
|
} from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
32
33
|
import {
|
|
33
34
|
AIModel,
|
|
@@ -35,6 +36,8 @@ import {
|
|
|
35
36
|
ChatBotProps,
|
|
36
37
|
PresetMessage
|
|
37
38
|
} from '../types/ChatBotTypes';
|
|
39
|
+
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
40
|
+
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
38
41
|
|
|
39
42
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
40
43
|
|
|
@@ -216,6 +219,25 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
216
219
|
const permissionsRef = React.useRef(permissions);
|
|
217
220
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
218
221
|
const signerRef = React.useRef(signer);
|
|
222
|
+
const authorizationLoadingStateRef = React.useRef(
|
|
223
|
+
new AuthorizationLoadingState(isAITLoading, isAutoConnecting),
|
|
224
|
+
);
|
|
225
|
+
const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
|
|
226
|
+
const sendMessageRef = React.useRef<ChatBotContextType['sendMessage']>(async () => {});
|
|
227
|
+
const retryPendingTextTool = React.useCallback(() => {
|
|
228
|
+
const controller = pendingTextToolRetryRef.current;
|
|
229
|
+
return controller.retry(async (request) => {
|
|
230
|
+
// This attempt consumes the old request. A still-blocked preflight will
|
|
231
|
+
// record it again with the next required recovery step.
|
|
232
|
+
controller.clear();
|
|
233
|
+
await sendMessageRef.current(
|
|
234
|
+
request.content,
|
|
235
|
+
1,
|
|
236
|
+
request.isPresetMessage,
|
|
237
|
+
request.attachments,
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
}, []);
|
|
219
241
|
|
|
220
242
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
221
243
|
const getFinalCustomUsername = React.useCallback((username: string | undefined): string | undefined => {
|
|
@@ -560,6 +582,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
560
582
|
permissionsRef.current = permissions;
|
|
561
583
|
}, [permissions]);
|
|
562
584
|
|
|
585
|
+
React.useEffect(() => {
|
|
586
|
+
authorizationLoadingStateRef.current.setAITLoading(isAITLoading);
|
|
587
|
+
}, [isAITLoading]);
|
|
588
|
+
|
|
589
|
+
React.useEffect(() => {
|
|
590
|
+
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
591
|
+
}, [isAutoConnecting]);
|
|
592
|
+
|
|
563
593
|
React.useEffect(() => {
|
|
564
594
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
565
595
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -1000,22 +1030,28 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1000
1030
|
|
|
1001
1031
|
// Refresh AIT
|
|
1002
1032
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
1003
|
-
|
|
1033
|
+
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1034
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
1035
|
+
if (!currentHitAddress) {
|
|
1036
|
+
aitRef.current = null;
|
|
1037
|
+
permissionsRef.current = [];
|
|
1004
1038
|
setAit(null);
|
|
1005
1039
|
setPermissions([]);
|
|
1006
1040
|
setWalletInfo(null);
|
|
1041
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1007
1042
|
setIsAITLoading(false);
|
|
1008
1043
|
return;
|
|
1009
1044
|
}
|
|
1010
1045
|
|
|
1046
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
1011
1047
|
setIsAITLoading(true);
|
|
1012
1048
|
try {
|
|
1013
1049
|
let walletAllowsAITLookup = !requireWalletIDVVerification;
|
|
1014
1050
|
|
|
1015
1051
|
// Get wallet info first - always try to get wallet info if we have a token
|
|
1016
|
-
if (
|
|
1052
|
+
if (currentToken) {
|
|
1017
1053
|
try {
|
|
1018
|
-
const walletResponse = await nxtlinqApi.wallet.getWallet({ address:
|
|
1054
|
+
const walletResponse = await nxtlinqApi.wallet.getWallet({ address: currentHitAddress }, currentToken);
|
|
1019
1055
|
if (!('error' in walletResponse)) {
|
|
1020
1056
|
setWalletInfo(walletResponse);
|
|
1021
1057
|
walletAllowsAITLookup = !requireWalletIDVVerification
|
|
@@ -1036,42 +1072,55 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1036
1072
|
|
|
1037
1073
|
// AIT existence is not identity verification. In strict mode, stop before
|
|
1038
1074
|
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
1039
|
-
if (
|
|
1075
|
+
if (currentToken && !walletAllowsAITLookup) {
|
|
1076
|
+
aitRef.current = null;
|
|
1077
|
+
permissionsRef.current = [];
|
|
1040
1078
|
setAit(null);
|
|
1041
1079
|
setPermissions([]);
|
|
1042
1080
|
return;
|
|
1043
1081
|
}
|
|
1044
1082
|
|
|
1045
1083
|
// Only try to fetch AIT if we have a token
|
|
1046
|
-
if (
|
|
1084
|
+
if (currentToken) {
|
|
1047
1085
|
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
1048
1086
|
serviceId,
|
|
1049
|
-
controller:
|
|
1087
|
+
controller: currentHitAddress,
|
|
1050
1088
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
1051
|
-
},
|
|
1089
|
+
}, currentToken);
|
|
1052
1090
|
|
|
1053
1091
|
if ('error' in response) {
|
|
1054
1092
|
console.error('Failed to fetch AIT:', response.error);
|
|
1093
|
+
aitRef.current = null;
|
|
1094
|
+
permissionsRef.current = [];
|
|
1055
1095
|
setAit(null);
|
|
1056
1096
|
setPermissions([]);
|
|
1057
1097
|
return;
|
|
1058
1098
|
}
|
|
1059
1099
|
|
|
1100
|
+
aitRef.current = response;
|
|
1060
1101
|
setAit(response);
|
|
1061
1102
|
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
1062
1103
|
const newPermissions = response.metadata?.permissions || [];
|
|
1104
|
+
permissionsRef.current = newPermissions;
|
|
1063
1105
|
setPermissions(newPermissions);
|
|
1064
1106
|
}
|
|
1065
1107
|
} else {
|
|
1066
1108
|
// No token available, clear AIT data
|
|
1109
|
+
aitRef.current = null;
|
|
1110
|
+
permissionsRef.current = [];
|
|
1067
1111
|
setAit(null);
|
|
1068
1112
|
setPermissions([]);
|
|
1069
1113
|
}
|
|
1070
1114
|
} catch (error) {
|
|
1071
1115
|
console.error('Failed to fetch AIT:', error);
|
|
1116
|
+
aitRef.current = null;
|
|
1117
|
+
permissionsRef.current = [];
|
|
1072
1118
|
setAit(null);
|
|
1073
1119
|
setPermissions([]);
|
|
1074
1120
|
} finally {
|
|
1121
|
+
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
1122
|
+
// React may not commit setState until the immediate pending-tool retry.
|
|
1123
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1075
1124
|
setIsAITLoading(false);
|
|
1076
1125
|
}
|
|
1077
1126
|
};
|
|
@@ -1080,17 +1129,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1080
1129
|
const isNeedSignInWithWallet = React.useMemo(() => {
|
|
1081
1130
|
if (!hitAddress) return false;
|
|
1082
1131
|
if (!nxtlinqAITServiceAccessToken) return true;
|
|
1083
|
-
|
|
1084
|
-
try {
|
|
1085
|
-
const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
|
|
1086
|
-
const address = payload.address;
|
|
1087
|
-
if (address !== hitAddress) return true;
|
|
1088
|
-
|
|
1089
|
-
return false;
|
|
1090
|
-
} catch (error) {
|
|
1091
|
-
console.error('Error parsing token:', error);
|
|
1092
|
-
return true;
|
|
1093
|
-
}
|
|
1132
|
+
return !validateWalletSession(nxtlinqAITServiceAccessToken, hitAddress).valid;
|
|
1094
1133
|
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
1095
1134
|
|
|
1096
1135
|
// Connect wallet
|
|
@@ -1200,7 +1239,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1200
1239
|
}
|
|
1201
1240
|
}, [nxtlinqAITServiceAccessToken, refreshAIT, isNeedSignInWithWallet, requireWalletIDVVerification, customUsername, nxtlinqApi]);
|
|
1202
1241
|
|
|
1203
|
-
const signInWallet = async (autoShowSuccessMessage = true) => {
|
|
1242
|
+
const signInWallet = async (autoShowSuccessMessage = true): Promise<boolean> => {
|
|
1204
1243
|
// Use refs to get latest state values for consistency with hasPermission
|
|
1205
1244
|
const currentHitAddress = hitAddressRef.current;
|
|
1206
1245
|
const currentSigner = signerRef.current;
|
|
@@ -1233,7 +1272,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1233
1272
|
if (autoShowSuccessMessage) {
|
|
1234
1273
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1235
1274
|
}
|
|
1236
|
-
return;
|
|
1275
|
+
return false;
|
|
1237
1276
|
}
|
|
1238
1277
|
|
|
1239
1278
|
if (!signerToUse) {
|
|
@@ -1241,7 +1280,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1241
1280
|
if (autoShowSuccessMessage) {
|
|
1242
1281
|
showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
|
|
1243
1282
|
}
|
|
1244
|
-
return;
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current;
|
|
1287
|
+
if (currentToken && validateWalletSession(currentToken, addressToUse).valid) {
|
|
1288
|
+
await refreshAIT();
|
|
1289
|
+
return true;
|
|
1245
1290
|
}
|
|
1246
1291
|
|
|
1247
1292
|
try {
|
|
@@ -1250,7 +1295,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1250
1295
|
if (autoShowSuccessMessage) {
|
|
1251
1296
|
showError(normalizeErrorForUser(nonceResponse.error));
|
|
1252
1297
|
}
|
|
1253
|
-
return;
|
|
1298
|
+
return false;
|
|
1254
1299
|
}
|
|
1255
1300
|
|
|
1256
1301
|
const payload = {
|
|
@@ -1271,10 +1316,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1271
1316
|
if (autoShowSuccessMessage) {
|
|
1272
1317
|
showError(normalizeErrorForUser(response.error));
|
|
1273
1318
|
}
|
|
1274
|
-
return;
|
|
1319
|
+
return false;
|
|
1275
1320
|
}
|
|
1276
1321
|
const { accessToken } = response;
|
|
1277
1322
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1323
|
+
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1278
1324
|
|
|
1279
1325
|
// Wait for state to update before refreshing AIT
|
|
1280
1326
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -1283,11 +1329,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1283
1329
|
if (autoShowSuccessMessage) {
|
|
1284
1330
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
1285
1331
|
}
|
|
1332
|
+
return true;
|
|
1286
1333
|
} catch (error) {
|
|
1287
1334
|
console.error('Failed to sign in:', error);
|
|
1288
1335
|
if (autoShowSuccessMessage) {
|
|
1289
1336
|
showError('Failed to sign in. Please try again.');
|
|
1290
1337
|
}
|
|
1338
|
+
return false;
|
|
1291
1339
|
}
|
|
1292
1340
|
};
|
|
1293
1341
|
|
|
@@ -1310,7 +1358,9 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1310
1358
|
// wallet-address subject until issuance/migration supports it.
|
|
1311
1359
|
externalId: undefined,
|
|
1312
1360
|
requireWalletIDVVerification,
|
|
1313
|
-
|
|
1361
|
+
// Use synchronously maintained refs so a retry immediately following
|
|
1362
|
+
// refreshAIT() does not observe the previous React render's loading state.
|
|
1363
|
+
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1314
1364
|
},
|
|
1315
1365
|
...authFields(),
|
|
1316
1366
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
@@ -1342,6 +1392,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1342
1392
|
case 'wallet_verification_required':
|
|
1343
1393
|
recovery.content = 'Please verify your wallet identity before using this tool.';
|
|
1344
1394
|
recovery.button = 'verifyWallet';
|
|
1395
|
+
setIsPermissionFormOpen(true);
|
|
1396
|
+
setShowPermissionForm(true);
|
|
1345
1397
|
break;
|
|
1346
1398
|
case 'ait_not_found':
|
|
1347
1399
|
recovery.content = 'No AIT found for your wallet. Please open settings to configure your AIT.';
|
|
@@ -1409,7 +1461,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1409
1461
|
clientPipelineOverride?: Array<{ name: string; durationMs: number }>
|
|
1410
1462
|
): Promise<void> => {
|
|
1411
1463
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
1412
|
-
|
|
1464
|
+
// Recovery retries may run immediately after a completion handler calls
|
|
1465
|
+
// setIsLoading(false), before React has committed that state update.
|
|
1466
|
+
if (!hasContent || (retryCount === 0 && isLoading)) return;
|
|
1467
|
+
if (retryCount === 0) {
|
|
1468
|
+
// A new user turn supersedes any older interrupted tool request.
|
|
1469
|
+
pendingTextToolRetryRef.current.clear();
|
|
1470
|
+
}
|
|
1413
1471
|
setPendingAutoTts(null);
|
|
1414
1472
|
const turnExternalId = typeof window !== 'undefined'
|
|
1415
1473
|
? localStorage.getItem('walletAddress') || undefined
|
|
@@ -1728,10 +1786,19 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1728
1786
|
|
|
1729
1787
|
// Typed preflight already emitted the only permitted recovery UI.
|
|
1730
1788
|
if (!isToolAllowed) {
|
|
1789
|
+
pendingTextToolRetryRef.current.set({
|
|
1790
|
+
content,
|
|
1791
|
+
isPresetMessage,
|
|
1792
|
+
attachments,
|
|
1793
|
+
});
|
|
1731
1794
|
setIsLoading(false);
|
|
1732
1795
|
return;
|
|
1733
1796
|
}
|
|
1734
1797
|
|
|
1798
|
+
// Authorization passed. Clear before invoking the host callback so
|
|
1799
|
+
// concurrent completion events cannot execute this tool twice.
|
|
1800
|
+
pendingTextToolRetryRef.current.clear();
|
|
1801
|
+
|
|
1735
1802
|
// Create streaming message for tool execution
|
|
1736
1803
|
const streamingMessageId = `streaming-${Date.now()}`;
|
|
1737
1804
|
const streamingMessage: Message = {
|
|
@@ -2041,6 +2108,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2041
2108
|
setIsLoading(false);
|
|
2042
2109
|
}
|
|
2043
2110
|
};
|
|
2111
|
+
sendMessageRef.current = sendMessage;
|
|
2044
2112
|
|
|
2045
2113
|
// Handle submit
|
|
2046
2114
|
const handleSubmit = async (e: React.FormEvent, attachments?: Attachment[]) => {
|
|
@@ -2465,8 +2533,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2465
2533
|
metadataCid,
|
|
2466
2534
|
};
|
|
2467
2535
|
|
|
2536
|
+
const nextPermissions = newPermissions || permissions;
|
|
2537
|
+
aitRef.current = aitInfo;
|
|
2538
|
+
permissionsRef.current = nextPermissions;
|
|
2468
2539
|
setAit(aitInfo);
|
|
2469
|
-
setPermissions(
|
|
2540
|
+
setPermissions(nextPermissions);
|
|
2470
2541
|
};
|
|
2471
2542
|
|
|
2472
2543
|
|
|
@@ -2595,6 +2666,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2595
2666
|
setShowPermissionForm(false);
|
|
2596
2667
|
setIsPermissionFormOpen(false);
|
|
2597
2668
|
await refreshAIT(true);
|
|
2669
|
+
await retryPendingTextTool();
|
|
2598
2670
|
} catch (error) {
|
|
2599
2671
|
console.error('Failed to generate AIT:', error);
|
|
2600
2672
|
setIsDisabled(false);
|
|
@@ -2653,6 +2725,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2653
2725
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2654
2726
|
}, token);
|
|
2655
2727
|
if (!('error' in aitResponse)) {
|
|
2728
|
+
aitRef.current = aitResponse;
|
|
2656
2729
|
setAit(aitResponse);
|
|
2657
2730
|
}
|
|
2658
2731
|
}
|
|
@@ -2673,7 +2746,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2673
2746
|
}
|
|
2674
2747
|
setIsLoading(false);
|
|
2675
2748
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2676
|
-
refreshAIT();
|
|
2749
|
+
await refreshAIT();
|
|
2750
|
+
await retryPendingTextTool();
|
|
2677
2751
|
return;
|
|
2678
2752
|
}
|
|
2679
2753
|
showError(verifyWalletResponse.error);
|
|
@@ -2691,6 +2765,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2691
2765
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2692
2766
|
}, token);
|
|
2693
2767
|
if (!('error' in aitResponse)) {
|
|
2768
|
+
aitRef.current = aitResponse;
|
|
2694
2769
|
setAit(aitResponse);
|
|
2695
2770
|
}
|
|
2696
2771
|
}
|
|
@@ -2711,7 +2786,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2711
2786
|
}
|
|
2712
2787
|
setIsLoading(false);
|
|
2713
2788
|
showSuccess('Wallet verification completed successfully! Your wallet is now verified and ready to use.');
|
|
2714
|
-
refreshAIT();
|
|
2789
|
+
await refreshAIT();
|
|
2790
|
+
await retryPendingTextTool();
|
|
2715
2791
|
return;
|
|
2716
2792
|
} catch (error) {
|
|
2717
2793
|
let msg = 'Verification failed';
|
|
@@ -2751,6 +2827,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2751
2827
|
await refreshAIT();
|
|
2752
2828
|
setIsLoading(false);
|
|
2753
2829
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2830
|
+
await retryPendingTextTool();
|
|
2754
2831
|
return;
|
|
2755
2832
|
}
|
|
2756
2833
|
// Handle specific error messages
|
|
@@ -2766,6 +2843,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2766
2843
|
await refreshAIT();
|
|
2767
2844
|
setIsLoading(false);
|
|
2768
2845
|
showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
|
|
2846
|
+
await retryPendingTextTool();
|
|
2769
2847
|
return;
|
|
2770
2848
|
} catch (error) {
|
|
2771
2849
|
console.error('Custom wallet verification failed:', error);
|
|
@@ -2804,6 +2882,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2804
2882
|
// Set loading state when permission form opens
|
|
2805
2883
|
React.useEffect(() => {
|
|
2806
2884
|
if (isPermissionFormOpen && hitAddress) {
|
|
2885
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
2807
2886
|
setIsAITLoading(true);
|
|
2808
2887
|
}
|
|
2809
2888
|
}, [isPermissionFormOpen, hitAddress]);
|
|
@@ -2946,6 +3025,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2946
3025
|
// Functions
|
|
2947
3026
|
connectWallet,
|
|
2948
3027
|
signInWallet,
|
|
3028
|
+
retryPendingTextTool,
|
|
2949
3029
|
sendMessage,
|
|
2950
3030
|
handleSubmit,
|
|
2951
3031
|
handlePresetMessage,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Attachment } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
|
|
3
|
+
export type PendingTextToolRequest = {
|
|
4
|
+
content: string;
|
|
5
|
+
isPresetMessage: boolean;
|
|
6
|
+
attachments?: Attachment[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** Keeps one interrupted text-tool request and serializes all resume attempts. */
|
|
10
|
+
export class PendingTextToolRetryController {
|
|
11
|
+
private pending: PendingTextToolRequest | null = null;
|
|
12
|
+
private inFlight: Promise<boolean> | null = null;
|
|
13
|
+
|
|
14
|
+
set(request: PendingTextToolRequest): void {
|
|
15
|
+
this.pending = request;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
clear(): void {
|
|
19
|
+
this.pending = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hasPending(): boolean {
|
|
23
|
+
return this.pending !== null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
retry(execute: (request: PendingTextToolRequest) => Promise<void>): Promise<boolean> {
|
|
27
|
+
if (this.inFlight) return this.inFlight;
|
|
28
|
+
if (!this.pending) return Promise.resolve(false);
|
|
29
|
+
|
|
30
|
+
const request = this.pending;
|
|
31
|
+
this.inFlight = (async () => {
|
|
32
|
+
await execute(request);
|
|
33
|
+
return true;
|
|
34
|
+
})().finally(() => {
|
|
35
|
+
this.inFlight = null;
|
|
36
|
+
});
|
|
37
|
+
return this.inFlight;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -163,8 +163,9 @@ export interface ChatBotContextType {
|
|
|
163
163
|
|
|
164
164
|
// Functions
|
|
165
165
|
connectWallet: (autoShowSignInMessage?: boolean) => Promise<string | false | undefined>;
|
|
166
|
-
signInWallet: (autoShowSuccessMessage?: boolean) => Promise<
|
|
167
|
-
|
|
166
|
+
signInWallet: (autoShowSuccessMessage?: boolean) => Promise<boolean>;
|
|
167
|
+
retryPendingTextTool: () => Promise<boolean>;
|
|
168
|
+
sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
|
|
168
169
|
handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
|
|
169
170
|
handlePresetMessage: (message: PresetMessage) => void;
|
|
170
171
|
savePermissions: (newPermissions?: string[]) => Promise<void>;
|
|
@@ -194,7 +195,7 @@ export interface ChatBotContextType {
|
|
|
194
195
|
// Additional properties for PermissionForm
|
|
195
196
|
onSave: (newPermissions?: string[]) => Promise<void>;
|
|
196
197
|
onConnectWallet: () => Promise<string | false | undefined>;
|
|
197
|
-
onSignIn: () => Promise<
|
|
198
|
+
onSignIn: () => Promise<boolean>;
|
|
198
199
|
isNeedSignInWithWallet: boolean;
|
|
199
200
|
onVerifyWallet: (method: 'berifyme' | 'custom') => Promise<void>;
|
|
200
201
|
serviceId: string;
|
|
@@ -203,4 +204,4 @@ export interface ChatBotContextType {
|
|
|
203
204
|
// Props
|
|
204
205
|
props: ChatBotProps;
|
|
205
206
|
nxtlinqApi: AITApi;
|
|
206
|
-
}
|
|
207
|
+
}
|
package/src/ui/MessageList.tsx
CHANGED
|
@@ -319,6 +319,7 @@ export const MessageList: React.FC = () => {
|
|
|
319
319
|
retryTtsWithGesture,
|
|
320
320
|
connectWallet,
|
|
321
321
|
signInWallet,
|
|
322
|
+
retryPendingTextTool,
|
|
322
323
|
hitAddress,
|
|
323
324
|
isAutoConnecting,
|
|
324
325
|
isNeedSignInWithWallet,
|
|
@@ -353,20 +354,22 @@ export const MessageList: React.FC = () => {
|
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
if (buttonType === 'connectWallet') {
|
|
356
|
-
connectWallet(
|
|
357
|
+
const connected = await connectWallet(false);
|
|
358
|
+
if (connected) {
|
|
359
|
+
// Continue the interrupted flow immediately. This opens the wallet
|
|
360
|
+
// signature UI instead of leaving the user on a stale connect prompt.
|
|
361
|
+
const signedIn = await signInWallet(true);
|
|
362
|
+
if (signedIn) await retryPendingTextTool();
|
|
363
|
+
}
|
|
357
364
|
} else if (buttonType === 'signIn') {
|
|
358
|
-
signInWallet(true);
|
|
365
|
+
const signedIn = await signInWallet(true);
|
|
366
|
+
if (signedIn) await retryPendingTextTool();
|
|
359
367
|
} else if (buttonType === 'enableAIT') {
|
|
360
368
|
const requiredPermission = message.metadata?.requiredPermission;
|
|
361
369
|
if (requiredPermission) {
|
|
362
370
|
const success = await enableAIT(requiredPermission);
|
|
363
371
|
if (success) {
|
|
364
|
-
|
|
365
|
-
const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
|
|
366
|
-
if (lastUserMsg && lastUserMsg.content) {
|
|
367
|
-
// Re-execute the previous user message (retryCount=1 to skip duplicate PII log)
|
|
368
|
-
await sendMessage(lastUserMsg.content, 1);
|
|
369
|
-
}
|
|
372
|
+
await retryPendingTextTool();
|
|
370
373
|
}
|
|
371
374
|
}
|
|
372
375
|
} else if (buttonType === 'verifyWallet') {
|