@copilotz/chat-ui 0.5.0 → 0.6.1

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.js CHANGED
@@ -845,7 +845,6 @@ var arePropsEqual = (prevProps, nextProps) => {
845
845
  if (prevProps.markdown !== nextProps.markdown) return false;
846
846
  if (prevProps.isExpanded !== nextProps.isExpanded) return false;
847
847
  if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
848
- if (prevProps.isGrouped !== nextProps.isGrouped) return false;
849
848
  if (prevProps.assistantAvatar !== nextProps.assistantAvatar) return false;
850
849
  return true;
851
850
  };
@@ -876,7 +875,6 @@ var Message = memo2(({
876
875
  markdown,
877
876
  isExpanded = false,
878
877
  onToggleExpanded,
879
- isGrouped = false,
880
878
  agentOptions = []
881
879
  }) => {
882
880
  const [isEditing, setIsEditing] = useState2(false);
@@ -949,7 +947,7 @@ var Message = memo2(({
949
947
  onMouseEnter: () => setShowActions(true),
950
948
  onMouseLeave: () => setShowActions(false),
951
949
  children: [
952
- !isGrouped && /* @__PURE__ */ jsxs3("div", { className: `flex gap-3 ${messageIsUser ? "flex-row-reverse" : "flex-row"} w-full mb-1`, children: [
950
+ /* @__PURE__ */ jsxs3("div", { className: `flex gap-3 ${messageIsUser ? "flex-row-reverse" : "flex-row"} w-full mb-1`, children: [
953
951
  showAvatar && /* @__PURE__ */ jsx8("div", { className: `flex-shrink-0 ${compactMode ? "mt-1" : "mt-0"}`, children: /* @__PURE__ */ jsx8(Avatar, { className: compactMode ? "h-6 w-6" : "h-8 w-8", children: messageIsUser ? /* @__PURE__ */ jsxs3(Fragment, { children: [
954
952
  /* @__PURE__ */ jsx8(AvatarImage, { src: userAvatar, alt: userName }),
955
953
  /* @__PURE__ */ jsx8(AvatarFallback, { className: "bg-primary text-primary-foreground", children: userName.charAt(0).toUpperCase() })
@@ -5029,6 +5027,81 @@ function getMessageSpeakerKey(message) {
5029
5027
  }
5030
5028
  return message.role;
5031
5029
  }
5030
+ var mergeToolCalls = (activities) => {
5031
+ const merged = /* @__PURE__ */ new Map();
5032
+ for (const activity of activities) {
5033
+ if (!Array.isArray(activity.toolCalls)) continue;
5034
+ for (const toolCall of activity.toolCalls) {
5035
+ const key = toolCall.id || `${toolCall.name}:${JSON.stringify(toolCall.arguments ?? {})}`;
5036
+ merged.set(key, toolCall);
5037
+ }
5038
+ }
5039
+ return merged.size > 0 ? Array.from(merged.values()) : void 0;
5040
+ };
5041
+ var mergeReasoning = (activities) => {
5042
+ const segments = activities.map((activity) => activity.reasoning?.trim()).filter((value) => Boolean(value));
5043
+ if (segments.length === 0) return void 0;
5044
+ return segments.filter((segment, index) => index === 0 || segment !== segments[index - 1]).join("\n\n");
5045
+ };
5046
+ var mergeGroupActivity = (messages) => {
5047
+ const activities = messages.map((message) => message.activity).filter((activity) => Boolean(activity));
5048
+ if (activities.length === 0) return void 0;
5049
+ const lastActivity = activities[activities.length - 1];
5050
+ const mergedReasoning = mergeReasoning(activities);
5051
+ const mergedToolCalls = mergeToolCalls(activities);
5052
+ return {
5053
+ ...lastActivity,
5054
+ ...mergedReasoning ? { reasoning: mergedReasoning } : {},
5055
+ ...mergedToolCalls ? { toolCalls: mergedToolCalls } : {}
5056
+ };
5057
+ };
5058
+ var mergeMessageGroup = (messages) => {
5059
+ const firstMessage = messages[0];
5060
+ const lastMessage = messages[messages.length - 1];
5061
+ const content = messages.map((message) => message.content.trim()).filter((value) => value.length > 0).join("\n\n");
5062
+ const attachments = messages.flatMap((message) => message.attachments ?? []);
5063
+ return {
5064
+ ...lastMessage,
5065
+ id: lastMessage.id,
5066
+ content,
5067
+ timestamp: firstMessage.timestamp,
5068
+ attachments: attachments.length > 0 ? attachments : void 0,
5069
+ isStreaming: lastMessage.isStreaming,
5070
+ isComplete: lastMessage.isComplete,
5071
+ isEdited: messages.some((message) => message.isEdited),
5072
+ originalContent: void 0,
5073
+ editedAt: lastMessage.editedAt,
5074
+ activity: mergeGroupActivity(messages),
5075
+ senderName: lastMessage.senderName ?? firstMessage.senderName,
5076
+ senderAgentId: lastMessage.senderAgentId ?? firstMessage.senderAgentId,
5077
+ metadata: lastMessage.metadata
5078
+ };
5079
+ };
5080
+ var groupMessagesForRender = (messages) => {
5081
+ if (messages.length === 0) return [];
5082
+ const groups = [];
5083
+ let currentGroup = [messages[0]];
5084
+ const flushGroup = () => {
5085
+ const mergedMessage = mergeMessageGroup(currentGroup);
5086
+ groups.push({
5087
+ id: mergedMessage.id,
5088
+ message: mergedMessage,
5089
+ suggestionMessageId: currentGroup[currentGroup.length - 1].id
5090
+ });
5091
+ };
5092
+ for (let index = 1; index < messages.length; index++) {
5093
+ const previous = currentGroup[currentGroup.length - 1];
5094
+ const next = messages[index];
5095
+ if (previous.role === next.role && getMessageSpeakerKey(previous) === getMessageSpeakerKey(next)) {
5096
+ currentGroup.push(next);
5097
+ continue;
5098
+ }
5099
+ flushGroup();
5100
+ currentGroup = [next];
5101
+ }
5102
+ flushGroup();
5103
+ return groups;
5104
+ };
5032
5105
  var ChatUI = ({
5033
5106
  messages = [],
5034
5107
  threads = [],
@@ -5122,8 +5195,9 @@ var ChatUI = ({
5122
5195
  }, [attachments]);
5123
5196
  const [isCustomMounted, setIsCustomMounted] = useState9(false);
5124
5197
  const [isCustomVisible, setIsCustomVisible] = useState9(false);
5198
+ const groupedMessages = useMemo6(() => groupMessagesForRender(messages), [messages]);
5125
5199
  const virtualizer = useVirtualizer({
5126
- count: messages.length,
5200
+ count: groupedMessages.length,
5127
5201
  getScrollElement: () => scrollAreaRef.current,
5128
5202
  estimateSize: () => 100,
5129
5203
  overscan: 5
@@ -5161,20 +5235,20 @@ var ChatUI = ({
5161
5235
  }, [state.showSidebar, isMobile, config.customComponent]);
5162
5236
  const prevMessageCountRef = useRef6(0);
5163
5237
  useEffect11(() => {
5164
- if (messages.length === 0) {
5238
+ if (groupedMessages.length === 0) {
5165
5239
  prevMessageCountRef.current = 0;
5166
5240
  return;
5167
5241
  }
5168
5242
  if (prependSnapshotRef.current) {
5169
- prevMessageCountRef.current = messages.length;
5243
+ prevMessageCountRef.current = groupedMessages.length;
5170
5244
  return;
5171
5245
  }
5172
5246
  const wasEmpty = prevMessageCountRef.current === 0;
5173
- prevMessageCountRef.current = messages.length;
5247
+ prevMessageCountRef.current = groupedMessages.length;
5174
5248
  if (wasEmpty) {
5175
5249
  requestAnimationFrame(() => {
5176
5250
  requestAnimationFrame(() => {
5177
- virtualizer.scrollToIndex(messages.length - 1, { align: "end" });
5251
+ virtualizer.scrollToIndex(groupedMessages.length - 1, { align: "end" });
5178
5252
  });
5179
5253
  });
5180
5254
  return;
@@ -5189,7 +5263,7 @@ var ChatUI = ({
5189
5263
  viewport.scrollTop = viewport.scrollHeight;
5190
5264
  }
5191
5265
  });
5192
- }, [messages, state.isAtBottom, virtualizer]);
5266
+ }, [groupedMessages, state.isAtBottom, virtualizer]);
5193
5267
  useEffect11(() => {
5194
5268
  virtualizer.measure();
5195
5269
  }, [expandedMessageIds, virtualizer]);
@@ -5199,13 +5273,13 @@ var ChatUI = ({
5199
5273
  useEffect11(() => {
5200
5274
  const snapshot = prependSnapshotRef.current;
5201
5275
  if (!snapshot) return;
5202
- if (messages.length <= snapshot.messageCount) {
5276
+ if (groupedMessages.length <= snapshot.messageCount) {
5203
5277
  if (!isLoadingOlderMessages) {
5204
5278
  prependSnapshotRef.current = null;
5205
5279
  }
5206
5280
  return;
5207
5281
  }
5208
- if ((messages[0]?.id ?? null) === snapshot.firstMessageId) {
5282
+ if ((groupedMessages[0]?.id ?? null) === snapshot.firstMessageId) {
5209
5283
  if (!isLoadingOlderMessages) {
5210
5284
  prependSnapshotRef.current = null;
5211
5285
  }
@@ -5221,20 +5295,20 @@ var ChatUI = ({
5221
5295
  prependSnapshotRef.current = null;
5222
5296
  });
5223
5297
  });
5224
- }, [messages, isLoadingOlderMessages, virtualizer]);
5298
+ }, [groupedMessages, isLoadingOlderMessages, virtualizer]);
5225
5299
  const requestOlderMessages = useCallback4(() => {
5226
5300
  if (!onLoadOlderMessages || !hasMoreMessagesBefore || isLoadingOlderMessages) return;
5227
5301
  const viewport = scrollAreaRef.current;
5228
5302
  prependSnapshotRef.current = viewport ? {
5229
5303
  scrollHeight: viewport.scrollHeight,
5230
5304
  scrollTop: viewport.scrollTop,
5231
- firstMessageId: messages[0]?.id ?? null,
5232
- messageCount: messages.length
5305
+ firstMessageId: groupedMessages[0]?.id ?? null,
5306
+ messageCount: groupedMessages.length
5233
5307
  } : null;
5234
5308
  onLoadOlderMessages();
5235
- }, [hasMoreMessagesBefore, isLoadingOlderMessages, messages, onLoadOlderMessages]);
5309
+ }, [groupedMessages, hasMoreMessagesBefore, isLoadingOlderMessages, onLoadOlderMessages]);
5236
5310
  useEffect11(() => {
5237
- const validMessageIds = new Set(messages.map((message) => message.id));
5311
+ const validMessageIds = new Set(groupedMessages.map((group) => group.id));
5238
5312
  setExpandedMessageIds((prev) => {
5239
5313
  const activeIds = Object.keys(prev);
5240
5314
  const staleIds = activeIds.filter((messageId) => !validMessageIds.has(messageId));
@@ -5247,7 +5321,7 @@ var ChatUI = ({
5247
5321
  });
5248
5322
  return next;
5249
5323
  });
5250
- }, [messages]);
5324
+ }, [groupedMessages]);
5251
5325
  const handleScroll = useCallback4((e) => {
5252
5326
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
5253
5327
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
@@ -5349,7 +5423,7 @@ var ChatUI = ({
5349
5423
  }, [config?.customComponent?.component, closeSidebar, isMobile]);
5350
5424
  const SuggestionIconComponents = [MessageSquare, Lightbulb2, Zap, HelpCircle];
5351
5425
  const renderSuggestions = () => {
5352
- if (messages.length > 0 || !suggestions.length) return null;
5426
+ if (groupedMessages.length > 0 || !suggestions.length) return null;
5353
5427
  return /* @__PURE__ */ jsxs17("div", { className: "flex flex-col items-center justify-center min-h-[60vh] py-8 px-4", children: [
5354
5428
  /* @__PURE__ */ jsxs17("div", { className: "text-center mb-8", children: [
5355
5429
  /* @__PURE__ */ jsx27("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__ */ jsx27(Sparkles2, { className: "w-7 h-7 text-primary" }) }),
@@ -5514,7 +5588,7 @@ var ChatUI = ({
5514
5588
  onScrollCapture: handleScroll,
5515
5589
  style: { contain: "strict" },
5516
5590
  children: /* @__PURE__ */ jsxs17("div", { className: "max-w-4xl mx-auto pb-4", children: [
5517
- messages.length > 0 && /* @__PURE__ */ jsx27("div", { className: "flex justify-center py-2", children: isLoadingOlderMessages ? /* @__PURE__ */ jsx27("span", { className: "text-xs text-muted-foreground", children: config.labels.loadingOlderMessages }) : hasMoreMessagesBefore ? /* @__PURE__ */ jsx27(
5591
+ groupedMessages.length > 0 && /* @__PURE__ */ jsx27("div", { className: "flex justify-center py-2", children: isLoadingOlderMessages ? /* @__PURE__ */ jsx27("span", { className: "text-xs text-muted-foreground", children: config.labels.loadingOlderMessages }) : hasMoreMessagesBefore ? /* @__PURE__ */ jsx27(
5518
5592
  "button",
5519
5593
  {
5520
5594
  type: "button",
@@ -5523,7 +5597,7 @@ var ChatUI = ({
5523
5597
  children: config.labels.loadOlderMessages
5524
5598
  }
5525
5599
  ) : null }),
5526
- isMessagesLoading ? renderMessageLoadingSkeleton() : messages.length === 0 ? renderSuggestions() : /* @__PURE__ */ jsx27(
5600
+ isMessagesLoading ? renderMessageLoadingSkeleton() : groupedMessages.length === 0 ? renderSuggestions() : /* @__PURE__ */ jsx27(
5527
5601
  "div",
5528
5602
  {
5529
5603
  style: {
@@ -5532,9 +5606,8 @@ var ChatUI = ({
5532
5606
  position: "relative"
5533
5607
  },
5534
5608
  children: virtualizer.getVirtualItems().map((virtualRow) => {
5535
- const message = messages[virtualRow.index];
5536
- const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5537
- const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5609
+ const group = groupedMessages[virtualRow.index];
5610
+ const message = group.message;
5538
5611
  return /* @__PURE__ */ jsx27(
5539
5612
  "div",
5540
5613
  {
@@ -5547,20 +5620,19 @@ var ChatUI = ({
5547
5620
  width: "100%",
5548
5621
  transform: `translateY(${virtualRow.start}px)`
5549
5622
  },
5550
- children: /* @__PURE__ */ jsxs17("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5623
+ children: /* @__PURE__ */ jsxs17("div", { className: virtualRow.index === 0 ? "" : "pt-4", children: [
5551
5624
  /* @__PURE__ */ jsx27(
5552
5625
  Message,
5553
5626
  {
5554
5627
  message,
5555
5628
  ...messageProps,
5556
- isGrouped,
5557
5629
  isExpanded: Boolean(expandedMessageIds[message.id])
5558
5630
  }
5559
5631
  ),
5560
- message.role === "assistant" && renderInlineSuggestions(message.id)
5632
+ message.role === "assistant" && renderInlineSuggestions(group.suggestionMessageId)
5561
5633
  ] })
5562
5634
  },
5563
- message.id
5635
+ group.id
5564
5636
  );
5565
5637
  })
5566
5638
  }