@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.4 → 0.4.7
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/context/ChatBotContext.d.ts.map +1 -1
- package/dist/context/ChatBotContext.js +230 -231
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- 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 +7 -6
- package/dist/types/ChatBotTypes.d.ts.map +1 -1
- package/dist/ui/ModelSelector.js +2 -2
- package/dist/ui/PermissionForm.d.ts.map +1 -1
- package/dist/ui/PermissionForm.js +14 -28
- package/dist/ui/PresetMessages.d.ts.map +1 -1
- package/dist/ui/PresetMessages.js +7 -4
- package/package.json +4 -4
- package/src/aitPermissionPropagation.ts +40 -0
- package/src/context/ChatBotContext.tsx +309 -266
- package/src/index.ts +0 -1
- package/src/permissionState.ts +313 -0
- package/src/piiMessageState.ts +35 -0
- package/src/suggestionState.ts +20 -0
- package/src/types/ChatBotTypes.ts +7 -7
- package/src/ui/ModelSelector.tsx +5 -5
- package/src/ui/PermissionForm.tsx +19 -30
- package/src/ui/PresetMessages.tsx +9 -6
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
Attachment,
|
|
24
24
|
ClientTtsVoiceSettings,
|
|
25
25
|
Message,
|
|
26
|
+
RuntimeAITResult,
|
|
26
27
|
ServicePermission,
|
|
27
28
|
} from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
28
29
|
import {
|
|
@@ -38,6 +39,24 @@ import {
|
|
|
38
39
|
} from '../types/ChatBotTypes';
|
|
39
40
|
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
40
41
|
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
42
|
+
import { completePiiScan } from '../piiMessageState';
|
|
43
|
+
import { filterSuggestions } from '../suggestionState';
|
|
44
|
+
import {
|
|
45
|
+
dispatchRuntimeAITCompleteReplacement,
|
|
46
|
+
removeOneDeniedPermission,
|
|
47
|
+
replaceCurrentMaximumDenyList,
|
|
48
|
+
RuntimeAITMutationQueue,
|
|
49
|
+
RuntimeAITRecomputeCoordinator,
|
|
50
|
+
StaleRuntimeAITResponseError,
|
|
51
|
+
runtimeAITContextKey,
|
|
52
|
+
runtimeAITSubjectKey,
|
|
53
|
+
runtimeAuthorizationMatchesContext,
|
|
54
|
+
runtimeAuthorizationState,
|
|
55
|
+
unavailableRuntimeAuthorization,
|
|
56
|
+
useProviderRuntimeCommit,
|
|
57
|
+
useRuntimeAITContextCommit,
|
|
58
|
+
type RuntimeAITContext,
|
|
59
|
+
} from '../permissionState';
|
|
41
60
|
|
|
42
61
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
43
62
|
|
|
@@ -68,6 +87,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
68
87
|
environment = 'production',
|
|
69
88
|
onVerifyWallet,
|
|
70
89
|
permissionGroup,
|
|
90
|
+
roles,
|
|
71
91
|
children,
|
|
72
92
|
// AI Model related attributes
|
|
73
93
|
onModelChange,
|
|
@@ -90,9 +110,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
90
110
|
debugVoiceRms = false,
|
|
91
111
|
sttGlossary,
|
|
92
112
|
}) => {
|
|
93
|
-
// Set API hosts immediately based on environment (before any API calls)
|
|
94
|
-
setApiHosts(environment);
|
|
95
|
-
|
|
96
113
|
const nxtlinqApi = React.useMemo(
|
|
97
114
|
() => (serviceToken || getAuthToken
|
|
98
115
|
? createNxtlinqApi({ apiKey, apiSecret, serviceToken, getAuthToken })
|
|
@@ -152,7 +169,22 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
152
169
|
const [hitAddress, setHitAddress] = React.useState<string | null>(null);
|
|
153
170
|
const [ait, setAit] = React.useState<AIT | null>(null);
|
|
154
171
|
const [permissions, setPermissions] = React.useState<string[]>([]);
|
|
155
|
-
const [
|
|
172
|
+
const [maximumPermissions, setMaximumPermissions] = React.useState<string[]>([]);
|
|
173
|
+
const [deniedPermissions, setDeniedPermissions] = React.useState<string[]>([]);
|
|
174
|
+
const [runtimePermissionsUnavailable, setRuntimePermissionsUnavailable] = React.useState(false);
|
|
175
|
+
const [runtimeAuthorizationContextKey, setRuntimeAuthorizationContextKey] =
|
|
176
|
+
React.useState<string | null>(null);
|
|
177
|
+
const [servicePermissionCatalog, setServicePermissionCatalog] = React.useState<ServicePermission[]>([]);
|
|
178
|
+
const availablePermissions = React.useMemo(
|
|
179
|
+
() => maximumPermissions.map((label) =>
|
|
180
|
+
servicePermissionCatalog.find((permission) => permission.label === label) ?? {
|
|
181
|
+
id: label,
|
|
182
|
+
label,
|
|
183
|
+
description: '',
|
|
184
|
+
groups: [],
|
|
185
|
+
}),
|
|
186
|
+
[maximumPermissions, servicePermissionCatalog],
|
|
187
|
+
);
|
|
156
188
|
const [showPermissionForm, setShowPermissionForm] = React.useState(false);
|
|
157
189
|
const [isPermissionFormOpen, setIsPermissionFormOpen] = React.useState(false);
|
|
158
190
|
const [isAITLoading, setIsAITLoading] = React.useState(false);
|
|
@@ -161,6 +193,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
161
193
|
const [walletInfo, setWalletInfo] = React.useState<any>(null);
|
|
162
194
|
const [isWalletLoading, setIsWalletLoading] = React.useState(false);
|
|
163
195
|
const [isAutoConnecting, setIsAutoConnecting] = React.useState(false);
|
|
196
|
+
const runtimeRolesKey = JSON.stringify(roles ?? null);
|
|
164
197
|
|
|
165
198
|
// Persistent data (always use localStorage)
|
|
166
199
|
const [nxtlinqAITServiceAccessToken, setNxtlinqAITServiceAccessToken] = useLocalStorage<string>('nxtlinqAITServiceAccessToken', '');
|
|
@@ -217,6 +250,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
217
250
|
const hitAddressRef = React.useRef(hitAddress);
|
|
218
251
|
const aitRef = React.useRef(ait);
|
|
219
252
|
const permissionsRef = React.useRef(permissions);
|
|
253
|
+
const deniedPermissionsRef = React.useRef(deniedPermissions);
|
|
220
254
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
221
255
|
const signerRef = React.useRef(signer);
|
|
222
256
|
const authorizationLoadingStateRef = React.useRef(
|
|
@@ -238,6 +272,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
238
272
|
);
|
|
239
273
|
});
|
|
240
274
|
}, []);
|
|
275
|
+
const runtimeRecomputeCoordinatorRef = React.useRef(new RuntimeAITRecomputeCoordinator());
|
|
276
|
+
const runtimeMutationQueueRef = React.useRef(new RuntimeAITMutationQueue());
|
|
277
|
+
useProviderRuntimeCommit(
|
|
278
|
+
hitAddressRef,
|
|
279
|
+
hitAddress,
|
|
280
|
+
environment,
|
|
281
|
+
setApiHosts,
|
|
282
|
+
);
|
|
241
283
|
|
|
242
284
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
243
285
|
const getFinalCustomUsername = React.useCallback((username: string | undefined): string | undefined => {
|
|
@@ -268,6 +310,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
268
310
|
const textInputRef = React.useRef<HTMLInputElement | null>(null);
|
|
269
311
|
const lastPartialRangeRef = React.useRef<{ start: number; end: number } | null>(null);
|
|
270
312
|
const lastAutoSentTranscriptRef = React.useRef<string>('');
|
|
313
|
+
const presetSendInFlightRef = React.useRef(false);
|
|
271
314
|
const autoSendTimerRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
272
315
|
const isCorrectingRef = React.useRef(false);
|
|
273
316
|
const lastCustomErrorRef = React.useRef<string | undefined>(undefined);
|
|
@@ -570,10 +613,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
570
613
|
}, []);
|
|
571
614
|
|
|
572
615
|
// Update refs when state changes
|
|
573
|
-
React.useEffect(() => {
|
|
574
|
-
hitAddressRef.current = hitAddress;
|
|
575
|
-
}, [hitAddress]);
|
|
576
|
-
|
|
577
616
|
React.useEffect(() => {
|
|
578
617
|
aitRef.current = ait;
|
|
579
618
|
}, [ait]);
|
|
@@ -590,6 +629,10 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
590
629
|
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
591
630
|
}, [isAutoConnecting]);
|
|
592
631
|
|
|
632
|
+
React.useEffect(() => {
|
|
633
|
+
deniedPermissionsRef.current = deniedPermissions;
|
|
634
|
+
}, [deniedPermissions]);
|
|
635
|
+
|
|
593
636
|
React.useEffect(() => {
|
|
594
637
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
595
638
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -1020,7 +1063,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1020
1063
|
console.error('Failed to fetch permissions:', result.error);
|
|
1021
1064
|
return undefined;
|
|
1022
1065
|
}
|
|
1023
|
-
|
|
1066
|
+
setServicePermissionCatalog(result.permissions);
|
|
1024
1067
|
return result.permissions;
|
|
1025
1068
|
} catch (error) {
|
|
1026
1069
|
console.error('Error fetching permissions:', error);
|
|
@@ -1028,7 +1071,109 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1028
1071
|
}
|
|
1029
1072
|
};
|
|
1030
1073
|
|
|
1031
|
-
|
|
1074
|
+
const clearRuntimeAuthorization = React.useCallback(() => {
|
|
1075
|
+
const cleared = unavailableRuntimeAuthorization(deniedPermissionsRef.current);
|
|
1076
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
1077
|
+
aitRef.current = null;
|
|
1078
|
+
permissionsRef.current = [];
|
|
1079
|
+
setAit(cleared.ait);
|
|
1080
|
+
setPermissions(cleared.effectivePermissions);
|
|
1081
|
+
setMaximumPermissions(cleared.maximumPermissions);
|
|
1082
|
+
setRuntimePermissionsUnavailable(cleared.unavailable);
|
|
1083
|
+
setRuntimeAuthorizationContextKey(
|
|
1084
|
+
activeContext ? runtimeAITContextKey(activeContext) : null,
|
|
1085
|
+
);
|
|
1086
|
+
}, []);
|
|
1087
|
+
|
|
1088
|
+
const applyRuntimeResult = React.useCallback((result: RuntimeAITResult, context: RuntimeAITContext) => {
|
|
1089
|
+
const next = runtimeAuthorizationState(result, context.controller, context.serviceId);
|
|
1090
|
+
aitRef.current = next.ait;
|
|
1091
|
+
permissionsRef.current = next.effectivePermissions;
|
|
1092
|
+
setAit(next.ait);
|
|
1093
|
+
setPermissions(next.effectivePermissions);
|
|
1094
|
+
setMaximumPermissions(next.maximumPermissions);
|
|
1095
|
+
setDeniedPermissions(next.deniedPermissions);
|
|
1096
|
+
deniedPermissionsRef.current = next.deniedPermissions;
|
|
1097
|
+
setRuntimePermissionsUnavailable(false);
|
|
1098
|
+
setRuntimeAuthorizationContextKey(runtimeAITContextKey(context));
|
|
1099
|
+
return result;
|
|
1100
|
+
}, []);
|
|
1101
|
+
|
|
1102
|
+
const runtimeContext = React.useMemo<RuntimeAITContext | null>(() => hitAddress ? {
|
|
1103
|
+
controller: hitAddress,
|
|
1104
|
+
serviceId,
|
|
1105
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1106
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1107
|
+
} : null, [hitAddress, permissionGroup, runtimeRolesKey, serviceId]);
|
|
1108
|
+
const invalidateCommittedRuntimeContext = React.useCallback(() => {
|
|
1109
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
1110
|
+
aitRef.current = null;
|
|
1111
|
+
permissionsRef.current = [];
|
|
1112
|
+
deniedPermissionsRef.current = [];
|
|
1113
|
+
setAit(null);
|
|
1114
|
+
setPermissions([]);
|
|
1115
|
+
setMaximumPermissions([]);
|
|
1116
|
+
setDeniedPermissions([]);
|
|
1117
|
+
setRuntimePermissionsUnavailable(activeContext !== null);
|
|
1118
|
+
setRuntimeAuthorizationContextKey(
|
|
1119
|
+
activeContext ? runtimeAITContextKey(activeContext) : null,
|
|
1120
|
+
);
|
|
1121
|
+
}, []);
|
|
1122
|
+
useRuntimeAITContextCommit(
|
|
1123
|
+
runtimeRecomputeCoordinatorRef.current,
|
|
1124
|
+
runtimeContext,
|
|
1125
|
+
invalidateCommittedRuntimeContext,
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
const recomputeRuntimeAIT = React.useCallback(async (context: RuntimeAITContext) => {
|
|
1129
|
+
const capturedContext: RuntimeAITContext = {
|
|
1130
|
+
controller: context.controller,
|
|
1131
|
+
serviceId: context.serviceId,
|
|
1132
|
+
...(context.roles !== undefined ? { roles: [...context.roles] } : {}),
|
|
1133
|
+
...(context.permissionGroup !== undefined ? { permissionGroup: context.permissionGroup } : {}),
|
|
1134
|
+
};
|
|
1135
|
+
return runtimeRecomputeCoordinatorRef.current.run(
|
|
1136
|
+
capturedContext,
|
|
1137
|
+
() => nxtlinqApi.ait.recomputeAIT(capturedContext),
|
|
1138
|
+
applyRuntimeResult,
|
|
1139
|
+
clearRuntimeAuthorization,
|
|
1140
|
+
);
|
|
1141
|
+
}, [applyRuntimeResult, clearRuntimeAuthorization, nxtlinqApi]);
|
|
1142
|
+
|
|
1143
|
+
const queueRuntimeAITRefresh = React.useCallback(
|
|
1144
|
+
(context: RuntimeAITContext) => runtimeMutationQueueRef.current.run(
|
|
1145
|
+
runtimeAITSubjectKey(context),
|
|
1146
|
+
async () => {
|
|
1147
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
1148
|
+
return recomputeRuntimeAIT(context);
|
|
1149
|
+
},
|
|
1150
|
+
),
|
|
1151
|
+
[recomputeRuntimeAIT],
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const convergeRuntimeAITSubject = React.useCallback(async (subjectKey: string) => {
|
|
1155
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
1156
|
+
if (!activeContext || runtimeAITSubjectKey(activeContext) !== subjectKey) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
await recomputeRuntimeAIT(activeContext);
|
|
1160
|
+
}, [recomputeRuntimeAIT]);
|
|
1161
|
+
|
|
1162
|
+
const dispatchRuntimeDenyListReplacement = React.useCallback(async (
|
|
1163
|
+
context: RuntimeAITContext,
|
|
1164
|
+
nextDeniedPermissions: string[],
|
|
1165
|
+
) => dispatchRuntimeAITCompleteReplacement(
|
|
1166
|
+
runtimeRecomputeCoordinatorRef.current,
|
|
1167
|
+
context,
|
|
1168
|
+
() => nxtlinqApi.ait.updatePermissionDenyList({
|
|
1169
|
+
...context,
|
|
1170
|
+
deniedPermissions: nextDeniedPermissions,
|
|
1171
|
+
}),
|
|
1172
|
+
convergeRuntimeAITSubject,
|
|
1173
|
+
clearRuntimeAuthorization,
|
|
1174
|
+
), [clearRuntimeAuthorization, convergeRuntimeAITSubject, nxtlinqApi]);
|
|
1175
|
+
|
|
1176
|
+
// Refresh the authoritative runtime AIT state.
|
|
1032
1177
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
1033
1178
|
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1034
1179
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
@@ -1037,8 +1182,10 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1037
1182
|
permissionsRef.current = [];
|
|
1038
1183
|
setAit(null);
|
|
1039
1184
|
setPermissions([]);
|
|
1185
|
+
setMaximumPermissions([]);
|
|
1040
1186
|
setWalletInfo(null);
|
|
1041
1187
|
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1188
|
+
setRuntimePermissionsUnavailable(false);
|
|
1042
1189
|
setIsAITLoading(false);
|
|
1043
1190
|
return;
|
|
1044
1191
|
}
|
|
@@ -1070,53 +1217,23 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1070
1217
|
}
|
|
1071
1218
|
}
|
|
1072
1219
|
|
|
1073
|
-
// AIT existence is not identity verification. In strict mode, stop before
|
|
1074
|
-
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
1075
1220
|
if (currentToken && !walletAllowsAITLookup) {
|
|
1076
|
-
|
|
1077
|
-
permissionsRef.current = [];
|
|
1078
|
-
setAit(null);
|
|
1079
|
-
setPermissions([]);
|
|
1221
|
+
clearRuntimeAuthorization();
|
|
1080
1222
|
return;
|
|
1081
1223
|
}
|
|
1082
1224
|
|
|
1083
|
-
//
|
|
1225
|
+
// Recompute is the sole mint/reuse path and the only permission authority.
|
|
1084
1226
|
if (currentToken) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
controller: currentHitAddress,
|
|
1088
|
-
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
1089
|
-
}, currentToken);
|
|
1090
|
-
|
|
1091
|
-
if ('error' in response) {
|
|
1092
|
-
console.error('Failed to fetch AIT:', response.error);
|
|
1093
|
-
aitRef.current = null;
|
|
1094
|
-
permissionsRef.current = [];
|
|
1095
|
-
setAit(null);
|
|
1096
|
-
setPermissions([]);
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
aitRef.current = response;
|
|
1101
|
-
setAit(response);
|
|
1102
|
-
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
1103
|
-
const newPermissions = response.metadata?.permissions || [];
|
|
1104
|
-
permissionsRef.current = newPermissions;
|
|
1105
|
-
setPermissions(newPermissions);
|
|
1227
|
+
if (runtimeContext) {
|
|
1228
|
+
await queueRuntimeAITRefresh(runtimeContext);
|
|
1106
1229
|
}
|
|
1107
1230
|
} else {
|
|
1108
|
-
|
|
1109
|
-
aitRef.current = null;
|
|
1110
|
-
permissionsRef.current = [];
|
|
1111
|
-
setAit(null);
|
|
1112
|
-
setPermissions([]);
|
|
1231
|
+
clearRuntimeAuthorization();
|
|
1113
1232
|
}
|
|
1114
1233
|
} catch (error) {
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
setAit(null);
|
|
1119
|
-
setPermissions([]);
|
|
1234
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
1235
|
+
console.error('Failed to recompute AIT:', error);
|
|
1236
|
+
}
|
|
1120
1237
|
} finally {
|
|
1121
1238
|
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
1122
1239
|
// React may not commit setState until the immediate pending-tool retry.
|
|
@@ -1321,10 +1438,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1321
1438
|
const { accessToken } = response;
|
|
1322
1439
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1323
1440
|
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1441
|
+
localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
|
|
1442
|
+
await queueRuntimeAITRefresh({
|
|
1443
|
+
controller: addressToUse,
|
|
1444
|
+
serviceId,
|
|
1445
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1446
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1447
|
+
});
|
|
1328
1448
|
|
|
1329
1449
|
if (autoShowSuccessMessage) {
|
|
1330
1450
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
@@ -1363,6 +1483,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1363
1483
|
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1364
1484
|
},
|
|
1365
1485
|
...authFields(),
|
|
1486
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1487
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1366
1488
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1367
1489
|
? getFinalCustomUsername(customUsername)
|
|
1368
1490
|
: undefined,
|
|
@@ -1433,7 +1555,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1433
1555
|
return effectiveAvailableModels[safeIndex];
|
|
1434
1556
|
}, [effectiveAvailableModels, selectedModelIndex]);
|
|
1435
1557
|
|
|
1436
|
-
const updateSuggestions = React.useCallback(async (
|
|
1558
|
+
const updateSuggestions = React.useCallback(async (
|
|
1559
|
+
pseudoId: string,
|
|
1560
|
+
externalId?: string,
|
|
1561
|
+
lastUserMessage?: string,
|
|
1562
|
+
) => {
|
|
1437
1563
|
const result = await nxtlinqApi.agent.generateSuggestions({
|
|
1438
1564
|
...authFields(),
|
|
1439
1565
|
pseudoId,
|
|
@@ -1446,11 +1572,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1446
1572
|
return;
|
|
1447
1573
|
}
|
|
1448
1574
|
|
|
1449
|
-
setSuggestions(
|
|
1450
|
-
text: sug,
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}, [])
|
|
1575
|
+
setSuggestions(filterSuggestions(
|
|
1576
|
+
result.suggestions.map((sug: string) => ({ text: sug, autoSend: true })),
|
|
1577
|
+
lastUserMessage,
|
|
1578
|
+
));
|
|
1579
|
+
}, [nxtlinqApi, authFields, setSuggestions])
|
|
1454
1580
|
|
|
1455
1581
|
// Updated sendMessage function to support different AI models and attachments
|
|
1456
1582
|
const sendMessage = async (
|
|
@@ -1586,15 +1712,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1586
1712
|
const updated = [...prev];
|
|
1587
1713
|
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1588
1714
|
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
};
|
|
1715
|
+
if (step === 'scan_complete') {
|
|
1716
|
+
// PII work is complete independently of any later tool
|
|
1717
|
+
// authorization. Never leave the bubble in Sending while
|
|
1718
|
+
// waiting for wallet recovery or permission elevation.
|
|
1719
|
+
updated[i] = completePiiScan(updated[i], data);
|
|
1720
|
+
} else {
|
|
1721
|
+
updated[i] = { ...updated[i], piiStep };
|
|
1596
1722
|
}
|
|
1597
|
-
updated[i] = { ...updated[i], ...patch };
|
|
1598
1723
|
break;
|
|
1599
1724
|
}
|
|
1600
1725
|
}
|
|
@@ -1603,6 +1728,27 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1603
1728
|
} : undefined,
|
|
1604
1729
|
});
|
|
1605
1730
|
|
|
1731
|
+
// PII scanning is complete when the Agent response arrives, regardless
|
|
1732
|
+
// of whether a later frontend-tool authorization step can continue.
|
|
1733
|
+
if (piiDisplayMode === 'redacted') {
|
|
1734
|
+
const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
|
|
1735
|
+
const piiMappingData = response.piiProtection?.mapping ?? undefined;
|
|
1736
|
+
setMessages(prev => {
|
|
1737
|
+
const updated = [...prev];
|
|
1738
|
+
for (let i = updated.length - 1; i >= 0; i--) {
|
|
1739
|
+
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
1740
|
+
updated[i] = completePiiScan(updated[i], {
|
|
1741
|
+
entityCount: piiMappingData ? Object.keys(piiMappingData).length : 0,
|
|
1742
|
+
anonymizedUserMessage: anonymizedUserMsg,
|
|
1743
|
+
mapping: piiMappingData,
|
|
1744
|
+
});
|
|
1745
|
+
break;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return updated;
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1606
1752
|
if (!('error' in response && (response as { error?: string }).error)) {
|
|
1607
1753
|
const tr = response as AgentResponse;
|
|
1608
1754
|
if (
|
|
@@ -1732,7 +1878,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1732
1878
|
replyText = 'Sorry, I cannot understand your question';
|
|
1733
1879
|
}
|
|
1734
1880
|
|
|
1735
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
1881
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1736
1882
|
|
|
1737
1883
|
setMessages(prev => prev.map(m =>
|
|
1738
1884
|
m.id === streamAssistantId
|
|
@@ -1893,7 +2039,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1893
2039
|
}
|
|
1894
2040
|
}
|
|
1895
2041
|
|
|
1896
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
2042
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1897
2043
|
|
|
1898
2044
|
// Skip creating a new botResponse since we already updated the streaming message
|
|
1899
2045
|
} else {
|
|
@@ -1933,7 +2079,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1933
2079
|
}
|
|
1934
2080
|
}
|
|
1935
2081
|
|
|
1936
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
2082
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1937
2083
|
const newBotResponse: Message = {
|
|
1938
2084
|
id: (Date.now() + 1).toString(),
|
|
1939
2085
|
content: mergedContent,
|
|
@@ -1959,7 +2105,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1959
2105
|
.join(' ') || 'Sorry, I cannot understand your question'
|
|
1960
2106
|
: response.reply || 'Sorry, I cannot understand your question';
|
|
1961
2107
|
|
|
1962
|
-
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
|
|
2108
|
+
updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1963
2109
|
|
|
1964
2110
|
const newBotResponse: Message = {
|
|
1965
2111
|
id: (Date.now() + 1).toString(),
|
|
@@ -1999,38 +2145,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1999
2145
|
botResponse = newBotResponse;
|
|
2000
2146
|
}
|
|
2001
2147
|
|
|
2002
|
-
// ===== PII Protection: Update user message with anonymized version =====
|
|
2003
|
-
const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
|
|
2004
|
-
const piiMappingData = response.piiProtection?.mapping ?? undefined;
|
|
2005
|
-
if (anonymizedUserMsg) {
|
|
2006
|
-
setMessages(prev => {
|
|
2007
|
-
const updated = [...prev];
|
|
2008
|
-
for (let i = updated.length - 1; i >= 0; i--) {
|
|
2009
|
-
if (updated[i].role === 'user') {
|
|
2010
|
-
updated[i] = {
|
|
2011
|
-
...updated[i],
|
|
2012
|
-
piiProtection: { anonymizedContent: anonymizedUserMsg, mapping: piiMappingData },
|
|
2013
|
-
piiStatus: 'complete',
|
|
2014
|
-
};
|
|
2015
|
-
break;
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
return updated;
|
|
2019
|
-
});
|
|
2020
|
-
} else if (piiDisplayMode === 'redacted') {
|
|
2021
|
-
// No PII detected — set piiStatus to 'none' to trigger "No sensitive data" indicator
|
|
2022
|
-
setMessages(prev => {
|
|
2023
|
-
const updated = [...prev];
|
|
2024
|
-
for (let i = updated.length - 1; i >= 0; i--) {
|
|
2025
|
-
if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
|
|
2026
|
-
updated[i] = { ...updated[i], piiStatus: 'none' };
|
|
2027
|
-
break;
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
return updated;
|
|
2031
|
-
});
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
2148
|
// Execute redirect after all message processing is complete
|
|
2035
2149
|
if (redirectUrl) {
|
|
2036
2150
|
// Use setTimeout to ensure the message is displayed before redirect
|
|
@@ -2400,21 +2514,19 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2400
2514
|
}, [nxtlinqApi, authFields, pseudoId]);
|
|
2401
2515
|
|
|
2402
2516
|
// Handle preset message
|
|
2403
|
-
const handlePresetMessage = (message: PresetMessage) => {
|
|
2517
|
+
const handlePresetMessage = async (message: PresetMessage): Promise<void> => {
|
|
2404
2518
|
// If preset is configured as auto-send, avoid duplicate sends when user clicks repeatedly
|
|
2405
2519
|
if (message.autoSend) {
|
|
2406
2520
|
const trimmedText = (message.text || '').trim();
|
|
2407
2521
|
if (!trimmedText) return;
|
|
2408
2522
|
|
|
2409
2523
|
// Prevent sending messages while AI Agent is processing
|
|
2410
|
-
if (isLoading) {
|
|
2524
|
+
if (isLoading || presetSendInFlightRef.current) {
|
|
2411
2525
|
return;
|
|
2412
2526
|
}
|
|
2413
2527
|
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
return;
|
|
2417
|
-
}
|
|
2528
|
+
presetSendInFlightRef.current = true;
|
|
2529
|
+
setSuggestions(previous => filterSuggestions(previous, trimmedText));
|
|
2418
2530
|
|
|
2419
2531
|
// For preset messages, we need to add the user message first since sendMessage won't add it on retries
|
|
2420
2532
|
const userMessage: Message = {
|
|
@@ -2434,114 +2546,16 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2434
2546
|
lastAutoSentTranscriptRef.current = trimmedText;
|
|
2435
2547
|
|
|
2436
2548
|
// Pass a flag to indicate this is a preset message so sendMessage won't add user message again
|
|
2437
|
-
|
|
2549
|
+
try {
|
|
2550
|
+
await sendMessage(trimmedText, 0, true);
|
|
2551
|
+
} finally {
|
|
2552
|
+
presetSendInFlightRef.current = false;
|
|
2553
|
+
}
|
|
2438
2554
|
} else {
|
|
2439
2555
|
setInputValue(message.text);
|
|
2440
2556
|
}
|
|
2441
2557
|
};
|
|
2442
2558
|
|
|
2443
|
-
// Generate and register AIT
|
|
2444
|
-
const generateAndRegisterAIT = async (newPermissions?: string[], isFromAIAgent = false) => {
|
|
2445
|
-
let currentSigner = signer;
|
|
2446
|
-
let currentAddress = hitAddress;
|
|
2447
|
-
|
|
2448
|
-
// If we have an address but no signer, try to auto-connect wallet
|
|
2449
|
-
if (currentAddress && !currentSigner) {
|
|
2450
|
-
const storedWalletAddress = localStorage.getItem('walletAddress');
|
|
2451
|
-
if (storedWalletAddress === currentAddress) {
|
|
2452
|
-
try {
|
|
2453
|
-
const autoConnectResult = await connectWallet(false); // Don't show sign-in message yet
|
|
2454
|
-
if (autoConnectResult) {
|
|
2455
|
-
// Wait for state to update
|
|
2456
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2457
|
-
// Get the updated signer and address
|
|
2458
|
-
currentSigner = signerRef.current || signer;
|
|
2459
|
-
currentAddress = hitAddressRef.current || hitAddress;
|
|
2460
|
-
}
|
|
2461
|
-
} catch (error) {
|
|
2462
|
-
console.error('Auto-connect failed during AIT generation:', error);
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
if (!currentSigner || !currentAddress) {
|
|
2468
|
-
throw new Error('Missing signer or wallet address');
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
return generateAndRegisterAITWithSigner(newPermissions, isFromAIAgent, currentSigner, currentAddress);
|
|
2472
|
-
};
|
|
2473
|
-
|
|
2474
|
-
// Generate and register AIT with explicit signer and address
|
|
2475
|
-
const generateAndRegisterAITWithSigner = async (
|
|
2476
|
-
newPermissions?: string[],
|
|
2477
|
-
isFromAIAgent = false,
|
|
2478
|
-
explicitSigner?: ethers.JsonRpcSigner,
|
|
2479
|
-
explicitAddress?: string
|
|
2480
|
-
) => {
|
|
2481
|
-
const currentSigner = explicitSigner || signer;
|
|
2482
|
-
const currentAddress = explicitAddress || hitAddress;
|
|
2483
|
-
|
|
2484
|
-
if (!currentSigner || !currentAddress) {
|
|
2485
|
-
throw new Error('Missing signer or wallet address');
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
// If AI Agent is creating, must check if user has existing AIT
|
|
2489
|
-
if (isFromAIAgent && !aitRef.current) {
|
|
2490
|
-
throw new Error('You must have an existing AIT before AI Agent can create new AITs');
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
2494
|
-
const aitId = `did:polygon:nxtlinq:${currentAddress}:${timestamp}`;
|
|
2495
|
-
|
|
2496
|
-
const metadata = {
|
|
2497
|
-
permissions: newPermissions || permissions,
|
|
2498
|
-
issuedBy: currentAddress,
|
|
2499
|
-
};
|
|
2500
|
-
|
|
2501
|
-
const metadataStr = stringify(metadata);
|
|
2502
|
-
// ethers v6: utils.keccak256 -> keccak256, utils.toUtf8Bytes -> toUtf8Bytes
|
|
2503
|
-
const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr));
|
|
2504
|
-
|
|
2505
|
-
const uploadResponse = await nxtlinqApi.metadata.createMetadata(metadata, nxtlinqAITServiceAccessToken || '');
|
|
2506
|
-
if ('error' in uploadResponse) {
|
|
2507
|
-
throw new Error(`Failed to upload metadata: ${uploadResponse.error}`);
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
const { metadataCid } = uploadResponse;
|
|
2511
|
-
|
|
2512
|
-
const createAITParams = {
|
|
2513
|
-
aitId,
|
|
2514
|
-
controller: currentAddress,
|
|
2515
|
-
serviceId,
|
|
2516
|
-
metadataHash,
|
|
2517
|
-
metadataCid,
|
|
2518
|
-
isFromAIAgent: isFromAIAgent,
|
|
2519
|
-
parentAITId: isFromAIAgent ? aitRef.current?.aitId : undefined,
|
|
2520
|
-
};
|
|
2521
|
-
|
|
2522
|
-
const createAITResponse = await nxtlinqApi.ait.createAIT(createAITParams, nxtlinqAITServiceAccessToken || '');
|
|
2523
|
-
|
|
2524
|
-
if ('error' in createAITResponse) {
|
|
2525
|
-
throw new Error(`Failed to create AIT: ${createAITResponse.error}`);
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
const aitInfo = {
|
|
2529
|
-
aitId,
|
|
2530
|
-
controller: currentAddress,
|
|
2531
|
-
metadata,
|
|
2532
|
-
metadataHash,
|
|
2533
|
-
metadataCid,
|
|
2534
|
-
};
|
|
2535
|
-
|
|
2536
|
-
const nextPermissions = newPermissions || permissions;
|
|
2537
|
-
aitRef.current = aitInfo;
|
|
2538
|
-
permissionsRef.current = nextPermissions;
|
|
2539
|
-
setAit(aitInfo);
|
|
2540
|
-
setPermissions(nextPermissions);
|
|
2541
|
-
};
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
2559
|
// Auto enable AIT permission
|
|
2546
2560
|
const enableAIT = async (toolName: string) => {
|
|
2547
2561
|
if (isAITEnabling) return false; // Prevent duplicate
|
|
@@ -2608,40 +2622,45 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2608
2622
|
}
|
|
2609
2623
|
|
|
2610
2624
|
try {
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2625
|
+
const context: RuntimeAITContext = {
|
|
2626
|
+
controller: currentHitAddress,
|
|
2627
|
+
serviceId,
|
|
2628
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2629
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2630
|
+
};
|
|
2631
|
+
const enabled = await runtimeMutationQueueRef.current.run(
|
|
2632
|
+
runtimeAITSubjectKey(context),
|
|
2633
|
+
async () => {
|
|
2634
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2635
|
+
const current = await recomputeRuntimeAIT(context);
|
|
2636
|
+
if (!current.maximumPermissions.includes(toolName)) {
|
|
2637
|
+
return false;
|
|
2638
|
+
}
|
|
2639
|
+
const nextDeniedPermissions = removeOneDeniedPermission(
|
|
2640
|
+
current.deniedPermissions,
|
|
2641
|
+
toolName,
|
|
2642
|
+
);
|
|
2643
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2644
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2645
|
+
return true;
|
|
2646
|
+
},
|
|
2647
|
+
);
|
|
2648
|
+
if (!enabled) {
|
|
2614
2649
|
showError(`Tool ${toolName} is not available for your current identity provider`);
|
|
2615
2650
|
setIsAITEnabling(false);
|
|
2616
2651
|
return false;
|
|
2617
2652
|
}
|
|
2618
|
-
|
|
2619
|
-
// Get current permissions from AIT metadata instead of React state
|
|
2620
|
-
// This ensures we don't lose existing permissions when enabling new ones
|
|
2621
|
-
const currentAITPermissions = aitRef.current?.metadata?.permissions || permissions;
|
|
2622
|
-
const newPermissions = [...currentAITPermissions];
|
|
2623
|
-
if (!newPermissions.includes(toolName)) {
|
|
2624
|
-
newPermissions.push(toolName);
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
// Filter out deleted permissions before saving
|
|
2628
|
-
const validPermissions = newPermissions.filter(permission =>
|
|
2629
|
-
availablePermissionLabels.includes(permission)
|
|
2630
|
-
);
|
|
2631
|
-
|
|
2632
|
-
// Generate and register AIT with new permissions
|
|
2633
|
-
// For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
|
|
2634
|
-
const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
|
|
2635
|
-
|
|
2636
|
-
await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
|
|
2637
2653
|
showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
|
|
2638
|
-
await refreshAIT(true);
|
|
2639
2654
|
setIsAITEnabling(false);
|
|
2640
2655
|
return true;
|
|
2641
2656
|
} catch (error) {
|
|
2642
|
-
|
|
2657
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2658
|
+
console.error('Failed to auto-enable AIT:', error);
|
|
2659
|
+
}
|
|
2643
2660
|
setIsAITEnabling(false);
|
|
2644
|
-
if (error instanceof
|
|
2661
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2662
|
+
return false;
|
|
2663
|
+
} else if (error instanceof Error) {
|
|
2645
2664
|
showError(error.message);
|
|
2646
2665
|
} else {
|
|
2647
2666
|
showError('Failed to enable AIT permission. Please try again.');
|
|
@@ -2654,23 +2673,43 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2654
2673
|
const savePermissions = async (newPermissions?: string[]) => {
|
|
2655
2674
|
setIsDisabled(true);
|
|
2656
2675
|
try {
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
const
|
|
2661
|
-
|
|
2676
|
+
if (!hitAddress) {
|
|
2677
|
+
throw new Error('Please connect your wallet first');
|
|
2678
|
+
}
|
|
2679
|
+
const context: RuntimeAITContext = {
|
|
2680
|
+
controller: hitAddress,
|
|
2681
|
+
serviceId,
|
|
2682
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2683
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2684
|
+
};
|
|
2685
|
+
const checkedPermissions = [...(newPermissions ?? permissions)];
|
|
2686
|
+
await runtimeMutationQueueRef.current.run(
|
|
2687
|
+
runtimeAITSubjectKey(context),
|
|
2688
|
+
async () => {
|
|
2689
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2690
|
+
const fresh = await recomputeRuntimeAIT(context);
|
|
2691
|
+
const nextDeniedPermissions = replaceCurrentMaximumDenyList(
|
|
2692
|
+
fresh.deniedPermissions,
|
|
2693
|
+
fresh.maximumPermissions,
|
|
2694
|
+
checkedPermissions,
|
|
2695
|
+
);
|
|
2696
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2697
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2698
|
+
},
|
|
2662
2699
|
);
|
|
2663
|
-
|
|
2664
|
-
await generateAndRegisterAIT(validPermissions, false);
|
|
2665
2700
|
showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
|
|
2666
2701
|
setShowPermissionForm(false);
|
|
2667
2702
|
setIsPermissionFormOpen(false);
|
|
2668
2703
|
await refreshAIT(true);
|
|
2669
2704
|
await retryPendingTextTool();
|
|
2670
2705
|
} catch (error) {
|
|
2671
|
-
|
|
2706
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2707
|
+
console.error('Failed to update AIT deny-list:', error);
|
|
2708
|
+
}
|
|
2672
2709
|
setIsDisabled(false);
|
|
2673
|
-
if (error instanceof
|
|
2710
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2711
|
+
return;
|
|
2712
|
+
} else if (error instanceof Error) {
|
|
2674
2713
|
showError(error.message);
|
|
2675
2714
|
} else {
|
|
2676
2715
|
showError('Failed to save permissions. Please try again.');
|
|
@@ -2719,15 +2758,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2719
2758
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2720
2759
|
if (!('error' in walletResponse)) {
|
|
2721
2760
|
setWalletInfo(walletResponse);
|
|
2722
|
-
|
|
2723
|
-
serviceId,
|
|
2761
|
+
await queueRuntimeAITRefresh({
|
|
2724
2762
|
controller: address,
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
setAit(aitResponse);
|
|
2730
|
-
}
|
|
2763
|
+
serviceId,
|
|
2764
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2765
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2766
|
+
});
|
|
2731
2767
|
}
|
|
2732
2768
|
} finally {
|
|
2733
2769
|
setIsWalletLoading(false);
|
|
@@ -2759,15 +2795,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2759
2795
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2760
2796
|
if (!('error' in walletResponse)) {
|
|
2761
2797
|
setWalletInfo(walletResponse);
|
|
2762
|
-
|
|
2763
|
-
serviceId,
|
|
2798
|
+
await queueRuntimeAITRefresh({
|
|
2764
2799
|
controller: address,
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
setAit(aitResponse);
|
|
2770
|
-
}
|
|
2800
|
+
serviceId,
|
|
2801
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2802
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2803
|
+
});
|
|
2771
2804
|
}
|
|
2772
2805
|
} finally {
|
|
2773
2806
|
setIsWalletLoading(false);
|
|
@@ -2877,7 +2910,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2877
2910
|
if (hitAddress && nxtlinqAITServiceAccessToken) {
|
|
2878
2911
|
refreshAIT();
|
|
2879
2912
|
}
|
|
2880
|
-
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
2913
|
+
}, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
|
|
2881
2914
|
|
|
2882
2915
|
// Set loading state when permission form opens
|
|
2883
2916
|
React.useEffect(() => {
|
|
@@ -2966,6 +2999,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2966
2999
|
updateExternalId()
|
|
2967
3000
|
}, [hitAddress, nxtlinqApi, authFields, pseudoId]);
|
|
2968
3001
|
|
|
3002
|
+
const runtimeAuthorizationMatchesRenderedContext =
|
|
3003
|
+
runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
|
|
2969
3004
|
const contextValue: ChatBotContextType = {
|
|
2970
3005
|
// State
|
|
2971
3006
|
messages,
|
|
@@ -2973,9 +3008,16 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2973
3008
|
isLoading,
|
|
2974
3009
|
isOpen,
|
|
2975
3010
|
hitAddress,
|
|
2976
|
-
ait,
|
|
2977
|
-
permissions,
|
|
2978
|
-
|
|
3011
|
+
ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
|
|
3012
|
+
permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
|
|
3013
|
+
maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
|
|
3014
|
+
deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
|
|
3015
|
+
runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
|
|
3016
|
+
? runtimePermissionsUnavailable
|
|
3017
|
+
: runtimeContext !== null,
|
|
3018
|
+
availablePermissions: runtimeAuthorizationMatchesRenderedContext
|
|
3019
|
+
? availablePermissions
|
|
3020
|
+
: [],
|
|
2979
3021
|
showPermissionForm,
|
|
2980
3022
|
isPermissionFormOpen,
|
|
2981
3023
|
isAITLoading,
|
|
@@ -3012,7 +3054,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3012
3054
|
setIsOpen,
|
|
3013
3055
|
setShowPermissionForm,
|
|
3014
3056
|
setIsPermissionFormOpen,
|
|
3015
|
-
setPermissions,
|
|
3016
3057
|
setIsDisabled,
|
|
3017
3058
|
setIsWalletLoading,
|
|
3018
3059
|
setNotification,
|
|
@@ -3063,6 +3104,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3063
3104
|
onVerifyWallet: (method: 'berifyme' | 'custom') => handleVerifyWalletClick(method),
|
|
3064
3105
|
serviceId,
|
|
3065
3106
|
permissionGroup,
|
|
3107
|
+
roles,
|
|
3066
3108
|
|
|
3067
3109
|
// Props
|
|
3068
3110
|
props: {
|
|
@@ -3081,6 +3123,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3081
3123
|
getAuthToken,
|
|
3082
3124
|
onVerifyWallet,
|
|
3083
3125
|
permissionGroup,
|
|
3126
|
+
roles,
|
|
3084
3127
|
onModelChange,
|
|
3085
3128
|
storageMode,
|
|
3086
3129
|
requireWalletIDVVerification,
|