@copilotz/chat-ui 0.3.3 → 0.3.4

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
@@ -142,6 +142,8 @@ var defaultChatConfig = {
142
142
  inputHelpText: "Press Enter to send, Shift+Enter to add a new line.",
143
143
  thinking: "Thinking...",
144
144
  defaultThreadName: "Main Thread",
145
+ loadOlderMessages: "Load older messages",
146
+ loadingOlderMessages: "Loading older messages...",
145
147
  showMoreMessage: "Show more",
146
148
  showLessMessage: "Show less"
147
149
  },
@@ -5063,7 +5065,10 @@ var ChatUI = ({
5063
5065
  sidebar: _sidebar,
5064
5066
  isGenerating = false,
5065
5067
  isMessagesLoading = false,
5068
+ isLoadingOlderMessages = false,
5069
+ hasMoreMessagesBefore = false,
5066
5070
  callbacks = {},
5071
+ onLoadOlderMessages,
5067
5072
  user,
5068
5073
  assistant,
5069
5074
  suggestions = [],
@@ -5130,6 +5135,7 @@ var ChatUI = ({
5130
5135
  }
5131
5136
  }, [initialInput]);
5132
5137
  const scrollAreaRef = (0, import_react8.useRef)(null);
5138
+ const prependSnapshotRef = (0, import_react8.useRef)(null);
5133
5139
  const stateRef = (0, import_react8.useRef)(state);
5134
5140
  const inputValueRef = (0, import_react8.useRef)(inputValue);
5135
5141
  const attachmentsRef = (0, import_react8.useRef)(attachments);
@@ -5187,6 +5193,10 @@ var ChatUI = ({
5187
5193
  prevMessageCountRef.current = 0;
5188
5194
  return;
5189
5195
  }
5196
+ if (prependSnapshotRef.current) {
5197
+ prevMessageCountRef.current = messages.length;
5198
+ return;
5199
+ }
5190
5200
  const wasEmpty = prevMessageCountRef.current === 0;
5191
5201
  prevMessageCountRef.current = messages.length;
5192
5202
  if (wasEmpty) {
@@ -5211,6 +5221,46 @@ var ChatUI = ({
5211
5221
  (0, import_react8.useEffect)(() => {
5212
5222
  virtualizer.measure();
5213
5223
  }, [expandedMessageIds, virtualizer]);
5224
+ (0, import_react8.useEffect)(() => {
5225
+ prependSnapshotRef.current = null;
5226
+ }, [currentThreadId]);
5227
+ (0, import_react8.useEffect)(() => {
5228
+ const snapshot = prependSnapshotRef.current;
5229
+ if (!snapshot) return;
5230
+ if (messages.length <= snapshot.messageCount) {
5231
+ if (!isLoadingOlderMessages) {
5232
+ prependSnapshotRef.current = null;
5233
+ }
5234
+ return;
5235
+ }
5236
+ if ((messages[0]?.id ?? null) === snapshot.firstMessageId) {
5237
+ if (!isLoadingOlderMessages) {
5238
+ prependSnapshotRef.current = null;
5239
+ }
5240
+ return;
5241
+ }
5242
+ requestAnimationFrame(() => {
5243
+ virtualizer.measure();
5244
+ requestAnimationFrame(() => {
5245
+ const viewport = scrollAreaRef.current;
5246
+ if (!viewport) return;
5247
+ const heightDelta = viewport.scrollHeight - snapshot.scrollHeight;
5248
+ viewport.scrollTop = snapshot.scrollTop + heightDelta;
5249
+ prependSnapshotRef.current = null;
5250
+ });
5251
+ });
5252
+ }, [messages, isLoadingOlderMessages, virtualizer]);
5253
+ const requestOlderMessages = (0, import_react8.useCallback)(() => {
5254
+ if (!onLoadOlderMessages || !hasMoreMessagesBefore || isLoadingOlderMessages) return;
5255
+ const viewport = scrollAreaRef.current;
5256
+ prependSnapshotRef.current = viewport ? {
5257
+ scrollHeight: viewport.scrollHeight,
5258
+ scrollTop: viewport.scrollTop,
5259
+ firstMessageId: messages[0]?.id ?? null,
5260
+ messageCount: messages.length
5261
+ } : null;
5262
+ onLoadOlderMessages();
5263
+ }, [hasMoreMessagesBefore, isLoadingOlderMessages, messages, onLoadOlderMessages]);
5214
5264
  (0, import_react8.useEffect)(() => {
5215
5265
  const validMessageIds = new Set(messages.map((message) => message.id));
5216
5266
  setExpandedMessageIds((prev) => {
@@ -5229,11 +5279,15 @@ var ChatUI = ({
5229
5279
  const handleScroll = (0, import_react8.useCallback)((e) => {
5230
5280
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
5231
5281
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
5282
+ const isNearTop = scrollTop < 120;
5283
+ if (isNearTop && hasMoreMessagesBefore && !isLoadingOlderMessages) {
5284
+ requestOlderMessages();
5285
+ }
5232
5286
  setState((prev) => {
5233
5287
  if (prev.isAtBottom === isAtBottom) return prev;
5234
5288
  return { ...prev, isAtBottom };
5235
5289
  });
5236
- }, []);
5290
+ }, [hasMoreMessagesBefore, isLoadingOlderMessages, requestOlderMessages]);
5237
5291
  const handleSendMessage = (0, import_react8.useCallback)((content, messageAttachments = []) => {
5238
5292
  if (!content.trim() && messageAttachments.length === 0) return;
5239
5293
  callbacks.onSendMessage?.(content, messageAttachments, createStateCallback());
@@ -5489,48 +5543,59 @@ var ChatUI = ({
5489
5543
  viewportClassName: "p-4 overscroll-contain",
5490
5544
  onScrollCapture: handleScroll,
5491
5545
  style: { contain: "strict" },
5492
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "max-w-4xl mx-auto pb-4", children: isMessagesLoading ? renderMessageLoadingSkeleton() : messages.length === 0 ? renderSuggestions() : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5493
- "div",
5494
- {
5495
- style: {
5496
- height: `${virtualizer.getTotalSize()}px`,
5497
- width: "100%",
5498
- position: "relative"
5499
- },
5500
- children: virtualizer.getVirtualItems().map((virtualRow) => {
5501
- const message = messages[virtualRow.index];
5502
- const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5503
- const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5504
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5505
- "div",
5506
- {
5507
- "data-index": virtualRow.index,
5508
- ref: virtualizer.measureElement,
5509
- style: {
5510
- position: "absolute",
5511
- top: 0,
5512
- left: 0,
5513
- width: "100%",
5514
- transform: `translateY(${virtualRow.start}px)`
5546
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "max-w-4xl mx-auto pb-4", children: [
5547
+ messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "flex justify-center py-2", children: isLoadingOlderMessages ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-xs text-muted-foreground", children: config.labels.loadingOlderMessages }) : hasMoreMessagesBefore ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5548
+ "button",
5549
+ {
5550
+ type: "button",
5551
+ onClick: requestOlderMessages,
5552
+ className: "text-xs text-muted-foreground transition-colors hover:text-foreground",
5553
+ children: config.labels.loadOlderMessages
5554
+ }
5555
+ ) : null }),
5556
+ isMessagesLoading ? renderMessageLoadingSkeleton() : messages.length === 0 ? renderSuggestions() : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5557
+ "div",
5558
+ {
5559
+ style: {
5560
+ height: `${virtualizer.getTotalSize()}px`,
5561
+ width: "100%",
5562
+ position: "relative"
5563
+ },
5564
+ children: virtualizer.getVirtualItems().map((virtualRow) => {
5565
+ const message = messages[virtualRow.index];
5566
+ const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5567
+ const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5568
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5569
+ "div",
5570
+ {
5571
+ "data-index": virtualRow.index,
5572
+ ref: virtualizer.measureElement,
5573
+ style: {
5574
+ position: "absolute",
5575
+ top: 0,
5576
+ left: 0,
5577
+ width: "100%",
5578
+ transform: `translateY(${virtualRow.start}px)`
5579
+ },
5580
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5581
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5582
+ Message,
5583
+ {
5584
+ message,
5585
+ ...messageProps,
5586
+ isGrouped,
5587
+ isExpanded: Boolean(expandedMessageIds[message.id])
5588
+ }
5589
+ ),
5590
+ message.role === "assistant" && renderInlineSuggestions(message.id)
5591
+ ] })
5515
5592
  },
5516
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5517
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5518
- Message,
5519
- {
5520
- message,
5521
- ...messageProps,
5522
- isGrouped,
5523
- isExpanded: Boolean(expandedMessageIds[message.id])
5524
- }
5525
- ),
5526
- message.role === "assistant" && renderInlineSuggestions(message.id)
5527
- ] })
5528
- },
5529
- message.id
5530
- );
5531
- })
5532
- }
5533
- ) })
5593
+ message.id
5594
+ );
5595
+ })
5596
+ }
5597
+ )
5598
+ ] })
5534
5599
  }
5535
5600
  ),
5536
5601
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "bg-background pb-[env(safe-area-inset-bottom)]", children: [