@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 +112 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +112 -206
- package/dist/index.js.map +1 -1
- package/dist/styles.css +0 -12
- package/package.json +1 -1
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
|
-
() =>
|
|
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 =
|
|
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 &&
|
|
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
|
|
4708
|
-
] })
|
|
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.
|
|
5652
|
-
"div",
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
{
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
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
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
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: [
|