@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.5 → 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/context/ChatBotContext.d.ts.map +1 -1
- package/dist/context/ChatBotContext.js +182 -222
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types/ChatBotTypes.d.ts +6 -5
- 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/package.json +4 -4
- package/src/context/ChatBotContext.tsx +255 -259
- package/src/index.ts +0 -1
- package/src/permissionState.ts +313 -0
- package/src/types/ChatBotTypes.ts +6 -6
- package/src/ui/ModelSelector.tsx +5 -5
- package/src/ui/PermissionForm.tsx +19 -30
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA+B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AA0B/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAggGlD,CAAC"}
|
|
@@ -6,11 +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
9
|
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
11
10
|
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
12
11
|
import { completePiiScan } from '../piiMessageState';
|
|
13
12
|
import { filterSuggestions } from '../suggestionState';
|
|
13
|
+
import { dispatchRuntimeAITCompleteReplacement, removeOneDeniedPermission, replaceCurrentMaximumDenyList, RuntimeAITMutationQueue, RuntimeAITRecomputeCoordinator, StaleRuntimeAITResponseError, runtimeAITContextKey, runtimeAITSubjectKey, runtimeAuthorizationMatchesContext, runtimeAuthorizationState, unavailableRuntimeAuthorization, useProviderRuntimeCommit, useRuntimeAITContextCommit, } from '../permissionState';
|
|
14
14
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
15
15
|
const ChatBotContext = React.createContext(undefined);
|
|
16
16
|
export const useChatBot = () => {
|
|
@@ -20,7 +20,7 @@ export const useChatBot = () => {
|
|
|
20
20
|
}
|
|
21
21
|
return context;
|
|
22
22
|
};
|
|
23
|
-
export const ChatBotProvider = ({ onMessage, onError, onToolUse, presetMessages = [], placeholder = 'Type a message...', className = '', maxRetries = 3, retryDelay = 2000, serviceId, apiKey, apiSecret, serviceToken, getAuthToken, environment = 'production', onVerifyWallet, permissionGroup, children,
|
|
23
|
+
export const ChatBotProvider = ({ onMessage, onError, onToolUse, presetMessages = [], placeholder = 'Type a message...', className = '', maxRetries = 3, retryDelay = 2000, serviceId, apiKey, apiSecret, serviceToken, getAuthToken, environment = 'production', onVerifyWallet, permissionGroup, roles, children,
|
|
24
24
|
// AI Model related attributes
|
|
25
25
|
onModelChange,
|
|
26
26
|
// Storage mode configuration
|
|
@@ -37,8 +37,6 @@ idvBannerDismissSeconds = 86400,
|
|
|
37
37
|
isStopRecordingOnSend = false,
|
|
38
38
|
// Custom error message to display in chat
|
|
39
39
|
customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
40
|
-
// Set API hosts immediately based on environment (before any API calls)
|
|
41
|
-
setApiHosts(environment);
|
|
42
40
|
const nxtlinqApi = React.useMemo(() => (serviceToken || getAuthToken
|
|
43
41
|
? createNxtlinqApi({ apiKey, apiSecret, serviceToken, getAuthToken })
|
|
44
42
|
: createNxtlinqApi(apiKey ?? '', apiSecret ?? '')), [apiKey, apiSecret, serviceToken, getAuthToken]);
|
|
@@ -90,7 +88,17 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
90
88
|
const [hitAddress, setHitAddress] = React.useState(null);
|
|
91
89
|
const [ait, setAit] = React.useState(null);
|
|
92
90
|
const [permissions, setPermissions] = React.useState([]);
|
|
93
|
-
const [
|
|
91
|
+
const [maximumPermissions, setMaximumPermissions] = React.useState([]);
|
|
92
|
+
const [deniedPermissions, setDeniedPermissions] = React.useState([]);
|
|
93
|
+
const [runtimePermissionsUnavailable, setRuntimePermissionsUnavailable] = React.useState(false);
|
|
94
|
+
const [runtimeAuthorizationContextKey, setRuntimeAuthorizationContextKey] = React.useState(null);
|
|
95
|
+
const [servicePermissionCatalog, setServicePermissionCatalog] = React.useState([]);
|
|
96
|
+
const availablePermissions = React.useMemo(() => maximumPermissions.map((label) => servicePermissionCatalog.find((permission) => permission.label === label) ?? {
|
|
97
|
+
id: label,
|
|
98
|
+
label,
|
|
99
|
+
description: '',
|
|
100
|
+
groups: [],
|
|
101
|
+
}), [maximumPermissions, servicePermissionCatalog]);
|
|
94
102
|
const [showPermissionForm, setShowPermissionForm] = React.useState(false);
|
|
95
103
|
const [isPermissionFormOpen, setIsPermissionFormOpen] = React.useState(false);
|
|
96
104
|
const [isAITLoading, setIsAITLoading] = React.useState(false);
|
|
@@ -99,6 +107,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
99
107
|
const [walletInfo, setWalletInfo] = React.useState(null);
|
|
100
108
|
const [isWalletLoading, setIsWalletLoading] = React.useState(false);
|
|
101
109
|
const [isAutoConnecting, setIsAutoConnecting] = React.useState(false);
|
|
110
|
+
const runtimeRolesKey = JSON.stringify(roles ?? null);
|
|
102
111
|
// Persistent data (always use localStorage)
|
|
103
112
|
const [nxtlinqAITServiceAccessToken, setNxtlinqAITServiceAccessToken] = useLocalStorage('nxtlinqAITServiceAccessToken', '');
|
|
104
113
|
const [pseudoId, setPseudoId] = useLocalStorage('pseudoId', uuidv4());
|
|
@@ -147,6 +156,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
147
156
|
const hitAddressRef = React.useRef(hitAddress);
|
|
148
157
|
const aitRef = React.useRef(ait);
|
|
149
158
|
const permissionsRef = React.useRef(permissions);
|
|
159
|
+
const deniedPermissionsRef = React.useRef(deniedPermissions);
|
|
150
160
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
151
161
|
const signerRef = React.useRef(signer);
|
|
152
162
|
const authorizationLoadingStateRef = React.useRef(new AuthorizationLoadingState(isAITLoading, isAutoConnecting));
|
|
@@ -161,6 +171,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
161
171
|
await sendMessageRef.current(request.content, 1, request.isPresetMessage, request.attachments);
|
|
162
172
|
});
|
|
163
173
|
}, []);
|
|
174
|
+
const runtimeRecomputeCoordinatorRef = React.useRef(new RuntimeAITRecomputeCoordinator());
|
|
175
|
+
const runtimeMutationQueueRef = React.useRef(new RuntimeAITMutationQueue());
|
|
176
|
+
useProviderRuntimeCommit(hitAddressRef, hitAddress, environment, setApiHosts);
|
|
164
177
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
165
178
|
const getFinalCustomUsername = React.useCallback((username) => {
|
|
166
179
|
if (!username)
|
|
@@ -480,9 +493,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
480
493
|
}
|
|
481
494
|
}, []);
|
|
482
495
|
// Update refs when state changes
|
|
483
|
-
React.useEffect(() => {
|
|
484
|
-
hitAddressRef.current = hitAddress;
|
|
485
|
-
}, [hitAddress]);
|
|
486
496
|
React.useEffect(() => {
|
|
487
497
|
aitRef.current = ait;
|
|
488
498
|
}, [ait]);
|
|
@@ -495,6 +505,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
495
505
|
React.useEffect(() => {
|
|
496
506
|
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
497
507
|
}, [isAutoConnecting]);
|
|
508
|
+
React.useEffect(() => {
|
|
509
|
+
deniedPermissionsRef.current = deniedPermissions;
|
|
510
|
+
}, [deniedPermissions]);
|
|
498
511
|
React.useEffect(() => {
|
|
499
512
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
500
513
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -874,7 +887,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
874
887
|
console.error('Failed to fetch permissions:', result.error);
|
|
875
888
|
return undefined;
|
|
876
889
|
}
|
|
877
|
-
|
|
890
|
+
setServicePermissionCatalog(result.permissions);
|
|
878
891
|
return result.permissions;
|
|
879
892
|
}
|
|
880
893
|
catch (error) {
|
|
@@ -882,7 +895,74 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
882
895
|
return undefined;
|
|
883
896
|
}
|
|
884
897
|
};
|
|
885
|
-
|
|
898
|
+
const clearRuntimeAuthorization = React.useCallback(() => {
|
|
899
|
+
const cleared = unavailableRuntimeAuthorization(deniedPermissionsRef.current);
|
|
900
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
901
|
+
aitRef.current = null;
|
|
902
|
+
permissionsRef.current = [];
|
|
903
|
+
setAit(cleared.ait);
|
|
904
|
+
setPermissions(cleared.effectivePermissions);
|
|
905
|
+
setMaximumPermissions(cleared.maximumPermissions);
|
|
906
|
+
setRuntimePermissionsUnavailable(cleared.unavailable);
|
|
907
|
+
setRuntimeAuthorizationContextKey(activeContext ? runtimeAITContextKey(activeContext) : null);
|
|
908
|
+
}, []);
|
|
909
|
+
const applyRuntimeResult = React.useCallback((result, context) => {
|
|
910
|
+
const next = runtimeAuthorizationState(result, context.controller, context.serviceId);
|
|
911
|
+
aitRef.current = next.ait;
|
|
912
|
+
permissionsRef.current = next.effectivePermissions;
|
|
913
|
+
setAit(next.ait);
|
|
914
|
+
setPermissions(next.effectivePermissions);
|
|
915
|
+
setMaximumPermissions(next.maximumPermissions);
|
|
916
|
+
setDeniedPermissions(next.deniedPermissions);
|
|
917
|
+
deniedPermissionsRef.current = next.deniedPermissions;
|
|
918
|
+
setRuntimePermissionsUnavailable(false);
|
|
919
|
+
setRuntimeAuthorizationContextKey(runtimeAITContextKey(context));
|
|
920
|
+
return result;
|
|
921
|
+
}, []);
|
|
922
|
+
const runtimeContext = React.useMemo(() => hitAddress ? {
|
|
923
|
+
controller: hitAddress,
|
|
924
|
+
serviceId,
|
|
925
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
926
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
927
|
+
} : null, [hitAddress, permissionGroup, runtimeRolesKey, serviceId]);
|
|
928
|
+
const invalidateCommittedRuntimeContext = React.useCallback(() => {
|
|
929
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
930
|
+
aitRef.current = null;
|
|
931
|
+
permissionsRef.current = [];
|
|
932
|
+
deniedPermissionsRef.current = [];
|
|
933
|
+
setAit(null);
|
|
934
|
+
setPermissions([]);
|
|
935
|
+
setMaximumPermissions([]);
|
|
936
|
+
setDeniedPermissions([]);
|
|
937
|
+
setRuntimePermissionsUnavailable(activeContext !== null);
|
|
938
|
+
setRuntimeAuthorizationContextKey(activeContext ? runtimeAITContextKey(activeContext) : null);
|
|
939
|
+
}, []);
|
|
940
|
+
useRuntimeAITContextCommit(runtimeRecomputeCoordinatorRef.current, runtimeContext, invalidateCommittedRuntimeContext);
|
|
941
|
+
const recomputeRuntimeAIT = React.useCallback(async (context) => {
|
|
942
|
+
const capturedContext = {
|
|
943
|
+
controller: context.controller,
|
|
944
|
+
serviceId: context.serviceId,
|
|
945
|
+
...(context.roles !== undefined ? { roles: [...context.roles] } : {}),
|
|
946
|
+
...(context.permissionGroup !== undefined ? { permissionGroup: context.permissionGroup } : {}),
|
|
947
|
+
};
|
|
948
|
+
return runtimeRecomputeCoordinatorRef.current.run(capturedContext, () => nxtlinqApi.ait.recomputeAIT(capturedContext), applyRuntimeResult, clearRuntimeAuthorization);
|
|
949
|
+
}, [applyRuntimeResult, clearRuntimeAuthorization, nxtlinqApi]);
|
|
950
|
+
const queueRuntimeAITRefresh = React.useCallback((context) => runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
|
|
951
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
952
|
+
return recomputeRuntimeAIT(context);
|
|
953
|
+
}), [recomputeRuntimeAIT]);
|
|
954
|
+
const convergeRuntimeAITSubject = React.useCallback(async (subjectKey) => {
|
|
955
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
956
|
+
if (!activeContext || runtimeAITSubjectKey(activeContext) !== subjectKey) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
await recomputeRuntimeAIT(activeContext);
|
|
960
|
+
}, [recomputeRuntimeAIT]);
|
|
961
|
+
const dispatchRuntimeDenyListReplacement = React.useCallback(async (context, nextDeniedPermissions) => dispatchRuntimeAITCompleteReplacement(runtimeRecomputeCoordinatorRef.current, context, () => nxtlinqApi.ait.updatePermissionDenyList({
|
|
962
|
+
...context,
|
|
963
|
+
deniedPermissions: nextDeniedPermissions,
|
|
964
|
+
}), convergeRuntimeAITSubject, clearRuntimeAuthorization), [clearRuntimeAuthorization, convergeRuntimeAITSubject, nxtlinqApi]);
|
|
965
|
+
// Refresh the authoritative runtime AIT state.
|
|
886
966
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
887
967
|
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
888
968
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
@@ -891,8 +971,10 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
891
971
|
permissionsRef.current = [];
|
|
892
972
|
setAit(null);
|
|
893
973
|
setPermissions([]);
|
|
974
|
+
setMaximumPermissions([]);
|
|
894
975
|
setWalletInfo(null);
|
|
895
976
|
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
977
|
+
setRuntimePermissionsUnavailable(false);
|
|
896
978
|
setIsAITLoading(false);
|
|
897
979
|
return;
|
|
898
980
|
}
|
|
@@ -923,52 +1005,24 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
923
1005
|
console.error('Failed to fetch wallet info:', error);
|
|
924
1006
|
}
|
|
925
1007
|
}
|
|
926
|
-
// AIT existence is not identity verification. In strict mode, stop before
|
|
927
|
-
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
928
1008
|
if (currentToken && !walletAllowsAITLookup) {
|
|
929
|
-
|
|
930
|
-
permissionsRef.current = [];
|
|
931
|
-
setAit(null);
|
|
932
|
-
setPermissions([]);
|
|
1009
|
+
clearRuntimeAuthorization();
|
|
933
1010
|
return;
|
|
934
1011
|
}
|
|
935
|
-
//
|
|
1012
|
+
// Recompute is the sole mint/reuse path and the only permission authority.
|
|
936
1013
|
if (currentToken) {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
controller: currentHitAddress,
|
|
940
|
-
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
941
|
-
}, currentToken);
|
|
942
|
-
if ('error' in response) {
|
|
943
|
-
console.error('Failed to fetch AIT:', response.error);
|
|
944
|
-
aitRef.current = null;
|
|
945
|
-
permissionsRef.current = [];
|
|
946
|
-
setAit(null);
|
|
947
|
-
setPermissions([]);
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
aitRef.current = response;
|
|
951
|
-
setAit(response);
|
|
952
|
-
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
953
|
-
const newPermissions = response.metadata?.permissions || [];
|
|
954
|
-
permissionsRef.current = newPermissions;
|
|
955
|
-
setPermissions(newPermissions);
|
|
1014
|
+
if (runtimeContext) {
|
|
1015
|
+
await queueRuntimeAITRefresh(runtimeContext);
|
|
956
1016
|
}
|
|
957
1017
|
}
|
|
958
1018
|
else {
|
|
959
|
-
|
|
960
|
-
aitRef.current = null;
|
|
961
|
-
permissionsRef.current = [];
|
|
962
|
-
setAit(null);
|
|
963
|
-
setPermissions([]);
|
|
1019
|
+
clearRuntimeAuthorization();
|
|
964
1020
|
}
|
|
965
1021
|
}
|
|
966
1022
|
catch (error) {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
setAit(null);
|
|
971
|
-
setPermissions([]);
|
|
1023
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
1024
|
+
console.error('Failed to recompute AIT:', error);
|
|
1025
|
+
}
|
|
972
1026
|
}
|
|
973
1027
|
finally {
|
|
974
1028
|
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
@@ -977,48 +1031,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
977
1031
|
setIsAITLoading(false);
|
|
978
1032
|
}
|
|
979
1033
|
};
|
|
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);
|
|
1019
|
-
setIsAITLoading(false);
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
1034
|
// Check if user needs to sign in
|
|
1023
1035
|
const isNeedSignInWithWallet = React.useMemo(() => {
|
|
1024
1036
|
if (!hitAddress)
|
|
@@ -1197,9 +1209,13 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1197
1209
|
const { accessToken } = response;
|
|
1198
1210
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1199
1211
|
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1200
|
-
|
|
1201
|
-
await
|
|
1202
|
-
|
|
1212
|
+
localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
|
|
1213
|
+
await queueRuntimeAITRefresh({
|
|
1214
|
+
controller: addressToUse,
|
|
1215
|
+
serviceId,
|
|
1216
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1217
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1218
|
+
});
|
|
1203
1219
|
if (autoShowSuccessMessage) {
|
|
1204
1220
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
1205
1221
|
}
|
|
@@ -1234,6 +1250,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1234
1250
|
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1235
1251
|
},
|
|
1236
1252
|
...authFields(),
|
|
1253
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1254
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1237
1255
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1238
1256
|
? getFinalCustomUsername(customUsername)
|
|
1239
1257
|
: undefined,
|
|
@@ -2211,85 +2229,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2211
2229
|
setInputValue(message.text);
|
|
2212
2230
|
}
|
|
2213
2231
|
};
|
|
2214
|
-
// Generate and register AIT
|
|
2215
|
-
const generateAndRegisterAIT = async (newPermissions, isFromAIAgent = false) => {
|
|
2216
|
-
let currentSigner = signer;
|
|
2217
|
-
let currentAddress = hitAddress;
|
|
2218
|
-
// If we have an address but no signer, try to auto-connect wallet
|
|
2219
|
-
if (currentAddress && !currentSigner) {
|
|
2220
|
-
const storedWalletAddress = localStorage.getItem('walletAddress');
|
|
2221
|
-
if (storedWalletAddress === currentAddress) {
|
|
2222
|
-
try {
|
|
2223
|
-
const autoConnectResult = await connectWallet(false); // Don't show sign-in message yet
|
|
2224
|
-
if (autoConnectResult) {
|
|
2225
|
-
// Wait for state to update
|
|
2226
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2227
|
-
// Get the updated signer and address
|
|
2228
|
-
currentSigner = signerRef.current || signer;
|
|
2229
|
-
currentAddress = hitAddressRef.current || hitAddress;
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
catch (error) {
|
|
2233
|
-
console.error('Auto-connect failed during AIT generation:', error);
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
if (!currentSigner || !currentAddress) {
|
|
2238
|
-
throw new Error('Missing signer or wallet address');
|
|
2239
|
-
}
|
|
2240
|
-
return generateAndRegisterAITWithSigner(newPermissions, isFromAIAgent, currentSigner, currentAddress);
|
|
2241
|
-
};
|
|
2242
|
-
// Generate and register AIT with explicit signer and address
|
|
2243
|
-
const generateAndRegisterAITWithSigner = async (newPermissions, isFromAIAgent = false, explicitSigner, explicitAddress) => {
|
|
2244
|
-
const currentSigner = explicitSigner || signer;
|
|
2245
|
-
const currentAddress = explicitAddress || hitAddress;
|
|
2246
|
-
if (!currentSigner || !currentAddress) {
|
|
2247
|
-
throw new Error('Missing signer or wallet address');
|
|
2248
|
-
}
|
|
2249
|
-
// If AI Agent is creating, must check if user has existing AIT
|
|
2250
|
-
if (isFromAIAgent && !aitRef.current) {
|
|
2251
|
-
throw new Error('You must have an existing AIT before AI Agent can create new AITs');
|
|
2252
|
-
}
|
|
2253
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
2254
|
-
const aitId = `did:polygon:nxtlinq:${currentAddress}:${timestamp}`;
|
|
2255
|
-
const metadata = {
|
|
2256
|
-
permissions: newPermissions || permissions,
|
|
2257
|
-
issuedBy: currentAddress,
|
|
2258
|
-
};
|
|
2259
|
-
const metadataStr = stringify(metadata);
|
|
2260
|
-
// ethers v6: utils.keccak256 -> keccak256, utils.toUtf8Bytes -> toUtf8Bytes
|
|
2261
|
-
const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr));
|
|
2262
|
-
const uploadResponse = await nxtlinqApi.metadata.createMetadata(metadata, nxtlinqAITServiceAccessToken || '');
|
|
2263
|
-
if ('error' in uploadResponse) {
|
|
2264
|
-
throw new Error(`Failed to upload metadata: ${uploadResponse.error}`);
|
|
2265
|
-
}
|
|
2266
|
-
const { metadataCid } = uploadResponse;
|
|
2267
|
-
const createAITParams = {
|
|
2268
|
-
aitId,
|
|
2269
|
-
controller: currentAddress,
|
|
2270
|
-
serviceId,
|
|
2271
|
-
metadataHash,
|
|
2272
|
-
metadataCid,
|
|
2273
|
-
isFromAIAgent: isFromAIAgent,
|
|
2274
|
-
parentAITId: isFromAIAgent ? aitRef.current?.aitId : undefined,
|
|
2275
|
-
};
|
|
2276
|
-
const createAITResponse = await nxtlinqApi.ait.createAIT(createAITParams, nxtlinqAITServiceAccessToken || '');
|
|
2277
|
-
if ('error' in createAITResponse) {
|
|
2278
|
-
throw new Error(`Failed to create AIT: ${createAITResponse.error}`);
|
|
2279
|
-
}
|
|
2280
|
-
const aitInfo = {
|
|
2281
|
-
aitId,
|
|
2282
|
-
controller: currentAddress,
|
|
2283
|
-
metadata,
|
|
2284
|
-
metadataHash,
|
|
2285
|
-
metadataCid,
|
|
2286
|
-
};
|
|
2287
|
-
const nextPermissions = newPermissions || permissions;
|
|
2288
|
-
aitRef.current = aitInfo;
|
|
2289
|
-
permissionsRef.current = nextPermissions;
|
|
2290
|
-
setAit(aitInfo);
|
|
2291
|
-
setPermissions(nextPermissions);
|
|
2292
|
-
};
|
|
2293
2232
|
// Auto enable AIT permission
|
|
2294
2233
|
const enableAIT = async (toolName) => {
|
|
2295
2234
|
if (isAITEnabling)
|
|
@@ -2351,40 +2290,41 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2351
2290
|
}
|
|
2352
2291
|
}
|
|
2353
2292
|
try {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2293
|
+
const context = {
|
|
2294
|
+
controller: currentHitAddress,
|
|
2295
|
+
serviceId,
|
|
2296
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2297
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2298
|
+
};
|
|
2299
|
+
const enabled = await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
|
|
2300
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2301
|
+
const current = await recomputeRuntimeAIT(context);
|
|
2302
|
+
if (!current.maximumPermissions.includes(toolName)) {
|
|
2303
|
+
return false;
|
|
2304
|
+
}
|
|
2305
|
+
const nextDeniedPermissions = removeOneDeniedPermission(current.deniedPermissions, toolName);
|
|
2306
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2307
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2308
|
+
return true;
|
|
2309
|
+
});
|
|
2310
|
+
if (!enabled) {
|
|
2357
2311
|
showError(`Tool ${toolName} is not available for your current identity provider`);
|
|
2358
2312
|
setIsAITEnabling(false);
|
|
2359
2313
|
return false;
|
|
2360
2314
|
}
|
|
2361
|
-
// Get current permissions from AIT metadata instead of React state
|
|
2362
|
-
// This ensures we don't lose existing permissions when enabling new ones
|
|
2363
|
-
const currentAITPermissions = aitRef.current?.metadata?.permissions || permissions;
|
|
2364
|
-
const newPermissions = [...currentAITPermissions];
|
|
2365
|
-
if (!newPermissions.includes(toolName)) {
|
|
2366
|
-
newPermissions.push(toolName);
|
|
2367
|
-
}
|
|
2368
|
-
// Filter out deleted permissions before saving
|
|
2369
|
-
const validPermissions = newPermissions.filter(permission => availablePermissionLabels.includes(permission));
|
|
2370
|
-
// Generate and register AIT with new permissions
|
|
2371
|
-
// For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
|
|
2372
|
-
const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
|
|
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
|
-
}
|
|
2380
2315
|
showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
|
|
2381
2316
|
setIsAITEnabling(false);
|
|
2382
2317
|
return true;
|
|
2383
2318
|
}
|
|
2384
2319
|
catch (error) {
|
|
2385
|
-
|
|
2320
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2321
|
+
console.error('Failed to auto-enable AIT:', error);
|
|
2322
|
+
}
|
|
2386
2323
|
setIsAITEnabling(false);
|
|
2387
|
-
if (error instanceof
|
|
2324
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2325
|
+
return false;
|
|
2326
|
+
}
|
|
2327
|
+
else if (error instanceof Error) {
|
|
2388
2328
|
showError(error.message);
|
|
2389
2329
|
}
|
|
2390
2330
|
else {
|
|
@@ -2397,11 +2337,23 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2397
2337
|
const savePermissions = async (newPermissions) => {
|
|
2398
2338
|
setIsDisabled(true);
|
|
2399
2339
|
try {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
const
|
|
2404
|
-
|
|
2340
|
+
if (!hitAddress) {
|
|
2341
|
+
throw new Error('Please connect your wallet first');
|
|
2342
|
+
}
|
|
2343
|
+
const context = {
|
|
2344
|
+
controller: hitAddress,
|
|
2345
|
+
serviceId,
|
|
2346
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2347
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2348
|
+
};
|
|
2349
|
+
const checkedPermissions = [...(newPermissions ?? permissions)];
|
|
2350
|
+
await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
|
|
2351
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2352
|
+
const fresh = await recomputeRuntimeAIT(context);
|
|
2353
|
+
const nextDeniedPermissions = replaceCurrentMaximumDenyList(fresh.deniedPermissions, fresh.maximumPermissions, checkedPermissions);
|
|
2354
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2355
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2356
|
+
});
|
|
2405
2357
|
showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
|
|
2406
2358
|
setShowPermissionForm(false);
|
|
2407
2359
|
setIsPermissionFormOpen(false);
|
|
@@ -2409,9 +2361,14 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2409
2361
|
await retryPendingTextTool();
|
|
2410
2362
|
}
|
|
2411
2363
|
catch (error) {
|
|
2412
|
-
|
|
2364
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2365
|
+
console.error('Failed to update AIT deny-list:', error);
|
|
2366
|
+
}
|
|
2413
2367
|
setIsDisabled(false);
|
|
2414
|
-
if (error instanceof
|
|
2368
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
else if (error instanceof Error) {
|
|
2415
2372
|
showError(error.message);
|
|
2416
2373
|
}
|
|
2417
2374
|
else {
|
|
@@ -2457,15 +2414,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2457
2414
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2458
2415
|
if (!('error' in walletResponse)) {
|
|
2459
2416
|
setWalletInfo(walletResponse);
|
|
2460
|
-
|
|
2461
|
-
serviceId,
|
|
2417
|
+
await queueRuntimeAITRefresh({
|
|
2462
2418
|
controller: address,
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
setAit(aitResponse);
|
|
2468
|
-
}
|
|
2419
|
+
serviceId,
|
|
2420
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2421
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2422
|
+
});
|
|
2469
2423
|
}
|
|
2470
2424
|
}
|
|
2471
2425
|
finally {
|
|
@@ -2499,15 +2453,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2499
2453
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2500
2454
|
if (!('error' in walletResponse)) {
|
|
2501
2455
|
setWalletInfo(walletResponse);
|
|
2502
|
-
|
|
2503
|
-
serviceId,
|
|
2456
|
+
await queueRuntimeAITRefresh({
|
|
2504
2457
|
controller: address,
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
setAit(aitResponse);
|
|
2510
|
-
}
|
|
2458
|
+
serviceId,
|
|
2459
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2460
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2461
|
+
});
|
|
2511
2462
|
}
|
|
2512
2463
|
}
|
|
2513
2464
|
finally {
|
|
@@ -2619,7 +2570,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2619
2570
|
if (hitAddress && nxtlinqAITServiceAccessToken) {
|
|
2620
2571
|
refreshAIT();
|
|
2621
2572
|
}
|
|
2622
|
-
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
2573
|
+
}, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
|
|
2623
2574
|
// Set loading state when permission form opens
|
|
2624
2575
|
React.useEffect(() => {
|
|
2625
2576
|
if (isPermissionFormOpen && hitAddress) {
|
|
@@ -2699,6 +2650,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2699
2650
|
};
|
|
2700
2651
|
updateExternalId();
|
|
2701
2652
|
}, [hitAddress, nxtlinqApi, authFields, pseudoId]);
|
|
2653
|
+
const runtimeAuthorizationMatchesRenderedContext = runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
|
|
2702
2654
|
const contextValue = {
|
|
2703
2655
|
// State
|
|
2704
2656
|
messages,
|
|
@@ -2706,9 +2658,16 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2706
2658
|
isLoading,
|
|
2707
2659
|
isOpen,
|
|
2708
2660
|
hitAddress,
|
|
2709
|
-
ait,
|
|
2710
|
-
permissions,
|
|
2711
|
-
|
|
2661
|
+
ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
|
|
2662
|
+
permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
|
|
2663
|
+
maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
|
|
2664
|
+
deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
|
|
2665
|
+
runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
|
|
2666
|
+
? runtimePermissionsUnavailable
|
|
2667
|
+
: runtimeContext !== null,
|
|
2668
|
+
availablePermissions: runtimeAuthorizationMatchesRenderedContext
|
|
2669
|
+
? availablePermissions
|
|
2670
|
+
: [],
|
|
2712
2671
|
showPermissionForm,
|
|
2713
2672
|
isPermissionFormOpen,
|
|
2714
2673
|
isAITLoading,
|
|
@@ -2744,7 +2703,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2744
2703
|
setIsOpen,
|
|
2745
2704
|
setShowPermissionForm,
|
|
2746
2705
|
setIsPermissionFormOpen,
|
|
2747
|
-
setPermissions,
|
|
2748
2706
|
setIsDisabled,
|
|
2749
2707
|
setIsWalletLoading,
|
|
2750
2708
|
setNotification,
|
|
@@ -2793,6 +2751,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2793
2751
|
onVerifyWallet: (method) => handleVerifyWalletClick(method),
|
|
2794
2752
|
serviceId,
|
|
2795
2753
|
permissionGroup,
|
|
2754
|
+
roles,
|
|
2796
2755
|
// Props
|
|
2797
2756
|
props: {
|
|
2798
2757
|
onMessage,
|
|
@@ -2810,6 +2769,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2810
2769
|
getAuthToken,
|
|
2811
2770
|
onVerifyWallet,
|
|
2812
2771
|
permissionGroup,
|
|
2772
|
+
roles,
|
|
2813
2773
|
onModelChange,
|
|
2814
2774
|
storageMode,
|
|
2815
2775
|
requireWalletIDVVerification,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { ChatBot } from './ChatBot';
|
|
2
2
|
export { ChatBotProvider, useChatBot } from './context/ChatBotContext';
|
|
3
3
|
export { ChatBotUI, MessageInput, MessageList, ModelSelector, NotificationModal, PermissionForm, PresetMessages, BerifyMeModal, } from './ui/index';
|
|
4
|
-
export type { AIModel,
|
|
4
|
+
export type { AIModel, ChatBotContextType, ChatBotProps, NovaError, NovaResponse, ToolCall, ToolUse, PresetMessage, } from './types/ChatBotTypes';
|
|
5
5
|
export type { AgentEnvironment, AgentConfig, Attachment, AgentResponse, Message, SendMessageOptions, NxtlinqAgentSnapshot, TextToolAuthorizationSnapshot, TextToolAuthorizationOutcome, VoiceSession, VoiceStatus, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
6
6
|
export { NxtlinqAgent, setApiHosts, VoiceNotSupportedError, STORAGE_KEYS, authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
7
7
|
export { createNxtlinqApi, useLocalStorage, useSessionStorage, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, } from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,OAAO,EACP,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,OAAO,EACP,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,oBAAoB,EACpB,6BAA6B,EAC7B,4BAA4B,EAC5B,YAAY,EACZ,WAAW,GACZ,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,sBAAsB,EACtB,YAAY,EACZ,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,KAAK,EACL,eAAe,GAChB,MAAM,6CAA6C,CAAC"}
|