@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.3 → 0.4.5
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/aitPermissionPropagation.d.ts +16 -0
- package/dist/aitPermissionPropagation.d.ts.map +1 -0
- package/dist/aitPermissionPropagation.js +21 -0
- 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 +132 -60
- package/dist/piiMessageState.d.ts +16 -0
- package/dist/piiMessageState.d.ts.map +1 -0
- package/dist/piiMessageState.js +18 -0
- package/dist/suggestionState.d.ts +4 -0
- package/dist/suggestionState.d.ts.map +1 -0
- package/dist/suggestionState.js +14 -0
- package/dist/types/ChatBotTypes.d.ts +1 -1
- package/dist/types/ChatBotTypes.d.ts.map +1 -1
- package/dist/ui/PresetMessages.d.ts.map +1 -1
- package/dist/ui/PresetMessages.js +7 -4
- package/package.json +1 -1
- package/src/aitPermissionPropagation.ts +40 -0
- package/src/authorizationLoadingState.ts +21 -0
- package/src/context/ChatBotContext.tsx +144 -60
- package/src/piiMessageState.ts +35 -0
- package/src/suggestionState.ts +20 -0
- package/src/types/ChatBotTypes.ts +1 -1
- package/src/ui/PresetMessages.tsx +9 -6
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type WaitForAITPermissionsOptions<T> = {
|
|
2
|
+
readAIT: () => Promise<T | undefined>;
|
|
3
|
+
getPermissions: (ait: T) => readonly string[];
|
|
4
|
+
expectedPermissions: readonly string[];
|
|
5
|
+
attempts?: number;
|
|
6
|
+
delayMs?: number;
|
|
7
|
+
sleep?: (delayMs: number) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
export declare function includesAITPermissions(actualPermissions: readonly string[], expectedPermissions: readonly string[]): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Wait until the AIT read path can observe a newly submitted permission set.
|
|
12
|
+
* Stale reads are deliberately discarded so callers never downgrade their
|
|
13
|
+
* optimistic local state while the chain/RPC read path is catching up.
|
|
14
|
+
*/
|
|
15
|
+
export declare function waitForAITPermissions<T>({ readAIT, getPermissions, expectedPermissions, attempts, delayMs, sleep, }: WaitForAITPermissionsOptions<T>): Promise<T | undefined>;
|
|
16
|
+
//# sourceMappingURL=aitPermissionPropagation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aitPermissionPropagation.d.ts","sourceRoot":"","sources":["../src/aitPermissionPropagation.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,4BAA4B,CAAC,CAAC,IAAI;IAC5C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACtC,cAAc,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC;IAC9C,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,mBAAmB,EAAE,SAAS,MAAM,EAAE,GACrC,OAAO,CAGT;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,EAC7C,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,QAAa,EACb,OAAa,EACb,KAA6E,GAC9E,EAAE,4BAA4B,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAU1D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function includesAITPermissions(actualPermissions, expectedPermissions) {
|
|
2
|
+
return actualPermissions.includes('ALL')
|
|
3
|
+
|| expectedPermissions.every((permission) => actualPermissions.includes(permission));
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wait until the AIT read path can observe a newly submitted permission set.
|
|
7
|
+
* Stale reads are deliberately discarded so callers never downgrade their
|
|
8
|
+
* optimistic local state while the chain/RPC read path is catching up.
|
|
9
|
+
*/
|
|
10
|
+
export async function waitForAITPermissions({ readAIT, getPermissions, expectedPermissions, attempts = 10, delayMs = 750, sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration)), }) {
|
|
11
|
+
const totalAttempts = Math.max(1, attempts);
|
|
12
|
+
for (let attempt = 0; attempt < totalAttempts; attempt += 1) {
|
|
13
|
+
const ait = await readAIT();
|
|
14
|
+
if (ait && includesAITPermissions(getPermissions(ait), expectedPermissions)) {
|
|
15
|
+
return ait;
|
|
16
|
+
}
|
|
17
|
+
if (attempt + 1 < totalAttempts)
|
|
18
|
+
await sleep(delayMs);
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
@@ -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;AA8B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,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;AAW/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAohGlD,CAAC"}
|
|
@@ -6,7 +6,11 @@ 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
8
|
import { authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
9
|
+
import { waitForAITPermissions } from '../aitPermissionPropagation';
|
|
10
|
+
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
9
11
|
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
12
|
+
import { completePiiScan } from '../piiMessageState';
|
|
13
|
+
import { filterSuggestions } from '../suggestionState';
|
|
10
14
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
11
15
|
const ChatBotContext = React.createContext(undefined);
|
|
12
16
|
export const useChatBot = () => {
|
|
@@ -145,6 +149,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
145
149
|
const permissionsRef = React.useRef(permissions);
|
|
146
150
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
147
151
|
const signerRef = React.useRef(signer);
|
|
152
|
+
const authorizationLoadingStateRef = React.useRef(new AuthorizationLoadingState(isAITLoading, isAutoConnecting));
|
|
148
153
|
const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
|
|
149
154
|
const sendMessageRef = React.useRef(async () => { });
|
|
150
155
|
const retryPendingTextTool = React.useCallback(() => {
|
|
@@ -185,6 +190,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
185
190
|
const textInputRef = React.useRef(null);
|
|
186
191
|
const lastPartialRangeRef = React.useRef(null);
|
|
187
192
|
const lastAutoSentTranscriptRef = React.useRef('');
|
|
193
|
+
const presetSendInFlightRef = React.useRef(false);
|
|
188
194
|
const autoSendTimerRef = React.useRef(null);
|
|
189
195
|
const isCorrectingRef = React.useRef(false);
|
|
190
196
|
const lastCustomErrorRef = React.useRef(undefined);
|
|
@@ -483,6 +489,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
483
489
|
React.useEffect(() => {
|
|
484
490
|
permissionsRef.current = permissions;
|
|
485
491
|
}, [permissions]);
|
|
492
|
+
React.useEffect(() => {
|
|
493
|
+
authorizationLoadingStateRef.current.setAITLoading(isAITLoading);
|
|
494
|
+
}, [isAITLoading]);
|
|
495
|
+
React.useEffect(() => {
|
|
496
|
+
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
497
|
+
}, [isAutoConnecting]);
|
|
486
498
|
React.useEffect(() => {
|
|
487
499
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
488
500
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -875,12 +887,16 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
875
887
|
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
876
888
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
877
889
|
if (!currentHitAddress) {
|
|
890
|
+
aitRef.current = null;
|
|
891
|
+
permissionsRef.current = [];
|
|
878
892
|
setAit(null);
|
|
879
893
|
setPermissions([]);
|
|
880
894
|
setWalletInfo(null);
|
|
895
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
881
896
|
setIsAITLoading(false);
|
|
882
897
|
return;
|
|
883
898
|
}
|
|
899
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
884
900
|
setIsAITLoading(true);
|
|
885
901
|
try {
|
|
886
902
|
let walletAllowsAITLookup = !requireWalletIDVVerification;
|
|
@@ -910,6 +926,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
910
926
|
// AIT existence is not identity verification. In strict mode, stop before
|
|
911
927
|
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
912
928
|
if (currentToken && !walletAllowsAITLookup) {
|
|
929
|
+
aitRef.current = null;
|
|
930
|
+
permissionsRef.current = [];
|
|
913
931
|
setAit(null);
|
|
914
932
|
setPermissions([]);
|
|
915
933
|
return;
|
|
@@ -923,28 +941,81 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
923
941
|
}, currentToken);
|
|
924
942
|
if ('error' in response) {
|
|
925
943
|
console.error('Failed to fetch AIT:', response.error);
|
|
944
|
+
aitRef.current = null;
|
|
945
|
+
permissionsRef.current = [];
|
|
926
946
|
setAit(null);
|
|
927
947
|
setPermissions([]);
|
|
928
948
|
return;
|
|
929
949
|
}
|
|
950
|
+
aitRef.current = response;
|
|
930
951
|
setAit(response);
|
|
931
952
|
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
932
953
|
const newPermissions = response.metadata?.permissions || [];
|
|
954
|
+
permissionsRef.current = newPermissions;
|
|
933
955
|
setPermissions(newPermissions);
|
|
934
956
|
}
|
|
935
957
|
}
|
|
936
958
|
else {
|
|
937
959
|
// No token available, clear AIT data
|
|
960
|
+
aitRef.current = null;
|
|
961
|
+
permissionsRef.current = [];
|
|
938
962
|
setAit(null);
|
|
939
963
|
setPermissions([]);
|
|
940
964
|
}
|
|
941
965
|
}
|
|
942
966
|
catch (error) {
|
|
943
967
|
console.error('Failed to fetch AIT:', error);
|
|
968
|
+
aitRef.current = null;
|
|
969
|
+
permissionsRef.current = [];
|
|
944
970
|
setAit(null);
|
|
945
971
|
setPermissions([]);
|
|
946
972
|
}
|
|
947
973
|
finally {
|
|
974
|
+
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
975
|
+
// React may not commit setState until the immediate pending-tool retry.
|
|
976
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
977
|
+
setIsAITLoading(false);
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
const waitForPersistedAITPermissions = async (expectedPermissions) => {
|
|
981
|
+
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
982
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
983
|
+
if (!currentHitAddress || !currentToken)
|
|
984
|
+
return false;
|
|
985
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
986
|
+
setIsAITLoading(true);
|
|
987
|
+
try {
|
|
988
|
+
const persistedAIT = await waitForAITPermissions({
|
|
989
|
+
expectedPermissions,
|
|
990
|
+
readAIT: async () => {
|
|
991
|
+
try {
|
|
992
|
+
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
993
|
+
serviceId,
|
|
994
|
+
controller: currentHitAddress,
|
|
995
|
+
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
996
|
+
? getFinalCustomUsername(customUsername)
|
|
997
|
+
: undefined,
|
|
998
|
+
}, currentToken);
|
|
999
|
+
return 'error' in response ? undefined : response;
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
console.warn('AIT permission propagation check failed:', error);
|
|
1003
|
+
return undefined;
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
getPermissions: (currentAIT) => currentAIT.metadata?.permissions || [],
|
|
1007
|
+
});
|
|
1008
|
+
if (!persistedAIT)
|
|
1009
|
+
return false;
|
|
1010
|
+
const persistedPermissions = persistedAIT.metadata?.permissions || [];
|
|
1011
|
+
aitRef.current = persistedAIT;
|
|
1012
|
+
permissionsRef.current = persistedPermissions;
|
|
1013
|
+
setAit(persistedAIT);
|
|
1014
|
+
setPermissions(persistedPermissions);
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
finally {
|
|
1018
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
948
1019
|
setIsAITLoading(false);
|
|
949
1020
|
}
|
|
950
1021
|
};
|
|
@@ -1158,7 +1229,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1158
1229
|
// wallet-address subject until issuance/migration supports it.
|
|
1159
1230
|
externalId: undefined,
|
|
1160
1231
|
requireWalletIDVVerification,
|
|
1161
|
-
|
|
1232
|
+
// Use synchronously maintained refs so a retry immediately following
|
|
1233
|
+
// refreshAIT() does not observe the previous React render's loading state.
|
|
1234
|
+
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1162
1235
|
},
|
|
1163
1236
|
...authFields(),
|
|
1164
1237
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
@@ -1225,7 +1298,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1225
1298
|
}
|
|
1226
1299
|
return effectiveAvailableModels[safeIndex];
|
|
1227
1300
|
}, [effectiveAvailableModels, selectedModelIndex]);
|
|
1228
|
-
const updateSuggestions = React.useCallback(async (pseudoId, externalId) => {
|
|
1301
|
+
const updateSuggestions = React.useCallback(async (pseudoId, externalId, lastUserMessage) => {
|
|
1229
1302
|
const result = await nxtlinqApi.agent.generateSuggestions({
|
|
1230
1303
|
...authFields(),
|
|
1231
1304
|
pseudoId,
|
|
@@ -1236,11 +1309,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1236
1309
|
setSuggestions([]);
|
|
1237
1310
|
return;
|
|
1238
1311
|
}
|
|
1239
|
-
setSuggestions(result.suggestions.map((sug) => ({
|
|
1240
|
-
|
|
1241
|
-
autoSend: true
|
|
1242
|
-
})));
|
|
1243
|
-
}, []);
|
|
1312
|
+
setSuggestions(filterSuggestions(result.suggestions.map((sug) => ({ text: sug, autoSend: true })), lastUserMessage));
|
|
1313
|
+
}, [nxtlinqApi, authFields, setSuggestions]);
|
|
1244
1314
|
// Updated sendMessage function to support different AI models and attachments
|
|
1245
1315
|
const sendMessage = async (content, retryCount = 0, isPresetMessage = false, attachments, clientPipelineOverride) => {
|
|
1246
1316
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
@@ -1360,15 +1430,15 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1360
1430
|
const updated = [...prev];
|
|
1361
1431
|
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1362
1432
|
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1433
|
+
if (step === 'scan_complete') {
|
|
1434
|
+
// PII work is complete independently of any later tool
|
|
1435
|
+
// authorization. Never leave the bubble in Sending while
|
|
1436
|
+
// waiting for wallet recovery or permission elevation.
|
|
1437
|
+
updated[i] = completePiiScan(updated[i], data);
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
updated[i] = { ...updated[i], piiStep };
|
|
1370
1441
|
}
|
|
1371
|
-
updated[i] = { ...updated[i], ...patch };
|
|
1372
1442
|
break;
|
|
1373
1443
|
}
|
|
1374
1444
|
}
|
|
@@ -1376,6 +1446,26 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1376
1446
|
});
|
|
1377
1447
|
} : undefined,
|
|
1378
1448
|
});
|
|
1449
|
+
// PII scanning is complete when the Agent response arrives, regardless
|
|
1450
|
+
// of whether a later frontend-tool authorization step can continue.
|
|
1451
|
+
if (piiDisplayMode === 'redacted') {
|
|
1452
|
+
const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
|
|
1453
|
+
const piiMappingData = response.piiProtection?.mapping ?? undefined;
|
|
1454
|
+
setMessages(prev => {
|
|
1455
|
+
const updated = [...prev];
|
|
1456
|
+
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1457
|
+
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1458
|
+
updated[i] = completePiiScan(updated[i], {
|
|
1459
|
+
entityCount: piiMappingData ? Object.keys(piiMappingData).length : 0,
|
|
1460
|
+
anonymizedUserMessage: anonymizedUserMsg,
|
|
1461
|
+
mapping: piiMappingData,
|
|
1462
|
+
});
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return updated;
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1379
1469
|
if (!('error' in response && response.error)) {
|
|
1380
1470
|
const tr = response;
|
|
1381
1471
|
if (tr.ttsVoice &&
|
|
@@ -1500,7 +1590,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1500
1590
|
if (!replyText.trim()) {
|
|
1501
1591
|
replyText = 'Sorry, I cannot understand your question';
|
|
1502
1592
|
}
|
|
1503
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1593
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1504
1594
|
setMessages(prev => prev.map(m => m.id === streamAssistantId
|
|
1505
1595
|
? {
|
|
1506
1596
|
...m,
|
|
@@ -1639,7 +1729,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1639
1729
|
// Don't block the UI if update fails
|
|
1640
1730
|
}
|
|
1641
1731
|
}
|
|
1642
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1732
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1643
1733
|
// Skip creating a new botResponse since we already updated the streaming message
|
|
1644
1734
|
}
|
|
1645
1735
|
else {
|
|
@@ -1678,7 +1768,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1678
1768
|
// Don't block the UI if update fails
|
|
1679
1769
|
}
|
|
1680
1770
|
}
|
|
1681
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1771
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1682
1772
|
const newBotResponse = {
|
|
1683
1773
|
id: (Date.now() + 1).toString(),
|
|
1684
1774
|
content: mergedContent,
|
|
@@ -1704,7 +1794,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1704
1794
|
.map((item) => item.text)
|
|
1705
1795
|
.join(' ') || 'Sorry, I cannot understand your question'
|
|
1706
1796
|
: response.reply || 'Sorry, I cannot understand your question';
|
|
1707
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1797
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1708
1798
|
const newBotResponse = {
|
|
1709
1799
|
id: (Date.now() + 1).toString(),
|
|
1710
1800
|
content: replyText,
|
|
@@ -1742,38 +1832,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1742
1832
|
setMessages(prev => [...prev, newBotResponse]);
|
|
1743
1833
|
botResponse = newBotResponse;
|
|
1744
1834
|
}
|
|
1745
|
-
// ===== PII Protection: Update user message with anonymized version =====
|
|
1746
|
-
const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
|
|
1747
|
-
const piiMappingData = response.piiProtection?.mapping ?? undefined;
|
|
1748
|
-
if (anonymizedUserMsg) {
|
|
1749
|
-
setMessages(prev => {
|
|
1750
|
-
const updated = [...prev];
|
|
1751
|
-
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1752
|
-
if (updated[i].role === 'user') {
|
|
1753
|
-
updated[i] = {
|
|
1754
|
-
...updated[i],
|
|
1755
|
-
piiProtection: { anonymizedContent: anonymizedUserMsg, mapping: piiMappingData },
|
|
1756
|
-
piiStatus: 'complete',
|
|
1757
|
-
};
|
|
1758
|
-
break;
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
return updated;
|
|
1762
|
-
});
|
|
1763
|
-
}
|
|
1764
|
-
else if (piiDisplayMode === 'redacted') {
|
|
1765
|
-
// No PII detected — set piiStatus to 'none' to trigger "No sensitive data" indicator
|
|
1766
|
-
setMessages(prev => {
|
|
1767
|
-
const updated = [...prev];
|
|
1768
|
-
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1769
|
-
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1770
|
-
updated[i] = { ...updated[i], piiStatus: 'none' };
|
|
1771
|
-
break;
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
return updated;
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
1835
|
// Execute redirect after all message processing is complete
|
|
1778
1836
|
if (redirectUrl) {
|
|
1779
1837
|
// Use setTimeout to ensure the message is displayed before redirect
|
|
@@ -2114,20 +2172,18 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2114
2172
|
return { url: result.url };
|
|
2115
2173
|
}, [nxtlinqApi, authFields, pseudoId]);
|
|
2116
2174
|
// Handle preset message
|
|
2117
|
-
const handlePresetMessage = (message) => {
|
|
2175
|
+
const handlePresetMessage = async (message) => {
|
|
2118
2176
|
// If preset is configured as auto-send, avoid duplicate sends when user clicks repeatedly
|
|
2119
2177
|
if (message.autoSend) {
|
|
2120
2178
|
const trimmedText = (message.text || '').trim();
|
|
2121
2179
|
if (!trimmedText)
|
|
2122
2180
|
return;
|
|
2123
2181
|
// Prevent sending messages while AI Agent is processing
|
|
2124
|
-
if (isLoading) {
|
|
2125
|
-
return;
|
|
2126
|
-
}
|
|
2127
|
-
// If this exact preset text was just sent (by auto-send / manual / preset), skip to prevent duplicates
|
|
2128
|
-
if (lastAutoSentTranscriptRef.current === trimmedText) {
|
|
2182
|
+
if (isLoading || presetSendInFlightRef.current) {
|
|
2129
2183
|
return;
|
|
2130
2184
|
}
|
|
2185
|
+
presetSendInFlightRef.current = true;
|
|
2186
|
+
setSuggestions(previous => filterSuggestions(previous, trimmedText));
|
|
2131
2187
|
// For preset messages, we need to add the user message first since sendMessage won't add it on retries
|
|
2132
2188
|
const userMessage = {
|
|
2133
2189
|
id: Date.now().toString(),
|
|
@@ -2144,7 +2200,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2144
2200
|
// Mark as last sent to guard against rapid re-clicks and other duplicate flows
|
|
2145
2201
|
lastAutoSentTranscriptRef.current = trimmedText;
|
|
2146
2202
|
// Pass a flag to indicate this is a preset message so sendMessage won't add user message again
|
|
2147
|
-
|
|
2203
|
+
try {
|
|
2204
|
+
await sendMessage(trimmedText, 0, true);
|
|
2205
|
+
}
|
|
2206
|
+
finally {
|
|
2207
|
+
presetSendInFlightRef.current = false;
|
|
2208
|
+
}
|
|
2148
2209
|
}
|
|
2149
2210
|
else {
|
|
2150
2211
|
setInputValue(message.text);
|
|
@@ -2223,8 +2284,11 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2223
2284
|
metadataHash,
|
|
2224
2285
|
metadataCid,
|
|
2225
2286
|
};
|
|
2287
|
+
const nextPermissions = newPermissions || permissions;
|
|
2288
|
+
aitRef.current = aitInfo;
|
|
2289
|
+
permissionsRef.current = nextPermissions;
|
|
2226
2290
|
setAit(aitInfo);
|
|
2227
|
-
setPermissions(
|
|
2291
|
+
setPermissions(nextPermissions);
|
|
2228
2292
|
};
|
|
2229
2293
|
// Auto enable AIT permission
|
|
2230
2294
|
const enableAIT = async (toolName) => {
|
|
@@ -2307,8 +2371,13 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2307
2371
|
// For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
|
|
2308
2372
|
const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
|
|
2309
2373
|
await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
|
|
2374
|
+
const permissionsPersisted = await waitForPersistedAITPermissions(validPermissions);
|
|
2375
|
+
if (!permissionsPersisted) {
|
|
2376
|
+
showWarning('AIT permission was submitted but is still synchronizing. Please try again shortly.');
|
|
2377
|
+
setIsAITEnabling(false);
|
|
2378
|
+
return false;
|
|
2379
|
+
}
|
|
2310
2380
|
showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
|
|
2311
|
-
await refreshAIT(true);
|
|
2312
2381
|
setIsAITEnabling(false);
|
|
2313
2382
|
return true;
|
|
2314
2383
|
}
|
|
@@ -2394,6 +2463,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2394
2463
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2395
2464
|
}, token);
|
|
2396
2465
|
if (!('error' in aitResponse)) {
|
|
2466
|
+
aitRef.current = aitResponse;
|
|
2397
2467
|
setAit(aitResponse);
|
|
2398
2468
|
}
|
|
2399
2469
|
}
|
|
@@ -2435,6 +2505,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2435
2505
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2436
2506
|
}, token);
|
|
2437
2507
|
if (!('error' in aitResponse)) {
|
|
2508
|
+
aitRef.current = aitResponse;
|
|
2438
2509
|
setAit(aitResponse);
|
|
2439
2510
|
}
|
|
2440
2511
|
}
|
|
@@ -2552,6 +2623,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2552
2623
|
// Set loading state when permission form opens
|
|
2553
2624
|
React.useEffect(() => {
|
|
2554
2625
|
if (isPermissionFormOpen && hitAddress) {
|
|
2626
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
2555
2627
|
setIsAITLoading(true);
|
|
2556
2628
|
}
|
|
2557
2629
|
}, [isPermissionFormOpen, hitAddress]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type PiiMessage = {
|
|
2
|
+
piiStatus?: 'scanning' | 'complete' | 'none';
|
|
3
|
+
piiStep?: 0 | 1 | 2;
|
|
4
|
+
piiProtection?: {
|
|
5
|
+
anonymizedContent?: string;
|
|
6
|
+
mapping?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export type PiiScanCompleteData = {
|
|
10
|
+
entityCount?: number;
|
|
11
|
+
anonymizedUserMessage?: string | null;
|
|
12
|
+
mapping?: Record<string, string> | null;
|
|
13
|
+
};
|
|
14
|
+
export declare function completePiiScan<T extends PiiMessage>(message: T, data?: PiiScanCompleteData): T;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=piiMessageState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piiMessageState.d.ts","sourceRoot":"","sources":["../src/piiMessageState.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG;IAChB,SAAS,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,aAAa,CAAC,EAAE;QACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACzC,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,CAAC,CAmB/F"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function completePiiScan(message, data) {
|
|
2
|
+
const mapping = data?.mapping || {};
|
|
3
|
+
const entityCount = typeof data?.entityCount === 'number'
|
|
4
|
+
? data.entityCount
|
|
5
|
+
: Object.keys(mapping).length;
|
|
6
|
+
if (entityCount <= 0 || !data?.anonymizedUserMessage) {
|
|
7
|
+
return { ...message, piiStatus: 'none', piiStep: 1 };
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
...message,
|
|
11
|
+
piiStatus: 'complete',
|
|
12
|
+
piiStep: 1,
|
|
13
|
+
piiProtection: {
|
|
14
|
+
anonymizedContent: data.anonymizedUserMessage,
|
|
15
|
+
mapping,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PresetMessage } from './types/ChatBotTypes';
|
|
2
|
+
export declare function normalizeSuggestionText(value: string): string;
|
|
3
|
+
export declare function filterSuggestions(suggestions: readonly PresetMessage[], lastUserMessage?: string): PresetMessage[];
|
|
4
|
+
//# sourceMappingURL=suggestionState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestionState.d.ts","sourceRoot":"","sources":["../src/suggestionState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,eAAe,CAAC,EAAE,MAAM,GACvB,aAAa,EAAE,CAUjB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function normalizeSuggestionText(value) {
|
|
2
|
+
return value.trim().replace(/\s+/g, ' ').toLocaleLowerCase();
|
|
3
|
+
}
|
|
4
|
+
export function filterSuggestions(suggestions, lastUserMessage) {
|
|
5
|
+
const lastUserText = normalizeSuggestionText(lastUserMessage || '');
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
return suggestions.filter((suggestion) => {
|
|
8
|
+
const normalized = normalizeSuggestionText(suggestion.text || '');
|
|
9
|
+
if (!normalized || normalized === lastUserText || seen.has(normalized))
|
|
10
|
+
return false;
|
|
11
|
+
seen.add(normalized);
|
|
12
|
+
return true;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -138,7 +138,7 @@ export interface ChatBotContextType {
|
|
|
138
138
|
retryPendingTextTool: () => Promise<boolean>;
|
|
139
139
|
sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
|
|
140
140
|
handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
|
|
141
|
-
handlePresetMessage: (message: PresetMessage) => void
|
|
141
|
+
handlePresetMessage: (message: PresetMessage) => Promise<void>;
|
|
142
142
|
savePermissions: (newPermissions?: string[]) => Promise<void>;
|
|
143
143
|
enableAIT: (toolName: string) => Promise<boolean>;
|
|
144
144
|
handleVerifyWalletClick: (method: 'berifyme' | 'custom') => Promise<void>;
|
|
@@ -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":"PresetMessages.d.ts","sourceRoot":"","sources":["../../src/ui/PresetMessages.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PresetMessages.d.ts","sourceRoot":"","sources":["../../src/ui/PresetMessages.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EA4ClC,CAAC"}
|
|
@@ -2,9 +2,12 @@ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
|
2
2
|
import { css } from '@emotion/react';
|
|
3
3
|
import { useChatBot } from '../context/ChatBotContext';
|
|
4
4
|
import { actionButton } from './styles/isolatedStyles';
|
|
5
|
+
import { filterSuggestions, normalizeSuggestionText } from '../suggestionState';
|
|
5
6
|
export const PresetMessages = () => {
|
|
6
|
-
const { suggestions, handlePresetMessage } = useChatBot();
|
|
7
|
-
|
|
7
|
+
const { suggestions, messages, handlePresetMessage } = useChatBot();
|
|
8
|
+
const lastUserMessage = [...messages].reverse().find(message => message.role === 'user')?.content;
|
|
9
|
+
const visibleSuggestions = filterSuggestions(suggestions || [], lastUserMessage);
|
|
10
|
+
if (visibleSuggestions.length === 0) {
|
|
8
11
|
return null;
|
|
9
12
|
}
|
|
10
13
|
return (_jsx("div", { css: css `
|
|
@@ -15,7 +18,7 @@ export const PresetMessages = () => {
|
|
|
15
18
|
display: flex !important;
|
|
16
19
|
gap: 10px !important;
|
|
17
20
|
align-items: center !important;
|
|
18
|
-
`, children:
|
|
21
|
+
`, children: visibleSuggestions.map((preset) => (_jsx("button", { onClick: () => { void handlePresetMessage(preset); }, css: css `
|
|
19
22
|
${actionButton}
|
|
20
23
|
padding: 5px 10px !important;
|
|
21
24
|
font-size: 12px !important;
|
|
@@ -29,5 +32,5 @@ export const PresetMessages = () => {
|
|
|
29
32
|
&:active {
|
|
30
33
|
background-color: #004085 !important;
|
|
31
34
|
}
|
|
32
|
-
`, children: preset.text },
|
|
35
|
+
`, children: preset.text }, normalizeSuggestionText(preset.text)))) }));
|
|
33
36
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type WaitForAITPermissionsOptions<T> = {
|
|
2
|
+
readAIT: () => Promise<T | undefined>;
|
|
3
|
+
getPermissions: (ait: T) => readonly string[];
|
|
4
|
+
expectedPermissions: readonly string[];
|
|
5
|
+
attempts?: number;
|
|
6
|
+
delayMs?: number;
|
|
7
|
+
sleep?: (delayMs: number) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function includesAITPermissions(
|
|
11
|
+
actualPermissions: readonly string[],
|
|
12
|
+
expectedPermissions: readonly string[],
|
|
13
|
+
): boolean {
|
|
14
|
+
return actualPermissions.includes('ALL')
|
|
15
|
+
|| expectedPermissions.every((permission) => actualPermissions.includes(permission));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wait until the AIT read path can observe a newly submitted permission set.
|
|
20
|
+
* Stale reads are deliberately discarded so callers never downgrade their
|
|
21
|
+
* optimistic local state while the chain/RPC read path is catching up.
|
|
22
|
+
*/
|
|
23
|
+
export async function waitForAITPermissions<T>({
|
|
24
|
+
readAIT,
|
|
25
|
+
getPermissions,
|
|
26
|
+
expectedPermissions,
|
|
27
|
+
attempts = 10,
|
|
28
|
+
delayMs = 750,
|
|
29
|
+
sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration)),
|
|
30
|
+
}: WaitForAITPermissionsOptions<T>): Promise<T | undefined> {
|
|
31
|
+
const totalAttempts = Math.max(1, attempts);
|
|
32
|
+
for (let attempt = 0; attempt < totalAttempts; attempt += 1) {
|
|
33
|
+
const ait = await readAIT();
|
|
34
|
+
if (ait && includesAITPermissions(getPermissions(ait), expectedPermissions)) {
|
|
35
|
+
return ait;
|
|
36
|
+
}
|
|
37
|
+
if (attempt + 1 < totalAttempts) await sleep(delayMs);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -36,7 +36,11 @@ import {
|
|
|
36
36
|
ChatBotProps,
|
|
37
37
|
PresetMessage
|
|
38
38
|
} from '../types/ChatBotTypes';
|
|
39
|
+
import { waitForAITPermissions } from '../aitPermissionPropagation';
|
|
40
|
+
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
39
41
|
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
42
|
+
import { completePiiScan } from '../piiMessageState';
|
|
43
|
+
import { filterSuggestions } from '../suggestionState';
|
|
40
44
|
|
|
41
45
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
42
46
|
|
|
@@ -218,6 +222,9 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
218
222
|
const permissionsRef = React.useRef(permissions);
|
|
219
223
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
220
224
|
const signerRef = React.useRef(signer);
|
|
225
|
+
const authorizationLoadingStateRef = React.useRef(
|
|
226
|
+
new AuthorizationLoadingState(isAITLoading, isAutoConnecting),
|
|
227
|
+
);
|
|
221
228
|
const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
|
|
222
229
|
const sendMessageRef = React.useRef<ChatBotContextType['sendMessage']>(async () => {});
|
|
223
230
|
const retryPendingTextTool = React.useCallback(() => {
|
|
@@ -264,6 +271,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
264
271
|
const textInputRef = React.useRef<HTMLInputElement | null>(null);
|
|
265
272
|
const lastPartialRangeRef = React.useRef<{ start: number; end: number } | null>(null);
|
|
266
273
|
const lastAutoSentTranscriptRef = React.useRef<string>('');
|
|
274
|
+
const presetSendInFlightRef = React.useRef(false);
|
|
267
275
|
const autoSendTimerRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
268
276
|
const isCorrectingRef = React.useRef(false);
|
|
269
277
|
const lastCustomErrorRef = React.useRef<string | undefined>(undefined);
|
|
@@ -578,6 +586,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
578
586
|
permissionsRef.current = permissions;
|
|
579
587
|
}, [permissions]);
|
|
580
588
|
|
|
589
|
+
React.useEffect(() => {
|
|
590
|
+
authorizationLoadingStateRef.current.setAITLoading(isAITLoading);
|
|
591
|
+
}, [isAITLoading]);
|
|
592
|
+
|
|
593
|
+
React.useEffect(() => {
|
|
594
|
+
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
595
|
+
}, [isAutoConnecting]);
|
|
596
|
+
|
|
581
597
|
React.useEffect(() => {
|
|
582
598
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
583
599
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -1021,13 +1037,17 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1021
1037
|
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1022
1038
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
1023
1039
|
if (!currentHitAddress) {
|
|
1040
|
+
aitRef.current = null;
|
|
1041
|
+
permissionsRef.current = [];
|
|
1024
1042
|
setAit(null);
|
|
1025
1043
|
setPermissions([]);
|
|
1026
1044
|
setWalletInfo(null);
|
|
1045
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1027
1046
|
setIsAITLoading(false);
|
|
1028
1047
|
return;
|
|
1029
1048
|
}
|
|
1030
1049
|
|
|
1050
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
1031
1051
|
setIsAITLoading(true);
|
|
1032
1052
|
try {
|
|
1033
1053
|
let walletAllowsAITLookup = !requireWalletIDVVerification;
|
|
@@ -1057,6 +1077,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1057
1077
|
// AIT existence is not identity verification. In strict mode, stop before
|
|
1058
1078
|
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
1059
1079
|
if (currentToken && !walletAllowsAITLookup) {
|
|
1080
|
+
aitRef.current = null;
|
|
1081
|
+
permissionsRef.current = [];
|
|
1060
1082
|
setAit(null);
|
|
1061
1083
|
setPermissions([]);
|
|
1062
1084
|
return;
|
|
@@ -1072,26 +1094,81 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1072
1094
|
|
|
1073
1095
|
if ('error' in response) {
|
|
1074
1096
|
console.error('Failed to fetch AIT:', response.error);
|
|
1097
|
+
aitRef.current = null;
|
|
1098
|
+
permissionsRef.current = [];
|
|
1075
1099
|
setAit(null);
|
|
1076
1100
|
setPermissions([]);
|
|
1077
1101
|
return;
|
|
1078
1102
|
}
|
|
1079
1103
|
|
|
1104
|
+
aitRef.current = response;
|
|
1080
1105
|
setAit(response);
|
|
1081
1106
|
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
1082
1107
|
const newPermissions = response.metadata?.permissions || [];
|
|
1108
|
+
permissionsRef.current = newPermissions;
|
|
1083
1109
|
setPermissions(newPermissions);
|
|
1084
1110
|
}
|
|
1085
1111
|
} else {
|
|
1086
1112
|
// No token available, clear AIT data
|
|
1113
|
+
aitRef.current = null;
|
|
1114
|
+
permissionsRef.current = [];
|
|
1087
1115
|
setAit(null);
|
|
1088
1116
|
setPermissions([]);
|
|
1089
1117
|
}
|
|
1090
1118
|
} catch (error) {
|
|
1091
1119
|
console.error('Failed to fetch AIT:', error);
|
|
1120
|
+
aitRef.current = null;
|
|
1121
|
+
permissionsRef.current = [];
|
|
1092
1122
|
setAit(null);
|
|
1093
1123
|
setPermissions([]);
|
|
1094
1124
|
} finally {
|
|
1125
|
+
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
1126
|
+
// React may not commit setState until the immediate pending-tool retry.
|
|
1127
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1128
|
+
setIsAITLoading(false);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
const waitForPersistedAITPermissions = async (
|
|
1133
|
+
expectedPermissions: string[],
|
|
1134
|
+
): Promise<boolean> => {
|
|
1135
|
+
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1136
|
+
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
1137
|
+
if (!currentHitAddress || !currentToken) return false;
|
|
1138
|
+
|
|
1139
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
1140
|
+
setIsAITLoading(true);
|
|
1141
|
+
try {
|
|
1142
|
+
const persistedAIT = await waitForAITPermissions({
|
|
1143
|
+
expectedPermissions,
|
|
1144
|
+
readAIT: async () => {
|
|
1145
|
+
try {
|
|
1146
|
+
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
1147
|
+
serviceId,
|
|
1148
|
+
controller: currentHitAddress,
|
|
1149
|
+
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1150
|
+
? getFinalCustomUsername(customUsername)
|
|
1151
|
+
: undefined,
|
|
1152
|
+
}, currentToken);
|
|
1153
|
+
return 'error' in response ? undefined : response;
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
console.warn('AIT permission propagation check failed:', error);
|
|
1156
|
+
return undefined;
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
getPermissions: (currentAIT) => currentAIT.metadata?.permissions || [],
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
if (!persistedAIT) return false;
|
|
1163
|
+
|
|
1164
|
+
const persistedPermissions = persistedAIT.metadata?.permissions || [];
|
|
1165
|
+
aitRef.current = persistedAIT;
|
|
1166
|
+
permissionsRef.current = persistedPermissions;
|
|
1167
|
+
setAit(persistedAIT);
|
|
1168
|
+
setPermissions(persistedPermissions);
|
|
1169
|
+
return true;
|
|
1170
|
+
} finally {
|
|
1171
|
+
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1095
1172
|
setIsAITLoading(false);
|
|
1096
1173
|
}
|
|
1097
1174
|
};
|
|
@@ -1329,7 +1406,9 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1329
1406
|
// wallet-address subject until issuance/migration supports it.
|
|
1330
1407
|
externalId: undefined,
|
|
1331
1408
|
requireWalletIDVVerification,
|
|
1332
|
-
|
|
1409
|
+
// Use synchronously maintained refs so a retry immediately following
|
|
1410
|
+
// refreshAIT() does not observe the previous React render's loading state.
|
|
1411
|
+
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1333
1412
|
},
|
|
1334
1413
|
...authFields(),
|
|
1335
1414
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
@@ -1402,7 +1481,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1402
1481
|
return effectiveAvailableModels[safeIndex];
|
|
1403
1482
|
}, [effectiveAvailableModels, selectedModelIndex]);
|
|
1404
1483
|
|
|
1405
|
-
const updateSuggestions = React.useCallback(async (
|
|
1484
|
+
const updateSuggestions = React.useCallback(async (
|
|
1485
|
+
pseudoId: string,
|
|
1486
|
+
externalId?: string,
|
|
1487
|
+
lastUserMessage?: string,
|
|
1488
|
+
) => {
|
|
1406
1489
|
const result = await nxtlinqApi.agent.generateSuggestions({
|
|
1407
1490
|
...authFields(),
|
|
1408
1491
|
pseudoId,
|
|
@@ -1415,11 +1498,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1415
1498
|
return;
|
|
1416
1499
|
}
|
|
1417
1500
|
|
|
1418
|
-
setSuggestions(
|
|
1419
|
-
text: sug,
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}, [])
|
|
1501
|
+
setSuggestions(filterSuggestions(
|
|
1502
|
+
result.suggestions.map((sug: string) => ({ text: sug, autoSend: true })),
|
|
1503
|
+
lastUserMessage,
|
|
1504
|
+
));
|
|
1505
|
+
}, [nxtlinqApi, authFields, setSuggestions])
|
|
1423
1506
|
|
|
1424
1507
|
// Updated sendMessage function to support different AI models and attachments
|
|
1425
1508
|
const sendMessage = async (
|
|
@@ -1555,15 +1638,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1555
1638
|
const updated = [...prev];
|
|
1556
1639
|
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1557
1640
|
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
};
|
|
1641
|
+
if (step === 'scan_complete') {
|
|
1642
|
+
// PII work is complete independently of any later tool
|
|
1643
|
+
// authorization. Never leave the bubble in Sending while
|
|
1644
|
+
// waiting for wallet recovery or permission elevation.
|
|
1645
|
+
updated[i] = completePiiScan(updated[i], data);
|
|
1646
|
+
} else {
|
|
1647
|
+
updated[i] = { ...updated[i], piiStep };
|
|
1565
1648
|
}
|
|
1566
|
-
updated[i] = { ...updated[i], ...patch };
|
|
1567
1649
|
break;
|
|
1568
1650
|
}
|
|
1569
1651
|
}
|
|
@@ -1572,6 +1654,27 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1572
1654
|
} : undefined,
|
|
1573
1655
|
});
|
|
1574
1656
|
|
|
1657
|
+
// PII scanning is complete when the Agent response arrives, regardless
|
|
1658
|
+
// of whether a later frontend-tool authorization step can continue.
|
|
1659
|
+
if (piiDisplayMode === 'redacted') {
|
|
1660
|
+
const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
|
|
1661
|
+
const piiMappingData = response.piiProtection?.mapping ?? undefined;
|
|
1662
|
+
setMessages(prev => {
|
|
1663
|
+
const updated = [...prev];
|
|
1664
|
+
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1665
|
+
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1666
|
+
updated[i] = completePiiScan(updated[i], {
|
|
1667
|
+
entityCount: piiMappingData ? Object.keys(piiMappingData).length : 0,
|
|
1668
|
+
anonymizedUserMessage: anonymizedUserMsg,
|
|
1669
|
+
mapping: piiMappingData,
|
|
1670
|
+
});
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return updated;
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1575
1678
|
if (!('error' in response && (response as { error?: string }).error)) {
|
|
1576
1679
|
const tr = response as AgentResponse;
|
|
1577
1680
|
if (
|
|
@@ -1701,7 +1804,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1701
1804
|
replyText = 'Sorry, I cannot understand your question';
|
|
1702
1805
|
}
|
|
1703
1806
|
|
|
1704
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1807
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1705
1808
|
|
|
1706
1809
|
setMessages(prev => prev.map(m =>
|
|
1707
1810
|
m.id === streamAssistantId
|
|
@@ -1862,7 +1965,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1862
1965
|
}
|
|
1863
1966
|
}
|
|
1864
1967
|
|
|
1865
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1968
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1866
1969
|
|
|
1867
1970
|
// Skip creating a new botResponse since we already updated the streaming message
|
|
1868
1971
|
} else {
|
|
@@ -1902,7 +2005,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1902
2005
|
}
|
|
1903
2006
|
}
|
|
1904
2007
|
|
|
1905
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
2008
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1906
2009
|
const newBotResponse: Message = {
|
|
1907
2010
|
id: (Date.now() + 1).toString(),
|
|
1908
2011
|
content: mergedContent,
|
|
@@ -1928,7 +2031,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1928
2031
|
.join(' ') || 'Sorry, I cannot understand your question'
|
|
1929
2032
|
: response.reply || 'Sorry, I cannot understand your question';
|
|
1930
2033
|
|
|
1931
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
2034
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1932
2035
|
|
|
1933
2036
|
const newBotResponse: Message = {
|
|
1934
2037
|
id: (Date.now() + 1).toString(),
|
|
@@ -1968,38 +2071,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1968
2071
|
botResponse = newBotResponse;
|
|
1969
2072
|
}
|
|
1970
2073
|
|
|
1971
|
-
// ===== PII Protection: Update user message with anonymized version =====
|
|
1972
|
-
const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
|
|
1973
|
-
const piiMappingData = response.piiProtection?.mapping ?? undefined;
|
|
1974
|
-
if (anonymizedUserMsg) {
|
|
1975
|
-
setMessages(prev => {
|
|
1976
|
-
const updated = [...prev];
|
|
1977
|
-
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1978
|
-
if (updated[i].role === 'user') {
|
|
1979
|
-
updated[i] = {
|
|
1980
|
-
...updated[i],
|
|
1981
|
-
piiProtection: { anonymizedContent: anonymizedUserMsg, mapping: piiMappingData },
|
|
1982
|
-
piiStatus: 'complete',
|
|
1983
|
-
};
|
|
1984
|
-
break;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return updated;
|
|
1988
|
-
});
|
|
1989
|
-
} else if (piiDisplayMode === 'redacted') {
|
|
1990
|
-
// No PII detected — set piiStatus to 'none' to trigger "No sensitive data" indicator
|
|
1991
|
-
setMessages(prev => {
|
|
1992
|
-
const updated = [...prev];
|
|
1993
|
-
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1994
|
-
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1995
|
-
updated[i] = { ...updated[i], piiStatus: 'none' };
|
|
1996
|
-
break;
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
return updated;
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
2074
|
// Execute redirect after all message processing is complete
|
|
2004
2075
|
if (redirectUrl) {
|
|
2005
2076
|
// Use setTimeout to ensure the message is displayed before redirect
|
|
@@ -2369,21 +2440,19 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2369
2440
|
}, [nxtlinqApi, authFields, pseudoId]);
|
|
2370
2441
|
|
|
2371
2442
|
// Handle preset message
|
|
2372
|
-
const handlePresetMessage = (message: PresetMessage) => {
|
|
2443
|
+
const handlePresetMessage = async (message: PresetMessage): Promise<void> => {
|
|
2373
2444
|
// If preset is configured as auto-send, avoid duplicate sends when user clicks repeatedly
|
|
2374
2445
|
if (message.autoSend) {
|
|
2375
2446
|
const trimmedText = (message.text || '').trim();
|
|
2376
2447
|
if (!trimmedText) return;
|
|
2377
2448
|
|
|
2378
2449
|
// Prevent sending messages while AI Agent is processing
|
|
2379
|
-
if (isLoading) {
|
|
2450
|
+
if (isLoading || presetSendInFlightRef.current) {
|
|
2380
2451
|
return;
|
|
2381
2452
|
}
|
|
2382
2453
|
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2454
|
+
presetSendInFlightRef.current = true;
|
|
2455
|
+
setSuggestions(previous => filterSuggestions(previous, trimmedText));
|
|
2387
2456
|
|
|
2388
2457
|
// For preset messages, we need to add the user message first since sendMessage won't add it on retries
|
|
2389
2458
|
const userMessage: Message = {
|
|
@@ -2403,7 +2472,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2403
2472
|
lastAutoSentTranscriptRef.current = trimmedText;
|
|
2404
2473
|
|
|
2405
2474
|
// Pass a flag to indicate this is a preset message so sendMessage won't add user message again
|
|
2406
|
-
|
|
2475
|
+
try {
|
|
2476
|
+
await sendMessage(trimmedText, 0, true);
|
|
2477
|
+
} finally {
|
|
2478
|
+
presetSendInFlightRef.current = false;
|
|
2479
|
+
}
|
|
2407
2480
|
} else {
|
|
2408
2481
|
setInputValue(message.text);
|
|
2409
2482
|
}
|
|
@@ -2502,8 +2575,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2502
2575
|
metadataCid,
|
|
2503
2576
|
};
|
|
2504
2577
|
|
|
2578
|
+
const nextPermissions = newPermissions || permissions;
|
|
2579
|
+
aitRef.current = aitInfo;
|
|
2580
|
+
permissionsRef.current = nextPermissions;
|
|
2505
2581
|
setAit(aitInfo);
|
|
2506
|
-
setPermissions(
|
|
2582
|
+
setPermissions(nextPermissions);
|
|
2507
2583
|
};
|
|
2508
2584
|
|
|
2509
2585
|
|
|
@@ -2600,8 +2676,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2600
2676
|
const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
|
|
2601
2677
|
|
|
2602
2678
|
await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
|
|
2679
|
+
const permissionsPersisted = await waitForPersistedAITPermissions(validPermissions);
|
|
2680
|
+
if (!permissionsPersisted) {
|
|
2681
|
+
showWarning('AIT permission was submitted but is still synchronizing. Please try again shortly.');
|
|
2682
|
+
setIsAITEnabling(false);
|
|
2683
|
+
return false;
|
|
2684
|
+
}
|
|
2603
2685
|
showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
|
|
2604
|
-
await refreshAIT(true);
|
|
2605
2686
|
setIsAITEnabling(false);
|
|
2606
2687
|
return true;
|
|
2607
2688
|
} catch (error) {
|
|
@@ -2691,6 +2772,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2691
2772
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2692
2773
|
}, token);
|
|
2693
2774
|
if (!('error' in aitResponse)) {
|
|
2775
|
+
aitRef.current = aitResponse;
|
|
2694
2776
|
setAit(aitResponse);
|
|
2695
2777
|
}
|
|
2696
2778
|
}
|
|
@@ -2730,6 +2812,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2730
2812
|
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
2731
2813
|
}, token);
|
|
2732
2814
|
if (!('error' in aitResponse)) {
|
|
2815
|
+
aitRef.current = aitResponse;
|
|
2733
2816
|
setAit(aitResponse);
|
|
2734
2817
|
}
|
|
2735
2818
|
}
|
|
@@ -2846,6 +2929,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2846
2929
|
// Set loading state when permission form opens
|
|
2847
2930
|
React.useEffect(() => {
|
|
2848
2931
|
if (isPermissionFormOpen && hitAddress) {
|
|
2932
|
+
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
2849
2933
|
setIsAITLoading(true);
|
|
2850
2934
|
}
|
|
2851
2935
|
}, [isPermissionFormOpen, hitAddress]);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type PiiMessage = {
|
|
2
|
+
piiStatus?: 'scanning' | 'complete' | 'none';
|
|
3
|
+
piiStep?: 0 | 1 | 2;
|
|
4
|
+
piiProtection?: {
|
|
5
|
+
anonymizedContent?: string;
|
|
6
|
+
mapping?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type PiiScanCompleteData = {
|
|
11
|
+
entityCount?: number;
|
|
12
|
+
anonymizedUserMessage?: string | null;
|
|
13
|
+
mapping?: Record<string, string> | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function completePiiScan<T extends PiiMessage>(message: T, data?: PiiScanCompleteData): T {
|
|
17
|
+
const mapping = data?.mapping || {};
|
|
18
|
+
const entityCount = typeof data?.entityCount === 'number'
|
|
19
|
+
? data.entityCount
|
|
20
|
+
: Object.keys(mapping).length;
|
|
21
|
+
|
|
22
|
+
if (entityCount <= 0 || !data?.anonymizedUserMessage) {
|
|
23
|
+
return { ...message, piiStatus: 'none', piiStep: 1 } as T;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...message,
|
|
28
|
+
piiStatus: 'complete',
|
|
29
|
+
piiStep: 1,
|
|
30
|
+
piiProtection: {
|
|
31
|
+
anonymizedContent: data.anonymizedUserMessage,
|
|
32
|
+
mapping,
|
|
33
|
+
},
|
|
34
|
+
} as T;
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PresetMessage } from './types/ChatBotTypes';
|
|
2
|
+
|
|
3
|
+
export function normalizeSuggestionText(value: string): string {
|
|
4
|
+
return value.trim().replace(/\s+/g, ' ').toLocaleLowerCase();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function filterSuggestions(
|
|
8
|
+
suggestions: readonly PresetMessage[],
|
|
9
|
+
lastUserMessage?: string,
|
|
10
|
+
): PresetMessage[] {
|
|
11
|
+
const lastUserText = normalizeSuggestionText(lastUserMessage || '');
|
|
12
|
+
const seen = new Set<string>();
|
|
13
|
+
|
|
14
|
+
return suggestions.filter((suggestion) => {
|
|
15
|
+
const normalized = normalizeSuggestionText(suggestion.text || '');
|
|
16
|
+
if (!normalized || normalized === lastUserText || seen.has(normalized)) return false;
|
|
17
|
+
seen.add(normalized);
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -167,7 +167,7 @@ export interface ChatBotContextType {
|
|
|
167
167
|
retryPendingTextTool: () => Promise<boolean>;
|
|
168
168
|
sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
|
|
169
169
|
handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
|
|
170
|
-
handlePresetMessage: (message: PresetMessage) => void
|
|
170
|
+
handlePresetMessage: (message: PresetMessage) => Promise<void>;
|
|
171
171
|
savePermissions: (newPermissions?: string[]) => Promise<void>;
|
|
172
172
|
enableAIT: (toolName: string) => Promise<boolean>;
|
|
173
173
|
handleVerifyWalletClick: (method: 'berifyme' | 'custom') => Promise<void>;
|
|
@@ -4,11 +4,14 @@ import { css } from '@emotion/react';
|
|
|
4
4
|
import { useChatBot } from '../context/ChatBotContext';
|
|
5
5
|
import { PresetMessage } from '../types/ChatBotTypes';
|
|
6
6
|
import { actionButton } from './styles/isolatedStyles';
|
|
7
|
+
import { filterSuggestions, normalizeSuggestionText } from '../suggestionState';
|
|
7
8
|
|
|
8
9
|
export const PresetMessages: React.FC = () => {
|
|
9
|
-
const { suggestions, handlePresetMessage } = useChatBot();
|
|
10
|
+
const { suggestions, messages, handlePresetMessage } = useChatBot();
|
|
11
|
+
const lastUserMessage = [...messages].reverse().find(message => message.role === 'user')?.content;
|
|
12
|
+
const visibleSuggestions = filterSuggestions(suggestions || [], lastUserMessage);
|
|
10
13
|
|
|
11
|
-
if (
|
|
14
|
+
if (visibleSuggestions.length === 0) {
|
|
12
15
|
return null;
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -22,10 +25,10 @@ export const PresetMessages: React.FC = () => {
|
|
|
22
25
|
gap: 10px !important;
|
|
23
26
|
align-items: center !important;
|
|
24
27
|
`}>
|
|
25
|
-
{
|
|
28
|
+
{visibleSuggestions.map((preset: PresetMessage) => (
|
|
26
29
|
<button
|
|
27
|
-
key={
|
|
28
|
-
onClick={() => handlePresetMessage(preset)}
|
|
30
|
+
key={normalizeSuggestionText(preset.text)}
|
|
31
|
+
onClick={() => { void handlePresetMessage(preset); }}
|
|
29
32
|
css={css`
|
|
30
33
|
${actionButton}
|
|
31
34
|
padding: 5px 10px !important;
|
|
@@ -47,4 +50,4 @@ export const PresetMessages: React.FC = () => {
|
|
|
47
50
|
))}
|
|
48
51
|
</div>
|
|
49
52
|
);
|
|
50
|
-
};
|
|
53
|
+
};
|