@copilotz/chat-ui 0.3.2 → 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.d.cts CHANGED
@@ -131,8 +131,6 @@ interface ChatConfig {
131
131
  stopGenerationTooltip?: string;
132
132
  attachFiles?: string;
133
133
  attachFileTooltip?: string;
134
- recordAudio?: string;
135
- recordAudioTooltip?: string;
136
134
  voiceEnter?: string;
137
135
  voiceExit?: string;
138
136
  voiceTitle?: string;
@@ -188,6 +186,8 @@ interface ChatConfig {
188
186
  inputHelpText?: string;
189
187
  thinking?: string;
190
188
  defaultThreadName?: string;
189
+ loadOlderMessages?: string;
190
+ loadingOlderMessages?: string;
191
191
  showMoreMessage?: string;
192
192
  showLessMessage?: string;
193
193
  };
@@ -216,7 +216,6 @@ interface ChatConfig {
216
216
  };
217
217
  markdown?: ChatMarkdownConfig;
218
218
  voiceCompose?: {
219
- enabled?: boolean;
220
219
  defaultMode?: 'text' | 'voice';
221
220
  reviewMode?: VoiceReviewMode;
222
221
  autoSendDelayMs?: number;
@@ -270,7 +269,10 @@ interface ChatV2Props {
270
269
  sidebar?: ReactNode;
271
270
  isGenerating?: boolean;
272
271
  isMessagesLoading?: boolean;
272
+ isLoadingOlderMessages?: boolean;
273
+ hasMoreMessagesBefore?: boolean;
273
274
  callbacks?: ChatCallbacks;
275
+ onLoadOlderMessages?: () => void;
274
276
  user?: {
275
277
  id: string;
276
278
  name?: string;
package/dist/index.d.ts CHANGED
@@ -131,8 +131,6 @@ interface ChatConfig {
131
131
  stopGenerationTooltip?: string;
132
132
  attachFiles?: string;
133
133
  attachFileTooltip?: string;
134
- recordAudio?: string;
135
- recordAudioTooltip?: string;
136
134
  voiceEnter?: string;
137
135
  voiceExit?: string;
138
136
  voiceTitle?: string;
@@ -188,6 +186,8 @@ interface ChatConfig {
188
186
  inputHelpText?: string;
189
187
  thinking?: string;
190
188
  defaultThreadName?: string;
189
+ loadOlderMessages?: string;
190
+ loadingOlderMessages?: string;
191
191
  showMoreMessage?: string;
192
192
  showLessMessage?: string;
193
193
  };
@@ -216,7 +216,6 @@ interface ChatConfig {
216
216
  };
217
217
  markdown?: ChatMarkdownConfig;
218
218
  voiceCompose?: {
219
- enabled?: boolean;
220
219
  defaultMode?: 'text' | 'voice';
221
220
  reviewMode?: VoiceReviewMode;
222
221
  autoSendDelayMs?: number;
@@ -270,7 +269,10 @@ interface ChatV2Props {
270
269
  sidebar?: ReactNode;
271
270
  isGenerating?: boolean;
272
271
  isMessagesLoading?: boolean;
272
+ isLoadingOlderMessages?: boolean;
273
+ hasMoreMessagesBefore?: boolean;
273
274
  callbacks?: ChatCallbacks;
275
+ onLoadOlderMessages?: () => void;
274
276
  user?: {
275
277
  id: string;
276
278
  name?: string;
package/dist/index.js CHANGED
@@ -28,8 +28,6 @@ var defaultChatConfig = {
28
28
  stopGenerationTooltip: "Stop generation",
29
29
  attachFiles: "Attach Files",
30
30
  attachFileTooltip: "Attach file",
31
- recordAudio: "Record Audio",
32
- recordAudioTooltip: "Record audio",
33
31
  voiceEnter: "Voice input",
34
32
  voiceExit: "Use keyboard",
35
33
  voiceTitle: "Voice",
@@ -87,6 +85,8 @@ var defaultChatConfig = {
87
85
  inputHelpText: "Press Enter to send, Shift+Enter to add a new line.",
88
86
  thinking: "Thinking...",
89
87
  defaultThreadName: "Main Thread",
88
+ loadOlderMessages: "Load older messages",
89
+ loadingOlderMessages: "Loading older messages...",
90
90
  showMoreMessage: "Show more",
91
91
  showLessMessage: "Show less"
92
92
  },
@@ -120,7 +120,6 @@ var defaultChatConfig = {
120
120
  components: {}
121
121
  },
122
122
  voiceCompose: {
123
- enabled: false,
124
123
  defaultMode: "text",
125
124
  reviewMode: "manual",
126
125
  autoSendDelayMs: 5e3,
@@ -3845,61 +3844,6 @@ var AttachmentPreview = memo3(function AttachmentPreview2({ attachment, onRemove
3845
3844
  attachment.fileName && attachment.kind !== "audio" && /* @__PURE__ */ jsx23("div", { className: "absolute bottom-0 left-0 right-0 bg-black/70 text-white text-xs p-1 rounded-b", children: /* @__PURE__ */ jsx23("p", { className: "truncate", children: attachment.fileName }) })
3846
3845
  ] }) });
3847
3846
  });
3848
- var AudioRecorder = memo3(function AudioRecorder2({ isRecording, onStartRecording, onStopRecording, onCancel, recordingDuration, config }) {
3849
- const formatTime = (seconds) => {
3850
- const mins = Math.floor(seconds / 60);
3851
- const secs = seconds % 60;
3852
- return `${mins}:${secs.toString().padStart(2, "0")}`;
3853
- };
3854
- if (!isRecording) {
3855
- return /* @__PURE__ */ jsxs13(Tooltip, { children: [
3856
- /* @__PURE__ */ jsx23(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx23(
3857
- Button,
3858
- {
3859
- variant: "outline",
3860
- size: "icon",
3861
- onClick: onStartRecording,
3862
- className: "h-10 w-10",
3863
- children: /* @__PURE__ */ jsx23(Mic2, { className: "h-4 w-4" })
3864
- }
3865
- ) }),
3866
- /* @__PURE__ */ jsx23(TooltipContent, { children: config?.labels?.recordAudioTooltip })
3867
- ] });
3868
- }
3869
- return /* @__PURE__ */ jsx23(Card, { className: "border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950", children: /* @__PURE__ */ jsx23(CardContent, { className: "p-3", children: /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-3", children: [
3870
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
3871
- /* @__PURE__ */ jsx23("div", { className: "h-3 w-3 bg-red-500 rounded-full animate-pulse" }),
3872
- /* @__PURE__ */ jsx23("span", { className: "text-sm font-medium text-red-700 dark:text-red-300", children: config?.labels?.voiceListening || "Recording" })
3873
- ] }),
3874
- /* @__PURE__ */ jsx23(Badge, { variant: "outline", className: "text-xs", children: formatTime(recordingDuration) }),
3875
- /* @__PURE__ */ jsxs13("div", { className: "flex gap-1 ml-auto", children: [
3876
- /* @__PURE__ */ jsxs13(
3877
- Button,
3878
- {
3879
- variant: "outline",
3880
- size: "sm",
3881
- onClick: onCancel,
3882
- children: [
3883
- /* @__PURE__ */ jsx23(X4, { className: "h-3 w-3 mr-1" }),
3884
- config?.labels?.cancel || "Cancel"
3885
- ]
3886
- }
3887
- ),
3888
- /* @__PURE__ */ jsxs13(
3889
- Button,
3890
- {
3891
- variant: "default",
3892
- size: "sm",
3893
- onClick: onStopRecording,
3894
- children: [
3895
- /* @__PURE__ */ jsx23(Square2, { className: "h-3 w-3 mr-1" }),
3896
- config?.labels?.voiceStop || "Stop"
3897
- ]
3898
- }
3899
- )
3900
- ] })
3901
- ] }) }) });
3902
- });
3903
3847
  var resolveVoiceErrorMessage = (error, config) => {
3904
3848
  if (error instanceof DOMException && error.name === "NotAllowedError") {
3905
3849
  return config?.labels?.voicePermissionDenied || "Microphone access was denied.";
@@ -3932,7 +3876,6 @@ var ChatInput = memo3(function ChatInput2({
3932
3876
  mentionAgents = [],
3933
3877
  onTargetAgentChange
3934
3878
  }) {
3935
- const voiceComposeEnabled = config?.voiceCompose?.enabled === true;
3936
3879
  const voiceDefaultMode = config?.voiceCompose?.defaultMode ?? "text";
3937
3880
  const voiceReviewMode = config?.voiceCompose?.reviewMode ?? "manual";
3938
3881
  const voiceAutoSendDelayMs = config?.voiceCompose?.autoSendDelayMs ?? 5e3;
@@ -3940,12 +3883,10 @@ var ChatInput = memo3(function ChatInput2({
3940
3883
  const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
3941
3884
  const voiceTranscriptMode = config?.voiceCompose?.transcriptMode ?? "final-only";
3942
3885
  const voiceMaxRecordingMs = config?.voiceCompose?.maxRecordingMs;
3943
- const [isRecording, setIsRecording] = useState6(false);
3944
3886
  const { setContext } = useChatUserContext();
3945
- const [recordingDuration, setRecordingDuration] = useState6(0);
3946
3887
  const [uploadProgress, setUploadProgress] = useState6(/* @__PURE__ */ new Map());
3947
3888
  const [isVoiceComposerOpen, setIsVoiceComposerOpen] = useState6(
3948
- () => voiceComposeEnabled && voiceDefaultMode === "voice"
3889
+ () => enableAudioRecording && voiceDefaultMode === "voice"
3949
3890
  );
3950
3891
  const [voiceState, setVoiceState] = useState6("idle");
3951
3892
  const [voiceDraft, setVoiceDraft] = useState6(null);
@@ -3959,10 +3900,6 @@ var ChatInput = memo3(function ChatInput2({
3959
3900
  const [activeMentionIndex, setActiveMentionIndex] = useState6(0);
3960
3901
  const textareaRef = useRef5(null);
3961
3902
  const fileInputRef = useRef5(null);
3962
- const mediaRecorderRef = useRef5(null);
3963
- const recordingStartTime = useRef5(0);
3964
- const recordingInterval = useRef5(null);
3965
- const mediaStreamRef = useRef5(null);
3966
3903
  const voiceProviderRef = useRef5(null);
3967
3904
  const voiceDraftRef = useRef5(null);
3968
3905
  const voiceAppendBaseRef = useRef5(null);
@@ -3998,12 +3935,6 @@ var ChatInput = memo3(function ChatInput2({
3998
3935
  }, []);
3999
3936
  useEffect9(() => {
4000
3937
  return () => {
4001
- if (mediaStreamRef.current) {
4002
- mediaStreamRef.current.getTracks().forEach((track) => track.stop());
4003
- }
4004
- if (recordingInterval.current) {
4005
- clearInterval(recordingInterval.current);
4006
- }
4007
3938
  if (voiceProviderRef.current) {
4008
3939
  void voiceProviderRef.current.destroy();
4009
3940
  voiceProviderRef.current = null;
@@ -4176,73 +4107,6 @@ var ChatInput = memo3(function ChatInput2({
4176
4107
  const handleDragOver = useCallback3((e) => {
4177
4108
  e.preventDefault();
4178
4109
  }, []);
4179
- const startRecording = async () => {
4180
- try {
4181
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
4182
- mediaStreamRef.current = stream;
4183
- const mediaRecorder = new MediaRecorder(stream);
4184
- mediaRecorderRef.current = mediaRecorder;
4185
- const chunks = [];
4186
- mediaRecorder.ondataavailable = (e) => {
4187
- chunks.push(e.data);
4188
- };
4189
- mediaRecorder.onstop = async () => {
4190
- const blob = new Blob(chunks, { type: "audio/webm" });
4191
- const dataUrl = await new Promise((resolve, reject) => {
4192
- const reader = new FileReader();
4193
- reader.onload = () => resolve(reader.result);
4194
- reader.onerror = reject;
4195
- reader.readAsDataURL(blob);
4196
- });
4197
- const attachment = {
4198
- kind: "audio",
4199
- dataUrl,
4200
- mimeType: blob.type,
4201
- durationMs: recordingDuration * 1e3,
4202
- fileName: `audio_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}.webm`,
4203
- size: blob.size
4204
- };
4205
- onAttachmentsChange([...attachments, attachment]);
4206
- if (mediaStreamRef.current) {
4207
- mediaStreamRef.current.getTracks().forEach((track) => track.stop());
4208
- mediaStreamRef.current = null;
4209
- }
4210
- };
4211
- recordingStartTime.current = Date.now();
4212
- setRecordingDuration(0);
4213
- setIsRecording(true);
4214
- mediaRecorder.start();
4215
- recordingInterval.current = setInterval(() => {
4216
- const duration = Math.floor((Date.now() - recordingStartTime.current) / 1e3);
4217
- setRecordingDuration(duration);
4218
- }, 1e3);
4219
- } catch (error) {
4220
- console.error("Error starting recording:", error);
4221
- alert(config?.labels?.voicePermissionDenied || "Microphone access was denied.");
4222
- }
4223
- };
4224
- const stopRecording = () => {
4225
- if (mediaRecorderRef.current && isRecording) {
4226
- mediaRecorderRef.current.stop();
4227
- setIsRecording(false);
4228
- if (recordingInterval.current) {
4229
- clearInterval(recordingInterval.current);
4230
- }
4231
- }
4232
- };
4233
- const cancelRecording = () => {
4234
- if (mediaRecorderRef.current && isRecording) {
4235
- mediaRecorderRef.current.stop();
4236
- setIsRecording(false);
4237
- if (recordingInterval.current) {
4238
- clearInterval(recordingInterval.current);
4239
- }
4240
- if (mediaStreamRef.current) {
4241
- mediaStreamRef.current.getTracks().forEach((track) => track.stop());
4242
- mediaStreamRef.current = null;
4243
- }
4244
- }
4245
- };
4246
4110
  const resetVoiceComposerState = useCallback3((nextState = "idle") => {
4247
4111
  setVoiceState(nextState);
4248
4112
  setVoiceDraft(null);
@@ -4527,7 +4391,7 @@ var ChatInput = memo3(function ChatInput2({
4527
4391
  onAttachmentsChange(newAttachments);
4528
4392
  };
4529
4393
  const canAddMoreAttachments = attachments.length < maxAttachments;
4530
- const showVoiceComposer = voiceComposeEnabled && isVoiceComposerOpen;
4394
+ const showVoiceComposer = enableAudioRecording && isVoiceComposerOpen;
4531
4395
  return /* @__PURE__ */ jsx23(TooltipProvider, { children: /* @__PURE__ */ jsx23("div", { className: `border-t py-0 bg-transparent ${className}`, children: /* @__PURE__ */ jsxs13("div", { className: "px-0 md:p-2 pb-1 space-y-4 bg-transparent", children: [
4532
4396
  uploadProgress.size > 0 && /* @__PURE__ */ jsx23("div", { className: "space-y-2", children: Array.from(uploadProgress.entries()).map(([id, progress]) => /* @__PURE__ */ jsx23(
4533
4397
  FileUploadItem,
@@ -4544,17 +4408,6 @@ var ChatInput = memo3(function ChatInput2({
4544
4408
  },
4545
4409
  id
4546
4410
  )) }),
4547
- isRecording && /* @__PURE__ */ jsx23(
4548
- AudioRecorder,
4549
- {
4550
- isRecording,
4551
- onStartRecording: startRecording,
4552
- onStopRecording: stopRecording,
4553
- onCancel: cancelRecording,
4554
- recordingDuration,
4555
- config
4556
- }
4557
- ),
4558
4411
  attachments.length > 0 && /* @__PURE__ */ jsx23("div", { className: "grid grid-cols-4 gap-2", children: attachments.map((attachment, index) => /* @__PURE__ */ jsx23(
4559
4412
  AttachmentPreview,
4560
4413
  {
@@ -4684,7 +4537,7 @@ var ChatInput = memo3(function ChatInput2({
4684
4537
  agent.id
4685
4538
  )) }) })
4686
4539
  ] }),
4687
- enableAudioRecording && !isRecording && canAddMoreAttachments && !value.trim() && (voiceComposeEnabled ? /* @__PURE__ */ jsxs13(Tooltip, { children: [
4540
+ enableAudioRecording && canAddMoreAttachments && !value.trim() && /* @__PURE__ */ jsxs13(Tooltip, { children: [
4688
4541
  /* @__PURE__ */ jsx23(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx23(
4689
4542
  Button,
4690
4543
  {
@@ -4699,18 +4552,8 @@ var ChatInput = memo3(function ChatInput2({
4699
4552
  children: /* @__PURE__ */ jsx23(Mic2, { className: "h-4 w-4" })
4700
4553
  }
4701
4554
  ) }),
4702
- /* @__PURE__ */ jsx23(TooltipContent, { children: config?.labels?.voiceEnter || config?.labels?.recordAudioTooltip })
4703
- ] }) : /* @__PURE__ */ jsx23(
4704
- AudioRecorder,
4705
- {
4706
- isRecording,
4707
- onStartRecording: startRecording,
4708
- onStopRecording: stopRecording,
4709
- onCancel: cancelRecording,
4710
- recordingDuration,
4711
- config
4712
- }
4713
- )),
4555
+ /* @__PURE__ */ jsx23(TooltipContent, { children: config?.labels?.voiceEnter })
4556
+ ] }),
4714
4557
  isGenerating ? /* @__PURE__ */ jsxs13(Tooltip, { children: [
4715
4558
  /* @__PURE__ */ jsx23(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx23(
4716
4559
  Button,
@@ -5243,7 +5086,10 @@ var ChatUI = ({
5243
5086
  sidebar: _sidebar,
5244
5087
  isGenerating = false,
5245
5088
  isMessagesLoading = false,
5089
+ isLoadingOlderMessages = false,
5090
+ hasMoreMessagesBefore = false,
5246
5091
  callbacks = {},
5092
+ onLoadOlderMessages,
5247
5093
  user,
5248
5094
  assistant,
5249
5095
  suggestions = [],
@@ -5310,6 +5156,7 @@ var ChatUI = ({
5310
5156
  }
5311
5157
  }, [initialInput]);
5312
5158
  const scrollAreaRef = useRef6(null);
5159
+ const prependSnapshotRef = useRef6(null);
5313
5160
  const stateRef = useRef6(state);
5314
5161
  const inputValueRef = useRef6(inputValue);
5315
5162
  const attachmentsRef = useRef6(attachments);
@@ -5367,6 +5214,10 @@ var ChatUI = ({
5367
5214
  prevMessageCountRef.current = 0;
5368
5215
  return;
5369
5216
  }
5217
+ if (prependSnapshotRef.current) {
5218
+ prevMessageCountRef.current = messages.length;
5219
+ return;
5220
+ }
5370
5221
  const wasEmpty = prevMessageCountRef.current === 0;
5371
5222
  prevMessageCountRef.current = messages.length;
5372
5223
  if (wasEmpty) {
@@ -5391,6 +5242,46 @@ var ChatUI = ({
5391
5242
  useEffect10(() => {
5392
5243
  virtualizer.measure();
5393
5244
  }, [expandedMessageIds, virtualizer]);
5245
+ useEffect10(() => {
5246
+ prependSnapshotRef.current = null;
5247
+ }, [currentThreadId]);
5248
+ useEffect10(() => {
5249
+ const snapshot = prependSnapshotRef.current;
5250
+ if (!snapshot) return;
5251
+ if (messages.length <= snapshot.messageCount) {
5252
+ if (!isLoadingOlderMessages) {
5253
+ prependSnapshotRef.current = null;
5254
+ }
5255
+ return;
5256
+ }
5257
+ if ((messages[0]?.id ?? null) === snapshot.firstMessageId) {
5258
+ if (!isLoadingOlderMessages) {
5259
+ prependSnapshotRef.current = null;
5260
+ }
5261
+ return;
5262
+ }
5263
+ requestAnimationFrame(() => {
5264
+ virtualizer.measure();
5265
+ requestAnimationFrame(() => {
5266
+ const viewport = scrollAreaRef.current;
5267
+ if (!viewport) return;
5268
+ const heightDelta = viewport.scrollHeight - snapshot.scrollHeight;
5269
+ viewport.scrollTop = snapshot.scrollTop + heightDelta;
5270
+ prependSnapshotRef.current = null;
5271
+ });
5272
+ });
5273
+ }, [messages, isLoadingOlderMessages, virtualizer]);
5274
+ const requestOlderMessages = useCallback4(() => {
5275
+ if (!onLoadOlderMessages || !hasMoreMessagesBefore || isLoadingOlderMessages) return;
5276
+ const viewport = scrollAreaRef.current;
5277
+ prependSnapshotRef.current = viewport ? {
5278
+ scrollHeight: viewport.scrollHeight,
5279
+ scrollTop: viewport.scrollTop,
5280
+ firstMessageId: messages[0]?.id ?? null,
5281
+ messageCount: messages.length
5282
+ } : null;
5283
+ onLoadOlderMessages();
5284
+ }, [hasMoreMessagesBefore, isLoadingOlderMessages, messages, onLoadOlderMessages]);
5394
5285
  useEffect10(() => {
5395
5286
  const validMessageIds = new Set(messages.map((message) => message.id));
5396
5287
  setExpandedMessageIds((prev) => {
@@ -5409,11 +5300,15 @@ var ChatUI = ({
5409
5300
  const handleScroll = useCallback4((e) => {
5410
5301
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
5411
5302
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
5303
+ const isNearTop = scrollTop < 120;
5304
+ if (isNearTop && hasMoreMessagesBefore && !isLoadingOlderMessages) {
5305
+ requestOlderMessages();
5306
+ }
5412
5307
  setState((prev) => {
5413
5308
  if (prev.isAtBottom === isAtBottom) return prev;
5414
5309
  return { ...prev, isAtBottom };
5415
5310
  });
5416
- }, []);
5311
+ }, [hasMoreMessagesBefore, isLoadingOlderMessages, requestOlderMessages]);
5417
5312
  const handleSendMessage = useCallback4((content, messageAttachments = []) => {
5418
5313
  if (!content.trim() && messageAttachments.length === 0) return;
5419
5314
  callbacks.onSendMessage?.(content, messageAttachments, createStateCallback());
@@ -5669,48 +5564,59 @@ var ChatUI = ({
5669
5564
  viewportClassName: "p-4 overscroll-contain",
5670
5565
  onScrollCapture: handleScroll,
5671
5566
  style: { contain: "strict" },
5672
- children: /* @__PURE__ */ jsx26("div", { className: "max-w-4xl mx-auto pb-4", children: isMessagesLoading ? renderMessageLoadingSkeleton() : messages.length === 0 ? renderSuggestions() : /* @__PURE__ */ jsx26(
5673
- "div",
5674
- {
5675
- style: {
5676
- height: `${virtualizer.getTotalSize()}px`,
5677
- width: "100%",
5678
- position: "relative"
5679
- },
5680
- children: virtualizer.getVirtualItems().map((virtualRow) => {
5681
- const message = messages[virtualRow.index];
5682
- const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5683
- const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5684
- return /* @__PURE__ */ jsx26(
5685
- "div",
5686
- {
5687
- "data-index": virtualRow.index,
5688
- ref: virtualizer.measureElement,
5689
- style: {
5690
- position: "absolute",
5691
- top: 0,
5692
- left: 0,
5693
- width: "100%",
5694
- transform: `translateY(${virtualRow.start}px)`
5567
+ children: /* @__PURE__ */ jsxs16("div", { className: "max-w-4xl mx-auto pb-4", children: [
5568
+ messages.length > 0 && /* @__PURE__ */ jsx26("div", { className: "flex justify-center py-2", children: isLoadingOlderMessages ? /* @__PURE__ */ jsx26("span", { className: "text-xs text-muted-foreground", children: config.labels.loadingOlderMessages }) : hasMoreMessagesBefore ? /* @__PURE__ */ jsx26(
5569
+ "button",
5570
+ {
5571
+ type: "button",
5572
+ onClick: requestOlderMessages,
5573
+ className: "text-xs text-muted-foreground transition-colors hover:text-foreground",
5574
+ children: config.labels.loadOlderMessages
5575
+ }
5576
+ ) : null }),
5577
+ isMessagesLoading ? renderMessageLoadingSkeleton() : messages.length === 0 ? renderSuggestions() : /* @__PURE__ */ jsx26(
5578
+ "div",
5579
+ {
5580
+ style: {
5581
+ height: `${virtualizer.getTotalSize()}px`,
5582
+ width: "100%",
5583
+ position: "relative"
5584
+ },
5585
+ children: virtualizer.getVirtualItems().map((virtualRow) => {
5586
+ const message = messages[virtualRow.index];
5587
+ const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5588
+ const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5589
+ return /* @__PURE__ */ jsx26(
5590
+ "div",
5591
+ {
5592
+ "data-index": virtualRow.index,
5593
+ ref: virtualizer.measureElement,
5594
+ style: {
5595
+ position: "absolute",
5596
+ top: 0,
5597
+ left: 0,
5598
+ width: "100%",
5599
+ transform: `translateY(${virtualRow.start}px)`
5600
+ },
5601
+ children: /* @__PURE__ */ jsxs16("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5602
+ /* @__PURE__ */ jsx26(
5603
+ Message,
5604
+ {
5605
+ message,
5606
+ ...messageProps,
5607
+ isGrouped,
5608
+ isExpanded: Boolean(expandedMessageIds[message.id])
5609
+ }
5610
+ ),
5611
+ message.role === "assistant" && renderInlineSuggestions(message.id)
5612
+ ] })
5695
5613
  },
5696
- children: /* @__PURE__ */ jsxs16("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5697
- /* @__PURE__ */ jsx26(
5698
- Message,
5699
- {
5700
- message,
5701
- ...messageProps,
5702
- isGrouped,
5703
- isExpanded: Boolean(expandedMessageIds[message.id])
5704
- }
5705
- ),
5706
- message.role === "assistant" && renderInlineSuggestions(message.id)
5707
- ] })
5708
- },
5709
- message.id
5710
- );
5711
- })
5712
- }
5713
- ) })
5614
+ message.id
5615
+ );
5616
+ })
5617
+ }
5618
+ )
5619
+ ] })
5714
5620
  }
5715
5621
  ),
5716
5622
  /* @__PURE__ */ jsxs16("div", { className: "bg-background pb-[env(safe-area-inset-bottom)]", children: [