@copilotz/chat-ui 0.9.44 → 0.9.46

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/index.cjs CHANGED
@@ -867,6 +867,10 @@ var hasRenderableAssistantBody = (message) => {
867
867
  if (Array.isArray(message.attachments) && message.attachments.length > 0) return true;
868
868
  return Boolean(message.activity?.items.length);
869
869
  };
870
+ var filterActivity = (message, placement) => {
871
+ const items = message.activity?.items.filter((item) => placement === "after-content" ? item.kind === "tool" : item.kind !== "tool") ?? [];
872
+ return items.length > 0 ? { items } : void 0;
873
+ };
870
874
  var defaultMarkdownComponents = {
871
875
  code: ({ node, className, children, ...props }) => {
872
876
  const inline = props.inline;
@@ -1133,6 +1137,7 @@ var MediaRenderer = (0, import_react2.memo)(function MediaRenderer2({ attachment
1133
1137
  });
1134
1138
  var arePropsEqual = (prevProps, nextProps) => {
1135
1139
  if (prevProps.message !== nextProps.message) return false;
1140
+ if (prevProps.fragments !== nextProps.fragments) return false;
1136
1141
  if (prevProps.isUser !== nextProps.isUser) return false;
1137
1142
  if (prevProps.userAvatar !== nextProps.userAvatar) return false;
1138
1143
  if (prevProps.userName !== nextProps.userName) return false;
@@ -1162,6 +1167,7 @@ var arePropsEqual = (prevProps, nextProps) => {
1162
1167
  };
1163
1168
  var Message = (0, import_react2.memo)(({
1164
1169
  message,
1170
+ fragments,
1165
1171
  isUser,
1166
1172
  userAvatar,
1167
1173
  userName = "Voc\xEA",
@@ -1194,12 +1200,15 @@ var Message = (0, import_react2.memo)(({
1194
1200
  const [showActions, setShowActions] = (0, import_react2.useState)(false);
1195
1201
  const [actionsFocused, setActionsFocused] = (0, import_react2.useState)(false);
1196
1202
  const [copied, setCopied] = (0, import_react2.useState)(false);
1203
+ const sourceMessages = fragments?.length ? fragments : [message];
1204
+ const headerMessage = sourceMessages[0];
1205
+ const groupContent = sourceMessages.map((sourceMessage) => sourceMessage.content.trim()).filter(Boolean).join("\n\n");
1197
1206
  const messageIsUser = isUser ?? message.role === "user";
1198
- if (!hasRenderableAssistantBody(message)) {
1207
+ if (!sourceMessages.some(hasRenderableAssistantBody)) {
1199
1208
  return null;
1200
1209
  }
1201
1210
  const senderDisplay = resolveMessageSenderDisplay({
1202
- sender: message.sender,
1211
+ sender: headerMessage.sender,
1203
1212
  fallbackName: messageIsUser ? userName : assistantName,
1204
1213
  fallbackAvatar: messageIsUser ? void 0 : assistantAvatar,
1205
1214
  fallbackAvatarUrl: messageIsUser ? userAvatar : void 0,
@@ -1209,17 +1218,17 @@ var Message = (0, import_react2.memo)(({
1209
1218
  const normalizedPreviewChars = Math.max(longMessagePreviewChars, 1);
1210
1219
  const normalizedChunkChars = Math.max(longMessageChunkChars, 1);
1211
1220
  const previewOverride = typeof message.metadata?.previewContent === "string" ? message.metadata.previewContent : void 0;
1212
- const canCollapseMessage = collapseLongMessages && !message.isStreaming && message.content.length > normalizedPreviewChars && (!collapseLongMessagesForUserOnly || messageIsUser);
1221
+ const canCollapseMessage = collapseLongMessages && sourceMessages.length === 1 && !sourceMessages.some((sourceMessage) => sourceMessage.isStreaming) && groupContent.length > normalizedPreviewChars && (!collapseLongMessagesForUserOnly || messageIsUser);
1213
1222
  const isCollapsed = canCollapseMessage && !isExpanded;
1214
- const contentToRender = isCollapsed ? getCollapsedPreview(message.content, normalizedPreviewChars, previewOverride) : message.content;
1223
+ const contentToRender = isCollapsed ? getCollapsedPreview(groupContent, normalizedPreviewChars, previewOverride) : groupContent;
1215
1224
  const shouldRenderMarkdown = !isCollapsed && (!messageIsUser || renderUserMarkdown);
1216
1225
  const horizontalOffsetClass = showAvatar ? messageIsUser ? compactMode ? "mr-9" : "mr-11" : compactMode ? "ml-9" : "ml-11" : "";
1217
1226
  const handleCopy = async () => {
1218
1227
  try {
1219
- await navigator.clipboard.writeText(message.content);
1228
+ await navigator.clipboard.writeText(groupContent);
1220
1229
  setCopied(true);
1221
1230
  setTimeout(() => setCopied(false), 2e3);
1222
- onAction?.({ action: "copy", messageId: message.id, content: message.content });
1231
+ onAction?.({ action: "copy", messageId: message.id, content: groupContent });
1223
1232
  } catch (error) {
1224
1233
  console.error("Failed to copy message:", error);
1225
1234
  }
@@ -1265,7 +1274,7 @@ var Message = (0, import_react2.memo)(({
1265
1274
  showAvatar && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex-shrink-0 ${compactMode ? "mt-1" : "mt-0"}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1266
1275
  MessageSenderAvatar,
1267
1276
  {
1268
- sender: message.sender,
1277
+ sender: headerMessage.sender,
1269
1278
  fallbackName: messageIsUser ? userName : assistantName,
1270
1279
  fallbackAvatar: messageIsUser ? void 0 : assistantAvatar,
1271
1280
  fallbackAvatarUrl: messageIsUser ? userAvatar : void 0,
@@ -1281,8 +1290,8 @@ var Message = (0, import_react2.memo)(({
1281
1290
  children: senderDisplay.name
1282
1291
  }
1283
1292
  ),
1284
- showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-muted-foreground", children: formatTime(message.timestamp) }),
1285
- message.isEdited && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Badge, { variant: "outline", className: "text-xs", children: "editado" })
1293
+ showTimestamp && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-muted-foreground", children: formatTime(headerMessage.timestamp) }),
1294
+ sourceMessages.some((sourceMessage) => sourceMessage.isEdited) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Badge, { variant: "outline", className: "text-xs", children: "editado" })
1286
1295
  ] })
1287
1296
  ] }),
1288
1297
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `flex-1 min-w-0 ${messageIsUser ? "text-right" : "text-left"} ${horizontalOffsetClass}`, children: [
@@ -1327,25 +1336,42 @@ var Message = (0, import_react2.memo)(({
1327
1336
  )
1328
1337
  ] })
1329
1338
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1330
- !messageIsUser && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1331
- AssistantActivity,
1332
- {
1333
- activity: message.activity,
1334
- showActivity,
1335
- showActivityDetails,
1336
- labels
1337
- }
1338
- ),
1339
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1340
- StreamingText,
1341
- {
1342
- content: contentToRender,
1343
- isStreaming: message.isStreaming,
1344
- renderMarkdown: shouldRenderMarkdown,
1345
- markdown,
1346
- plainTextChunkChars: normalizedChunkChars
1347
- }
1348
- ),
1339
+ sourceMessages.map((sourceMessage) => {
1340
+ const beforeContentActivity = filterActivity(sourceMessage, "before-content");
1341
+ const afterContentActivity = filterActivity(sourceMessage, "after-content");
1342
+ const fragmentContent = sourceMessages.length === 1 ? contentToRender : sourceMessage.content;
1343
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react2.default.Fragment, { children: [
1344
+ !messageIsUser && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1345
+ AssistantActivity,
1346
+ {
1347
+ activity: beforeContentActivity,
1348
+ showActivity,
1349
+ showActivityDetails,
1350
+ labels
1351
+ }
1352
+ ),
1353
+ fragmentContent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1354
+ StreamingText,
1355
+ {
1356
+ content: fragmentContent,
1357
+ isStreaming: sourceMessage.isStreaming,
1358
+ renderMarkdown: shouldRenderMarkdown,
1359
+ markdown,
1360
+ plainTextChunkChars: normalizedChunkChars
1361
+ }
1362
+ ),
1363
+ !messageIsUser && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1364
+ AssistantActivity,
1365
+ {
1366
+ activity: afterContentActivity,
1367
+ showActivity,
1368
+ showActivityDetails,
1369
+ labels
1370
+ }
1371
+ ),
1372
+ sourceMessage.attachments && sourceMessage.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mt-3 space-y-2", children: sourceMessage.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MediaRenderer, { attachment }, `${sourceMessage.id}:${index}`)) })
1373
+ ] }, sourceMessage.id);
1374
+ }),
1349
1375
  canCollapseMessage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1350
1376
  Button,
1351
1377
  {
@@ -1357,8 +1383,7 @@ var Message = (0, import_react2.memo)(({
1357
1383
  onClick: handleToggleExpanded,
1358
1384
  children: isCollapsed ? showMoreLabel : showLessLabel
1359
1385
  }
1360
- ) }),
1361
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mt-3 space-y-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MediaRenderer, { attachment }, index)) })
1386
+ ) })
1362
1387
  ] }) }),
1363
1388
  !isEditing && (enableCopy || canEdit) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1364
1389
  "div",
@@ -5925,72 +5950,27 @@ var getMessageSpeakerKey = (message) => {
5925
5950
  }
5926
5951
  return message.role;
5927
5952
  };
5928
- var getAssistantSpeakerTokens = (message) => {
5929
- if (!message || message.role !== "assistant") return [];
5930
- if (message.sender) {
5931
- return Array.from(new Set([
5932
- `${message.sender.type}:${message.sender.id}`,
5933
- message.sender.agentId ? `agent:${message.sender.agentId}` : "",
5934
- message.sender.externalId ? `external:${message.sender.externalId}` : ""
5935
- ].filter(Boolean).map((value) => value.toLowerCase())));
5936
- }
5937
- return ["assistant"];
5938
- };
5939
- var hasAssistantContent = (message) => message.role === "assistant" && (message.content.trim().length > 0 || Boolean(message.attachments?.length));
5940
- var hasActivity = (message) => Boolean(message.activity?.items.length);
5941
5953
  var canGroupMessages = (previous, next) => {
5942
5954
  if (previous.role !== next.role) {
5943
5955
  return false;
5944
5956
  }
5945
5957
  if (previous.role !== "assistant") {
5946
- return getMessageSpeakerKey(previous) === getMessageSpeakerKey(next);
5947
- }
5948
- const previousTokens = getAssistantSpeakerTokens(previous);
5949
- const nextTokens = getAssistantSpeakerTokens(next);
5950
- if (!previousTokens.some((token) => nextTokens.includes(token))) return false;
5951
- return !(hasAssistantContent(previous) && hasActivity(next));
5952
- };
5953
- var mergeGroupActivity = (messages) => {
5954
- const merged = /* @__PURE__ */ new Map();
5955
- for (const activity of messages.map((message) => message.activity).filter((activity2) => Boolean(activity2))) {
5956
- for (const item of activity.items) merged.set(item.id, item);
5957
- }
5958
- return merged.size > 0 ? { items: Array.from(merged.values()) } : void 0;
5959
- };
5960
- var mergeMessageGroup = (messages) => {
5961
- if (messages.length === 1) {
5962
- return messages[0];
5958
+ return false;
5963
5959
  }
5964
- const firstMessage = messages[0];
5965
- const lastMessage = messages[messages.length - 1];
5966
- const content = messages.map((message) => message.content.trim()).filter((value) => value.length > 0).join("\n\n");
5967
- const attachments = messages.flatMap((message) => message.attachments ?? []);
5968
- return {
5969
- ...lastMessage,
5970
- id: lastMessage.id,
5971
- content,
5972
- timestamp: firstMessage.timestamp,
5973
- attachments: attachments.length > 0 ? attachments : void 0,
5974
- isStreaming: lastMessage.isStreaming,
5975
- isComplete: lastMessage.isComplete,
5976
- isEdited: messages.some((message) => message.isEdited),
5977
- originalContent: void 0,
5978
- editedAt: lastMessage.editedAt,
5979
- activity: mergeGroupActivity(messages),
5980
- sender: lastMessage.sender ?? firstMessage.sender,
5981
- metadata: lastMessage.metadata
5982
- };
5960
+ return getMessageSpeakerKey(previous) === getMessageSpeakerKey(next);
5983
5961
  };
5984
5962
  var groupMessagesForRender = (messages) => {
5985
5963
  if (messages.length === 0) return [];
5986
5964
  const groups = [];
5987
5965
  let currentGroup = [messages[0]];
5988
5966
  const flushGroup = () => {
5989
- const mergedMessage = mergeMessageGroup(currentGroup);
5967
+ const firstMessage = currentGroup[0];
5968
+ const lastMessage = currentGroup[currentGroup.length - 1];
5990
5969
  groups.push({
5991
- id: mergedMessage.id,
5992
- message: mergedMessage,
5993
- suggestionMessageId: currentGroup[currentGroup.length - 1].id
5970
+ id: firstMessage.id,
5971
+ messages: currentGroup,
5972
+ primaryMessage: lastMessage,
5973
+ suggestionMessageId: lastMessage.id
5994
5974
  });
5995
5975
  };
5996
5976
  for (let index = 1; index < messages.length; index++) {
@@ -6634,7 +6614,7 @@ var ChatUI = ({
6634
6614
  },
6635
6615
  children: virtualizer.getVirtualItems().map((virtualRow) => {
6636
6616
  const group = groupedMessages[virtualRow.index];
6637
- const message = group.message;
6617
+ const message = group.primaryMessage;
6638
6618
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
6639
6619
  "div",
6640
6620
  {
@@ -6656,6 +6636,7 @@ var ChatUI = ({
6656
6636
  Message,
6657
6637
  {
6658
6638
  message,
6639
+ fragments: group.messages,
6659
6640
  ...messageProps,
6660
6641
  isExpanded: Boolean(
6661
6642
  expandedMessageIds[message.id]