@kite-copilot/chat-panel 0.2.55 → 0.2.56

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",
1901
+ customer_last_seen: (/* @__PURE__ */ new Date()).toISOString()
1902
+ }).eq("session_id", sessionId).then(
1903
+ () => console.log("[KiteChat] Successfully marked inactive on panel minimize"),
1904
+ (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",
1856
1912
  customer_last_seen: (/* @__PURE__ */ new Date()).toISOString()
1857
1913
  }).eq("session_id", sessionId).then(
1858
- () => console.log("[KiteChat] Successfully marked disconnected on panel close"),
1914
+ () => console.log("[KiteChat] Successfully marked active on panel reopen"),
1859
1915
  (err) => {
1860
- console.error("[KiteChat] Failed to mark disconnected on panel close:", 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);
@@ -2738,7 +2805,7 @@ ${imageMarkdown}` : imageMarkdown;
2738
2805
  body: JSON.stringify({
2739
2806
  session_id: sessionId,
2740
2807
  message: userText,
2741
- current_page: currentPage || "dashboard",
2808
+ current_page: typeof window !== "undefined" ? window.location.href : currentPage || "dashboard",
2742
2809
  user_id: userId,
2743
2810
  org_id: orgId,
2744
2811
  user_name: userName,
@@ -2935,6 +3002,7 @@ ${imageMarkdown}` : imageMarkdown;
2935
3002
  setPhase("idle");
2936
3003
  streamCompleted = true;
2937
3004
  } else if (eventType === "escalation") {
3005
+ console.log("[KiteChat] SSE escalation event received (AI-triggered)");
2938
3006
  setIsEscalated(true);
2939
3007
  setPhase("idle");
2940
3008
  const escalationMessageId = Date.now() + 2;
@@ -3007,7 +3075,7 @@ ${userText}`
3007
3075
  formData.append("file", file);
3008
3076
  formData.append("message", userText);
3009
3077
  formData.append("session_id", sessionId);
3010
- formData.append("current_page", currentPage || "dashboard");
3078
+ formData.append("current_page", typeof window !== "undefined" ? window.location.href : currentPage || "dashboard");
3011
3079
  if (orgId) formData.append("org_id", orgId);
3012
3080
  const controller = new AbortController();
3013
3081
  const timeoutId = setTimeout(() => controller.abort(), 12e4);
@@ -3781,9 +3849,10 @@ ${userText}`
3781
3849
  import_framer_motion2.motion.section,
3782
3850
  {
3783
3851
  drag: true,
3852
+ dragControls,
3853
+ dragListener: false,
3784
3854
  dragMomentum: false,
3785
3855
  dragElastic: 0,
3786
- animate: dragControls,
3787
3856
  onDragStart: () => setIsDragging(true),
3788
3857
  onDragEnd: handleDragEnd,
3789
3858
  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 +3861,60 @@ ${userText}`
3792
3861
  height: `${PANEL_HEIGHT}px`
3793
3862
  },
3794
3863
  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);
3864
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3865
+ "div",
3866
+ {
3867
+ 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",
3868
+ onPointerDown: (e) => dragControls.start(e),
3869
+ children: [
3870
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2.5", children: [
3871
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" }),
3872
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-sm font-semibold text-gray-800", children: "Copilot" })
3873
+ ] }),
3874
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1", children: [
3875
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3876
+ Button,
3877
+ {
3878
+ variant: "ghost",
3879
+ size: "sm",
3880
+ className: "h-7 w-7 p-0 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full",
3881
+ onClick: () => {
3882
+ setMessages([]);
3883
+ resetSession();
3884
+ setCurrentFolderId(void 0);
3885
+ setActiveGuide(void 0);
3886
+ activeGuideRef.current = void 0;
3887
+ setGuideComplete(false);
3888
+ },
3889
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.SquarePen, { className: "h-3 w-3" })
3833
3890
  }
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: [
3891
+ ),
3892
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3893
+ Button,
3894
+ {
3895
+ variant: "ghost",
3896
+ size: "sm",
3897
+ className: "h-7 w-7 p-0 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full",
3898
+ onClick: () => {
3899
+ if (isEscalated) {
3900
+ resetSession();
3901
+ setMessages([]);
3902
+ setPanelView("landing");
3903
+ setCurrentFolderId(void 0);
3904
+ setActiveGuide(void 0);
3905
+ activeGuideRef.current = void 0;
3906
+ setGuideComplete(false);
3907
+ }
3908
+ onClose?.();
3909
+ },
3910
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Minus, { className: "h-3.5 w-3.5" })
3911
+ }
3912
+ )
3913
+ ] })
3914
+ ]
3915
+ }
3916
+ ),
3917
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col min-h-0 overflow-hidden relative select-text", children: [
3842
3918
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3843
3919
  "div",
3844
3920
  {
@@ -3958,8 +4034,8 @@ ${userText}`
3958
4034
  }
3959
4035
  const agentImageUrls = message.imageUrls || extractedImageUrls;
3960
4036
  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" }),
4037
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start ${isRoleChange ? "mt-2" : ""}`, children: [
4038
+ isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-0.5 ml-1", children: "Agent" }),
3963
4039
  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
4040
  "button",
3965
4041
  {
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-YTH7EUFD.js";
4
4
 
5
5
  // src/auto.ts
6
6
  function mountKiteChat(config) {