@aws-amplify/ui-react-ai 1.5.0 → 1.5.2

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.
@@ -2,19 +2,19 @@ const contentFromEvents = (contentBlocks) => {
2
2
  if (!contentBlocks)
3
3
  return [];
4
4
  return contentBlocks.map((contentBlock) => {
5
- const isTextBlock = contentBlock.some((event) => event.text);
5
+ // Filter out sparse array holes from out-of-order direct index assignment
6
+ const events = contentBlock.filter(Boolean);
7
+ const isTextBlock = events.some((event) => event.text);
6
8
  if (isTextBlock) {
7
9
  return {
8
- text: contentBlock
9
- .map((event) => {
10
- return event.text;
11
- })
10
+ text: events
11
+ .map((event) => event.text ?? '')
12
12
  .join(''),
13
13
  };
14
14
  }
15
15
  // tool use is never chunked
16
- if (contentBlock[0].toolUse) {
17
- return { toolUse: contentBlock[0].toolUse };
16
+ if (events[0]?.toolUse) {
17
+ return { toolUse: events[0].toolUse };
18
18
  }
19
19
  });
20
20
  };
@@ -4,6 +4,13 @@ import { isFunction } from '@aws-amplify/ui';
4
4
  import { contentFromEvents } from './contentFromEvents.mjs';
5
5
  import { exhaustivelyListMessages } from './exhaustivelyListMessages.mjs';
6
6
 
7
+ // Synthetic ids used for the messages optimistically added by
8
+ // `handleSendMessage` before the backend assigns real ids. The streamed
9
+ // assistant events carry the real message id, so the first event of a turn
10
+ // won't match by id; we use this to swap the placeholder in place instead of
11
+ // appending a second (empty) assistant bubble.
12
+ const OPTIMISTIC_USER_MESSAGE_ID = 'temp-id';
13
+ const OPTIMISTIC_ASSISTANT_MESSAGE_ID = 'temp-id-2';
7
14
  function hasStarted(state) {
8
15
  return ['initialLoading', 'initialized'].includes(state);
9
16
  }
@@ -174,11 +181,13 @@ function createUseAIConversation(client) {
174
181
  contentBlocksRef.current[contentBlockIndex] = [event];
175
182
  }
176
183
  else {
177
- contentBlocksRef.current[contentBlockIndex] = [
178
- ...currentBlock.slice(0, contentBlockDeltaIndex),
179
- event,
180
- ...currentBlock.slice(contentBlockDeltaIndex),
181
- ];
184
+ // Direct index assignment: idempotent, handles out-of-order and duplicates
185
+ if (contentBlockDeltaIndex !== undefined) {
186
+ currentBlock[contentBlockDeltaIndex] = event;
187
+ }
188
+ else {
189
+ currentBlock.push(event);
190
+ }
182
191
  }
183
192
  }
184
193
  setClientState((prev) => {
@@ -190,13 +199,27 @@ function createUseAIConversation(client) {
190
199
  role: 'assistant',
191
200
  isLoading: true,
192
201
  };
202
+ // Match by real message ID (handles out-of-order / duplicate
203
+ // events for a message we've already started rendering). On the
204
+ // first event of a turn there is no match yet, so fall back to the
205
+ // optimistic assistant placeholder added by handleSendMessage;
206
+ // replacing it in place avoids leaving an empty assistant bubble.
207
+ const existingIndex = prev.data.messages.findIndex((m) => m.id === id);
208
+ const targetIndex = existingIndex >= 0
209
+ ? existingIndex
210
+ : prev.data.messages.findIndex((m) => m.id === OPTIMISTIC_ASSISTANT_MESSAGE_ID);
211
+ const updatedMessages = targetIndex >= 0
212
+ ? [
213
+ ...prev.data.messages.slice(0, targetIndex),
214
+ message,
215
+ ...prev.data.messages.slice(targetIndex + 1),
216
+ ]
217
+ : [...prev.data.messages, message];
193
218
  return {
194
219
  ...prev,
195
220
  data: {
196
221
  ...prev.data,
197
- // TODO: we are assuming we only update the last
198
- // message, but maybe we should match it by message ID?
199
- messages: [...prev.data.messages.slice(0, -1), message],
222
+ messages: updatedMessages,
200
223
  },
201
224
  };
202
225
  });
@@ -235,14 +258,14 @@ function createUseAIConversation(client) {
235
258
  content,
236
259
  role: 'user',
237
260
  createdAt: new Date().toISOString(),
238
- id: 'temp-id',
261
+ id: OPTIMISTIC_USER_MESSAGE_ID,
239
262
  conversationId: conversation.id ?? '',
240
263
  },
241
264
  {
242
265
  content: [{ text: ' ' }],
243
266
  role: 'assistant',
244
267
  createdAt: new Date().toISOString(),
245
- id: 'temp-id-2',
268
+ id: OPTIMISTIC_ASSISTANT_MESSAGE_ID,
246
269
  conversationId: conversation.id ?? '',
247
270
  isLoading: true,
248
271
  },
@@ -1,3 +1,3 @@
1
- const VERSION = '1.5.0';
1
+ const VERSION = '1.5.2';
2
2
 
3
3
  export { VERSION };
package/dist/index.js CHANGED
@@ -1230,7 +1230,7 @@ const PromptList = ({ setInput, suggestedPrompts = [], }) => {
1230
1230
  })));
1231
1231
  };
1232
1232
 
1233
- const VERSION = '1.5.0';
1233
+ const VERSION = '1.5.2';
1234
1234
 
1235
1235
  function AIConversationBase({ avatars, controls, messages, ...rest }) {
1236
1236
  uiReactCore.useSetUserAgent({
@@ -1306,19 +1306,19 @@ const contentFromEvents = (contentBlocks) => {
1306
1306
  if (!contentBlocks)
1307
1307
  return [];
1308
1308
  return contentBlocks.map((contentBlock) => {
1309
- const isTextBlock = contentBlock.some((event) => event.text);
1309
+ // Filter out sparse array holes from out-of-order direct index assignment
1310
+ const events = contentBlock.filter(Boolean);
1311
+ const isTextBlock = events.some((event) => event.text);
1310
1312
  if (isTextBlock) {
1311
1313
  return {
1312
- text: contentBlock
1313
- .map((event) => {
1314
- return event.text;
1315
- })
1314
+ text: events
1315
+ .map((event) => event.text ?? '')
1316
1316
  .join(''),
1317
1317
  };
1318
1318
  }
1319
1319
  // tool use is never chunked
1320
- if (contentBlock[0].toolUse) {
1321
- return { toolUse: contentBlock[0].toolUse };
1320
+ if (events[0]?.toolUse) {
1321
+ return { toolUse: events[0].toolUse };
1322
1322
  }
1323
1323
  });
1324
1324
  };
@@ -1341,6 +1341,13 @@ async function exhaustivelyListMessages({ conversation, messages = [], nextToken
1341
1341
  };
1342
1342
  }
1343
1343
 
1344
+ // Synthetic ids used for the messages optimistically added by
1345
+ // `handleSendMessage` before the backend assigns real ids. The streamed
1346
+ // assistant events carry the real message id, so the first event of a turn
1347
+ // won't match by id; we use this to swap the placeholder in place instead of
1348
+ // appending a second (empty) assistant bubble.
1349
+ const OPTIMISTIC_USER_MESSAGE_ID = 'temp-id';
1350
+ const OPTIMISTIC_ASSISTANT_MESSAGE_ID = 'temp-id-2';
1344
1351
  function hasStarted(state) {
1345
1352
  return ['initialLoading', 'initialized'].includes(state);
1346
1353
  }
@@ -1511,11 +1518,13 @@ function createUseAIConversation(client) {
1511
1518
  contentBlocksRef.current[contentBlockIndex] = [event];
1512
1519
  }
1513
1520
  else {
1514
- contentBlocksRef.current[contentBlockIndex] = [
1515
- ...currentBlock.slice(0, contentBlockDeltaIndex),
1516
- event,
1517
- ...currentBlock.slice(contentBlockDeltaIndex),
1518
- ];
1521
+ // Direct index assignment: idempotent, handles out-of-order and duplicates
1522
+ if (contentBlockDeltaIndex !== undefined) {
1523
+ currentBlock[contentBlockDeltaIndex] = event;
1524
+ }
1525
+ else {
1526
+ currentBlock.push(event);
1527
+ }
1519
1528
  }
1520
1529
  }
1521
1530
  setClientState((prev) => {
@@ -1527,13 +1536,27 @@ function createUseAIConversation(client) {
1527
1536
  role: 'assistant',
1528
1537
  isLoading: true,
1529
1538
  };
1539
+ // Match by real message ID (handles out-of-order / duplicate
1540
+ // events for a message we've already started rendering). On the
1541
+ // first event of a turn there is no match yet, so fall back to the
1542
+ // optimistic assistant placeholder added by handleSendMessage;
1543
+ // replacing it in place avoids leaving an empty assistant bubble.
1544
+ const existingIndex = prev.data.messages.findIndex((m) => m.id === id);
1545
+ const targetIndex = existingIndex >= 0
1546
+ ? existingIndex
1547
+ : prev.data.messages.findIndex((m) => m.id === OPTIMISTIC_ASSISTANT_MESSAGE_ID);
1548
+ const updatedMessages = targetIndex >= 0
1549
+ ? [
1550
+ ...prev.data.messages.slice(0, targetIndex),
1551
+ message,
1552
+ ...prev.data.messages.slice(targetIndex + 1),
1553
+ ]
1554
+ : [...prev.data.messages, message];
1530
1555
  return {
1531
1556
  ...prev,
1532
1557
  data: {
1533
1558
  ...prev.data,
1534
- // TODO: we are assuming we only update the last
1535
- // message, but maybe we should match it by message ID?
1536
- messages: [...prev.data.messages.slice(0, -1), message],
1559
+ messages: updatedMessages,
1537
1560
  },
1538
1561
  };
1539
1562
  });
@@ -1572,14 +1595,14 @@ function createUseAIConversation(client) {
1572
1595
  content,
1573
1596
  role: 'user',
1574
1597
  createdAt: new Date().toISOString(),
1575
- id: 'temp-id',
1598
+ id: OPTIMISTIC_USER_MESSAGE_ID,
1576
1599
  conversationId: conversation.id ?? '',
1577
1600
  },
1578
1601
  {
1579
1602
  content: [{ text: ' ' }],
1580
1603
  role: 'assistant',
1581
1604
  createdAt: new Date().toISOString(),
1582
- id: 'temp-id-2',
1605
+ id: OPTIMISTIC_ASSISTANT_MESSAGE_ID,
1583
1606
  conversationId: conversation.id ?? '',
1584
1607
  isLoading: true,
1585
1608
  },
@@ -1 +1 @@
1
- export declare const VERSION = "1.5.0";
1
+ export declare const VERSION = "1.5.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/ui-react-ai",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/esm/index.mjs",
6
6
  "exports": {
@@ -48,9 +48,9 @@
48
48
  "react-dom": "^16.14 || ^17 || ^18 || ^19"
49
49
  },
50
50
  "dependencies": {
51
- "@aws-amplify/ui": "^6.10.3",
52
- "@aws-amplify/ui-react": "^6.11.2",
53
- "@aws-amplify/ui-react-core": "^3.4.3"
51
+ "@aws-amplify/ui": "^6.15.6",
52
+ "@aws-amplify/ui-react": "^6.15.6",
53
+ "@aws-amplify/ui-react-core": "^3.6.6"
54
54
  },
55
55
  "size-limit": [
56
56
  {