@cmnd-ai/chatbot-react 1.13.0 → 1.14.0

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.
@@ -7,6 +7,8 @@ import deleteChatbotConversationMemory from "../services/deleteChatbotConversati
7
7
  import parseUITools from "../utils/parseUITools.js";
8
8
  import getTools from "../utils/getTools/index.js";
9
9
  import { ConversationsPanel, } from "../components/ConversationsPanel/index.js";
10
+ import socketService from "../services/socketService.js";
11
+ import { SOCKET_EVENTS } from "../constants/socketEvents.js";
10
12
  let globalChatbotConversationRef;
11
13
  let globalChatbotProps;
12
14
  export const ChatProviderContext = React.createContext(undefined);
@@ -64,7 +66,26 @@ function ChatProvider(props) {
64
66
  const conversationsPanelRef = useRef(null);
65
67
  useEffect(() => {
66
68
  globalChatbotConversationRef = chatbotConversationRef;
69
+ if (chatbotConversationRef && socketService.isConnected()) {
70
+ socketService.getSocket()?.emit(SOCKET_EVENTS.JOIN_CHATBOT_CONVERSATION, {
71
+ chatbotConversationRef,
72
+ });
73
+ }
67
74
  }, [chatbotConversationRef]);
75
+ const [isConnected, setIsConnected] = useState(false);
76
+ useEffect(() => {
77
+ const socket = socketService.connect(baseUrl);
78
+ const handleConnect = () => setIsConnected(true);
79
+ const handleDisconnect = () => setIsConnected(false);
80
+ socket.on(SOCKET_EVENTS.CONNECT, handleConnect);
81
+ socket.on(SOCKET_EVENTS.DISCONNECT, handleDisconnect);
82
+ setIsConnected(socket.connected);
83
+ return () => {
84
+ socket.off(SOCKET_EVENTS.CONNECT, handleConnect);
85
+ socket.off(SOCKET_EVENTS.DISCONNECT, handleDisconnect);
86
+ socketService.disconnect();
87
+ };
88
+ }, [baseUrl]);
68
89
  useEffect(() => {
69
90
  setLoading(true);
70
91
  getEmbedChatBotById(baseUrl, organizationId, chatbotId)
@@ -171,6 +192,8 @@ function ChatProvider(props) {
171
192
  closeSidePanel,
172
193
  setMessageText,
173
194
  sendMessage,
195
+ socket: socketService.getSocket(),
196
+ isConnected,
174
197
  }, children: error ? (_jsx("div", { children: "An error occured" })) : (_jsxs(_Fragment, { children: [props.children, _jsx(ConversationsPanel, { ref: conversationsPanelRef, organizationId: organizationId, chatbotId: chatbotId, baseUrl: baseUrl, chatHistoryStorageKey: props.chatHistoryStorageKey, theme: props.theme, enabledTools: getTools({
175
198
  apiTools: enabledTools,
176
199
  uiTools: parseUITools(UITools),
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useState, useEffect, forwardRef, useImperativeHandle, } from "react";
2
+ import React, { useState, useEffect, forwardRef, useImperativeHandle, useCallback, } from "react";
3
3
  import useFetchData from "../../hooks/use-fetch-data.js";
4
4
  import getChatBotConversationsList from "../../services/getChatBotConversationsList/index.js";
5
5
  import Conversation from "../Conversation.js";
@@ -11,6 +11,8 @@ import saveConversationIdToLocalStorage from "../../utils/saveConversationIdToLo
11
11
  import getUTCDateTime from "../../utils/getUTCDateTime/index.js";
12
12
  import useMessagesScroll from "../../hooks/use-messages-scroll.js";
13
13
  import { v4 as uuidv4 } from "uuid";
14
+ import { useSocket } from "../../hooks/use-socket.js";
15
+ import { SOCKET_MESSAGE_TYPES } from "../../constants/socketEvents.js";
14
16
  export const ConversationsPanel = forwardRef(({ organizationId, chatbotId, baseUrl, chatHistoryStorageKey, theme = "light", enabledTools = [], setChatbotConversationRef, chatbotConversationRef, postSessionMessage, Components, UITools, customStyles, isSidebarCollapsed, setIsSidebarCollapsed, selectedConversation, setSelectedConversation, messages, setMessages, input, setInput, inputRef, }, ref) => {
15
17
  const [isChatLoading, setIsChatLoading] = useState(false);
16
18
  const [canSendMessage, setCanSendMessage] = useState(true);
@@ -98,6 +100,41 @@ export const ConversationsPanel = forwardRef(({ organizationId, chatbotId, baseU
98
100
  return { chatbotConversations: [] };
99
101
  }
100
102
  }, [organizationId, chatbotId, conversationIds, baseUrl]);
103
+ // Socket message handler
104
+ const handleSocketMessage = useCallback((notification) => {
105
+ console.log("chatbot notification", notification);
106
+ if (notification.type === SOCKET_MESSAGE_TYPES.NEW_HUMAN_INBOX_MESSAGE &&
107
+ notification.chatbotConversationRef === chatbotConversationRef) {
108
+ setMessages((prev) => {
109
+ if (prev.some((m) => m.id === notification.message.id))
110
+ return prev;
111
+ return [...prev, notification.message];
112
+ });
113
+ resetMessagesScroll();
114
+ }
115
+ else if (notification.type === SOCKET_MESSAGE_TYPES.HUMAN_AGENT_JOINED &&
116
+ notification.chatbotConversationRef === chatbotConversationRef) {
117
+ // Add a system message to indicate human agent joined
118
+ const agentName = notification.agentName || "A human agent";
119
+ const systemMessage = {
120
+ id: `system-${notification.timestamp}`,
121
+ role: "assistant",
122
+ message: `${agentName} has joined the conversation`,
123
+ unuseful: false,
124
+ hiddenFromUser: false,
125
+ };
126
+ setMessages((prev) => [...prev, systemMessage]);
127
+ resetMessagesScroll();
128
+ }
129
+ }, [chatbotConversationRef, resetMessagesScroll]);
130
+ // Socket connection
131
+ useSocket({
132
+ baseUrl,
133
+ accessToken: null,
134
+ chatbotConversationRef,
135
+ onMessage: handleSocketMessage,
136
+ enabled: Boolean(chatbotConversationRef),
137
+ });
101
138
  const handleSendClick = () => {
102
139
  if (!input.trim() || !canSendMessage)
103
140
  return;
@@ -0,0 +1,14 @@
1
+ export declare const SOCKET_EVENTS: {
2
+ readonly MESSAGE: "message";
3
+ readonly CONNECT: "connect";
4
+ readonly DISCONNECT: "disconnect";
5
+ readonly ERROR: "error";
6
+ readonly JOIN_CHATBOT_CONVERSATION: "join_chatbot_conversation";
7
+ };
8
+ export declare const SOCKET_MESSAGE_TYPES: {
9
+ readonly NEW_HUMAN_INBOX_MESSAGE: "NEW_HUMAN_INBOX_MESSAGE";
10
+ readonly HUMAN_AGENT_JOINED: "HUMAN_AGENT_JOINED";
11
+ readonly HANDOVER_REQUEST: "HANDOVER_REQUEST";
12
+ };
13
+ export type SocketEvent = (typeof SOCKET_EVENTS)[keyof typeof SOCKET_EVENTS];
14
+ export type SocketMessageType = (typeof SOCKET_MESSAGE_TYPES)[keyof typeof SOCKET_MESSAGE_TYPES];
@@ -0,0 +1,12 @@
1
+ export const SOCKET_EVENTS = {
2
+ MESSAGE: "message",
3
+ CONNECT: "connect",
4
+ DISCONNECT: "disconnect",
5
+ ERROR: "error",
6
+ JOIN_CHATBOT_CONVERSATION: "join_chatbot_conversation",
7
+ };
8
+ export const SOCKET_MESSAGE_TYPES = {
9
+ NEW_HUMAN_INBOX_MESSAGE: "NEW_HUMAN_INBOX_MESSAGE",
10
+ HUMAN_AGENT_JOINED: "HUMAN_AGENT_JOINED",
11
+ HANDOVER_REQUEST: "HANDOVER_REQUEST",
12
+ };
@@ -10,6 +10,8 @@ import getUTCDateTime from "../utils/getUTCDateTime/index.js";
10
10
  import parseUITools from "../utils/parseUITools.js";
11
11
  import getTools from "../utils/getTools/index.js";
12
12
  import { cleanMarkdown } from "../utils/cleanMarkdown.js";
13
+ import { useSocket } from "./use-socket.js";
14
+ import { SOCKET_MESSAGE_TYPES } from "../constants/socketEvents.js";
13
15
  /**
14
16
  * A comprehensive hook to manage CMND.AI chatbot logic without being forced to use the default UI.
15
17
  * This hook handles conversation state, message sending (including streaming), history management, and tool call orchestration.
@@ -90,6 +92,35 @@ export const useCMNDChat = (options) => {
90
92
  useEffect(() => {
91
93
  refreshConversations();
92
94
  }, [refreshConversations]);
95
+ const handleSocketMessage = useCallback((notification) => {
96
+ if (notification.type === SOCKET_MESSAGE_TYPES.NEW_HUMAN_INBOX_MESSAGE &&
97
+ notification.chatbotConversationRef === chatbotConversationRef) {
98
+ setMessages((prev) => {
99
+ if (prev.some((m) => m.id === notification.message.id))
100
+ return prev;
101
+ return [...prev, notification.message];
102
+ });
103
+ }
104
+ else if (notification.type === SOCKET_MESSAGE_TYPES.HUMAN_AGENT_JOINED &&
105
+ notification.chatbotConversationRef === chatbotConversationRef) {
106
+ // Add a system message to indicate human agent joined
107
+ const agentName = notification.agentName || "A human agent";
108
+ const systemMessage = {
109
+ id: `system-${notification.timestamp}`,
110
+ role: MessageRole.ASSISTANT,
111
+ message: `${agentName} has joined the conversation`,
112
+ unuseful: false,
113
+ hiddenFromUser: false,
114
+ };
115
+ setMessages((prev) => [...prev, systemMessage]);
116
+ }
117
+ }, [chatbotConversationRef]);
118
+ useSocket({
119
+ baseUrl,
120
+ chatbotConversationRef,
121
+ onMessage: handleSocketMessage,
122
+ enabled: !!baseUrl,
123
+ });
93
124
  const handleStreamData = useCallback(async (data, currentMessages, onDataCallback) => {
94
125
  if (onDataCallback)
95
126
  onDataCallback(data);
@@ -358,7 +389,12 @@ export const useCMNDChat = (options) => {
358
389
  catch (err) {
359
390
  console.error("Error deleting conversation:", err);
360
391
  }
361
- }, [localStorageKey, selectedConversation, handleNewChat, refreshConversations]);
392
+ }, [
393
+ localStorageKey,
394
+ selectedConversation,
395
+ handleNewChat,
396
+ refreshConversations,
397
+ ]);
362
398
  const visibleMessages = useMemo(() => {
363
399
  return messages
364
400
  .filter((msg) => !msg.hiddenFromUser && msg.role !== MessageRole.FUNCTION)
@@ -0,0 +1,13 @@
1
+ import { Socket } from "socket.io-client";
2
+ interface UseSocketOptions {
3
+ baseUrl?: string;
4
+ accessToken?: string | null;
5
+ chatbotConversationRef?: string;
6
+ onMessage?: (data: any) => void;
7
+ enabled?: boolean;
8
+ }
9
+ export declare const useSocket: ({ baseUrl, accessToken, chatbotConversationRef, onMessage, enabled, }: UseSocketOptions) => {
10
+ socket: Socket<import("@socket.io/component-emitter").DefaultEventsMap, import("@socket.io/component-emitter").DefaultEventsMap> | null;
11
+ isConnected: boolean;
12
+ };
13
+ export default useSocket;
@@ -0,0 +1,25 @@
1
+ import { useEffect, useRef } from "react";
2
+ import socketService from "../services/socketService.js";
3
+ import { SOCKET_EVENTS } from "../constants/socketEvents.js";
4
+ export const useSocket = ({ baseUrl, accessToken, chatbotConversationRef, onMessage, enabled = true, }) => {
5
+ const socketRef = useRef(null);
6
+ useEffect(() => {
7
+ if (!enabled || !baseUrl)
8
+ return;
9
+ const socket = socketService.connect(baseUrl, accessToken, chatbotConversationRef);
10
+ socketRef.current = socket;
11
+ const handleMessage = (data) => {
12
+ if (onMessage)
13
+ onMessage(data);
14
+ };
15
+ socket.on(SOCKET_EVENTS.MESSAGE, handleMessage);
16
+ return () => {
17
+ socket.off(SOCKET_EVENTS.MESSAGE, handleMessage);
18
+ };
19
+ }, [baseUrl, accessToken, chatbotConversationRef, enabled, onMessage]);
20
+ return {
21
+ socket: socketRef.current,
22
+ isConnected: socketService.isConnected(),
23
+ };
24
+ };
25
+ export default useSocket;
package/dist/index.d.ts CHANGED
@@ -6,3 +6,6 @@ export { CmndChatContext, InputFieldProps, SendButtonProps, CustomStyles, CMNDCh
6
6
  export { setCurrentConversationMemory, deleteCurrentConversationMemory, } from "./ChatProvider/index.js";
7
7
  export { useCMNDChatContext } from "./ChatProvider/useChatContext.js";
8
8
  export { useCMNDChat, UseCMNDChatOptions, UseCMNDChatResult, } from "./hooks/use-cmnd-chat.js";
9
+ export { useSocket } from "./hooks/use-socket.js";
10
+ export { default as socketService } from "./services/socketService.js";
11
+ export { SOCKET_EVENTS, SOCKET_MESSAGE_TYPES, SocketEvent, SocketMessageType, } from "./constants/socketEvents.js";
package/dist/index.js CHANGED
@@ -6,3 +6,7 @@ export { MessageRole, FunctionType, } from "./type.js";
6
6
  export { setCurrentConversationMemory, deleteCurrentConversationMemory, } from "./ChatProvider/index.js";
7
7
  export { useCMNDChatContext } from "./ChatProvider/useChatContext.js";
8
8
  export { useCMNDChat, } from "./hooks/use-cmnd-chat.js";
9
+ // Socket exports
10
+ export { useSocket } from "./hooks/use-socket.js";
11
+ export { default as socketService } from "./services/socketService.js";
12
+ export { SOCKET_EVENTS, SOCKET_MESSAGE_TYPES, } from "./constants/socketEvents.js";
@@ -0,0 +1,10 @@
1
+ import { Socket } from "socket.io-client";
2
+ declare class SocketService {
3
+ private socket;
4
+ connect(baseUrl: string, accessToken?: string | null, chatbotConversationRef?: string): Socket;
5
+ disconnect(): void;
6
+ getSocket(): Socket | null;
7
+ isConnected(): boolean;
8
+ }
9
+ export declare const socketService: SocketService;
10
+ export default socketService;
@@ -0,0 +1,40 @@
1
+ import { io } from "socket.io-client";
2
+ import { SOCKET_EVENTS } from "../constants/socketEvents.js";
3
+ class SocketService {
4
+ socket = null;
5
+ connect(baseUrl, accessToken, chatbotConversationRef) {
6
+ if (this.socket?.connected) {
7
+ if (chatbotConversationRef) {
8
+ this.socket.emit(SOCKET_EVENTS.JOIN_CHATBOT_CONVERSATION, {
9
+ chatbotConversationRef,
10
+ });
11
+ }
12
+ return this.socket;
13
+ }
14
+ this.socket = io(baseUrl, {
15
+ auth: {
16
+ accessToken,
17
+ chatbotConversationRef,
18
+ },
19
+ transports: ["websocket", "polling"],
20
+ reconnection: true,
21
+ reconnectionAttempts: 5,
22
+ reconnectionDelay: 1000,
23
+ });
24
+ return this.socket;
25
+ }
26
+ disconnect() {
27
+ if (this.socket) {
28
+ this.socket.disconnect();
29
+ this.socket = null;
30
+ }
31
+ }
32
+ getSocket() {
33
+ return this.socket;
34
+ }
35
+ isConnected() {
36
+ return this.socket?.connected ?? false;
37
+ }
38
+ }
39
+ export const socketService = new SocketService();
40
+ export default socketService;
package/dist/type.d.ts CHANGED
@@ -19,6 +19,10 @@ export interface CmndChatContext {
19
19
  setMessageText: (text: string) => void;
20
20
  /** Send the current message, or set and send if text is provided. */
21
21
  sendMessage: (text?: string) => void;
22
+ /** The socket instance. */
23
+ socket: any | null;
24
+ /** Whether the socket is connected. */
25
+ isConnected: boolean;
22
26
  }
23
27
  /**
24
28
  * Props for a custom input field component.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmnd-ai/chatbot-react",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "main": "dist/index.js",
5
5
  "description": "",
6
6
  "type": "module",
@@ -37,6 +37,7 @@
37
37
  "react-markdown": "^8.0.6",
38
38
  "react-syntax-highlighter": "^15.5.0",
39
39
  "remark-gfm": "^3.0.1",
40
+ "socket.io-client": "^4.8.3",
40
41
  "uuid": "^13.0.0"
41
42
  }
42
43
  }