@cadraos/sdk 0.1.7 → 0.1.9
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/README.md +38 -5
- package/dist/attachments/index.cjs +879 -0
- package/dist/attachments/index.cjs.map +1 -0
- package/dist/attachments/index.d.cts +185 -0
- package/dist/attachments/index.d.ts +185 -0
- package/dist/attachments/index.js +875 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/chat/index.cjs +134 -2
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +56 -8
- package/dist/chat/index.d.ts +56 -8
- package/dist/chat/index.js +134 -2
- package/dist/chat/index.js.map +1 -1
- package/package.json +14 -1
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
import { forwardRef, useState, useRef, useImperativeHandle, useCallback, useEffect } from 'react';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/chat/utils/compress-image.ts
|
|
5
|
+
var DEFAULT_TARGET_SIZE_KB = 150;
|
|
6
|
+
var MAX_DIMENSION = 1920;
|
|
7
|
+
var QUALITY_START = 0.85;
|
|
8
|
+
var QUALITY_STEP = 0.05;
|
|
9
|
+
var QUALITY_MIN = 0.1;
|
|
10
|
+
function loadImage(file) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const img = new Image();
|
|
13
|
+
const url = URL.createObjectURL(file);
|
|
14
|
+
img.onload = () => {
|
|
15
|
+
URL.revokeObjectURL(url);
|
|
16
|
+
resolve(img);
|
|
17
|
+
};
|
|
18
|
+
img.onerror = () => {
|
|
19
|
+
URL.revokeObjectURL(url);
|
|
20
|
+
reject(new Error(`Failed to load image: ${file.name}`));
|
|
21
|
+
};
|
|
22
|
+
img.src = url;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function getScaledDimensions(width, height, maxDim) {
|
|
26
|
+
if (width <= maxDim && height <= maxDim) {
|
|
27
|
+
return { width, height };
|
|
28
|
+
}
|
|
29
|
+
const ratio = Math.min(maxDim / width, maxDim / height);
|
|
30
|
+
return {
|
|
31
|
+
width: Math.round(width * ratio),
|
|
32
|
+
height: Math.round(height * ratio)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function canvasToBlob(img, width, height, format, quality) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const canvas = document.createElement("canvas");
|
|
38
|
+
canvas.width = width;
|
|
39
|
+
canvas.height = height;
|
|
40
|
+
const ctx = canvas.getContext("2d");
|
|
41
|
+
if (!ctx) {
|
|
42
|
+
reject(new Error("Failed to get canvas 2d context"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
46
|
+
canvas.toBlob(
|
|
47
|
+
(blob) => {
|
|
48
|
+
if (!blob) {
|
|
49
|
+
reject(new Error("Canvas toBlob returned null"));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
resolve(blob);
|
|
53
|
+
},
|
|
54
|
+
format,
|
|
55
|
+
quality
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
var webpSupported = null;
|
|
60
|
+
function supportsWebP() {
|
|
61
|
+
if (webpSupported !== null) return webpSupported;
|
|
62
|
+
try {
|
|
63
|
+
const canvas = document.createElement("canvas");
|
|
64
|
+
canvas.width = 1;
|
|
65
|
+
canvas.height = 1;
|
|
66
|
+
webpSupported = canvas.toDataURL("image/webp").startsWith("data:image/webp");
|
|
67
|
+
} catch {
|
|
68
|
+
webpSupported = false;
|
|
69
|
+
}
|
|
70
|
+
return webpSupported;
|
|
71
|
+
}
|
|
72
|
+
function getOutputFormat(_originalType) {
|
|
73
|
+
if (supportsWebP()) {
|
|
74
|
+
return { mime: "image/webp", ext: ".webp" };
|
|
75
|
+
}
|
|
76
|
+
return { mime: "image/jpeg", ext: ".jpg" };
|
|
77
|
+
}
|
|
78
|
+
function replaceExtension(filename, newExt) {
|
|
79
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
80
|
+
if (dotIndex === -1) return filename + newExt;
|
|
81
|
+
return filename.substring(0, dotIndex) + newExt;
|
|
82
|
+
}
|
|
83
|
+
async function compressImage(file, targetSizeKB = DEFAULT_TARGET_SIZE_KB) {
|
|
84
|
+
if (!file.type.startsWith("image/")) {
|
|
85
|
+
return file;
|
|
86
|
+
}
|
|
87
|
+
if (file.type === "image/svg+xml") {
|
|
88
|
+
return file;
|
|
89
|
+
}
|
|
90
|
+
if (file.type === "image/gif") {
|
|
91
|
+
return file;
|
|
92
|
+
}
|
|
93
|
+
const targetBytes = targetSizeKB * 1024;
|
|
94
|
+
if (file.size <= targetBytes) {
|
|
95
|
+
return file;
|
|
96
|
+
}
|
|
97
|
+
const img = await loadImage(file);
|
|
98
|
+
const { width, height } = getScaledDimensions(
|
|
99
|
+
img.naturalWidth,
|
|
100
|
+
img.naturalHeight,
|
|
101
|
+
MAX_DIMENSION
|
|
102
|
+
);
|
|
103
|
+
const { mime, ext } = getOutputFormat(file.type);
|
|
104
|
+
let quality = QUALITY_START;
|
|
105
|
+
let blob = null;
|
|
106
|
+
while (quality >= QUALITY_MIN) {
|
|
107
|
+
blob = await canvasToBlob(img, width, height, mime, quality);
|
|
108
|
+
if (blob.size <= targetBytes) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
quality -= QUALITY_STEP;
|
|
112
|
+
}
|
|
113
|
+
if (!blob) {
|
|
114
|
+
blob = await canvasToBlob(img, width, height, mime, QUALITY_MIN);
|
|
115
|
+
}
|
|
116
|
+
if (blob.size >= file.size) {
|
|
117
|
+
return file;
|
|
118
|
+
}
|
|
119
|
+
const newFilename = replaceExtension(file.name, ext);
|
|
120
|
+
return new File([blob], newFilename, {
|
|
121
|
+
type: mime,
|
|
122
|
+
lastModified: Date.now()
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function Upload({ className }) {
|
|
126
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
127
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
128
|
+
/* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
|
|
129
|
+
/* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
|
|
130
|
+
] });
|
|
131
|
+
}
|
|
132
|
+
function X({ className }) {
|
|
133
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
134
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
135
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
136
|
+
] });
|
|
137
|
+
}
|
|
138
|
+
function FileText({ className }) {
|
|
139
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
140
|
+
/* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
141
|
+
/* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
|
|
142
|
+
/* @__PURE__ */ jsx("line", { x1: "16", x2: "8", y1: "13", y2: "13" }),
|
|
143
|
+
/* @__PURE__ */ jsx("line", { x1: "16", x2: "8", y1: "17", y2: "17" }),
|
|
144
|
+
/* @__PURE__ */ jsx("line", { x1: "10", x2: "8", y1: "9", y2: "9" })
|
|
145
|
+
] });
|
|
146
|
+
}
|
|
147
|
+
function ImageIcon({ className }) {
|
|
148
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
149
|
+
/* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
150
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" }),
|
|
151
|
+
/* @__PURE__ */ jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
154
|
+
function Music({ className }) {
|
|
155
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
156
|
+
/* @__PURE__ */ jsx("path", { d: "M9 18V5l12-2v13" }),
|
|
157
|
+
/* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
|
|
158
|
+
/* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3" })
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
function Loader2({ className }) {
|
|
162
|
+
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
163
|
+
}
|
|
164
|
+
function FileIcon({ className }) {
|
|
165
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
166
|
+
/* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
167
|
+
/* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
|
|
168
|
+
] });
|
|
169
|
+
}
|
|
170
|
+
function RotateCw({ className }) {
|
|
171
|
+
return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
172
|
+
/* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
|
|
173
|
+
/* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" })
|
|
174
|
+
] });
|
|
175
|
+
}
|
|
176
|
+
function cn(...classes) {
|
|
177
|
+
return classes.filter(Boolean).join(" ");
|
|
178
|
+
}
|
|
179
|
+
var SUPPORTED_TYPES = {
|
|
180
|
+
document: {
|
|
181
|
+
mimeTypes: [
|
|
182
|
+
"application/pdf",
|
|
183
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
184
|
+
"application/msword",
|
|
185
|
+
"text/plain",
|
|
186
|
+
"text/markdown",
|
|
187
|
+
"text/csv",
|
|
188
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
189
|
+
"application/vnd.ms-excel"
|
|
190
|
+
],
|
|
191
|
+
extensions: [
|
|
192
|
+
".pdf",
|
|
193
|
+
".docx",
|
|
194
|
+
".doc",
|
|
195
|
+
".txt",
|
|
196
|
+
".md",
|
|
197
|
+
".csv",
|
|
198
|
+
".xlsx",
|
|
199
|
+
".xls"
|
|
200
|
+
],
|
|
201
|
+
maxSizeMB: 25,
|
|
202
|
+
label: "Documents"
|
|
203
|
+
},
|
|
204
|
+
image: {
|
|
205
|
+
mimeTypes: [
|
|
206
|
+
"image/png",
|
|
207
|
+
"image/jpeg",
|
|
208
|
+
"image/gif",
|
|
209
|
+
"image/webp",
|
|
210
|
+
"image/svg+xml"
|
|
211
|
+
],
|
|
212
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"],
|
|
213
|
+
maxSizeMB: 10,
|
|
214
|
+
label: "Images"
|
|
215
|
+
},
|
|
216
|
+
audio: {
|
|
217
|
+
mimeTypes: [
|
|
218
|
+
"audio/mpeg",
|
|
219
|
+
"audio/wav",
|
|
220
|
+
"audio/mp4",
|
|
221
|
+
"audio/ogg",
|
|
222
|
+
"audio/webm"
|
|
223
|
+
],
|
|
224
|
+
extensions: [".mp3", ".wav", ".m4a", ".ogg", ".webm"],
|
|
225
|
+
maxSizeMB: 50,
|
|
226
|
+
label: "Audio"
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var MAX_FILES_PER_DROP = 10;
|
|
230
|
+
var MAX_TOTAL_SIZE_MB = 100;
|
|
231
|
+
function categorizeFile(mimeType) {
|
|
232
|
+
for (const [category, config] of Object.entries(SUPPORTED_TYPES)) {
|
|
233
|
+
if (config.mimeTypes.includes(mimeType)) {
|
|
234
|
+
return category;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
function validateFile(file) {
|
|
240
|
+
const category = categorizeFile(file.type);
|
|
241
|
+
if (!category) {
|
|
242
|
+
return {
|
|
243
|
+
valid: false,
|
|
244
|
+
error: `Unsupported file type: ${file.type || file.name.split(".").pop()}`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const config = SUPPORTED_TYPES[category];
|
|
248
|
+
const maxSizeBytes = config.maxSizeMB * 1024 * 1024;
|
|
249
|
+
if (file.size > maxSizeBytes) {
|
|
250
|
+
return {
|
|
251
|
+
valid: false,
|
|
252
|
+
error: `${file.name} exceeds ${config.maxSizeMB}MB limit for ${config.label.toLowerCase()}`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return { valid: true, category };
|
|
256
|
+
}
|
|
257
|
+
function formatFileSize(bytes) {
|
|
258
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
259
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
260
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
261
|
+
}
|
|
262
|
+
function getCategoryIcon(category) {
|
|
263
|
+
switch (category) {
|
|
264
|
+
case "document":
|
|
265
|
+
return FileText;
|
|
266
|
+
case "image":
|
|
267
|
+
return ImageIcon;
|
|
268
|
+
case "audio":
|
|
269
|
+
return Music;
|
|
270
|
+
default:
|
|
271
|
+
return FileIcon;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
var FileDropZone = forwardRef(
|
|
275
|
+
function FileDropZone2({
|
|
276
|
+
onFilesChange,
|
|
277
|
+
files,
|
|
278
|
+
onUpload,
|
|
279
|
+
onConfirmUpload,
|
|
280
|
+
onError,
|
|
281
|
+
maxFiles = MAX_FILES_PER_DROP,
|
|
282
|
+
maxTotalSizeMB = MAX_TOTAL_SIZE_MB,
|
|
283
|
+
disabled = false,
|
|
284
|
+
className,
|
|
285
|
+
accept,
|
|
286
|
+
compact = false,
|
|
287
|
+
children
|
|
288
|
+
}, ref) {
|
|
289
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
290
|
+
const fileInputRef = useRef(null);
|
|
291
|
+
const dropZoneRef = useRef(null);
|
|
292
|
+
const dragCounter = useRef(0);
|
|
293
|
+
useImperativeHandle(
|
|
294
|
+
ref,
|
|
295
|
+
() => ({
|
|
296
|
+
openFileDialog: () => {
|
|
297
|
+
if (!disabled) {
|
|
298
|
+
fileInputRef.current?.click();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}),
|
|
302
|
+
[disabled]
|
|
303
|
+
);
|
|
304
|
+
const currentTotalSize = files.reduce((sum, f) => sum + f.file.size, 0);
|
|
305
|
+
const uploadFile = useCallback(
|
|
306
|
+
async (file, _tempId) => {
|
|
307
|
+
const result = await onUpload(file, {
|
|
308
|
+
filename: file.name,
|
|
309
|
+
mimeType: file.type,
|
|
310
|
+
size: file.size
|
|
311
|
+
});
|
|
312
|
+
if (result.uploadUrl) {
|
|
313
|
+
const uploadResponse = await fetch(result.uploadUrl, {
|
|
314
|
+
method: "PUT",
|
|
315
|
+
body: file,
|
|
316
|
+
headers: { "Content-Type": file.type }
|
|
317
|
+
});
|
|
318
|
+
if (!uploadResponse.ok) {
|
|
319
|
+
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (onConfirmUpload) {
|
|
323
|
+
await onConfirmUpload(result.uuid);
|
|
324
|
+
}
|
|
325
|
+
return result.uuid;
|
|
326
|
+
},
|
|
327
|
+
[onUpload, onConfirmUpload]
|
|
328
|
+
);
|
|
329
|
+
const handleFiles = useCallback(
|
|
330
|
+
async (selectedFiles) => {
|
|
331
|
+
if (disabled) return;
|
|
332
|
+
if (files.length + selectedFiles.length > maxFiles) {
|
|
333
|
+
onError?.(`Maximum ${maxFiles} files allowed`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const validFiles = [];
|
|
337
|
+
let newTotalSize = currentTotalSize;
|
|
338
|
+
for (const file of selectedFiles) {
|
|
339
|
+
const validation = validateFile(file);
|
|
340
|
+
if (!validation.valid) {
|
|
341
|
+
onError?.(validation.error);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
newTotalSize += file.size;
|
|
345
|
+
if (newTotalSize > maxTotalSizeMB * 1024 * 1024) {
|
|
346
|
+
onError?.(`Total upload size exceeds ${maxTotalSizeMB}MB`);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
validFiles.push({ file, category: validation.category });
|
|
350
|
+
}
|
|
351
|
+
if (validFiles.length === 0) return;
|
|
352
|
+
const processedFiles = [];
|
|
353
|
+
for (const entry of validFiles) {
|
|
354
|
+
if (entry.category === "image") {
|
|
355
|
+
try {
|
|
356
|
+
const compressed = await compressImage(entry.file);
|
|
357
|
+
processedFiles.push({ file: compressed, category: entry.category });
|
|
358
|
+
} catch (err) {
|
|
359
|
+
console.warn("Image compression failed, using original:", err);
|
|
360
|
+
processedFiles.push(entry);
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
processedFiles.push(entry);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const newAttachments = processedFiles.map(
|
|
367
|
+
({ file, category }) => {
|
|
368
|
+
const tempId = `temp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
369
|
+
return {
|
|
370
|
+
uuid: tempId,
|
|
371
|
+
file,
|
|
372
|
+
status: "pending",
|
|
373
|
+
progress: 0,
|
|
374
|
+
category,
|
|
375
|
+
previewUrl: category === "image" ? URL.createObjectURL(file) : void 0
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
let currentFiles = [...files, ...newAttachments];
|
|
380
|
+
onFilesChange(currentFiles);
|
|
381
|
+
for (const attachment of newAttachments) {
|
|
382
|
+
try {
|
|
383
|
+
currentFiles = currentFiles.map(
|
|
384
|
+
(f) => f.uuid === attachment.uuid ? { ...f, status: "uploading", progress: 10 } : f
|
|
385
|
+
);
|
|
386
|
+
onFilesChange(currentFiles);
|
|
387
|
+
const realUuid = await uploadFile(attachment.file, attachment.uuid);
|
|
388
|
+
currentFiles = currentFiles.map(
|
|
389
|
+
(f) => f.uuid === attachment.uuid ? {
|
|
390
|
+
...f,
|
|
391
|
+
uuid: realUuid,
|
|
392
|
+
status: "ready",
|
|
393
|
+
progress: 100,
|
|
394
|
+
hasThumbnail: f.category === "image"
|
|
395
|
+
} : f
|
|
396
|
+
);
|
|
397
|
+
onFilesChange(currentFiles);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error("Upload error:", error);
|
|
400
|
+
currentFiles = currentFiles.map(
|
|
401
|
+
(f) => f.uuid === attachment.uuid ? {
|
|
402
|
+
...f,
|
|
403
|
+
status: "error",
|
|
404
|
+
error: error instanceof Error ? error.message : "Upload failed"
|
|
405
|
+
} : f
|
|
406
|
+
);
|
|
407
|
+
onFilesChange(currentFiles);
|
|
408
|
+
onError?.(`Failed to upload ${attachment.file.name}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
[
|
|
413
|
+
disabled,
|
|
414
|
+
files,
|
|
415
|
+
maxFiles,
|
|
416
|
+
currentTotalSize,
|
|
417
|
+
maxTotalSizeMB,
|
|
418
|
+
onFilesChange,
|
|
419
|
+
uploadFile,
|
|
420
|
+
onError
|
|
421
|
+
]
|
|
422
|
+
);
|
|
423
|
+
const handleRemove = useCallback(
|
|
424
|
+
(uuid) => {
|
|
425
|
+
const file = files.find((f) => f.uuid === uuid);
|
|
426
|
+
if (file?.previewUrl) {
|
|
427
|
+
URL.revokeObjectURL(file.previewUrl);
|
|
428
|
+
}
|
|
429
|
+
onFilesChange(files.filter((f) => f.uuid !== uuid));
|
|
430
|
+
},
|
|
431
|
+
[files, onFilesChange]
|
|
432
|
+
);
|
|
433
|
+
const handleRetry = useCallback(
|
|
434
|
+
async (uuid) => {
|
|
435
|
+
const attachment = files.find((f) => f.uuid === uuid);
|
|
436
|
+
if (!attachment || attachment.status !== "error") return;
|
|
437
|
+
let currentFiles = files.map(
|
|
438
|
+
(f) => f.uuid === uuid ? { ...f, status: "pending", progress: 0, error: void 0 } : f
|
|
439
|
+
);
|
|
440
|
+
onFilesChange(currentFiles);
|
|
441
|
+
try {
|
|
442
|
+
currentFiles = currentFiles.map(
|
|
443
|
+
(f) => f.uuid === uuid ? { ...f, status: "uploading", progress: 10 } : f
|
|
444
|
+
);
|
|
445
|
+
onFilesChange(currentFiles);
|
|
446
|
+
const realUuid = await uploadFile(attachment.file, uuid);
|
|
447
|
+
currentFiles = currentFiles.map(
|
|
448
|
+
(f) => f.uuid === uuid ? {
|
|
449
|
+
...f,
|
|
450
|
+
uuid: realUuid,
|
|
451
|
+
status: "ready",
|
|
452
|
+
progress: 100,
|
|
453
|
+
hasThumbnail: f.category === "image"
|
|
454
|
+
} : f
|
|
455
|
+
);
|
|
456
|
+
onFilesChange(currentFiles);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
console.error("Retry upload error:", error);
|
|
459
|
+
currentFiles = currentFiles.map(
|
|
460
|
+
(f) => f.uuid === uuid ? {
|
|
461
|
+
...f,
|
|
462
|
+
status: "error",
|
|
463
|
+
error: error instanceof Error ? error.message : "Upload failed"
|
|
464
|
+
} : f
|
|
465
|
+
);
|
|
466
|
+
onFilesChange(currentFiles);
|
|
467
|
+
onError?.(`Failed to upload ${attachment.file.name}`);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
[files, onFilesChange, uploadFile, onError]
|
|
471
|
+
);
|
|
472
|
+
const hasDraggedFiles = useCallback((e) => {
|
|
473
|
+
if (e.dataTransfer.types) {
|
|
474
|
+
for (const type of e.dataTransfer.types) {
|
|
475
|
+
if (type === "Files") return true;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}, []);
|
|
480
|
+
const handleDragEnter = useCallback(
|
|
481
|
+
(e) => {
|
|
482
|
+
e.preventDefault();
|
|
483
|
+
e.stopPropagation();
|
|
484
|
+
if (!hasDraggedFiles(e)) return;
|
|
485
|
+
dragCounter.current++;
|
|
486
|
+
if (!disabled && dragCounter.current === 1) {
|
|
487
|
+
setIsDragging(true);
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
[disabled, hasDraggedFiles]
|
|
491
|
+
);
|
|
492
|
+
const handleDragOver = useCallback(
|
|
493
|
+
(e) => {
|
|
494
|
+
e.preventDefault();
|
|
495
|
+
e.stopPropagation();
|
|
496
|
+
if (hasDraggedFiles(e)) {
|
|
497
|
+
e.dataTransfer.dropEffect = disabled ? "none" : "copy";
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
[disabled, hasDraggedFiles]
|
|
501
|
+
);
|
|
502
|
+
const handleDragLeave = useCallback((e) => {
|
|
503
|
+
e.preventDefault();
|
|
504
|
+
e.stopPropagation();
|
|
505
|
+
if (dragCounter.current > 0) {
|
|
506
|
+
dragCounter.current--;
|
|
507
|
+
if (dragCounter.current === 0) {
|
|
508
|
+
setIsDragging(false);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}, []);
|
|
512
|
+
const handleDrop = useCallback(
|
|
513
|
+
(e) => {
|
|
514
|
+
e.preventDefault();
|
|
515
|
+
e.stopPropagation();
|
|
516
|
+
dragCounter.current = 0;
|
|
517
|
+
setIsDragging(false);
|
|
518
|
+
if (disabled) return;
|
|
519
|
+
const droppedFiles = Array.from(e.dataTransfer.files);
|
|
520
|
+
if (droppedFiles.length > 0) {
|
|
521
|
+
handleFiles(droppedFiles);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
[handleFiles, disabled]
|
|
525
|
+
);
|
|
526
|
+
const handleFileSelect = useCallback(
|
|
527
|
+
(e) => {
|
|
528
|
+
const selectedFiles = e.target.files;
|
|
529
|
+
if (selectedFiles) {
|
|
530
|
+
handleFiles(Array.from(selectedFiles));
|
|
531
|
+
}
|
|
532
|
+
if (fileInputRef.current) {
|
|
533
|
+
fileInputRef.current.value = "";
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
[handleFiles]
|
|
537
|
+
);
|
|
538
|
+
const handleClick = useCallback(() => {
|
|
539
|
+
if (!disabled) {
|
|
540
|
+
fileInputRef.current?.click();
|
|
541
|
+
}
|
|
542
|
+
}, [disabled]);
|
|
543
|
+
const checkDataTransferHasFiles = useCallback(
|
|
544
|
+
(dataTransfer) => {
|
|
545
|
+
if (!dataTransfer?.types) return false;
|
|
546
|
+
for (let i = 0; i < dataTransfer.types.length; i++) {
|
|
547
|
+
if (dataTransfer.types[i] === "Files") return true;
|
|
548
|
+
}
|
|
549
|
+
return false;
|
|
550
|
+
},
|
|
551
|
+
[]
|
|
552
|
+
);
|
|
553
|
+
useEffect(() => {
|
|
554
|
+
if (!children || !dropZoneRef.current) return;
|
|
555
|
+
const element = dropZoneRef.current;
|
|
556
|
+
let enterCounter = 0;
|
|
557
|
+
const handleDragEnterNative = (e) => {
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
e.stopPropagation();
|
|
560
|
+
if (!checkDataTransferHasFiles(e.dataTransfer)) return;
|
|
561
|
+
enterCounter++;
|
|
562
|
+
if (enterCounter === 1 && !disabled) {
|
|
563
|
+
setIsDragging(true);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const handleDragLeaveNative = (e) => {
|
|
567
|
+
e.preventDefault();
|
|
568
|
+
e.stopPropagation();
|
|
569
|
+
if (!checkDataTransferHasFiles(e.dataTransfer)) return;
|
|
570
|
+
enterCounter--;
|
|
571
|
+
if (enterCounter <= 0) {
|
|
572
|
+
enterCounter = 0;
|
|
573
|
+
setIsDragging(false);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
const handleDragOverNative = (e) => {
|
|
577
|
+
e.preventDefault();
|
|
578
|
+
e.stopPropagation();
|
|
579
|
+
if (checkDataTransferHasFiles(e.dataTransfer) && !disabled && e.dataTransfer) {
|
|
580
|
+
e.dataTransfer.dropEffect = "copy";
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
const handleDropNative = (e) => {
|
|
584
|
+
e.preventDefault();
|
|
585
|
+
e.stopPropagation();
|
|
586
|
+
enterCounter = 0;
|
|
587
|
+
setIsDragging(false);
|
|
588
|
+
if (disabled) return;
|
|
589
|
+
const droppedFiles = e.dataTransfer?.files;
|
|
590
|
+
if (droppedFiles && droppedFiles.length > 0) {
|
|
591
|
+
handleFiles(Array.from(droppedFiles));
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
element.addEventListener("dragenter", handleDragEnterNative, true);
|
|
595
|
+
element.addEventListener("dragleave", handleDragLeaveNative, true);
|
|
596
|
+
element.addEventListener("dragover", handleDragOverNative, true);
|
|
597
|
+
element.addEventListener("drop", handleDropNative, true);
|
|
598
|
+
return () => {
|
|
599
|
+
element.removeEventListener("dragenter", handleDragEnterNative, true);
|
|
600
|
+
element.removeEventListener("dragleave", handleDragLeaveNative, true);
|
|
601
|
+
element.removeEventListener("dragover", handleDragOverNative, true);
|
|
602
|
+
element.removeEventListener("drop", handleDropNative, true);
|
|
603
|
+
};
|
|
604
|
+
}, [children, disabled, handleFiles, checkDataTransferHasFiles]);
|
|
605
|
+
useEffect(() => {
|
|
606
|
+
if (disabled) {
|
|
607
|
+
dragCounter.current = 0;
|
|
608
|
+
setIsDragging(false);
|
|
609
|
+
}
|
|
610
|
+
}, [disabled]);
|
|
611
|
+
const acceptTypes = accept || Object.values(SUPPORTED_TYPES).flatMap((t) => [...t.mimeTypes, ...t.extensions]).join(",");
|
|
612
|
+
const hasFiles = files.length > 0;
|
|
613
|
+
const isUploading = files.some((f) => f.status === "uploading");
|
|
614
|
+
if (children) {
|
|
615
|
+
return /* @__PURE__ */ jsxs(
|
|
616
|
+
"div",
|
|
617
|
+
{
|
|
618
|
+
ref: dropZoneRef,
|
|
619
|
+
"data-dropzone": "wrapper",
|
|
620
|
+
className: cn("relative w-full h-full", className),
|
|
621
|
+
style: { minHeight: "inherit" },
|
|
622
|
+
children: [
|
|
623
|
+
/* @__PURE__ */ jsx(
|
|
624
|
+
"input",
|
|
625
|
+
{
|
|
626
|
+
ref: fileInputRef,
|
|
627
|
+
type: "file",
|
|
628
|
+
multiple: true,
|
|
629
|
+
accept: acceptTypes,
|
|
630
|
+
onChange: handleFileSelect,
|
|
631
|
+
className: "hidden",
|
|
632
|
+
disabled
|
|
633
|
+
}
|
|
634
|
+
),
|
|
635
|
+
children,
|
|
636
|
+
isDragging && !disabled && /* @__PURE__ */ jsx(
|
|
637
|
+
"div",
|
|
638
|
+
{
|
|
639
|
+
className: "absolute inset-0 z-50 bg-primary/10 border-2 border-dashed border-primary rounded-xl flex flex-col items-center justify-center backdrop-blur-sm transition-opacity duration-200",
|
|
640
|
+
style: { pointerEvents: "none" },
|
|
641
|
+
children: /* @__PURE__ */ jsxs("div", { className: "bg-background/80 rounded-xl p-8 flex flex-col items-center shadow-lg", children: [
|
|
642
|
+
/* @__PURE__ */ jsx(Upload, { className: "h-12 w-12 text-primary mb-4 animate-bounce" }),
|
|
643
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-medium text-primary", children: "Drop files here" }),
|
|
644
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "Documents, images, or audio files" })
|
|
645
|
+
] })
|
|
646
|
+
}
|
|
647
|
+
)
|
|
648
|
+
]
|
|
649
|
+
}
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("w-full", className), children: [
|
|
653
|
+
/* @__PURE__ */ jsx(
|
|
654
|
+
"input",
|
|
655
|
+
{
|
|
656
|
+
ref: fileInputRef,
|
|
657
|
+
type: "file",
|
|
658
|
+
multiple: true,
|
|
659
|
+
accept: acceptTypes,
|
|
660
|
+
onChange: handleFileSelect,
|
|
661
|
+
className: "hidden",
|
|
662
|
+
disabled
|
|
663
|
+
}
|
|
664
|
+
),
|
|
665
|
+
/* @__PURE__ */ jsxs(
|
|
666
|
+
"div",
|
|
667
|
+
{
|
|
668
|
+
onDragEnter: handleDragEnter,
|
|
669
|
+
onDragOver: handleDragOver,
|
|
670
|
+
onDragLeave: handleDragLeave,
|
|
671
|
+
onDrop: handleDrop,
|
|
672
|
+
onClick: handleClick,
|
|
673
|
+
className: cn(
|
|
674
|
+
"relative rounded-lg border-2 border-dashed transition-all cursor-pointer",
|
|
675
|
+
"flex flex-col items-center justify-center",
|
|
676
|
+
compact ? "p-2 min-h-[60px]" : "p-4 min-h-[100px]",
|
|
677
|
+
isDragging && !disabled ? "border-primary bg-primary/5" : "border-muted-foreground/25",
|
|
678
|
+
disabled ? "opacity-50 cursor-not-allowed" : "hover:border-primary/50 hover:bg-muted/25"
|
|
679
|
+
),
|
|
680
|
+
children: [
|
|
681
|
+
isUploading ? /* @__PURE__ */ jsx(
|
|
682
|
+
Loader2,
|
|
683
|
+
{
|
|
684
|
+
className: cn(
|
|
685
|
+
"animate-spin text-muted-foreground",
|
|
686
|
+
compact ? "h-5 w-5 mb-1" : "h-8 w-8 mb-2"
|
|
687
|
+
)
|
|
688
|
+
}
|
|
689
|
+
) : /* @__PURE__ */ jsx(
|
|
690
|
+
Upload,
|
|
691
|
+
{
|
|
692
|
+
className: cn(
|
|
693
|
+
"text-muted-foreground",
|
|
694
|
+
compact ? "h-5 w-5 mb-1" : "h-8 w-8 mb-2"
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
),
|
|
698
|
+
/* @__PURE__ */ jsx(
|
|
699
|
+
"p",
|
|
700
|
+
{
|
|
701
|
+
className: cn(
|
|
702
|
+
"font-medium text-center text-muted-foreground",
|
|
703
|
+
compact ? "text-xs" : "text-sm"
|
|
704
|
+
),
|
|
705
|
+
children: isDragging ? "Drop files here" : isUploading ? "Uploading..." : "Click to upload or drag and drop"
|
|
706
|
+
}
|
|
707
|
+
),
|
|
708
|
+
!compact && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground/70 text-center mt-1", children: "Documents, images, or audio files" })
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
),
|
|
712
|
+
hasFiles && /* @__PURE__ */ jsx("div", { className: cn("space-y-2", compact ? "mt-2" : "mt-3"), children: files.map((attachment) => {
|
|
713
|
+
const Icon = getCategoryIcon(attachment.category);
|
|
714
|
+
const isError = attachment.status === "error";
|
|
715
|
+
const isLoading = attachment.status === "uploading" || attachment.status === "processing";
|
|
716
|
+
return /* @__PURE__ */ jsxs(
|
|
717
|
+
"div",
|
|
718
|
+
{
|
|
719
|
+
className: cn(
|
|
720
|
+
"flex items-center gap-3 p-2 rounded-lg border",
|
|
721
|
+
isError ? "border-destructive/50 bg-destructive/5" : "border-border bg-muted/25"
|
|
722
|
+
),
|
|
723
|
+
children: [
|
|
724
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex-shrink-0 w-10 h-10 rounded overflow-hidden bg-muted flex items-center justify-center", children: [
|
|
725
|
+
attachment.previewUrl ? /* @__PURE__ */ jsx(
|
|
726
|
+
"img",
|
|
727
|
+
{
|
|
728
|
+
src: attachment.previewUrl,
|
|
729
|
+
alt: attachment.file.name,
|
|
730
|
+
className: "w-full h-full object-cover"
|
|
731
|
+
}
|
|
732
|
+
) : /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-muted-foreground" }),
|
|
733
|
+
isLoading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-background/80 flex items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) })
|
|
734
|
+
] }),
|
|
735
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
736
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: attachment.file.name }),
|
|
737
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
738
|
+
formatFileSize(attachment.file.size),
|
|
739
|
+
isError && attachment.error && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-2", children: attachment.error })
|
|
740
|
+
] })
|
|
741
|
+
] }),
|
|
742
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: isLoading ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
743
|
+
attachment.progress,
|
|
744
|
+
"%"
|
|
745
|
+
] }) : attachment.status === "ready" ? /* @__PURE__ */ jsx("span", { className: "text-xs text-green-600", children: "Ready" }) : null }),
|
|
746
|
+
isError && /* @__PURE__ */ jsx(
|
|
747
|
+
"button",
|
|
748
|
+
{
|
|
749
|
+
type: "button",
|
|
750
|
+
className: "flex-shrink-0 h-8 w-8 inline-flex items-center justify-center rounded-md text-primary hover:bg-muted transition-colors",
|
|
751
|
+
onClick: (e) => {
|
|
752
|
+
e.stopPropagation();
|
|
753
|
+
handleRetry(attachment.uuid);
|
|
754
|
+
},
|
|
755
|
+
title: "Retry upload",
|
|
756
|
+
children: /* @__PURE__ */ jsx(RotateCw, { className: "h-4 w-4" })
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
/* @__PURE__ */ jsx(
|
|
760
|
+
"button",
|
|
761
|
+
{
|
|
762
|
+
type: "button",
|
|
763
|
+
className: "flex-shrink-0 h-8 w-8 inline-flex items-center justify-center rounded-md hover:bg-muted transition-colors disabled:opacity-50",
|
|
764
|
+
onClick: (e) => {
|
|
765
|
+
e.stopPropagation();
|
|
766
|
+
handleRemove(attachment.uuid);
|
|
767
|
+
},
|
|
768
|
+
disabled: isLoading,
|
|
769
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
770
|
+
}
|
|
771
|
+
)
|
|
772
|
+
]
|
|
773
|
+
},
|
|
774
|
+
attachment.uuid
|
|
775
|
+
);
|
|
776
|
+
}) })
|
|
777
|
+
] });
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
function useFileAttachments(options) {
|
|
781
|
+
const { onUpload, onConfirmUpload, onSubmitUpload, onError, maxFiles = 10, maxTotalSizeMB = 100 } = options;
|
|
782
|
+
const [files, setFiles] = useState([]);
|
|
783
|
+
const filesRef = useRef(files);
|
|
784
|
+
filesRef.current = files;
|
|
785
|
+
const onFilesChange = useCallback((newFiles) => {
|
|
786
|
+
filesRef.current = newFiles;
|
|
787
|
+
setFiles(newFiles);
|
|
788
|
+
}, []);
|
|
789
|
+
const removeFile = useCallback((uuid) => {
|
|
790
|
+
setFiles((prev) => {
|
|
791
|
+
const file = prev.find((f) => f.uuid === uuid);
|
|
792
|
+
if (file?.previewUrl) {
|
|
793
|
+
URL.revokeObjectURL(file.previewUrl);
|
|
794
|
+
}
|
|
795
|
+
return prev.filter((f) => f.uuid !== uuid);
|
|
796
|
+
});
|
|
797
|
+
}, []);
|
|
798
|
+
const getReadyUuids = useCallback(() => {
|
|
799
|
+
return filesRef.current.filter((f) => f.status === "ready").map((f) => f.uuid);
|
|
800
|
+
}, []);
|
|
801
|
+
const getReadyUrls = useCallback(() => {
|
|
802
|
+
return filesRef.current.filter((f) => f.status === "ready").map((f) => f.uuid);
|
|
803
|
+
}, []);
|
|
804
|
+
const prepareForSubmit = useCallback(async () => {
|
|
805
|
+
const readyFiles = filesRef.current.filter((f) => f.status === "ready");
|
|
806
|
+
console.log("[useFileAttachments] prepareForSubmit - readyFiles:", readyFiles.length, "hasSubmitUpload:", !!onSubmitUpload);
|
|
807
|
+
if (readyFiles.length === 0) return [];
|
|
808
|
+
if (!onSubmitUpload) {
|
|
809
|
+
return readyFiles.map((f) => f.uuid);
|
|
810
|
+
}
|
|
811
|
+
const aiUuids = [];
|
|
812
|
+
for (const attachment of readyFiles) {
|
|
813
|
+
try {
|
|
814
|
+
let fileToSend = attachment.file;
|
|
815
|
+
if (attachment.category === "image") {
|
|
816
|
+
try {
|
|
817
|
+
fileToSend = await compressImage(attachment.file);
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
console.log("[useFileAttachments] Uploading to AI service:", attachment.file.name, "localUuid:", attachment.uuid.substring(0, 40));
|
|
822
|
+
const aiUuid = await onSubmitUpload(
|
|
823
|
+
fileToSend,
|
|
824
|
+
attachment.uuid,
|
|
825
|
+
{ filename: fileToSend.name, mimeType: fileToSend.type, size: fileToSend.size }
|
|
826
|
+
);
|
|
827
|
+
console.log("[useFileAttachments] AI service returned UUID:", aiUuid);
|
|
828
|
+
aiUuids.push(aiUuid);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
const msg = err instanceof Error ? err.message : "Submit upload failed";
|
|
831
|
+
console.error("[useFileAttachments] Submit upload failed for:", attachment.file.name, msg);
|
|
832
|
+
onError?.(msg);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
console.log("[useFileAttachments] prepareForSubmit result:", aiUuids);
|
|
836
|
+
return aiUuids;
|
|
837
|
+
}, [onSubmitUpload, onError]);
|
|
838
|
+
const clear = useCallback(() => {
|
|
839
|
+
for (const file of filesRef.current) {
|
|
840
|
+
if (file.previewUrl) {
|
|
841
|
+
URL.revokeObjectURL(file.previewUrl);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
setFiles([]);
|
|
845
|
+
}, []);
|
|
846
|
+
const isUploading = files.some(
|
|
847
|
+
(f) => f.status === "uploading" || f.status === "pending" || f.status === "processing"
|
|
848
|
+
);
|
|
849
|
+
const hasReadyFiles = files.some((f) => f.status === "ready");
|
|
850
|
+
const dropZoneProps = {
|
|
851
|
+
files,
|
|
852
|
+
onFilesChange,
|
|
853
|
+
onUpload,
|
|
854
|
+
onConfirmUpload,
|
|
855
|
+
onError,
|
|
856
|
+
maxFiles,
|
|
857
|
+
maxTotalSizeMB
|
|
858
|
+
};
|
|
859
|
+
return {
|
|
860
|
+
files,
|
|
861
|
+
dropZoneProps,
|
|
862
|
+
removeFile,
|
|
863
|
+
getReadyUuids,
|
|
864
|
+
getReadyUrls,
|
|
865
|
+
prepareForSubmit,
|
|
866
|
+
clear,
|
|
867
|
+
isUploading,
|
|
868
|
+
hasReadyFiles,
|
|
869
|
+
fileCount: files.length
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export { FileDropZone, compressImage, useFileAttachments };
|
|
874
|
+
//# sourceMappingURL=index.js.map
|
|
875
|
+
//# sourceMappingURL=index.js.map
|