@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/index.cjs CHANGED
@@ -1120,29 +1120,29 @@ function DataRenderer({ type, data }) {
1120
1120
  // src/components/TypingIndicator.tsx
1121
1121
  var import_jsx_runtime9 = require("react/jsx-runtime");
1122
1122
  function TypingIndicator({ className = "" }) {
1123
- 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: [
1123
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `flex items-center gap-1.5 ${className}`, children: [
1124
1124
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1125
1125
  "span",
1126
1126
  {
1127
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1127
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1128
1128
  style: { animationDelay: "0ms", animationDuration: "600ms" }
1129
1129
  }
1130
1130
  ),
1131
1131
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1132
1132
  "span",
1133
1133
  {
1134
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1134
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1135
1135
  style: { animationDelay: "150ms", animationDuration: "600ms" }
1136
1136
  }
1137
1137
  ),
1138
1138
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1139
1139
  "span",
1140
1140
  {
1141
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1141
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1142
1142
  style: { animationDelay: "300ms", animationDuration: "600ms" }
1143
1143
  }
1144
1144
  )
1145
- ] }) });
1145
+ ] });
1146
1146
  }
1147
1147
 
1148
1148
  // src/ChatPanel.tsx
@@ -1591,6 +1591,78 @@ function AuthErrorState({
1591
1591
  )
1592
1592
  ] });
1593
1593
  }
1594
+ function ImageLightbox({
1595
+ imageUrl,
1596
+ onClose
1597
+ }) {
1598
+ const handleDownload = async () => {
1599
+ try {
1600
+ const response = await fetch(imageUrl);
1601
+ const blob = await response.blob();
1602
+ const url = window.URL.createObjectURL(blob);
1603
+ const a = document.createElement("a");
1604
+ a.href = url;
1605
+ const urlParts = imageUrl.split("/");
1606
+ const filename = urlParts[urlParts.length - 1] || "image.jpg";
1607
+ a.download = filename;
1608
+ document.body.appendChild(a);
1609
+ a.click();
1610
+ document.body.removeChild(a);
1611
+ window.URL.revokeObjectURL(url);
1612
+ } catch (error) {
1613
+ console.error("Failed to download image:", error);
1614
+ window.open(imageUrl, "_blank");
1615
+ }
1616
+ };
1617
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1618
+ "div",
1619
+ {
1620
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/80",
1621
+ onClick: onClose,
1622
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1623
+ "div",
1624
+ {
1625
+ className: "relative max-w-[90vw] max-h-[90vh] flex flex-col items-center",
1626
+ onClick: (e) => e.stopPropagation(),
1627
+ children: [
1628
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1629
+ "img",
1630
+ {
1631
+ src: imageUrl,
1632
+ alt: "Full size preview",
1633
+ className: "max-w-full max-h-[80vh] object-contain rounded-lg"
1634
+ }
1635
+ ),
1636
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-3 mt-4", children: [
1637
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1638
+ "button",
1639
+ {
1640
+ onClick: handleDownload,
1641
+ 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",
1642
+ children: [
1643
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Download, { className: "h-4 w-4" }),
1644
+ "Download"
1645
+ ]
1646
+ }
1647
+ ),
1648
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1649
+ "button",
1650
+ {
1651
+ onClick: onClose,
1652
+ 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",
1653
+ children: [
1654
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" }),
1655
+ "Close"
1656
+ ]
1657
+ }
1658
+ )
1659
+ ] })
1660
+ ]
1661
+ }
1662
+ )
1663
+ }
1664
+ );
1665
+ }
1594
1666
  function ChatPanel({
1595
1667
  isOpen = true,
1596
1668
  onClose,
@@ -1611,7 +1683,8 @@ function ChatPanel({
1611
1683
  initialCorner = "bottom-left",
1612
1684
  onCornerChange,
1613
1685
  productBackendUrl,
1614
- getAuthHeaders
1686
+ getAuthHeaders,
1687
+ isEval = false
1615
1688
  } = {}) {
1616
1689
  const [messages, setMessages] = React6.useState(initialMessages);
1617
1690
  const [input, setInput] = React6.useState("");
@@ -1721,13 +1794,20 @@ function ChatPanel({
1721
1794
  }, [effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1722
1795
  React6.useEffect(() => {
1723
1796
  if (!isEscalated || !sessionId || !supabaseRef.current) {
1797
+ console.log("[KiteChat] Typing channel skip - isEscalated:", isEscalated, "sessionId:", sessionId, "supabase:", !!supabaseRef.current);
1724
1798
  return;
1725
1799
  }
1726
1800
  const channelName = `typing:${sessionId}`;
1727
- const channel = supabaseRef.current.channel(channelName);
1801
+ console.log("[KiteChat] Subscribing to typing channel:", channelName, "sessionId:", sessionId);
1802
+ const channel = supabaseRef.current.channel(channelName, {
1803
+ config: { broadcast: { self: true } }
1804
+ });
1728
1805
  channel.on("broadcast", { event: "typing" }, (payload) => {
1806
+ console.log("[KiteChat] Received typing event:", payload);
1729
1807
  const { sender, isTyping } = payload.payload;
1808
+ console.log("[KiteChat] Typing event - sender:", sender, "isTyping:", isTyping);
1730
1809
  if (sender === "agent") {
1810
+ console.log("[KiteChat] Setting agentIsTyping to:", isTyping);
1731
1811
  setAgentIsTyping(isTyping);
1732
1812
  if (isTyping) {
1733
1813
  if (typingTimeoutRef.current) {
@@ -1739,6 +1819,7 @@ function ChatPanel({
1739
1819
  }
1740
1820
  }
1741
1821
  }).subscribe((status) => {
1822
+ console.log("[KiteChat] Typing channel subscription status:", status);
1742
1823
  if (status === "SUBSCRIBED") {
1743
1824
  typingChannelRef.current = channel;
1744
1825
  console.log("[KiteChat] Typing channel subscribed successfully");
@@ -1753,7 +1834,7 @@ function ChatPanel({
1753
1834
  window.clearTimeout(typingTimeoutRef.current);
1754
1835
  }
1755
1836
  };
1756
- }, [isEscalated, sessionId]);
1837
+ }, [isEscalated, sessionId, effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1757
1838
  React6.useEffect(() => {
1758
1839
  if (!isOpen && isEscalated && supabaseRef.current && sessionId) {
1759
1840
  console.log("[KiteChat] Panel closed during live chat, marking disconnected");
@@ -1826,9 +1907,7 @@ function ChatPanel({
1826
1907
  markDisconnectedWithKeepalive();
1827
1908
  };
1828
1909
  const handleVisibilityChange = () => {
1829
- if (document.visibilityState === "hidden") {
1830
- markDisconnectedWithKeepalive();
1831
- } else if (document.visibilityState === "visible") {
1910
+ if (document.visibilityState === "visible") {
1832
1911
  markActive();
1833
1912
  }
1834
1913
  };
@@ -1966,6 +2045,77 @@ function ChatPanel({
1966
2045
  const [pendingBulkSession, setPendingBulkSession] = React6.useState(null);
1967
2046
  const pendingBulkSessionRef = React6.useRef(null);
1968
2047
  const fileInputRef = React6.useRef(null);
2048
+ const [pendingImages, setPendingImages] = React6.useState([]);
2049
+ const [isUploadingImage, setIsUploadingImage] = React6.useState(false);
2050
+ const [isDragOver, setIsDragOver] = React6.useState(false);
2051
+ const imageInputRef = React6.useRef(null);
2052
+ const [lightboxImageUrl, setLightboxImageUrl] = React6.useState(null);
2053
+ const uploadImageToStorage = React6.useCallback(async (file) => {
2054
+ if (!supabaseRef.current) {
2055
+ console.error("[KiteChat] Supabase client not available for file upload");
2056
+ return null;
2057
+ }
2058
+ const fileExt = file.name.split(".").pop();
2059
+ const fileName = `${sessionId}/${Date.now()}-${Math.random().toString(36).substring(7)}.${fileExt}`;
2060
+ const bucketName = "chat-attachments";
2061
+ try {
2062
+ const { data, error } = await supabaseRef.current.storage.from(bucketName).upload(fileName, file, {
2063
+ cacheControl: "3600",
2064
+ upsert: false
2065
+ });
2066
+ if (error) {
2067
+ console.error("[KiteChat] Upload error:", error);
2068
+ return null;
2069
+ }
2070
+ const { data: urlData } = supabaseRef.current.storage.from(bucketName).getPublicUrl(fileName);
2071
+ return urlData?.publicUrl || null;
2072
+ } catch (err) {
2073
+ console.error("[KiteChat] Upload failed:", err);
2074
+ return null;
2075
+ }
2076
+ }, [sessionId]);
2077
+ const handleImageSelect = React6.useCallback((files) => {
2078
+ if (!files) return;
2079
+ const imageFiles = Array.from(files).filter(
2080
+ (file) => file.type.startsWith("image/") || file.type === "application/pdf"
2081
+ );
2082
+ const newImages = imageFiles.map((file) => ({
2083
+ file,
2084
+ preview: file.type.startsWith("image/") ? URL.createObjectURL(file) : ""
2085
+ }));
2086
+ setPendingImages((prev) => [...prev, ...newImages]);
2087
+ }, []);
2088
+ const handleDragOver = React6.useCallback((e) => {
2089
+ e.preventDefault();
2090
+ e.stopPropagation();
2091
+ setIsDragOver(true);
2092
+ }, []);
2093
+ const handleDragLeave = React6.useCallback((e) => {
2094
+ e.preventDefault();
2095
+ e.stopPropagation();
2096
+ setIsDragOver(false);
2097
+ }, []);
2098
+ const handleDrop = React6.useCallback((e) => {
2099
+ e.preventDefault();
2100
+ e.stopPropagation();
2101
+ setIsDragOver(false);
2102
+ const files = e.dataTransfer.files;
2103
+ if (files.length > 0) {
2104
+ const csvFile = Array.from(files).find((f) => f.name.endsWith(".csv"));
2105
+ if (csvFile && !isEscalated) {
2106
+ setPendingFile(csvFile);
2107
+ } else {
2108
+ handleImageSelect(files);
2109
+ }
2110
+ }
2111
+ }, [isEscalated, handleImageSelect]);
2112
+ React6.useEffect(() => {
2113
+ return () => {
2114
+ pendingImages.forEach((img) => {
2115
+ if (img.preview) URL.revokeObjectURL(img.preview);
2116
+ });
2117
+ };
2118
+ }, [pendingImages]);
1969
2119
  const [searchExpanded, setSearchExpanded] = React6.useState(false);
1970
2120
  const [searchInput, setSearchInput] = React6.useState("");
1971
2121
  const searchInputRef = React6.useRef(null);
@@ -2022,6 +2172,11 @@ function ChatPanel({
2022
2172
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2023
2173
  }
2024
2174
  }, [messages, phase, activeGuide]);
2175
+ React6.useEffect(() => {
2176
+ if (isEscalated && agentIsTyping && !activeGuide) {
2177
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2178
+ }
2179
+ }, [isEscalated, agentIsTyping, activeGuide]);
2025
2180
  const latestBulkSummaryNavigation = React6.useMemo(() => {
2026
2181
  for (let i = messages.length - 1; i >= 0; i--) {
2027
2182
  const msg = messages[i];
@@ -2258,7 +2413,7 @@ function ChatPanel({
2258
2413
  setPanelView("landing");
2259
2414
  setCurrentFolderId(void 0);
2260
2415
  }
2261
- function handleSubmit(e) {
2416
+ async function handleSubmit(e) {
2262
2417
  e.preventDefault();
2263
2418
  const trimmed = input.trim();
2264
2419
  if (pendingFile) {
@@ -2278,6 +2433,49 @@ function ChatPanel({
2278
2433
  if (fileInputRef.current) fileInputRef.current.value = "";
2279
2434
  return;
2280
2435
  }
2436
+ if (pendingImages.length > 0) {
2437
+ setIsUploadingImage(true);
2438
+ try {
2439
+ const uploadedUrls = [];
2440
+ for (const img of pendingImages) {
2441
+ const url = await uploadImageToStorage(img.file);
2442
+ if (url) {
2443
+ uploadedUrls.push(url);
2444
+ }
2445
+ if (img.preview) URL.revokeObjectURL(img.preview);
2446
+ }
2447
+ if (uploadedUrls.length > 0) {
2448
+ const imageMarkdown = uploadedUrls.map((url) => `![image](${url})`).join("\n");
2449
+ const messageContent = trimmed ? `${trimmed}
2450
+
2451
+ ${imageMarkdown}` : imageMarkdown;
2452
+ const userMessage = {
2453
+ id: Date.now(),
2454
+ role: "user",
2455
+ content: messageContent,
2456
+ imageUrls: uploadedUrls
2457
+ };
2458
+ setMessages((prev) => [...prev, userMessage]);
2459
+ if (isEscalated) {
2460
+ sendEscalatedMessage(messageContent);
2461
+ sendTypingIndicator(false);
2462
+ if (userTypingTimeoutRef.current) {
2463
+ window.clearTimeout(userTypingTimeoutRef.current);
2464
+ userTypingTimeoutRef.current = null;
2465
+ }
2466
+ } else {
2467
+ startChatFlow(messageContent);
2468
+ }
2469
+ }
2470
+ } catch (err) {
2471
+ console.error("[KiteChat] Failed to upload images:", err);
2472
+ } finally {
2473
+ setIsUploadingImage(false);
2474
+ setPendingImages([]);
2475
+ setInput("");
2476
+ }
2477
+ return;
2478
+ }
2281
2479
  if (!trimmed) return;
2282
2480
  if (isEscalated) {
2283
2481
  const userMessage = {
@@ -2442,7 +2640,8 @@ function ChatPanel({
2442
2640
  user_id: userId,
2443
2641
  org_id: orgId,
2444
2642
  user_name: userName,
2445
- user_email: userEmail
2643
+ user_email: userEmail,
2644
+ is_eval: isEval
2446
2645
  }),
2447
2646
  signal: controller.signal
2448
2647
  });
@@ -3335,19 +3534,23 @@ ${userText}`
3335
3534
  )
3336
3535
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3337
3536
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3338
- "input",
3537
+ "textarea",
3339
3538
  {
3340
3539
  ref: searchInputRef,
3341
- type: "text",
3342
3540
  value: searchInput,
3343
- onChange: (e) => setSearchInput(e.target.value),
3541
+ onChange: (e) => {
3542
+ setSearchInput(e.target.value);
3543
+ e.target.style.height = "auto";
3544
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
3545
+ },
3344
3546
  onKeyDown: (e) => {
3345
- if (e.key === "Enter" && searchInput.trim()) {
3547
+ if (e.key === "Enter" && !e.shiftKey && searchInput.trim()) {
3346
3548
  e.preventDefault();
3347
3549
  onOpen?.();
3348
3550
  startChatFlow(searchInput.trim());
3349
3551
  setSearchInput("");
3350
3552
  setSearchExpanded(false);
3553
+ e.currentTarget.style.height = "auto";
3351
3554
  } else if (e.key === "Escape") {
3352
3555
  setSearchExpanded(false);
3353
3556
  setSearchInput("");
@@ -3359,7 +3562,8 @@ ${userText}`
3359
3562
  }
3360
3563
  },
3361
3564
  placeholder: "Ask a question...",
3362
- className: "flex-1 text-sm text-gray-700 outline-none bg-transparent"
3565
+ rows: 1,
3566
+ className: "flex-1 text-sm text-gray-700 outline-none bg-transparent resize-none min-h-[20px] max-h-[120px]"
3363
3567
  }
3364
3568
  ),
3365
3569
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -3619,12 +3823,55 @@ ${userText}`
3619
3823
  return null;
3620
3824
  }
3621
3825
  if (isUser) {
3622
- 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);
3826
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-end gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3827
+ 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)(
3828
+ "button",
3829
+ {
3830
+ onClick: () => setLightboxImageUrl(url),
3831
+ className: "block cursor-pointer",
3832
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3833
+ "img",
3834
+ {
3835
+ src: url,
3836
+ alt: `Attachment ${imgIndex + 1}`,
3837
+ className: "max-h-32 max-w-[200px] rounded-lg object-cover border border-gray-700 hover:opacity-90 transition-opacity"
3838
+ }
3839
+ )
3840
+ },
3841
+ imgIndex
3842
+ )) }),
3843
+ 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() })
3844
+ ] }, message.id);
3623
3845
  }
3624
3846
  if (message.role === "agent") {
3625
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start ${isRoleChange ? "mt-3" : ""}`, children: [
3847
+ const imageRegex = /!\[image\]\(([^)]+)\)/g;
3848
+ const extractedImageUrls = [];
3849
+ let match;
3850
+ const contentStr = message.content || "";
3851
+ while ((match = imageRegex.exec(contentStr)) !== null) {
3852
+ extractedImageUrls.push(match[1]);
3853
+ }
3854
+ const agentImageUrls = message.imageUrls || extractedImageUrls;
3855
+ const agentTextContent = contentStr.replace(/!\[image\]\([^)]+\)\n*/g, "").trim();
3856
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3626
3857
  isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-1 ml-1", children: "Agent" }),
3627
- /* @__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 })
3858
+ 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)(
3859
+ "button",
3860
+ {
3861
+ onClick: () => setLightboxImageUrl(url),
3862
+ className: "block cursor-pointer",
3863
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3864
+ "img",
3865
+ {
3866
+ src: url,
3867
+ alt: `Attachment ${imgIndex + 1}`,
3868
+ className: "max-h-32 max-w-[200px] rounded-lg object-cover border border-gray-300 hover:opacity-90 transition-opacity"
3869
+ }
3870
+ )
3871
+ },
3872
+ imgIndex
3873
+ )) }),
3874
+ 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 })
3628
3875
  ] }, message.id);
3629
3876
  }
3630
3877
  if (message.kind === "searchSummary") {
@@ -4660,131 +4907,183 @@ ${userText}`
4660
4907
  progressSteps
4661
4908
  }
4662
4909
  ) }),
4663
- isEscalated && agentIsTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TypingIndicator, {}) }),
4910
+ 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, {}) }) }),
4664
4911
  !activeGuide && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
4665
4912
  ] }) }) }) })
4666
4913
  }
4667
4914
  ),
4668
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "px-4 py-3 bg-white shrink-0", children: [
4669
- 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: [
4670
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4671
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4672
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4673
- "button",
4674
- {
4675
- type: "button",
4676
- onClick: () => {
4677
- setPendingFile(null);
4678
- if (fileInputRef.current) fileInputRef.current.value = "";
4679
- },
4680
- className: "text-blue-600 hover:text-blue-800",
4681
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4682
- }
4683
- )
4684
- ] }),
4685
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4686
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4687
- "input",
4688
- {
4689
- ref: fileInputRef,
4690
- type: "file",
4691
- accept: ".csv",
4692
- className: "hidden",
4693
- onChange: (e) => {
4694
- const file = e.target.files?.[0];
4695
- if (file) {
4696
- setPendingFile(file);
4915
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4916
+ "div",
4917
+ {
4918
+ className: `px-4 py-3 bg-white shrink-0 ${isDragOver ? "ring-2 ring-blue-400 ring-inset bg-blue-50" : ""}`,
4919
+ onDragOver: handleDragOver,
4920
+ onDragLeave: handleDragLeave,
4921
+ onDrop: handleDrop,
4922
+ children: [
4923
+ 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: [
4924
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4925
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4926
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4927
+ "button",
4928
+ {
4929
+ type: "button",
4930
+ onClick: () => {
4931
+ setPendingFile(null);
4932
+ if (fileInputRef.current) fileInputRef.current.value = "";
4933
+ },
4934
+ className: "text-blue-600 hover:text-blue-800",
4935
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4697
4936
  }
4698
- }
4699
- }
4700
- ),
4701
- /* @__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: [
4702
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4703
- Button,
4704
- {
4705
- type: "button",
4706
- size: "icon",
4707
- variant: "ghost",
4708
- onClick: () => fileInputRef.current?.click(),
4709
- className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
4710
- title: "Upload CSV for bulk operations",
4711
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
4712
- }
4713
- ),
4714
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4715
- "textarea",
4716
- {
4717
- placeholder: pendingFile ? "Describe what to do with this CSV..." : "Ask anything...",
4718
- value: input,
4719
- onChange: (e) => {
4720
- setInput(e.target.value);
4721
- if (e.target.value.length > 0) {
4722
- handleTypingStart();
4937
+ )
4938
+ ] }),
4939
+ pendingImages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex flex-wrap gap-2", children: [
4940
+ pendingImages.map((img, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative group", children: [
4941
+ img.preview ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4942
+ "img",
4943
+ {
4944
+ src: img.preview,
4945
+ alt: `Preview ${index + 1}`,
4946
+ className: "h-16 w-16 object-cover rounded-lg border border-gray-200"
4723
4947
  }
4724
- },
4725
- rows: 1,
4726
- 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",
4727
- style: { minHeight: "20px", maxHeight: "120px" },
4728
- onInput: (e) => {
4729
- const target = e.target;
4730
- target.style.height = "auto";
4731
- target.style.height = Math.min(target.scrollHeight, 120) + "px";
4732
- },
4733
- onKeyDown: (e) => {
4734
- if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4735
- e.preventDefault();
4736
- if (input.trim() || pendingFile) {
4737
- const form = e.currentTarget.closest("form");
4738
- if (form) {
4739
- form.requestSubmit();
4740
- }
4741
- }
4742
- return;
4948
+ ) : /* @__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" }) }),
4949
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4950
+ "button",
4951
+ {
4952
+ type: "button",
4953
+ onClick: () => {
4954
+ if (img.preview) URL.revokeObjectURL(img.preview);
4955
+ setPendingImages((prev) => prev.filter((_, i) => i !== index));
4956
+ },
4957
+ 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",
4958
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-2.5 w-2.5" })
4743
4959
  }
4744
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4745
- const currentBulkSession = pendingBulkSessionRef.current;
4746
- if (currentBulkSession) {
4747
- e.preventDefault();
4748
- e.stopPropagation();
4749
- confirmBulkOperation(currentBulkSession);
4750
- return;
4751
- }
4752
- if (pendingAction) {
4753
- e.preventDefault();
4754
- e.stopPropagation();
4755
- handleActionSubmit();
4756
- return;
4757
- }
4758
- if (pendingNavigation) {
4759
- e.preventDefault();
4760
- e.stopPropagation();
4761
- handleConfirmNavigation(pendingNavigation);
4762
- return;
4763
- }
4764
- const currentGuide = activeGuideRef.current;
4765
- if (currentGuide) {
4766
- e.preventDefault();
4767
- e.stopPropagation();
4768
- advanceGuide();
4769
- return;
4960
+ )
4961
+ ] }, index)),
4962
+ 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" }) })
4963
+ ] }),
4964
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4965
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4966
+ "input",
4967
+ {
4968
+ ref: fileInputRef,
4969
+ type: "file",
4970
+ accept: ".csv",
4971
+ className: "hidden",
4972
+ onChange: (e) => {
4973
+ const file = e.target.files?.[0];
4974
+ if (file) {
4975
+ setPendingFile(file);
4770
4976
  }
4771
4977
  }
4772
4978
  }
4773
- }
4774
- ),
4775
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4776
- Button,
4777
- {
4778
- type: "submit",
4779
- size: "icon",
4780
- disabled: !input.trim() && !pendingFile || isWaitingForAuth,
4781
- className: "h-6 w-6 rounded-full bg-gray-900 hover:bg-gray-800 disabled:bg-gray-300",
4782
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5" })
4783
- }
4784
- )
4785
- ] })
4786
- ] })
4787
- ] }),
4979
+ ),
4980
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4981
+ "input",
4982
+ {
4983
+ ref: imageInputRef,
4984
+ type: "file",
4985
+ accept: "image/*,.pdf",
4986
+ multiple: true,
4987
+ className: "hidden",
4988
+ onChange: (e) => handleImageSelect(e.target.files)
4989
+ }
4990
+ ),
4991
+ /* @__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: [
4992
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4993
+ Button,
4994
+ {
4995
+ type: "button",
4996
+ size: "icon",
4997
+ variant: "ghost",
4998
+ onClick: () => {
4999
+ if (isEscalated) {
5000
+ imageInputRef.current?.click();
5001
+ } else {
5002
+ fileInputRef.current?.click();
5003
+ }
5004
+ },
5005
+ className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
5006
+ title: isEscalated ? "Attach image or file" : "Upload CSV for bulk operations",
5007
+ disabled: isUploadingImage,
5008
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
5009
+ }
5010
+ ),
5011
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5012
+ "textarea",
5013
+ {
5014
+ placeholder: pendingFile ? "Describe what to do with this CSV..." : pendingImages.length > 0 ? "Add a message (optional)..." : isDragOver ? "Drop files here..." : "Ask anything...",
5015
+ value: input,
5016
+ onChange: (e) => {
5017
+ setInput(e.target.value);
5018
+ if (e.target.value.length > 0) {
5019
+ handleTypingStart();
5020
+ }
5021
+ },
5022
+ rows: 1,
5023
+ 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",
5024
+ style: { minHeight: "20px", maxHeight: "120px" },
5025
+ onInput: (e) => {
5026
+ const target = e.target;
5027
+ target.style.height = "auto";
5028
+ target.style.height = Math.min(target.scrollHeight, 120) + "px";
5029
+ },
5030
+ onKeyDown: (e) => {
5031
+ if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
5032
+ e.preventDefault();
5033
+ if (input.trim() || pendingFile || pendingImages.length > 0) {
5034
+ const form = e.currentTarget.closest("form");
5035
+ if (form) {
5036
+ form.requestSubmit();
5037
+ }
5038
+ }
5039
+ return;
5040
+ }
5041
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
5042
+ const currentBulkSession = pendingBulkSessionRef.current;
5043
+ if (currentBulkSession) {
5044
+ e.preventDefault();
5045
+ e.stopPropagation();
5046
+ confirmBulkOperation(currentBulkSession);
5047
+ return;
5048
+ }
5049
+ if (pendingAction) {
5050
+ e.preventDefault();
5051
+ e.stopPropagation();
5052
+ handleActionSubmit();
5053
+ return;
5054
+ }
5055
+ if (pendingNavigation) {
5056
+ e.preventDefault();
5057
+ e.stopPropagation();
5058
+ handleConfirmNavigation(pendingNavigation);
5059
+ return;
5060
+ }
5061
+ const currentGuide = activeGuideRef.current;
5062
+ if (currentGuide) {
5063
+ e.preventDefault();
5064
+ e.stopPropagation();
5065
+ advanceGuide();
5066
+ return;
5067
+ }
5068
+ }
5069
+ }
5070
+ }
5071
+ ),
5072
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5073
+ Button,
5074
+ {
5075
+ type: "submit",
5076
+ size: "icon",
5077
+ disabled: !input.trim() && !pendingFile || isWaitingForAuth,
5078
+ className: "h-6 w-6 rounded-full bg-gray-700 hover:bg-gray-600 disabled:bg-gray-300",
5079
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5 text-white" })
5080
+ }
5081
+ )
5082
+ ] })
5083
+ ] })
5084
+ ]
5085
+ }
5086
+ ),
4788
5087
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4789
5088
  GuideCursor,
4790
5089
  {
@@ -4793,6 +5092,13 @@ ${userText}`
4793
5092
  visible: cursorState.visible,
4794
5093
  onClick: cursorState.onClick
4795
5094
  }
5095
+ ),
5096
+ lightboxImageUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5097
+ ImageLightbox,
5098
+ {
5099
+ imageUrl: lightboxImageUrl,
5100
+ onClose: () => setLightboxImageUrl(null)
5101
+ }
4796
5102
  )
4797
5103
  ]
4798
5104
  }
@@ -4837,7 +5143,8 @@ function ChatPanelWithToggle({
4837
5143
  initialCorner,
4838
5144
  onCornerChange,
4839
5145
  productBackendUrl,
4840
- getAuthHeaders
5146
+ getAuthHeaders,
5147
+ isEval
4841
5148
  }) {
4842
5149
  const [internalIsOpen, setInternalIsOpen] = React6.useState(defaultOpen);
4843
5150
  const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
@@ -4868,7 +5175,8 @@ function ChatPanelWithToggle({
4868
5175
  initialCorner,
4869
5176
  onCornerChange,
4870
5177
  productBackendUrl,
4871
- getAuthHeaders
5178
+ getAuthHeaders,
5179
+ isEval
4872
5180
  }
4873
5181
  );
4874
5182
  }