@kite-copilot/chat-panel 0.2.55 → 0.2.57

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
@@ -6,6 +6,7 @@ An AI-powered chat panel SDK for embedding intelligent chat assistants. This pac
6
6
 
7
7
  - **Side Panel UX**: Slides in from the right edge, pushes page content (no overlay)
8
8
  - **Zero-Config**: Drop-in integration - no layout changes required from host apps
9
+ - **Automatic URL Tracking**: Captures the exact browser URL (including path and query params) for context
9
10
  - **AI-Powered Chat**: Connects to your AI backend agent for intelligent responses
10
11
  - **CSV Bulk Operations**: Upload CSV files for bulk data processing
11
12
  - **Interactive Guides**: Built-in guided tours with animated cursor
@@ -76,11 +77,11 @@ if (chat.isOpen()) {
76
77
  console.log('Panel is open');
77
78
  }
78
79
 
79
- // Update context as user navigates
80
- chat.setCurrentPage('settings');
81
-
82
80
  // Clean up when done
83
81
  chat.unmount();
82
+
83
+ // Note: The chat panel automatically captures the browser URL (window.location.href)
84
+ // so you don't need to manually track or pass the current page.
84
85
  ```
85
86
 
86
87
  ### Vue.js
@@ -124,11 +125,7 @@ export function useKiteChat(config: KiteChatConfig) {
124
125
  isOpen.value = !isOpen.value;
125
126
  };
126
127
 
127
- const setCurrentPage = (page: string) => {
128
- chat.value?.setCurrentPage(page);
129
- };
130
-
131
- return { isOpen, open, close, toggle, setCurrentPage };
128
+ return { isOpen, open, close, toggle };
132
129
  }
133
130
  ```
134
131
 
@@ -142,7 +139,7 @@ import { useRouter } from 'vue-router';
142
139
 
143
140
  const router = useRouter();
144
141
 
145
- const { open, close, toggle, setCurrentPage } = useKiteChat({
142
+ const { open, close, toggle } = useKiteChat({
146
143
  userId: 'user-123',
147
144
  agentUrl: 'https://your-api.example.com',
148
145
  onNavigate: (page, subtab) => {
@@ -150,10 +147,8 @@ const { open, close, toggle, setCurrentPage } = useKiteChat({
150
147
  },
151
148
  });
152
149
 
153
- // Update context when route changes
154
- router.afterEach((to) => {
155
- setCurrentPage(to.name as string);
156
- });
150
+ // Note: The chat panel automatically captures window.location.href,
151
+ // so you don't need to manually track route changes.
157
152
  </script>
158
153
 
159
154
  <template>
@@ -320,7 +315,7 @@ Creates a new chat instance with explicit lifecycle control.
320
315
  | `userId` | `string` | Yes | Unique identifier for the current user |
321
316
  | `orgId` | `string` | No | Organization ID for multi-tenant data isolation |
322
317
  | `agentUrl` | `string` | No | Backend agent API URL |
323
- | `currentPage` | `string` | No | Current page context |
318
+ | ~~`currentPage`~~ | `string` | No | **Deprecated** - URL is now captured automatically |
324
319
  | `userName` | `string` | No | User's display name (for control center) |
325
320
  | `userEmail` | `string` | No | User's email address (for control center) |
326
321
  | `theme` | `'light' \| 'dark' \| 'system'` | No | Theme preference |
@@ -340,7 +335,7 @@ Creates a new chat instance with explicit lifecycle control.
340
335
  | `close()` | Close the side panel |
341
336
  | `toggle()` | Toggle the panel open/closed |
342
337
  | `isOpen()` | Check if the panel is currently open |
343
- | `setCurrentPage(page)` | Update the current page context |
338
+ | ~~`setCurrentPage(page)`~~ | **Deprecated** - URL is now captured automatically |
344
339
  | `updateConfig(config)` | Update configuration options |
345
340
  | `isMounted()` | Check if the widget is currently mounted |
346
341
 
@@ -357,7 +352,7 @@ import { ChatPanelWithToggle } from '@kite-copilot/chat-panel';
357
352
  | Prop | Type | Default | Description |
358
353
  |------|------|---------|-------------|
359
354
  | `agentUrl` | `string` | `localhost:5002` | Backend agent URL |
360
- | `currentPage` | `string` | - | Current page for context |
355
+ | ~~`currentPage`~~ | `string` | - | **Deprecated** - URL is now captured automatically |
361
356
  | `userId` | `string` | - | Unique user identifier |
362
357
  | `orgId` | `string` | - | Organization ID for multi-tenant data isolation |
363
358
  | `userName` | `string` | - | User's display name (for control center) |
@@ -457,7 +452,7 @@ Main chat endpoint for streaming responses.
457
452
  "user_id": "user-123",
458
453
  "org_id": "org-456",
459
454
  "message": "user message",
460
- "current_page": "dashboard",
455
+ "current_page": "https://example.com/dashboard/123?tab=settings",
461
456
  "user_name": "John Doe",
462
457
  "user_email": "john@example.com"
463
458
  }
@@ -473,7 +468,7 @@ Main chat endpoint for streaming responses.
473
468
 
474
469
  CSV file upload endpoint for bulk operations.
475
470
 
476
- **Request:** FormData with `file`, `message`, `session_id`, `user_id`, `org_id`, `current_page`
471
+ **Request:** FormData with `file`, `message`, `session_id`, `user_id`, `org_id`, `current_page` (full browser URL)
477
472
 
478
473
  ### `/chat/bulk/confirm` (POST)
479
474
 
package/dist/auto.cjs CHANGED
@@ -1691,9 +1691,8 @@ function ChatPanel({
1691
1691
  const [input, setInput] = React6.useState("");
1692
1692
  const [corner, setCorner] = React6.useState(initialCorner);
1693
1693
  const [isDragging, setIsDragging] = React6.useState(false);
1694
- const dragControls = (0, import_framer_motion2.useAnimationControls)();
1694
+ const dragControls = (0, import_framer_motion2.useDragControls)();
1695
1695
  const handleDragEnd = React6.useCallback((_event, info) => {
1696
- dragControls.set({ x: 0, y: 0 });
1697
1696
  setIsDragging(false);
1698
1697
  const viewportWidth = window.innerWidth;
1699
1698
  const pointerX = info.point.x;
@@ -1702,7 +1701,7 @@ function ChatPanel({
1702
1701
  setCorner(newCorner);
1703
1702
  onCornerChange?.(newCorner);
1704
1703
  }
1705
- }, [corner, onCornerChange, dragControls]);
1704
+ }, [corner, onCornerChange]);
1706
1705
  const [sessionId, setSessionId] = React6.useState(() => crypto.randomUUID());
1707
1706
  const [sessionUser, setSessionUser] = React6.useState(null);
1708
1707
  const orgConfigState = useOrgConfig({ agentUrl, orgId: orgId || "" });
@@ -1849,15 +1848,72 @@ function ChatPanel({
1849
1848
  };
1850
1849
  }, [isEscalated, sessionId, effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1851
1850
  React6.useEffect(() => {
1852
- if (!isOpen && isEscalated && supabaseRef.current && sessionId) {
1853
- console.log("[KiteChat] Panel closed during live chat, marking disconnected");
1851
+ if (!sessionId || !supabaseRef.current || isEscalated) {
1852
+ console.log("[KiteChat] Session status subscription skip - sessionId:", sessionId, "supabase:", !!supabaseRef.current, "isEscalated:", isEscalated);
1853
+ return;
1854
+ }
1855
+ const channelName = `session-status:${sessionId}`;
1856
+ console.log("[KiteChat] Subscribing to session status changes:", channelName);
1857
+ const statusChannel = supabaseRef.current.channel(channelName).on(
1858
+ "postgres_changes",
1859
+ {
1860
+ event: "UPDATE",
1861
+ schema: "public",
1862
+ table: "sessions",
1863
+ filter: `session_id=eq.${sessionId}`
1864
+ },
1865
+ (payload) => {
1866
+ console.log("[KiteChat] Session status changed:", payload);
1867
+ if (payload.new.session_status === "escalated") {
1868
+ console.log("[KiteChat] Manual escalation detected - agent took over from control center");
1869
+ setIsEscalated(true);
1870
+ setPhase("idle");
1871
+ const escalationMessageId = Date.now() + 2;
1872
+ const escalationMessage = {
1873
+ id: escalationMessageId,
1874
+ role: "assistant",
1875
+ kind: "text",
1876
+ content: "A support agent has joined the conversation and will assist you shortly."
1877
+ };
1878
+ setMessages((prev) => [...prev, escalationMessage]);
1879
+ }
1880
+ }
1881
+ ).subscribe((status) => {
1882
+ if (status === "SUBSCRIBED") {
1883
+ console.log("[KiteChat] Successfully subscribed to session status changes");
1884
+ } else if (status === "CHANNEL_ERROR") {
1885
+ console.error("[KiteChat] Failed to subscribe to session status changes");
1886
+ }
1887
+ });
1888
+ return () => {
1889
+ console.log("[KiteChat] Unsubscribing from session status changes");
1890
+ statusChannel.unsubscribe();
1891
+ };
1892
+ }, [sessionId, isEscalated, effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1893
+ React6.useEffect(() => {
1894
+ if (!isEscalated || !supabaseRef.current || !sessionId) {
1895
+ return;
1896
+ }
1897
+ if (!isOpen) {
1898
+ console.log("[KiteChat] Panel minimized during live chat, marking inactive");
1854
1899
  supabaseRef.current.from("escalations").update({
1855
- customer_status: "disconnected",
1900
+ customer_status: "inactive",
1856
1901
  customer_last_seen: (/* @__PURE__ */ new Date()).toISOString()
1857
1902
  }).eq("session_id", sessionId).then(
1858
- () => console.log("[KiteChat] Successfully marked disconnected on panel close"),
1903
+ () => console.log("[KiteChat] Successfully marked inactive on panel minimize"),
1859
1904
  (err) => {
1860
- console.error("[KiteChat] Failed to mark disconnected on panel close:", err);
1905
+ console.error("[KiteChat] Failed to mark inactive on panel minimize:", err);
1906
+ }
1907
+ );
1908
+ } else {
1909
+ console.log("[KiteChat] Panel reopened during live chat, marking active");
1910
+ supabaseRef.current.from("escalations").update({
1911
+ customer_status: "active",
1912
+ customer_last_seen: (/* @__PURE__ */ new Date()).toISOString()
1913
+ }).eq("session_id", sessionId).then(
1914
+ () => console.log("[KiteChat] Successfully marked active on panel reopen"),
1915
+ (err) => {
1916
+ console.error("[KiteChat] Failed to mark active on panel reopen:", err);
1861
1917
  }
1862
1918
  );
1863
1919
  }
@@ -1928,6 +1984,15 @@ function ChatPanel({
1928
1984
  (err) => console.error("[KiteChat] Failed to update customer status:", err)
1929
1985
  );
1930
1986
  };
1987
+ const markInactive = () => {
1988
+ supabase.from("escalations").update({
1989
+ customer_status: "inactive",
1990
+ customer_last_seen: (/* @__PURE__ */ new Date()).toISOString()
1991
+ }).eq("session_id", currentSessionId).then(
1992
+ () => console.log("[KiteChat] Marked inactive on tab hidden"),
1993
+ (err) => console.error("[KiteChat] Failed to mark inactive:", err)
1994
+ );
1995
+ };
1931
1996
  const markDisconnectedWithKeepalive = () => {
1932
1997
  console.log("[KiteChat] markDisconnectedWithKeepalive called for escalated session:", currentSessionId);
1933
1998
  if (!sbUrl || !sbKey) {
@@ -1976,6 +2041,8 @@ function ChatPanel({
1976
2041
  const handleVisibilityChange = () => {
1977
2042
  if (document.visibilityState === "visible") {
1978
2043
  markActive();
2044
+ } else {
2045
+ markInactive();
1979
2046
  }
1980
2047
  };
1981
2048
  window.addEventListener("beforeunload", handleBeforeUnload);
@@ -2730,6 +2797,26 @@ ${imageMarkdown}` : imageMarkdown;
2730
2797
  try {
2731
2798
  const controller = new AbortController();
2732
2799
  const timeoutId = setTimeout(() => controller.abort(), 6e4);
2800
+ let authToken;
2801
+ let authTokenType;
2802
+ if (getAuthHeaders) {
2803
+ try {
2804
+ const headers = await getAuthHeaders();
2805
+ const authHeader = headers["Authorization"] || headers["authorization"];
2806
+ if (authHeader) {
2807
+ const parts = authHeader.split(" ");
2808
+ if (parts.length === 2) {
2809
+ authTokenType = parts[0];
2810
+ authToken = parts[1];
2811
+ } else {
2812
+ authToken = authHeader;
2813
+ }
2814
+ console.log("[ChatPanel] Auth token obtained from getAuthHeaders");
2815
+ }
2816
+ } catch (e) {
2817
+ console.warn("[ChatPanel] Failed to get auth headers:", e);
2818
+ }
2819
+ }
2733
2820
  const response = await fetch(`${agentUrl}/chat/stream`, {
2734
2821
  method: "POST",
2735
2822
  headers: {
@@ -2738,12 +2825,15 @@ ${imageMarkdown}` : imageMarkdown;
2738
2825
  body: JSON.stringify({
2739
2826
  session_id: sessionId,
2740
2827
  message: userText,
2741
- current_page: currentPage || "dashboard",
2828
+ current_page: typeof window !== "undefined" ? window.location.href : currentPage || "dashboard",
2742
2829
  user_id: userId,
2743
2830
  org_id: orgId,
2744
2831
  user_name: userName,
2745
2832
  user_email: userEmail,
2746
- is_eval: isEval
2833
+ is_eval: isEval,
2834
+ auth_token: authToken,
2835
+ auth_token_type: authTokenType,
2836
+ product_backend_url: effectiveProductBackendUrl
2747
2837
  }),
2748
2838
  signal: controller.signal
2749
2839
  });
@@ -2935,6 +3025,7 @@ ${imageMarkdown}` : imageMarkdown;
2935
3025
  setPhase("idle");
2936
3026
  streamCompleted = true;
2937
3027
  } else if (eventType === "escalation") {
3028
+ console.log("[KiteChat] SSE escalation event received (AI-triggered)");
2938
3029
  setIsEscalated(true);
2939
3030
  setPhase("idle");
2940
3031
  const escalationMessageId = Date.now() + 2;
@@ -3007,7 +3098,7 @@ ${userText}`
3007
3098
  formData.append("file", file);
3008
3099
  formData.append("message", userText);
3009
3100
  formData.append("session_id", sessionId);
3010
- formData.append("current_page", currentPage || "dashboard");
3101
+ formData.append("current_page", typeof window !== "undefined" ? window.location.href : currentPage || "dashboard");
3011
3102
  if (orgId) formData.append("org_id", orgId);
3012
3103
  const controller = new AbortController();
3013
3104
  const timeoutId = setTimeout(() => controller.abort(), 12e4);
@@ -3781,9 +3872,10 @@ ${userText}`
3781
3872
  import_framer_motion2.motion.section,
3782
3873
  {
3783
3874
  drag: true,
3875
+ dragControls,
3876
+ dragListener: false,
3784
3877
  dragMomentum: false,
3785
3878
  dragElastic: 0,
3786
- animate: dragControls,
3787
3879
  onDragStart: () => setIsDragging(true),
3788
3880
  onDragEnd: handleDragEnd,
3789
3881
  className: `fixed bottom-4 z-40 flex flex-col bg-white border border-gray-200 rounded-2xl overflow-hidden shadow-2xl ${isDragging ? "cursor-grabbing" : ""} ${corner === "bottom-left" ? "left-4" : "right-4"} ${isOpen ? "opacity-100 scale-100" : "opacity-0 scale-95 pointer-events-none"}`,
@@ -3792,53 +3884,60 @@ ${userText}`
3792
3884
  height: `${PANEL_HEIGHT}px`
3793
3885
  },
3794
3886
  children: [
3795
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between px-4 py-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white shrink-0 cursor-grab active:cursor-grabbing", children: [
3796
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2.5", children: [
3797
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" }),
3798
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-sm font-semibold text-gray-800", children: "Copilot" })
3799
- ] }),
3800
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1", children: [
3801
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3802
- Button,
3803
- {
3804
- variant: "ghost",
3805
- size: "sm",
3806
- className: "h-7 w-7 p-0 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full",
3807
- onClick: () => {
3808
- setMessages([]);
3809
- resetSession();
3810
- setCurrentFolderId(void 0);
3811
- setActiveGuide(void 0);
3812
- activeGuideRef.current = void 0;
3813
- setGuideComplete(false);
3814
- },
3815
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.SquarePen, { className: "h-3 w-3" })
3816
- }
3817
- ),
3818
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3819
- Button,
3820
- {
3821
- variant: "ghost",
3822
- size: "sm",
3823
- className: "h-7 w-7 p-0 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full",
3824
- onClick: () => {
3825
- if (isEscalated) {
3826
- resetSession();
3827
- setMessages([]);
3828
- setPanelView("landing");
3829
- setCurrentFolderId(void 0);
3830
- setActiveGuide(void 0);
3831
- activeGuideRef.current = void 0;
3832
- setGuideComplete(false);
3887
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3888
+ "div",
3889
+ {
3890
+ className: "flex items-center justify-between px-4 py-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white shrink-0 cursor-grab active:cursor-grabbing",
3891
+ onPointerDown: (e) => dragControls.start(e),
3892
+ children: [
3893
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2.5", children: [
3894
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" }),
3895
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-sm font-semibold text-gray-800", children: "Copilot" })
3896
+ ] }),
3897
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1", children: [
3898
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3899
+ Button,
3900
+ {
3901
+ variant: "ghost",
3902
+ size: "sm",
3903
+ className: "h-7 w-7 p-0 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full",
3904
+ onClick: () => {
3905
+ setMessages([]);
3906
+ resetSession();
3907
+ setCurrentFolderId(void 0);
3908
+ setActiveGuide(void 0);
3909
+ activeGuideRef.current = void 0;
3910
+ setGuideComplete(false);
3911
+ },
3912
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.SquarePen, { className: "h-3 w-3" })
3833
3913
  }
3834
- onClose?.();
3835
- },
3836
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Minus, { className: "h-3.5 w-3.5" })
3837
- }
3838
- )
3839
- ] })
3840
- ] }),
3841
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col min-h-0 overflow-hidden relative", children: [
3914
+ ),
3915
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3916
+ Button,
3917
+ {
3918
+ variant: "ghost",
3919
+ size: "sm",
3920
+ className: "h-7 w-7 p-0 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full",
3921
+ onClick: () => {
3922
+ if (isEscalated) {
3923
+ resetSession();
3924
+ setMessages([]);
3925
+ setPanelView("landing");
3926
+ setCurrentFolderId(void 0);
3927
+ setActiveGuide(void 0);
3928
+ activeGuideRef.current = void 0;
3929
+ setGuideComplete(false);
3930
+ }
3931
+ onClose?.();
3932
+ },
3933
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Minus, { className: "h-3.5 w-3.5" })
3934
+ }
3935
+ )
3936
+ ] })
3937
+ ]
3938
+ }
3939
+ ),
3940
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col min-h-0 overflow-hidden relative select-text", children: [
3842
3941
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3843
3942
  "div",
3844
3943
  {
@@ -3958,8 +4057,8 @@ ${userText}`
3958
4057
  }
3959
4058
  const agentImageUrls = message.imageUrls || extractedImageUrls;
3960
4059
  const agentTextContent = contentStr.replace(/!\[image\]\([^)]+\)\n*/g, "").trim();
3961
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3962
- isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-1 ml-1", children: "Agent" }),
4060
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start ${isRoleChange ? "mt-2" : ""}`, children: [
4061
+ isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-0.5 ml-1", children: "Agent" }),
3963
4062
  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)(
3964
4063
  "button",
3965
4064
  {
package/dist/auto.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-Cl1sNjam.cjs';
1
+ import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-DiJ2Otkm.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-Cl1sNjam.js';
1
+ import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-DiJ2Otkm.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-AHIYBFU6.js";
3
+ } from "./chunk-FS6VPFSF.js";
4
4
 
5
5
  // src/auto.ts
6
6
  function mountKiteChat(config) {