@kite-copilot/chat-panel 0.2.23 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auto.cjs CHANGED
@@ -885,6 +885,7 @@ function DataRenderer({ type, data }) {
885
885
  }
886
886
 
887
887
  // src/ChatPanel.tsx
888
+ var import_supabase_js = require("@supabase/supabase-js");
888
889
  var import_jsx_runtime9 = require("react/jsx-runtime");
889
890
  var DEFAULT_AGENT_URL = "http://localhost:5002";
890
891
  var PANEL_WIDTH = 340;
@@ -938,7 +939,7 @@ function renderMarkdown(text) {
938
939
  "a",
939
940
  {
940
941
  href: linkMatch[2],
941
- className: "text-blue-600 hover:underline",
942
+ className: "kite-link",
942
943
  target: "_blank",
943
944
  rel: "noopener noreferrer",
944
945
  children: linkMatch[1]
@@ -949,7 +950,44 @@ function renderMarkdown(text) {
949
950
  remaining = remaining.slice(linkMatch[0].length);
950
951
  continue;
951
952
  }
952
- const nextSpecial = remaining.search(/[`*\[]/);
953
+ const urlMatch = remaining.match(/^(https?:\/\/[^\s<>]+|www\.[^\s<>]+)/);
954
+ if (urlMatch) {
955
+ const url = urlMatch[1];
956
+ const href = url.startsWith("www.") ? `https://${url}` : url;
957
+ parts.push(
958
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
959
+ "a",
960
+ {
961
+ href,
962
+ className: "kite-link",
963
+ target: "_blank",
964
+ rel: "noopener noreferrer",
965
+ children: url
966
+ },
967
+ keyIndex++
968
+ )
969
+ );
970
+ remaining = remaining.slice(url.length);
971
+ continue;
972
+ }
973
+ const emailMatch = remaining.match(/^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);
974
+ if (emailMatch) {
975
+ const email = emailMatch[1];
976
+ parts.push(
977
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
978
+ "a",
979
+ {
980
+ href: `mailto:${email}`,
981
+ className: "kite-link",
982
+ children: email
983
+ },
984
+ keyIndex++
985
+ )
986
+ );
987
+ remaining = remaining.slice(email.length);
988
+ continue;
989
+ }
990
+ const nextSpecial = remaining.search(/[`*\[@h]/);
953
991
  if (nextSpecial === -1) {
954
992
  parts.push(remaining);
955
993
  break;
@@ -1269,17 +1307,23 @@ var initialMessages = [];
1269
1307
  function ChatPanel({
1270
1308
  isOpen = true,
1271
1309
  onClose,
1310
+ onOpen,
1272
1311
  onBack,
1273
1312
  onNavigate,
1274
1313
  onActionComplete,
1275
1314
  currentPage,
1276
1315
  agentUrl = DEFAULT_AGENT_URL,
1277
1316
  startingQuestions: startingQuestionsProp,
1278
- startingQuestionsEndpoint
1317
+ startingQuestionsEndpoint,
1318
+ supabaseUrl,
1319
+ supabaseAnonKey
1279
1320
  } = {}) {
1280
1321
  const [messages, setMessages] = React4.useState(initialMessages);
1281
1322
  const [input, setInput] = React4.useState("");
1282
1323
  const [sessionId, setSessionId] = React4.useState(() => crypto.randomUUID());
1324
+ const [isEscalated, setIsEscalated] = React4.useState(false);
1325
+ const [supabaseClient, setSupabaseClient] = React4.useState(null);
1326
+ const realtimeChannelRef = React4.useRef(null);
1283
1327
  const resetSession = React4.useCallback(() => {
1284
1328
  setSessionId(crypto.randomUUID());
1285
1329
  }, []);
@@ -1370,6 +1414,9 @@ function ChatPanel({
1370
1414
  const [pendingBulkSession, setPendingBulkSession] = React4.useState(null);
1371
1415
  const pendingBulkSessionRef = React4.useRef(null);
1372
1416
  const fileInputRef = React4.useRef(null);
1417
+ const [searchExpanded, setSearchExpanded] = React4.useState(false);
1418
+ const [searchInput, setSearchInput] = React4.useState("");
1419
+ const searchInputRef = React4.useRef(null);
1373
1420
  React4.useEffect(() => {
1374
1421
  if (!activeGuide || activeGuide.id !== "add-api-key" || activeGuide.stepIndex !== 2) {
1375
1422
  return;
@@ -1501,6 +1548,63 @@ function ChatPanel({
1501
1548
  guideComplete,
1502
1549
  onNavigate
1503
1550
  ]);
1551
+ React4.useEffect(() => {
1552
+ if (supabaseUrl && supabaseAnonKey && !supabaseClient) {
1553
+ const client = (0, import_supabase_js.createClient)(supabaseUrl, supabaseAnonKey);
1554
+ setSupabaseClient(client);
1555
+ }
1556
+ }, [supabaseUrl, supabaseAnonKey, supabaseClient]);
1557
+ const subscribeToAgentMessages = React4.useCallback((currentSessionId) => {
1558
+ if (!supabaseClient) return;
1559
+ if (realtimeChannelRef.current) {
1560
+ supabaseClient.removeChannel(realtimeChannelRef.current);
1561
+ }
1562
+ const channel = supabaseClient.channel(`user-chat-${currentSessionId}`).on(
1563
+ "postgres_changes",
1564
+ {
1565
+ event: "INSERT",
1566
+ schema: "public",
1567
+ table: "chat_history",
1568
+ filter: `session_id=eq.${currentSessionId}`
1569
+ },
1570
+ (payload) => {
1571
+ const newMsg = payload.new;
1572
+ if (newMsg.role === "agent") {
1573
+ setMessages((prev) => [
1574
+ ...prev,
1575
+ {
1576
+ id: Date.now(),
1577
+ role: "agent",
1578
+ kind: "text",
1579
+ content: newMsg.content
1580
+ }
1581
+ ]);
1582
+ }
1583
+ }
1584
+ ).subscribe();
1585
+ realtimeChannelRef.current = channel;
1586
+ }, [supabaseClient]);
1587
+ React4.useEffect(() => {
1588
+ return () => {
1589
+ if (realtimeChannelRef.current && supabaseClient) {
1590
+ supabaseClient.removeChannel(realtimeChannelRef.current);
1591
+ }
1592
+ };
1593
+ }, [supabaseClient]);
1594
+ const sendEscalatedMessage = React4.useCallback(async (content) => {
1595
+ if (!supabaseClient || !isEscalated) return false;
1596
+ try {
1597
+ await supabaseClient.from("chat_history").insert({
1598
+ session_id: sessionId,
1599
+ role: "user",
1600
+ content
1601
+ });
1602
+ return true;
1603
+ } catch (err) {
1604
+ console.error("[KiteChat] Failed to send escalated message:", err);
1605
+ return false;
1606
+ }
1607
+ }, [supabaseClient, isEscalated, sessionId]);
1504
1608
  function streamAssistantMessage(messageId, fullText, followups) {
1505
1609
  let textToStream = fullText;
1506
1610
  let extractedFollowups = followups;
@@ -1611,6 +1715,17 @@ function ChatPanel({
1611
1715
  return;
1612
1716
  }
1613
1717
  if (!trimmed) return;
1718
+ if (isEscalated && supabaseClient) {
1719
+ const userMessage = {
1720
+ id: Date.now(),
1721
+ role: "user",
1722
+ content: trimmed
1723
+ };
1724
+ setMessages((prev) => [...prev, userMessage]);
1725
+ sendEscalatedMessage(trimmed);
1726
+ setInput("");
1727
+ return;
1728
+ }
1614
1729
  startChatFlow(trimmed);
1615
1730
  setInput("");
1616
1731
  }
@@ -1719,7 +1834,11 @@ function ChatPanel({
1719
1834
  const isRespondingToNotification = lastAssistantMessage?.isNotificationMessage === true;
1720
1835
  const now = Date.now();
1721
1836
  const userMessage = { id: now, role: "user", content: userText };
1722
- setMessages((prev) => [...prev, userMessage]);
1837
+ setMessages(
1838
+ (prev) => prev.map(
1839
+ (m) => m.followups && m.followups.length > 0 ? { ...m, followupSelected: true } : m
1840
+ ).concat(userMessage)
1841
+ );
1723
1842
  if (isRespondingToNotification) {
1724
1843
  const thankYouMessageId = Date.now() + 1;
1725
1844
  const thankYouMessage = {
@@ -1935,6 +2054,18 @@ function ChatPanel({
1935
2054
  setProgressSteps([]);
1936
2055
  setPhase("idle");
1937
2056
  streamCompleted = true;
2057
+ } else if (eventType === "escalation") {
2058
+ setIsEscalated(true);
2059
+ setPhase("idle");
2060
+ const escalationMessageId = Date.now() + 2;
2061
+ const escalationMessage = {
2062
+ id: escalationMessageId,
2063
+ role: "assistant",
2064
+ kind: "text",
2065
+ content: data.message || "You've been connected to our support queue. An agent will be with you shortly."
2066
+ };
2067
+ setMessages((prev) => [...prev, escalationMessage]);
2068
+ subscribeToAgentMessages(sessionId);
1938
2069
  }
1939
2070
  } catch (parseError) {
1940
2071
  console.error("Failed to parse SSE event:", parseError);
@@ -2597,6 +2728,81 @@ ${userText}`
2597
2728
  ]);
2598
2729
  }
2599
2730
  }
2731
+ if (!isOpen) {
2732
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-50", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex items-center gap-3 rounded-2xl bg-white border border-gray-200 shadow-lg px-4 py-2 hover:shadow-xl transition-all duration-300 ease-out focus-within:border-gray-700 ${searchExpanded ? "w-[480px]" : "w-[320px]"}`, children: !searchExpanded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-3 w-full", children: [
2733
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2734
+ "button",
2735
+ {
2736
+ onClick: () => {
2737
+ setSearchExpanded(true);
2738
+ setTimeout(() => searchInputRef.current?.focus(), 100);
2739
+ },
2740
+ className: "flex items-center gap-3 flex-1 group",
2741
+ children: [
2742
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-sm text-gray-500 flex-1 text-left", children: "Ask a question..." }),
2743
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-gray-400 font-medium", children: "\u2318I" })
2744
+ ]
2745
+ }
2746
+ ),
2747
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2748
+ "button",
2749
+ {
2750
+ onClick: () => {
2751
+ onOpen?.();
2752
+ },
2753
+ className: "h-7 w-7 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors",
2754
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" })
2755
+ }
2756
+ )
2757
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2759
+ "input",
2760
+ {
2761
+ ref: searchInputRef,
2762
+ type: "text",
2763
+ value: searchInput,
2764
+ onChange: (e) => setSearchInput(e.target.value),
2765
+ onKeyDown: (e) => {
2766
+ if (e.key === "Enter" && searchInput.trim()) {
2767
+ e.preventDefault();
2768
+ onOpen?.();
2769
+ startChatFlow(searchInput.trim());
2770
+ setSearchInput("");
2771
+ setSearchExpanded(false);
2772
+ } else if (e.key === "Escape") {
2773
+ setSearchExpanded(false);
2774
+ setSearchInput("");
2775
+ }
2776
+ },
2777
+ onBlur: () => {
2778
+ if (!searchInput.trim()) {
2779
+ setTimeout(() => setSearchExpanded(false), 200);
2780
+ }
2781
+ },
2782
+ placeholder: "Ask a question...",
2783
+ className: "flex-1 text-sm text-gray-700 outline-none bg-transparent"
2784
+ }
2785
+ ),
2786
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
2787
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-gray-400 font-medium", children: "\u21B5 Enter" }),
2788
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2789
+ "button",
2790
+ {
2791
+ onClick: () => {
2792
+ onOpen?.();
2793
+ if (searchInput.trim()) {
2794
+ setInput(searchInput);
2795
+ }
2796
+ setSearchInput("");
2797
+ setSearchExpanded(false);
2798
+ },
2799
+ className: "h-7 w-7 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors cursor-pointer",
2800
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" })
2801
+ }
2802
+ )
2803
+ ] })
2804
+ ] }) }) });
2805
+ }
2600
2806
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2601
2807
  "section",
2602
2808
  {
@@ -2727,6 +2933,12 @@ ${userText}`
2727
2933
  if (isUser) {
2728
2934
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex justify-end ${isRoleChange ? "mt-3" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "max-w-[260px] rounded-2xl rounded-br-md bg-gray-900 px-3.5 py-2.5 text-sm text-white shadow-sm", children: message.content }) }, message.id);
2729
2935
  }
2936
+ if (message.role === "agent") {
2937
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex justify-start ${isRoleChange ? "mt-3" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "max-w-[260px] rounded-2xl rounded-bl-md bg-blue-50 border border-blue-200 px-3.5 py-2.5 text-sm text-gray-900 shadow-sm", children: [
2938
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-xs text-blue-600 font-medium mb-1", children: "Support Agent" }),
2939
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "whitespace-pre-wrap leading-relaxed", children: renderMarkdown(message.content || "") })
2940
+ ] }) }, message.id);
2941
+ }
2730
2942
  if (message.kind === "searchSummary") {
2731
2943
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2732
2944
  "div",
@@ -3909,26 +4121,6 @@ ${userText}`
3909
4121
  }
3910
4122
  );
3911
4123
  }
3912
- function PanelToggle({
3913
- isOpen,
3914
- onClick,
3915
- className = ""
3916
- }) {
3917
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3918
- "button",
3919
- {
3920
- type: "button",
3921
- onClick,
3922
- className: `fixed top-1/2 z-50 flex items-center justify-center w-6 h-16 bg-gray-100 hover:bg-gray-200 border border-gray-200 border-r-0 rounded-l-lg text-gray-600 hover:text-gray-800 shadow-md transition-all duration-300 ${className}`,
3923
- "aria-label": isOpen ? "Close help panel" : "Open help panel",
3924
- style: {
3925
- right: isOpen ? `${PANEL_WIDTH}px` : "0px",
3926
- transform: "translateY(-50%)"
3927
- },
3928
- children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.ChevronLeft, { className: "h-4 w-4" })
3929
- }
3930
- );
3931
- }
3932
4124
  function ChatPanelWithToggle({
3933
4125
  onNavigate,
3934
4126
  onActionComplete,
@@ -3938,7 +4130,9 @@ function ChatPanelWithToggle({
3938
4130
  startingQuestionsEndpoint,
3939
4131
  defaultOpen = false,
3940
4132
  isOpen: controlledIsOpen,
3941
- onOpenChange
4133
+ onOpenChange,
4134
+ supabaseUrl,
4135
+ supabaseAnonKey
3942
4136
  }) {
3943
4137
  const [internalIsOpen, setInternalIsOpen] = React4.useState(defaultOpen);
3944
4138
  const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
@@ -3958,22 +4152,22 @@ function ChatPanelWithToggle({
3958
4152
  document.body.style.transition = originalTransition;
3959
4153
  };
3960
4154
  }, [isOpen]);
3961
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3962
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PanelToggle, { isOpen, onClick: () => setIsOpen(!isOpen) }),
3963
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3964
- ChatPanel,
3965
- {
3966
- isOpen,
3967
- onClose: () => setIsOpen(false),
3968
- onNavigate,
3969
- onActionComplete,
3970
- currentPage,
3971
- agentUrl,
3972
- startingQuestions,
3973
- startingQuestionsEndpoint
3974
- }
3975
- )
3976
- ] });
4155
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4156
+ ChatPanel,
4157
+ {
4158
+ isOpen,
4159
+ onClose: () => setIsOpen(false),
4160
+ onOpen: () => setIsOpen(true),
4161
+ onNavigate,
4162
+ onActionComplete,
4163
+ currentPage,
4164
+ agentUrl,
4165
+ startingQuestions,
4166
+ startingQuestionsEndpoint,
4167
+ supabaseUrl,
4168
+ supabaseAnonKey
4169
+ }
4170
+ );
3977
4171
  }
3978
4172
 
3979
4173
  // src/createKiteChat.tsx
package/dist/auto.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-BMLaQQQk.cjs';
1
+ import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-CGiuk776.cjs';
2
2
  import 'react/jsx-runtime';
3
3
 
4
4
  /**
package/dist/auto.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-BMLaQQQk.js';
1
+ import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-CGiuk776.js';
2
2
  import 'react/jsx-runtime';
3
3
 
4
4
  /**
package/dist/auto.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createKiteChat
3
- } from "./chunk-MIJSRC3X.js";
3
+ } from "./chunk-WCRTS3TD.js";
4
4
 
5
5
  // src/auto.ts
6
6
  function mountKiteChat(config) {