@kite-copilot/chat-panel 0.2.52 → 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");
@@ -1964,6 +2045,77 @@ function ChatPanel({
1964
2045
  const [pendingBulkSession, setPendingBulkSession] = React6.useState(null);
1965
2046
  const pendingBulkSessionRef = React6.useRef(null);
1966
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]);
1967
2119
  const [searchExpanded, setSearchExpanded] = React6.useState(false);
1968
2120
  const [searchInput, setSearchInput] = React6.useState("");
1969
2121
  const searchInputRef = React6.useRef(null);
@@ -2020,6 +2172,11 @@ function ChatPanel({
2020
2172
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2021
2173
  }
2022
2174
  }, [messages, phase, activeGuide]);
2175
+ React6.useEffect(() => {
2176
+ if (isEscalated && agentIsTyping && !activeGuide) {
2177
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2178
+ }
2179
+ }, [isEscalated, agentIsTyping, activeGuide]);
2023
2180
  const latestBulkSummaryNavigation = React6.useMemo(() => {
2024
2181
  for (let i = messages.length - 1; i >= 0; i--) {
2025
2182
  const msg = messages[i];
@@ -2256,7 +2413,7 @@ function ChatPanel({
2256
2413
  setPanelView("landing");
2257
2414
  setCurrentFolderId(void 0);
2258
2415
  }
2259
- function handleSubmit(e) {
2416
+ async function handleSubmit(e) {
2260
2417
  e.preventDefault();
2261
2418
  const trimmed = input.trim();
2262
2419
  if (pendingFile) {
@@ -2276,6 +2433,49 @@ function ChatPanel({
2276
2433
  if (fileInputRef.current) fileInputRef.current.value = "";
2277
2434
  return;
2278
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
+ }
2279
2479
  if (!trimmed) return;
2280
2480
  if (isEscalated) {
2281
2481
  const userMessage = {
@@ -2440,7 +2640,8 @@ function ChatPanel({
2440
2640
  user_id: userId,
2441
2641
  org_id: orgId,
2442
2642
  user_name: userName,
2443
- user_email: userEmail
2643
+ user_email: userEmail,
2644
+ is_eval: isEval
2444
2645
  }),
2445
2646
  signal: controller.signal
2446
2647
  });
@@ -3333,19 +3534,23 @@ ${userText}`
3333
3534
  )
3334
3535
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3335
3536
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3336
- "input",
3537
+ "textarea",
3337
3538
  {
3338
3539
  ref: searchInputRef,
3339
- type: "text",
3340
3540
  value: searchInput,
3341
- 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
+ },
3342
3546
  onKeyDown: (e) => {
3343
- if (e.key === "Enter" && searchInput.trim()) {
3547
+ if (e.key === "Enter" && !e.shiftKey && searchInput.trim()) {
3344
3548
  e.preventDefault();
3345
3549
  onOpen?.();
3346
3550
  startChatFlow(searchInput.trim());
3347
3551
  setSearchInput("");
3348
3552
  setSearchExpanded(false);
3553
+ e.currentTarget.style.height = "auto";
3349
3554
  } else if (e.key === "Escape") {
3350
3555
  setSearchExpanded(false);
3351
3556
  setSearchInput("");
@@ -3357,7 +3562,8 @@ ${userText}`
3357
3562
  }
3358
3563
  },
3359
3564
  placeholder: "Ask a question...",
3360
- 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]"
3361
3567
  }
3362
3568
  ),
3363
3569
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -3617,12 +3823,55 @@ ${userText}`
3617
3823
  return null;
3618
3824
  }
3619
3825
  if (isUser) {
3620
- 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);
3621
3845
  }
3622
3846
  if (message.role === "agent") {
3623
- 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: [
3624
3857
  isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-1 ml-1", children: "Agent" }),
3625
- /* @__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 })
3626
3875
  ] }, message.id);
3627
3876
  }
3628
3877
  if (message.kind === "searchSummary") {
@@ -4658,131 +4907,183 @@ ${userText}`
4658
4907
  progressSteps
4659
4908
  }
4660
4909
  ) }),
4661
- 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, {}) }) }),
4662
4911
  !activeGuide && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
4663
4912
  ] }) }) }) })
4664
4913
  }
4665
4914
  ),
4666
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "px-4 py-3 bg-white shrink-0", children: [
4667
- 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: [
4668
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4669
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4670
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4671
- "button",
4672
- {
4673
- type: "button",
4674
- onClick: () => {
4675
- setPendingFile(null);
4676
- if (fileInputRef.current) fileInputRef.current.value = "";
4677
- },
4678
- className: "text-blue-600 hover:text-blue-800",
4679
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4680
- }
4681
- )
4682
- ] }),
4683
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4684
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4685
- "input",
4686
- {
4687
- ref: fileInputRef,
4688
- type: "file",
4689
- accept: ".csv",
4690
- className: "hidden",
4691
- onChange: (e) => {
4692
- const file = e.target.files?.[0];
4693
- if (file) {
4694
- 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" })
4695
4936
  }
4696
- }
4697
- }
4698
- ),
4699
- /* @__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: [
4700
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4701
- Button,
4702
- {
4703
- type: "button",
4704
- size: "icon",
4705
- variant: "ghost",
4706
- onClick: () => fileInputRef.current?.click(),
4707
- className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
4708
- title: "Upload CSV for bulk operations",
4709
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
4710
- }
4711
- ),
4712
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4713
- "textarea",
4714
- {
4715
- placeholder: pendingFile ? "Describe what to do with this CSV..." : "Ask anything...",
4716
- value: input,
4717
- onChange: (e) => {
4718
- setInput(e.target.value);
4719
- if (e.target.value.length > 0) {
4720
- 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"
4721
4947
  }
4722
- },
4723
- rows: 1,
4724
- 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",
4725
- style: { minHeight: "20px", maxHeight: "120px" },
4726
- onInput: (e) => {
4727
- const target = e.target;
4728
- target.style.height = "auto";
4729
- target.style.height = Math.min(target.scrollHeight, 120) + "px";
4730
- },
4731
- onKeyDown: (e) => {
4732
- if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4733
- e.preventDefault();
4734
- if (input.trim() || pendingFile) {
4735
- const form = e.currentTarget.closest("form");
4736
- if (form) {
4737
- form.requestSubmit();
4738
- }
4739
- }
4740
- 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" })
4741
4959
  }
4742
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4743
- const currentBulkSession = pendingBulkSessionRef.current;
4744
- if (currentBulkSession) {
4745
- e.preventDefault();
4746
- e.stopPropagation();
4747
- confirmBulkOperation(currentBulkSession);
4748
- return;
4749
- }
4750
- if (pendingAction) {
4751
- e.preventDefault();
4752
- e.stopPropagation();
4753
- handleActionSubmit();
4754
- return;
4755
- }
4756
- if (pendingNavigation) {
4757
- e.preventDefault();
4758
- e.stopPropagation();
4759
- handleConfirmNavigation(pendingNavigation);
4760
- return;
4761
- }
4762
- const currentGuide = activeGuideRef.current;
4763
- if (currentGuide) {
4764
- e.preventDefault();
4765
- e.stopPropagation();
4766
- advanceGuide();
4767
- 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);
4768
4976
  }
4769
4977
  }
4770
4978
  }
4771
- }
4772
- ),
4773
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4774
- Button,
4775
- {
4776
- type: "submit",
4777
- size: "icon",
4778
- disabled: !input.trim() && !pendingFile || isWaitingForAuth,
4779
- className: "h-6 w-6 rounded-full bg-gray-900 hover:bg-gray-800 disabled:bg-gray-300",
4780
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5" })
4781
- }
4782
- )
4783
- ] })
4784
- ] })
4785
- ] }),
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
+ ),
4786
5087
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4787
5088
  GuideCursor,
4788
5089
  {
@@ -4791,6 +5092,13 @@ ${userText}`
4791
5092
  visible: cursorState.visible,
4792
5093
  onClick: cursorState.onClick
4793
5094
  }
5095
+ ),
5096
+ lightboxImageUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5097
+ ImageLightbox,
5098
+ {
5099
+ imageUrl: lightboxImageUrl,
5100
+ onClose: () => setLightboxImageUrl(null)
5101
+ }
4794
5102
  )
4795
5103
  ]
4796
5104
  }
@@ -4835,7 +5143,8 @@ function ChatPanelWithToggle({
4835
5143
  initialCorner,
4836
5144
  onCornerChange,
4837
5145
  productBackendUrl,
4838
- getAuthHeaders
5146
+ getAuthHeaders,
5147
+ isEval
4839
5148
  }) {
4840
5149
  const [internalIsOpen, setInternalIsOpen] = React6.useState(defaultOpen);
4841
5150
  const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
@@ -4866,7 +5175,8 @@ function ChatPanelWithToggle({
4866
5175
  initialCorner,
4867
5176
  onCornerChange,
4868
5177
  productBackendUrl,
4869
- getAuthHeaders
5178
+ getAuthHeaders,
5179
+ isEval
4870
5180
  }
4871
5181
  );
4872
5182
  }