@cmnd-ai/chatbot-react 1.4.0 → 1.5.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.
@@ -6,6 +6,8 @@ import Conversation from "../components/Conversation.js";
6
6
  import getChatBotById from "../services/getchatBotById.js";
7
7
  import patchChatbotConversationMemory from "../services/patchChatbotConversationMemory/index.js";
8
8
  import deleteChatbotConversationMemory from "../services/deleteChatbotConversationMemory/index.js";
9
+ import parseUITools from "../utils/parseUITools.js";
10
+ import getTools from "../utils/getTools/index.js";
9
11
  let globalChatbotConversationId;
10
12
  let globalChatbotProps;
11
13
  export const ChatProviderContext = React.createContext(undefined);
@@ -86,6 +88,7 @@ function ChatProvider(props) {
86
88
  return;
87
89
  const payload = {
88
90
  messages: newMessages,
91
+ uiTools: parseUITools(UITools),
89
92
  };
90
93
  if (chatbotConversationId) {
91
94
  payload["chatbotConversationId"] = chatbotConversationId;
@@ -162,9 +165,16 @@ function ChatProvider(props) {
162
165
  setIsChatLoading,
163
166
  setCanSendMessage,
164
167
  scrollToBottom,
165
- enabledTools,
168
+ enabledTools: getTools({
169
+ apiTools: enabledTools,
170
+ uiTools: parseUITools(UITools),
171
+ }),
166
172
  chatbotConversationId,
173
+ UITools: parseUITools(UITools),
167
174
  },
168
- }, children: error ? (_jsx("div", { children: "An error occured" })) : (_jsxs(_Fragment, { children: [props.children, _jsx(Conversation, { messages: messages, setMessages: setMessages, postSessionMessage: postSessionMessage, isChatLoading: isChatLoading, messagesRef: messagesRef, input: input, setInput: setInput, handleSendClick: handleSendClick, setChatbotConversationId: setChatbotConversationId, setIsChatLoading: setIsChatLoading, setCanSendMessage: setCanSendMessage, canSendMessage: canSendMessage, scrollToBottom: scrollToBottom, error: error, enabledTools: enabledTools, Components: Components, UITools: UITools, customStyles: props.customStyles, chatbotConversationId: chatbotConversationId, setCurrentConversationMemory: setCurrentConversationMemory })] })) }));
175
+ }, children: error ? (_jsx("div", { children: "An error occured" })) : (_jsxs(_Fragment, { children: [props.children, _jsx(Conversation, { messages: messages, setMessages: setMessages, postSessionMessage: postSessionMessage, isChatLoading: isChatLoading, messagesRef: messagesRef, input: input, setInput: setInput, handleSendClick: handleSendClick, setChatbotConversationId: setChatbotConversationId, setIsChatLoading: setIsChatLoading, setCanSendMessage: setCanSendMessage, canSendMessage: canSendMessage, scrollToBottom: scrollToBottom, error: error, enabledTools: getTools({
176
+ apiTools: enabledTools,
177
+ uiTools: parseUITools(UITools),
178
+ }), Components: Components, UITools: parseUITools(UITools), customStyles: props.customStyles, chatbotConversationId: chatbotConversationId, setCurrentConversationMemory: setCurrentConversationMemory })] })) }));
169
179
  }
170
180
  export default ChatProvider;
@@ -1,12 +1,11 @@
1
- import React from "react";
2
- import { CMNDChatMemory, CustomStyles, UIFunctionArguments } from "../type.js";
1
+ import { CMNDChatbotUITool, CMNDChatMemory, CustomStyles } from "../type.js";
3
2
  import { ConversationProps } from "../components/Conversation.js";
4
- export interface CmndChatBotProps extends Pick<ConversationProps, 'Components'> {
3
+ export interface CmndChatBotProps extends Pick<ConversationProps, "Components"> {
5
4
  chatbotId: number;
6
5
  organizationId: number;
7
6
  apiKey?: string;
8
7
  baseUrl: string;
9
- UITools?: Record<string, React.FC<UIFunctionArguments<any>>>;
8
+ UITools?: CMNDChatbotUITool[];
10
9
  customStyles?: CustomStyles;
11
10
  initialMemory?: CMNDChatMemory;
12
11
  }
@@ -1,5 +1,5 @@
1
- import React, { Dispatch, ReactNode, SetStateAction } from "react";
2
- import { MessageRole, ToolDetails, ToolData, UIFunctionArguments, CustomStyles } from "../type.js";
1
+ import { Dispatch, ReactNode, SetStateAction } from "react";
2
+ import { MessageRole, ToolDetails, ToolData, CustomStyles, CMNDChatbotUITool } from "../type.js";
3
3
  interface ChatBubbleProps {
4
4
  message: string | ReactNode | null;
5
5
  role: MessageRole;
@@ -15,7 +15,7 @@ interface ChatBubbleProps {
15
15
  setCanSendMessage: Dispatch<SetStateAction<boolean>>;
16
16
  setIsChatLoading: Dispatch<SetStateAction<boolean>>;
17
17
  scrollToBottom: () => void;
18
- UITools?: Record<string, React.FC<UIFunctionArguments<any>>>;
18
+ UITools?: CMNDChatbotUITool[];
19
19
  customStyles?: CustomStyles;
20
20
  }
21
21
  declare const Chatbubble: ({ message, role, toolCallDetails, tools, postSessionMessage, setMessages, messages, hide, setChatbotConversationId, setCanSendMessage, setIsChatLoading, scrollToBottom, isLoadingBubble, UITools, customStyles, }: ChatBubbleProps) => JSX.Element | null;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, } from "react";
2
+ import { useEffect, useRef, } from "react";
3
3
  import { MessageRole, FunctionType, } from "../type.js";
4
4
  import { BsTools, BsRobot } from "react-icons/bs";
5
5
  import { FaUserCircle } from "react-icons/fa";
@@ -11,6 +11,57 @@ import SyntaxHighlighter from "react-syntax-highlighter";
11
11
  //@ts-ignore
12
12
  import oneDark from "react-syntax-highlighter/dist/cjs/styles/prism/one-dark.js";
13
13
  import { overrideStyle } from "../utils.js";
14
+ const patchLastMessageToolInfo = (messages, toolInfo) => {
15
+ console.log("patchLastMessageToolInfo", messages, toolInfo);
16
+ const lastMessage = messages[messages.length - 1];
17
+ if (lastMessage.role !== MessageRole.FUNCTION) {
18
+ return messages;
19
+ }
20
+ const shallowCopyOfMessagesWithoutLast = messages.slice(0, -1);
21
+ const copyOfLastMessage = { ...lastMessage };
22
+ copyOfLastMessage.tool = {
23
+ ...copyOfLastMessage.tool,
24
+ ...toolInfo,
25
+ };
26
+ const newMessages = [...shallowCopyOfMessagesWithoutLast, copyOfLastMessage];
27
+ return newMessages;
28
+ };
29
+ const confirmToolRun = async ({ messages, setMessages, postSessionMessage, setIsChatLoading, setCanSendMessage, setChatbotConversationId, scrollToBottom, }) => {
30
+ const newMessages = patchLastMessageToolInfo(messages, {
31
+ confirmed: true,
32
+ });
33
+ setIsChatLoading(true);
34
+ setCanSendMessage(false);
35
+ setMessages(newMessages);
36
+ await postSessionMessage(newMessages, (data) => {
37
+ if (data.finalResponseWithUsageData) {
38
+ setCanSendMessage(true);
39
+ setIsChatLoading(false);
40
+ const { messages, chatbotConversationId } = data;
41
+ if (chatbotConversationId) {
42
+ setChatbotConversationId(chatbotConversationId);
43
+ // saveConversationIdToLocalStorage(
44
+ // chatbotConversationId.toString(),
45
+ // config.chatbot_id!,
46
+ // config.organization_id!
47
+ // );
48
+ }
49
+ messages && setMessages(messages);
50
+ }
51
+ if (data.message) {
52
+ setIsChatLoading(false);
53
+ const newAssistantMessage = {
54
+ unuseful: false,
55
+ hiddenFromUser: false,
56
+ role: MessageRole.ASSISTANT,
57
+ message: "",
58
+ };
59
+ newAssistantMessage.message = data.message;
60
+ setMessages([...newMessages, newAssistantMessage]);
61
+ scrollToBottom();
62
+ }
63
+ });
64
+ };
14
65
  const getChatAvatar = (role) => {
15
66
  switch (role) {
16
67
  case MessageRole.USER:
@@ -24,78 +75,31 @@ const getChatAvatar = (role) => {
24
75
  }
25
76
  };
26
77
  const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage, setMessages, messages, hide, setChatbotConversationId, setCanSendMessage, setIsChatLoading, scrollToBottom, isLoadingBubble, UITools, customStyles, }) => {
27
- const patchLastMessageToolInfo = useCallback((messages, toolInfo) => {
28
- const lastMessage = messages[messages.length - 1];
29
- if (lastMessage.role !== MessageRole.FUNCTION) {
30
- return messages;
31
- }
32
- const shallowCopyOfMessagesWithoutLast = messages.slice(0, -1);
33
- const copyOfLastMessage = { ...lastMessage };
34
- copyOfLastMessage.tool = {
35
- ...copyOfLastMessage.tool,
36
- ...toolInfo,
37
- };
38
- const newMessages = [
39
- ...shallowCopyOfMessagesWithoutLast,
40
- copyOfLastMessage,
41
- ];
42
- return newMessages;
43
- }, []);
44
- const confirmToolRun = useCallback(async ({ messages, setMessages, postSessionMessage, }) => {
45
- const newMessages = patchLastMessageToolInfo(messages, {
46
- confirmed: true,
47
- });
48
- setIsChatLoading(true);
49
- setCanSendMessage(false);
50
- setMessages(newMessages);
51
- await postSessionMessage(newMessages, (data) => {
52
- if (data.finalResponseWithUsageData) {
53
- setCanSendMessage(true);
54
- setIsChatLoading(false);
55
- const { messages, chatbotConversationId } = data;
56
- if (chatbotConversationId) {
57
- setChatbotConversationId(chatbotConversationId);
58
- // saveConversationIdToLocalStorage(
59
- // chatbotConversationId.toString(),
60
- // config.chatbot_id!,
61
- // config.organization_id!
62
- // );
63
- }
64
- messages && setMessages(messages);
65
- }
66
- if (data.message) {
67
- setIsChatLoading(false);
68
- const newAssistantMessage = {
69
- unuseful: false,
70
- hiddenFromUser: false,
71
- role: MessageRole.ASSISTANT,
72
- message: "",
73
- };
74
- newAssistantMessage.message = data.message;
75
- setMessages([...newMessages, newAssistantMessage]);
76
- scrollToBottom();
77
- }
78
- });
79
- }, [patchLastMessageToolInfo]);
80
- const toolData = tools?.find((t) => t.title === toolCallDetails?.name);
78
+ const toolData = tools?.find((t) => t.name === toolCallDetails?.name);
79
+ const hasConfirmedToolRun = useRef(false);
80
+ const isPostingMessage = useRef(false);
81
81
  useEffect(() => {
82
- if (role === MessageRole.FUNCTION &&
82
+ if (!hasConfirmedToolRun.current &&
83
+ role === MessageRole.FUNCTION &&
83
84
  tools &&
84
- toolData?.metadata.functionType === FunctionType.BACKEND &&
85
+ toolData?.functionType === FunctionType.BACKEND &&
85
86
  Boolean(toolCallDetails?.runAt) === false) {
87
+ hasConfirmedToolRun.current = true;
86
88
  confirmToolRun({
87
89
  messages,
88
90
  setMessages,
89
91
  postSessionMessage,
92
+ setIsChatLoading,
93
+ setCanSendMessage,
94
+ setChatbotConversationId,
95
+ scrollToBottom,
90
96
  });
91
97
  }
92
98
  }, []);
93
99
  if (hide)
94
100
  return null;
95
101
  const defaultStyle = {
96
- display: toolData?.metadata.functionType === FunctionType.BACKEND
97
- ? "none"
98
- : "flex",
102
+ display: toolData?.functionType === FunctionType.BACKEND ? "none" : "flex",
99
103
  flexDirection: role === MessageRole.USER ? "row-reverse" : "row",
100
104
  gap: "10px",
101
105
  textAlign: role === MessageRole.USER ? "right" : "left",
@@ -125,11 +129,14 @@ const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage,
125
129
  const language = match ? match[1] : undefined;
126
130
  return !inline && match ? (_jsx(SyntaxHighlighter, { children: code, language: language, wrapLongLines: true, style: oneDark, ...props })) : (_jsx("code", { className: className, ...props, children: children }));
127
131
  },
128
- } })), role === MessageRole.FUNCTION && (_jsx("div", { children: toolData?.metadata?.functionType === FunctionType.UI ? (_jsx(ErrorBoundary, { fallback: _jsxs("div", { children: ["Failed to render ", toolCallDetails?.name, ". Please report this error."] }), onError: (error) => {
132
+ } })), role === MessageRole.FUNCTION && (_jsx("div", { children: toolData?.functionType === FunctionType.UI ? (_jsx(ErrorBoundary, { fallback: _jsxs("div", { children: ["Failed to render ", toolCallDetails?.name, ". Please report this error."] }), onError: (error) => {
129
133
  // todo: report this error to some central place
130
134
  // with toolCallDetails name, args, and conversation id
131
135
  console.log("captured error", error);
132
136
  }, children: _jsx(UIToolComponent, { functionName: toolCallDetails?.name ?? "", functionArgs: toolCallDetails?.args, previousRunResults: toolCallDetails?.output, UITools: UITools, captureResults: async (msg) => {
137
+ if (isPostingMessage.current)
138
+ return; // Prevent multiple calls
139
+ isPostingMessage.current = true;
133
140
  setIsChatLoading(true);
134
141
  setCanSendMessage(false);
135
142
  const newMessages = patchLastMessageToolInfo(messages, {
@@ -139,10 +146,11 @@ const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage,
139
146
  });
140
147
  setMessages(newMessages);
141
148
  const onData = (data) => {
149
+ isPostingMessage.current = false;
142
150
  if (data.finalResponseWithUsageData) {
143
151
  setIsChatLoading(false);
144
152
  setCanSendMessage(true);
145
- const { messages, chatbotConversationId } = data;
153
+ const { chatbotConversationId, messages } = data;
146
154
  if (chatbotConversationId) {
147
155
  setChatbotConversationId(chatbotConversationId);
148
156
  }
@@ -154,14 +162,18 @@ const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage,
154
162
  unuseful: false,
155
163
  hiddenFromUser: false,
156
164
  role: MessageRole.ASSISTANT,
157
- message: "",
165
+ message: data.message,
158
166
  };
159
- newAssistantMessage.message = data.message;
160
167
  setMessages([...newMessages, newAssistantMessage]);
161
168
  scrollToBottom();
162
169
  }
163
170
  };
164
- await postSessionMessage(newMessages, onData);
171
+ try {
172
+ await postSessionMessage(newMessages, onData);
173
+ }
174
+ finally {
175
+ isPostingMessage.current = false;
176
+ }
165
177
  } }) })) : (_jsx(_Fragment, {})) }, "2"))] })] }));
166
178
  };
167
179
  export default Chatbubble;
@@ -1,5 +1,5 @@
1
1
  import React, { Dispatch, SetStateAction } from "react";
2
- import { CMNDChatMemory, CustomStyles, InputFieldProps, SendButtonProps, UIFunctionArguments } from "../type.js";
2
+ import { CMNDChatbotUITool, CMNDChatMemory, CustomStyles, InputFieldProps, SendButtonProps } from "../type.js";
3
3
  export interface ConversationProps {
4
4
  messages: any[];
5
5
  setMessages: Dispatch<SetStateAction<any[]>>;
@@ -22,7 +22,7 @@ export interface ConversationProps {
22
22
  SendButton?: (params: SendButtonProps) => JSX.Element;
23
23
  error?: any;
24
24
  };
25
- UITools?: Record<string, React.FC<UIFunctionArguments<any>>>;
25
+ UITools?: CMNDChatbotUITool[];
26
26
  customStyles?: CustomStyles;
27
27
  setCurrentConversationMemory: (memory: CMNDChatMemory) => Promise<any>;
28
28
  }
@@ -5,7 +5,7 @@ import ChatInputBox from "./ChatInputBox.js";
5
5
  const Conversation = ({ messages, handleSendClick, isChatLoading, error, messagesRef, enabledTools, postSessionMessage, setMessages, setChatbotConversationId, setIsChatLoading, setCanSendMessage, scrollToBottom, canSendMessage, setInput, input, Components, UITools, customStyles, }) => {
6
6
  return (_jsxs("div", { className: "cmnd-conversations", children: [_jsxs("div", { ref: messagesRef, id: "messages", className: "cmnd-conversations-messages", children: [error, messages.map((m, i) => (_jsx(Chatbubble, { customStyles: customStyles, hide: m.hiddenFromUser, message: m.message, role: m.role, toolCallDetails: m.tool, tools: enabledTools, postSessionMessage: postSessionMessage, messages: messages, setMessages: setMessages, id: m.id, setChatbotConversationId: setChatbotConversationId, setIsChatLoading: setIsChatLoading, setCanSendMessage: setCanSendMessage, scrollToBottom: scrollToBottom, UITools: UITools }, i))), isChatLoading ? _jsx(LoadingBubble, { customStyles: customStyles }) : null] }), _jsxs("div", { id: "cmnd-input-div", className: "cmnd-input-div", children: [Components?.InputField ? (_jsx(Components.InputField, { ...{ input, setInput, canSendMessage, handleSendClick } })) : (_jsx(ChatInputBox, { input: input, setInput: setInput, handleSendClick: handleSendClick })), Components?.SendButton ? (_jsx(Components.SendButton, { ...{
7
7
  canSendMessage,
8
- handleSendClick
8
+ handleSendClick,
9
9
  } })) : (_jsx("button", { className: "cmnd-send-button", onClick: handleSendClick, children: "Send" }))] })] }));
10
10
  };
11
11
  export default Conversation;
package/dist/type.d.ts CHANGED
@@ -56,7 +56,6 @@ export declare enum FunctionType {
56
56
  BACKEND = "backend"
57
57
  }
58
58
  export type ToolData = {
59
- [x: string]: any;
60
59
  name: string;
61
60
  description: string;
62
61
  category: string;
@@ -71,6 +70,25 @@ export type UIFunctionArguments<AIProvidedParams> = {
71
70
  previousRunResults?: string;
72
71
  captureResults: (result: string) => void;
73
72
  };
73
+ export interface CMNDChatbotUITool {
74
+ name: string;
75
+ description: string;
76
+ category: string;
77
+ subcategory?: string;
78
+ dangerous?: boolean;
79
+ associatedCommands?: string[];
80
+ prerequisites?: string[];
81
+ argumentSchema: any;
82
+ rerun?: boolean;
83
+ rerunWithDifferentParameters?: boolean;
84
+ capturesUserInput?: boolean;
85
+ functionType?: "ui";
86
+ runCmd: (args: {
87
+ functionArgs?: Record<string, any>;
88
+ captureResults?: (result: any) => Promise<any>;
89
+ previousRunResults?: any;
90
+ }, ref?: React.RefObject<HTMLElement>) => void;
91
+ }
74
92
  export declare enum MessageRole {
75
93
  FUNCTION = "function",
76
94
  USER = "user",
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
- import { UIFunctionArguments } from "../type.js";
3
- declare const UIToolComponent: ({ functionName, functionArgs, previousRunResults, captureResults, UITools, }: {
2
+ import { CMNDChatbotUITool } from "../type.js";
3
+ interface UIToolComponentProps {
4
4
  functionName: string;
5
5
  functionArgs: any;
6
- previousRunResults?: string | undefined;
7
- captureResults: (result: string) => void;
8
- UITools?: Record<string, React.FC<UIFunctionArguments<any>>> | undefined;
9
- }) => JSX.Element;
6
+ previousRunResults?: string;
7
+ captureResults?: ((result: any) => Promise<any>) | undefined;
8
+ UITools?: CMNDChatbotUITool[];
9
+ }
10
+ declare const UIToolComponent: React.FC<UIToolComponentProps>;
10
11
  export default UIToolComponent;
@@ -1,9 +1,30 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- const UIToolComponent = ({ functionName, functionArgs, previousRunResults, captureResults, UITools = {}, }) => {
3
- const Component = UITools[functionName];
4
- if (!Component) {
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useEffect, useRef, useState } from "react";
3
+ const UIToolComponent = ({ functionName, functionArgs, previousRunResults, captureResults, UITools = [], }) => {
4
+ const itemRef = useRef(null);
5
+ const hasRun = useRef(false); // Prevent multiple calls
6
+ const [component, setComponent] = useState(null);
7
+ const tool = UITools.find((tool) => tool.name === functionName);
8
+ useEffect(() => {
9
+ if (tool && !hasRun.current) {
10
+ hasRun.current = true; // Mark as executed
11
+ const result = tool.runCmd({
12
+ functionArgs,
13
+ captureResults,
14
+ previousRunResults,
15
+ }, itemRef);
16
+ // If runCmd returns a React element, store it in state
17
+ if (React.isValidElement(result)) {
18
+ setComponent(result);
19
+ }
20
+ }
21
+ }, [tool, functionArgs, previousRunResults, captureResults]);
22
+ if (!tool) {
5
23
  throw new Error(`Tried to render non-existing UI-Tool component for ${functionName}.`);
6
24
  }
7
- return (_jsx(Component, { functionArgs: functionArgs, previousRunResults: previousRunResults, captureResults: captureResults }));
25
+ if (component) {
26
+ return _jsx(_Fragment, { children: component });
27
+ }
28
+ return (_jsx(_Fragment, { children: _jsx("div", { ref: itemRef }) }));
8
29
  };
9
30
  export default UIToolComponent;
@@ -0,0 +1,5 @@
1
+ declare const getTools: ({ uiTools, apiTools, }: {
2
+ uiTools?: any[] | undefined;
3
+ apiTools?: any[] | undefined;
4
+ }) => any[];
5
+ export default getTools;
@@ -0,0 +1,5 @@
1
+ const getTools = ({ uiTools = [], apiTools = [], }) => {
2
+ apiTools = apiTools.map((tool) => ({ ...tool.metadata }));
3
+ return [...apiTools, ...uiTools];
4
+ };
5
+ export default getTools;
@@ -0,0 +1,3 @@
1
+ import { CMNDChatbotUITool } from "../type.js";
2
+ declare const parseUITools: (uiTools?: CMNDChatbotUITool[]) => CMNDChatbotUITool[];
3
+ export default parseUITools;
@@ -0,0 +1,18 @@
1
+ const parseUITools = (uiTools) => {
2
+ if (!uiTools)
3
+ return [];
4
+ return uiTools.map((tool) => {
5
+ return {
6
+ ...tool,
7
+ rerun: tool.rerun ?? false,
8
+ capturesUserInput: tool.capturesUserInput ?? false,
9
+ rerunWithDifferentParameters: tool.rerunWithDifferentParameters ?? false,
10
+ prerequisites: tool.prerequisites ?? [],
11
+ associatedCommands: tool.associatedCommands ?? [],
12
+ dangerous: tool.dangerous ?? false,
13
+ subcategory: tool.subcategory ?? "",
14
+ functionType: "ui",
15
+ };
16
+ });
17
+ };
18
+ export default parseUITools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmnd-ai/chatbot-react",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "main": "dist/index.js",
5
5
  "description": "",
6
6
  "type": "module",