@cadraos/sdk 0.1.6 → 0.1.7

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.
@@ -3221,8 +3221,9 @@ function useAgentChat(options) {
3221
3221
  [adapter, handleExecutionLog, updateStreamingMessage, updateToolCall, handleClarification, handleComplete, handleError]
3222
3222
  );
3223
3223
  const sendMessage = react.useCallback(
3224
- async (content) => {
3224
+ async (content, options2) => {
3225
3225
  if (!content.trim()) return;
3226
+ const attachmentUuids = options2?.attachmentUuids;
3226
3227
  lastMessageContentRef.current = content;
3227
3228
  setIsLoading(true);
3228
3229
  setError(null);
@@ -3253,7 +3254,8 @@ function useAgentChat(options) {
3253
3254
  agentId,
3254
3255
  teamId,
3255
3256
  sessionId: currentSessionId ?? void 0,
3256
- content: content.trim()
3257
+ content: content.trim(),
3258
+ attachmentUuids
3257
3259
  });
3258
3260
  console.log("[useAgentChat] Execution started:", result);
3259
3261
  if (!currentSessionId && result.sessionId) {
@@ -3462,6 +3464,222 @@ function useAgentChat(options) {
3462
3464
  loadSession
3463
3465
  };
3464
3466
  }
3467
+
3468
+ // src/chat/utils/compress-image.ts
3469
+ var DEFAULT_TARGET_SIZE_KB = 150;
3470
+ var MAX_DIMENSION = 1920;
3471
+ var QUALITY_START = 0.85;
3472
+ var QUALITY_STEP = 0.05;
3473
+ var QUALITY_MIN = 0.1;
3474
+ function loadImage(file) {
3475
+ return new Promise((resolve, reject) => {
3476
+ const img = new Image();
3477
+ const url = URL.createObjectURL(file);
3478
+ img.onload = () => {
3479
+ URL.revokeObjectURL(url);
3480
+ resolve(img);
3481
+ };
3482
+ img.onerror = () => {
3483
+ URL.revokeObjectURL(url);
3484
+ reject(new Error(`Failed to load image: ${file.name}`));
3485
+ };
3486
+ img.src = url;
3487
+ });
3488
+ }
3489
+ function getScaledDimensions(width, height, maxDim) {
3490
+ if (width <= maxDim && height <= maxDim) {
3491
+ return { width, height };
3492
+ }
3493
+ const ratio = Math.min(maxDim / width, maxDim / height);
3494
+ return {
3495
+ width: Math.round(width * ratio),
3496
+ height: Math.round(height * ratio)
3497
+ };
3498
+ }
3499
+ function canvasToBlob(img, width, height, format, quality) {
3500
+ return new Promise((resolve, reject) => {
3501
+ const canvas = document.createElement("canvas");
3502
+ canvas.width = width;
3503
+ canvas.height = height;
3504
+ const ctx = canvas.getContext("2d");
3505
+ if (!ctx) {
3506
+ reject(new Error("Failed to get canvas 2d context"));
3507
+ return;
3508
+ }
3509
+ ctx.drawImage(img, 0, 0, width, height);
3510
+ canvas.toBlob(
3511
+ (blob) => {
3512
+ if (!blob) {
3513
+ reject(new Error("Canvas toBlob returned null"));
3514
+ return;
3515
+ }
3516
+ resolve(blob);
3517
+ },
3518
+ format,
3519
+ quality
3520
+ );
3521
+ });
3522
+ }
3523
+ var webpSupported = null;
3524
+ function supportsWebP() {
3525
+ if (webpSupported !== null) return webpSupported;
3526
+ try {
3527
+ const canvas = document.createElement("canvas");
3528
+ canvas.width = 1;
3529
+ canvas.height = 1;
3530
+ webpSupported = canvas.toDataURL("image/webp").startsWith("data:image/webp");
3531
+ } catch {
3532
+ webpSupported = false;
3533
+ }
3534
+ return webpSupported;
3535
+ }
3536
+ function getOutputFormat(_originalType) {
3537
+ if (supportsWebP()) {
3538
+ return { mime: "image/webp", ext: ".webp" };
3539
+ }
3540
+ return { mime: "image/jpeg", ext: ".jpg" };
3541
+ }
3542
+ function replaceExtension(filename, newExt) {
3543
+ const dotIndex = filename.lastIndexOf(".");
3544
+ if (dotIndex === -1) return filename + newExt;
3545
+ return filename.substring(0, dotIndex) + newExt;
3546
+ }
3547
+ async function compressImage(file, targetSizeKB = DEFAULT_TARGET_SIZE_KB) {
3548
+ if (!file.type.startsWith("image/")) {
3549
+ return file;
3550
+ }
3551
+ if (file.type === "image/svg+xml") {
3552
+ return file;
3553
+ }
3554
+ if (file.type === "image/gif") {
3555
+ return file;
3556
+ }
3557
+ const targetBytes = targetSizeKB * 1024;
3558
+ if (file.size <= targetBytes) {
3559
+ return file;
3560
+ }
3561
+ const img = await loadImage(file);
3562
+ const { width, height } = getScaledDimensions(
3563
+ img.naturalWidth,
3564
+ img.naturalHeight,
3565
+ MAX_DIMENSION
3566
+ );
3567
+ const { mime, ext } = getOutputFormat(file.type);
3568
+ let quality = QUALITY_START;
3569
+ let blob = null;
3570
+ while (quality >= QUALITY_MIN) {
3571
+ blob = await canvasToBlob(img, width, height, mime, quality);
3572
+ if (blob.size <= targetBytes) {
3573
+ break;
3574
+ }
3575
+ quality -= QUALITY_STEP;
3576
+ }
3577
+ if (!blob) {
3578
+ blob = await canvasToBlob(img, width, height, mime, QUALITY_MIN);
3579
+ }
3580
+ if (blob.size >= file.size) {
3581
+ return file;
3582
+ }
3583
+ const newFilename = replaceExtension(file.name, ext);
3584
+ return new File([blob], newFilename, {
3585
+ type: mime,
3586
+ lastModified: Date.now()
3587
+ });
3588
+ }
3589
+
3590
+ // src/chat/hooks/useFileAttachments.ts
3591
+ function useFileAttachments(options) {
3592
+ const { onUpload, onConfirmUpload, onSubmitUpload, onError, maxFiles = 10, maxTotalSizeMB = 100 } = options;
3593
+ const [files, setFiles] = react.useState([]);
3594
+ const filesRef = react.useRef(files);
3595
+ filesRef.current = files;
3596
+ const onFilesChange = react.useCallback((newFiles) => {
3597
+ filesRef.current = newFiles;
3598
+ setFiles(newFiles);
3599
+ }, []);
3600
+ const removeFile = react.useCallback((uuid) => {
3601
+ setFiles((prev) => {
3602
+ const file = prev.find((f) => f.uuid === uuid);
3603
+ if (file?.previewUrl) {
3604
+ URL.revokeObjectURL(file.previewUrl);
3605
+ }
3606
+ return prev.filter((f) => f.uuid !== uuid);
3607
+ });
3608
+ }, []);
3609
+ const getReadyUuids = react.useCallback(() => {
3610
+ return filesRef.current.filter((f) => f.status === "ready").map((f) => f.uuid);
3611
+ }, []);
3612
+ const getReadyUrls = react.useCallback(() => {
3613
+ return filesRef.current.filter((f) => f.status === "ready").map((f) => f.uuid);
3614
+ }, []);
3615
+ const prepareForSubmit = react.useCallback(async () => {
3616
+ const readyFiles = filesRef.current.filter((f) => f.status === "ready");
3617
+ console.log("[useFileAttachments] prepareForSubmit - readyFiles:", readyFiles.length, "hasSubmitUpload:", !!onSubmitUpload);
3618
+ if (readyFiles.length === 0) return [];
3619
+ if (!onSubmitUpload) {
3620
+ return readyFiles.map((f) => f.uuid);
3621
+ }
3622
+ const aiUuids = [];
3623
+ for (const attachment of readyFiles) {
3624
+ try {
3625
+ let fileToSend = attachment.file;
3626
+ if (attachment.category === "image") {
3627
+ try {
3628
+ fileToSend = await compressImage(attachment.file);
3629
+ } catch {
3630
+ }
3631
+ }
3632
+ console.log("[useFileAttachments] Uploading to AI service:", attachment.file.name, "localUuid:", attachment.uuid.substring(0, 40));
3633
+ const aiUuid = await onSubmitUpload(
3634
+ fileToSend,
3635
+ attachment.uuid,
3636
+ { filename: fileToSend.name, mimeType: fileToSend.type, size: fileToSend.size }
3637
+ );
3638
+ console.log("[useFileAttachments] AI service returned UUID:", aiUuid);
3639
+ aiUuids.push(aiUuid);
3640
+ } catch (err) {
3641
+ const msg = err instanceof Error ? err.message : "Submit upload failed";
3642
+ console.error("[useFileAttachments] Submit upload failed for:", attachment.file.name, msg);
3643
+ onError?.(msg);
3644
+ }
3645
+ }
3646
+ console.log("[useFileAttachments] prepareForSubmit result:", aiUuids);
3647
+ return aiUuids;
3648
+ }, [onSubmitUpload, onError]);
3649
+ const clear = react.useCallback(() => {
3650
+ for (const file of filesRef.current) {
3651
+ if (file.previewUrl) {
3652
+ URL.revokeObjectURL(file.previewUrl);
3653
+ }
3654
+ }
3655
+ setFiles([]);
3656
+ }, []);
3657
+ const isUploading = files.some(
3658
+ (f) => f.status === "uploading" || f.status === "pending" || f.status === "processing"
3659
+ );
3660
+ const hasReadyFiles = files.some((f) => f.status === "ready");
3661
+ const dropZoneProps = {
3662
+ files,
3663
+ onFilesChange,
3664
+ onUpload,
3665
+ onConfirmUpload,
3666
+ onError,
3667
+ maxFiles,
3668
+ maxTotalSizeMB
3669
+ };
3670
+ return {
3671
+ files,
3672
+ dropZoneProps,
3673
+ removeFile,
3674
+ getReadyUuids,
3675
+ getReadyUrls,
3676
+ prepareForSubmit,
3677
+ clear,
3678
+ isUploading,
3679
+ hasReadyFiles,
3680
+ fileCount: files.length
3681
+ };
3682
+ }
3465
3683
  function cn(...classes) {
3466
3684
  return classes.filter(Boolean).join(" ");
3467
3685
  }
@@ -4111,6 +4329,51 @@ function ChatContainer({
4111
4329
  function cn3(...classes) {
4112
4330
  return classes.filter(Boolean).join(" ");
4113
4331
  }
4332
+ function formatFileSize(bytes) {
4333
+ if (bytes < 1024) return `${bytes} B`;
4334
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
4335
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4336
+ }
4337
+ function AttachmentChip({ attachment, onRemove }) {
4338
+ const isLoading = attachment.status === "uploading" || attachment.status === "pending" || attachment.status === "processing";
4339
+ const isError = attachment.status === "error" || attachment.status === "failed";
4340
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4341
+ "div",
4342
+ {
4343
+ className: cn3(
4344
+ "inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs max-w-[180px]",
4345
+ isError ? "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-600"
4346
+ ),
4347
+ children: [
4348
+ attachment.previewUrl && /* @__PURE__ */ jsxRuntime.jsx(
4349
+ "img",
4350
+ {
4351
+ src: attachment.previewUrl,
4352
+ alt: "",
4353
+ className: "w-5 h-5 rounded object-cover flex-shrink-0"
4354
+ }
4355
+ ),
4356
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: attachment.file.name }),
4357
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { className: "h-3 w-3 flex-shrink-0" }) : isError ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-red-500", title: attachment.error, children: "!" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-gray-400 dark:text-gray-500", children: formatFileSize(attachment.actualSize ?? attachment.file.size) }),
4358
+ /* @__PURE__ */ jsxRuntime.jsx(
4359
+ "button",
4360
+ {
4361
+ type: "button",
4362
+ onClick: (e) => {
4363
+ e.preventDefault();
4364
+ e.stopPropagation();
4365
+ onRemove(attachment.uuid);
4366
+ },
4367
+ className: "flex-shrink-0 p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",
4368
+ "aria-label": `Remove ${attachment.file.name}`,
4369
+ disabled: isLoading,
4370
+ children: /* @__PURE__ */ jsxRuntime.jsx(XIcon, { className: "h-3 w-3" })
4371
+ }
4372
+ )
4373
+ ]
4374
+ }
4375
+ );
4376
+ }
4114
4377
  function ChatInput({
4115
4378
  onSend,
4116
4379
  onStop,
@@ -4118,10 +4381,15 @@ function ChatInput({
4118
4381
  disabled = false,
4119
4382
  isLoading = false,
4120
4383
  enableAttachments = false,
4384
+ attachments = [],
4385
+ onRemoveAttachment,
4386
+ dropZoneRef,
4387
+ isUploadingAttachments = false,
4121
4388
  className
4122
4389
  }) {
4123
4390
  const [content, setContent] = react.useState("");
4124
4391
  const textareaRef = react.useRef(null);
4392
+ const hasAttachments = enableAttachments && attachments.length > 0;
4125
4393
  react.useEffect(() => {
4126
4394
  const textarea = textareaRef.current;
4127
4395
  if (textarea) {
@@ -4131,10 +4399,10 @@ function ChatInput({
4131
4399
  }, [content]);
4132
4400
  const handleSubmit = react.useCallback(async () => {
4133
4401
  const trimmedContent = content.trim();
4134
- if (!trimmedContent || disabled || isLoading) return;
4402
+ if (!trimmedContent && !hasAttachments || disabled || isLoading || isUploadingAttachments) return;
4135
4403
  setContent("");
4136
4404
  await onSend(trimmedContent);
4137
- }, [content, disabled, isLoading, onSend]);
4405
+ }, [content, hasAttachments, disabled, isLoading, isUploadingAttachments, onSend]);
4138
4406
  const handleKeyDown = react.useCallback(
4139
4407
  (e) => {
4140
4408
  if (e.key === "Enter" && !e.shiftKey) {
@@ -4147,7 +4415,10 @@ function ChatInput({
4147
4415
  const handleStopClick = react.useCallback(() => {
4148
4416
  onStop?.();
4149
4417
  }, [onStop]);
4150
- const canSend = content.trim().length > 0 && !disabled && !isLoading;
4418
+ const handleAttachClick = react.useCallback(() => {
4419
+ dropZoneRef?.current?.openFileDialog();
4420
+ }, [dropZoneRef]);
4421
+ const canSend = (content.trim().length > 0 || hasAttachments) && !disabled && !isLoading && !isUploadingAttachments;
4151
4422
  const showStopButton = isLoading && onStop;
4152
4423
  return /* @__PURE__ */ jsxRuntime.jsxs(
4153
4424
  "div",
@@ -4157,7 +4428,34 @@ function ChatInput({
4157
4428
  className
4158
4429
  ),
4159
4430
  children: [
4431
+ hasAttachments && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 mb-2", children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
4432
+ AttachmentChip,
4433
+ {
4434
+ attachment,
4435
+ onRemove: onRemoveAttachment ?? (() => {
4436
+ })
4437
+ },
4438
+ attachment.uuid
4439
+ )) }),
4160
4440
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
4441
+ enableAttachments && /* @__PURE__ */ jsxRuntime.jsx(
4442
+ "button",
4443
+ {
4444
+ type: "button",
4445
+ onClick: handleAttachClick,
4446
+ disabled: disabled || isLoading,
4447
+ className: cn3(
4448
+ "flex-shrink-0 p-3 rounded-lg transition-colors",
4449
+ "text-gray-500 dark:text-gray-400",
4450
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
4451
+ "disabled:opacity-50 disabled:cursor-not-allowed",
4452
+ "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
4453
+ ),
4454
+ "aria-label": "Attach files",
4455
+ title: "Attach files",
4456
+ children: /* @__PURE__ */ jsxRuntime.jsx(PaperclipIcon, { className: "h-5 w-5" })
4457
+ }
4458
+ ),
4161
4459
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsxRuntime.jsx(
4162
4460
  "textarea",
4163
4461
  {
@@ -4165,7 +4463,7 @@ function ChatInput({
4165
4463
  value: content,
4166
4464
  onChange: (e) => setContent(e.target.value),
4167
4465
  onKeyDown: handleKeyDown,
4168
- placeholder,
4466
+ placeholder: hasAttachments && !content ? `${attachments.length} file${attachments.length > 1 ? "s" : ""} attached. Add a message or press Enter to send.` : placeholder,
4169
4467
  disabled,
4170
4468
  rows: 1,
4171
4469
  className: cn3(
@@ -4215,9 +4513,664 @@ function ChatInput({
4215
4513
  }
4216
4514
  );
4217
4515
  }
4516
+ function Upload({ className }) {
4517
+ return /* @__PURE__ */ jsxRuntime.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: [
4518
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
4519
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "17 8 12 3 7 8" }),
4520
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
4521
+ ] });
4522
+ }
4523
+ function X({ className }) {
4524
+ return /* @__PURE__ */ jsxRuntime.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: [
4525
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
4526
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
4527
+ ] });
4528
+ }
4529
+ function FileText({ className }) {
4530
+ return /* @__PURE__ */ jsxRuntime.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: [
4531
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4532
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
4533
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", x2: "8", y1: "13", y2: "13" }),
4534
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", x2: "8", y1: "17", y2: "17" }),
4535
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", x2: "8", y1: "9", y2: "9" })
4536
+ ] });
4537
+ }
4538
+ function ImageIcon2({ className }) {
4539
+ return /* @__PURE__ */ jsxRuntime.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: [
4540
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
4541
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "9", r: "2" }),
4542
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
4543
+ ] });
4544
+ }
4545
+ function Music({ className }) {
4546
+ return /* @__PURE__ */ jsxRuntime.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: [
4547
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 18V5l12-2v13" }),
4548
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "18", r: "3" }),
4549
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "18", cy: "16", r: "3" })
4550
+ ] });
4551
+ }
4552
+ function Loader2({ className }) {
4553
+ return /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
4554
+ }
4555
+ function FileIcon2({ className }) {
4556
+ return /* @__PURE__ */ jsxRuntime.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: [
4557
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4558
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4559
+ ] });
4560
+ }
4561
+ function RotateCw({ className }) {
4562
+ return /* @__PURE__ */ jsxRuntime.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: [
4563
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
4564
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 3v5h-5" })
4565
+ ] });
4566
+ }
4218
4567
  function cn4(...classes) {
4219
4568
  return classes.filter(Boolean).join(" ");
4220
4569
  }
4570
+ var SUPPORTED_TYPES = {
4571
+ document: {
4572
+ mimeTypes: [
4573
+ "application/pdf",
4574
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
4575
+ "application/msword",
4576
+ "text/plain",
4577
+ "text/markdown",
4578
+ "text/csv",
4579
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4580
+ "application/vnd.ms-excel"
4581
+ ],
4582
+ extensions: [
4583
+ ".pdf",
4584
+ ".docx",
4585
+ ".doc",
4586
+ ".txt",
4587
+ ".md",
4588
+ ".csv",
4589
+ ".xlsx",
4590
+ ".xls"
4591
+ ],
4592
+ maxSizeMB: 25,
4593
+ label: "Documents"
4594
+ },
4595
+ image: {
4596
+ mimeTypes: [
4597
+ "image/png",
4598
+ "image/jpeg",
4599
+ "image/gif",
4600
+ "image/webp",
4601
+ "image/svg+xml"
4602
+ ],
4603
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"],
4604
+ maxSizeMB: 10,
4605
+ label: "Images"
4606
+ },
4607
+ audio: {
4608
+ mimeTypes: [
4609
+ "audio/mpeg",
4610
+ "audio/wav",
4611
+ "audio/mp4",
4612
+ "audio/ogg",
4613
+ "audio/webm"
4614
+ ],
4615
+ extensions: [".mp3", ".wav", ".m4a", ".ogg", ".webm"],
4616
+ maxSizeMB: 50,
4617
+ label: "Audio"
4618
+ }
4619
+ };
4620
+ var MAX_FILES_PER_DROP = 10;
4621
+ var MAX_TOTAL_SIZE_MB = 100;
4622
+ function categorizeFile(mimeType) {
4623
+ for (const [category, config] of Object.entries(SUPPORTED_TYPES)) {
4624
+ if (config.mimeTypes.includes(mimeType)) {
4625
+ return category;
4626
+ }
4627
+ }
4628
+ return null;
4629
+ }
4630
+ function validateFile(file) {
4631
+ const category = categorizeFile(file.type);
4632
+ if (!category) {
4633
+ return {
4634
+ valid: false,
4635
+ error: `Unsupported file type: ${file.type || file.name.split(".").pop()}`
4636
+ };
4637
+ }
4638
+ const config = SUPPORTED_TYPES[category];
4639
+ const maxSizeBytes = config.maxSizeMB * 1024 * 1024;
4640
+ if (file.size > maxSizeBytes) {
4641
+ return {
4642
+ valid: false,
4643
+ error: `${file.name} exceeds ${config.maxSizeMB}MB limit for ${config.label.toLowerCase()}`
4644
+ };
4645
+ }
4646
+ return { valid: true, category };
4647
+ }
4648
+ function formatFileSize2(bytes) {
4649
+ if (bytes < 1024) return `${bytes} B`;
4650
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
4651
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4652
+ }
4653
+ function getCategoryIcon(category) {
4654
+ switch (category) {
4655
+ case "document":
4656
+ return FileText;
4657
+ case "image":
4658
+ return ImageIcon2;
4659
+ case "audio":
4660
+ return Music;
4661
+ default:
4662
+ return FileIcon2;
4663
+ }
4664
+ }
4665
+ var FileDropZone = react.forwardRef(
4666
+ function FileDropZone2({
4667
+ onFilesChange,
4668
+ files,
4669
+ onUpload,
4670
+ onConfirmUpload,
4671
+ onError,
4672
+ maxFiles = MAX_FILES_PER_DROP,
4673
+ maxTotalSizeMB = MAX_TOTAL_SIZE_MB,
4674
+ disabled = false,
4675
+ className,
4676
+ accept,
4677
+ compact = false,
4678
+ children
4679
+ }, ref) {
4680
+ const [isDragging, setIsDragging] = react.useState(false);
4681
+ const fileInputRef = react.useRef(null);
4682
+ const dropZoneRef = react.useRef(null);
4683
+ const dragCounter = react.useRef(0);
4684
+ react.useImperativeHandle(
4685
+ ref,
4686
+ () => ({
4687
+ openFileDialog: () => {
4688
+ if (!disabled) {
4689
+ fileInputRef.current?.click();
4690
+ }
4691
+ }
4692
+ }),
4693
+ [disabled]
4694
+ );
4695
+ const currentTotalSize = files.reduce((sum, f) => sum + f.file.size, 0);
4696
+ const uploadFile = react.useCallback(
4697
+ async (file, _tempId) => {
4698
+ const result = await onUpload(file, {
4699
+ filename: file.name,
4700
+ mimeType: file.type,
4701
+ size: file.size
4702
+ });
4703
+ if (result.uploadUrl) {
4704
+ const uploadResponse = await fetch(result.uploadUrl, {
4705
+ method: "PUT",
4706
+ body: file,
4707
+ headers: { "Content-Type": file.type }
4708
+ });
4709
+ if (!uploadResponse.ok) {
4710
+ throw new Error(`Upload failed: ${uploadResponse.statusText}`);
4711
+ }
4712
+ }
4713
+ if (onConfirmUpload) {
4714
+ await onConfirmUpload(result.uuid);
4715
+ }
4716
+ return result.uuid;
4717
+ },
4718
+ [onUpload, onConfirmUpload]
4719
+ );
4720
+ const handleFiles = react.useCallback(
4721
+ async (selectedFiles) => {
4722
+ if (disabled) return;
4723
+ if (files.length + selectedFiles.length > maxFiles) {
4724
+ onError?.(`Maximum ${maxFiles} files allowed`);
4725
+ return;
4726
+ }
4727
+ const validFiles = [];
4728
+ let newTotalSize = currentTotalSize;
4729
+ for (const file of selectedFiles) {
4730
+ const validation = validateFile(file);
4731
+ if (!validation.valid) {
4732
+ onError?.(validation.error);
4733
+ continue;
4734
+ }
4735
+ newTotalSize += file.size;
4736
+ if (newTotalSize > maxTotalSizeMB * 1024 * 1024) {
4737
+ onError?.(`Total upload size exceeds ${maxTotalSizeMB}MB`);
4738
+ break;
4739
+ }
4740
+ validFiles.push({ file, category: validation.category });
4741
+ }
4742
+ if (validFiles.length === 0) return;
4743
+ const processedFiles = [];
4744
+ for (const entry of validFiles) {
4745
+ if (entry.category === "image") {
4746
+ try {
4747
+ const compressed = await compressImage(entry.file);
4748
+ processedFiles.push({ file: compressed, category: entry.category });
4749
+ } catch (err) {
4750
+ console.warn("Image compression failed, using original:", err);
4751
+ processedFiles.push(entry);
4752
+ }
4753
+ } else {
4754
+ processedFiles.push(entry);
4755
+ }
4756
+ }
4757
+ const newAttachments = processedFiles.map(
4758
+ ({ file, category }) => {
4759
+ const tempId = `temp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4760
+ return {
4761
+ uuid: tempId,
4762
+ file,
4763
+ status: "pending",
4764
+ progress: 0,
4765
+ category,
4766
+ previewUrl: category === "image" ? URL.createObjectURL(file) : void 0
4767
+ };
4768
+ }
4769
+ );
4770
+ let currentFiles = [...files, ...newAttachments];
4771
+ onFilesChange(currentFiles);
4772
+ for (const attachment of newAttachments) {
4773
+ try {
4774
+ currentFiles = currentFiles.map(
4775
+ (f) => f.uuid === attachment.uuid ? { ...f, status: "uploading", progress: 10 } : f
4776
+ );
4777
+ onFilesChange(currentFiles);
4778
+ const realUuid = await uploadFile(attachment.file, attachment.uuid);
4779
+ currentFiles = currentFiles.map(
4780
+ (f) => f.uuid === attachment.uuid ? {
4781
+ ...f,
4782
+ uuid: realUuid,
4783
+ status: "ready",
4784
+ progress: 100,
4785
+ hasThumbnail: f.category === "image"
4786
+ } : f
4787
+ );
4788
+ onFilesChange(currentFiles);
4789
+ } catch (error) {
4790
+ console.error("Upload error:", error);
4791
+ currentFiles = currentFiles.map(
4792
+ (f) => f.uuid === attachment.uuid ? {
4793
+ ...f,
4794
+ status: "error",
4795
+ error: error instanceof Error ? error.message : "Upload failed"
4796
+ } : f
4797
+ );
4798
+ onFilesChange(currentFiles);
4799
+ onError?.(`Failed to upload ${attachment.file.name}`);
4800
+ }
4801
+ }
4802
+ },
4803
+ [
4804
+ disabled,
4805
+ files,
4806
+ maxFiles,
4807
+ currentTotalSize,
4808
+ maxTotalSizeMB,
4809
+ onFilesChange,
4810
+ uploadFile,
4811
+ onError
4812
+ ]
4813
+ );
4814
+ const handleRemove = react.useCallback(
4815
+ (uuid) => {
4816
+ const file = files.find((f) => f.uuid === uuid);
4817
+ if (file?.previewUrl) {
4818
+ URL.revokeObjectURL(file.previewUrl);
4819
+ }
4820
+ onFilesChange(files.filter((f) => f.uuid !== uuid));
4821
+ },
4822
+ [files, onFilesChange]
4823
+ );
4824
+ const handleRetry = react.useCallback(
4825
+ async (uuid) => {
4826
+ const attachment = files.find((f) => f.uuid === uuid);
4827
+ if (!attachment || attachment.status !== "error") return;
4828
+ let currentFiles = files.map(
4829
+ (f) => f.uuid === uuid ? { ...f, status: "pending", progress: 0, error: void 0 } : f
4830
+ );
4831
+ onFilesChange(currentFiles);
4832
+ try {
4833
+ currentFiles = currentFiles.map(
4834
+ (f) => f.uuid === uuid ? { ...f, status: "uploading", progress: 10 } : f
4835
+ );
4836
+ onFilesChange(currentFiles);
4837
+ const realUuid = await uploadFile(attachment.file, uuid);
4838
+ currentFiles = currentFiles.map(
4839
+ (f) => f.uuid === uuid ? {
4840
+ ...f,
4841
+ uuid: realUuid,
4842
+ status: "ready",
4843
+ progress: 100,
4844
+ hasThumbnail: f.category === "image"
4845
+ } : f
4846
+ );
4847
+ onFilesChange(currentFiles);
4848
+ } catch (error) {
4849
+ console.error("Retry upload error:", error);
4850
+ currentFiles = currentFiles.map(
4851
+ (f) => f.uuid === uuid ? {
4852
+ ...f,
4853
+ status: "error",
4854
+ error: error instanceof Error ? error.message : "Upload failed"
4855
+ } : f
4856
+ );
4857
+ onFilesChange(currentFiles);
4858
+ onError?.(`Failed to upload ${attachment.file.name}`);
4859
+ }
4860
+ },
4861
+ [files, onFilesChange, uploadFile, onError]
4862
+ );
4863
+ const hasDraggedFiles = react.useCallback((e) => {
4864
+ if (e.dataTransfer.types) {
4865
+ for (const type of e.dataTransfer.types) {
4866
+ if (type === "Files") return true;
4867
+ }
4868
+ }
4869
+ return false;
4870
+ }, []);
4871
+ const handleDragEnter = react.useCallback(
4872
+ (e) => {
4873
+ e.preventDefault();
4874
+ e.stopPropagation();
4875
+ if (!hasDraggedFiles(e)) return;
4876
+ dragCounter.current++;
4877
+ if (!disabled && dragCounter.current === 1) {
4878
+ setIsDragging(true);
4879
+ }
4880
+ },
4881
+ [disabled, hasDraggedFiles]
4882
+ );
4883
+ const handleDragOver = react.useCallback(
4884
+ (e) => {
4885
+ e.preventDefault();
4886
+ e.stopPropagation();
4887
+ if (hasDraggedFiles(e)) {
4888
+ e.dataTransfer.dropEffect = disabled ? "none" : "copy";
4889
+ }
4890
+ },
4891
+ [disabled, hasDraggedFiles]
4892
+ );
4893
+ const handleDragLeave = react.useCallback((e) => {
4894
+ e.preventDefault();
4895
+ e.stopPropagation();
4896
+ if (dragCounter.current > 0) {
4897
+ dragCounter.current--;
4898
+ if (dragCounter.current === 0) {
4899
+ setIsDragging(false);
4900
+ }
4901
+ }
4902
+ }, []);
4903
+ const handleDrop = react.useCallback(
4904
+ (e) => {
4905
+ e.preventDefault();
4906
+ e.stopPropagation();
4907
+ dragCounter.current = 0;
4908
+ setIsDragging(false);
4909
+ if (disabled) return;
4910
+ const droppedFiles = Array.from(e.dataTransfer.files);
4911
+ if (droppedFiles.length > 0) {
4912
+ handleFiles(droppedFiles);
4913
+ }
4914
+ },
4915
+ [handleFiles, disabled]
4916
+ );
4917
+ const handleFileSelect = react.useCallback(
4918
+ (e) => {
4919
+ const selectedFiles = e.target.files;
4920
+ if (selectedFiles) {
4921
+ handleFiles(Array.from(selectedFiles));
4922
+ }
4923
+ if (fileInputRef.current) {
4924
+ fileInputRef.current.value = "";
4925
+ }
4926
+ },
4927
+ [handleFiles]
4928
+ );
4929
+ const handleClick = react.useCallback(() => {
4930
+ if (!disabled) {
4931
+ fileInputRef.current?.click();
4932
+ }
4933
+ }, [disabled]);
4934
+ const checkDataTransferHasFiles = react.useCallback(
4935
+ (dataTransfer) => {
4936
+ if (!dataTransfer?.types) return false;
4937
+ for (let i = 0; i < dataTransfer.types.length; i++) {
4938
+ if (dataTransfer.types[i] === "Files") return true;
4939
+ }
4940
+ return false;
4941
+ },
4942
+ []
4943
+ );
4944
+ react.useEffect(() => {
4945
+ if (!children || !dropZoneRef.current) return;
4946
+ const element = dropZoneRef.current;
4947
+ let enterCounter = 0;
4948
+ const handleDragEnterNative = (e) => {
4949
+ e.preventDefault();
4950
+ e.stopPropagation();
4951
+ if (!checkDataTransferHasFiles(e.dataTransfer)) return;
4952
+ enterCounter++;
4953
+ if (enterCounter === 1 && !disabled) {
4954
+ setIsDragging(true);
4955
+ }
4956
+ };
4957
+ const handleDragLeaveNative = (e) => {
4958
+ e.preventDefault();
4959
+ e.stopPropagation();
4960
+ if (!checkDataTransferHasFiles(e.dataTransfer)) return;
4961
+ enterCounter--;
4962
+ if (enterCounter <= 0) {
4963
+ enterCounter = 0;
4964
+ setIsDragging(false);
4965
+ }
4966
+ };
4967
+ const handleDragOverNative = (e) => {
4968
+ e.preventDefault();
4969
+ e.stopPropagation();
4970
+ if (checkDataTransferHasFiles(e.dataTransfer) && !disabled && e.dataTransfer) {
4971
+ e.dataTransfer.dropEffect = "copy";
4972
+ }
4973
+ };
4974
+ const handleDropNative = (e) => {
4975
+ e.preventDefault();
4976
+ e.stopPropagation();
4977
+ enterCounter = 0;
4978
+ setIsDragging(false);
4979
+ if (disabled) return;
4980
+ const droppedFiles = e.dataTransfer?.files;
4981
+ if (droppedFiles && droppedFiles.length > 0) {
4982
+ handleFiles(Array.from(droppedFiles));
4983
+ }
4984
+ };
4985
+ element.addEventListener("dragenter", handleDragEnterNative, true);
4986
+ element.addEventListener("dragleave", handleDragLeaveNative, true);
4987
+ element.addEventListener("dragover", handleDragOverNative, true);
4988
+ element.addEventListener("drop", handleDropNative, true);
4989
+ return () => {
4990
+ element.removeEventListener("dragenter", handleDragEnterNative, true);
4991
+ element.removeEventListener("dragleave", handleDragLeaveNative, true);
4992
+ element.removeEventListener("dragover", handleDragOverNative, true);
4993
+ element.removeEventListener("drop", handleDropNative, true);
4994
+ };
4995
+ }, [children, disabled, handleFiles, checkDataTransferHasFiles]);
4996
+ react.useEffect(() => {
4997
+ if (disabled) {
4998
+ dragCounter.current = 0;
4999
+ setIsDragging(false);
5000
+ }
5001
+ }, [disabled]);
5002
+ const acceptTypes = accept || Object.values(SUPPORTED_TYPES).flatMap((t) => [...t.mimeTypes, ...t.extensions]).join(",");
5003
+ const hasFiles = files.length > 0;
5004
+ const isUploading = files.some((f) => f.status === "uploading");
5005
+ if (children) {
5006
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5007
+ "div",
5008
+ {
5009
+ ref: dropZoneRef,
5010
+ "data-dropzone": "wrapper",
5011
+ className: cn4("relative w-full h-full", className),
5012
+ style: { minHeight: "inherit" },
5013
+ children: [
5014
+ /* @__PURE__ */ jsxRuntime.jsx(
5015
+ "input",
5016
+ {
5017
+ ref: fileInputRef,
5018
+ type: "file",
5019
+ multiple: true,
5020
+ accept: acceptTypes,
5021
+ onChange: handleFileSelect,
5022
+ className: "hidden",
5023
+ disabled
5024
+ }
5025
+ ),
5026
+ children,
5027
+ isDragging && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
5028
+ "div",
5029
+ {
5030
+ 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",
5031
+ style: { pointerEvents: "none" },
5032
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-background/80 rounded-xl p-8 flex flex-col items-center shadow-lg", children: [
5033
+ /* @__PURE__ */ jsxRuntime.jsx(Upload, { className: "h-12 w-12 text-primary mb-4 animate-bounce" }),
5034
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg font-medium text-primary", children: "Drop files here" }),
5035
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "Documents, images, or audio files" })
5036
+ ] })
5037
+ }
5038
+ )
5039
+ ]
5040
+ }
5041
+ );
5042
+ }
5043
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn4("w-full", className), children: [
5044
+ /* @__PURE__ */ jsxRuntime.jsx(
5045
+ "input",
5046
+ {
5047
+ ref: fileInputRef,
5048
+ type: "file",
5049
+ multiple: true,
5050
+ accept: acceptTypes,
5051
+ onChange: handleFileSelect,
5052
+ className: "hidden",
5053
+ disabled
5054
+ }
5055
+ ),
5056
+ /* @__PURE__ */ jsxRuntime.jsxs(
5057
+ "div",
5058
+ {
5059
+ onDragEnter: handleDragEnter,
5060
+ onDragOver: handleDragOver,
5061
+ onDragLeave: handleDragLeave,
5062
+ onDrop: handleDrop,
5063
+ onClick: handleClick,
5064
+ className: cn4(
5065
+ "relative rounded-lg border-2 border-dashed transition-all cursor-pointer",
5066
+ "flex flex-col items-center justify-center",
5067
+ compact ? "p-2 min-h-[60px]" : "p-4 min-h-[100px]",
5068
+ isDragging && !disabled ? "border-primary bg-primary/5" : "border-muted-foreground/25",
5069
+ disabled ? "opacity-50 cursor-not-allowed" : "hover:border-primary/50 hover:bg-muted/25"
5070
+ ),
5071
+ children: [
5072
+ isUploading ? /* @__PURE__ */ jsxRuntime.jsx(
5073
+ Loader2,
5074
+ {
5075
+ className: cn4(
5076
+ "animate-spin text-muted-foreground",
5077
+ compact ? "h-5 w-5 mb-1" : "h-8 w-8 mb-2"
5078
+ )
5079
+ }
5080
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
5081
+ Upload,
5082
+ {
5083
+ className: cn4(
5084
+ "text-muted-foreground",
5085
+ compact ? "h-5 w-5 mb-1" : "h-8 w-8 mb-2"
5086
+ )
5087
+ }
5088
+ ),
5089
+ /* @__PURE__ */ jsxRuntime.jsx(
5090
+ "p",
5091
+ {
5092
+ className: cn4(
5093
+ "font-medium text-center text-muted-foreground",
5094
+ compact ? "text-xs" : "text-sm"
5095
+ ),
5096
+ children: isDragging ? "Drop files here" : isUploading ? "Uploading..." : "Click to upload or drag and drop"
5097
+ }
5098
+ ),
5099
+ !compact && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground/70 text-center mt-1", children: "Documents, images, or audio files" })
5100
+ ]
5101
+ }
5102
+ ),
5103
+ hasFiles && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn4("space-y-2", compact ? "mt-2" : "mt-3"), children: files.map((attachment) => {
5104
+ const Icon = getCategoryIcon(attachment.category);
5105
+ const isError = attachment.status === "error";
5106
+ const isLoading = attachment.status === "uploading" || attachment.status === "processing";
5107
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5108
+ "div",
5109
+ {
5110
+ className: cn4(
5111
+ "flex items-center gap-3 p-2 rounded-lg border",
5112
+ isError ? "border-destructive/50 bg-destructive/5" : "border-border bg-muted/25"
5113
+ ),
5114
+ children: [
5115
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0 w-10 h-10 rounded overflow-hidden bg-muted flex items-center justify-center", children: [
5116
+ attachment.previewUrl ? /* @__PURE__ */ jsxRuntime.jsx(
5117
+ "img",
5118
+ {
5119
+ src: attachment.previewUrl,
5120
+ alt: attachment.file.name,
5121
+ className: "w-full h-full object-cover"
5122
+ }
5123
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-5 w-5 text-muted-foreground" }),
5124
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-background/80 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Loader2, { className: "h-4 w-4 animate-spin" }) })
5125
+ ] }),
5126
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
5127
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: attachment.file.name }),
5128
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
5129
+ formatFileSize2(attachment.file.size),
5130
+ isError && attachment.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-2", children: attachment.error })
5131
+ ] })
5132
+ ] }),
5133
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
5134
+ attachment.progress,
5135
+ "%"
5136
+ ] }) : attachment.status === "ready" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-green-600", children: "Ready" }) : null }),
5137
+ isError && /* @__PURE__ */ jsxRuntime.jsx(
5138
+ "button",
5139
+ {
5140
+ type: "button",
5141
+ className: "flex-shrink-0 h-8 w-8 inline-flex items-center justify-center rounded-md text-primary hover:bg-muted transition-colors",
5142
+ onClick: (e) => {
5143
+ e.stopPropagation();
5144
+ handleRetry(attachment.uuid);
5145
+ },
5146
+ title: "Retry upload",
5147
+ children: /* @__PURE__ */ jsxRuntime.jsx(RotateCw, { className: "h-4 w-4" })
5148
+ }
5149
+ ),
5150
+ /* @__PURE__ */ jsxRuntime.jsx(
5151
+ "button",
5152
+ {
5153
+ type: "button",
5154
+ className: "flex-shrink-0 h-8 w-8 inline-flex items-center justify-center rounded-md hover:bg-muted transition-colors disabled:opacity-50",
5155
+ onClick: (e) => {
5156
+ e.stopPropagation();
5157
+ handleRemove(attachment.uuid);
5158
+ },
5159
+ disabled: isLoading,
5160
+ children: /* @__PURE__ */ jsxRuntime.jsx(X, { className: "h-4 w-4" })
5161
+ }
5162
+ )
5163
+ ]
5164
+ },
5165
+ attachment.uuid
5166
+ );
5167
+ }) })
5168
+ ] });
5169
+ }
5170
+ );
5171
+ function cn5(...classes) {
5172
+ return classes.filter(Boolean).join(" ");
5173
+ }
4221
5174
  function AskUserPrompt({
4222
5175
  question,
4223
5176
  options,
@@ -4251,7 +5204,7 @@ function AskUserPrompt({
4251
5204
  return /* @__PURE__ */ jsxRuntime.jsxs(
4252
5205
  "div",
4253
5206
  {
4254
- className: cn4(
5207
+ className: cn5(
4255
5208
  "rounded-lg border p-4 space-y-3",
4256
5209
  status === "waiting" ? "border-blue-200 bg-blue-50/50 dark:border-blue-800 dark:bg-blue-950/20" : "border-green-200 bg-green-50/50 dark:border-green-800 dark:bg-green-950/20",
4257
5210
  className
@@ -4263,7 +5216,7 @@ function AskUserPrompt({
4263
5216
  /* @__PURE__ */ jsxRuntime.jsx(
4264
5217
  "span",
4265
5218
  {
4266
- className: cn4(
5219
+ className: cn5(
4267
5220
  "ml-auto text-xs px-2 py-1 rounded-full font-medium",
4268
5221
  status === "waiting" ? "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300" : "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"
4269
5222
  ),
@@ -4278,7 +5231,7 @@ function AskUserPrompt({
4278
5231
  type: "button",
4279
5232
  onClick: () => handleOptionSelect(option),
4280
5233
  disabled,
4281
- className: cn4(
5234
+ className: cn5(
4282
5235
  "px-3 py-1.5 text-sm rounded-md border transition-all",
4283
5236
  selectedOption === option ? "bg-blue-600 text-white border-blue-600 hover:bg-blue-700" : "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300",
4284
5237
  disabled && "opacity-50 cursor-not-allowed"
@@ -4298,7 +5251,7 @@ function AskUserPrompt({
4298
5251
  },
4299
5252
  onKeyDown: handleKeyDown,
4300
5253
  placeholder: options?.length ? "Or type a custom response..." : "Type your response...",
4301
- className: cn4(
5254
+ className: cn5(
4302
5255
  "flex-1 min-h-[60px] px-3 py-2 text-sm resize-none rounded-md border",
4303
5256
  "border-gray-300 dark:border-gray-600",
4304
5257
  "bg-white dark:bg-gray-800",
@@ -4316,7 +5269,7 @@ function AskUserPrompt({
4316
5269
  type: "button",
4317
5270
  onClick: handleSubmit,
4318
5271
  disabled: isSubmitDisabled,
4319
- className: cn4(
5272
+ className: cn5(
4320
5273
  "self-end px-4 py-2 rounded-md text-white transition-colors",
4321
5274
  "bg-blue-600 hover:bg-blue-700",
4322
5275
  "disabled:opacity-50 disabled:cursor-not-allowed"
@@ -4337,7 +5290,7 @@ function AskUserPrompt({
4337
5290
  }
4338
5291
  );
4339
5292
  }
4340
- function cn5(...classes) {
5293
+ function cn6(...classes) {
4341
5294
  return classes.filter(Boolean).join(" ");
4342
5295
  }
4343
5296
  function MarkdownContent({
@@ -4398,7 +5351,7 @@ function MarkdownContent({
4398
5351
  if (isCodeBlock) {
4399
5352
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative my-2", children: [
4400
5353
  language && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 right-0 px-2 py-1 text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-bl rounded-tr border-b border-l border-gray-200 dark:border-gray-700", children: language }),
4401
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "bg-gray-100 dark:bg-gray-800 rounded-lg p-4 overflow-x-auto text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: cn5("font-mono", codeClassName), ...props, children }) })
5354
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "bg-gray-100 dark:bg-gray-800 rounded-lg p-4 overflow-x-auto text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: cn6("font-mono", codeClassName), ...props, children }) })
4402
5355
  ] });
4403
5356
  }
4404
5357
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -4445,7 +5398,7 @@ function MarkdownContent({
4445
5398
  () => customComponents ? { ...defaultComponents, ...customComponents } : defaultComponents,
4446
5399
  [defaultComponents, customComponents]
4447
5400
  );
4448
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn5("prose prose-sm dark:prose-invert max-w-none", className), children: /* @__PURE__ */ jsxRuntime.jsx(
5401
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn6("prose prose-sm dark:prose-invert max-w-none", className), children: /* @__PURE__ */ jsxRuntime.jsx(
4449
5402
  ReactMarkdown__default.default,
4450
5403
  {
4451
5404
  remarkPlugins: [remarkGfm__default.default],
@@ -4455,7 +5408,7 @@ function MarkdownContent({
4455
5408
  }
4456
5409
  ) });
4457
5410
  }
4458
- function cn6(...classes) {
5411
+ function cn7(...classes) {
4459
5412
  return classes.filter(Boolean).join(" ");
4460
5413
  }
4461
5414
  function formatToolName(toolName) {
@@ -4603,7 +5556,7 @@ function ToolCallEntry({
4603
5556
  return /* @__PURE__ */ jsxRuntime.jsxs(
4604
5557
  "div",
4605
5558
  {
4606
- className: cn6(
5559
+ className: cn7(
4607
5560
  "rounded-lg border text-sm",
4608
5561
  statusColor,
4609
5562
  className
@@ -4624,7 +5577,7 @@ function ToolCallEntry({
4624
5577
  ]
4625
5578
  }
4626
5579
  ),
4627
- hasImages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn6(
5580
+ hasImages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn7(
4628
5581
  "grid gap-2",
4629
5582
  resultImages.length === 1 ? "grid-cols-1" : "grid-cols-2"
4630
5583
  ), children: resultImages.map((img, idx) => {
@@ -4683,7 +5636,7 @@ function ToolCallEntry({
4683
5636
  }
4684
5637
  );
4685
5638
  }
4686
- function cn7(...classes) {
5639
+ function cn8(...classes) {
4687
5640
  return classes.filter(Boolean).join(" ");
4688
5641
  }
4689
5642
  function ChatMessage({
@@ -4705,7 +5658,7 @@ function ChatMessage({
4705
5658
  return /* @__PURE__ */ jsxRuntime.jsxs(
4706
5659
  "div",
4707
5660
  {
4708
- className: cn7(
5661
+ className: cn8(
4709
5662
  "flex gap-3 p-4",
4710
5663
  isUser ? "flex-row-reverse" : "flex-row",
4711
5664
  className
@@ -4714,7 +5667,7 @@ function ChatMessage({
4714
5667
  /* @__PURE__ */ jsxRuntime.jsx(
4715
5668
  "div",
4716
5669
  {
4717
- className: cn7(
5670
+ className: cn8(
4718
5671
  "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
4719
5672
  isUser ? "bg-blue-500 text-white" : "bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300"
4720
5673
  ),
@@ -4724,7 +5677,7 @@ function ChatMessage({
4724
5677
  /* @__PURE__ */ jsxRuntime.jsxs(
4725
5678
  "div",
4726
5679
  {
4727
- className: cn7(
5680
+ className: cn8(
4728
5681
  "flex-1 min-w-0 space-y-2",
4729
5682
  isUser ? "text-right" : "text-left"
4730
5683
  ),
@@ -4732,7 +5685,7 @@ function ChatMessage({
4732
5685
  /* @__PURE__ */ jsxRuntime.jsxs(
4733
5686
  "div",
4734
5687
  {
4735
- className: cn7(
5688
+ className: cn8(
4736
5689
  "flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400",
4737
5690
  isUser ? "justify-end" : "justify-start"
4738
5691
  ),
@@ -4765,7 +5718,7 @@ function ChatMessage({
4765
5718
  message.content && /* @__PURE__ */ jsxRuntime.jsx(
4766
5719
  "div",
4767
5720
  {
4768
- className: cn7(
5721
+ className: cn8(
4769
5722
  "rounded-lg p-3",
4770
5723
  isUser ? "bg-blue-500 text-white" : "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100"
4771
5724
  ),
@@ -4795,7 +5748,7 @@ function ChatMessage({
4795
5748
  }
4796
5749
  );
4797
5750
  }
4798
- function cn8(...classes) {
5751
+ function cn9(...classes) {
4799
5752
  return classes.filter(Boolean).join(" ");
4800
5753
  }
4801
5754
  function MessageList({
@@ -4825,7 +5778,7 @@ function MessageList({
4825
5778
  "div",
4826
5779
  {
4827
5780
  ref: scrollRef,
4828
- className: cn8(
5781
+ className: cn9(
4829
5782
  "flex-1 overflow-y-auto",
4830
5783
  className
4831
5784
  ),
@@ -4918,6 +5871,7 @@ function AgentChat({
4918
5871
  isStreaming,
4919
5872
  isLoadingHistory,
4920
5873
  error,
5874
+ sessionId: currentSessionId,
4921
5875
  clarification,
4922
5876
  sendMessage,
4923
5877
  respondToClarification,
@@ -4938,8 +5892,59 @@ function AgentChat({
4938
5892
  onToolResult,
4939
5893
  onClarification
4940
5894
  });
5895
+ const dropZoneRef = react.useRef(null);
5896
+ const attachments = useFileAttachments({
5897
+ onUpload: enableAttachments && adapter ? async (_file, meta) => {
5898
+ const result = await adapter.getUploadUrl({
5899
+ sessionId: currentSessionId ?? "",
5900
+ ...meta
5901
+ });
5902
+ return { uuid: result.attachmentUuid, uploadUrl: result.uploadUrl };
5903
+ } : async () => ({ uuid: "", uploadUrl: "" }),
5904
+ onConfirmUpload: enableAttachments && adapter ? (uuid) => adapter.confirmUpload(uuid) : void 0,
5905
+ onError: (msg) => {
5906
+ console.error("[AgentChat] Attachment error:", msg);
5907
+ }
5908
+ });
5909
+ const handleSend = react.useCallback(
5910
+ async (content) => {
5911
+ if (enableAttachments && attachments.hasReadyFiles) {
5912
+ const uuids = attachments.getReadyUuids();
5913
+ await sendMessage(content, { attachmentUuids: uuids });
5914
+ attachments.clear();
5915
+ } else {
5916
+ await sendMessage(content);
5917
+ }
5918
+ },
5919
+ [enableAttachments, attachments, sendMessage]
5920
+ );
4941
5921
  return /* @__PURE__ */ jsxRuntime.jsxs(ChatContainer, { maxHeight, className, theme, children: [
4942
- /* @__PURE__ */ jsxRuntime.jsx(
5922
+ enableAttachments ? /* @__PURE__ */ jsxRuntime.jsx(
5923
+ FileDropZone,
5924
+ {
5925
+ ref: dropZoneRef,
5926
+ ...attachments.dropZoneProps,
5927
+ disabled: isLoading || !!clarification || isLoadingHistory,
5928
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5929
+ MessageList,
5930
+ {
5931
+ messages,
5932
+ welcomeMessage,
5933
+ renderWelcome,
5934
+ isLoading,
5935
+ isStreaming,
5936
+ isLoadingHistory,
5937
+ enableMarkdown,
5938
+ showToolCalls,
5939
+ markdownComponents,
5940
+ markdownUrlTransform,
5941
+ markdownContentTransform,
5942
+ onImageClick,
5943
+ sendMessage: handleSend
5944
+ }
5945
+ )
5946
+ }
5947
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4943
5948
  MessageList,
4944
5949
  {
4945
5950
  messages,
@@ -4954,7 +5959,7 @@ function AgentChat({
4954
5959
  markdownUrlTransform,
4955
5960
  markdownContentTransform,
4956
5961
  onImageClick,
4957
- sendMessage
5962
+ sendMessage: handleSend
4958
5963
  }
4959
5964
  ),
4960
5965
  clarification && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -4969,17 +5974,21 @@ function AgentChat({
4969
5974
  /* @__PURE__ */ jsxRuntime.jsx(
4970
5975
  ChatInput,
4971
5976
  {
4972
- onSend: sendMessage,
5977
+ onSend: handleSend,
4973
5978
  onStop: isStreaming ? cancel : void 0,
4974
5979
  placeholder,
4975
5980
  disabled: !!clarification || isLoadingHistory,
4976
5981
  isLoading,
4977
- enableAttachments
5982
+ enableAttachments,
5983
+ attachments: enableAttachments ? attachments.files : void 0,
5984
+ onRemoveAttachment: enableAttachments ? attachments.removeFile : void 0,
5985
+ dropZoneRef: enableAttachments ? dropZoneRef : void 0,
5986
+ isUploadingAttachments: enableAttachments ? attachments.isUploading : void 0
4978
5987
  }
4979
5988
  )
4980
5989
  ] });
4981
5990
  }
4982
- function cn9(...classes) {
5991
+ function cn10(...classes) {
4983
5992
  return classes.filter(Boolean).join(" ");
4984
5993
  }
4985
5994
  function TodoList({
@@ -5008,7 +6017,7 @@ function TodoList({
5008
6017
  return /* @__PURE__ */ jsxRuntime.jsxs(
5009
6018
  "div",
5010
6019
  {
5011
- className: cn9(
6020
+ className: cn10(
5012
6021
  "rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden",
5013
6022
  className
5014
6023
  ),
@@ -5016,7 +6025,7 @@ function TodoList({
5016
6025
  /* @__PURE__ */ jsxRuntime.jsxs(
5017
6026
  "div",
5018
6027
  {
5019
- className: cn9(
6028
+ className: cn10(
5020
6029
  "flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800/50",
5021
6030
  collapsible && "cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
5022
6031
  ),
@@ -5047,7 +6056,7 @@ function TodoList({
5047
6056
  !isCollapsed && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-gray-100 dark:divide-gray-700", children: todos.map((todo, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
5048
6057
  "li",
5049
6058
  {
5050
- className: cn9(
6059
+ className: cn10(
5051
6060
  "px-3 py-2 flex items-start gap-2 text-sm",
5052
6061
  todo.status === "in_progress" && "bg-blue-50/50 dark:bg-blue-950/20",
5053
6062
  todo.status === "completed" && "opacity-60"
@@ -5057,7 +6066,7 @@ function TodoList({
5057
6066
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
5058
6067
  "p",
5059
6068
  {
5060
- className: cn9(
6069
+ className: cn10(
5061
6070
  "text-gray-700 dark:text-gray-300",
5062
6071
  todo.status === "completed" && "line-through"
5063
6072
  ),
@@ -5094,6 +6103,7 @@ exports.CopyIcon = CopyIcon;
5094
6103
  exports.DatabaseIcon = DatabaseIcon;
5095
6104
  exports.DownloadIcon = DownloadIcon;
5096
6105
  exports.ExternalLinkIcon = ExternalLinkIcon;
6106
+ exports.FileDropZone = FileDropZone;
5097
6107
  exports.FileIcon = FileIcon;
5098
6108
  exports.FileTextIcon = FileTextIcon;
5099
6109
  exports.HelpCircleIcon = HelpCircleIcon;
@@ -5116,10 +6126,12 @@ exports.UserIcon = UserIcon;
5116
6126
  exports.XIcon = XIcon;
5117
6127
  exports.ZoomInIcon = ZoomInIcon;
5118
6128
  exports.ZoomOutIcon = ZoomOutIcon;
6129
+ exports.compressImage = compressImage;
5119
6130
  exports.createDefaultAdapter = createDefaultAdapter;
5120
6131
  exports.useAgentChat = useAgentChat;
5121
6132
  exports.useChatConfig = useChatConfig;
5122
6133
  exports.useChatConfigOptional = useChatConfigOptional;
5123
6134
  exports.useExecutionStream = useExecutionStream;
6135
+ exports.useFileAttachments = useFileAttachments;
5124
6136
  //# sourceMappingURL=index.cjs.map
5125
6137
  //# sourceMappingURL=index.cjs.map