@kite-copilot/chat-panel 0.2.51 → 0.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auto.cjs CHANGED
@@ -1066,29 +1066,29 @@ function DataRenderer({ type, data }) {
1066
1066
  // src/components/TypingIndicator.tsx
1067
1067
  var import_jsx_runtime9 = require("react/jsx-runtime");
1068
1068
  function TypingIndicator({ className = "" }) {
1069
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex items-center gap-1 px-4 py-3 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1", children: [
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `flex items-center gap-1.5 ${className}`, children: [
1070
1070
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1071
1071
  "span",
1072
1072
  {
1073
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1073
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1074
1074
  style: { animationDelay: "0ms", animationDuration: "600ms" }
1075
1075
  }
1076
1076
  ),
1077
1077
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1078
1078
  "span",
1079
1079
  {
1080
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1080
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1081
1081
  style: { animationDelay: "150ms", animationDuration: "600ms" }
1082
1082
  }
1083
1083
  ),
1084
1084
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1085
1085
  "span",
1086
1086
  {
1087
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1087
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1088
1088
  style: { animationDelay: "300ms", animationDuration: "600ms" }
1089
1089
  }
1090
1090
  )
1091
- ] }) });
1091
+ ] });
1092
1092
  }
1093
1093
 
1094
1094
  // src/ChatPanel.tsx
@@ -1537,6 +1537,78 @@ function AuthErrorState({
1537
1537
  )
1538
1538
  ] });
1539
1539
  }
1540
+ function ImageLightbox({
1541
+ imageUrl,
1542
+ onClose
1543
+ }) {
1544
+ const handleDownload = async () => {
1545
+ try {
1546
+ const response = await fetch(imageUrl);
1547
+ const blob = await response.blob();
1548
+ const url = window.URL.createObjectURL(blob);
1549
+ const a = document.createElement("a");
1550
+ a.href = url;
1551
+ const urlParts = imageUrl.split("/");
1552
+ const filename = urlParts[urlParts.length - 1] || "image.jpg";
1553
+ a.download = filename;
1554
+ document.body.appendChild(a);
1555
+ a.click();
1556
+ document.body.removeChild(a);
1557
+ window.URL.revokeObjectURL(url);
1558
+ } catch (error) {
1559
+ console.error("Failed to download image:", error);
1560
+ window.open(imageUrl, "_blank");
1561
+ }
1562
+ };
1563
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1564
+ "div",
1565
+ {
1566
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/80",
1567
+ onClick: onClose,
1568
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1569
+ "div",
1570
+ {
1571
+ className: "relative max-w-[90vw] max-h-[90vh] flex flex-col items-center",
1572
+ onClick: (e) => e.stopPropagation(),
1573
+ children: [
1574
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1575
+ "img",
1576
+ {
1577
+ src: imageUrl,
1578
+ alt: "Full size preview",
1579
+ className: "max-w-full max-h-[80vh] object-contain rounded-lg"
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-3 mt-4", children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1584
+ "button",
1585
+ {
1586
+ onClick: handleDownload,
1587
+ className: "flex items-center gap-2 px-4 py-2 bg-white text-gray-800 rounded-lg hover:bg-gray-100 transition-colors text-sm font-medium",
1588
+ children: [
1589
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Download, { className: "h-4 w-4" }),
1590
+ "Download"
1591
+ ]
1592
+ }
1593
+ ),
1594
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1595
+ "button",
1596
+ {
1597
+ onClick: onClose,
1598
+ className: "flex items-center gap-2 px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-colors text-sm font-medium",
1599
+ children: [
1600
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" }),
1601
+ "Close"
1602
+ ]
1603
+ }
1604
+ )
1605
+ ] })
1606
+ ]
1607
+ }
1608
+ )
1609
+ }
1610
+ );
1611
+ }
1540
1612
  function ChatPanel({
1541
1613
  isOpen = true,
1542
1614
  onClose,
@@ -1557,7 +1629,8 @@ function ChatPanel({
1557
1629
  initialCorner = "bottom-left",
1558
1630
  onCornerChange,
1559
1631
  productBackendUrl,
1560
- getAuthHeaders
1632
+ getAuthHeaders,
1633
+ isEval = false
1561
1634
  } = {}) {
1562
1635
  const [messages, setMessages] = React6.useState(initialMessages);
1563
1636
  const [input, setInput] = React6.useState("");
@@ -1667,13 +1740,20 @@ function ChatPanel({
1667
1740
  }, [effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1668
1741
  React6.useEffect(() => {
1669
1742
  if (!isEscalated || !sessionId || !supabaseRef.current) {
1743
+ console.log("[KiteChat] Typing channel skip - isEscalated:", isEscalated, "sessionId:", sessionId, "supabase:", !!supabaseRef.current);
1670
1744
  return;
1671
1745
  }
1672
1746
  const channelName = `typing:${sessionId}`;
1673
- const channel = supabaseRef.current.channel(channelName);
1747
+ console.log("[KiteChat] Subscribing to typing channel:", channelName, "sessionId:", sessionId);
1748
+ const channel = supabaseRef.current.channel(channelName, {
1749
+ config: { broadcast: { self: true } }
1750
+ });
1674
1751
  channel.on("broadcast", { event: "typing" }, (payload) => {
1752
+ console.log("[KiteChat] Received typing event:", payload);
1675
1753
  const { sender, isTyping } = payload.payload;
1754
+ console.log("[KiteChat] Typing event - sender:", sender, "isTyping:", isTyping);
1676
1755
  if (sender === "agent") {
1756
+ console.log("[KiteChat] Setting agentIsTyping to:", isTyping);
1677
1757
  setAgentIsTyping(isTyping);
1678
1758
  if (isTyping) {
1679
1759
  if (typingTimeoutRef.current) {
@@ -1685,6 +1765,7 @@ function ChatPanel({
1685
1765
  }
1686
1766
  }
1687
1767
  }).subscribe((status) => {
1768
+ console.log("[KiteChat] Typing channel subscription status:", status);
1688
1769
  if (status === "SUBSCRIBED") {
1689
1770
  typingChannelRef.current = channel;
1690
1771
  console.log("[KiteChat] Typing channel subscribed successfully");
@@ -1699,7 +1780,7 @@ function ChatPanel({
1699
1780
  window.clearTimeout(typingTimeoutRef.current);
1700
1781
  }
1701
1782
  };
1702
- }, [isEscalated, sessionId]);
1783
+ }, [isEscalated, sessionId, effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1703
1784
  React6.useEffect(() => {
1704
1785
  if (!isOpen && isEscalated && supabaseRef.current && sessionId) {
1705
1786
  console.log("[KiteChat] Panel closed during live chat, marking disconnected");
@@ -1772,9 +1853,7 @@ function ChatPanel({
1772
1853
  markDisconnectedWithKeepalive();
1773
1854
  };
1774
1855
  const handleVisibilityChange = () => {
1775
- if (document.visibilityState === "hidden") {
1776
- markDisconnectedWithKeepalive();
1777
- } else if (document.visibilityState === "visible") {
1856
+ if (document.visibilityState === "visible") {
1778
1857
  markActive();
1779
1858
  }
1780
1859
  };
@@ -1912,6 +1991,77 @@ function ChatPanel({
1912
1991
  const [pendingBulkSession, setPendingBulkSession] = React6.useState(null);
1913
1992
  const pendingBulkSessionRef = React6.useRef(null);
1914
1993
  const fileInputRef = React6.useRef(null);
1994
+ const [pendingImages, setPendingImages] = React6.useState([]);
1995
+ const [isUploadingImage, setIsUploadingImage] = React6.useState(false);
1996
+ const [isDragOver, setIsDragOver] = React6.useState(false);
1997
+ const imageInputRef = React6.useRef(null);
1998
+ const [lightboxImageUrl, setLightboxImageUrl] = React6.useState(null);
1999
+ const uploadImageToStorage = React6.useCallback(async (file) => {
2000
+ if (!supabaseRef.current) {
2001
+ console.error("[KiteChat] Supabase client not available for file upload");
2002
+ return null;
2003
+ }
2004
+ const fileExt = file.name.split(".").pop();
2005
+ const fileName = `${sessionId}/${Date.now()}-${Math.random().toString(36).substring(7)}.${fileExt}`;
2006
+ const bucketName = "chat-attachments";
2007
+ try {
2008
+ const { data, error } = await supabaseRef.current.storage.from(bucketName).upload(fileName, file, {
2009
+ cacheControl: "3600",
2010
+ upsert: false
2011
+ });
2012
+ if (error) {
2013
+ console.error("[KiteChat] Upload error:", error);
2014
+ return null;
2015
+ }
2016
+ const { data: urlData } = supabaseRef.current.storage.from(bucketName).getPublicUrl(fileName);
2017
+ return urlData?.publicUrl || null;
2018
+ } catch (err) {
2019
+ console.error("[KiteChat] Upload failed:", err);
2020
+ return null;
2021
+ }
2022
+ }, [sessionId]);
2023
+ const handleImageSelect = React6.useCallback((files) => {
2024
+ if (!files) return;
2025
+ const imageFiles = Array.from(files).filter(
2026
+ (file) => file.type.startsWith("image/") || file.type === "application/pdf"
2027
+ );
2028
+ const newImages = imageFiles.map((file) => ({
2029
+ file,
2030
+ preview: file.type.startsWith("image/") ? URL.createObjectURL(file) : ""
2031
+ }));
2032
+ setPendingImages((prev) => [...prev, ...newImages]);
2033
+ }, []);
2034
+ const handleDragOver = React6.useCallback((e) => {
2035
+ e.preventDefault();
2036
+ e.stopPropagation();
2037
+ setIsDragOver(true);
2038
+ }, []);
2039
+ const handleDragLeave = React6.useCallback((e) => {
2040
+ e.preventDefault();
2041
+ e.stopPropagation();
2042
+ setIsDragOver(false);
2043
+ }, []);
2044
+ const handleDrop = React6.useCallback((e) => {
2045
+ e.preventDefault();
2046
+ e.stopPropagation();
2047
+ setIsDragOver(false);
2048
+ const files = e.dataTransfer.files;
2049
+ if (files.length > 0) {
2050
+ const csvFile = Array.from(files).find((f) => f.name.endsWith(".csv"));
2051
+ if (csvFile && !isEscalated) {
2052
+ setPendingFile(csvFile);
2053
+ } else {
2054
+ handleImageSelect(files);
2055
+ }
2056
+ }
2057
+ }, [isEscalated, handleImageSelect]);
2058
+ React6.useEffect(() => {
2059
+ return () => {
2060
+ pendingImages.forEach((img) => {
2061
+ if (img.preview) URL.revokeObjectURL(img.preview);
2062
+ });
2063
+ };
2064
+ }, [pendingImages]);
1915
2065
  const [searchExpanded, setSearchExpanded] = React6.useState(false);
1916
2066
  const [searchInput, setSearchInput] = React6.useState("");
1917
2067
  const searchInputRef = React6.useRef(null);
@@ -1968,6 +2118,11 @@ function ChatPanel({
1968
2118
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
1969
2119
  }
1970
2120
  }, [messages, phase, activeGuide]);
2121
+ React6.useEffect(() => {
2122
+ if (isEscalated && agentIsTyping && !activeGuide) {
2123
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2124
+ }
2125
+ }, [isEscalated, agentIsTyping, activeGuide]);
1971
2126
  const latestBulkSummaryNavigation = React6.useMemo(() => {
1972
2127
  for (let i = messages.length - 1; i >= 0; i--) {
1973
2128
  const msg = messages[i];
@@ -2204,7 +2359,7 @@ function ChatPanel({
2204
2359
  setPanelView("landing");
2205
2360
  setCurrentFolderId(void 0);
2206
2361
  }
2207
- function handleSubmit(e) {
2362
+ async function handleSubmit(e) {
2208
2363
  e.preventDefault();
2209
2364
  const trimmed = input.trim();
2210
2365
  if (pendingFile) {
@@ -2224,6 +2379,49 @@ function ChatPanel({
2224
2379
  if (fileInputRef.current) fileInputRef.current.value = "";
2225
2380
  return;
2226
2381
  }
2382
+ if (pendingImages.length > 0) {
2383
+ setIsUploadingImage(true);
2384
+ try {
2385
+ const uploadedUrls = [];
2386
+ for (const img of pendingImages) {
2387
+ const url = await uploadImageToStorage(img.file);
2388
+ if (url) {
2389
+ uploadedUrls.push(url);
2390
+ }
2391
+ if (img.preview) URL.revokeObjectURL(img.preview);
2392
+ }
2393
+ if (uploadedUrls.length > 0) {
2394
+ const imageMarkdown = uploadedUrls.map((url) => `![image](${url})`).join("\n");
2395
+ const messageContent = trimmed ? `${trimmed}
2396
+
2397
+ ${imageMarkdown}` : imageMarkdown;
2398
+ const userMessage = {
2399
+ id: Date.now(),
2400
+ role: "user",
2401
+ content: messageContent,
2402
+ imageUrls: uploadedUrls
2403
+ };
2404
+ setMessages((prev) => [...prev, userMessage]);
2405
+ if (isEscalated) {
2406
+ sendEscalatedMessage(messageContent);
2407
+ sendTypingIndicator(false);
2408
+ if (userTypingTimeoutRef.current) {
2409
+ window.clearTimeout(userTypingTimeoutRef.current);
2410
+ userTypingTimeoutRef.current = null;
2411
+ }
2412
+ } else {
2413
+ startChatFlow(messageContent);
2414
+ }
2415
+ }
2416
+ } catch (err) {
2417
+ console.error("[KiteChat] Failed to upload images:", err);
2418
+ } finally {
2419
+ setIsUploadingImage(false);
2420
+ setPendingImages([]);
2421
+ setInput("");
2422
+ }
2423
+ return;
2424
+ }
2227
2425
  if (!trimmed) return;
2228
2426
  if (isEscalated) {
2229
2427
  const userMessage = {
@@ -2388,7 +2586,8 @@ function ChatPanel({
2388
2586
  user_id: userId,
2389
2587
  org_id: orgId,
2390
2588
  user_name: userName,
2391
- user_email: userEmail
2589
+ user_email: userEmail,
2590
+ is_eval: isEval
2392
2591
  }),
2393
2592
  signal: controller.signal
2394
2593
  });
@@ -3281,19 +3480,23 @@ ${userText}`
3281
3480
  )
3282
3481
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3283
3482
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3284
- "input",
3483
+ "textarea",
3285
3484
  {
3286
3485
  ref: searchInputRef,
3287
- type: "text",
3288
3486
  value: searchInput,
3289
- onChange: (e) => setSearchInput(e.target.value),
3487
+ onChange: (e) => {
3488
+ setSearchInput(e.target.value);
3489
+ e.target.style.height = "auto";
3490
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
3491
+ },
3290
3492
  onKeyDown: (e) => {
3291
- if (e.key === "Enter" && searchInput.trim()) {
3493
+ if (e.key === "Enter" && !e.shiftKey && searchInput.trim()) {
3292
3494
  e.preventDefault();
3293
3495
  onOpen?.();
3294
3496
  startChatFlow(searchInput.trim());
3295
3497
  setSearchInput("");
3296
3498
  setSearchExpanded(false);
3499
+ e.currentTarget.style.height = "auto";
3297
3500
  } else if (e.key === "Escape") {
3298
3501
  setSearchExpanded(false);
3299
3502
  setSearchInput("");
@@ -3305,7 +3508,8 @@ ${userText}`
3305
3508
  }
3306
3509
  },
3307
3510
  placeholder: "Ask a question...",
3308
- className: "flex-1 text-sm text-gray-700 outline-none bg-transparent"
3511
+ rows: 1,
3512
+ className: "flex-1 text-sm text-gray-700 outline-none bg-transparent resize-none min-h-[20px] max-h-[120px]"
3309
3513
  }
3310
3514
  ),
3311
3515
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -3565,12 +3769,55 @@ ${userText}`
3565
3769
  return null;
3566
3770
  }
3567
3771
  if (isUser) {
3568
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: `flex justify-end ${isRoleChange ? "mt-3" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[260px] rounded-2xl rounded-br-md bg-gray-900 px-3 py-2 text-sm text-white shadow-sm", children: message.content }) }, message.id);
3772
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-end gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3773
+ message.imageUrls && message.imageUrls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-1 justify-end max-w-[260px]", children: message.imageUrls.map((url, imgIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3774
+ "button",
3775
+ {
3776
+ onClick: () => setLightboxImageUrl(url),
3777
+ className: "block cursor-pointer",
3778
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3779
+ "img",
3780
+ {
3781
+ src: url,
3782
+ alt: `Attachment ${imgIndex + 1}`,
3783
+ className: "max-h-32 max-w-[200px] rounded-lg object-cover border border-gray-700 hover:opacity-90 transition-opacity"
3784
+ }
3785
+ )
3786
+ },
3787
+ imgIndex
3788
+ )) }),
3789
+ message.content && !message.content.match(/^!\[image\]\([^)]+\)(\n!\[image\]\([^)]+\))*$/) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[260px] rounded-2xl rounded-br-md bg-gray-900 px-3 py-2 text-sm text-white shadow-sm", children: message.content.replace(/!\[image\]\([^)]+\)\n*/g, "").trim() })
3790
+ ] }, message.id);
3569
3791
  }
3570
3792
  if (message.role === "agent") {
3571
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start ${isRoleChange ? "mt-3" : ""}`, children: [
3793
+ const imageRegex = /!\[image\]\(([^)]+)\)/g;
3794
+ const extractedImageUrls = [];
3795
+ let match;
3796
+ const contentStr = message.content || "";
3797
+ while ((match = imageRegex.exec(contentStr)) !== null) {
3798
+ extractedImageUrls.push(match[1]);
3799
+ }
3800
+ const agentImageUrls = message.imageUrls || extractedImageUrls;
3801
+ const agentTextContent = contentStr.replace(/!\[image\]\([^)]+\)\n*/g, "").trim();
3802
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3572
3803
  isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-1 ml-1", children: "Agent" }),
3573
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[300px] rounded-2xl rounded-bl-md bg-gray-100 px-4 py-3 text-sm text-gray-700", children: message.content })
3804
+ agentImageUrls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-1 justify-start max-w-[300px]", children: agentImageUrls.map((url, imgIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3805
+ "button",
3806
+ {
3807
+ onClick: () => setLightboxImageUrl(url),
3808
+ className: "block cursor-pointer",
3809
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3810
+ "img",
3811
+ {
3812
+ src: url,
3813
+ alt: `Attachment ${imgIndex + 1}`,
3814
+ className: "max-h-32 max-w-[200px] rounded-lg object-cover border border-gray-300 hover:opacity-90 transition-opacity"
3815
+ }
3816
+ )
3817
+ },
3818
+ imgIndex
3819
+ )) }),
3820
+ agentTextContent && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[300px] rounded-2xl rounded-bl-md bg-gray-100 px-4 py-3 text-sm text-gray-700", children: agentTextContent })
3574
3821
  ] }, message.id);
3575
3822
  }
3576
3823
  if (message.kind === "searchSummary") {
@@ -4606,131 +4853,183 @@ ${userText}`
4606
4853
  progressSteps
4607
4854
  }
4608
4855
  ) }),
4609
- isEscalated && agentIsTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TypingIndicator, {}) }),
4856
+ isEscalated && agentIsTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex items-start mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "bg-gray-200 rounded-2xl rounded-bl-md px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TypingIndicator, {}) }) }),
4610
4857
  !activeGuide && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
4611
4858
  ] }) }) }) })
4612
4859
  }
4613
4860
  ),
4614
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "px-4 py-3 bg-white shrink-0", children: [
4615
- pendingFile && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex items-center gap-2 rounded-xl bg-blue-50 border border-blue-200 px-3 py-2", children: [
4616
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4617
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4618
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4619
- "button",
4620
- {
4621
- type: "button",
4622
- onClick: () => {
4623
- setPendingFile(null);
4624
- if (fileInputRef.current) fileInputRef.current.value = "";
4625
- },
4626
- className: "text-blue-600 hover:text-blue-800",
4627
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4628
- }
4629
- )
4630
- ] }),
4631
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4632
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4633
- "input",
4634
- {
4635
- ref: fileInputRef,
4636
- type: "file",
4637
- accept: ".csv",
4638
- className: "hidden",
4639
- onChange: (e) => {
4640
- const file = e.target.files?.[0];
4641
- if (file) {
4642
- setPendingFile(file);
4861
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4862
+ "div",
4863
+ {
4864
+ className: `px-4 py-3 bg-white shrink-0 ${isDragOver ? "ring-2 ring-blue-400 ring-inset bg-blue-50" : ""}`,
4865
+ onDragOver: handleDragOver,
4866
+ onDragLeave: handleDragLeave,
4867
+ onDrop: handleDrop,
4868
+ children: [
4869
+ pendingFile && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex items-center gap-2 rounded-xl bg-blue-50 border border-blue-200 px-3 py-2", children: [
4870
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4871
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4872
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4873
+ "button",
4874
+ {
4875
+ type: "button",
4876
+ onClick: () => {
4877
+ setPendingFile(null);
4878
+ if (fileInputRef.current) fileInputRef.current.value = "";
4879
+ },
4880
+ className: "text-blue-600 hover:text-blue-800",
4881
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4643
4882
  }
4644
- }
4645
- }
4646
- ),
4647
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex w-full items-start gap-2 rounded-xl border border-gray-200 bg-white px-3 py-2 shadow-sm", children: [
4648
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4649
- Button,
4650
- {
4651
- type: "button",
4652
- size: "icon",
4653
- variant: "ghost",
4654
- onClick: () => fileInputRef.current?.click(),
4655
- className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
4656
- title: "Upload CSV for bulk operations",
4657
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
4658
- }
4659
- ),
4660
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4661
- "textarea",
4662
- {
4663
- placeholder: pendingFile ? "Describe what to do with this CSV..." : "Ask anything...",
4664
- value: input,
4665
- onChange: (e) => {
4666
- setInput(e.target.value);
4667
- if (e.target.value.length > 0) {
4668
- handleTypingStart();
4883
+ )
4884
+ ] }),
4885
+ pendingImages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex flex-wrap gap-2", children: [
4886
+ pendingImages.map((img, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative group", children: [
4887
+ img.preview ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4888
+ "img",
4889
+ {
4890
+ src: img.preview,
4891
+ alt: `Preview ${index + 1}`,
4892
+ className: "h-16 w-16 object-cover rounded-lg border border-gray-200"
4669
4893
  }
4670
- },
4671
- rows: 1,
4672
- className: "flex-1 border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 text-sm placeholder:text-gray-400 resize-none overflow-hidden outline-none",
4673
- style: { minHeight: "20px", maxHeight: "120px" },
4674
- onInput: (e) => {
4675
- const target = e.target;
4676
- target.style.height = "auto";
4677
- target.style.height = Math.min(target.scrollHeight, 120) + "px";
4678
- },
4679
- onKeyDown: (e) => {
4680
- if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4681
- e.preventDefault();
4682
- if (input.trim() || pendingFile) {
4683
- const form = e.currentTarget.closest("form");
4684
- if (form) {
4685
- form.requestSubmit();
4686
- }
4687
- }
4688
- return;
4894
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-16 w-16 rounded-lg border border-gray-200 bg-gray-100 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-6 w-6 text-gray-400" }) }),
4895
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4896
+ "button",
4897
+ {
4898
+ type: "button",
4899
+ onClick: () => {
4900
+ if (img.preview) URL.revokeObjectURL(img.preview);
4901
+ setPendingImages((prev) => prev.filter((_, i) => i !== index));
4902
+ },
4903
+ className: "absolute -top-1 -right-1 h-4 w-4 rounded-full bg-gray-800 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity",
4904
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-2.5 w-2.5" })
4689
4905
  }
4690
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4691
- const currentBulkSession = pendingBulkSessionRef.current;
4692
- if (currentBulkSession) {
4693
- e.preventDefault();
4694
- e.stopPropagation();
4695
- confirmBulkOperation(currentBulkSession);
4696
- return;
4697
- }
4698
- if (pendingAction) {
4699
- e.preventDefault();
4700
- e.stopPropagation();
4701
- handleActionSubmit();
4702
- return;
4703
- }
4704
- if (pendingNavigation) {
4705
- e.preventDefault();
4706
- e.stopPropagation();
4707
- handleConfirmNavigation(pendingNavigation);
4708
- return;
4709
- }
4710
- const currentGuide = activeGuideRef.current;
4711
- if (currentGuide) {
4712
- e.preventDefault();
4713
- e.stopPropagation();
4714
- advanceGuide();
4715
- return;
4906
+ )
4907
+ ] }, index)),
4908
+ isUploadingImage && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-16 w-16 rounded-lg border border-gray-200 bg-gray-50 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }) })
4909
+ ] }),
4910
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4911
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4912
+ "input",
4913
+ {
4914
+ ref: fileInputRef,
4915
+ type: "file",
4916
+ accept: ".csv",
4917
+ className: "hidden",
4918
+ onChange: (e) => {
4919
+ const file = e.target.files?.[0];
4920
+ if (file) {
4921
+ setPendingFile(file);
4716
4922
  }
4717
4923
  }
4718
4924
  }
4719
- }
4720
- ),
4721
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4722
- Button,
4723
- {
4724
- type: "submit",
4725
- size: "icon",
4726
- disabled: !input.trim() && !pendingFile || isWaitingForAuth,
4727
- className: "h-6 w-6 rounded-full bg-gray-900 hover:bg-gray-800 disabled:bg-gray-300",
4728
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5" })
4729
- }
4730
- )
4731
- ] })
4732
- ] })
4733
- ] }),
4925
+ ),
4926
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4927
+ "input",
4928
+ {
4929
+ ref: imageInputRef,
4930
+ type: "file",
4931
+ accept: "image/*,.pdf",
4932
+ multiple: true,
4933
+ className: "hidden",
4934
+ onChange: (e) => handleImageSelect(e.target.files)
4935
+ }
4936
+ ),
4937
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex w-full items-start gap-2 rounded-xl border bg-white px-3 py-2 shadow-sm ${isDragOver ? "border-blue-400" : "border-gray-200"}`, children: [
4938
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4939
+ Button,
4940
+ {
4941
+ type: "button",
4942
+ size: "icon",
4943
+ variant: "ghost",
4944
+ onClick: () => {
4945
+ if (isEscalated) {
4946
+ imageInputRef.current?.click();
4947
+ } else {
4948
+ fileInputRef.current?.click();
4949
+ }
4950
+ },
4951
+ className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
4952
+ title: isEscalated ? "Attach image or file" : "Upload CSV for bulk operations",
4953
+ disabled: isUploadingImage,
4954
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
4955
+ }
4956
+ ),
4957
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4958
+ "textarea",
4959
+ {
4960
+ placeholder: pendingFile ? "Describe what to do with this CSV..." : pendingImages.length > 0 ? "Add a message (optional)..." : isDragOver ? "Drop files here..." : "Ask anything...",
4961
+ value: input,
4962
+ onChange: (e) => {
4963
+ setInput(e.target.value);
4964
+ if (e.target.value.length > 0) {
4965
+ handleTypingStart();
4966
+ }
4967
+ },
4968
+ rows: 1,
4969
+ className: "flex-1 border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 text-sm placeholder:text-gray-400 resize-none overflow-hidden outline-none",
4970
+ style: { minHeight: "20px", maxHeight: "120px" },
4971
+ onInput: (e) => {
4972
+ const target = e.target;
4973
+ target.style.height = "auto";
4974
+ target.style.height = Math.min(target.scrollHeight, 120) + "px";
4975
+ },
4976
+ onKeyDown: (e) => {
4977
+ if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4978
+ e.preventDefault();
4979
+ if (input.trim() || pendingFile || pendingImages.length > 0) {
4980
+ const form = e.currentTarget.closest("form");
4981
+ if (form) {
4982
+ form.requestSubmit();
4983
+ }
4984
+ }
4985
+ return;
4986
+ }
4987
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4988
+ const currentBulkSession = pendingBulkSessionRef.current;
4989
+ if (currentBulkSession) {
4990
+ e.preventDefault();
4991
+ e.stopPropagation();
4992
+ confirmBulkOperation(currentBulkSession);
4993
+ return;
4994
+ }
4995
+ if (pendingAction) {
4996
+ e.preventDefault();
4997
+ e.stopPropagation();
4998
+ handleActionSubmit();
4999
+ return;
5000
+ }
5001
+ if (pendingNavigation) {
5002
+ e.preventDefault();
5003
+ e.stopPropagation();
5004
+ handleConfirmNavigation(pendingNavigation);
5005
+ return;
5006
+ }
5007
+ const currentGuide = activeGuideRef.current;
5008
+ if (currentGuide) {
5009
+ e.preventDefault();
5010
+ e.stopPropagation();
5011
+ advanceGuide();
5012
+ return;
5013
+ }
5014
+ }
5015
+ }
5016
+ }
5017
+ ),
5018
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5019
+ Button,
5020
+ {
5021
+ type: "submit",
5022
+ size: "icon",
5023
+ disabled: !input.trim() && !pendingFile || isWaitingForAuth,
5024
+ className: "h-6 w-6 rounded-full bg-gray-700 hover:bg-gray-600 disabled:bg-gray-300",
5025
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5 text-white" })
5026
+ }
5027
+ )
5028
+ ] })
5029
+ ] })
5030
+ ]
5031
+ }
5032
+ ),
4734
5033
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4735
5034
  GuideCursor,
4736
5035
  {
@@ -4739,6 +5038,13 @@ ${userText}`
4739
5038
  visible: cursorState.visible,
4740
5039
  onClick: cursorState.onClick
4741
5040
  }
5041
+ ),
5042
+ lightboxImageUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5043
+ ImageLightbox,
5044
+ {
5045
+ imageUrl: lightboxImageUrl,
5046
+ onClose: () => setLightboxImageUrl(null)
5047
+ }
4742
5048
  )
4743
5049
  ]
4744
5050
  }
@@ -4763,7 +5069,8 @@ function ChatPanelWithToggle({
4763
5069
  initialCorner,
4764
5070
  onCornerChange,
4765
5071
  productBackendUrl,
4766
- getAuthHeaders
5072
+ getAuthHeaders,
5073
+ isEval
4767
5074
  }) {
4768
5075
  const [internalIsOpen, setInternalIsOpen] = React6.useState(defaultOpen);
4769
5076
  const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
@@ -4794,7 +5101,8 @@ function ChatPanelWithToggle({
4794
5101
  initialCorner,
4795
5102
  onCornerChange,
4796
5103
  productBackendUrl,
4797
- getAuthHeaders
5104
+ getAuthHeaders,
5105
+ isEval
4798
5106
  }
4799
5107
  );
4800
5108
  }