@cadraos/sdk 0.1.5 → 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.
@@ -1530,6 +1530,29 @@ var ToolsAPI = class extends BaseResourceAPI {
1530
1530
  const idempotencyKey = generateIdempotencyKey();
1531
1531
  return this.httpPost(`/${uuid}/execute`, { input }, { idempotencyKey });
1532
1532
  }
1533
+ /**
1534
+ * Execute a tool directly, bypassing the agent runtime.
1535
+ *
1536
+ * Enqueues the tool for async processing via BullMQ (no LLM, no ReAct loop).
1537
+ * Use SSE or polling with the returned executionId to get the result.
1538
+ *
1539
+ * @param request - Direct execution request
1540
+ * @returns Queued execution response with executionId for tracking
1541
+ *
1542
+ * @example
1543
+ * ```typescript
1544
+ * const result = await client.tools.executeDirect({
1545
+ * toolUuid: 'tool-uuid',
1546
+ * parameters: { prompt: 'Generate an image of a sunset' },
1547
+ * });
1548
+ * console.log(result.executionId); // Use for polling/SSE
1549
+ * console.log(result.status); // 'queued'
1550
+ * ```
1551
+ */
1552
+ async executeDirect(request) {
1553
+ const idempotencyKey = generateIdempotencyKey();
1554
+ return this.httpPost("/execute-direct", request, { idempotencyKey });
1555
+ }
1533
1556
  /**
1534
1557
  * Validate input against tool schema
1535
1558
  *
@@ -3198,8 +3221,9 @@ function useAgentChat(options) {
3198
3221
  [adapter, handleExecutionLog, updateStreamingMessage, updateToolCall, handleClarification, handleComplete, handleError]
3199
3222
  );
3200
3223
  const sendMessage = react.useCallback(
3201
- async (content) => {
3224
+ async (content, options2) => {
3202
3225
  if (!content.trim()) return;
3226
+ const attachmentUuids = options2?.attachmentUuids;
3203
3227
  lastMessageContentRef.current = content;
3204
3228
  setIsLoading(true);
3205
3229
  setError(null);
@@ -3230,7 +3254,8 @@ function useAgentChat(options) {
3230
3254
  agentId,
3231
3255
  teamId,
3232
3256
  sessionId: currentSessionId ?? void 0,
3233
- content: content.trim()
3257
+ content: content.trim(),
3258
+ attachmentUuids
3234
3259
  });
3235
3260
  console.log("[useAgentChat] Execution started:", result);
3236
3261
  if (!currentSessionId && result.sessionId) {
@@ -3439,6 +3464,222 @@ function useAgentChat(options) {
3439
3464
  loadSession
3440
3465
  };
3441
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
+ }
3442
3683
  function cn(...classes) {
3443
3684
  return classes.filter(Boolean).join(" ");
3444
3685
  }
@@ -4088,6 +4329,51 @@ function ChatContainer({
4088
4329
  function cn3(...classes) {
4089
4330
  return classes.filter(Boolean).join(" ");
4090
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
+ }
4091
4377
  function ChatInput({
4092
4378
  onSend,
4093
4379
  onStop,
@@ -4095,10 +4381,15 @@ function ChatInput({
4095
4381
  disabled = false,
4096
4382
  isLoading = false,
4097
4383
  enableAttachments = false,
4384
+ attachments = [],
4385
+ onRemoveAttachment,
4386
+ dropZoneRef,
4387
+ isUploadingAttachments = false,
4098
4388
  className
4099
4389
  }) {
4100
4390
  const [content, setContent] = react.useState("");
4101
4391
  const textareaRef = react.useRef(null);
4392
+ const hasAttachments = enableAttachments && attachments.length > 0;
4102
4393
  react.useEffect(() => {
4103
4394
  const textarea = textareaRef.current;
4104
4395
  if (textarea) {
@@ -4108,10 +4399,10 @@ function ChatInput({
4108
4399
  }, [content]);
4109
4400
  const handleSubmit = react.useCallback(async () => {
4110
4401
  const trimmedContent = content.trim();
4111
- if (!trimmedContent || disabled || isLoading) return;
4402
+ if (!trimmedContent && !hasAttachments || disabled || isLoading || isUploadingAttachments) return;
4112
4403
  setContent("");
4113
4404
  await onSend(trimmedContent);
4114
- }, [content, disabled, isLoading, onSend]);
4405
+ }, [content, hasAttachments, disabled, isLoading, isUploadingAttachments, onSend]);
4115
4406
  const handleKeyDown = react.useCallback(
4116
4407
  (e) => {
4117
4408
  if (e.key === "Enter" && !e.shiftKey) {
@@ -4124,7 +4415,10 @@ function ChatInput({
4124
4415
  const handleStopClick = react.useCallback(() => {
4125
4416
  onStop?.();
4126
4417
  }, [onStop]);
4127
- 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;
4128
4422
  const showStopButton = isLoading && onStop;
4129
4423
  return /* @__PURE__ */ jsxRuntime.jsxs(
4130
4424
  "div",
@@ -4134,7 +4428,34 @@ function ChatInput({
4134
4428
  className
4135
4429
  ),
4136
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
+ )) }),
4137
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
+ ),
4138
4459
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsxRuntime.jsx(
4139
4460
  "textarea",
4140
4461
  {
@@ -4142,7 +4463,7 @@ function ChatInput({
4142
4463
  value: content,
4143
4464
  onChange: (e) => setContent(e.target.value),
4144
4465
  onKeyDown: handleKeyDown,
4145
- placeholder,
4466
+ placeholder: hasAttachments && !content ? `${attachments.length} file${attachments.length > 1 ? "s" : ""} attached. Add a message or press Enter to send.` : placeholder,
4146
4467
  disabled,
4147
4468
  rows: 1,
4148
4469
  className: cn3(
@@ -4192,9 +4513,664 @@ function ChatInput({
4192
4513
  }
4193
4514
  );
4194
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
+ }
4195
4567
  function cn4(...classes) {
4196
4568
  return classes.filter(Boolean).join(" ");
4197
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
+ }
4198
5174
  function AskUserPrompt({
4199
5175
  question,
4200
5176
  options,
@@ -4228,7 +5204,7 @@ function AskUserPrompt({
4228
5204
  return /* @__PURE__ */ jsxRuntime.jsxs(
4229
5205
  "div",
4230
5206
  {
4231
- className: cn4(
5207
+ className: cn5(
4232
5208
  "rounded-lg border p-4 space-y-3",
4233
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",
4234
5210
  className
@@ -4240,7 +5216,7 @@ function AskUserPrompt({
4240
5216
  /* @__PURE__ */ jsxRuntime.jsx(
4241
5217
  "span",
4242
5218
  {
4243
- className: cn4(
5219
+ className: cn5(
4244
5220
  "ml-auto text-xs px-2 py-1 rounded-full font-medium",
4245
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"
4246
5222
  ),
@@ -4255,7 +5231,7 @@ function AskUserPrompt({
4255
5231
  type: "button",
4256
5232
  onClick: () => handleOptionSelect(option),
4257
5233
  disabled,
4258
- className: cn4(
5234
+ className: cn5(
4259
5235
  "px-3 py-1.5 text-sm rounded-md border transition-all",
4260
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",
4261
5237
  disabled && "opacity-50 cursor-not-allowed"
@@ -4275,7 +5251,7 @@ function AskUserPrompt({
4275
5251
  },
4276
5252
  onKeyDown: handleKeyDown,
4277
5253
  placeholder: options?.length ? "Or type a custom response..." : "Type your response...",
4278
- className: cn4(
5254
+ className: cn5(
4279
5255
  "flex-1 min-h-[60px] px-3 py-2 text-sm resize-none rounded-md border",
4280
5256
  "border-gray-300 dark:border-gray-600",
4281
5257
  "bg-white dark:bg-gray-800",
@@ -4293,7 +5269,7 @@ function AskUserPrompt({
4293
5269
  type: "button",
4294
5270
  onClick: handleSubmit,
4295
5271
  disabled: isSubmitDisabled,
4296
- className: cn4(
5272
+ className: cn5(
4297
5273
  "self-end px-4 py-2 rounded-md text-white transition-colors",
4298
5274
  "bg-blue-600 hover:bg-blue-700",
4299
5275
  "disabled:opacity-50 disabled:cursor-not-allowed"
@@ -4314,7 +5290,7 @@ function AskUserPrompt({
4314
5290
  }
4315
5291
  );
4316
5292
  }
4317
- function cn5(...classes) {
5293
+ function cn6(...classes) {
4318
5294
  return classes.filter(Boolean).join(" ");
4319
5295
  }
4320
5296
  function MarkdownContent({
@@ -4375,7 +5351,7 @@ function MarkdownContent({
4375
5351
  if (isCodeBlock) {
4376
5352
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative my-2", children: [
4377
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 }),
4378
- /* @__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 }) })
4379
5355
  ] });
4380
5356
  }
4381
5357
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -4422,7 +5398,7 @@ function MarkdownContent({
4422
5398
  () => customComponents ? { ...defaultComponents, ...customComponents } : defaultComponents,
4423
5399
  [defaultComponents, customComponents]
4424
5400
  );
4425
- 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(
4426
5402
  ReactMarkdown__default.default,
4427
5403
  {
4428
5404
  remarkPlugins: [remarkGfm__default.default],
@@ -4432,7 +5408,7 @@ function MarkdownContent({
4432
5408
  }
4433
5409
  ) });
4434
5410
  }
4435
- function cn6(...classes) {
5411
+ function cn7(...classes) {
4436
5412
  return classes.filter(Boolean).join(" ");
4437
5413
  }
4438
5414
  function formatToolName(toolName) {
@@ -4580,7 +5556,7 @@ function ToolCallEntry({
4580
5556
  return /* @__PURE__ */ jsxRuntime.jsxs(
4581
5557
  "div",
4582
5558
  {
4583
- className: cn6(
5559
+ className: cn7(
4584
5560
  "rounded-lg border text-sm",
4585
5561
  statusColor,
4586
5562
  className
@@ -4601,7 +5577,7 @@ function ToolCallEntry({
4601
5577
  ]
4602
5578
  }
4603
5579
  ),
4604
- 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(
4605
5581
  "grid gap-2",
4606
5582
  resultImages.length === 1 ? "grid-cols-1" : "grid-cols-2"
4607
5583
  ), children: resultImages.map((img, idx) => {
@@ -4660,7 +5636,7 @@ function ToolCallEntry({
4660
5636
  }
4661
5637
  );
4662
5638
  }
4663
- function cn7(...classes) {
5639
+ function cn8(...classes) {
4664
5640
  return classes.filter(Boolean).join(" ");
4665
5641
  }
4666
5642
  function ChatMessage({
@@ -4682,7 +5658,7 @@ function ChatMessage({
4682
5658
  return /* @__PURE__ */ jsxRuntime.jsxs(
4683
5659
  "div",
4684
5660
  {
4685
- className: cn7(
5661
+ className: cn8(
4686
5662
  "flex gap-3 p-4",
4687
5663
  isUser ? "flex-row-reverse" : "flex-row",
4688
5664
  className
@@ -4691,7 +5667,7 @@ function ChatMessage({
4691
5667
  /* @__PURE__ */ jsxRuntime.jsx(
4692
5668
  "div",
4693
5669
  {
4694
- className: cn7(
5670
+ className: cn8(
4695
5671
  "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
4696
5672
  isUser ? "bg-blue-500 text-white" : "bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300"
4697
5673
  ),
@@ -4701,7 +5677,7 @@ function ChatMessage({
4701
5677
  /* @__PURE__ */ jsxRuntime.jsxs(
4702
5678
  "div",
4703
5679
  {
4704
- className: cn7(
5680
+ className: cn8(
4705
5681
  "flex-1 min-w-0 space-y-2",
4706
5682
  isUser ? "text-right" : "text-left"
4707
5683
  ),
@@ -4709,7 +5685,7 @@ function ChatMessage({
4709
5685
  /* @__PURE__ */ jsxRuntime.jsxs(
4710
5686
  "div",
4711
5687
  {
4712
- className: cn7(
5688
+ className: cn8(
4713
5689
  "flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400",
4714
5690
  isUser ? "justify-end" : "justify-start"
4715
5691
  ),
@@ -4742,7 +5718,7 @@ function ChatMessage({
4742
5718
  message.content && /* @__PURE__ */ jsxRuntime.jsx(
4743
5719
  "div",
4744
5720
  {
4745
- className: cn7(
5721
+ className: cn8(
4746
5722
  "rounded-lg p-3",
4747
5723
  isUser ? "bg-blue-500 text-white" : "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100"
4748
5724
  ),
@@ -4772,7 +5748,7 @@ function ChatMessage({
4772
5748
  }
4773
5749
  );
4774
5750
  }
4775
- function cn8(...classes) {
5751
+ function cn9(...classes) {
4776
5752
  return classes.filter(Boolean).join(" ");
4777
5753
  }
4778
5754
  function MessageList({
@@ -4802,7 +5778,7 @@ function MessageList({
4802
5778
  "div",
4803
5779
  {
4804
5780
  ref: scrollRef,
4805
- className: cn8(
5781
+ className: cn9(
4806
5782
  "flex-1 overflow-y-auto",
4807
5783
  className
4808
5784
  ),
@@ -4895,6 +5871,7 @@ function AgentChat({
4895
5871
  isStreaming,
4896
5872
  isLoadingHistory,
4897
5873
  error,
5874
+ sessionId: currentSessionId,
4898
5875
  clarification,
4899
5876
  sendMessage,
4900
5877
  respondToClarification,
@@ -4915,8 +5892,59 @@ function AgentChat({
4915
5892
  onToolResult,
4916
5893
  onClarification
4917
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
+ );
4918
5921
  return /* @__PURE__ */ jsxRuntime.jsxs(ChatContainer, { maxHeight, className, theme, children: [
4919
- /* @__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(
4920
5948
  MessageList,
4921
5949
  {
4922
5950
  messages,
@@ -4931,7 +5959,7 @@ function AgentChat({
4931
5959
  markdownUrlTransform,
4932
5960
  markdownContentTransform,
4933
5961
  onImageClick,
4934
- sendMessage
5962
+ sendMessage: handleSend
4935
5963
  }
4936
5964
  ),
4937
5965
  clarification && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -4946,17 +5974,21 @@ function AgentChat({
4946
5974
  /* @__PURE__ */ jsxRuntime.jsx(
4947
5975
  ChatInput,
4948
5976
  {
4949
- onSend: sendMessage,
5977
+ onSend: handleSend,
4950
5978
  onStop: isStreaming ? cancel : void 0,
4951
5979
  placeholder,
4952
5980
  disabled: !!clarification || isLoadingHistory,
4953
5981
  isLoading,
4954
- 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
4955
5987
  }
4956
5988
  )
4957
5989
  ] });
4958
5990
  }
4959
- function cn9(...classes) {
5991
+ function cn10(...classes) {
4960
5992
  return classes.filter(Boolean).join(" ");
4961
5993
  }
4962
5994
  function TodoList({
@@ -4985,7 +6017,7 @@ function TodoList({
4985
6017
  return /* @__PURE__ */ jsxRuntime.jsxs(
4986
6018
  "div",
4987
6019
  {
4988
- className: cn9(
6020
+ className: cn10(
4989
6021
  "rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden",
4990
6022
  className
4991
6023
  ),
@@ -4993,7 +6025,7 @@ function TodoList({
4993
6025
  /* @__PURE__ */ jsxRuntime.jsxs(
4994
6026
  "div",
4995
6027
  {
4996
- className: cn9(
6028
+ className: cn10(
4997
6029
  "flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800/50",
4998
6030
  collapsible && "cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
4999
6031
  ),
@@ -5024,7 +6056,7 @@ function TodoList({
5024
6056
  !isCollapsed && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-gray-100 dark:divide-gray-700", children: todos.map((todo, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
5025
6057
  "li",
5026
6058
  {
5027
- className: cn9(
6059
+ className: cn10(
5028
6060
  "px-3 py-2 flex items-start gap-2 text-sm",
5029
6061
  todo.status === "in_progress" && "bg-blue-50/50 dark:bg-blue-950/20",
5030
6062
  todo.status === "completed" && "opacity-60"
@@ -5034,7 +6066,7 @@ function TodoList({
5034
6066
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
5035
6067
  "p",
5036
6068
  {
5037
- className: cn9(
6069
+ className: cn10(
5038
6070
  "text-gray-700 dark:text-gray-300",
5039
6071
  todo.status === "completed" && "line-through"
5040
6072
  ),
@@ -5071,6 +6103,7 @@ exports.CopyIcon = CopyIcon;
5071
6103
  exports.DatabaseIcon = DatabaseIcon;
5072
6104
  exports.DownloadIcon = DownloadIcon;
5073
6105
  exports.ExternalLinkIcon = ExternalLinkIcon;
6106
+ exports.FileDropZone = FileDropZone;
5074
6107
  exports.FileIcon = FileIcon;
5075
6108
  exports.FileTextIcon = FileTextIcon;
5076
6109
  exports.HelpCircleIcon = HelpCircleIcon;
@@ -5093,10 +6126,12 @@ exports.UserIcon = UserIcon;
5093
6126
  exports.XIcon = XIcon;
5094
6127
  exports.ZoomInIcon = ZoomInIcon;
5095
6128
  exports.ZoomOutIcon = ZoomOutIcon;
6129
+ exports.compressImage = compressImage;
5096
6130
  exports.createDefaultAdapter = createDefaultAdapter;
5097
6131
  exports.useAgentChat = useAgentChat;
5098
6132
  exports.useChatConfig = useChatConfig;
5099
6133
  exports.useChatConfigOptional = useChatConfigOptional;
5100
6134
  exports.useExecutionStream = useExecutionStream;
6135
+ exports.useFileAttachments = useFileAttachments;
5101
6136
  //# sourceMappingURL=index.cjs.map
5102
6137
  //# sourceMappingURL=index.cjs.map