@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.5 → 0.4.8
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 +186 -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 +263 -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,CAwgGlD,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,
|
|
@@ -1634,6 +1652,10 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
1634
1652
|
const isToolAllowed = await hasPermission(toolUse.name);
|
|
1635
1653
|
// Typed preflight already emitted the only permitted recovery UI.
|
|
1636
1654
|
if (!isToolAllowed) {
|
|
1655
|
+
// Do not leave suggestions from the previous turn visible while
|
|
1656
|
+
// authorization recovery short-circuits the normal response path.
|
|
1657
|
+
setSuggestions([]);
|
|
1658
|
+
void updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
|
|
1637
1659
|
pendingTextToolRetryRef.current.set({
|
|
1638
1660
|
content,
|
|
1639
1661
|
isPresetMessage,
|
|
@@ -2211,85 +2233,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2211
2233
|
setInputValue(message.text);
|
|
2212
2234
|
}
|
|
2213
2235
|
};
|
|
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
2236
|
// Auto enable AIT permission
|
|
2294
2237
|
const enableAIT = async (toolName) => {
|
|
2295
2238
|
if (isAITEnabling)
|
|
@@ -2351,40 +2294,41 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2351
2294
|
}
|
|
2352
2295
|
}
|
|
2353
2296
|
try {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2297
|
+
const context = {
|
|
2298
|
+
controller: currentHitAddress,
|
|
2299
|
+
serviceId,
|
|
2300
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2301
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2302
|
+
};
|
|
2303
|
+
const enabled = await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
|
|
2304
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2305
|
+
const current = await recomputeRuntimeAIT(context);
|
|
2306
|
+
if (!current.maximumPermissions.includes(toolName)) {
|
|
2307
|
+
return false;
|
|
2308
|
+
}
|
|
2309
|
+
const nextDeniedPermissions = removeOneDeniedPermission(current.deniedPermissions, toolName);
|
|
2310
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2311
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2312
|
+
return true;
|
|
2313
|
+
});
|
|
2314
|
+
if (!enabled) {
|
|
2357
2315
|
showError(`Tool ${toolName} is not available for your current identity provider`);
|
|
2358
2316
|
setIsAITEnabling(false);
|
|
2359
2317
|
return false;
|
|
2360
2318
|
}
|
|
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
2319
|
showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
|
|
2381
2320
|
setIsAITEnabling(false);
|
|
2382
2321
|
return true;
|
|
2383
2322
|
}
|
|
2384
2323
|
catch (error) {
|
|
2385
|
-
|
|
2324
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2325
|
+
console.error('Failed to auto-enable AIT:', error);
|
|
2326
|
+
}
|
|
2386
2327
|
setIsAITEnabling(false);
|
|
2387
|
-
if (error instanceof
|
|
2328
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2329
|
+
return false;
|
|
2330
|
+
}
|
|
2331
|
+
else if (error instanceof Error) {
|
|
2388
2332
|
showError(error.message);
|
|
2389
2333
|
}
|
|
2390
2334
|
else {
|
|
@@ -2397,11 +2341,23 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2397
2341
|
const savePermissions = async (newPermissions) => {
|
|
2398
2342
|
setIsDisabled(true);
|
|
2399
2343
|
try {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
const
|
|
2404
|
-
|
|
2344
|
+
if (!hitAddress) {
|
|
2345
|
+
throw new Error('Please connect your wallet first');
|
|
2346
|
+
}
|
|
2347
|
+
const context = {
|
|
2348
|
+
controller: hitAddress,
|
|
2349
|
+
serviceId,
|
|
2350
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2351
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2352
|
+
};
|
|
2353
|
+
const checkedPermissions = [...(newPermissions ?? permissions)];
|
|
2354
|
+
await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
|
|
2355
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2356
|
+
const fresh = await recomputeRuntimeAIT(context);
|
|
2357
|
+
const nextDeniedPermissions = replaceCurrentMaximumDenyList(fresh.deniedPermissions, fresh.maximumPermissions, checkedPermissions);
|
|
2358
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2359
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2360
|
+
});
|
|
2405
2361
|
showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
|
|
2406
2362
|
setShowPermissionForm(false);
|
|
2407
2363
|
setIsPermissionFormOpen(false);
|
|
@@ -2409,9 +2365,14 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2409
2365
|
await retryPendingTextTool();
|
|
2410
2366
|
}
|
|
2411
2367
|
catch (error) {
|
|
2412
|
-
|
|
2368
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2369
|
+
console.error('Failed to update AIT deny-list:', error);
|
|
2370
|
+
}
|
|
2413
2371
|
setIsDisabled(false);
|
|
2414
|
-
if (error instanceof
|
|
2372
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
else if (error instanceof Error) {
|
|
2415
2376
|
showError(error.message);
|
|
2416
2377
|
}
|
|
2417
2378
|
else {
|
|
@@ -2457,15 +2418,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2457
2418
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2458
2419
|
if (!('error' in walletResponse)) {
|
|
2459
2420
|
setWalletInfo(walletResponse);
|
|
2460
|
-
|
|
2461
|
-
serviceId,
|
|
2421
|
+
await queueRuntimeAITRefresh({
|
|
2462
2422
|
controller: address,
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
setAit(aitResponse);
|
|
2468
|
-
}
|
|
2423
|
+
serviceId,
|
|
2424
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2425
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2426
|
+
});
|
|
2469
2427
|
}
|
|
2470
2428
|
}
|
|
2471
2429
|
finally {
|
|
@@ -2499,15 +2457,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2499
2457
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2500
2458
|
if (!('error' in walletResponse)) {
|
|
2501
2459
|
setWalletInfo(walletResponse);
|
|
2502
|
-
|
|
2503
|
-
serviceId,
|
|
2460
|
+
await queueRuntimeAITRefresh({
|
|
2504
2461
|
controller: address,
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
setAit(aitResponse);
|
|
2510
|
-
}
|
|
2462
|
+
serviceId,
|
|
2463
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2464
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2465
|
+
});
|
|
2511
2466
|
}
|
|
2512
2467
|
}
|
|
2513
2468
|
finally {
|
|
@@ -2619,7 +2574,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2619
2574
|
if (hitAddress && nxtlinqAITServiceAccessToken) {
|
|
2620
2575
|
refreshAIT();
|
|
2621
2576
|
}
|
|
2622
|
-
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
2577
|
+
}, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
|
|
2623
2578
|
// Set loading state when permission form opens
|
|
2624
2579
|
React.useEffect(() => {
|
|
2625
2580
|
if (isPermissionFormOpen && hitAddress) {
|
|
@@ -2699,6 +2654,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2699
2654
|
};
|
|
2700
2655
|
updateExternalId();
|
|
2701
2656
|
}, [hitAddress, nxtlinqApi, authFields, pseudoId]);
|
|
2657
|
+
const runtimeAuthorizationMatchesRenderedContext = runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
|
|
2702
2658
|
const contextValue = {
|
|
2703
2659
|
// State
|
|
2704
2660
|
messages,
|
|
@@ -2706,9 +2662,16 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2706
2662
|
isLoading,
|
|
2707
2663
|
isOpen,
|
|
2708
2664
|
hitAddress,
|
|
2709
|
-
ait,
|
|
2710
|
-
permissions,
|
|
2711
|
-
|
|
2665
|
+
ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
|
|
2666
|
+
permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
|
|
2667
|
+
maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
|
|
2668
|
+
deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
|
|
2669
|
+
runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
|
|
2670
|
+
? runtimePermissionsUnavailable
|
|
2671
|
+
: runtimeContext !== null,
|
|
2672
|
+
availablePermissions: runtimeAuthorizationMatchesRenderedContext
|
|
2673
|
+
? availablePermissions
|
|
2674
|
+
: [],
|
|
2712
2675
|
showPermissionForm,
|
|
2713
2676
|
isPermissionFormOpen,
|
|
2714
2677
|
isAITLoading,
|
|
@@ -2744,7 +2707,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2744
2707
|
setIsOpen,
|
|
2745
2708
|
setShowPermissionForm,
|
|
2746
2709
|
setIsPermissionFormOpen,
|
|
2747
|
-
setPermissions,
|
|
2748
2710
|
setIsDisabled,
|
|
2749
2711
|
setIsWalletLoading,
|
|
2750
2712
|
setNotification,
|
|
@@ -2793,6 +2755,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2793
2755
|
onVerifyWallet: (method) => handleVerifyWalletClick(method),
|
|
2794
2756
|
serviceId,
|
|
2795
2757
|
permissionGroup,
|
|
2758
|
+
roles,
|
|
2796
2759
|
// Props
|
|
2797
2760
|
props: {
|
|
2798
2761
|
onMessage,
|
|
@@ -2810,6 +2773,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
|
|
|
2810
2773
|
getAuthToken,
|
|
2811
2774
|
onVerifyWallet,
|
|
2812
2775
|
permissionGroup,
|
|
2776
|
+
roles,
|
|
2813
2777
|
onModelChange,
|
|
2814
2778
|
storageMode,
|
|
2815
2779
|
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"}
|