@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ export type WaitForAITPermissionsOptions<T> = {
2
+ readAIT: () => Promise<T | undefined>;
3
+ getPermissions: (ait: T) => readonly string[];
4
+ expectedPermissions: readonly string[];
5
+ attempts?: number;
6
+ delayMs?: number;
7
+ sleep?: (delayMs: number) => Promise<void>;
8
+ };
9
+ export declare function includesAITPermissions(actualPermissions: readonly string[], expectedPermissions: readonly string[]): boolean;
10
+ /**
11
+ * Wait until the AIT read path can observe a newly submitted permission set.
12
+ * Stale reads are deliberately discarded so callers never downgrade their
13
+ * optimistic local state while the chain/RPC read path is catching up.
14
+ */
15
+ export declare function waitForAITPermissions<T>({ readAIT, getPermissions, expectedPermissions, attempts, delayMs, sleep, }: WaitForAITPermissionsOptions<T>): Promise<T | undefined>;
16
+ //# sourceMappingURL=aitPermissionPropagation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aitPermissionPropagation.d.ts","sourceRoot":"","sources":["../src/aitPermissionPropagation.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,4BAA4B,CAAC,CAAC,IAAI;IAC5C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACtC,cAAc,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC;IAC9C,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,mBAAmB,EAAE,SAAS,MAAM,EAAE,GACrC,OAAO,CAGT;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,EAC7C,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,QAAa,EACb,OAAa,EACb,KAA6E,GAC9E,EAAE,4BAA4B,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAU1D"}
@@ -0,0 +1,21 @@
1
+ export function includesAITPermissions(actualPermissions, expectedPermissions) {
2
+ return actualPermissions.includes('ALL')
3
+ || expectedPermissions.every((permission) => actualPermissions.includes(permission));
4
+ }
5
+ /**
6
+ * Wait until the AIT read path can observe a newly submitted permission set.
7
+ * Stale reads are deliberately discarded so callers never downgrade their
8
+ * optimistic local state while the chain/RPC read path is catching up.
9
+ */
10
+ export async function waitForAITPermissions({ readAIT, getPermissions, expectedPermissions, attempts = 10, delayMs = 750, sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration)), }) {
11
+ const totalAttempts = Math.max(1, attempts);
12
+ for (let attempt = 0; attempt < totalAttempts; attempt += 1) {
13
+ const ait = await readAIT();
14
+ if (ait && includesAITPermissions(getPermissions(ait), expectedPermissions)) {
15
+ return ait;
16
+ }
17
+ if (attempt + 1 < totalAttempts)
18
+ await sleep(delayMs);
19
+ }
20
+ return undefined;
21
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA8B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAw+FlD,CAAC"}
1
+ {"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA8B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAW/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAohGlD,CAAC"}
@@ -6,8 +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';
9
10
  import { AuthorizationLoadingState } from '../authorizationLoadingState';
10
11
  import { PendingTextToolRetryController } from '../pendingTextToolRetry';
12
+ import { completePiiScan } from '../piiMessageState';
13
+ import { filterSuggestions } from '../suggestionState';
11
14
  const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
12
15
  const ChatBotContext = React.createContext(undefined);
13
16
  export const useChatBot = () => {
@@ -187,6 +190,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
187
190
  const textInputRef = React.useRef(null);
188
191
  const lastPartialRangeRef = React.useRef(null);
189
192
  const lastAutoSentTranscriptRef = React.useRef('');
193
+ const presetSendInFlightRef = React.useRef(false);
190
194
  const autoSendTimerRef = React.useRef(null);
191
195
  const isCorrectingRef = React.useRef(false);
192
196
  const lastCustomErrorRef = React.useRef(undefined);
@@ -973,6 +977,48 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
973
977
  setIsAITLoading(false);
974
978
  }
975
979
  };
980
+ const waitForPersistedAITPermissions = async (expectedPermissions) => {
981
+ const currentHitAddress = hitAddressRef.current || hitAddress;
982
+ const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
983
+ if (!currentHitAddress || !currentToken)
984
+ return false;
985
+ authorizationLoadingStateRef.current.setAITLoading(true);
986
+ setIsAITLoading(true);
987
+ try {
988
+ const persistedAIT = await waitForAITPermissions({
989
+ expectedPermissions,
990
+ readAIT: async () => {
991
+ try {
992
+ const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
993
+ serviceId,
994
+ controller: currentHitAddress,
995
+ customUsername: (!requireWalletIDVVerification && customUsername)
996
+ ? getFinalCustomUsername(customUsername)
997
+ : undefined,
998
+ }, currentToken);
999
+ return 'error' in response ? undefined : response;
1000
+ }
1001
+ catch (error) {
1002
+ console.warn('AIT permission propagation check failed:', error);
1003
+ return undefined;
1004
+ }
1005
+ },
1006
+ getPermissions: (currentAIT) => currentAIT.metadata?.permissions || [],
1007
+ });
1008
+ if (!persistedAIT)
1009
+ return false;
1010
+ const persistedPermissions = persistedAIT.metadata?.permissions || [];
1011
+ aitRef.current = persistedAIT;
1012
+ permissionsRef.current = persistedPermissions;
1013
+ setAit(persistedAIT);
1014
+ setPermissions(persistedPermissions);
1015
+ return true;
1016
+ }
1017
+ finally {
1018
+ authorizationLoadingStateRef.current.setAITLoading(false);
1019
+ setIsAITLoading(false);
1020
+ }
1021
+ };
976
1022
  // Check if user needs to sign in
977
1023
  const isNeedSignInWithWallet = React.useMemo(() => {
978
1024
  if (!hitAddress)
@@ -1252,7 +1298,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1252
1298
  }
1253
1299
  return effectiveAvailableModels[safeIndex];
1254
1300
  }, [effectiveAvailableModels, selectedModelIndex]);
1255
- const updateSuggestions = React.useCallback(async (pseudoId, externalId) => {
1301
+ const updateSuggestions = React.useCallback(async (pseudoId, externalId, lastUserMessage) => {
1256
1302
  const result = await nxtlinqApi.agent.generateSuggestions({
1257
1303
  ...authFields(),
1258
1304
  pseudoId,
@@ -1263,11 +1309,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1263
1309
  setSuggestions([]);
1264
1310
  return;
1265
1311
  }
1266
- setSuggestions(result.suggestions.map((sug) => ({
1267
- text: sug,
1268
- autoSend: true
1269
- })));
1270
- }, []);
1312
+ setSuggestions(filterSuggestions(result.suggestions.map((sug) => ({ text: sug, autoSend: true })), lastUserMessage));
1313
+ }, [nxtlinqApi, authFields, setSuggestions]);
1271
1314
  // Updated sendMessage function to support different AI models and attachments
1272
1315
  const sendMessage = async (content, retryCount = 0, isPresetMessage = false, attachments, clientPipelineOverride) => {
1273
1316
  const hasContent = content.trim() || (attachments && attachments.length > 0);
@@ -1387,15 +1430,15 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1387
1430
  const updated = [...prev];
1388
1431
  for (let i = updated.length - 1; i >= 0; i--) {
1389
1432
  if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
1390
- const patch = { piiStep };
1391
- // scan_complete carries anonymized data set piiProtection early for immediate redact
1392
- if (step === 'scan_complete' && data?.anonymizedUserMessage) {
1393
- patch.piiProtection = {
1394
- anonymizedContent: data.anonymizedUserMessage,
1395
- mapping: data.mapping || {},
1396
- };
1433
+ if (step === 'scan_complete') {
1434
+ // PII work is complete independently of any later tool
1435
+ // authorization. Never leave the bubble in Sending while
1436
+ // waiting for wallet recovery or permission elevation.
1437
+ updated[i] = completePiiScan(updated[i], data);
1438
+ }
1439
+ else {
1440
+ updated[i] = { ...updated[i], piiStep };
1397
1441
  }
1398
- updated[i] = { ...updated[i], ...patch };
1399
1442
  break;
1400
1443
  }
1401
1444
  }
@@ -1403,6 +1446,26 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1403
1446
  });
1404
1447
  } : undefined,
1405
1448
  });
1449
+ // PII scanning is complete when the Agent response arrives, regardless
1450
+ // of whether a later frontend-tool authorization step can continue.
1451
+ if (piiDisplayMode === 'redacted') {
1452
+ const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
1453
+ const piiMappingData = response.piiProtection?.mapping ?? undefined;
1454
+ setMessages(prev => {
1455
+ const updated = [...prev];
1456
+ for (let i = updated.length - 1; i >= 0; i--) {
1457
+ if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
1458
+ updated[i] = completePiiScan(updated[i], {
1459
+ entityCount: piiMappingData ? Object.keys(piiMappingData).length : 0,
1460
+ anonymizedUserMessage: anonymizedUserMsg,
1461
+ mapping: piiMappingData,
1462
+ });
1463
+ break;
1464
+ }
1465
+ }
1466
+ return updated;
1467
+ });
1468
+ }
1406
1469
  if (!('error' in response && response.error)) {
1407
1470
  const tr = response;
1408
1471
  if (tr.ttsVoice &&
@@ -1527,7 +1590,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1527
1590
  if (!replyText.trim()) {
1528
1591
  replyText = 'Sorry, I cannot understand your question';
1529
1592
  }
1530
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
1593
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1531
1594
  setMessages(prev => prev.map(m => m.id === streamAssistantId
1532
1595
  ? {
1533
1596
  ...m,
@@ -1666,7 +1729,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1666
1729
  // Don't block the UI if update fails
1667
1730
  }
1668
1731
  }
1669
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
1732
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1670
1733
  // Skip creating a new botResponse since we already updated the streaming message
1671
1734
  }
1672
1735
  else {
@@ -1705,7 +1768,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1705
1768
  // Don't block the UI if update fails
1706
1769
  }
1707
1770
  }
1708
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
1771
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1709
1772
  const newBotResponse = {
1710
1773
  id: (Date.now() + 1).toString(),
1711
1774
  content: mergedContent,
@@ -1731,7 +1794,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1731
1794
  .map((item) => item.text)
1732
1795
  .join(' ') || 'Sorry, I cannot understand your question'
1733
1796
  : response.reply || 'Sorry, I cannot understand your question';
1734
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
1797
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1735
1798
  const newBotResponse = {
1736
1799
  id: (Date.now() + 1).toString(),
1737
1800
  content: replyText,
@@ -1769,38 +1832,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1769
1832
  setMessages(prev => [...prev, newBotResponse]);
1770
1833
  botResponse = newBotResponse;
1771
1834
  }
1772
- // ===== PII Protection: Update user message with anonymized version =====
1773
- const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
1774
- const piiMappingData = response.piiProtection?.mapping ?? undefined;
1775
- if (anonymizedUserMsg) {
1776
- setMessages(prev => {
1777
- const updated = [...prev];
1778
- for (let i = updated.length - 1; i >= 0; i--) {
1779
- if (updated[i].role === 'user') {
1780
- updated[i] = {
1781
- ...updated[i],
1782
- piiProtection: { anonymizedContent: anonymizedUserMsg, mapping: piiMappingData },
1783
- piiStatus: 'complete',
1784
- };
1785
- break;
1786
- }
1787
- }
1788
- return updated;
1789
- });
1790
- }
1791
- else if (piiDisplayMode === 'redacted') {
1792
- // No PII detected — set piiStatus to 'none' to trigger "No sensitive data" indicator
1793
- setMessages(prev => {
1794
- const updated = [...prev];
1795
- for (let i = updated.length - 1; i >= 0; i--) {
1796
- if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
1797
- updated[i] = { ...updated[i], piiStatus: 'none' };
1798
- break;
1799
- }
1800
- }
1801
- return updated;
1802
- });
1803
- }
1804
1835
  // Execute redirect after all message processing is complete
1805
1836
  if (redirectUrl) {
1806
1837
  // Use setTimeout to ensure the message is displayed before redirect
@@ -2141,20 +2172,18 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2141
2172
  return { url: result.url };
2142
2173
  }, [nxtlinqApi, authFields, pseudoId]);
2143
2174
  // Handle preset message
2144
- const handlePresetMessage = (message) => {
2175
+ const handlePresetMessage = async (message) => {
2145
2176
  // If preset is configured as auto-send, avoid duplicate sends when user clicks repeatedly
2146
2177
  if (message.autoSend) {
2147
2178
  const trimmedText = (message.text || '').trim();
2148
2179
  if (!trimmedText)
2149
2180
  return;
2150
2181
  // Prevent sending messages while AI Agent is processing
2151
- if (isLoading) {
2152
- return;
2153
- }
2154
- // If this exact preset text was just sent (by auto-send / manual / preset), skip to prevent duplicates
2155
- if (lastAutoSentTranscriptRef.current === trimmedText) {
2182
+ if (isLoading || presetSendInFlightRef.current) {
2156
2183
  return;
2157
2184
  }
2185
+ presetSendInFlightRef.current = true;
2186
+ setSuggestions(previous => filterSuggestions(previous, trimmedText));
2158
2187
  // For preset messages, we need to add the user message first since sendMessage won't add it on retries
2159
2188
  const userMessage = {
2160
2189
  id: Date.now().toString(),
@@ -2171,7 +2200,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2171
2200
  // Mark as last sent to guard against rapid re-clicks and other duplicate flows
2172
2201
  lastAutoSentTranscriptRef.current = trimmedText;
2173
2202
  // Pass a flag to indicate this is a preset message so sendMessage won't add user message again
2174
- sendMessage(trimmedText, 0, true);
2203
+ try {
2204
+ await sendMessage(trimmedText, 0, true);
2205
+ }
2206
+ finally {
2207
+ presetSendInFlightRef.current = false;
2208
+ }
2175
2209
  }
2176
2210
  else {
2177
2211
  setInputValue(message.text);
@@ -2337,8 +2371,13 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2337
2371
  // For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
2338
2372
  const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
2339
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
+ }
2340
2380
  showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
2341
- await refreshAIT(true);
2342
2381
  setIsAITEnabling(false);
2343
2382
  return true;
2344
2383
  }
@@ -0,0 +1,16 @@
1
+ type PiiMessage = {
2
+ piiStatus?: 'scanning' | 'complete' | 'none';
3
+ piiStep?: 0 | 1 | 2;
4
+ piiProtection?: {
5
+ anonymizedContent?: string;
6
+ mapping?: Record<string, string>;
7
+ };
8
+ };
9
+ export type PiiScanCompleteData = {
10
+ entityCount?: number;
11
+ anonymizedUserMessage?: string | null;
12
+ mapping?: Record<string, string> | null;
13
+ };
14
+ export declare function completePiiScan<T extends PiiMessage>(message: T, data?: PiiScanCompleteData): T;
15
+ export {};
16
+ //# sourceMappingURL=piiMessageState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"piiMessageState.d.ts","sourceRoot":"","sources":["../src/piiMessageState.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG;IAChB,SAAS,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,aAAa,CAAC,EAAE;QACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACzC,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,CAAC,CAmB/F"}
@@ -0,0 +1,18 @@
1
+ export function completePiiScan(message, data) {
2
+ const mapping = data?.mapping || {};
3
+ const entityCount = typeof data?.entityCount === 'number'
4
+ ? data.entityCount
5
+ : Object.keys(mapping).length;
6
+ if (entityCount <= 0 || !data?.anonymizedUserMessage) {
7
+ return { ...message, piiStatus: 'none', piiStep: 1 };
8
+ }
9
+ return {
10
+ ...message,
11
+ piiStatus: 'complete',
12
+ piiStep: 1,
13
+ piiProtection: {
14
+ anonymizedContent: data.anonymizedUserMessage,
15
+ mapping,
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,4 @@
1
+ import type { PresetMessage } from './types/ChatBotTypes';
2
+ export declare function normalizeSuggestionText(value: string): string;
3
+ export declare function filterSuggestions(suggestions: readonly PresetMessage[], lastUserMessage?: string): PresetMessage[];
4
+ //# sourceMappingURL=suggestionState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestionState.d.ts","sourceRoot":"","sources":["../src/suggestionState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,eAAe,CAAC,EAAE,MAAM,GACvB,aAAa,EAAE,CAUjB"}
@@ -0,0 +1,14 @@
1
+ export function normalizeSuggestionText(value) {
2
+ return value.trim().replace(/\s+/g, ' ').toLocaleLowerCase();
3
+ }
4
+ export function filterSuggestions(suggestions, lastUserMessage) {
5
+ const lastUserText = normalizeSuggestionText(lastUserMessage || '');
6
+ const seen = new Set();
7
+ return suggestions.filter((suggestion) => {
8
+ const normalized = normalizeSuggestionText(suggestion.text || '');
9
+ if (!normalized || normalized === lastUserText || seen.has(normalized))
10
+ return false;
11
+ seen.add(normalized);
12
+ return true;
13
+ });
14
+ }
@@ -138,7 +138,7 @@ export interface ChatBotContextType {
138
138
  retryPendingTextTool: () => Promise<boolean>;
139
139
  sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
140
140
  handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
141
- handlePresetMessage: (message: PresetMessage) => void;
141
+ handlePresetMessage: (message: PresetMessage) => Promise<void>;
142
142
  savePermissions: (newPermissions?: string[]) => Promise<void>;
143
143
  enableAIT: (toolName: string) => Promise<boolean>;
144
144
  handleVerifyWalletClick: (method: 'berifyme' | 'custom') => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBotTypes.d.ts","sourceRoot":"","sources":["../../src/types/ChatBotTypes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChatBotTypes.d.ts","sourceRoot":"","sources":["../../src/types/ChatBotTypes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PresetMessages.d.ts","sourceRoot":"","sources":["../../src/ui/PresetMessages.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EA0ClC,CAAC"}
1
+ {"version":3,"file":"PresetMessages.d.ts","sourceRoot":"","sources":["../../src/ui/PresetMessages.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EA4ClC,CAAC"}
@@ -2,9 +2,12 @@ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
2
  import { css } from '@emotion/react';
3
3
  import { useChatBot } from '../context/ChatBotContext';
4
4
  import { actionButton } from './styles/isolatedStyles';
5
+ import { filterSuggestions, normalizeSuggestionText } from '../suggestionState';
5
6
  export const PresetMessages = () => {
6
- const { suggestions, handlePresetMessage } = useChatBot();
7
- if (!suggestions || suggestions.length === 0) {
7
+ const { suggestions, messages, handlePresetMessage } = useChatBot();
8
+ const lastUserMessage = [...messages].reverse().find(message => message.role === 'user')?.content;
9
+ const visibleSuggestions = filterSuggestions(suggestions || [], lastUserMessage);
10
+ if (visibleSuggestions.length === 0) {
8
11
  return null;
9
12
  }
10
13
  return (_jsx("div", { css: css `
@@ -15,7 +18,7 @@ export const PresetMessages = () => {
15
18
  display: flex !important;
16
19
  gap: 10px !important;
17
20
  align-items: center !important;
18
- `, children: suggestions.map((preset, index) => (_jsx("button", { onClick: () => handlePresetMessage(preset), css: css `
21
+ `, children: visibleSuggestions.map((preset) => (_jsx("button", { onClick: () => { void handlePresetMessage(preset); }, css: css `
19
22
  ${actionButton}
20
23
  padding: 5px 10px !important;
21
24
  font-size: 12px !important;
@@ -29,5 +32,5 @@ export const PresetMessages = () => {
29
32
  &:active {
30
33
  background-color: #004085 !important;
31
34
  }
32
- `, children: preset.text }, index))) }));
35
+ `, children: preset.text }, normalizeSuggestionText(preset.text)))) }));
33
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytexbyte/nxtlinq-ai-agent-ui-react-development",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Official React Web UI for nxtlinq AI Agent — drop-in chat widget",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,40 @@
1
+ export type WaitForAITPermissionsOptions<T> = {
2
+ readAIT: () => Promise<T | undefined>;
3
+ getPermissions: (ait: T) => readonly string[];
4
+ expectedPermissions: readonly string[];
5
+ attempts?: number;
6
+ delayMs?: number;
7
+ sleep?: (delayMs: number) => Promise<void>;
8
+ };
9
+
10
+ export function includesAITPermissions(
11
+ actualPermissions: readonly string[],
12
+ expectedPermissions: readonly string[],
13
+ ): boolean {
14
+ return actualPermissions.includes('ALL')
15
+ || expectedPermissions.every((permission) => actualPermissions.includes(permission));
16
+ }
17
+
18
+ /**
19
+ * Wait until the AIT read path can observe a newly submitted permission set.
20
+ * Stale reads are deliberately discarded so callers never downgrade their
21
+ * optimistic local state while the chain/RPC read path is catching up.
22
+ */
23
+ export async function waitForAITPermissions<T>({
24
+ readAIT,
25
+ getPermissions,
26
+ expectedPermissions,
27
+ attempts = 10,
28
+ delayMs = 750,
29
+ sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration)),
30
+ }: WaitForAITPermissionsOptions<T>): Promise<T | undefined> {
31
+ const totalAttempts = Math.max(1, attempts);
32
+ for (let attempt = 0; attempt < totalAttempts; attempt += 1) {
33
+ const ait = await readAIT();
34
+ if (ait && includesAITPermissions(getPermissions(ait), expectedPermissions)) {
35
+ return ait;
36
+ }
37
+ if (attempt + 1 < totalAttempts) await sleep(delayMs);
38
+ }
39
+ return undefined;
40
+ }
@@ -36,8 +36,11 @@ import {
36
36
  ChatBotProps,
37
37
  PresetMessage
38
38
  } from '../types/ChatBotTypes';
39
+ import { waitForAITPermissions } from '../aitPermissionPropagation';
39
40
  import { AuthorizationLoadingState } from '../authorizationLoadingState';
40
41
  import { PendingTextToolRetryController } from '../pendingTextToolRetry';
42
+ import { completePiiScan } from '../piiMessageState';
43
+ import { filterSuggestions } from '../suggestionState';
41
44
 
42
45
  const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
43
46
 
@@ -268,6 +271,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
268
271
  const textInputRef = React.useRef<HTMLInputElement | null>(null);
269
272
  const lastPartialRangeRef = React.useRef<{ start: number; end: number } | null>(null);
270
273
  const lastAutoSentTranscriptRef = React.useRef<string>('');
274
+ const presetSendInFlightRef = React.useRef(false);
271
275
  const autoSendTimerRef = React.useRef<NodeJS.Timeout | null>(null);
272
276
  const isCorrectingRef = React.useRef(false);
273
277
  const lastCustomErrorRef = React.useRef<string | undefined>(undefined);
@@ -1125,6 +1129,50 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1125
1129
  }
1126
1130
  };
1127
1131
 
1132
+ const waitForPersistedAITPermissions = async (
1133
+ expectedPermissions: string[],
1134
+ ): Promise<boolean> => {
1135
+ const currentHitAddress = hitAddressRef.current || hitAddress;
1136
+ const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
1137
+ if (!currentHitAddress || !currentToken) return false;
1138
+
1139
+ authorizationLoadingStateRef.current.setAITLoading(true);
1140
+ setIsAITLoading(true);
1141
+ try {
1142
+ const persistedAIT = await waitForAITPermissions({
1143
+ expectedPermissions,
1144
+ readAIT: async () => {
1145
+ try {
1146
+ const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
1147
+ serviceId,
1148
+ controller: currentHitAddress,
1149
+ customUsername: (!requireWalletIDVVerification && customUsername)
1150
+ ? getFinalCustomUsername(customUsername)
1151
+ : undefined,
1152
+ }, currentToken);
1153
+ return 'error' in response ? undefined : response;
1154
+ } catch (error) {
1155
+ console.warn('AIT permission propagation check failed:', error);
1156
+ return undefined;
1157
+ }
1158
+ },
1159
+ getPermissions: (currentAIT) => currentAIT.metadata?.permissions || [],
1160
+ });
1161
+
1162
+ if (!persistedAIT) return false;
1163
+
1164
+ const persistedPermissions = persistedAIT.metadata?.permissions || [];
1165
+ aitRef.current = persistedAIT;
1166
+ permissionsRef.current = persistedPermissions;
1167
+ setAit(persistedAIT);
1168
+ setPermissions(persistedPermissions);
1169
+ return true;
1170
+ } finally {
1171
+ authorizationLoadingStateRef.current.setAITLoading(false);
1172
+ setIsAITLoading(false);
1173
+ }
1174
+ };
1175
+
1128
1176
  // Check if user needs to sign in
1129
1177
  const isNeedSignInWithWallet = React.useMemo(() => {
1130
1178
  if (!hitAddress) return false;
@@ -1433,7 +1481,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1433
1481
  return effectiveAvailableModels[safeIndex];
1434
1482
  }, [effectiveAvailableModels, selectedModelIndex]);
1435
1483
 
1436
- const updateSuggestions = React.useCallback(async (pseudoId: string, externalId?: string) => {
1484
+ const updateSuggestions = React.useCallback(async (
1485
+ pseudoId: string,
1486
+ externalId?: string,
1487
+ lastUserMessage?: string,
1488
+ ) => {
1437
1489
  const result = await nxtlinqApi.agent.generateSuggestions({
1438
1490
  ...authFields(),
1439
1491
  pseudoId,
@@ -1446,11 +1498,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1446
1498
  return;
1447
1499
  }
1448
1500
 
1449
- setSuggestions(result.suggestions.map((sug: string) => ({
1450
- text: sug,
1451
- autoSend: true
1452
- })));
1453
- }, [])
1501
+ setSuggestions(filterSuggestions(
1502
+ result.suggestions.map((sug: string) => ({ text: sug, autoSend: true })),
1503
+ lastUserMessage,
1504
+ ));
1505
+ }, [nxtlinqApi, authFields, setSuggestions])
1454
1506
 
1455
1507
  // Updated sendMessage function to support different AI models and attachments
1456
1508
  const sendMessage = async (
@@ -1586,15 +1638,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1586
1638
  const updated = [...prev];
1587
1639
  for (let i = updated.length - 1; i >= 0; i--) {
1588
1640
  if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
1589
- const patch: Partial<typeof updated[0]> = { piiStep };
1590
- // scan_complete carries anonymized data set piiProtection early for immediate redact
1591
- if (step === 'scan_complete' && data?.anonymizedUserMessage) {
1592
- patch.piiProtection = {
1593
- anonymizedContent: data.anonymizedUserMessage,
1594
- mapping: data.mapping || {},
1595
- };
1641
+ if (step === 'scan_complete') {
1642
+ // PII work is complete independently of any later tool
1643
+ // authorization. Never leave the bubble in Sending while
1644
+ // waiting for wallet recovery or permission elevation.
1645
+ updated[i] = completePiiScan(updated[i], data);
1646
+ } else {
1647
+ updated[i] = { ...updated[i], piiStep };
1596
1648
  }
1597
- updated[i] = { ...updated[i], ...patch };
1598
1649
  break;
1599
1650
  }
1600
1651
  }
@@ -1603,6 +1654,27 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1603
1654
  } : undefined,
1604
1655
  });
1605
1656
 
1657
+ // PII scanning is complete when the Agent response arrives, regardless
1658
+ // of whether a later frontend-tool authorization step can continue.
1659
+ if (piiDisplayMode === 'redacted') {
1660
+ const anonymizedUserMsg = response.piiProtection?.anonymizedUserMessage;
1661
+ const piiMappingData = response.piiProtection?.mapping ?? undefined;
1662
+ setMessages(prev => {
1663
+ const updated = [...prev];
1664
+ for (let i = updated.length - 1; i >= 0; i--) {
1665
+ if (updated[i].role === 'user' && updated[i].piiStatus === 'scanning') {
1666
+ updated[i] = completePiiScan(updated[i], {
1667
+ entityCount: piiMappingData ? Object.keys(piiMappingData).length : 0,
1668
+ anonymizedUserMessage: anonymizedUserMsg,
1669
+ mapping: piiMappingData,
1670
+ });
1671
+ break;
1672
+ }
1673
+ }
1674
+ return updated;
1675
+ });
1676
+ }
1677
+
1606
1678
  if (!('error' in response && (response as { error?: string }).error)) {
1607
1679
  const tr = response as AgentResponse;
1608
1680
  if (
@@ -1732,7 +1804,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1732
1804
  replyText = 'Sorry, I cannot understand your question';
1733
1805
  }
1734
1806
 
1735
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
1807
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1736
1808
 
1737
1809
  setMessages(prev => prev.map(m =>
1738
1810
  m.id === streamAssistantId
@@ -1893,7 +1965,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1893
1965
  }
1894
1966
  }
1895
1967
 
1896
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
1968
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1897
1969
 
1898
1970
  // Skip creating a new botResponse since we already updated the streaming message
1899
1971
  } else {
@@ -1933,7 +2005,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1933
2005
  }
1934
2006
  }
1935
2007
 
1936
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
2008
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1937
2009
  const newBotResponse: Message = {
1938
2010
  id: (Date.now() + 1).toString(),
1939
2011
  content: mergedContent,
@@ -1959,7 +2031,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1959
2031
  .join(' ') || 'Sorry, I cannot understand your question'
1960
2032
  : response.reply || 'Sorry, I cannot understand your question';
1961
2033
 
1962
- updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined);
2034
+ updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1963
2035
 
1964
2036
  const newBotResponse: Message = {
1965
2037
  id: (Date.now() + 1).toString(),
@@ -1999,38 +2071,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1999
2071
  botResponse = newBotResponse;
2000
2072
  }
2001
2073
 
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
2074
  // Execute redirect after all message processing is complete
2035
2075
  if (redirectUrl) {
2036
2076
  // Use setTimeout to ensure the message is displayed before redirect
@@ -2400,21 +2440,19 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2400
2440
  }, [nxtlinqApi, authFields, pseudoId]);
2401
2441
 
2402
2442
  // Handle preset message
2403
- const handlePresetMessage = (message: PresetMessage) => {
2443
+ const handlePresetMessage = async (message: PresetMessage): Promise<void> => {
2404
2444
  // If preset is configured as auto-send, avoid duplicate sends when user clicks repeatedly
2405
2445
  if (message.autoSend) {
2406
2446
  const trimmedText = (message.text || '').trim();
2407
2447
  if (!trimmedText) return;
2408
2448
 
2409
2449
  // Prevent sending messages while AI Agent is processing
2410
- if (isLoading) {
2450
+ if (isLoading || presetSendInFlightRef.current) {
2411
2451
  return;
2412
2452
  }
2413
2453
 
2414
- // If this exact preset text was just sent (by auto-send / manual / preset), skip to prevent duplicates
2415
- if (lastAutoSentTranscriptRef.current === trimmedText) {
2416
- return;
2417
- }
2454
+ presetSendInFlightRef.current = true;
2455
+ setSuggestions(previous => filterSuggestions(previous, trimmedText));
2418
2456
 
2419
2457
  // For preset messages, we need to add the user message first since sendMessage won't add it on retries
2420
2458
  const userMessage: Message = {
@@ -2434,7 +2472,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2434
2472
  lastAutoSentTranscriptRef.current = trimmedText;
2435
2473
 
2436
2474
  // Pass a flag to indicate this is a preset message so sendMessage won't add user message again
2437
- sendMessage(trimmedText, 0, true);
2475
+ try {
2476
+ await sendMessage(trimmedText, 0, true);
2477
+ } finally {
2478
+ presetSendInFlightRef.current = false;
2479
+ }
2438
2480
  } else {
2439
2481
  setInputValue(message.text);
2440
2482
  }
@@ -2634,8 +2676,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2634
2676
  const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
2635
2677
 
2636
2678
  await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
2679
+ const permissionsPersisted = await waitForPersistedAITPermissions(validPermissions);
2680
+ if (!permissionsPersisted) {
2681
+ showWarning('AIT permission was submitted but is still synchronizing. Please try again shortly.');
2682
+ setIsAITEnabling(false);
2683
+ return false;
2684
+ }
2637
2685
  showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
2638
- await refreshAIT(true);
2639
2686
  setIsAITEnabling(false);
2640
2687
  return true;
2641
2688
  } catch (error) {
@@ -0,0 +1,35 @@
1
+ type PiiMessage = {
2
+ piiStatus?: 'scanning' | 'complete' | 'none';
3
+ piiStep?: 0 | 1 | 2;
4
+ piiProtection?: {
5
+ anonymizedContent?: string;
6
+ mapping?: Record<string, string>;
7
+ };
8
+ };
9
+
10
+ export type PiiScanCompleteData = {
11
+ entityCount?: number;
12
+ anonymizedUserMessage?: string | null;
13
+ mapping?: Record<string, string> | null;
14
+ };
15
+
16
+ export function completePiiScan<T extends PiiMessage>(message: T, data?: PiiScanCompleteData): T {
17
+ const mapping = data?.mapping || {};
18
+ const entityCount = typeof data?.entityCount === 'number'
19
+ ? data.entityCount
20
+ : Object.keys(mapping).length;
21
+
22
+ if (entityCount <= 0 || !data?.anonymizedUserMessage) {
23
+ return { ...message, piiStatus: 'none', piiStep: 1 } as T;
24
+ }
25
+
26
+ return {
27
+ ...message,
28
+ piiStatus: 'complete',
29
+ piiStep: 1,
30
+ piiProtection: {
31
+ anonymizedContent: data.anonymizedUserMessage,
32
+ mapping,
33
+ },
34
+ } as T;
35
+ }
@@ -0,0 +1,20 @@
1
+ import type { PresetMessage } from './types/ChatBotTypes';
2
+
3
+ export function normalizeSuggestionText(value: string): string {
4
+ return value.trim().replace(/\s+/g, ' ').toLocaleLowerCase();
5
+ }
6
+
7
+ export function filterSuggestions(
8
+ suggestions: readonly PresetMessage[],
9
+ lastUserMessage?: string,
10
+ ): PresetMessage[] {
11
+ const lastUserText = normalizeSuggestionText(lastUserMessage || '');
12
+ const seen = new Set<string>();
13
+
14
+ return suggestions.filter((suggestion) => {
15
+ const normalized = normalizeSuggestionText(suggestion.text || '');
16
+ if (!normalized || normalized === lastUserText || seen.has(normalized)) return false;
17
+ seen.add(normalized);
18
+ return true;
19
+ });
20
+ }
@@ -167,7 +167,7 @@ export interface ChatBotContextType {
167
167
  retryPendingTextTool: () => Promise<boolean>;
168
168
  sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
169
169
  handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
170
- handlePresetMessage: (message: PresetMessage) => void;
170
+ handlePresetMessage: (message: PresetMessage) => Promise<void>;
171
171
  savePermissions: (newPermissions?: string[]) => Promise<void>;
172
172
  enableAIT: (toolName: string) => Promise<boolean>;
173
173
  handleVerifyWalletClick: (method: 'berifyme' | 'custom') => Promise<void>;
@@ -4,11 +4,14 @@ import { css } from '@emotion/react';
4
4
  import { useChatBot } from '../context/ChatBotContext';
5
5
  import { PresetMessage } from '../types/ChatBotTypes';
6
6
  import { actionButton } from './styles/isolatedStyles';
7
+ import { filterSuggestions, normalizeSuggestionText } from '../suggestionState';
7
8
 
8
9
  export const PresetMessages: React.FC = () => {
9
- const { suggestions, handlePresetMessage } = useChatBot();
10
+ const { suggestions, messages, handlePresetMessage } = useChatBot();
11
+ const lastUserMessage = [...messages].reverse().find(message => message.role === 'user')?.content;
12
+ const visibleSuggestions = filterSuggestions(suggestions || [], lastUserMessage);
10
13
 
11
- if (!suggestions || suggestions.length === 0) {
14
+ if (visibleSuggestions.length === 0) {
12
15
  return null;
13
16
  }
14
17
 
@@ -22,10 +25,10 @@ export const PresetMessages: React.FC = () => {
22
25
  gap: 10px !important;
23
26
  align-items: center !important;
24
27
  `}>
25
- {suggestions.map((preset: PresetMessage, index: number) => (
28
+ {visibleSuggestions.map((preset: PresetMessage) => (
26
29
  <button
27
- key={index}
28
- onClick={() => handlePresetMessage(preset)}
30
+ key={normalizeSuggestionText(preset.text)}
31
+ onClick={() => { void handlePresetMessage(preset); }}
29
32
  css={css`
30
33
  ${actionButton}
31
34
  padding: 5px 10px !important;
@@ -47,4 +50,4 @@ export const PresetMessages: React.FC = () => {
47
50
  ))}
48
51
  </div>
49
52
  );
50
- };
53
+ };