@kite-copilot/chat-panel 0.2.47 → 0.2.49

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/README.md CHANGED
@@ -548,6 +548,36 @@ If upgrading from a previous version with overlay/floating panel:
548
548
  <ChatPanelWithToggle />
549
549
  ```
550
550
 
551
+ ## Local Development with Test Page
552
+
553
+ To test the embed script locally using the included test page:
554
+
555
+ 1. **Install dependencies:**
556
+ ```bash
557
+ npm install
558
+ ```
559
+
560
+ 2. **Build the embed script:**
561
+ ```bash
562
+ npm run build
563
+ ```
564
+
565
+ 3. **Serve the test page:**
566
+ ```bash
567
+ npx serve .
568
+ ```
569
+
570
+ 4. **Open in browser:**
571
+ Navigate to `http://localhost:3000/test/test-embed.html`
572
+
573
+ The test page (`test/test-embed.html`) provides:
574
+ - Interactive controls to open/close/toggle the panel
575
+ - Page context switching buttons
576
+ - Backend URL toggle (localhost vs production)
577
+ - Event logging for navigation and action callbacks
578
+
579
+ **Note:** Make sure your backend agent is running on `http://localhost:5002` (or toggle to production in the test page).
580
+
551
581
  ## License
552
582
 
553
583
  MIT © Kite
package/dist/auto.cjs CHANGED
@@ -35,7 +35,7 @@ __export(auto_exports, {
35
35
  module.exports = __toCommonJS(auto_exports);
36
36
 
37
37
  // src/createKiteChat.tsx
38
- var import_react = __toESM(require("react"), 1);
38
+ var import_react2 = __toESM(require("react"), 1);
39
39
  var import_client = require("react-dom/client");
40
40
 
41
41
  // src/ChatPanel.tsx
@@ -447,6 +447,78 @@ function useOrgConfig({ agentUrl, orgId }) {
447
447
  return state;
448
448
  }
449
449
 
450
+ // src/hooks/useFrontendToolExecutor.ts
451
+ var import_react = require("react");
452
+ function useFrontendToolExecutor({
453
+ productBackendUrl,
454
+ agentUrl,
455
+ sessionId
456
+ }) {
457
+ const sendToolResult = (0, import_react.useCallback)(async (payload) => {
458
+ try {
459
+ await fetch(`${agentUrl}/chat/tool-result`, {
460
+ method: "POST",
461
+ headers: {
462
+ "Content-Type": "application/json"
463
+ },
464
+ body: JSON.stringify(payload)
465
+ });
466
+ } catch (error) {
467
+ console.error("[FrontendToolExecutor] Failed to send tool result:", error);
468
+ }
469
+ }, [agentUrl]);
470
+ const executeToolRequest = (0, import_react.useCallback)(async (toolRequest) => {
471
+ const { call_id, tool_name, arguments: args, endpoint, method, path_params } = toolRequest;
472
+ console.log("[FrontendToolExecutor] Executing tool:", tool_name, "with args:", args);
473
+ try {
474
+ let url = endpoint;
475
+ for (const param of path_params) {
476
+ if (args[param]) {
477
+ url = url.replace(`{${param}}`, encodeURIComponent(args[param]));
478
+ }
479
+ }
480
+ const queryParams = new URLSearchParams();
481
+ for (const [key, value] of Object.entries(args)) {
482
+ if (!path_params.includes(key) && value !== void 0 && value !== null) {
483
+ queryParams.append(key, String(value));
484
+ }
485
+ }
486
+ const queryString = queryParams.toString();
487
+ const fullUrl = `${productBackendUrl}${url}${queryString ? "?" + queryString : ""}`;
488
+ console.log("[FrontendToolExecutor] Fetching:", fullUrl);
489
+ const response = await fetch(fullUrl, {
490
+ method,
491
+ credentials: "include",
492
+ headers: {
493
+ "Accept": "application/json",
494
+ "Content-Type": "application/json"
495
+ }
496
+ });
497
+ let result;
498
+ if (response.ok) {
499
+ result = await response.json();
500
+ console.log("[FrontendToolExecutor] Tool result:", result);
501
+ } else {
502
+ const errorText = await response.text();
503
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
504
+ }
505
+ await sendToolResult({
506
+ session_id: sessionId,
507
+ call_id,
508
+ result
509
+ });
510
+ } catch (error) {
511
+ console.error("[FrontendToolExecutor] Tool execution failed:", error);
512
+ await sendToolResult({
513
+ session_id: sessionId,
514
+ call_id,
515
+ error: error instanceof Error ? error.message : "Unknown error"
516
+ });
517
+ }
518
+ }, [productBackendUrl, sessionId, sendToolResult]);
519
+ return { executeToolRequest };
520
+ }
521
+
450
522
  // src/components/ui/card.tsx
451
523
  var import_jsx_runtime7 = require("react/jsx-runtime");
452
524
  function Card({ className, ...props }) {
@@ -1024,7 +1096,7 @@ function TypingIndicator({ className = "" }) {
1024
1096
 
1025
1097
  // src/ChatPanel.tsx
1026
1098
  var import_jsx_runtime10 = require("react/jsx-runtime");
1027
- var CHAT_PANEL_VERSION = true ? "0.2.47" : "dev";
1099
+ var CHAT_PANEL_VERSION = true ? "0.2.49" : "dev";
1028
1100
  var DEFAULT_AGENT_URL = "http://localhost:5002";
1029
1101
  var PANEL_WIDTH = 400;
1030
1102
  var PANEL_HEIGHT = 600;
@@ -1507,6 +1579,7 @@ function ChatPanel({
1507
1579
  }
1508
1580
  }, [corner, onCornerChange, dragControls]);
1509
1581
  const [sessionId, setSessionId] = React6.useState(() => crypto.randomUUID());
1582
+ const [sessionUser, setSessionUser] = React6.useState(null);
1510
1583
  const orgConfigState = useOrgConfig({ agentUrl, orgId: orgId || "" });
1511
1584
  const effectiveProductBackendUrl = orgConfigState.config?.productBackendUrl || productBackendUrl;
1512
1585
  const { authState, retry: retryAuth } = useUserAuth({
@@ -1515,6 +1588,11 @@ function ChatPanel({
1515
1588
  enabled: !!effectiveProductBackendUrl && orgConfigState.status === "success"
1516
1589
  // Only enable after config is fetched
1517
1590
  });
1591
+ const { executeToolRequest } = useFrontendToolExecutor({
1592
+ productBackendUrl: effectiveProductBackendUrl || "",
1593
+ agentUrl,
1594
+ sessionId
1595
+ });
1518
1596
  React6.useEffect(() => {
1519
1597
  if (!effectiveProductBackendUrl || orgConfigState.status !== "success") {
1520
1598
  return;
@@ -1547,6 +1625,9 @@ function ChatPanel({
1547
1625
  testProductBackendEndpoint();
1548
1626
  }, [effectiveProductBackendUrl, orgConfigState.status]);
1549
1627
  const effectiveUser = React6.useMemo(() => {
1628
+ if (sessionUser) {
1629
+ return sessionUser;
1630
+ }
1550
1631
  if (authState.status === "authenticated") {
1551
1632
  return {
1552
1633
  userId: authState.user.id,
@@ -1563,7 +1644,23 @@ function ChatPanel({
1563
1644
  userRole: void 0,
1564
1645
  isInternal: false
1565
1646
  };
1566
- }, [authState, userId, userName, userEmail]);
1647
+ }, [sessionUser, authState, userId, userName, userEmail]);
1648
+ React6.useEffect(() => {
1649
+ if (authState.status === "authenticated" && sessionUser === null) {
1650
+ setSessionUser({
1651
+ userId: authState.user.id,
1652
+ userName: authState.user.name,
1653
+ userEmail: authState.user.email,
1654
+ userRole: authState.user.role,
1655
+ isInternal: authState.user.isInternal
1656
+ });
1657
+ console.log("[ChatPanel] Session user captured:", authState.user.id);
1658
+ }
1659
+ }, [authState, sessionUser]);
1660
+ const isWaitingForAuth = React6.useMemo(() => {
1661
+ if (!effectiveProductBackendUrl) return false;
1662
+ return authState.status === "loading" || authState.status === "idle";
1663
+ }, [effectiveProductBackendUrl, authState.status]);
1567
1664
  const [isEscalated, setIsEscalated] = React6.useState(false);
1568
1665
  const escalationWsRef = React6.useRef(null);
1569
1666
  const [agentIsTyping, setAgentIsTyping] = React6.useState(false);
@@ -1591,6 +1688,7 @@ function ChatPanel({
1591
1688
  } else {
1592
1689
  console.log("[KiteChat] Skipping disconnect update - conditions not met");
1593
1690
  }
1691
+ setSessionUser(null);
1594
1692
  setSessionId(crypto.randomUUID());
1595
1693
  setIsEscalated(false);
1596
1694
  if (escalationWsRef.current) {
@@ -2509,6 +2607,12 @@ function ChatPanel({
2509
2607
  content: data.message || "You've been connected to our support queue. An agent will be with you shortly."
2510
2608
  };
2511
2609
  setMessages((prev) => [...prev, escalationMessage]);
2610
+ } else if (eventType === "tool_request") {
2611
+ const toolRequest = data;
2612
+ console.log("[KiteChat] Received tool_request:", toolRequest);
2613
+ executeToolRequest(toolRequest).catch((err) => {
2614
+ console.error("[KiteChat] Tool execution failed:", err);
2615
+ });
2512
2616
  } else if (eventType === "token") {
2513
2617
  }
2514
2618
  } catch (parseError) {
@@ -4660,7 +4764,7 @@ ${userText}`
4660
4764
  {
4661
4765
  type: "submit",
4662
4766
  size: "icon",
4663
- disabled: !input.trim() && !pendingFile,
4767
+ disabled: !input.trim() && !pendingFile || isWaitingForAuth,
4664
4768
  className: "h-6 w-6 rounded-full bg-gray-900 hover:bg-gray-800 disabled:bg-gray-300",
4665
4769
  children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5" })
4666
4770
  }
@@ -4741,14 +4845,14 @@ function KiteChatWrapper({
4741
4845
  onConfigUpdate,
4742
4846
  onStateUpdate
4743
4847
  }) {
4744
- const [config, setConfig] = import_react.default.useState(initialConfig);
4745
- const [currentPage, setCurrentPage] = import_react.default.useState(initialConfig.currentPage || "dashboard");
4746
- const [isOpen, setIsOpen] = import_react.default.useState(false);
4747
- const isOpenRef = import_react.default.useRef(false);
4748
- import_react.default.useEffect(() => {
4848
+ const [config, setConfig] = import_react2.default.useState(initialConfig);
4849
+ const [currentPage, setCurrentPage] = import_react2.default.useState(initialConfig.currentPage || "dashboard");
4850
+ const [isOpen, setIsOpen] = import_react2.default.useState(false);
4851
+ const isOpenRef = import_react2.default.useRef(false);
4852
+ import_react2.default.useEffect(() => {
4749
4853
  isOpenRef.current = isOpen;
4750
4854
  }, [isOpen]);
4751
- import_react.default.useEffect(() => {
4855
+ import_react2.default.useEffect(() => {
4752
4856
  onConfigUpdate((newConfig) => {
4753
4857
  if (newConfig.currentPage !== void 0) {
4754
4858
  setCurrentPage(newConfig.currentPage);
@@ -4760,7 +4864,7 @@ function KiteChatWrapper({
4760
4864
  getIsOpen: () => isOpenRef.current
4761
4865
  });
4762
4866
  }, [onConfigUpdate, onStateUpdate]);
4763
- import_react.default.useEffect(() => {
4867
+ import_react2.default.useEffect(() => {
4764
4868
  const container = document.getElementById("kite-chat-root");
4765
4869
  if (!container) return;
4766
4870
  if (config.theme === "dark") {
package/dist/auto.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createKiteChat
3
- } from "./chunk-XMIO4GMW.js";
3
+ } from "./chunk-YZXB3LLU.js";
4
4
 
5
5
  // src/auto.ts
6
6
  function mountKiteChat(config) {
@@ -995,6 +995,78 @@ function useOrgConfig({ agentUrl, orgId }) {
995
995
  return state;
996
996
  }
997
997
 
998
+ // src/hooks/useFrontendToolExecutor.ts
999
+ import { useCallback as useCallback3 } from "react";
1000
+ function useFrontendToolExecutor({
1001
+ productBackendUrl,
1002
+ agentUrl,
1003
+ sessionId
1004
+ }) {
1005
+ const sendToolResult = useCallback3(async (payload) => {
1006
+ try {
1007
+ await fetch(`${agentUrl}/chat/tool-result`, {
1008
+ method: "POST",
1009
+ headers: {
1010
+ "Content-Type": "application/json"
1011
+ },
1012
+ body: JSON.stringify(payload)
1013
+ });
1014
+ } catch (error) {
1015
+ console.error("[FrontendToolExecutor] Failed to send tool result:", error);
1016
+ }
1017
+ }, [agentUrl]);
1018
+ const executeToolRequest = useCallback3(async (toolRequest) => {
1019
+ const { call_id, tool_name, arguments: args, endpoint, method, path_params } = toolRequest;
1020
+ console.log("[FrontendToolExecutor] Executing tool:", tool_name, "with args:", args);
1021
+ try {
1022
+ let url = endpoint;
1023
+ for (const param of path_params) {
1024
+ if (args[param]) {
1025
+ url = url.replace(`{${param}}`, encodeURIComponent(args[param]));
1026
+ }
1027
+ }
1028
+ const queryParams = new URLSearchParams();
1029
+ for (const [key, value] of Object.entries(args)) {
1030
+ if (!path_params.includes(key) && value !== void 0 && value !== null) {
1031
+ queryParams.append(key, String(value));
1032
+ }
1033
+ }
1034
+ const queryString = queryParams.toString();
1035
+ const fullUrl = `${productBackendUrl}${url}${queryString ? "?" + queryString : ""}`;
1036
+ console.log("[FrontendToolExecutor] Fetching:", fullUrl);
1037
+ const response = await fetch(fullUrl, {
1038
+ method,
1039
+ credentials: "include",
1040
+ headers: {
1041
+ "Accept": "application/json",
1042
+ "Content-Type": "application/json"
1043
+ }
1044
+ });
1045
+ let result;
1046
+ if (response.ok) {
1047
+ result = await response.json();
1048
+ console.log("[FrontendToolExecutor] Tool result:", result);
1049
+ } else {
1050
+ const errorText = await response.text();
1051
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1052
+ }
1053
+ await sendToolResult({
1054
+ session_id: sessionId,
1055
+ call_id,
1056
+ result
1057
+ });
1058
+ } catch (error) {
1059
+ console.error("[FrontendToolExecutor] Tool execution failed:", error);
1060
+ await sendToolResult({
1061
+ session_id: sessionId,
1062
+ call_id,
1063
+ error: error instanceof Error ? error.message : "Unknown error"
1064
+ });
1065
+ }
1066
+ }, [productBackendUrl, sessionId, sendToolResult]);
1067
+ return { executeToolRequest };
1068
+ }
1069
+
998
1070
  // src/components/TypingIndicator.tsx
999
1071
  import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1000
1072
  function TypingIndicator({ className = "" }) {
@@ -1025,7 +1097,7 @@ function TypingIndicator({ className = "" }) {
1025
1097
 
1026
1098
  // src/ChatPanel.tsx
1027
1099
  import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1028
- var CHAT_PANEL_VERSION = true ? "0.2.47" : "dev";
1100
+ var CHAT_PANEL_VERSION = true ? "0.2.49" : "dev";
1029
1101
  var DEFAULT_AGENT_URL = "http://localhost:5002";
1030
1102
  var PANEL_WIDTH = 400;
1031
1103
  var PANEL_HEIGHT = 600;
@@ -1508,6 +1580,7 @@ function ChatPanel({
1508
1580
  }
1509
1581
  }, [corner, onCornerChange, dragControls]);
1510
1582
  const [sessionId, setSessionId] = React6.useState(() => crypto.randomUUID());
1583
+ const [sessionUser, setSessionUser] = React6.useState(null);
1511
1584
  const orgConfigState = useOrgConfig({ agentUrl, orgId: orgId || "" });
1512
1585
  const effectiveProductBackendUrl = orgConfigState.config?.productBackendUrl || productBackendUrl;
1513
1586
  const { authState, retry: retryAuth } = useUserAuth({
@@ -1516,6 +1589,11 @@ function ChatPanel({
1516
1589
  enabled: !!effectiveProductBackendUrl && orgConfigState.status === "success"
1517
1590
  // Only enable after config is fetched
1518
1591
  });
1592
+ const { executeToolRequest } = useFrontendToolExecutor({
1593
+ productBackendUrl: effectiveProductBackendUrl || "",
1594
+ agentUrl,
1595
+ sessionId
1596
+ });
1519
1597
  React6.useEffect(() => {
1520
1598
  if (!effectiveProductBackendUrl || orgConfigState.status !== "success") {
1521
1599
  return;
@@ -1548,6 +1626,9 @@ function ChatPanel({
1548
1626
  testProductBackendEndpoint();
1549
1627
  }, [effectiveProductBackendUrl, orgConfigState.status]);
1550
1628
  const effectiveUser = React6.useMemo(() => {
1629
+ if (sessionUser) {
1630
+ return sessionUser;
1631
+ }
1551
1632
  if (authState.status === "authenticated") {
1552
1633
  return {
1553
1634
  userId: authState.user.id,
@@ -1564,7 +1645,23 @@ function ChatPanel({
1564
1645
  userRole: void 0,
1565
1646
  isInternal: false
1566
1647
  };
1567
- }, [authState, userId, userName, userEmail]);
1648
+ }, [sessionUser, authState, userId, userName, userEmail]);
1649
+ React6.useEffect(() => {
1650
+ if (authState.status === "authenticated" && sessionUser === null) {
1651
+ setSessionUser({
1652
+ userId: authState.user.id,
1653
+ userName: authState.user.name,
1654
+ userEmail: authState.user.email,
1655
+ userRole: authState.user.role,
1656
+ isInternal: authState.user.isInternal
1657
+ });
1658
+ console.log("[ChatPanel] Session user captured:", authState.user.id);
1659
+ }
1660
+ }, [authState, sessionUser]);
1661
+ const isWaitingForAuth = React6.useMemo(() => {
1662
+ if (!effectiveProductBackendUrl) return false;
1663
+ return authState.status === "loading" || authState.status === "idle";
1664
+ }, [effectiveProductBackendUrl, authState.status]);
1568
1665
  const [isEscalated, setIsEscalated] = React6.useState(false);
1569
1666
  const escalationWsRef = React6.useRef(null);
1570
1667
  const [agentIsTyping, setAgentIsTyping] = React6.useState(false);
@@ -1592,6 +1689,7 @@ function ChatPanel({
1592
1689
  } else {
1593
1690
  console.log("[KiteChat] Skipping disconnect update - conditions not met");
1594
1691
  }
1692
+ setSessionUser(null);
1595
1693
  setSessionId(crypto.randomUUID());
1596
1694
  setIsEscalated(false);
1597
1695
  if (escalationWsRef.current) {
@@ -2510,6 +2608,12 @@ function ChatPanel({
2510
2608
  content: data.message || "You've been connected to our support queue. An agent will be with you shortly."
2511
2609
  };
2512
2610
  setMessages((prev) => [...prev, escalationMessage]);
2611
+ } else if (eventType === "tool_request") {
2612
+ const toolRequest = data;
2613
+ console.log("[KiteChat] Received tool_request:", toolRequest);
2614
+ executeToolRequest(toolRequest).catch((err) => {
2615
+ console.error("[KiteChat] Tool execution failed:", err);
2616
+ });
2513
2617
  } else if (eventType === "token") {
2514
2618
  }
2515
2619
  } catch (parseError) {
@@ -4661,7 +4765,7 @@ ${userText}`
4661
4765
  {
4662
4766
  type: "submit",
4663
4767
  size: "icon",
4664
- disabled: !input.trim() && !pendingFile,
4768
+ disabled: !input.trim() && !pendingFile || isWaitingForAuth,
4665
4769
  className: "h-6 w-6 rounded-full bg-gray-900 hover:bg-gray-800 disabled:bg-gray-300",
4666
4770
  children: /* @__PURE__ */ jsx10(ArrowUp, { className: "h-2.5 w-2.5" })
4667
4771
  }