@cmnd-ai/chatbot-react 1.9.2 → 1.13.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.
Files changed (34) hide show
  1. package/Readme.md +113 -9
  2. package/dist/ChatProvider/index.d.ts +2 -2
  3. package/dist/ChatProvider/index.js +13 -13
  4. package/dist/ChatProvider/processStream/index.js +40 -15
  5. package/dist/CmndChatBot/index.d.ts +2 -1
  6. package/dist/CmndChatBot/index.js +2 -2
  7. package/dist/components/Chatbubble.d.ts +2 -2
  8. package/dist/components/Chatbubble.js +11 -11
  9. package/dist/components/Conversation.d.ts +3 -3
  10. package/dist/components/Conversation.js +2 -2
  11. package/dist/components/ConversationCard.js +1 -1
  12. package/dist/components/ConversationsPanel/index.d.ts +2 -2
  13. package/dist/components/ConversationsPanel/index.js +25 -23
  14. package/dist/components/LoadingBubble.js +1 -1
  15. package/dist/constants/endpoints.d.ts +3 -3
  16. package/dist/constants/endpoints.js +2 -2
  17. package/dist/hooks/use-cmnd-chat.d.ts +130 -0
  18. package/dist/hooks/use-cmnd-chat.js +396 -0
  19. package/dist/index.d.ts +4 -1
  20. package/dist/index.js +4 -0
  21. package/dist/services/deleteChatbotConversationMemory/index.d.ts +2 -2
  22. package/dist/services/deleteChatbotConversationMemory/index.js +2 -2
  23. package/dist/services/getChatBotConversationsList/index.d.ts +1 -1
  24. package/dist/services/getEmbedChatBotById.d.ts +1 -1
  25. package/dist/services/patchChatbotConversationMemory/index.d.ts +2 -2
  26. package/dist/services/patchChatbotConversationMemory/index.js +2 -2
  27. package/dist/type.d.ts +134 -41
  28. package/dist/type.js +12 -0
  29. package/dist/utils/cleanMarkdown.d.ts +8 -0
  30. package/dist/utils/cleanMarkdown.js +31 -0
  31. package/dist/utils/saveConversationIdToLocalStorage/index.d.ts +2 -2
  32. package/dist/utils/saveConversationIdToLocalStorage/index.js +8 -7
  33. package/package.json +4 -3
  34. package/readme.dev.md +49 -0
@@ -0,0 +1,396 @@
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import postUserConversation from "../services/postUserConversation.js";
4
+ import getChatBotConversationsList from "../services/getChatBotConversationsList/index.js";
5
+ import getEmbedChatBotById from "../services/getEmbedChatBotById.js";
6
+ import { MessageRole, FunctionType, } from "../type.js";
7
+ import getConversationLocalStorageKey from "../utils/getConversationLocalStorageKey/index.js";
8
+ import saveConversationIdToLocalStorage from "../utils/saveConversationIdToLocalStorage/index.js";
9
+ import getUTCDateTime from "../utils/getUTCDateTime/index.js";
10
+ import parseUITools from "../utils/parseUITools.js";
11
+ import getTools from "../utils/getTools/index.js";
12
+ import { cleanMarkdown } from "../utils/cleanMarkdown.js";
13
+ /**
14
+ * A comprehensive hook to manage CMND.AI chatbot logic without being forced to use the default UI.
15
+ * This hook handles conversation state, message sending (including streaming), history management, and tool call orchestration.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * const { messages, sendMessage, input, setInput, submitToolResult } = useCMNDChat({
20
+ * chatbotId: 1,
21
+ * organizationId: 1,
22
+ * baseUrl: 'https://api.cmnd.ai',
23
+ * cleanResponse: true, // Optional: Returns assistant messages as plaintext
24
+ * onToolCall: async (toolDetails) => {
25
+ * // Backend tools are auto-confirmed by the hook
26
+ * if (toolDetails.name === 'get_playlists') {
27
+ * console.log('Fetching playlists from backend...');
28
+ * }
29
+ *
30
+ * // UI tools need manual execution and confirmation
31
+ * if (toolDetails.name === 'show_notification') {
32
+ * showNotification(toolDetails.args.message);
33
+ * submitToolResult('Notification displayed');
34
+ * }
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ export const useCMNDChat = (options) => {
40
+ const { chatbotId, organizationId, baseUrl, apiKey, chatHistoryStorageKey, initialMemory, UITools, onData: globalOnData, onToolCall, cleanResponse = false, } = options;
41
+ const [messages, setMessages] = useState([]);
42
+ const [input, setInput] = useState("");
43
+ const [isChatLoading, setIsChatLoading] = useState(false);
44
+ const [canSendMessage, setCanSendMessage] = useState(true);
45
+ const [chatbotConversationRef, setChatbotConversationRef] = useState(undefined);
46
+ const [conversations, setConversations] = useState([]);
47
+ const [selectedConversation, setSelectedConversation] = useState(null);
48
+ const [enabledTools, setEnabledTools] = useState([]);
49
+ const [error, setError] = useState(null);
50
+ const lastProcessedToolCallId = useRef(null);
51
+ const localStorageKey = useMemo(() => chatHistoryStorageKey ||
52
+ getConversationLocalStorageKey({ organizationId, chatbotId }), [chatHistoryStorageKey, organizationId, chatbotId]);
53
+ const tools = useMemo(() => getTools({
54
+ apiTools: enabledTools,
55
+ uiTools: parseUITools(UITools),
56
+ }), [enabledTools, UITools]);
57
+ // Fetch Chatbot Info for enabled tools
58
+ useEffect(() => {
59
+ if (!baseUrl || !organizationId || !chatbotId)
60
+ return;
61
+ getEmbedChatBotById(baseUrl, organizationId, chatbotId)
62
+ .then((response) => {
63
+ const { chatbot } = response.data;
64
+ setEnabledTools(chatbot.enabledTools || []);
65
+ })
66
+ .catch((err) => {
67
+ console.error("Error fetching chatbot tools:", err);
68
+ });
69
+ }, [baseUrl, organizationId, chatbotId]);
70
+ const refreshConversations = useCallback(async () => {
71
+ try {
72
+ const storedIds = JSON.parse(localStorage.getItem(localStorageKey) || "[]");
73
+ if (storedIds.length > 0) {
74
+ const response = await getChatBotConversationsList({
75
+ organizationId,
76
+ chatbotId,
77
+ conversationIds: storedIds,
78
+ baseUrl,
79
+ });
80
+ setConversations(response.chatbotConversations || []);
81
+ }
82
+ else {
83
+ setConversations([]);
84
+ }
85
+ }
86
+ catch (err) {
87
+ console.error("Error fetching conversations:", err);
88
+ }
89
+ }, [baseUrl, chatbotId, localStorageKey, organizationId]);
90
+ useEffect(() => {
91
+ refreshConversations();
92
+ }, [refreshConversations]);
93
+ const handleStreamData = useCallback(async (data, currentMessages, onDataCallback) => {
94
+ if (onDataCallback)
95
+ onDataCallback(data);
96
+ if (globalOnData)
97
+ globalOnData(data);
98
+ // Handle streaming assistant message
99
+ if (data.message && !data.finalResponseWithUsageData) {
100
+ // Clear loading state when assistant starts responding
101
+ setIsChatLoading(false);
102
+ setCanSendMessage(true);
103
+ const assistantMessage = {
104
+ role: MessageRole.ASSISTANT,
105
+ message: data.message,
106
+ id: "streaming-assistant-message",
107
+ unuseful: false,
108
+ hiddenFromUser: false,
109
+ };
110
+ setMessages([...currentMessages, assistantMessage]);
111
+ }
112
+ if (data.finalResponseWithUsageData) {
113
+ const { messages: updatedMessages, message, hasError } = data;
114
+ const newConversationId = data.chatbotConversationRef || data.conversationRef;
115
+ if (hasError) {
116
+ setError(message || "Oops! I ran into a problem.");
117
+ }
118
+ if (newConversationId) {
119
+ setChatbotConversationRef(newConversationId);
120
+ await saveConversationIdToLocalStorage({
121
+ chatbotConversationRef: newConversationId,
122
+ chatbotId,
123
+ organizationId,
124
+ chatHistoryStorageKey,
125
+ });
126
+ refreshConversations();
127
+ }
128
+ if (updatedMessages) {
129
+ setMessages(updatedMessages);
130
+ }
131
+ else if (message) {
132
+ // If we have a standalone message in the final response (e.g. an error)
133
+ const assistantMessage = {
134
+ role: MessageRole.ASSISTANT,
135
+ message: message,
136
+ id: uuidv4(),
137
+ unuseful: false,
138
+ hiddenFromUser: false,
139
+ };
140
+ setMessages([...currentMessages, assistantMessage]);
141
+ }
142
+ // Ensure loading state is cleared (in case no streaming message came)
143
+ setIsChatLoading(false);
144
+ setCanSendMessage(true);
145
+ }
146
+ }, [
147
+ globalOnData,
148
+ refreshConversations,
149
+ chatbotId,
150
+ organizationId,
151
+ chatHistoryStorageKey,
152
+ ]);
153
+ const sendMessage = useCallback(async (text, onData) => {
154
+ const messageToSend = text !== undefined ? text : input;
155
+ if (!messageToSend.trim() || !canSendMessage)
156
+ return;
157
+ const userMessage = {
158
+ role: MessageRole.USER,
159
+ message: messageToSend,
160
+ id: uuidv4(),
161
+ unuseful: false,
162
+ hiddenFromUser: false,
163
+ };
164
+ const newMessages = [...messages, userMessage];
165
+ setMessages(newMessages);
166
+ if (text === undefined)
167
+ setInput("");
168
+ setIsChatLoading(true);
169
+ setCanSendMessage(false);
170
+ setError(null);
171
+ const payload = {
172
+ messages: newMessages,
173
+ uiTools: parseUITools(UITools),
174
+ };
175
+ if (chatbotConversationRef) {
176
+ payload["chatbotConversationRef"] = chatbotConversationRef;
177
+ }
178
+ else if (initialMemory) {
179
+ payload["initialMemory"] = initialMemory;
180
+ }
181
+ try {
182
+ await postUserConversation({
183
+ payload,
184
+ apikey: apiKey,
185
+ chatbotId,
186
+ baseUrl,
187
+ onData: (data) => handleStreamData(data, newMessages, onData),
188
+ });
189
+ }
190
+ catch (err) {
191
+ console.error("Error sending message:", err);
192
+ setError(err.message || "Failed to send message");
193
+ setIsChatLoading(false);
194
+ setCanSendMessage(true);
195
+ }
196
+ }, [
197
+ input,
198
+ canSendMessage,
199
+ messages,
200
+ UITools,
201
+ chatbotConversationRef,
202
+ initialMemory,
203
+ chatbotId,
204
+ baseUrl,
205
+ apiKey,
206
+ handleStreamData,
207
+ ]);
208
+ // Confirm a backend tool (sets confirmed: true, server will add output)
209
+ const confirmBackendTool = useCallback(async () => {
210
+ const lastMessage = messages[messages.length - 1];
211
+ if (!lastMessage || lastMessage.role !== MessageRole.FUNCTION) {
212
+ return;
213
+ }
214
+ const updatedLastMessage = {
215
+ ...lastMessage,
216
+ tool: {
217
+ ...lastMessage.tool,
218
+ confirmed: true,
219
+ },
220
+ };
221
+ const newMessages = [...messages.slice(0, -1), updatedLastMessage];
222
+ setMessages(newMessages);
223
+ // Keep loading state true while backend tool executes
224
+ setIsChatLoading(true);
225
+ setCanSendMessage(false);
226
+ const payload = {
227
+ messages: newMessages,
228
+ uiTools: parseUITools(UITools),
229
+ chatbotConversationRef,
230
+ };
231
+ try {
232
+ await postUserConversation({
233
+ payload,
234
+ apikey: apiKey,
235
+ chatbotId,
236
+ baseUrl,
237
+ onData: (data) => handleStreamData(data, newMessages),
238
+ });
239
+ }
240
+ catch (err) {
241
+ console.error("Error confirming backend tool:", err);
242
+ setError(err.message || "Failed to confirm backend tool");
243
+ setIsChatLoading(false);
244
+ setCanSendMessage(true);
245
+ }
246
+ }, [
247
+ messages,
248
+ UITools,
249
+ chatbotConversationRef,
250
+ apiKey,
251
+ chatbotId,
252
+ baseUrl,
253
+ handleStreamData,
254
+ ]);
255
+ const submitToolResult = useCallback(async (toolOutput, onData) => {
256
+ const lastMessage = messages[messages.length - 1];
257
+ if (!lastMessage || lastMessage.role !== MessageRole.FUNCTION) {
258
+ console.error("Last message is not a function call");
259
+ return;
260
+ }
261
+ const updatedLastMessage = {
262
+ ...lastMessage,
263
+ tool: {
264
+ ...lastMessage.tool,
265
+ output: toolOutput,
266
+ confirmed: true,
267
+ runAt: new Date().toISOString(),
268
+ },
269
+ };
270
+ const newMessages = [...messages.slice(0, -1), updatedLastMessage];
271
+ setMessages(newMessages);
272
+ setIsChatLoading(true);
273
+ setCanSendMessage(false);
274
+ const payload = {
275
+ messages: newMessages,
276
+ uiTools: parseUITools(UITools),
277
+ chatbotConversationRef,
278
+ };
279
+ try {
280
+ await postUserConversation({
281
+ payload,
282
+ apikey: apiKey,
283
+ chatbotId,
284
+ baseUrl,
285
+ onData: (data) => handleStreamData(data, newMessages, onData),
286
+ });
287
+ }
288
+ catch (err) {
289
+ console.error("Error submitting tool result:", err);
290
+ setError(err.message || "Failed to submit tool result");
291
+ setIsChatLoading(false);
292
+ setCanSendMessage(true);
293
+ }
294
+ }, [
295
+ messages,
296
+ UITools,
297
+ chatbotConversationRef,
298
+ apiKey,
299
+ chatbotId,
300
+ baseUrl,
301
+ handleStreamData,
302
+ ]);
303
+ // Handle tool call detection and auto-confirmation
304
+ useEffect(() => {
305
+ const lastMessage = messages[messages.length - 1];
306
+ if (lastMessage &&
307
+ lastMessage.role === MessageRole.FUNCTION &&
308
+ lastMessage.id !== lastProcessedToolCallId.current) {
309
+ const toolDetails = lastMessage.tool;
310
+ const toolDef = tools.find((t) => t.name === toolDetails.name);
311
+ // Notify consumer about the tool call
312
+ if (onToolCall)
313
+ onToolCall(toolDetails);
314
+ // Auto-confirm backend tools so the server can execute them
315
+ if (toolDef?.functionType === FunctionType.BACKEND &&
316
+ !toolDetails.confirmed) {
317
+ lastProcessedToolCallId.current = lastMessage.id;
318
+ confirmBackendTool();
319
+ }
320
+ }
321
+ }, [messages, tools, onToolCall, confirmBackendTool]);
322
+ const handleNewChat = useCallback(() => {
323
+ setSelectedConversation(null);
324
+ setChatbotConversationRef(undefined);
325
+ setMessages([]);
326
+ setInput("");
327
+ const dateString = getUTCDateTime();
328
+ const newConversation = {
329
+ chatbotConversationRef: uuidv4(),
330
+ messages: [],
331
+ chatbotId: chatbotId,
332
+ chatbotConversationTitle: "New Conversation",
333
+ createdAt: dateString,
334
+ updatedAt: dateString,
335
+ totalCostSoFar: 0,
336
+ totalTokensSoFar: 0,
337
+ };
338
+ setSelectedConversation(newConversation);
339
+ }, [chatbotId]);
340
+ const handleConversationSelect = useCallback((conversation) => {
341
+ setSelectedConversation(conversation);
342
+ setChatbotConversationRef(conversation.chatbotConversationRef);
343
+ setMessages(conversation.messages.map((m) => ({
344
+ ...m,
345
+ id: m.id || uuidv4(),
346
+ })));
347
+ }, []);
348
+ const handleDeleteConversation = useCallback(async (conversationId) => {
349
+ try {
350
+ const storedIds = JSON.parse(localStorage.getItem(localStorageKey) || "[]");
351
+ const updatedIds = storedIds.filter((id) => id !== conversationId);
352
+ localStorage.setItem(localStorageKey, JSON.stringify(updatedIds));
353
+ if (selectedConversation?.chatbotConversationRef === conversationId) {
354
+ handleNewChat();
355
+ }
356
+ refreshConversations();
357
+ }
358
+ catch (err) {
359
+ console.error("Error deleting conversation:", err);
360
+ }
361
+ }, [localStorageKey, selectedConversation, handleNewChat, refreshConversations]);
362
+ const visibleMessages = useMemo(() => {
363
+ return messages
364
+ .filter((msg) => !msg.hiddenFromUser && msg.role !== MessageRole.FUNCTION)
365
+ .map((msg) => {
366
+ if (cleanResponse &&
367
+ msg.role === MessageRole.ASSISTANT &&
368
+ msg.message) {
369
+ return {
370
+ ...msg,
371
+ message: cleanMarkdown(msg.message),
372
+ };
373
+ }
374
+ return msg;
375
+ });
376
+ }, [messages, cleanResponse]);
377
+ return {
378
+ messages: visibleMessages,
379
+ setMessages,
380
+ input,
381
+ setInput,
382
+ isChatLoading,
383
+ canSendMessage,
384
+ sendMessage,
385
+ submitToolResult,
386
+ chatbotConversationRef,
387
+ conversations,
388
+ selectedConversation,
389
+ handleNewChat,
390
+ handleConversationSelect,
391
+ handleDeleteConversation,
392
+ refreshConversations,
393
+ tools,
394
+ error,
395
+ };
396
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ export { default as CmndChatBot } from "./CmndChatBot/index.js";
1
2
  export { default as ChatProvider } from "./ChatProvider/index.js";
2
3
  export { ConversationsPanel } from "./components/ConversationsPanel/index.js";
3
- export { CmndChatContext, InputFieldProps, SendButtonProps, CustomStyles, CMNDChatMemory, ChatbotConversation, ChatbotConversationsResponse, } from "./type.js";
4
+ export { default as Conversation } from "./components/Conversation.js";
5
+ export { CmndChatContext, InputFieldProps, SendButtonProps, CustomStyles, CMNDChatMemory, ChatbotConversation, ChatbotConversationsResponse, Message, MessageRole, ConversationDataObject, ToolDetails, FunctionType, CMNDChatbotUITool, ChatbotConversationMessage, } from "./type.js";
4
6
  export { setCurrentConversationMemory, deleteCurrentConversationMemory, } from "./ChatProvider/index.js";
5
7
  export { useCMNDChatContext } from "./ChatProvider/useChatContext.js";
8
+ export { useCMNDChat, UseCMNDChatOptions, UseCMNDChatResult, } from "./hooks/use-cmnd-chat.js";
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
+ export { default as CmndChatBot } from "./CmndChatBot/index.js";
1
2
  export { default as ChatProvider } from "./ChatProvider/index.js";
2
3
  export { ConversationsPanel } from "./components/ConversationsPanel/index.js";
4
+ export { default as Conversation } from "./components/Conversation.js";
5
+ export { MessageRole, FunctionType, } from "./type.js";
3
6
  export { setCurrentConversationMemory, deleteCurrentConversationMemory, } from "./ChatProvider/index.js";
4
7
  export { useCMNDChatContext } from "./ChatProvider/useChatContext.js";
8
+ export { useCMNDChat, } from "./hooks/use-cmnd-chat.js";
@@ -1,9 +1,9 @@
1
1
  interface IDeleteChatbotConversationMemory {
2
2
  organizationId: number;
3
3
  chatbotId: number;
4
- chatbotConversationId: number;
4
+ chatbotConversationRef: string;
5
5
  memoryKeyToDelete: string;
6
6
  baseUrl: string;
7
7
  }
8
- declare const deleteChatbotConversationMemory: ({ organizationId, chatbotId, chatbotConversationId, memoryKeyToDelete, baseUrl, }: IDeleteChatbotConversationMemory) => Promise<import("axios").AxiosResponse<any, any>>;
8
+ declare const deleteChatbotConversationMemory: ({ organizationId, chatbotId, chatbotConversationRef, memoryKeyToDelete, baseUrl, }: IDeleteChatbotConversationMemory) => Promise<import("axios").AxiosResponse<any, any, {}>>;
9
9
  export default deleteChatbotConversationMemory;
@@ -1,7 +1,7 @@
1
1
  import { chatbot } from "../../constants/endpoints.js";
2
2
  import axios from "axios";
3
- const deleteChatbotConversationMemory = ({ organizationId, chatbotId, chatbotConversationId, memoryKeyToDelete, baseUrl, }) => {
4
- const endpoint = chatbot.deleteChatbotConversationMemory(organizationId, chatbotId, chatbotConversationId, memoryKeyToDelete);
3
+ const deleteChatbotConversationMemory = ({ organizationId, chatbotId, chatbotConversationRef, memoryKeyToDelete, baseUrl, }) => {
4
+ const endpoint = chatbot.deleteChatbotConversationMemory(organizationId, chatbotId, chatbotConversationRef, memoryKeyToDelete);
5
5
  return axios.delete(`${baseUrl}${endpoint}`);
6
6
  };
7
7
  export default deleteChatbotConversationMemory;
@@ -1,7 +1,7 @@
1
1
  declare const getChatBotConversationsList: ({ organizationId, chatbotId, conversationIds, baseUrl, }: {
2
2
  organizationId: number;
3
3
  chatbotId: number;
4
- conversationIds: number[];
4
+ conversationIds: string[];
5
5
  baseUrl: string;
6
6
  }) => Promise<any>;
7
7
  export default getChatBotConversationsList;
@@ -1,2 +1,2 @@
1
- declare const getEmbedChatBotById: (baseUrl: string, organizationId: number, chatbotId: number) => Promise<import("axios").AxiosResponse<any, any>>;
1
+ declare const getEmbedChatBotById: (baseUrl: string, organizationId: number, chatbotId: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
2
2
  export default getEmbedChatBotById;
@@ -1,11 +1,11 @@
1
1
  interface IPatchChatbotConversationMemory {
2
2
  organizationId: number;
3
3
  chatbotId: number;
4
- chatbotConversationId: number;
4
+ chatbotConversationRef: string;
5
5
  memory: {
6
6
  [key: string]: any;
7
7
  };
8
8
  baseUrl: string;
9
9
  }
10
- declare const patchChatbotConversationMemory: ({ organizationId, chatbotId, chatbotConversationId, memory, baseUrl, }: IPatchChatbotConversationMemory) => Promise<import("axios").AxiosResponse<any, any>>;
10
+ declare const patchChatbotConversationMemory: ({ organizationId, chatbotId, chatbotConversationRef, memory, baseUrl, }: IPatchChatbotConversationMemory) => Promise<import("axios").AxiosResponse<any, any, {}>>;
11
11
  export default patchChatbotConversationMemory;
@@ -1,7 +1,7 @@
1
1
  import axios from "axios";
2
2
  import { chatbot } from "../../constants/endpoints.js";
3
- const patchChatbotConversationMemory = ({ organizationId, chatbotId, chatbotConversationId, memory, baseUrl, }) => {
4
- const endpoint = chatbot.patchChatbotConversationMemory(organizationId, chatbotId, chatbotConversationId);
3
+ const patchChatbotConversationMemory = ({ organizationId, chatbotId, chatbotConversationRef, memory, baseUrl, }) => {
4
+ const endpoint = chatbot.patchChatbotConversationMemory(organizationId, chatbotId, chatbotConversationRef);
5
5
  return axios.patch(`${baseUrl}${endpoint}`, {
6
6
  memory,
7
7
  });