@copilotz/chat-ui 0.9.15 → 0.9.16

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
@@ -173,6 +173,9 @@ interface ChatConfig {
173
173
  attachFiles?: string;
174
174
  attachFileTooltip?: string;
175
175
  fileTooLarge?: string;
176
+ fileProcessError?: string;
177
+ dropFiles?: string;
178
+ dropFilesHelp?: string;
176
179
  attachmentsCount?: string;
177
180
  voiceEnter?: string;
178
181
  voiceExit?: string;
package/dist/index.d.ts CHANGED
@@ -173,6 +173,9 @@ interface ChatConfig {
173
173
  attachFiles?: string;
174
174
  attachFileTooltip?: string;
175
175
  fileTooLarge?: string;
176
+ fileProcessError?: string;
177
+ dropFiles?: string;
178
+ dropFilesHelp?: string;
176
179
  attachmentsCount?: string;
177
180
  voiceEnter?: string;
178
181
  voiceExit?: string;
package/dist/index.js CHANGED
@@ -35,6 +35,9 @@ var defaultChatConfig = {
35
35
  attachFiles: "Attach Files",
36
36
  attachFileTooltip: "Attach file",
37
37
  fileTooLarge: "File too large. Max allowed: {{maxSize}}MB",
38
+ fileProcessError: "Failed to process file",
39
+ dropFiles: "Drop files to attach",
40
+ dropFilesHelp: "Up to {{remaining}} more files",
38
41
  attachmentsCount: "{{count}}/{{max}} attachments",
39
42
  voiceEnter: "Voice input",
40
43
  voiceExit: "Use keyboard",
@@ -4321,7 +4324,8 @@ import {
4321
4324
  Square as Square2,
4322
4325
  Play,
4323
4326
  Pause,
4324
- Loader2 as Loader22
4327
+ Loader2 as Loader22,
4328
+ UploadCloud
4325
4329
  } from "lucide-react";
4326
4330
  import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
4327
4331
  function getActiveMentionMatch(value, caret) {
@@ -4341,6 +4345,9 @@ function formatLabel(template, fallback, values) {
4341
4345
  (_, key) => String(values[key] ?? "")
4342
4346
  );
4343
4347
  }
4348
+ function hasDraggedFiles(dataTransfer) {
4349
+ return Array.from(dataTransfer.types || []).includes("Files");
4350
+ }
4344
4351
  function resolveTargetFromMentions(value, agents) {
4345
4352
  const matches = value.matchAll(/(^|\s)@([\w.-]+)/g);
4346
4353
  for (const match of matches) {
@@ -4604,8 +4611,10 @@ var ChatInput = memo4(function ChatInput2({
4604
4611
  const [voiceError, setVoiceError] = useState7(null);
4605
4612
  const [activeMention, setActiveMention] = useState7(null);
4606
4613
  const [activeMentionIndex, setActiveMentionIndex] = useState7(0);
4614
+ const [isDraggingFiles, setIsDraggingFiles] = useState7(false);
4607
4615
  const textareaRef = useRef5(null);
4608
4616
  const fileInputRef = useRef5(null);
4617
+ const dragDepthRef = useRef5(0);
4609
4618
  const voiceProviderRef = useRef5(null);
4610
4619
  const voiceDraftRef = useRef5(null);
4611
4620
  const voiceAppendBaseRef = useRef5(null);
@@ -4731,7 +4740,7 @@ var ChatInput = memo4(function ChatInput2({
4731
4740
  handleSubmit(e);
4732
4741
  }
4733
4742
  };
4734
- const processFile = async (file) => {
4743
+ const processFile = useCallback3(async (file) => {
4735
4744
  if (file.size > maxFileSize) {
4736
4745
  alert(formatLabel(
4737
4746
  config?.labels?.fileTooLarge,
@@ -4796,39 +4805,63 @@ var ChatInput = memo4(function ChatInput2({
4796
4805
  newMap.delete(fileId);
4797
4806
  return newMap;
4798
4807
  });
4799
- alert("Failed to process file");
4808
+ alert(config?.labels?.fileProcessError || "Failed to process file");
4800
4809
  return null;
4801
4810
  }
4802
- };
4803
- const handleFileSelect = async (e) => {
4804
- const files = e.target.files;
4805
- if (!files) return;
4811
+ }, [config?.labels?.fileProcessError, config?.labels?.fileTooLarge, maxFileSize, setContext]);
4812
+ const processFiles = useCallback3(async (files) => {
4813
+ if (!enableFileUpload || disabled || files.length === 0) return;
4806
4814
  const remainingSlots = maxAttachments - attachments.length;
4807
- const filesToProcess = Array.from(files).slice(0, remainingSlots);
4815
+ if (remainingSlots <= 0) return;
4816
+ const filesToProcess = files.slice(0, remainingSlots);
4817
+ const nextAttachments = [];
4808
4818
  for (const file of filesToProcess) {
4809
4819
  const attachment = await processFile(file);
4810
4820
  if (attachment) {
4811
- onAttachmentsChange([...attachments, attachment]);
4821
+ nextAttachments.push(attachment);
4812
4822
  }
4813
4823
  }
4824
+ if (nextAttachments.length > 0) {
4825
+ onAttachmentsChange([...attachments, ...nextAttachments]);
4826
+ }
4827
+ }, [attachments, disabled, enableFileUpload, maxAttachments, onAttachmentsChange, processFile]);
4828
+ const handleFileSelect = async (e) => {
4829
+ const files = e.target.files;
4830
+ if (!files) return;
4831
+ await processFiles(Array.from(files));
4814
4832
  e.target.value = "";
4815
4833
  };
4816
4834
  const handleDrop = useCallback3(async (e) => {
4835
+ if (!enableFileUpload || !hasDraggedFiles(e.dataTransfer)) return;
4817
4836
  e.preventDefault();
4818
- if (!enableFileUpload) return;
4819
- const files = Array.from(e.dataTransfer.files);
4820
- const remainingSlots = maxAttachments - attachments.length;
4821
- const filesToProcess = files.slice(0, remainingSlots);
4822
- for (const file of filesToProcess) {
4823
- const attachment = await processFile(file);
4824
- if (attachment) {
4825
- onAttachmentsChange([...attachments, attachment]);
4826
- }
4837
+ dragDepthRef.current = 0;
4838
+ setIsDraggingFiles(false);
4839
+ await processFiles(Array.from(e.dataTransfer.files));
4840
+ }, [enableFileUpload, processFiles]);
4841
+ const handleDragEnter = useCallback3((e) => {
4842
+ if (!enableFileUpload || !hasDraggedFiles(e.dataTransfer)) return;
4843
+ e.preventDefault();
4844
+ dragDepthRef.current += 1;
4845
+ if (attachments.length < maxAttachments) {
4846
+ setIsDraggingFiles(true);
4827
4847
  }
4828
- }, [attachments, enableFileUpload, maxAttachments, onAttachmentsChange]);
4848
+ }, [attachments.length, enableFileUpload, maxAttachments]);
4829
4849
  const handleDragOver = useCallback3((e) => {
4850
+ if (!enableFileUpload || !hasDraggedFiles(e.dataTransfer)) return;
4830
4851
  e.preventDefault();
4831
- }, []);
4852
+ e.dataTransfer.dropEffect = attachments.length < maxAttachments ? "copy" : "none";
4853
+ if (attachments.length < maxAttachments) {
4854
+ setIsDraggingFiles(true);
4855
+ }
4856
+ }, [attachments.length, enableFileUpload, maxAttachments]);
4857
+ const handleDragLeave = useCallback3((e) => {
4858
+ if (!enableFileUpload || !hasDraggedFiles(e.dataTransfer)) return;
4859
+ e.preventDefault();
4860
+ dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
4861
+ if (dragDepthRef.current === 0) {
4862
+ setIsDraggingFiles(false);
4863
+ }
4864
+ }, [enableFileUpload]);
4832
4865
  const resetVoiceComposerState = useCallback3((nextState = "idle") => {
4833
4866
  setVoiceState(nextState);
4834
4867
  setVoiceDraft(null);
@@ -5181,10 +5214,21 @@ var ChatInput = memo4(function ChatInput2({
5181
5214
  ) }) : /* @__PURE__ */ jsx25("form", { onSubmit: handleSubmit, className: "mb-1", children: /* @__PURE__ */ jsxs15(
5182
5215
  "div",
5183
5216
  {
5184
- className: "group/composer flex w-full flex-col gap-2 rounded-3xl border border-border/80 bg-card/95 p-2.5 shadow-sm transition-[border-color,box-shadow,background-color] focus-within:border-ring/60 focus-within:bg-card focus-within:shadow-md focus-within:ring-2 focus-within:ring-ring/15",
5217
+ className: `group/composer relative flex w-full flex-col gap-2 overflow-hidden rounded-3xl border border-border/80 bg-card/95 p-2.5 shadow-sm transition-[border-color,box-shadow,background-color] focus-within:border-ring/60 focus-within:bg-card focus-within:shadow-md focus-within:ring-2 focus-within:ring-ring/15 ${isDraggingFiles ? "border-primary/60 bg-primary/5 shadow-md ring-2 ring-primary/15" : ""}`,
5185
5218
  onDrop: handleDrop,
5219
+ onDragEnter: handleDragEnter,
5186
5220
  onDragOver: handleDragOver,
5221
+ onDragLeave: handleDragLeave,
5187
5222
  children: [
5223
+ isDraggingFiles && enableFileUpload && canAddMoreAttachments && /* @__PURE__ */ jsx25("div", { className: "pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-3xl border-2 border-dashed border-primary/60 bg-primary/10 text-primary backdrop-blur-[1px]", children: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col items-center gap-2 px-4 text-center", children: [
5224
+ /* @__PURE__ */ jsx25("span", { className: "flex h-11 w-11 items-center justify-center rounded-full bg-primary/15", children: /* @__PURE__ */ jsx25(UploadCloud, { className: "h-5 w-5" }) }),
5225
+ /* @__PURE__ */ jsx25("span", { className: "text-sm font-semibold", children: config?.labels?.dropFiles || "Drop files to attach" }),
5226
+ /* @__PURE__ */ jsx25("span", { className: "text-xs text-muted-foreground", children: formatLabel(
5227
+ config?.labels?.dropFilesHelp,
5228
+ "Up to {{remaining}} more files",
5229
+ { remaining: Math.max(0, maxAttachments - attachments.length) }
5230
+ ) })
5231
+ ] }) }),
5188
5232
  /* @__PURE__ */ jsxs15("div", { className: "relative min-w-0", children: [
5189
5233
  /* @__PURE__ */ jsx25(
5190
5234
  Textarea,