@cadraos/sdk 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -5
- package/dist/chat/index.cjs +1180 -36
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +229 -15
- package/dist/chat/index.d.ts +229 -15
- package/dist/chat/index.js +1180 -39
- package/dist/chat/index.js.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/package.json +6 -3
package/dist/chat/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
+
var fetchEventSource = require('@microsoft/fetch-event-source');
|
|
4
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
6
|
var ReactMarkdown = require('react-markdown');
|
|
6
7
|
var remarkGfm = require('remark-gfm');
|
|
@@ -2210,7 +2211,7 @@ function resolveClient(config) {
|
|
|
2210
2211
|
}
|
|
2211
2212
|
function resolveBaseUrl(config) {
|
|
2212
2213
|
if (config.baseUrl) return config.baseUrl;
|
|
2213
|
-
return "https://api.
|
|
2214
|
+
return "https://api.cadra.io/v1";
|
|
2214
2215
|
}
|
|
2215
2216
|
async function getAuthHeaders(config) {
|
|
2216
2217
|
const headers = {
|
|
@@ -2316,7 +2317,7 @@ function createDefaultAdapter(config) {
|
|
|
2316
2317
|
};
|
|
2317
2318
|
},
|
|
2318
2319
|
// -------------------------------------------------------------------------
|
|
2319
|
-
// Attachment Methods (raw fetch —
|
|
2320
|
+
// Attachment Methods (raw fetch — CadraClient has no AttachmentsAPI yet)
|
|
2320
2321
|
// -------------------------------------------------------------------------
|
|
2321
2322
|
async getUploadUrl(input) {
|
|
2322
2323
|
return rawRequest(config, "POST", "/attachments/upload-url", {
|
|
@@ -2396,8 +2397,106 @@ function createDefaultAdapter(config) {
|
|
|
2396
2397
|
// -------------------------------------------------------------------------
|
|
2397
2398
|
// SSE Stream URL (legacy backward compat)
|
|
2398
2399
|
// -------------------------------------------------------------------------
|
|
2400
|
+
/**
|
|
2401
|
+
* @deprecated Use subscribeToStream() instead. getStreamUrl() exposes
|
|
2402
|
+
* API keys in URLs which are logged by browsers, proxies, and servers.
|
|
2403
|
+
*/
|
|
2399
2404
|
getStreamUrl(executionId) {
|
|
2405
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
2406
|
+
const warnKey = "__cadra_getStreamUrl_deprecated__";
|
|
2407
|
+
const win = window;
|
|
2408
|
+
if (!win[warnKey]) {
|
|
2409
|
+
win[warnKey] = true;
|
|
2410
|
+
console.warn(
|
|
2411
|
+
"[CadraOS SDK] getStreamUrl() is deprecated. Use subscribeToStream() instead which provides secure header-based auth. API keys in URLs are a security risk."
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2400
2415
|
return `${baseUrl}/agents/runs/${executionId}/stream`;
|
|
2416
|
+
},
|
|
2417
|
+
// -------------------------------------------------------------------------
|
|
2418
|
+
// Secure SSE Streaming (Recommended)
|
|
2419
|
+
// -------------------------------------------------------------------------
|
|
2420
|
+
subscribeToStream(executionId, callbacks) {
|
|
2421
|
+
const abortController = new AbortController();
|
|
2422
|
+
const url = `${baseUrl}/api/v1/agents/runs/${executionId}/stream`;
|
|
2423
|
+
const getHeaders = async () => {
|
|
2424
|
+
const headers = {
|
|
2425
|
+
Accept: "text/event-stream",
|
|
2426
|
+
"Cache-Control": "no-cache"
|
|
2427
|
+
};
|
|
2428
|
+
if (config.apiKey) {
|
|
2429
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
2430
|
+
} else if (config.token) {
|
|
2431
|
+
headers["Authorization"] = `Bearer ${config.token}`;
|
|
2432
|
+
} else if (config.tokenProvider) {
|
|
2433
|
+
const token = await config.tokenProvider();
|
|
2434
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
2435
|
+
}
|
|
2436
|
+
if (config.orgId !== void 0) {
|
|
2437
|
+
headers["X-Org-Id"] = String(config.orgId);
|
|
2438
|
+
}
|
|
2439
|
+
return headers;
|
|
2440
|
+
};
|
|
2441
|
+
const terminalEvents = /* @__PURE__ */ new Set(["complete", "failed", "error"]);
|
|
2442
|
+
(async () => {
|
|
2443
|
+
try {
|
|
2444
|
+
const headers = await getHeaders();
|
|
2445
|
+
callbacks.onConnectionChange?.(true);
|
|
2446
|
+
await fetchEventSource.fetchEventSource(url, {
|
|
2447
|
+
method: "GET",
|
|
2448
|
+
headers,
|
|
2449
|
+
signal: abortController.signal,
|
|
2450
|
+
openWhenHidden: true,
|
|
2451
|
+
// Keep connection open even when tab is hidden
|
|
2452
|
+
onopen: async (response) => {
|
|
2453
|
+
if (!response.ok) {
|
|
2454
|
+
const errorText = await response.text();
|
|
2455
|
+
throw new Error(`SSE connection failed: ${response.status} - ${errorText}`);
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
onmessage: (ev) => {
|
|
2459
|
+
if (!ev.event || ev.event === "") {
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
let data;
|
|
2463
|
+
try {
|
|
2464
|
+
data = ev.data ? JSON.parse(ev.data) : {};
|
|
2465
|
+
} catch {
|
|
2466
|
+
data = ev.data;
|
|
2467
|
+
}
|
|
2468
|
+
const event = {
|
|
2469
|
+
type: ev.event,
|
|
2470
|
+
id: ev.id,
|
|
2471
|
+
data
|
|
2472
|
+
};
|
|
2473
|
+
callbacks.onEvent(event);
|
|
2474
|
+
if (terminalEvents.has(ev.event)) {
|
|
2475
|
+
callbacks.onComplete?.();
|
|
2476
|
+
abortController.abort();
|
|
2477
|
+
}
|
|
2478
|
+
},
|
|
2479
|
+
onerror: (err) => {
|
|
2480
|
+
callbacks.onConnectionChange?.(false);
|
|
2481
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
2482
|
+
throw err;
|
|
2483
|
+
},
|
|
2484
|
+
onclose: () => {
|
|
2485
|
+
callbacks.onConnectionChange?.(false);
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
} catch (err) {
|
|
2489
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
callbacks.onConnectionChange?.(false);
|
|
2493
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
2494
|
+
}
|
|
2495
|
+
})();
|
|
2496
|
+
return () => {
|
|
2497
|
+
abortController.abort();
|
|
2498
|
+
callbacks.onConnectionChange?.(false);
|
|
2499
|
+
};
|
|
2401
2500
|
}
|
|
2402
2501
|
};
|
|
2403
2502
|
}
|
|
@@ -3221,8 +3320,9 @@ function useAgentChat(options) {
|
|
|
3221
3320
|
[adapter, handleExecutionLog, updateStreamingMessage, updateToolCall, handleClarification, handleComplete, handleError]
|
|
3222
3321
|
);
|
|
3223
3322
|
const sendMessage = react.useCallback(
|
|
3224
|
-
async (content) => {
|
|
3323
|
+
async (content, options2) => {
|
|
3225
3324
|
if (!content.trim()) return;
|
|
3325
|
+
const attachmentUuids = options2?.attachmentUuids;
|
|
3226
3326
|
lastMessageContentRef.current = content;
|
|
3227
3327
|
setIsLoading(true);
|
|
3228
3328
|
setError(null);
|
|
@@ -3253,7 +3353,8 @@ function useAgentChat(options) {
|
|
|
3253
3353
|
agentId,
|
|
3254
3354
|
teamId,
|
|
3255
3355
|
sessionId: currentSessionId ?? void 0,
|
|
3256
|
-
content: content.trim()
|
|
3356
|
+
content: content.trim(),
|
|
3357
|
+
attachmentUuids
|
|
3257
3358
|
});
|
|
3258
3359
|
console.log("[useAgentChat] Execution started:", result);
|
|
3259
3360
|
if (!currentSessionId && result.sessionId) {
|
|
@@ -3462,6 +3563,222 @@ function useAgentChat(options) {
|
|
|
3462
3563
|
loadSession
|
|
3463
3564
|
};
|
|
3464
3565
|
}
|
|
3566
|
+
|
|
3567
|
+
// src/chat/utils/compress-image.ts
|
|
3568
|
+
var DEFAULT_TARGET_SIZE_KB = 150;
|
|
3569
|
+
var MAX_DIMENSION = 1920;
|
|
3570
|
+
var QUALITY_START = 0.85;
|
|
3571
|
+
var QUALITY_STEP = 0.05;
|
|
3572
|
+
var QUALITY_MIN = 0.1;
|
|
3573
|
+
function loadImage(file) {
|
|
3574
|
+
return new Promise((resolve, reject) => {
|
|
3575
|
+
const img = new Image();
|
|
3576
|
+
const url = URL.createObjectURL(file);
|
|
3577
|
+
img.onload = () => {
|
|
3578
|
+
URL.revokeObjectURL(url);
|
|
3579
|
+
resolve(img);
|
|
3580
|
+
};
|
|
3581
|
+
img.onerror = () => {
|
|
3582
|
+
URL.revokeObjectURL(url);
|
|
3583
|
+
reject(new Error(`Failed to load image: ${file.name}`));
|
|
3584
|
+
};
|
|
3585
|
+
img.src = url;
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
function getScaledDimensions(width, height, maxDim) {
|
|
3589
|
+
if (width <= maxDim && height <= maxDim) {
|
|
3590
|
+
return { width, height };
|
|
3591
|
+
}
|
|
3592
|
+
const ratio = Math.min(maxDim / width, maxDim / height);
|
|
3593
|
+
return {
|
|
3594
|
+
width: Math.round(width * ratio),
|
|
3595
|
+
height: Math.round(height * ratio)
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
function canvasToBlob(img, width, height, format, quality) {
|
|
3599
|
+
return new Promise((resolve, reject) => {
|
|
3600
|
+
const canvas = document.createElement("canvas");
|
|
3601
|
+
canvas.width = width;
|
|
3602
|
+
canvas.height = height;
|
|
3603
|
+
const ctx = canvas.getContext("2d");
|
|
3604
|
+
if (!ctx) {
|
|
3605
|
+
reject(new Error("Failed to get canvas 2d context"));
|
|
3606
|
+
return;
|
|
3607
|
+
}
|
|
3608
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
3609
|
+
canvas.toBlob(
|
|
3610
|
+
(blob) => {
|
|
3611
|
+
if (!blob) {
|
|
3612
|
+
reject(new Error("Canvas toBlob returned null"));
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
resolve(blob);
|
|
3616
|
+
},
|
|
3617
|
+
format,
|
|
3618
|
+
quality
|
|
3619
|
+
);
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
var webpSupported = null;
|
|
3623
|
+
function supportsWebP() {
|
|
3624
|
+
if (webpSupported !== null) return webpSupported;
|
|
3625
|
+
try {
|
|
3626
|
+
const canvas = document.createElement("canvas");
|
|
3627
|
+
canvas.width = 1;
|
|
3628
|
+
canvas.height = 1;
|
|
3629
|
+
webpSupported = canvas.toDataURL("image/webp").startsWith("data:image/webp");
|
|
3630
|
+
} catch {
|
|
3631
|
+
webpSupported = false;
|
|
3632
|
+
}
|
|
3633
|
+
return webpSupported;
|
|
3634
|
+
}
|
|
3635
|
+
function getOutputFormat(_originalType) {
|
|
3636
|
+
if (supportsWebP()) {
|
|
3637
|
+
return { mime: "image/webp", ext: ".webp" };
|
|
3638
|
+
}
|
|
3639
|
+
return { mime: "image/jpeg", ext: ".jpg" };
|
|
3640
|
+
}
|
|
3641
|
+
function replaceExtension(filename, newExt) {
|
|
3642
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
3643
|
+
if (dotIndex === -1) return filename + newExt;
|
|
3644
|
+
return filename.substring(0, dotIndex) + newExt;
|
|
3645
|
+
}
|
|
3646
|
+
async function compressImage(file, targetSizeKB = DEFAULT_TARGET_SIZE_KB) {
|
|
3647
|
+
if (!file.type.startsWith("image/")) {
|
|
3648
|
+
return file;
|
|
3649
|
+
}
|
|
3650
|
+
if (file.type === "image/svg+xml") {
|
|
3651
|
+
return file;
|
|
3652
|
+
}
|
|
3653
|
+
if (file.type === "image/gif") {
|
|
3654
|
+
return file;
|
|
3655
|
+
}
|
|
3656
|
+
const targetBytes = targetSizeKB * 1024;
|
|
3657
|
+
if (file.size <= targetBytes) {
|
|
3658
|
+
return file;
|
|
3659
|
+
}
|
|
3660
|
+
const img = await loadImage(file);
|
|
3661
|
+
const { width, height } = getScaledDimensions(
|
|
3662
|
+
img.naturalWidth,
|
|
3663
|
+
img.naturalHeight,
|
|
3664
|
+
MAX_DIMENSION
|
|
3665
|
+
);
|
|
3666
|
+
const { mime, ext } = getOutputFormat(file.type);
|
|
3667
|
+
let quality = QUALITY_START;
|
|
3668
|
+
let blob = null;
|
|
3669
|
+
while (quality >= QUALITY_MIN) {
|
|
3670
|
+
blob = await canvasToBlob(img, width, height, mime, quality);
|
|
3671
|
+
if (blob.size <= targetBytes) {
|
|
3672
|
+
break;
|
|
3673
|
+
}
|
|
3674
|
+
quality -= QUALITY_STEP;
|
|
3675
|
+
}
|
|
3676
|
+
if (!blob) {
|
|
3677
|
+
blob = await canvasToBlob(img, width, height, mime, QUALITY_MIN);
|
|
3678
|
+
}
|
|
3679
|
+
if (blob.size >= file.size) {
|
|
3680
|
+
return file;
|
|
3681
|
+
}
|
|
3682
|
+
const newFilename = replaceExtension(file.name, ext);
|
|
3683
|
+
return new File([blob], newFilename, {
|
|
3684
|
+
type: mime,
|
|
3685
|
+
lastModified: Date.now()
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
// src/chat/hooks/useFileAttachments.ts
|
|
3690
|
+
function useFileAttachments(options) {
|
|
3691
|
+
const { onUpload, onConfirmUpload, onSubmitUpload, onError, maxFiles = 10, maxTotalSizeMB = 100 } = options;
|
|
3692
|
+
const [files, setFiles] = react.useState([]);
|
|
3693
|
+
const filesRef = react.useRef(files);
|
|
3694
|
+
filesRef.current = files;
|
|
3695
|
+
const onFilesChange = react.useCallback((newFiles) => {
|
|
3696
|
+
filesRef.current = newFiles;
|
|
3697
|
+
setFiles(newFiles);
|
|
3698
|
+
}, []);
|
|
3699
|
+
const removeFile = react.useCallback((uuid) => {
|
|
3700
|
+
setFiles((prev) => {
|
|
3701
|
+
const file = prev.find((f) => f.uuid === uuid);
|
|
3702
|
+
if (file?.previewUrl) {
|
|
3703
|
+
URL.revokeObjectURL(file.previewUrl);
|
|
3704
|
+
}
|
|
3705
|
+
return prev.filter((f) => f.uuid !== uuid);
|
|
3706
|
+
});
|
|
3707
|
+
}, []);
|
|
3708
|
+
const getReadyUuids = react.useCallback(() => {
|
|
3709
|
+
return filesRef.current.filter((f) => f.status === "ready").map((f) => f.uuid);
|
|
3710
|
+
}, []);
|
|
3711
|
+
const getReadyUrls = react.useCallback(() => {
|
|
3712
|
+
return filesRef.current.filter((f) => f.status === "ready").map((f) => f.uuid);
|
|
3713
|
+
}, []);
|
|
3714
|
+
const prepareForSubmit = react.useCallback(async () => {
|
|
3715
|
+
const readyFiles = filesRef.current.filter((f) => f.status === "ready");
|
|
3716
|
+
console.log("[useFileAttachments] prepareForSubmit - readyFiles:", readyFiles.length, "hasSubmitUpload:", !!onSubmitUpload);
|
|
3717
|
+
if (readyFiles.length === 0) return [];
|
|
3718
|
+
if (!onSubmitUpload) {
|
|
3719
|
+
return readyFiles.map((f) => f.uuid);
|
|
3720
|
+
}
|
|
3721
|
+
const aiUuids = [];
|
|
3722
|
+
for (const attachment of readyFiles) {
|
|
3723
|
+
try {
|
|
3724
|
+
let fileToSend = attachment.file;
|
|
3725
|
+
if (attachment.category === "image") {
|
|
3726
|
+
try {
|
|
3727
|
+
fileToSend = await compressImage(attachment.file);
|
|
3728
|
+
} catch {
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
console.log("[useFileAttachments] Uploading to AI service:", attachment.file.name, "localUuid:", attachment.uuid.substring(0, 40));
|
|
3732
|
+
const aiUuid = await onSubmitUpload(
|
|
3733
|
+
fileToSend,
|
|
3734
|
+
attachment.uuid,
|
|
3735
|
+
{ filename: fileToSend.name, mimeType: fileToSend.type, size: fileToSend.size }
|
|
3736
|
+
);
|
|
3737
|
+
console.log("[useFileAttachments] AI service returned UUID:", aiUuid);
|
|
3738
|
+
aiUuids.push(aiUuid);
|
|
3739
|
+
} catch (err) {
|
|
3740
|
+
const msg = err instanceof Error ? err.message : "Submit upload failed";
|
|
3741
|
+
console.error("[useFileAttachments] Submit upload failed for:", attachment.file.name, msg);
|
|
3742
|
+
onError?.(msg);
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
console.log("[useFileAttachments] prepareForSubmit result:", aiUuids);
|
|
3746
|
+
return aiUuids;
|
|
3747
|
+
}, [onSubmitUpload, onError]);
|
|
3748
|
+
const clear = react.useCallback(() => {
|
|
3749
|
+
for (const file of filesRef.current) {
|
|
3750
|
+
if (file.previewUrl) {
|
|
3751
|
+
URL.revokeObjectURL(file.previewUrl);
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
setFiles([]);
|
|
3755
|
+
}, []);
|
|
3756
|
+
const isUploading = files.some(
|
|
3757
|
+
(f) => f.status === "uploading" || f.status === "pending" || f.status === "processing"
|
|
3758
|
+
);
|
|
3759
|
+
const hasReadyFiles = files.some((f) => f.status === "ready");
|
|
3760
|
+
const dropZoneProps = {
|
|
3761
|
+
files,
|
|
3762
|
+
onFilesChange,
|
|
3763
|
+
onUpload,
|
|
3764
|
+
onConfirmUpload,
|
|
3765
|
+
onError,
|
|
3766
|
+
maxFiles,
|
|
3767
|
+
maxTotalSizeMB
|
|
3768
|
+
};
|
|
3769
|
+
return {
|
|
3770
|
+
files,
|
|
3771
|
+
dropZoneProps,
|
|
3772
|
+
removeFile,
|
|
3773
|
+
getReadyUuids,
|
|
3774
|
+
getReadyUrls,
|
|
3775
|
+
prepareForSubmit,
|
|
3776
|
+
clear,
|
|
3777
|
+
isUploading,
|
|
3778
|
+
hasReadyFiles,
|
|
3779
|
+
fileCount: files.length
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3465
3782
|
function cn(...classes) {
|
|
3466
3783
|
return classes.filter(Boolean).join(" ");
|
|
3467
3784
|
}
|
|
@@ -4111,6 +4428,51 @@ function ChatContainer({
|
|
|
4111
4428
|
function cn3(...classes) {
|
|
4112
4429
|
return classes.filter(Boolean).join(" ");
|
|
4113
4430
|
}
|
|
4431
|
+
function formatFileSize(bytes) {
|
|
4432
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4433
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
4434
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4435
|
+
}
|
|
4436
|
+
function AttachmentChip({ attachment, onRemove }) {
|
|
4437
|
+
const isLoading = attachment.status === "uploading" || attachment.status === "pending" || attachment.status === "processing";
|
|
4438
|
+
const isError = attachment.status === "error" || attachment.status === "failed";
|
|
4439
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4440
|
+
"div",
|
|
4441
|
+
{
|
|
4442
|
+
className: cn3(
|
|
4443
|
+
"inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs max-w-[180px]",
|
|
4444
|
+
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"
|
|
4445
|
+
),
|
|
4446
|
+
children: [
|
|
4447
|
+
attachment.previewUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4448
|
+
"img",
|
|
4449
|
+
{
|
|
4450
|
+
src: attachment.previewUrl,
|
|
4451
|
+
alt: "",
|
|
4452
|
+
className: "w-5 h-5 rounded object-cover flex-shrink-0"
|
|
4453
|
+
}
|
|
4454
|
+
),
|
|
4455
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: attachment.file.name }),
|
|
4456
|
+
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) }),
|
|
4457
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4458
|
+
"button",
|
|
4459
|
+
{
|
|
4460
|
+
type: "button",
|
|
4461
|
+
onClick: (e) => {
|
|
4462
|
+
e.preventDefault();
|
|
4463
|
+
e.stopPropagation();
|
|
4464
|
+
onRemove(attachment.uuid);
|
|
4465
|
+
},
|
|
4466
|
+
className: "flex-shrink-0 p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",
|
|
4467
|
+
"aria-label": `Remove ${attachment.file.name}`,
|
|
4468
|
+
disabled: isLoading,
|
|
4469
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(XIcon, { className: "h-3 w-3" })
|
|
4470
|
+
}
|
|
4471
|
+
)
|
|
4472
|
+
]
|
|
4473
|
+
}
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4114
4476
|
function ChatInput({
|
|
4115
4477
|
onSend,
|
|
4116
4478
|
onStop,
|
|
@@ -4118,10 +4480,15 @@ function ChatInput({
|
|
|
4118
4480
|
disabled = false,
|
|
4119
4481
|
isLoading = false,
|
|
4120
4482
|
enableAttachments = false,
|
|
4483
|
+
attachments = [],
|
|
4484
|
+
onRemoveAttachment,
|
|
4485
|
+
dropZoneRef,
|
|
4486
|
+
isUploadingAttachments = false,
|
|
4121
4487
|
className
|
|
4122
4488
|
}) {
|
|
4123
4489
|
const [content, setContent] = react.useState("");
|
|
4124
4490
|
const textareaRef = react.useRef(null);
|
|
4491
|
+
const hasAttachments = enableAttachments && attachments.length > 0;
|
|
4125
4492
|
react.useEffect(() => {
|
|
4126
4493
|
const textarea = textareaRef.current;
|
|
4127
4494
|
if (textarea) {
|
|
@@ -4131,10 +4498,10 @@ function ChatInput({
|
|
|
4131
4498
|
}, [content]);
|
|
4132
4499
|
const handleSubmit = react.useCallback(async () => {
|
|
4133
4500
|
const trimmedContent = content.trim();
|
|
4134
|
-
if (!trimmedContent || disabled || isLoading) return;
|
|
4501
|
+
if (!trimmedContent && !hasAttachments || disabled || isLoading || isUploadingAttachments) return;
|
|
4135
4502
|
setContent("");
|
|
4136
4503
|
await onSend(trimmedContent);
|
|
4137
|
-
}, [content, disabled, isLoading, onSend]);
|
|
4504
|
+
}, [content, hasAttachments, disabled, isLoading, isUploadingAttachments, onSend]);
|
|
4138
4505
|
const handleKeyDown = react.useCallback(
|
|
4139
4506
|
(e) => {
|
|
4140
4507
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
@@ -4147,7 +4514,10 @@ function ChatInput({
|
|
|
4147
4514
|
const handleStopClick = react.useCallback(() => {
|
|
4148
4515
|
onStop?.();
|
|
4149
4516
|
}, [onStop]);
|
|
4150
|
-
const
|
|
4517
|
+
const handleAttachClick = react.useCallback(() => {
|
|
4518
|
+
dropZoneRef?.current?.openFileDialog();
|
|
4519
|
+
}, [dropZoneRef]);
|
|
4520
|
+
const canSend = (content.trim().length > 0 || hasAttachments) && !disabled && !isLoading && !isUploadingAttachments;
|
|
4151
4521
|
const showStopButton = isLoading && onStop;
|
|
4152
4522
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4153
4523
|
"div",
|
|
@@ -4157,7 +4527,34 @@ function ChatInput({
|
|
|
4157
4527
|
className
|
|
4158
4528
|
),
|
|
4159
4529
|
children: [
|
|
4530
|
+
hasAttachments && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 mb-2", children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4531
|
+
AttachmentChip,
|
|
4532
|
+
{
|
|
4533
|
+
attachment,
|
|
4534
|
+
onRemove: onRemoveAttachment ?? (() => {
|
|
4535
|
+
})
|
|
4536
|
+
},
|
|
4537
|
+
attachment.uuid
|
|
4538
|
+
)) }),
|
|
4160
4539
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
|
|
4540
|
+
enableAttachments && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4541
|
+
"button",
|
|
4542
|
+
{
|
|
4543
|
+
type: "button",
|
|
4544
|
+
onClick: handleAttachClick,
|
|
4545
|
+
disabled: disabled || isLoading,
|
|
4546
|
+
className: cn3(
|
|
4547
|
+
"flex-shrink-0 p-3 rounded-lg transition-colors",
|
|
4548
|
+
"text-gray-500 dark:text-gray-400",
|
|
4549
|
+
"hover:bg-gray-100 dark:hover:bg-gray-700",
|
|
4550
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
4551
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
4552
|
+
),
|
|
4553
|
+
"aria-label": "Attach files",
|
|
4554
|
+
title: "Attach files",
|
|
4555
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(PaperclipIcon, { className: "h-5 w-5" })
|
|
4556
|
+
}
|
|
4557
|
+
),
|
|
4161
4558
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4162
4559
|
"textarea",
|
|
4163
4560
|
{
|
|
@@ -4165,7 +4562,7 @@ function ChatInput({
|
|
|
4165
4562
|
value: content,
|
|
4166
4563
|
onChange: (e) => setContent(e.target.value),
|
|
4167
4564
|
onKeyDown: handleKeyDown,
|
|
4168
|
-
placeholder,
|
|
4565
|
+
placeholder: hasAttachments && !content ? `${attachments.length} file${attachments.length > 1 ? "s" : ""} attached. Add a message or press Enter to send.` : placeholder,
|
|
4169
4566
|
disabled,
|
|
4170
4567
|
rows: 1,
|
|
4171
4568
|
className: cn3(
|
|
@@ -4215,9 +4612,664 @@ function ChatInput({
|
|
|
4215
4612
|
}
|
|
4216
4613
|
);
|
|
4217
4614
|
}
|
|
4615
|
+
function Upload({ className }) {
|
|
4616
|
+
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: [
|
|
4617
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
4618
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "17 8 12 3 7 8" }),
|
|
4619
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
|
|
4620
|
+
] });
|
|
4621
|
+
}
|
|
4622
|
+
function X({ className }) {
|
|
4623
|
+
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: [
|
|
4624
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
|
|
4625
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
|
|
4626
|
+
] });
|
|
4627
|
+
}
|
|
4628
|
+
function FileText({ className }) {
|
|
4629
|
+
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: [
|
|
4630
|
+
/* @__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" }),
|
|
4631
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
|
|
4632
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", x2: "8", y1: "13", y2: "13" }),
|
|
4633
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", x2: "8", y1: "17", y2: "17" }),
|
|
4634
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", x2: "8", y1: "9", y2: "9" })
|
|
4635
|
+
] });
|
|
4636
|
+
}
|
|
4637
|
+
function ImageIcon2({ className }) {
|
|
4638
|
+
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: [
|
|
4639
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
4640
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "9", r: "2" }),
|
|
4641
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
4642
|
+
] });
|
|
4643
|
+
}
|
|
4644
|
+
function Music({ className }) {
|
|
4645
|
+
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: [
|
|
4646
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 18V5l12-2v13" }),
|
|
4647
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "18", r: "3" }),
|
|
4648
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "18", cy: "16", r: "3" })
|
|
4649
|
+
] });
|
|
4650
|
+
}
|
|
4651
|
+
function Loader2({ className }) {
|
|
4652
|
+
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" }) });
|
|
4653
|
+
}
|
|
4654
|
+
function FileIcon2({ className }) {
|
|
4655
|
+
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: [
|
|
4656
|
+
/* @__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" }),
|
|
4657
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
|
|
4658
|
+
] });
|
|
4659
|
+
}
|
|
4660
|
+
function RotateCw({ className }) {
|
|
4661
|
+
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: [
|
|
4662
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
|
|
4663
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 3v5h-5" })
|
|
4664
|
+
] });
|
|
4665
|
+
}
|
|
4218
4666
|
function cn4(...classes) {
|
|
4219
4667
|
return classes.filter(Boolean).join(" ");
|
|
4220
4668
|
}
|
|
4669
|
+
var SUPPORTED_TYPES = {
|
|
4670
|
+
document: {
|
|
4671
|
+
mimeTypes: [
|
|
4672
|
+
"application/pdf",
|
|
4673
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
4674
|
+
"application/msword",
|
|
4675
|
+
"text/plain",
|
|
4676
|
+
"text/markdown",
|
|
4677
|
+
"text/csv",
|
|
4678
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
4679
|
+
"application/vnd.ms-excel"
|
|
4680
|
+
],
|
|
4681
|
+
extensions: [
|
|
4682
|
+
".pdf",
|
|
4683
|
+
".docx",
|
|
4684
|
+
".doc",
|
|
4685
|
+
".txt",
|
|
4686
|
+
".md",
|
|
4687
|
+
".csv",
|
|
4688
|
+
".xlsx",
|
|
4689
|
+
".xls"
|
|
4690
|
+
],
|
|
4691
|
+
maxSizeMB: 25,
|
|
4692
|
+
label: "Documents"
|
|
4693
|
+
},
|
|
4694
|
+
image: {
|
|
4695
|
+
mimeTypes: [
|
|
4696
|
+
"image/png",
|
|
4697
|
+
"image/jpeg",
|
|
4698
|
+
"image/gif",
|
|
4699
|
+
"image/webp",
|
|
4700
|
+
"image/svg+xml"
|
|
4701
|
+
],
|
|
4702
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"],
|
|
4703
|
+
maxSizeMB: 10,
|
|
4704
|
+
label: "Images"
|
|
4705
|
+
},
|
|
4706
|
+
audio: {
|
|
4707
|
+
mimeTypes: [
|
|
4708
|
+
"audio/mpeg",
|
|
4709
|
+
"audio/wav",
|
|
4710
|
+
"audio/mp4",
|
|
4711
|
+
"audio/ogg",
|
|
4712
|
+
"audio/webm"
|
|
4713
|
+
],
|
|
4714
|
+
extensions: [".mp3", ".wav", ".m4a", ".ogg", ".webm"],
|
|
4715
|
+
maxSizeMB: 50,
|
|
4716
|
+
label: "Audio"
|
|
4717
|
+
}
|
|
4718
|
+
};
|
|
4719
|
+
var MAX_FILES_PER_DROP = 10;
|
|
4720
|
+
var MAX_TOTAL_SIZE_MB = 100;
|
|
4721
|
+
function categorizeFile(mimeType) {
|
|
4722
|
+
for (const [category, config] of Object.entries(SUPPORTED_TYPES)) {
|
|
4723
|
+
if (config.mimeTypes.includes(mimeType)) {
|
|
4724
|
+
return category;
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
return null;
|
|
4728
|
+
}
|
|
4729
|
+
function validateFile(file) {
|
|
4730
|
+
const category = categorizeFile(file.type);
|
|
4731
|
+
if (!category) {
|
|
4732
|
+
return {
|
|
4733
|
+
valid: false,
|
|
4734
|
+
error: `Unsupported file type: ${file.type || file.name.split(".").pop()}`
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
const config = SUPPORTED_TYPES[category];
|
|
4738
|
+
const maxSizeBytes = config.maxSizeMB * 1024 * 1024;
|
|
4739
|
+
if (file.size > maxSizeBytes) {
|
|
4740
|
+
return {
|
|
4741
|
+
valid: false,
|
|
4742
|
+
error: `${file.name} exceeds ${config.maxSizeMB}MB limit for ${config.label.toLowerCase()}`
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4745
|
+
return { valid: true, category };
|
|
4746
|
+
}
|
|
4747
|
+
function formatFileSize2(bytes) {
|
|
4748
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4749
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
4750
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4751
|
+
}
|
|
4752
|
+
function getCategoryIcon(category) {
|
|
4753
|
+
switch (category) {
|
|
4754
|
+
case "document":
|
|
4755
|
+
return FileText;
|
|
4756
|
+
case "image":
|
|
4757
|
+
return ImageIcon2;
|
|
4758
|
+
case "audio":
|
|
4759
|
+
return Music;
|
|
4760
|
+
default:
|
|
4761
|
+
return FileIcon2;
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
var FileDropZone = react.forwardRef(
|
|
4765
|
+
function FileDropZone2({
|
|
4766
|
+
onFilesChange,
|
|
4767
|
+
files,
|
|
4768
|
+
onUpload,
|
|
4769
|
+
onConfirmUpload,
|
|
4770
|
+
onError,
|
|
4771
|
+
maxFiles = MAX_FILES_PER_DROP,
|
|
4772
|
+
maxTotalSizeMB = MAX_TOTAL_SIZE_MB,
|
|
4773
|
+
disabled = false,
|
|
4774
|
+
className,
|
|
4775
|
+
accept,
|
|
4776
|
+
compact = false,
|
|
4777
|
+
children
|
|
4778
|
+
}, ref) {
|
|
4779
|
+
const [isDragging, setIsDragging] = react.useState(false);
|
|
4780
|
+
const fileInputRef = react.useRef(null);
|
|
4781
|
+
const dropZoneRef = react.useRef(null);
|
|
4782
|
+
const dragCounter = react.useRef(0);
|
|
4783
|
+
react.useImperativeHandle(
|
|
4784
|
+
ref,
|
|
4785
|
+
() => ({
|
|
4786
|
+
openFileDialog: () => {
|
|
4787
|
+
if (!disabled) {
|
|
4788
|
+
fileInputRef.current?.click();
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
}),
|
|
4792
|
+
[disabled]
|
|
4793
|
+
);
|
|
4794
|
+
const currentTotalSize = files.reduce((sum, f) => sum + f.file.size, 0);
|
|
4795
|
+
const uploadFile = react.useCallback(
|
|
4796
|
+
async (file, _tempId) => {
|
|
4797
|
+
const result = await onUpload(file, {
|
|
4798
|
+
filename: file.name,
|
|
4799
|
+
mimeType: file.type,
|
|
4800
|
+
size: file.size
|
|
4801
|
+
});
|
|
4802
|
+
if (result.uploadUrl) {
|
|
4803
|
+
const uploadResponse = await fetch(result.uploadUrl, {
|
|
4804
|
+
method: "PUT",
|
|
4805
|
+
body: file,
|
|
4806
|
+
headers: { "Content-Type": file.type }
|
|
4807
|
+
});
|
|
4808
|
+
if (!uploadResponse.ok) {
|
|
4809
|
+
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
if (onConfirmUpload) {
|
|
4813
|
+
await onConfirmUpload(result.uuid);
|
|
4814
|
+
}
|
|
4815
|
+
return result.uuid;
|
|
4816
|
+
},
|
|
4817
|
+
[onUpload, onConfirmUpload]
|
|
4818
|
+
);
|
|
4819
|
+
const handleFiles = react.useCallback(
|
|
4820
|
+
async (selectedFiles) => {
|
|
4821
|
+
if (disabled) return;
|
|
4822
|
+
if (files.length + selectedFiles.length > maxFiles) {
|
|
4823
|
+
onError?.(`Maximum ${maxFiles} files allowed`);
|
|
4824
|
+
return;
|
|
4825
|
+
}
|
|
4826
|
+
const validFiles = [];
|
|
4827
|
+
let newTotalSize = currentTotalSize;
|
|
4828
|
+
for (const file of selectedFiles) {
|
|
4829
|
+
const validation = validateFile(file);
|
|
4830
|
+
if (!validation.valid) {
|
|
4831
|
+
onError?.(validation.error);
|
|
4832
|
+
continue;
|
|
4833
|
+
}
|
|
4834
|
+
newTotalSize += file.size;
|
|
4835
|
+
if (newTotalSize > maxTotalSizeMB * 1024 * 1024) {
|
|
4836
|
+
onError?.(`Total upload size exceeds ${maxTotalSizeMB}MB`);
|
|
4837
|
+
break;
|
|
4838
|
+
}
|
|
4839
|
+
validFiles.push({ file, category: validation.category });
|
|
4840
|
+
}
|
|
4841
|
+
if (validFiles.length === 0) return;
|
|
4842
|
+
const processedFiles = [];
|
|
4843
|
+
for (const entry of validFiles) {
|
|
4844
|
+
if (entry.category === "image") {
|
|
4845
|
+
try {
|
|
4846
|
+
const compressed = await compressImage(entry.file);
|
|
4847
|
+
processedFiles.push({ file: compressed, category: entry.category });
|
|
4848
|
+
} catch (err) {
|
|
4849
|
+
console.warn("Image compression failed, using original:", err);
|
|
4850
|
+
processedFiles.push(entry);
|
|
4851
|
+
}
|
|
4852
|
+
} else {
|
|
4853
|
+
processedFiles.push(entry);
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
const newAttachments = processedFiles.map(
|
|
4857
|
+
({ file, category }) => {
|
|
4858
|
+
const tempId = `temp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
4859
|
+
return {
|
|
4860
|
+
uuid: tempId,
|
|
4861
|
+
file,
|
|
4862
|
+
status: "pending",
|
|
4863
|
+
progress: 0,
|
|
4864
|
+
category,
|
|
4865
|
+
previewUrl: category === "image" ? URL.createObjectURL(file) : void 0
|
|
4866
|
+
};
|
|
4867
|
+
}
|
|
4868
|
+
);
|
|
4869
|
+
let currentFiles = [...files, ...newAttachments];
|
|
4870
|
+
onFilesChange(currentFiles);
|
|
4871
|
+
for (const attachment of newAttachments) {
|
|
4872
|
+
try {
|
|
4873
|
+
currentFiles = currentFiles.map(
|
|
4874
|
+
(f) => f.uuid === attachment.uuid ? { ...f, status: "uploading", progress: 10 } : f
|
|
4875
|
+
);
|
|
4876
|
+
onFilesChange(currentFiles);
|
|
4877
|
+
const realUuid = await uploadFile(attachment.file, attachment.uuid);
|
|
4878
|
+
currentFiles = currentFiles.map(
|
|
4879
|
+
(f) => f.uuid === attachment.uuid ? {
|
|
4880
|
+
...f,
|
|
4881
|
+
uuid: realUuid,
|
|
4882
|
+
status: "ready",
|
|
4883
|
+
progress: 100,
|
|
4884
|
+
hasThumbnail: f.category === "image"
|
|
4885
|
+
} : f
|
|
4886
|
+
);
|
|
4887
|
+
onFilesChange(currentFiles);
|
|
4888
|
+
} catch (error) {
|
|
4889
|
+
console.error("Upload error:", error);
|
|
4890
|
+
currentFiles = currentFiles.map(
|
|
4891
|
+
(f) => f.uuid === attachment.uuid ? {
|
|
4892
|
+
...f,
|
|
4893
|
+
status: "error",
|
|
4894
|
+
error: error instanceof Error ? error.message : "Upload failed"
|
|
4895
|
+
} : f
|
|
4896
|
+
);
|
|
4897
|
+
onFilesChange(currentFiles);
|
|
4898
|
+
onError?.(`Failed to upload ${attachment.file.name}`);
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
},
|
|
4902
|
+
[
|
|
4903
|
+
disabled,
|
|
4904
|
+
files,
|
|
4905
|
+
maxFiles,
|
|
4906
|
+
currentTotalSize,
|
|
4907
|
+
maxTotalSizeMB,
|
|
4908
|
+
onFilesChange,
|
|
4909
|
+
uploadFile,
|
|
4910
|
+
onError
|
|
4911
|
+
]
|
|
4912
|
+
);
|
|
4913
|
+
const handleRemove = react.useCallback(
|
|
4914
|
+
(uuid) => {
|
|
4915
|
+
const file = files.find((f) => f.uuid === uuid);
|
|
4916
|
+
if (file?.previewUrl) {
|
|
4917
|
+
URL.revokeObjectURL(file.previewUrl);
|
|
4918
|
+
}
|
|
4919
|
+
onFilesChange(files.filter((f) => f.uuid !== uuid));
|
|
4920
|
+
},
|
|
4921
|
+
[files, onFilesChange]
|
|
4922
|
+
);
|
|
4923
|
+
const handleRetry = react.useCallback(
|
|
4924
|
+
async (uuid) => {
|
|
4925
|
+
const attachment = files.find((f) => f.uuid === uuid);
|
|
4926
|
+
if (!attachment || attachment.status !== "error") return;
|
|
4927
|
+
let currentFiles = files.map(
|
|
4928
|
+
(f) => f.uuid === uuid ? { ...f, status: "pending", progress: 0, error: void 0 } : f
|
|
4929
|
+
);
|
|
4930
|
+
onFilesChange(currentFiles);
|
|
4931
|
+
try {
|
|
4932
|
+
currentFiles = currentFiles.map(
|
|
4933
|
+
(f) => f.uuid === uuid ? { ...f, status: "uploading", progress: 10 } : f
|
|
4934
|
+
);
|
|
4935
|
+
onFilesChange(currentFiles);
|
|
4936
|
+
const realUuid = await uploadFile(attachment.file, uuid);
|
|
4937
|
+
currentFiles = currentFiles.map(
|
|
4938
|
+
(f) => f.uuid === uuid ? {
|
|
4939
|
+
...f,
|
|
4940
|
+
uuid: realUuid,
|
|
4941
|
+
status: "ready",
|
|
4942
|
+
progress: 100,
|
|
4943
|
+
hasThumbnail: f.category === "image"
|
|
4944
|
+
} : f
|
|
4945
|
+
);
|
|
4946
|
+
onFilesChange(currentFiles);
|
|
4947
|
+
} catch (error) {
|
|
4948
|
+
console.error("Retry upload error:", error);
|
|
4949
|
+
currentFiles = currentFiles.map(
|
|
4950
|
+
(f) => f.uuid === uuid ? {
|
|
4951
|
+
...f,
|
|
4952
|
+
status: "error",
|
|
4953
|
+
error: error instanceof Error ? error.message : "Upload failed"
|
|
4954
|
+
} : f
|
|
4955
|
+
);
|
|
4956
|
+
onFilesChange(currentFiles);
|
|
4957
|
+
onError?.(`Failed to upload ${attachment.file.name}`);
|
|
4958
|
+
}
|
|
4959
|
+
},
|
|
4960
|
+
[files, onFilesChange, uploadFile, onError]
|
|
4961
|
+
);
|
|
4962
|
+
const hasDraggedFiles = react.useCallback((e) => {
|
|
4963
|
+
if (e.dataTransfer.types) {
|
|
4964
|
+
for (const type of e.dataTransfer.types) {
|
|
4965
|
+
if (type === "Files") return true;
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
return false;
|
|
4969
|
+
}, []);
|
|
4970
|
+
const handleDragEnter = react.useCallback(
|
|
4971
|
+
(e) => {
|
|
4972
|
+
e.preventDefault();
|
|
4973
|
+
e.stopPropagation();
|
|
4974
|
+
if (!hasDraggedFiles(e)) return;
|
|
4975
|
+
dragCounter.current++;
|
|
4976
|
+
if (!disabled && dragCounter.current === 1) {
|
|
4977
|
+
setIsDragging(true);
|
|
4978
|
+
}
|
|
4979
|
+
},
|
|
4980
|
+
[disabled, hasDraggedFiles]
|
|
4981
|
+
);
|
|
4982
|
+
const handleDragOver = react.useCallback(
|
|
4983
|
+
(e) => {
|
|
4984
|
+
e.preventDefault();
|
|
4985
|
+
e.stopPropagation();
|
|
4986
|
+
if (hasDraggedFiles(e)) {
|
|
4987
|
+
e.dataTransfer.dropEffect = disabled ? "none" : "copy";
|
|
4988
|
+
}
|
|
4989
|
+
},
|
|
4990
|
+
[disabled, hasDraggedFiles]
|
|
4991
|
+
);
|
|
4992
|
+
const handleDragLeave = react.useCallback((e) => {
|
|
4993
|
+
e.preventDefault();
|
|
4994
|
+
e.stopPropagation();
|
|
4995
|
+
if (dragCounter.current > 0) {
|
|
4996
|
+
dragCounter.current--;
|
|
4997
|
+
if (dragCounter.current === 0) {
|
|
4998
|
+
setIsDragging(false);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
}, []);
|
|
5002
|
+
const handleDrop = react.useCallback(
|
|
5003
|
+
(e) => {
|
|
5004
|
+
e.preventDefault();
|
|
5005
|
+
e.stopPropagation();
|
|
5006
|
+
dragCounter.current = 0;
|
|
5007
|
+
setIsDragging(false);
|
|
5008
|
+
if (disabled) return;
|
|
5009
|
+
const droppedFiles = Array.from(e.dataTransfer.files);
|
|
5010
|
+
if (droppedFiles.length > 0) {
|
|
5011
|
+
handleFiles(droppedFiles);
|
|
5012
|
+
}
|
|
5013
|
+
},
|
|
5014
|
+
[handleFiles, disabled]
|
|
5015
|
+
);
|
|
5016
|
+
const handleFileSelect = react.useCallback(
|
|
5017
|
+
(e) => {
|
|
5018
|
+
const selectedFiles = e.target.files;
|
|
5019
|
+
if (selectedFiles) {
|
|
5020
|
+
handleFiles(Array.from(selectedFiles));
|
|
5021
|
+
}
|
|
5022
|
+
if (fileInputRef.current) {
|
|
5023
|
+
fileInputRef.current.value = "";
|
|
5024
|
+
}
|
|
5025
|
+
},
|
|
5026
|
+
[handleFiles]
|
|
5027
|
+
);
|
|
5028
|
+
const handleClick = react.useCallback(() => {
|
|
5029
|
+
if (!disabled) {
|
|
5030
|
+
fileInputRef.current?.click();
|
|
5031
|
+
}
|
|
5032
|
+
}, [disabled]);
|
|
5033
|
+
const checkDataTransferHasFiles = react.useCallback(
|
|
5034
|
+
(dataTransfer) => {
|
|
5035
|
+
if (!dataTransfer?.types) return false;
|
|
5036
|
+
for (let i = 0; i < dataTransfer.types.length; i++) {
|
|
5037
|
+
if (dataTransfer.types[i] === "Files") return true;
|
|
5038
|
+
}
|
|
5039
|
+
return false;
|
|
5040
|
+
},
|
|
5041
|
+
[]
|
|
5042
|
+
);
|
|
5043
|
+
react.useEffect(() => {
|
|
5044
|
+
if (!children || !dropZoneRef.current) return;
|
|
5045
|
+
const element = dropZoneRef.current;
|
|
5046
|
+
let enterCounter = 0;
|
|
5047
|
+
const handleDragEnterNative = (e) => {
|
|
5048
|
+
e.preventDefault();
|
|
5049
|
+
e.stopPropagation();
|
|
5050
|
+
if (!checkDataTransferHasFiles(e.dataTransfer)) return;
|
|
5051
|
+
enterCounter++;
|
|
5052
|
+
if (enterCounter === 1 && !disabled) {
|
|
5053
|
+
setIsDragging(true);
|
|
5054
|
+
}
|
|
5055
|
+
};
|
|
5056
|
+
const handleDragLeaveNative = (e) => {
|
|
5057
|
+
e.preventDefault();
|
|
5058
|
+
e.stopPropagation();
|
|
5059
|
+
if (!checkDataTransferHasFiles(e.dataTransfer)) return;
|
|
5060
|
+
enterCounter--;
|
|
5061
|
+
if (enterCounter <= 0) {
|
|
5062
|
+
enterCounter = 0;
|
|
5063
|
+
setIsDragging(false);
|
|
5064
|
+
}
|
|
5065
|
+
};
|
|
5066
|
+
const handleDragOverNative = (e) => {
|
|
5067
|
+
e.preventDefault();
|
|
5068
|
+
e.stopPropagation();
|
|
5069
|
+
if (checkDataTransferHasFiles(e.dataTransfer) && !disabled && e.dataTransfer) {
|
|
5070
|
+
e.dataTransfer.dropEffect = "copy";
|
|
5071
|
+
}
|
|
5072
|
+
};
|
|
5073
|
+
const handleDropNative = (e) => {
|
|
5074
|
+
e.preventDefault();
|
|
5075
|
+
e.stopPropagation();
|
|
5076
|
+
enterCounter = 0;
|
|
5077
|
+
setIsDragging(false);
|
|
5078
|
+
if (disabled) return;
|
|
5079
|
+
const droppedFiles = e.dataTransfer?.files;
|
|
5080
|
+
if (droppedFiles && droppedFiles.length > 0) {
|
|
5081
|
+
handleFiles(Array.from(droppedFiles));
|
|
5082
|
+
}
|
|
5083
|
+
};
|
|
5084
|
+
element.addEventListener("dragenter", handleDragEnterNative, true);
|
|
5085
|
+
element.addEventListener("dragleave", handleDragLeaveNative, true);
|
|
5086
|
+
element.addEventListener("dragover", handleDragOverNative, true);
|
|
5087
|
+
element.addEventListener("drop", handleDropNative, true);
|
|
5088
|
+
return () => {
|
|
5089
|
+
element.removeEventListener("dragenter", handleDragEnterNative, true);
|
|
5090
|
+
element.removeEventListener("dragleave", handleDragLeaveNative, true);
|
|
5091
|
+
element.removeEventListener("dragover", handleDragOverNative, true);
|
|
5092
|
+
element.removeEventListener("drop", handleDropNative, true);
|
|
5093
|
+
};
|
|
5094
|
+
}, [children, disabled, handleFiles, checkDataTransferHasFiles]);
|
|
5095
|
+
react.useEffect(() => {
|
|
5096
|
+
if (disabled) {
|
|
5097
|
+
dragCounter.current = 0;
|
|
5098
|
+
setIsDragging(false);
|
|
5099
|
+
}
|
|
5100
|
+
}, [disabled]);
|
|
5101
|
+
const acceptTypes = accept || Object.values(SUPPORTED_TYPES).flatMap((t) => [...t.mimeTypes, ...t.extensions]).join(",");
|
|
5102
|
+
const hasFiles = files.length > 0;
|
|
5103
|
+
const isUploading = files.some((f) => f.status === "uploading");
|
|
5104
|
+
if (children) {
|
|
5105
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5106
|
+
"div",
|
|
5107
|
+
{
|
|
5108
|
+
ref: dropZoneRef,
|
|
5109
|
+
"data-dropzone": "wrapper",
|
|
5110
|
+
className: cn4("relative w-full h-full", className),
|
|
5111
|
+
style: { minHeight: "inherit" },
|
|
5112
|
+
children: [
|
|
5113
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5114
|
+
"input",
|
|
5115
|
+
{
|
|
5116
|
+
ref: fileInputRef,
|
|
5117
|
+
type: "file",
|
|
5118
|
+
multiple: true,
|
|
5119
|
+
accept: acceptTypes,
|
|
5120
|
+
onChange: handleFileSelect,
|
|
5121
|
+
className: "hidden",
|
|
5122
|
+
disabled
|
|
5123
|
+
}
|
|
5124
|
+
),
|
|
5125
|
+
children,
|
|
5126
|
+
isDragging && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5127
|
+
"div",
|
|
5128
|
+
{
|
|
5129
|
+
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",
|
|
5130
|
+
style: { pointerEvents: "none" },
|
|
5131
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-background/80 rounded-xl p-8 flex flex-col items-center shadow-lg", children: [
|
|
5132
|
+
/* @__PURE__ */ jsxRuntime.jsx(Upload, { className: "h-12 w-12 text-primary mb-4 animate-bounce" }),
|
|
5133
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg font-medium text-primary", children: "Drop files here" }),
|
|
5134
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "Documents, images, or audio files" })
|
|
5135
|
+
] })
|
|
5136
|
+
}
|
|
5137
|
+
)
|
|
5138
|
+
]
|
|
5139
|
+
}
|
|
5140
|
+
);
|
|
5141
|
+
}
|
|
5142
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn4("w-full", className), children: [
|
|
5143
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5144
|
+
"input",
|
|
5145
|
+
{
|
|
5146
|
+
ref: fileInputRef,
|
|
5147
|
+
type: "file",
|
|
5148
|
+
multiple: true,
|
|
5149
|
+
accept: acceptTypes,
|
|
5150
|
+
onChange: handleFileSelect,
|
|
5151
|
+
className: "hidden",
|
|
5152
|
+
disabled
|
|
5153
|
+
}
|
|
5154
|
+
),
|
|
5155
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5156
|
+
"div",
|
|
5157
|
+
{
|
|
5158
|
+
onDragEnter: handleDragEnter,
|
|
5159
|
+
onDragOver: handleDragOver,
|
|
5160
|
+
onDragLeave: handleDragLeave,
|
|
5161
|
+
onDrop: handleDrop,
|
|
5162
|
+
onClick: handleClick,
|
|
5163
|
+
className: cn4(
|
|
5164
|
+
"relative rounded-lg border-2 border-dashed transition-all cursor-pointer",
|
|
5165
|
+
"flex flex-col items-center justify-center",
|
|
5166
|
+
compact ? "p-2 min-h-[60px]" : "p-4 min-h-[100px]",
|
|
5167
|
+
isDragging && !disabled ? "border-primary bg-primary/5" : "border-muted-foreground/25",
|
|
5168
|
+
disabled ? "opacity-50 cursor-not-allowed" : "hover:border-primary/50 hover:bg-muted/25"
|
|
5169
|
+
),
|
|
5170
|
+
children: [
|
|
5171
|
+
isUploading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5172
|
+
Loader2,
|
|
5173
|
+
{
|
|
5174
|
+
className: cn4(
|
|
5175
|
+
"animate-spin text-muted-foreground",
|
|
5176
|
+
compact ? "h-5 w-5 mb-1" : "h-8 w-8 mb-2"
|
|
5177
|
+
)
|
|
5178
|
+
}
|
|
5179
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
5180
|
+
Upload,
|
|
5181
|
+
{
|
|
5182
|
+
className: cn4(
|
|
5183
|
+
"text-muted-foreground",
|
|
5184
|
+
compact ? "h-5 w-5 mb-1" : "h-8 w-8 mb-2"
|
|
5185
|
+
)
|
|
5186
|
+
}
|
|
5187
|
+
),
|
|
5188
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5189
|
+
"p",
|
|
5190
|
+
{
|
|
5191
|
+
className: cn4(
|
|
5192
|
+
"font-medium text-center text-muted-foreground",
|
|
5193
|
+
compact ? "text-xs" : "text-sm"
|
|
5194
|
+
),
|
|
5195
|
+
children: isDragging ? "Drop files here" : isUploading ? "Uploading..." : "Click to upload or drag and drop"
|
|
5196
|
+
}
|
|
5197
|
+
),
|
|
5198
|
+
!compact && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground/70 text-center mt-1", children: "Documents, images, or audio files" })
|
|
5199
|
+
]
|
|
5200
|
+
}
|
|
5201
|
+
),
|
|
5202
|
+
hasFiles && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn4("space-y-2", compact ? "mt-2" : "mt-3"), children: files.map((attachment) => {
|
|
5203
|
+
const Icon = getCategoryIcon(attachment.category);
|
|
5204
|
+
const isError = attachment.status === "error";
|
|
5205
|
+
const isLoading = attachment.status === "uploading" || attachment.status === "processing";
|
|
5206
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5207
|
+
"div",
|
|
5208
|
+
{
|
|
5209
|
+
className: cn4(
|
|
5210
|
+
"flex items-center gap-3 p-2 rounded-lg border",
|
|
5211
|
+
isError ? "border-destructive/50 bg-destructive/5" : "border-border bg-muted/25"
|
|
5212
|
+
),
|
|
5213
|
+
children: [
|
|
5214
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0 w-10 h-10 rounded overflow-hidden bg-muted flex items-center justify-center", children: [
|
|
5215
|
+
attachment.previewUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5216
|
+
"img",
|
|
5217
|
+
{
|
|
5218
|
+
src: attachment.previewUrl,
|
|
5219
|
+
alt: attachment.file.name,
|
|
5220
|
+
className: "w-full h-full object-cover"
|
|
5221
|
+
}
|
|
5222
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-5 w-5 text-muted-foreground" }),
|
|
5223
|
+
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" }) })
|
|
5224
|
+
] }),
|
|
5225
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
5226
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: attachment.file.name }),
|
|
5227
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
5228
|
+
formatFileSize2(attachment.file.size),
|
|
5229
|
+
isError && attachment.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-2", children: attachment.error })
|
|
5230
|
+
] })
|
|
5231
|
+
] }),
|
|
5232
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
5233
|
+
attachment.progress,
|
|
5234
|
+
"%"
|
|
5235
|
+
] }) : attachment.status === "ready" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-green-600", children: "Ready" }) : null }),
|
|
5236
|
+
isError && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5237
|
+
"button",
|
|
5238
|
+
{
|
|
5239
|
+
type: "button",
|
|
5240
|
+
className: "flex-shrink-0 h-8 w-8 inline-flex items-center justify-center rounded-md text-primary hover:bg-muted transition-colors",
|
|
5241
|
+
onClick: (e) => {
|
|
5242
|
+
e.stopPropagation();
|
|
5243
|
+
handleRetry(attachment.uuid);
|
|
5244
|
+
},
|
|
5245
|
+
title: "Retry upload",
|
|
5246
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(RotateCw, { className: "h-4 w-4" })
|
|
5247
|
+
}
|
|
5248
|
+
),
|
|
5249
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5250
|
+
"button",
|
|
5251
|
+
{
|
|
5252
|
+
type: "button",
|
|
5253
|
+
className: "flex-shrink-0 h-8 w-8 inline-flex items-center justify-center rounded-md hover:bg-muted transition-colors disabled:opacity-50",
|
|
5254
|
+
onClick: (e) => {
|
|
5255
|
+
e.stopPropagation();
|
|
5256
|
+
handleRemove(attachment.uuid);
|
|
5257
|
+
},
|
|
5258
|
+
disabled: isLoading,
|
|
5259
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(X, { className: "h-4 w-4" })
|
|
5260
|
+
}
|
|
5261
|
+
)
|
|
5262
|
+
]
|
|
5263
|
+
},
|
|
5264
|
+
attachment.uuid
|
|
5265
|
+
);
|
|
5266
|
+
}) })
|
|
5267
|
+
] });
|
|
5268
|
+
}
|
|
5269
|
+
);
|
|
5270
|
+
function cn5(...classes) {
|
|
5271
|
+
return classes.filter(Boolean).join(" ");
|
|
5272
|
+
}
|
|
4221
5273
|
function AskUserPrompt({
|
|
4222
5274
|
question,
|
|
4223
5275
|
options,
|
|
@@ -4251,7 +5303,7 @@ function AskUserPrompt({
|
|
|
4251
5303
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4252
5304
|
"div",
|
|
4253
5305
|
{
|
|
4254
|
-
className:
|
|
5306
|
+
className: cn5(
|
|
4255
5307
|
"rounded-lg border p-4 space-y-3",
|
|
4256
5308
|
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
5309
|
className
|
|
@@ -4263,7 +5315,7 @@ function AskUserPrompt({
|
|
|
4263
5315
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4264
5316
|
"span",
|
|
4265
5317
|
{
|
|
4266
|
-
className:
|
|
5318
|
+
className: cn5(
|
|
4267
5319
|
"ml-auto text-xs px-2 py-1 rounded-full font-medium",
|
|
4268
5320
|
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
5321
|
),
|
|
@@ -4278,7 +5330,7 @@ function AskUserPrompt({
|
|
|
4278
5330
|
type: "button",
|
|
4279
5331
|
onClick: () => handleOptionSelect(option),
|
|
4280
5332
|
disabled,
|
|
4281
|
-
className:
|
|
5333
|
+
className: cn5(
|
|
4282
5334
|
"px-3 py-1.5 text-sm rounded-md border transition-all",
|
|
4283
5335
|
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
5336
|
disabled && "opacity-50 cursor-not-allowed"
|
|
@@ -4298,7 +5350,7 @@ function AskUserPrompt({
|
|
|
4298
5350
|
},
|
|
4299
5351
|
onKeyDown: handleKeyDown,
|
|
4300
5352
|
placeholder: options?.length ? "Or type a custom response..." : "Type your response...",
|
|
4301
|
-
className:
|
|
5353
|
+
className: cn5(
|
|
4302
5354
|
"flex-1 min-h-[60px] px-3 py-2 text-sm resize-none rounded-md border",
|
|
4303
5355
|
"border-gray-300 dark:border-gray-600",
|
|
4304
5356
|
"bg-white dark:bg-gray-800",
|
|
@@ -4316,7 +5368,7 @@ function AskUserPrompt({
|
|
|
4316
5368
|
type: "button",
|
|
4317
5369
|
onClick: handleSubmit,
|
|
4318
5370
|
disabled: isSubmitDisabled,
|
|
4319
|
-
className:
|
|
5371
|
+
className: cn5(
|
|
4320
5372
|
"self-end px-4 py-2 rounded-md text-white transition-colors",
|
|
4321
5373
|
"bg-blue-600 hover:bg-blue-700",
|
|
4322
5374
|
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
@@ -4337,7 +5389,7 @@ function AskUserPrompt({
|
|
|
4337
5389
|
}
|
|
4338
5390
|
);
|
|
4339
5391
|
}
|
|
4340
|
-
function
|
|
5392
|
+
function cn6(...classes) {
|
|
4341
5393
|
return classes.filter(Boolean).join(" ");
|
|
4342
5394
|
}
|
|
4343
5395
|
function MarkdownContent({
|
|
@@ -4398,7 +5450,7 @@ function MarkdownContent({
|
|
|
4398
5450
|
if (isCodeBlock) {
|
|
4399
5451
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative my-2", children: [
|
|
4400
5452
|
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:
|
|
5453
|
+
/* @__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
5454
|
] });
|
|
4403
5455
|
}
|
|
4404
5456
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4445,7 +5497,7 @@ function MarkdownContent({
|
|
|
4445
5497
|
() => customComponents ? { ...defaultComponents, ...customComponents } : defaultComponents,
|
|
4446
5498
|
[defaultComponents, customComponents]
|
|
4447
5499
|
);
|
|
4448
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className:
|
|
5500
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn6("prose prose-sm dark:prose-invert max-w-none", className), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4449
5501
|
ReactMarkdown__default.default,
|
|
4450
5502
|
{
|
|
4451
5503
|
remarkPlugins: [remarkGfm__default.default],
|
|
@@ -4455,7 +5507,7 @@ function MarkdownContent({
|
|
|
4455
5507
|
}
|
|
4456
5508
|
) });
|
|
4457
5509
|
}
|
|
4458
|
-
function
|
|
5510
|
+
function cn7(...classes) {
|
|
4459
5511
|
return classes.filter(Boolean).join(" ");
|
|
4460
5512
|
}
|
|
4461
5513
|
function formatToolName(toolName) {
|
|
@@ -4603,7 +5655,7 @@ function ToolCallEntry({
|
|
|
4603
5655
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4604
5656
|
"div",
|
|
4605
5657
|
{
|
|
4606
|
-
className:
|
|
5658
|
+
className: cn7(
|
|
4607
5659
|
"rounded-lg border text-sm",
|
|
4608
5660
|
statusColor,
|
|
4609
5661
|
className
|
|
@@ -4624,7 +5676,7 @@ function ToolCallEntry({
|
|
|
4624
5676
|
]
|
|
4625
5677
|
}
|
|
4626
5678
|
),
|
|
4627
|
-
hasImages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className:
|
|
5679
|
+
hasImages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn7(
|
|
4628
5680
|
"grid gap-2",
|
|
4629
5681
|
resultImages.length === 1 ? "grid-cols-1" : "grid-cols-2"
|
|
4630
5682
|
), children: resultImages.map((img, idx) => {
|
|
@@ -4683,7 +5735,7 @@ function ToolCallEntry({
|
|
|
4683
5735
|
}
|
|
4684
5736
|
);
|
|
4685
5737
|
}
|
|
4686
|
-
function
|
|
5738
|
+
function cn8(...classes) {
|
|
4687
5739
|
return classes.filter(Boolean).join(" ");
|
|
4688
5740
|
}
|
|
4689
5741
|
function ChatMessage({
|
|
@@ -4705,7 +5757,7 @@ function ChatMessage({
|
|
|
4705
5757
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4706
5758
|
"div",
|
|
4707
5759
|
{
|
|
4708
|
-
className:
|
|
5760
|
+
className: cn8(
|
|
4709
5761
|
"flex gap-3 p-4",
|
|
4710
5762
|
isUser ? "flex-row-reverse" : "flex-row",
|
|
4711
5763
|
className
|
|
@@ -4714,7 +5766,7 @@ function ChatMessage({
|
|
|
4714
5766
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4715
5767
|
"div",
|
|
4716
5768
|
{
|
|
4717
|
-
className:
|
|
5769
|
+
className: cn8(
|
|
4718
5770
|
"flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
|
|
4719
5771
|
isUser ? "bg-blue-500 text-white" : "bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300"
|
|
4720
5772
|
),
|
|
@@ -4724,7 +5776,7 @@ function ChatMessage({
|
|
|
4724
5776
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4725
5777
|
"div",
|
|
4726
5778
|
{
|
|
4727
|
-
className:
|
|
5779
|
+
className: cn8(
|
|
4728
5780
|
"flex-1 min-w-0 space-y-2",
|
|
4729
5781
|
isUser ? "text-right" : "text-left"
|
|
4730
5782
|
),
|
|
@@ -4732,7 +5784,7 @@ function ChatMessage({
|
|
|
4732
5784
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4733
5785
|
"div",
|
|
4734
5786
|
{
|
|
4735
|
-
className:
|
|
5787
|
+
className: cn8(
|
|
4736
5788
|
"flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400",
|
|
4737
5789
|
isUser ? "justify-end" : "justify-start"
|
|
4738
5790
|
),
|
|
@@ -4765,7 +5817,7 @@ function ChatMessage({
|
|
|
4765
5817
|
message.content && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4766
5818
|
"div",
|
|
4767
5819
|
{
|
|
4768
|
-
className:
|
|
5820
|
+
className: cn8(
|
|
4769
5821
|
"rounded-lg p-3",
|
|
4770
5822
|
isUser ? "bg-blue-500 text-white" : "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
|
4771
5823
|
),
|
|
@@ -4795,7 +5847,7 @@ function ChatMessage({
|
|
|
4795
5847
|
}
|
|
4796
5848
|
);
|
|
4797
5849
|
}
|
|
4798
|
-
function
|
|
5850
|
+
function cn9(...classes) {
|
|
4799
5851
|
return classes.filter(Boolean).join(" ");
|
|
4800
5852
|
}
|
|
4801
5853
|
function MessageList({
|
|
@@ -4825,7 +5877,7 @@ function MessageList({
|
|
|
4825
5877
|
"div",
|
|
4826
5878
|
{
|
|
4827
5879
|
ref: scrollRef,
|
|
4828
|
-
className:
|
|
5880
|
+
className: cn9(
|
|
4829
5881
|
"flex-1 overflow-y-auto",
|
|
4830
5882
|
className
|
|
4831
5883
|
),
|
|
@@ -4918,6 +5970,7 @@ function AgentChat({
|
|
|
4918
5970
|
isStreaming,
|
|
4919
5971
|
isLoadingHistory,
|
|
4920
5972
|
error,
|
|
5973
|
+
sessionId: currentSessionId,
|
|
4921
5974
|
clarification,
|
|
4922
5975
|
sendMessage,
|
|
4923
5976
|
respondToClarification,
|
|
@@ -4938,8 +5991,59 @@ function AgentChat({
|
|
|
4938
5991
|
onToolResult,
|
|
4939
5992
|
onClarification
|
|
4940
5993
|
});
|
|
5994
|
+
const dropZoneRef = react.useRef(null);
|
|
5995
|
+
const attachments = useFileAttachments({
|
|
5996
|
+
onUpload: enableAttachments && adapter ? async (_file, meta) => {
|
|
5997
|
+
const result = await adapter.getUploadUrl({
|
|
5998
|
+
sessionId: currentSessionId ?? "",
|
|
5999
|
+
...meta
|
|
6000
|
+
});
|
|
6001
|
+
return { uuid: result.attachmentUuid, uploadUrl: result.uploadUrl };
|
|
6002
|
+
} : async () => ({ uuid: "", uploadUrl: "" }),
|
|
6003
|
+
onConfirmUpload: enableAttachments && adapter ? (uuid) => adapter.confirmUpload(uuid) : void 0,
|
|
6004
|
+
onError: (msg) => {
|
|
6005
|
+
console.error("[AgentChat] Attachment error:", msg);
|
|
6006
|
+
}
|
|
6007
|
+
});
|
|
6008
|
+
const handleSend = react.useCallback(
|
|
6009
|
+
async (content) => {
|
|
6010
|
+
if (enableAttachments && attachments.hasReadyFiles) {
|
|
6011
|
+
const uuids = attachments.getReadyUuids();
|
|
6012
|
+
await sendMessage(content, { attachmentUuids: uuids });
|
|
6013
|
+
attachments.clear();
|
|
6014
|
+
} else {
|
|
6015
|
+
await sendMessage(content);
|
|
6016
|
+
}
|
|
6017
|
+
},
|
|
6018
|
+
[enableAttachments, attachments, sendMessage]
|
|
6019
|
+
);
|
|
4941
6020
|
return /* @__PURE__ */ jsxRuntime.jsxs(ChatContainer, { maxHeight, className, theme, children: [
|
|
4942
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6021
|
+
enableAttachments ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6022
|
+
FileDropZone,
|
|
6023
|
+
{
|
|
6024
|
+
ref: dropZoneRef,
|
|
6025
|
+
...attachments.dropZoneProps,
|
|
6026
|
+
disabled: isLoading || !!clarification || isLoadingHistory,
|
|
6027
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6028
|
+
MessageList,
|
|
6029
|
+
{
|
|
6030
|
+
messages,
|
|
6031
|
+
welcomeMessage,
|
|
6032
|
+
renderWelcome,
|
|
6033
|
+
isLoading,
|
|
6034
|
+
isStreaming,
|
|
6035
|
+
isLoadingHistory,
|
|
6036
|
+
enableMarkdown,
|
|
6037
|
+
showToolCalls,
|
|
6038
|
+
markdownComponents,
|
|
6039
|
+
markdownUrlTransform,
|
|
6040
|
+
markdownContentTransform,
|
|
6041
|
+
onImageClick,
|
|
6042
|
+
sendMessage: handleSend
|
|
6043
|
+
}
|
|
6044
|
+
)
|
|
6045
|
+
}
|
|
6046
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4943
6047
|
MessageList,
|
|
4944
6048
|
{
|
|
4945
6049
|
messages,
|
|
@@ -4954,7 +6058,7 @@ function AgentChat({
|
|
|
4954
6058
|
markdownUrlTransform,
|
|
4955
6059
|
markdownContentTransform,
|
|
4956
6060
|
onImageClick,
|
|
4957
|
-
sendMessage
|
|
6061
|
+
sendMessage: handleSend
|
|
4958
6062
|
}
|
|
4959
6063
|
),
|
|
4960
6064
|
clarification && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4969,17 +6073,21 @@ function AgentChat({
|
|
|
4969
6073
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4970
6074
|
ChatInput,
|
|
4971
6075
|
{
|
|
4972
|
-
onSend:
|
|
6076
|
+
onSend: handleSend,
|
|
4973
6077
|
onStop: isStreaming ? cancel : void 0,
|
|
4974
6078
|
placeholder,
|
|
4975
6079
|
disabled: !!clarification || isLoadingHistory,
|
|
4976
6080
|
isLoading,
|
|
4977
|
-
enableAttachments
|
|
6081
|
+
enableAttachments,
|
|
6082
|
+
attachments: enableAttachments ? attachments.files : void 0,
|
|
6083
|
+
onRemoveAttachment: enableAttachments ? attachments.removeFile : void 0,
|
|
6084
|
+
dropZoneRef: enableAttachments ? dropZoneRef : void 0,
|
|
6085
|
+
isUploadingAttachments: enableAttachments ? attachments.isUploading : void 0
|
|
4978
6086
|
}
|
|
4979
6087
|
)
|
|
4980
6088
|
] });
|
|
4981
6089
|
}
|
|
4982
|
-
function
|
|
6090
|
+
function cn10(...classes) {
|
|
4983
6091
|
return classes.filter(Boolean).join(" ");
|
|
4984
6092
|
}
|
|
4985
6093
|
function TodoList({
|
|
@@ -5008,7 +6116,7 @@ function TodoList({
|
|
|
5008
6116
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5009
6117
|
"div",
|
|
5010
6118
|
{
|
|
5011
|
-
className:
|
|
6119
|
+
className: cn10(
|
|
5012
6120
|
"rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden",
|
|
5013
6121
|
className
|
|
5014
6122
|
),
|
|
@@ -5016,7 +6124,7 @@ function TodoList({
|
|
|
5016
6124
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5017
6125
|
"div",
|
|
5018
6126
|
{
|
|
5019
|
-
className:
|
|
6127
|
+
className: cn10(
|
|
5020
6128
|
"flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800/50",
|
|
5021
6129
|
collapsible && "cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
5022
6130
|
),
|
|
@@ -5047,7 +6155,7 @@ function TodoList({
|
|
|
5047
6155
|
!isCollapsed && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-gray-100 dark:divide-gray-700", children: todos.map((todo, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5048
6156
|
"li",
|
|
5049
6157
|
{
|
|
5050
|
-
className:
|
|
6158
|
+
className: cn10(
|
|
5051
6159
|
"px-3 py-2 flex items-start gap-2 text-sm",
|
|
5052
6160
|
todo.status === "in_progress" && "bg-blue-50/50 dark:bg-blue-950/20",
|
|
5053
6161
|
todo.status === "completed" && "opacity-60"
|
|
@@ -5057,7 +6165,7 @@ function TodoList({
|
|
|
5057
6165
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5058
6166
|
"p",
|
|
5059
6167
|
{
|
|
5060
|
-
className:
|
|
6168
|
+
className: cn10(
|
|
5061
6169
|
"text-gray-700 dark:text-gray-300",
|
|
5062
6170
|
todo.status === "completed" && "line-through"
|
|
5063
6171
|
),
|
|
@@ -5074,6 +6182,39 @@ function TodoList({
|
|
|
5074
6182
|
);
|
|
5075
6183
|
}
|
|
5076
6184
|
|
|
6185
|
+
// src/chat/index.ts
|
|
6186
|
+
var DEPRECATION_WARNING = `
|
|
6187
|
+
[@cadraos/sdk/chat] DEPRECATION WARNING
|
|
6188
|
+
|
|
6189
|
+
This module is deprecated and will be removed in a future version.
|
|
6190
|
+
Please migrate to @cadraos/react for:
|
|
6191
|
+
- Simpler setup with CadraCopilot
|
|
6192
|
+
- Better security (header-based SSE auth)
|
|
6193
|
+
- Scoped CSS that won't conflict with your app
|
|
6194
|
+
|
|
6195
|
+
Migration steps:
|
|
6196
|
+
1. npm install @cadraos/react
|
|
6197
|
+
2. import '@cadraos/react/styles.css'
|
|
6198
|
+
3. Replace <AgentChat> with <CadraCopilot> or <CadraChat>
|
|
6199
|
+
|
|
6200
|
+
See: https://github.com/cadraos/cadra-sdk/blob/main/packages/react/MIGRATION.md
|
|
6201
|
+
`;
|
|
6202
|
+
var hasWarned = false;
|
|
6203
|
+
function showDeprecationWarning() {
|
|
6204
|
+
if (hasWarned) return;
|
|
6205
|
+
hasWarned = true;
|
|
6206
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
|
|
6207
|
+
return;
|
|
6208
|
+
}
|
|
6209
|
+
if (typeof window === "undefined") {
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
console.warn(DEPRECATION_WARNING);
|
|
6213
|
+
}
|
|
6214
|
+
if (typeof window !== "undefined") {
|
|
6215
|
+
setTimeout(showDeprecationWarning, 0);
|
|
6216
|
+
}
|
|
6217
|
+
|
|
5077
6218
|
exports.AgentChat = AgentChat;
|
|
5078
6219
|
exports.AlertCircleIcon = AlertCircleIcon;
|
|
5079
6220
|
exports.AskUserPrompt = AskUserPrompt;
|
|
@@ -5094,6 +6235,7 @@ exports.CopyIcon = CopyIcon;
|
|
|
5094
6235
|
exports.DatabaseIcon = DatabaseIcon;
|
|
5095
6236
|
exports.DownloadIcon = DownloadIcon;
|
|
5096
6237
|
exports.ExternalLinkIcon = ExternalLinkIcon;
|
|
6238
|
+
exports.FileDropZone = FileDropZone;
|
|
5097
6239
|
exports.FileIcon = FileIcon;
|
|
5098
6240
|
exports.FileTextIcon = FileTextIcon;
|
|
5099
6241
|
exports.HelpCircleIcon = HelpCircleIcon;
|
|
@@ -5116,10 +6258,12 @@ exports.UserIcon = UserIcon;
|
|
|
5116
6258
|
exports.XIcon = XIcon;
|
|
5117
6259
|
exports.ZoomInIcon = ZoomInIcon;
|
|
5118
6260
|
exports.ZoomOutIcon = ZoomOutIcon;
|
|
6261
|
+
exports.compressImage = compressImage;
|
|
5119
6262
|
exports.createDefaultAdapter = createDefaultAdapter;
|
|
5120
6263
|
exports.useAgentChat = useAgentChat;
|
|
5121
6264
|
exports.useChatConfig = useChatConfig;
|
|
5122
6265
|
exports.useChatConfigOptional = useChatConfigOptional;
|
|
5123
6266
|
exports.useExecutionStream = useExecutionStream;
|
|
6267
|
+
exports.useFileAttachments = useFileAttachments;
|
|
5124
6268
|
//# sourceMappingURL=index.cjs.map
|
|
5125
6269
|
//# sourceMappingURL=index.cjs.map
|