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