@copilotz/chat-ui 0.5.0 → 0.6.0

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
@@ -880,7 +880,6 @@ var arePropsEqual = (prevProps, nextProps) => {
880
880
  if (prevProps.markdown !== nextProps.markdown) return false;
881
881
  if (prevProps.isExpanded !== nextProps.isExpanded) return false;
882
882
  if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
883
- if (prevProps.isGrouped !== nextProps.isGrouped) return false;
884
883
  if (prevProps.assistantAvatar !== nextProps.assistantAvatar) return false;
885
884
  return true;
886
885
  };
@@ -911,7 +910,6 @@ var Message = (0, import_react2.memo)(({
911
910
  markdown,
912
911
  isExpanded = false,
913
912
  onToggleExpanded,
914
- isGrouped = false,
915
913
  agentOptions = []
916
914
  }) => {
917
915
  const [isEditing, setIsEditing] = (0, import_react2.useState)(false);
@@ -984,7 +982,7 @@ var Message = (0, import_react2.memo)(({
984
982
  onMouseEnter: () => setShowActions(true),
985
983
  onMouseLeave: () => setShowActions(false),
986
984
  children: [
987
- !isGrouped && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `flex gap-3 ${messageIsUser ? "flex-row-reverse" : "flex-row"} w-full mb-1`, children: [
985
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `flex gap-3 ${messageIsUser ? "flex-row-reverse" : "flex-row"} w-full mb-1`, children: [
988
986
  showAvatar && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: `flex-shrink-0 ${compactMode ? "mt-1" : "mt-0"}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Avatar, { className: compactMode ? "h-6 w-6" : "h-8 w-8", children: messageIsUser ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
989
987
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AvatarImage, { src: userAvatar, alt: userName }),
990
988
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AvatarFallback, { className: "bg-primary text-primary-foreground", children: userName.charAt(0).toUpperCase() })
@@ -4997,6 +4995,81 @@ function getMessageSpeakerKey(message) {
4997
4995
  }
4998
4996
  return message.role;
4999
4997
  }
4998
+ var mergeToolCalls = (activities) => {
4999
+ const merged = /* @__PURE__ */ new Map();
5000
+ for (const activity of activities) {
5001
+ if (!Array.isArray(activity.toolCalls)) continue;
5002
+ for (const toolCall of activity.toolCalls) {
5003
+ const key = toolCall.id || `${toolCall.name}:${JSON.stringify(toolCall.arguments ?? {})}`;
5004
+ merged.set(key, toolCall);
5005
+ }
5006
+ }
5007
+ return merged.size > 0 ? Array.from(merged.values()) : void 0;
5008
+ };
5009
+ var mergeReasoning = (activities) => {
5010
+ const segments = activities.map((activity) => activity.reasoning?.trim()).filter((value) => Boolean(value));
5011
+ if (segments.length === 0) return void 0;
5012
+ return segments.filter((segment, index) => index === 0 || segment !== segments[index - 1]).join("\n\n");
5013
+ };
5014
+ var mergeGroupActivity = (messages) => {
5015
+ const activities = messages.map((message) => message.activity).filter((activity) => Boolean(activity));
5016
+ if (activities.length === 0) return void 0;
5017
+ const lastActivity = activities[activities.length - 1];
5018
+ const mergedReasoning = mergeReasoning(activities);
5019
+ const mergedToolCalls = mergeToolCalls(activities);
5020
+ return {
5021
+ ...lastActivity,
5022
+ ...mergedReasoning ? { reasoning: mergedReasoning } : {},
5023
+ ...mergedToolCalls ? { toolCalls: mergedToolCalls } : {}
5024
+ };
5025
+ };
5026
+ var mergeMessageGroup = (messages) => {
5027
+ const firstMessage = messages[0];
5028
+ const lastMessage = messages[messages.length - 1];
5029
+ const content = messages.map((message) => message.content.trim()).filter((value) => value.length > 0).join("\n\n");
5030
+ const attachments = messages.flatMap((message) => message.attachments ?? []);
5031
+ return {
5032
+ ...lastMessage,
5033
+ id: lastMessage.id,
5034
+ content,
5035
+ timestamp: firstMessage.timestamp,
5036
+ attachments: attachments.length > 0 ? attachments : void 0,
5037
+ isStreaming: lastMessage.isStreaming,
5038
+ isComplete: lastMessage.isComplete,
5039
+ isEdited: messages.some((message) => message.isEdited),
5040
+ originalContent: void 0,
5041
+ editedAt: lastMessage.editedAt,
5042
+ activity: mergeGroupActivity(messages),
5043
+ senderName: lastMessage.senderName ?? firstMessage.senderName,
5044
+ senderAgentId: lastMessage.senderAgentId ?? firstMessage.senderAgentId,
5045
+ metadata: lastMessage.metadata
5046
+ };
5047
+ };
5048
+ var groupMessagesForRender = (messages) => {
5049
+ if (messages.length === 0) return [];
5050
+ const groups = [];
5051
+ let currentGroup = [messages[0]];
5052
+ const flushGroup = () => {
5053
+ const mergedMessage = mergeMessageGroup(currentGroup);
5054
+ groups.push({
5055
+ id: mergedMessage.id,
5056
+ message: mergedMessage,
5057
+ suggestionMessageId: currentGroup[currentGroup.length - 1].id
5058
+ });
5059
+ };
5060
+ for (let index = 1; index < messages.length; index++) {
5061
+ const previous = currentGroup[currentGroup.length - 1];
5062
+ const next = messages[index];
5063
+ if (previous.role === next.role && getMessageSpeakerKey(previous) === getMessageSpeakerKey(next)) {
5064
+ currentGroup.push(next);
5065
+ continue;
5066
+ }
5067
+ flushGroup();
5068
+ currentGroup = [next];
5069
+ }
5070
+ flushGroup();
5071
+ return groups;
5072
+ };
5000
5073
  var ChatUI = ({
5001
5074
  messages = [],
5002
5075
  threads = [],
@@ -5090,8 +5163,9 @@ var ChatUI = ({
5090
5163
  }, [attachments]);
5091
5164
  const [isCustomMounted, setIsCustomMounted] = (0, import_react9.useState)(false);
5092
5165
  const [isCustomVisible, setIsCustomVisible] = (0, import_react9.useState)(false);
5166
+ const groupedMessages = (0, import_react9.useMemo)(() => groupMessagesForRender(messages), [messages]);
5093
5167
  const virtualizer = (0, import_react_virtual.useVirtualizer)({
5094
- count: messages.length,
5168
+ count: groupedMessages.length,
5095
5169
  getScrollElement: () => scrollAreaRef.current,
5096
5170
  estimateSize: () => 100,
5097
5171
  overscan: 5
@@ -5129,20 +5203,20 @@ var ChatUI = ({
5129
5203
  }, [state.showSidebar, isMobile, config.customComponent]);
5130
5204
  const prevMessageCountRef = (0, import_react9.useRef)(0);
5131
5205
  (0, import_react9.useEffect)(() => {
5132
- if (messages.length === 0) {
5206
+ if (groupedMessages.length === 0) {
5133
5207
  prevMessageCountRef.current = 0;
5134
5208
  return;
5135
5209
  }
5136
5210
  if (prependSnapshotRef.current) {
5137
- prevMessageCountRef.current = messages.length;
5211
+ prevMessageCountRef.current = groupedMessages.length;
5138
5212
  return;
5139
5213
  }
5140
5214
  const wasEmpty = prevMessageCountRef.current === 0;
5141
- prevMessageCountRef.current = messages.length;
5215
+ prevMessageCountRef.current = groupedMessages.length;
5142
5216
  if (wasEmpty) {
5143
5217
  requestAnimationFrame(() => {
5144
5218
  requestAnimationFrame(() => {
5145
- virtualizer.scrollToIndex(messages.length - 1, { align: "end" });
5219
+ virtualizer.scrollToIndex(groupedMessages.length - 1, { align: "end" });
5146
5220
  });
5147
5221
  });
5148
5222
  return;
@@ -5157,7 +5231,7 @@ var ChatUI = ({
5157
5231
  viewport.scrollTop = viewport.scrollHeight;
5158
5232
  }
5159
5233
  });
5160
- }, [messages, state.isAtBottom, virtualizer]);
5234
+ }, [groupedMessages, state.isAtBottom, virtualizer]);
5161
5235
  (0, import_react9.useEffect)(() => {
5162
5236
  virtualizer.measure();
5163
5237
  }, [expandedMessageIds, virtualizer]);
@@ -5167,13 +5241,13 @@ var ChatUI = ({
5167
5241
  (0, import_react9.useEffect)(() => {
5168
5242
  const snapshot = prependSnapshotRef.current;
5169
5243
  if (!snapshot) return;
5170
- if (messages.length <= snapshot.messageCount) {
5244
+ if (groupedMessages.length <= snapshot.messageCount) {
5171
5245
  if (!isLoadingOlderMessages) {
5172
5246
  prependSnapshotRef.current = null;
5173
5247
  }
5174
5248
  return;
5175
5249
  }
5176
- if ((messages[0]?.id ?? null) === snapshot.firstMessageId) {
5250
+ if ((groupedMessages[0]?.id ?? null) === snapshot.firstMessageId) {
5177
5251
  if (!isLoadingOlderMessages) {
5178
5252
  prependSnapshotRef.current = null;
5179
5253
  }
@@ -5189,20 +5263,20 @@ var ChatUI = ({
5189
5263
  prependSnapshotRef.current = null;
5190
5264
  });
5191
5265
  });
5192
- }, [messages, isLoadingOlderMessages, virtualizer]);
5266
+ }, [groupedMessages, isLoadingOlderMessages, virtualizer]);
5193
5267
  const requestOlderMessages = (0, import_react9.useCallback)(() => {
5194
5268
  if (!onLoadOlderMessages || !hasMoreMessagesBefore || isLoadingOlderMessages) return;
5195
5269
  const viewport = scrollAreaRef.current;
5196
5270
  prependSnapshotRef.current = viewport ? {
5197
5271
  scrollHeight: viewport.scrollHeight,
5198
5272
  scrollTop: viewport.scrollTop,
5199
- firstMessageId: messages[0]?.id ?? null,
5200
- messageCount: messages.length
5273
+ firstMessageId: groupedMessages[0]?.id ?? null,
5274
+ messageCount: groupedMessages.length
5201
5275
  } : null;
5202
5276
  onLoadOlderMessages();
5203
- }, [hasMoreMessagesBefore, isLoadingOlderMessages, messages, onLoadOlderMessages]);
5277
+ }, [groupedMessages, hasMoreMessagesBefore, isLoadingOlderMessages, onLoadOlderMessages]);
5204
5278
  (0, import_react9.useEffect)(() => {
5205
- const validMessageIds = new Set(messages.map((message) => message.id));
5279
+ const validMessageIds = new Set(groupedMessages.map((group) => group.id));
5206
5280
  setExpandedMessageIds((prev) => {
5207
5281
  const activeIds = Object.keys(prev);
5208
5282
  const staleIds = activeIds.filter((messageId) => !validMessageIds.has(messageId));
@@ -5215,7 +5289,7 @@ var ChatUI = ({
5215
5289
  });
5216
5290
  return next;
5217
5291
  });
5218
- }, [messages]);
5292
+ }, [groupedMessages]);
5219
5293
  const handleScroll = (0, import_react9.useCallback)((e) => {
5220
5294
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
5221
5295
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
@@ -5317,7 +5391,7 @@ var ChatUI = ({
5317
5391
  }, [config?.customComponent?.component, closeSidebar, isMobile]);
5318
5392
  const SuggestionIconComponents = [import_lucide_react14.MessageSquare, import_lucide_react14.Lightbulb, import_lucide_react14.Zap, import_lucide_react14.HelpCircle];
5319
5393
  const renderSuggestions = () => {
5320
- if (messages.length > 0 || !suggestions.length) return null;
5394
+ if (groupedMessages.length > 0 || !suggestions.length) return null;
5321
5395
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col items-center justify-center min-h-[60vh] py-8 px-4", children: [
5322
5396
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-center mb-8", children: [
5323
5397
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-primary/20 to-primary/5 mb-4 shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.Sparkles, { className: "w-7 h-7 text-primary" }) }),
@@ -5482,7 +5556,7 @@ var ChatUI = ({
5482
5556
  onScrollCapture: handleScroll,
5483
5557
  style: { contain: "strict" },
5484
5558
  children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "max-w-4xl mx-auto pb-4", children: [
5485
- messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "flex justify-center py-2", children: isLoadingOlderMessages ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "text-xs text-muted-foreground", children: config.labels.loadingOlderMessages }) : hasMoreMessagesBefore ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5559
+ groupedMessages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "flex justify-center py-2", children: isLoadingOlderMessages ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "text-xs text-muted-foreground", children: config.labels.loadingOlderMessages }) : hasMoreMessagesBefore ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5486
5560
  "button",
5487
5561
  {
5488
5562
  type: "button",
@@ -5491,7 +5565,7 @@ var ChatUI = ({
5491
5565
  children: config.labels.loadOlderMessages
5492
5566
  }
5493
5567
  ) : null }),
5494
- isMessagesLoading ? renderMessageLoadingSkeleton() : messages.length === 0 ? renderSuggestions() : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5568
+ isMessagesLoading ? renderMessageLoadingSkeleton() : groupedMessages.length === 0 ? renderSuggestions() : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5495
5569
  "div",
5496
5570
  {
5497
5571
  style: {
@@ -5500,9 +5574,8 @@ var ChatUI = ({
5500
5574
  position: "relative"
5501
5575
  },
5502
5576
  children: virtualizer.getVirtualItems().map((virtualRow) => {
5503
- const message = messages[virtualRow.index];
5504
- const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5505
- const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5577
+ const group = groupedMessages[virtualRow.index];
5578
+ const message = group.message;
5506
5579
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5507
5580
  "div",
5508
5581
  {
@@ -5515,20 +5588,19 @@ var ChatUI = ({
5515
5588
  width: "100%",
5516
5589
  transform: `translateY(${virtualRow.start}px)`
5517
5590
  },
5518
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5591
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: virtualRow.index === 0 ? "" : "pt-4", children: [
5519
5592
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5520
5593
  Message,
5521
5594
  {
5522
5595
  message,
5523
5596
  ...messageProps,
5524
- isGrouped,
5525
5597
  isExpanded: Boolean(expandedMessageIds[message.id])
5526
5598
  }
5527
5599
  ),
5528
- message.role === "assistant" && renderInlineSuggestions(message.id)
5600
+ message.role === "assistant" && renderInlineSuggestions(group.suggestionMessageId)
5529
5601
  ] })
5530
5602
  },
5531
- message.id
5603
+ group.id
5532
5604
  );
5533
5605
  })
5534
5606
  }