@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.
- package/dist/ChatProvider/index.js +23 -0
- package/dist/components/ConversationsPanel/index.js +38 -1
- package/dist/constants/socketEvents.d.ts +14 -0
- package/dist/constants/socketEvents.js +12 -0
- package/dist/hooks/use-cmnd-chat.js +37 -1
- package/dist/hooks/use-socket.d.ts +13 -0
- package/dist/hooks/use-socket.js +25 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/services/socketService.d.ts +10 -0
- package/dist/services/socketService.js +40 -0
- package/dist/type.d.ts +4 -0
- package/package.json +2 -1
|
@@ -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
|
-
}, [
|
|
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.
|
|
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
|
}
|