@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.cjs CHANGED
@@ -85,8 +85,6 @@ var defaultChatConfig = {
85
85
  stopGenerationTooltip: "Stop generation",
86
86
  attachFiles: "Attach Files",
87
87
  attachFileTooltip: "Attach file",
88
- recordAudio: "Record Audio",
89
- recordAudioTooltip: "Record audio",
90
88
  voiceEnter: "Voice input",
91
89
  voiceExit: "Use keyboard",
92
90
  voiceTitle: "Voice",
@@ -144,6 +142,8 @@ var defaultChatConfig = {
144
142
  inputHelpText: "Press Enter to send, Shift+Enter to add a new line.",
145
143
  thinking: "Thinking...",
146
144
  defaultThreadName: "Main Thread",
145
+ loadOlderMessages: "Load older messages",
146
+ loadingOlderMessages: "Loading older messages...",
147
147
  showMoreMessage: "Show more",
148
148
  showLessMessage: "Show less"
149
149
  },
@@ -177,7 +177,6 @@ var defaultChatConfig = {
177
177
  components: {}
178
178
  },
179
179
  voiceCompose: {
180
- enabled: false,
181
180
  defaultMode: "text",
182
181
  reviewMode: "manual",
183
182
  autoSendDelayMs: 5e3,
@@ -3850,61 +3849,6 @@ var AttachmentPreview = (0, import_react6.memo)(function AttachmentPreview2({ at
3850
3849
  attachment.fileName && attachment.kind !== "audio" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "absolute bottom-0 left-0 right-0 bg-black/70 text-white text-xs p-1 rounded-b", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "truncate", children: attachment.fileName }) })
3851
3850
  ] }) });
3852
3851
  });
3853
- var AudioRecorder = (0, import_react6.memo)(function AudioRecorder2({ isRecording, onStartRecording, onStopRecording, onCancel, recordingDuration, config }) {
3854
- const formatTime = (seconds) => {
3855
- const mins = Math.floor(seconds / 60);
3856
- const secs = seconds % 60;
3857
- return `${mins}:${secs.toString().padStart(2, "0")}`;
3858
- };
3859
- if (!isRecording) {
3860
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Tooltip, { children: [
3861
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3862
- Button,
3863
- {
3864
- variant: "outline",
3865
- size: "icon",
3866
- onClick: onStartRecording,
3867
- className: "h-10 w-10",
3868
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Mic, { className: "h-4 w-4" })
3869
- }
3870
- ) }),
3871
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipContent, { children: config?.labels?.recordAudioTooltip })
3872
- ] });
3873
- }
3874
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Card, { className: "border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CardContent, { className: "p-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-3", children: [
3875
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-2", children: [
3876
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "h-3 w-3 bg-red-500 rounded-full animate-pulse" }),
3877
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-sm font-medium text-red-700 dark:text-red-300", children: config?.labels?.voiceListening || "Recording" })
3878
- ] }),
3879
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Badge, { variant: "outline", className: "text-xs", children: formatTime(recordingDuration) }),
3880
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex gap-1 ml-auto", children: [
3881
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
3882
- Button,
3883
- {
3884
- variant: "outline",
3885
- size: "sm",
3886
- onClick: onCancel,
3887
- children: [
3888
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.X, { className: "h-3 w-3 mr-1" }),
3889
- config?.labels?.cancel || "Cancel"
3890
- ]
3891
- }
3892
- ),
3893
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
3894
- Button,
3895
- {
3896
- variant: "default",
3897
- size: "sm",
3898
- onClick: onStopRecording,
3899
- children: [
3900
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Square, { className: "h-3 w-3 mr-1" }),
3901
- config?.labels?.voiceStop || "Stop"
3902
- ]
3903
- }
3904
- )
3905
- ] })
3906
- ] }) }) });
3907
- });
3908
3852
  var resolveVoiceErrorMessage = (error, config) => {
3909
3853
  if (error instanceof DOMException && error.name === "NotAllowedError") {
3910
3854
  return config?.labels?.voicePermissionDenied || "Microphone access was denied.";
@@ -3937,7 +3881,6 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
3937
3881
  mentionAgents = [],
3938
3882
  onTargetAgentChange
3939
3883
  }) {
3940
- const voiceComposeEnabled = config?.voiceCompose?.enabled === true;
3941
3884
  const voiceDefaultMode = config?.voiceCompose?.defaultMode ?? "text";
3942
3885
  const voiceReviewMode = config?.voiceCompose?.reviewMode ?? "manual";
3943
3886
  const voiceAutoSendDelayMs = config?.voiceCompose?.autoSendDelayMs ?? 5e3;
@@ -3945,12 +3888,10 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
3945
3888
  const voiceShowTranscriptPreview = config?.voiceCompose?.showTranscriptPreview ?? true;
3946
3889
  const voiceTranscriptMode = config?.voiceCompose?.transcriptMode ?? "final-only";
3947
3890
  const voiceMaxRecordingMs = config?.voiceCompose?.maxRecordingMs;
3948
- const [isRecording, setIsRecording] = (0, import_react6.useState)(false);
3949
3891
  const { setContext } = useChatUserContext();
3950
- const [recordingDuration, setRecordingDuration] = (0, import_react6.useState)(0);
3951
3892
  const [uploadProgress, setUploadProgress] = (0, import_react6.useState)(/* @__PURE__ */ new Map());
3952
3893
  const [isVoiceComposerOpen, setIsVoiceComposerOpen] = (0, import_react6.useState)(
3953
- () => voiceComposeEnabled && voiceDefaultMode === "voice"
3894
+ () => enableAudioRecording && voiceDefaultMode === "voice"
3954
3895
  );
3955
3896
  const [voiceState, setVoiceState] = (0, import_react6.useState)("idle");
3956
3897
  const [voiceDraft, setVoiceDraft] = (0, import_react6.useState)(null);
@@ -3964,10 +3905,6 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
3964
3905
  const [activeMentionIndex, setActiveMentionIndex] = (0, import_react6.useState)(0);
3965
3906
  const textareaRef = (0, import_react6.useRef)(null);
3966
3907
  const fileInputRef = (0, import_react6.useRef)(null);
3967
- const mediaRecorderRef = (0, import_react6.useRef)(null);
3968
- const recordingStartTime = (0, import_react6.useRef)(0);
3969
- const recordingInterval = (0, import_react6.useRef)(null);
3970
- const mediaStreamRef = (0, import_react6.useRef)(null);
3971
3908
  const voiceProviderRef = (0, import_react6.useRef)(null);
3972
3909
  const voiceDraftRef = (0, import_react6.useRef)(null);
3973
3910
  const voiceAppendBaseRef = (0, import_react6.useRef)(null);
@@ -4003,12 +3940,6 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
4003
3940
  }, []);
4004
3941
  (0, import_react6.useEffect)(() => {
4005
3942
  return () => {
4006
- if (mediaStreamRef.current) {
4007
- mediaStreamRef.current.getTracks().forEach((track) => track.stop());
4008
- }
4009
- if (recordingInterval.current) {
4010
- clearInterval(recordingInterval.current);
4011
- }
4012
3943
  if (voiceProviderRef.current) {
4013
3944
  void voiceProviderRef.current.destroy();
4014
3945
  voiceProviderRef.current = null;
@@ -4181,73 +4112,6 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
4181
4112
  const handleDragOver = (0, import_react6.useCallback)((e) => {
4182
4113
  e.preventDefault();
4183
4114
  }, []);
4184
- const startRecording = async () => {
4185
- try {
4186
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
4187
- mediaStreamRef.current = stream;
4188
- const mediaRecorder = new MediaRecorder(stream);
4189
- mediaRecorderRef.current = mediaRecorder;
4190
- const chunks = [];
4191
- mediaRecorder.ondataavailable = (e) => {
4192
- chunks.push(e.data);
4193
- };
4194
- mediaRecorder.onstop = async () => {
4195
- const blob = new Blob(chunks, { type: "audio/webm" });
4196
- const dataUrl = await new Promise((resolve, reject) => {
4197
- const reader = new FileReader();
4198
- reader.onload = () => resolve(reader.result);
4199
- reader.onerror = reject;
4200
- reader.readAsDataURL(blob);
4201
- });
4202
- const attachment = {
4203
- kind: "audio",
4204
- dataUrl,
4205
- mimeType: blob.type,
4206
- durationMs: recordingDuration * 1e3,
4207
- fileName: `audio_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}.webm`,
4208
- size: blob.size
4209
- };
4210
- onAttachmentsChange([...attachments, attachment]);
4211
- if (mediaStreamRef.current) {
4212
- mediaStreamRef.current.getTracks().forEach((track) => track.stop());
4213
- mediaStreamRef.current = null;
4214
- }
4215
- };
4216
- recordingStartTime.current = Date.now();
4217
- setRecordingDuration(0);
4218
- setIsRecording(true);
4219
- mediaRecorder.start();
4220
- recordingInterval.current = setInterval(() => {
4221
- const duration = Math.floor((Date.now() - recordingStartTime.current) / 1e3);
4222
- setRecordingDuration(duration);
4223
- }, 1e3);
4224
- } catch (error) {
4225
- console.error("Error starting recording:", error);
4226
- alert(config?.labels?.voicePermissionDenied || "Microphone access was denied.");
4227
- }
4228
- };
4229
- const stopRecording = () => {
4230
- if (mediaRecorderRef.current && isRecording) {
4231
- mediaRecorderRef.current.stop();
4232
- setIsRecording(false);
4233
- if (recordingInterval.current) {
4234
- clearInterval(recordingInterval.current);
4235
- }
4236
- }
4237
- };
4238
- const cancelRecording = () => {
4239
- if (mediaRecorderRef.current && isRecording) {
4240
- mediaRecorderRef.current.stop();
4241
- setIsRecording(false);
4242
- if (recordingInterval.current) {
4243
- clearInterval(recordingInterval.current);
4244
- }
4245
- if (mediaStreamRef.current) {
4246
- mediaStreamRef.current.getTracks().forEach((track) => track.stop());
4247
- mediaStreamRef.current = null;
4248
- }
4249
- }
4250
- };
4251
4115
  const resetVoiceComposerState = (0, import_react6.useCallback)((nextState = "idle") => {
4252
4116
  setVoiceState(nextState);
4253
4117
  setVoiceDraft(null);
@@ -4532,7 +4396,7 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
4532
4396
  onAttachmentsChange(newAttachments);
4533
4397
  };
4534
4398
  const canAddMoreAttachments = attachments.length < maxAttachments;
4535
- const showVoiceComposer = voiceComposeEnabled && isVoiceComposerOpen;
4399
+ const showVoiceComposer = enableAudioRecording && isVoiceComposerOpen;
4536
4400
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: `border-t py-0 bg-transparent ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "px-0 md:p-2 pb-1 space-y-4 bg-transparent", children: [
4537
4401
  uploadProgress.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "space-y-2", children: Array.from(uploadProgress.entries()).map(([id, progress]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4538
4402
  FileUploadItem,
@@ -4549,17 +4413,6 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
4549
4413
  },
4550
4414
  id
4551
4415
  )) }),
4552
- isRecording && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4553
- AudioRecorder,
4554
- {
4555
- isRecording,
4556
- onStartRecording: startRecording,
4557
- onStopRecording: stopRecording,
4558
- onCancel: cancelRecording,
4559
- recordingDuration,
4560
- config
4561
- }
4562
- ),
4563
4416
  attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "grid grid-cols-4 gap-2", children: attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4564
4417
  AttachmentPreview,
4565
4418
  {
@@ -4689,7 +4542,7 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
4689
4542
  agent.id
4690
4543
  )) }) })
4691
4544
  ] }),
4692
- enableAudioRecording && !isRecording && canAddMoreAttachments && !value.trim() && (voiceComposeEnabled ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Tooltip, { children: [
4545
+ enableAudioRecording && canAddMoreAttachments && !value.trim() && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Tooltip, { children: [
4693
4546
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4694
4547
  Button,
4695
4548
  {
@@ -4704,18 +4557,8 @@ var ChatInput = (0, import_react6.memo)(function ChatInput2({
4704
4557
  children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Mic, { className: "h-4 w-4" })
4705
4558
  }
4706
4559
  ) }),
4707
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipContent, { children: config?.labels?.voiceEnter || config?.labels?.recordAudioTooltip })
4708
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4709
- AudioRecorder,
4710
- {
4711
- isRecording,
4712
- onStartRecording: startRecording,
4713
- onStopRecording: stopRecording,
4714
- onCancel: cancelRecording,
4715
- recordingDuration,
4716
- config
4717
- }
4718
- )),
4560
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipContent, { children: config?.labels?.voiceEnter })
4561
+ ] }),
4719
4562
  isGenerating ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Tooltip, { children: [
4720
4563
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4721
4564
  Button,
@@ -5222,7 +5065,10 @@ var ChatUI = ({
5222
5065
  sidebar: _sidebar,
5223
5066
  isGenerating = false,
5224
5067
  isMessagesLoading = false,
5068
+ isLoadingOlderMessages = false,
5069
+ hasMoreMessagesBefore = false,
5225
5070
  callbacks = {},
5071
+ onLoadOlderMessages,
5226
5072
  user,
5227
5073
  assistant,
5228
5074
  suggestions = [],
@@ -5289,6 +5135,7 @@ var ChatUI = ({
5289
5135
  }
5290
5136
  }, [initialInput]);
5291
5137
  const scrollAreaRef = (0, import_react8.useRef)(null);
5138
+ const prependSnapshotRef = (0, import_react8.useRef)(null);
5292
5139
  const stateRef = (0, import_react8.useRef)(state);
5293
5140
  const inputValueRef = (0, import_react8.useRef)(inputValue);
5294
5141
  const attachmentsRef = (0, import_react8.useRef)(attachments);
@@ -5346,6 +5193,10 @@ var ChatUI = ({
5346
5193
  prevMessageCountRef.current = 0;
5347
5194
  return;
5348
5195
  }
5196
+ if (prependSnapshotRef.current) {
5197
+ prevMessageCountRef.current = messages.length;
5198
+ return;
5199
+ }
5349
5200
  const wasEmpty = prevMessageCountRef.current === 0;
5350
5201
  prevMessageCountRef.current = messages.length;
5351
5202
  if (wasEmpty) {
@@ -5370,6 +5221,46 @@ var ChatUI = ({
5370
5221
  (0, import_react8.useEffect)(() => {
5371
5222
  virtualizer.measure();
5372
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]);
5373
5264
  (0, import_react8.useEffect)(() => {
5374
5265
  const validMessageIds = new Set(messages.map((message) => message.id));
5375
5266
  setExpandedMessageIds((prev) => {
@@ -5388,11 +5279,15 @@ var ChatUI = ({
5388
5279
  const handleScroll = (0, import_react8.useCallback)((e) => {
5389
5280
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
5390
5281
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
5282
+ const isNearTop = scrollTop < 120;
5283
+ if (isNearTop && hasMoreMessagesBefore && !isLoadingOlderMessages) {
5284
+ requestOlderMessages();
5285
+ }
5391
5286
  setState((prev) => {
5392
5287
  if (prev.isAtBottom === isAtBottom) return prev;
5393
5288
  return { ...prev, isAtBottom };
5394
5289
  });
5395
- }, []);
5290
+ }, [hasMoreMessagesBefore, isLoadingOlderMessages, requestOlderMessages]);
5396
5291
  const handleSendMessage = (0, import_react8.useCallback)((content, messageAttachments = []) => {
5397
5292
  if (!content.trim() && messageAttachments.length === 0) return;
5398
5293
  callbacks.onSendMessage?.(content, messageAttachments, createStateCallback());
@@ -5648,48 +5543,59 @@ var ChatUI = ({
5648
5543
  viewportClassName: "p-4 overscroll-contain",
5649
5544
  onScrollCapture: handleScroll,
5650
5545
  style: { contain: "strict" },
5651
- 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)(
5652
- "div",
5653
- {
5654
- style: {
5655
- height: `${virtualizer.getTotalSize()}px`,
5656
- width: "100%",
5657
- position: "relative"
5658
- },
5659
- children: virtualizer.getVirtualItems().map((virtualRow) => {
5660
- const message = messages[virtualRow.index];
5661
- const prevMessage = virtualRow.index > 0 ? messages[virtualRow.index - 1] : null;
5662
- const isGrouped = prevMessage !== null && prevMessage.role === message.role && getMessageSpeakerKey(prevMessage) === getMessageSpeakerKey(message);
5663
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5664
- "div",
5665
- {
5666
- "data-index": virtualRow.index,
5667
- ref: virtualizer.measureElement,
5668
- style: {
5669
- position: "absolute",
5670
- top: 0,
5671
- left: 0,
5672
- width: "100%",
5673
- 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
+ ] })
5674
5592
  },
5675
- children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: virtualRow.index === 0 ? "" : isGrouped ? "pt-2" : "pt-4", children: [
5676
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5677
- Message,
5678
- {
5679
- message,
5680
- ...messageProps,
5681
- isGrouped,
5682
- isExpanded: Boolean(expandedMessageIds[message.id])
5683
- }
5684
- ),
5685
- message.role === "assistant" && renderInlineSuggestions(message.id)
5686
- ] })
5687
- },
5688
- message.id
5689
- );
5690
- })
5691
- }
5692
- ) })
5593
+ message.id
5594
+ );
5595
+ })
5596
+ }
5597
+ )
5598
+ ] })
5693
5599
  }
5694
5600
  ),
5695
5601
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "bg-background pb-[env(safe-area-inset-bottom)]", children: [