@cmnd-ai/chatbot-react 1.8.0 → 1.9.1

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
@@ -36,6 +36,7 @@ const App = () => {
36
36
  <CmndChatBot
37
37
  chatbotId={123}
38
38
  organizationId={456}
39
+ chatHistoryStorageKey="your-unique-storage-key"
39
40
  apiKey="your-api-key"
40
41
  baseUrl="https://api.example.com"
41
42
  />
@@ -56,6 +57,7 @@ const App = () => {
56
57
  <CmndChatBot
57
58
  chatbotId={123}
58
59
  organizationId={456}
60
+ chatHistoryStorageKey="your-unique-storage-key"
59
61
  apiKey="your-api-key"
60
62
  baseUrl="https://api.example.com"
61
63
  theme="dark"
@@ -128,14 +130,15 @@ const App = () => {
128
130
 
129
131
  ### Optional Props
130
132
 
131
- | Prop | Type | Default | Description |
132
- | --------------- | --------------------- | ----------- | --------------------------- |
133
- | `theme` | `"light" \| "dark"` | `"light"` | Theme for the chatbot |
134
- | `UITools` | `CMNDChatbotUITool[]` | `[]` | Array of UI tools |
135
- | `enabledTools` | `any[]` | `[]` | Array of enabled tools |
136
- | `initialMemory` | `CMNDChatMemory` | `undefined` | Initial conversation memory |
137
- | `customStyles` | `CustomStyles` | `undefined` | Custom CSS styles |
138
- | `Components` | `Components` | `undefined` | Custom component overrides |
133
+ | Prop | Type | Default | Description |
134
+ | ----------------------- | --------------------- | ----------- | ----------------------------------------------------- |
135
+ | `theme` | `"light" \| "dark"` | `"light"` | Theme for the chatbot |
136
+ | `UITools` | `CMNDChatbotUITool[]` | `[]` | Array of UI tools |
137
+ | `enabledTools` | `any[]` | `[]` | Array of enabled tools |
138
+ | `initialMemory` | `CMNDChatMemory` | `undefined` | Initial conversation memory |
139
+ | `customStyles` | `CustomStyles` | `undefined` | Custom CSS styles |
140
+ | `Components` | `Components` | `undefined` | Custom component overrides |
141
+ | `chatHistoryStorageKey` | `string` | `undefined` | The chat history key defined by the client |
139
142
 
140
143
  ## Custom Components
141
144
 
@@ -1,15 +1,12 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useEffect, useState } from "react";
3
- import { MessageRole, } from "../type.js";
2
+ import React, { useEffect, useRef, useState } from "react";
4
3
  import postUserConversation from "../services/postUserConversation.js";
5
4
  import getChatBotById from "../services/getchatBotById.js";
6
5
  import patchChatbotConversationMemory from "../services/patchChatbotConversationMemory/index.js";
7
6
  import deleteChatbotConversationMemory from "../services/deleteChatbotConversationMemory/index.js";
8
7
  import parseUITools from "../utils/parseUITools.js";
9
8
  import getTools from "../utils/getTools/index.js";
10
- import saveConversationIdToLocalStorage from "../utils/saveConversationIdToLocalStorage/index.js";
11
- import { ConversationsPanel } from "../components/ConversationsPanel/index.js";
12
- import useMessagesScroll from "../hooks/use-messages-scroll.js";
9
+ import { ConversationsPanel, } from "../components/ConversationsPanel/index.js";
13
10
  let globalChatbotConversationId;
14
11
  let globalChatbotProps;
15
12
  export const ChatProviderContext = React.createContext(undefined);
@@ -57,9 +54,14 @@ function ChatProvider(props) {
57
54
  const [canContinue] = useState(false);
58
55
  const [isChatLoading, setIsChatLoading] = useState(false);
59
56
  const [canSendMessage, setCanSendMessage] = useState(true);
60
- const { messagesRef, resetMessagesScroll, isMessagesScrolledToBottom } = useMessagesScroll(messages);
61
57
  const [chatbotConversationId, setChatbotConversationId] = useState(undefined);
62
58
  const [enabledTools, setEnabledTools] = useState([]);
59
+ const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
60
+ const [selectedConversation, setSelectedConversation] = useState(null);
61
+ const inputRef = useRef(null);
62
+ const [shouldFocusInput, setShouldFocusInput] = useState(false);
63
+ const [pendingSendText, setPendingSendText] = useState(null);
64
+ const conversationsPanelRef = useRef(null);
63
65
  useEffect(() => {
64
66
  globalChatbotConversationId = chatbotConversationId;
65
67
  }, [chatbotConversationId]);
@@ -98,54 +100,48 @@ function ChatProvider(props) {
98
100
  onData,
99
101
  });
100
102
  };
101
- const handleSendClick = async () => {
102
- if (input.trim() === "" || !canSendMessage)
103
- return;
104
- const newUserMessage = {
105
- unuseful: false,
106
- hiddenFromUser: false,
107
- role: "user",
108
- message: canContinue ? "continue" : input,
109
- };
110
- const newMessages = [...messages, newUserMessage];
111
- setMessages(newMessages);
112
- setInput("");
113
- setIsChatLoading(true);
114
- try {
115
- const onData = async (data) => {
116
- if (data.finalResponseWithUsageData) {
117
- setIsChatLoading(false);
118
- setCanSendMessage(true);
119
- const { chatbotConversationId, messages } = data;
120
- if (chatbotConversationId) {
121
- setChatbotConversationId(chatbotConversationId);
122
- await saveConversationIdToLocalStorage({
123
- chatbotConversationId,
124
- chatbotId,
125
- organizationId,
126
- });
127
- }
128
- messages && setMessages(messages);
129
- }
130
- if (data.message) {
131
- setIsChatLoading(false);
132
- const newAssistantMessage = {
133
- unuseful: false,
134
- hiddenFromUser: false,
135
- role: MessageRole.ASSISTANT,
136
- message: data.message,
137
- };
138
- setMessages([...newMessages, newAssistantMessage]);
139
- }
140
- };
141
- setCanSendMessage(false);
142
- await postSessionMessage(newMessages, onData);
103
+ const toggleSidebar = () => {
104
+ setIsSidebarCollapsed((prev) => !prev);
105
+ };
106
+ const openSidePanel = () => {
107
+ setIsSidebarCollapsed(false);
108
+ };
109
+ const closeSidePanel = () => {
110
+ setIsSidebarCollapsed(true);
111
+ };
112
+ const createNewConversation = () => {
113
+ conversationsPanelRef.current?.handleNewChat();
114
+ };
115
+ const setMessageText = (text) => {
116
+ setInput(text);
117
+ setShouldFocusInput(true);
118
+ };
119
+ useEffect(() => {
120
+ if (shouldFocusInput) {
121
+ inputRef.current?.focus();
122
+ if (inputRef.current) {
123
+ const len = inputRef.current.value.length;
124
+ inputRef.current.setSelectionRange(len, len);
125
+ }
126
+ setShouldFocusInput(false);
127
+ }
128
+ }, [input, shouldFocusInput]);
129
+ const sendMessage = (text) => {
130
+ if (typeof text === "string") {
131
+ setInput(text);
132
+ setShouldFocusInput(true);
133
+ setPendingSendText(text);
143
134
  }
144
- catch (error) {
145
- console.error(error);
146
- setError("Error sending message");
135
+ else {
136
+ conversationsPanelRef.current?.handleSendClick();
147
137
  }
148
138
  };
139
+ useEffect(() => {
140
+ if (pendingSendText !== null && input === pendingSendText) {
141
+ conversationsPanelRef.current?.handleSendClick();
142
+ setPendingSendText(null);
143
+ }
144
+ }, [input, pendingSendText]);
149
145
  if (loading)
150
146
  return null;
151
147
  return (_jsx(ChatProviderContext.Provider, { value: {
@@ -169,9 +165,15 @@ function ChatProvider(props) {
169
165
  chatbotConversationId,
170
166
  UITools: parseUITools(UITools),
171
167
  },
172
- }, children: error ? (_jsx("div", { children: "An error occured" })) : (_jsxs(_Fragment, { children: [props.children, _jsx(ConversationsPanel, { organizationId: organizationId, chatbotId: chatbotId, baseUrl: baseUrl, theme: props.theme, enabledTools: getTools({
168
+ createNewConversation,
169
+ toggleSidebar,
170
+ openSidePanel,
171
+ closeSidePanel,
172
+ setMessageText,
173
+ sendMessage,
174
+ }, 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({
173
175
  apiTools: enabledTools,
174
176
  uiTools: parseUITools(UITools),
175
- }), postSessionMessage: postSessionMessage, setChatbotConversationId: setChatbotConversationId, chatbotConversationId: chatbotConversationId, Components: Components, UITools: parseUITools(UITools), customStyles: props.customStyles })] })) }));
177
+ }), postSessionMessage: postSessionMessage, setChatbotConversationId: setChatbotConversationId, chatbotConversationId: chatbotConversationId, Components: Components, UITools: parseUITools(UITools), customStyles: props.customStyles, isSidebarCollapsed: isSidebarCollapsed, setIsSidebarCollapsed: setIsSidebarCollapsed, selectedConversation: selectedConversation, setSelectedConversation: setSelectedConversation, messages: messages, setMessages: setMessages, input: input, setInput: setInput, inputRef: inputRef })] })) }));
176
178
  }
177
179
  export default ChatProvider;
@@ -1,2 +1,2 @@
1
- declare const useChatContext: () => import("../type.js").CmndChatContext;
2
- export default useChatContext;
1
+ declare const useCMNDChatContext: () => import("../type.js").CmndChatContext;
2
+ export { useCMNDChatContext };
@@ -1,9 +1,9 @@
1
1
  import { useContext } from "react";
2
2
  import { ChatProviderContext } from "./index.js";
3
- const useChatContext = () => {
3
+ const useCMNDChatContext = () => {
4
4
  const context = useContext(ChatProviderContext);
5
5
  if (!context)
6
6
  throw new Error("Cmnd chat context must be wrapped in a provider");
7
7
  return context;
8
8
  };
9
- export default useChatContext;
9
+ export { useCMNDChatContext };
@@ -3,6 +3,7 @@ import { CMNDChatbotUITool, CMNDChatMemory, CustomStyles, InputFieldProps, SendB
3
3
  export interface CmndChatBotProps {
4
4
  chatbotId: number;
5
5
  organizationId: number;
6
+ chatHistoryStorageKey?: string;
6
7
  apiKey?: string;
7
8
  baseUrl: string;
8
9
  theme?: "light" | "dark";
@@ -20,5 +21,5 @@ export interface CmndChatBotProps {
20
21
  error?: any;
21
22
  };
22
23
  }
23
- declare function CmndChatBot({ chatbotId, organizationId, apiKey, baseUrl, theme, UITools, customStyles, enabledTools, Components, initialMemory, }: CmndChatBotProps): JSX.Element;
24
+ declare function CmndChatBot({ chatbotId, organizationId, apiKey, baseUrl, theme, UITools, customStyles, chatHistoryStorageKey, enabledTools, Components, initialMemory, }: CmndChatBotProps): JSX.Element;
24
25
  export default CmndChatBot;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import ChatProvider from "../ChatProvider/index.js";
3
- function CmndChatBot({ chatbotId, organizationId, apiKey, baseUrl, theme, UITools, customStyles, enabledTools = [], Components, initialMemory, }) {
4
- return (_jsx(ChatProvider, { chatbotId: chatbotId, organizationId: organizationId, apiKey: apiKey, baseUrl: baseUrl, theme: theme, UITools: UITools, customStyles: customStyles, enabledTools: enabledTools, Components: Components, initialMemory: initialMemory }));
3
+ function CmndChatBot({ chatbotId, organizationId, apiKey, baseUrl, theme, UITools, customStyles, chatHistoryStorageKey, enabledTools = [], Components, initialMemory, }) {
4
+ return (_jsx(ChatProvider, { chatbotId: chatbotId, organizationId: organizationId, chatHistoryStorageKey: chatHistoryStorageKey, apiKey: apiKey, baseUrl: baseUrl, theme: theme, UITools: UITools, customStyles: customStyles, enabledTools: enabledTools, Components: Components, initialMemory: initialMemory }));
5
5
  }
6
6
  export default CmndChatBot;
@@ -3,6 +3,7 @@ interface ChatInputBoxProps {
3
3
  input: string;
4
4
  setInput: (input: string) => void;
5
5
  handleSendClick: () => void;
6
+ inputRef?: React.RefObject<HTMLInputElement>;
6
7
  }
7
- declare const ChatInputBox: ({ input, setInput, handleSendClick }: ChatInputBoxProps) => JSX.Element;
8
+ declare const ChatInputBox: ({ input, setInput, handleSendClick, inputRef, }: ChatInputBoxProps) => JSX.Element;
8
9
  export default ChatInputBox;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- const ChatInputBox = ({ input, setInput, handleSendClick }) => {
3
- return (_jsx("div", { className: "cmnd-input-wrapper", children: _jsx("input", { autoFocus: true, id: "cmnd-input", className: "cmnd-input", onKeyDown: e => {
2
+ const ChatInputBox = ({ input, setInput, handleSendClick, inputRef, }) => {
3
+ return (_jsx("div", { className: "cmnd-input-wrapper", children: _jsx("input", { ref: inputRef, autoFocus: true, id: "cmnd-input", className: "cmnd-input", onKeyDown: (e) => {
4
4
  if (e.key === "Enter") {
5
5
  handleSendClick();
6
6
  }
7
- }, autoComplete: "off", value: input, onChange: e => setInput(e.target.value), type: "text", placeholder: "Type something..." }) }));
7
+ }, autoComplete: "off", value: input, onChange: (e) => setInput(e.target.value), type: "text", placeholder: "Type something..." }) }));
8
8
  };
9
9
  export default ChatInputBox;
@@ -18,6 +18,7 @@ interface ChatBubbleProps {
18
18
  customStyles?: CustomStyles;
19
19
  chatbotId: number;
20
20
  organizationId: number;
21
+ chatHistoryStorageKey?: string;
21
22
  theme?: "light" | "dark";
22
23
  Components?: {
23
24
  InputField?: (params: InputFieldProps) => JSX.Element;
@@ -28,5 +29,5 @@ interface ChatBubbleProps {
28
29
  error?: any;
29
30
  };
30
31
  }
31
- declare const Chatbubble: ({ message, role, toolCallDetails, tools, postSessionMessage, setMessages, messages, hide, setChatbotConversationId, setCanSendMessage, setIsChatLoading, isLoadingBubble, UITools, customStyles, chatbotId, organizationId, theme, Components, }: ChatBubbleProps) => JSX.Element | null;
32
+ declare const Chatbubble: ({ message, role, toolCallDetails, tools, postSessionMessage, setMessages, messages, hide, setChatbotConversationId, setCanSendMessage, setIsChatLoading, isLoadingBubble, UITools, customStyles, chatbotId, organizationId, chatHistoryStorageKey, theme, Components, }: ChatBubbleProps) => JSX.Element | null;
32
33
  export default Chatbubble;
@@ -27,7 +27,7 @@ const patchLastMessageToolInfo = (messages, toolInfo) => {
27
27
  const newMessages = [...shallowCopyOfMessagesWithoutLast, copyOfLastMessage];
28
28
  return newMessages;
29
29
  };
30
- const confirmToolRun = async ({ messages, setMessages, postSessionMessage, setIsChatLoading, setCanSendMessage, setChatbotConversationId, chatbotId, organizationId, }) => {
30
+ const confirmToolRun = async ({ messages, setMessages, postSessionMessage, setIsChatLoading, setCanSendMessage, setChatbotConversationId, chatbotId, organizationId, chatHistoryStorageKey }) => {
31
31
  const newMessages = patchLastMessageToolInfo(messages, {
32
32
  confirmed: true,
33
33
  });
@@ -45,6 +45,7 @@ const confirmToolRun = async ({ messages, setMessages, postSessionMessage, setIs
45
45
  chatbotConversationId,
46
46
  chatbotId,
47
47
  organizationId,
48
+ chatHistoryStorageKey
48
49
  });
49
50
  }
50
51
  messages && setMessages(messages);
@@ -75,7 +76,7 @@ const getChatAvatar = (role, theme = "light", Components) => {
75
76
  return _jsx(BsRobot, { color: iconColor });
76
77
  }
77
78
  };
78
- const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage, setMessages, messages, hide, setChatbotConversationId, setCanSendMessage, setIsChatLoading, isLoadingBubble, UITools, customStyles, chatbotId, organizationId, theme, Components, }) => {
79
+ const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage, setMessages, messages, hide, setChatbotConversationId, setCanSendMessage, setIsChatLoading, isLoadingBubble, UITools, customStyles, chatbotId, organizationId, chatHistoryStorageKey, theme, Components, }) => {
79
80
  const toolData = tools?.find((t) => t.name === toolCallDetails?.name);
80
81
  const hasConfirmedToolRun = useRef(false);
81
82
  const isPostingMessage = useRef(false);
@@ -95,6 +96,7 @@ const Chatbubble = ({ message, role, toolCallDetails, tools, postSessionMessage,
95
96
  setChatbotConversationId,
96
97
  chatbotId,
97
98
  organizationId,
99
+ chatHistoryStorageKey,
98
100
  });
99
101
  }
100
102
  }, []);
@@ -27,8 +27,10 @@ export interface ConversationProps {
27
27
  setCurrentConversationMemory: (memory: CMNDChatMemory) => Promise<any>;
28
28
  chatbotId: number;
29
29
  organizationId: number;
30
+ chatHistoryStorageKey?: string;
30
31
  isMessagesScrolledToBottom?: boolean;
31
32
  resetMessagesScroll?: () => void;
33
+ inputRef?: React.RefObject<HTMLInputElement>;
32
34
  }
33
- declare const Conversation: ({ messages, handleSendClick, isChatLoading, error, messagesRef, enabledTools, postSessionMessage, setMessages, setChatbotConversationId, setIsChatLoading, setCanSendMessage, canSendMessage, setInput, input, theme, Components, UITools, customStyles, chatbotId, organizationId, isMessagesScrolledToBottom, resetMessagesScroll, }: ConversationProps) => JSX.Element;
35
+ declare const Conversation: ({ messages, handleSendClick, isChatLoading, error, messagesRef, enabledTools, postSessionMessage, setMessages, setChatbotConversationId, setIsChatLoading, setCanSendMessage, canSendMessage, setInput, input, theme, Components, UITools, customStyles, chatbotId, organizationId, chatHistoryStorageKey, isMessagesScrolledToBottom, resetMessagesScroll, inputRef, }: ConversationProps) => JSX.Element;
34
36
  export default Conversation;
@@ -3,8 +3,8 @@ import Chatbubble from "./Chatbubble.js";
3
3
  import LoadingBubble from "./LoadingBubble.js";
4
4
  import ScrollToBottomButton from "./ScrollToBottomButton.js";
5
5
  import ChatInputBox from "./ChatInputBox.js";
6
- const Conversation = ({ messages, handleSendClick, isChatLoading, error, messagesRef, enabledTools, postSessionMessage, setMessages, setChatbotConversationId, setIsChatLoading, setCanSendMessage, canSendMessage, setInput, input, theme, Components, UITools, customStyles, chatbotId, organizationId, isMessagesScrolledToBottom = true, resetMessagesScroll, }) => {
7
- 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, { chatbotId: chatbotId, organizationId: organizationId, 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, UITools: UITools, theme: theme, Components: Components }, i))), isChatLoading ? (_jsx(LoadingBubble, { customStyles: customStyles, chatbotId: chatbotId, organizationId: organizationId, theme: theme, Components: Components })) : null] }), _jsx(ScrollToBottomButton, { onClick: resetMessagesScroll || (() => { }), isVisible: !isMessagesScrolledToBottom && messages.length > 0, theme: theme, customStyles: customStyles }), _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, { ...{
6
+ const Conversation = ({ messages, handleSendClick, isChatLoading, error, messagesRef, enabledTools, postSessionMessage, setMessages, setChatbotConversationId, setIsChatLoading, setCanSendMessage, canSendMessage, setInput, input, theme, Components, UITools, customStyles, chatbotId, organizationId, chatHistoryStorageKey, isMessagesScrolledToBottom = true, resetMessagesScroll, inputRef, }) => {
7
+ 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, { chatbotId: chatbotId, organizationId: organizationId, chatHistoryStorageKey: chatHistoryStorageKey, 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, UITools: UITools, theme: theme, Components: Components }, i))), isChatLoading ? (_jsx(LoadingBubble, { customStyles: customStyles, chatbotId: chatbotId, organizationId: organizationId, theme: theme, Components: Components })) : null] }), _jsx(ScrollToBottomButton, { onClick: resetMessagesScroll || (() => { }), isVisible: !isMessagesScrolledToBottom && messages.length > 0, theme: theme, customStyles: customStyles }), _jsxs("div", { id: "cmnd-input-div", className: "cmnd-input-div", children: [Components?.InputField ? (_jsx(Components.InputField, { ...{ input, setInput, canSendMessage, handleSendClick, inputRef } })) : (_jsx(ChatInputBox, { input: input, setInput: setInput, handleSendClick: handleSendClick, inputRef: inputRef })), Components?.SendButton ? (_jsx(Components.SendButton, { ...{
8
8
  canSendMessage,
9
9
  handleSendClick,
10
10
  } })) : (_jsx("button", { className: "cmnd-send-button", onClick: handleSendClick, children: "Send" }))] })] }));
@@ -1,9 +1,10 @@
1
1
  import React, { Dispatch, SetStateAction } from "react";
2
- import { InputFieldProps, SendButtonProps } from "../../type.js";
2
+ import { ChatbotConversation, InputFieldProps, SendButtonProps } from "../../type.js";
3
3
  interface ConversationsPanelProps {
4
4
  organizationId: number;
5
5
  chatbotId: number;
6
6
  baseUrl: string;
7
+ chatHistoryStorageKey?: string;
7
8
  theme?: "light" | "dark";
8
9
  enabledTools?: any[];
9
10
  setChatbotConversationId: Dispatch<SetStateAction<number | undefined>>;
@@ -28,6 +29,19 @@ interface ConversationsPanelProps {
28
29
  dateStyle?: React.CSSProperties;
29
30
  deleteButtonStyle?: React.CSSProperties;
30
31
  };
32
+ isSidebarCollapsed?: boolean;
33
+ setIsSidebarCollapsed?: React.Dispatch<React.SetStateAction<boolean>>;
34
+ selectedConversation: ChatbotConversation | null;
35
+ setSelectedConversation: React.Dispatch<React.SetStateAction<ChatbotConversation | null>>;
36
+ messages: any[];
37
+ setMessages: React.Dispatch<React.SetStateAction<any[]>>;
38
+ input: string;
39
+ setInput: React.Dispatch<React.SetStateAction<string>>;
40
+ inputRef: React.RefObject<HTMLInputElement>;
31
41
  }
32
- export declare const ConversationsPanel: React.FC<ConversationsPanelProps>;
42
+ export interface ConversationsPanelRef {
43
+ handleSendClick: () => void;
44
+ handleNewChat: () => void;
45
+ }
46
+ export declare const ConversationsPanel: React.ForwardRefExoticComponent<ConversationsPanelProps & React.RefAttributes<ConversationsPanelRef>>;
33
47
  export {};
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useState, useEffect, } from "react";
2
+ import React, { useState, useEffect, forwardRef, useImperativeHandle, } 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";
@@ -10,14 +10,17 @@ import getConversationLocalStorageKey from "../../utils/getConversationLocalStor
10
10
  import saveConversationIdToLocalStorage from "../../utils/saveConversationIdToLocalStorage/index.js";
11
11
  import getUTCDateTime from "../../utils/getUTCDateTime/index.js";
12
12
  import useMessagesScroll from "../../hooks/use-messages-scroll.js";
13
- export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme = "light", enabledTools = [], setChatbotConversationId, chatbotConversationId, postSessionMessage, Components, UITools, customStyles, }) => {
14
- const [selectedConversation, setSelectedConversation] = useState(null);
15
- const [messages, setMessages] = useState([]);
16
- const [input, setInput] = useState("");
13
+ export const ConversationsPanel = forwardRef(({ organizationId, chatbotId, baseUrl, chatHistoryStorageKey, theme = "light", enabledTools = [], setChatbotConversationId, chatbotConversationId, postSessionMessage, Components, UITools, customStyles, isSidebarCollapsed, setIsSidebarCollapsed, selectedConversation, setSelectedConversation, messages, setMessages, input, setInput, inputRef, }, ref) => {
17
14
  const [isChatLoading, setIsChatLoading] = useState(false);
18
15
  const [canSendMessage, setCanSendMessage] = useState(true);
19
16
  const [conversationIds, setConversationIds] = useState([]);
20
- const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
17
+ const isSidebarCollapsedProp = typeof isSidebarCollapsed === "boolean" ? isSidebarCollapsed : false;
18
+ const setIsSidebarCollapsedProp = setIsSidebarCollapsed;
19
+ const [internalSidebarCollapsed, setInternalSidebarCollapsed] = useState(false);
20
+ const isSidebarCollapsedFinal = typeof isSidebarCollapsed === "boolean"
21
+ ? isSidebarCollapsed
22
+ : internalSidebarCollapsed;
23
+ const setIsSidebarCollapsedFinal = setIsSidebarCollapsedProp || setInternalSidebarCollapsed;
21
24
  const { messagesRef, resetMessagesScroll, isMessagesScrolledToBottom } = useMessagesScroll(messages);
22
25
  const themeColors = {
23
26
  light: {
@@ -48,7 +51,7 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
48
51
  },
49
52
  };
50
53
  const colors = themeColors[theme];
51
- const localStorageKey = getConversationLocalStorageKey({
54
+ const localStorageKey = chatHistoryStorageKey || getConversationLocalStorageKey({
52
55
  organizationId,
53
56
  chatbotId,
54
57
  });
@@ -72,23 +75,27 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
72
75
  setConversationIds([]);
73
76
  }
74
77
  };
75
- const { data, loading, error, refetch } = useFetchData(() => {
78
+ const { data, loading, error, refetch } = useFetchData(async () => {
76
79
  // Only fetch if we have conversation IDs
77
80
  if (conversationIds.length === 0) {
78
- return Promise.resolve({
79
- data: { chatbotConversations: [] },
80
- status: 200,
81
- statusText: "OK",
82
- headers: {},
83
- config: {},
81
+ return { chatbotConversations: [] };
82
+ }
83
+ try {
84
+ return await getChatBotConversationsList({
85
+ organizationId,
86
+ chatbotId,
87
+ conversationIds,
88
+ baseUrl,
89
+ }).catch((error) => {
90
+ // Ignore any error from axios and return an empty list
91
+ console.error("Error fetching conversations (ignored):", error);
92
+ return { chatbotConversations: [] };
84
93
  });
85
94
  }
86
- return getChatBotConversationsList({
87
- organizationId,
88
- chatbotId,
89
- conversationIds,
90
- baseUrl,
91
- });
95
+ catch (error) {
96
+ console.error("Error fetching conversations (sync):", error);
97
+ return { chatbotConversations: [] };
98
+ }
92
99
  }, [organizationId, chatbotId, conversationIds, baseUrl]);
93
100
  const handleSendClick = () => {
94
101
  if (!input.trim() || !canSendMessage)
@@ -116,10 +123,12 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
116
123
  chatbotConversationId: newConversationId,
117
124
  chatbotId,
118
125
  organizationId,
126
+ chatHistoryStorageKey,
119
127
  });
120
128
  refetchConversationIds();
121
129
  if (selectedConversation &&
122
- selectedConversation.chatbotConversationTitle === "New Conversation") {
130
+ selectedConversation.chatbotConversationTitle ===
131
+ "New Conversation") {
123
132
  setSelectedConversation({
124
133
  ...selectedConversation,
125
134
  chatbotConversationId: newConversationId,
@@ -213,6 +222,10 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
213
222
  return dateB.getTime() - dateA.getTime();
214
223
  });
215
224
  }, [data?.chatbotConversations, selectedConversation]);
225
+ useImperativeHandle(ref, () => ({
226
+ handleSendClick,
227
+ handleNewChat,
228
+ }));
216
229
  return (_jsxs("div", { className: "cmnd-conversations-panel", style: {
217
230
  display: "flex",
218
231
  height: "100%",
@@ -220,7 +233,7 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
220
233
  color: colors.text,
221
234
  ...customStyles?.panelStyle,
222
235
  }, children: [_jsxs("div", { className: "cmnd-conversations-sidebar", style: {
223
- width: isSidebarCollapsed ? "50px" : "300px",
236
+ width: isSidebarCollapsedFinal ? "50px" : "300px",
224
237
  borderRight: `1px solid ${colors.border}`,
225
238
  display: "flex",
226
239
  flexDirection: "column",
@@ -228,15 +241,17 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
228
241
  transition: "width 0.3s ease",
229
242
  ...customStyles?.sidebarStyle,
230
243
  }, children: [_jsxs("div", { className: "cmnd-conversations-header", style: {
231
- padding: isSidebarCollapsed ? "12px 8px" : "16px",
244
+ padding: isSidebarCollapsedFinal ? "12px 8px" : "16px",
232
245
  borderBottom: `1px solid ${colors.border}`,
233
246
  display: "flex",
234
- justifyContent: isSidebarCollapsed ? "center" : "space-between",
247
+ justifyContent: isSidebarCollapsedFinal
248
+ ? "center"
249
+ : "space-between",
235
250
  alignItems: "center",
236
- flexDirection: isSidebarCollapsed ? "column" : "row",
237
- gap: isSidebarCollapsed ? "8px" : "0",
251
+ flexDirection: isSidebarCollapsedFinal ? "column" : "row",
252
+ gap: isSidebarCollapsedFinal ? "8px" : "0",
238
253
  ...customStyles?.headerStyle,
239
- }, children: [!isSidebarCollapsed && (_jsx("h3", { style: {
254
+ }, children: [!isSidebarCollapsedFinal && (_jsx("h3", { style: {
240
255
  margin: 0,
241
256
  fontSize: "16px",
242
257
  fontWeight: "600",
@@ -244,8 +259,10 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
244
259
  }, children: "Conversations" })), _jsxs("div", { style: {
245
260
  display: "flex",
246
261
  gap: "8px",
247
- flexDirection: isSidebarCollapsed ? "column" : "row",
248
- }, children: [_jsx("button", { className: "cmnd-toggle-sidebar-button", onClick: () => setIsSidebarCollapsed(!isSidebarCollapsed), title: isSidebarCollapsed ? "Expand Sidebar" : "Collapse Sidebar", style: {
262
+ flexDirection: isSidebarCollapsedFinal ? "column" : "row",
263
+ }, children: [_jsx("button", { className: "cmnd-toggle-sidebar-button", onClick: () => setIsSidebarCollapsedFinal((prev) => !prev), title: isSidebarCollapsedFinal
264
+ ? "Expand Sidebar"
265
+ : "Collapse Sidebar", style: {
249
266
  background: colors.button,
250
267
  border: "none",
251
268
  color: theme === "light" ? "#ffffff" : colors.text,
@@ -282,7 +299,7 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
282
299
  overflowY: "auto",
283
300
  padding: "12px",
284
301
  ...customStyles?.conversationListStyle,
285
- }, children: isSidebarCollapsed ? (_jsx("div", { style: {
302
+ }, children: isSidebarCollapsedFinal ? (_jsx("div", { style: {
286
303
  textAlign: "center",
287
304
  padding: "8px",
288
305
  color: colors.secondaryText,
@@ -291,19 +308,25 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
291
308
  textAlign: "center",
292
309
  padding: "20px",
293
310
  color: colors.secondaryText,
294
- }, children: "Loading conversations..." })) : error && allConversations.length === 0 ? (_jsx("div", { style: { textAlign: "center", padding: "20px", color: "#ff6b6b" }, children: "Error loading conversations" })) : allConversations.length === 0 ? (_jsx("div", { style: {
311
+ }, children: "Loading conversations..." })) : error && allConversations.length === 0 ? (_jsx("div", { style: {
312
+ textAlign: "center",
313
+ padding: "20px",
314
+ color: "#ff6b6b",
315
+ }, children: "Error loading conversations" })) : allConversations.length === 0 ? (_jsx("div", { style: {
295
316
  textAlign: "center",
296
317
  padding: "20px",
297
318
  color: colors.secondaryText,
298
319
  }, children: "No conversations yet" })) : (allConversations.map((conversation) => (_jsx(ConversationCard, { conversation: conversation, isActive: selectedConversation?.chatbotConversationId ===
299
320
  conversation.chatbotConversationId, onClick: () => {
300
- if (conversation.chatbotConversationTitle === "New Conversation") {
321
+ if (conversation.chatbotConversationTitle ===
322
+ "New Conversation") {
301
323
  }
302
324
  else {
303
325
  handleConversationSelect(conversation);
304
326
  }
305
327
  }, onDelete: () => {
306
- if (conversation.chatbotConversationTitle === "New Conversation") {
328
+ if (conversation.chatbotConversationTitle ===
329
+ "New Conversation") {
307
330
  setSelectedConversation(null);
308
331
  setMessages([]);
309
332
  }
@@ -329,5 +352,5 @@ export const ConversationsPanel = ({ organizationId, chatbotId, baseUrl, theme =
329
352
  fontSize: "16px",
330
353
  textAlign: "center",
331
354
  padding: "20px",
332
- }, children: _jsx("div", { children: _jsx("div", { children: "Select a conversation or start a new one" }) }) })) : (_jsx(Conversation, { messages: messages, setMessages: setMessages, handleSendClick: handleSendClick, input: input, setInput: setInput, isChatLoading: isChatLoading, error: error ? "Error loading conversations" : null, messagesRef: messagesRef, setChatbotConversationId: setChatbotConversationId, chatbotConversationId: chatbotConversationId, setIsChatLoading: setIsChatLoading, setCanSendMessage: setCanSendMessage, canSendMessage: canSendMessage, enabledTools: enabledTools, postSessionMessage: postSessionMessage, theme: theme, Components: Components, UITools: UITools, customStyles: customStyles, setCurrentConversationMemory: setCurrentConversationMemory, chatbotId: chatbotId, organizationId: organizationId, isMessagesScrolledToBottom: isMessagesScrolledToBottom, resetMessagesScroll: resetMessagesScroll })) })] }));
333
- };
355
+ }, children: _jsx("div", { children: _jsx("div", { children: "Select a conversation or start a new one" }) }) })) : (_jsx(Conversation, { messages: messages, setMessages: setMessages, handleSendClick: handleSendClick, input: input, setInput: setInput, isChatLoading: isChatLoading, error: error ? "Error loading conversations" : null, messagesRef: messagesRef, setChatbotConversationId: setChatbotConversationId, chatbotConversationId: chatbotConversationId, setIsChatLoading: setIsChatLoading, setCanSendMessage: setCanSendMessage, canSendMessage: canSendMessage, enabledTools: enabledTools, postSessionMessage: postSessionMessage, theme: theme, Components: Components, UITools: UITools, customStyles: customStyles, setCurrentConversationMemory: setCurrentConversationMemory, chatbotId: chatbotId, organizationId: organizationId, chatHistoryStorageKey: chatHistoryStorageKey, isMessagesScrolledToBottom: isMessagesScrolledToBottom, resetMessagesScroll: resetMessagesScroll, inputRef: inputRef })) })] }));
356
+ });
@@ -1,9 +1,8 @@
1
- import { AxiosPromise } from "axios";
2
1
  type UseFetchDataResult<T = any> = {
3
2
  loading: boolean;
4
3
  error: boolean;
5
4
  data: T;
6
5
  refetch: () => void;
7
6
  };
8
- declare const useFetchData: <T = any>(apiCallFn: () => AxiosPromise<T>, deps?: any[]) => UseFetchDataResult<T>;
7
+ declare const useFetchData: <T = any>(apiCallFn: () => Promise<T>, deps?: any[]) => UseFetchDataResult<T>;
9
8
  export default useFetchData;
@@ -10,7 +10,7 @@ const useFetchData = (apiCallFn, deps = []) => {
10
10
  setError(false);
11
11
  try {
12
12
  const response = await apiCallFn();
13
- setData(response.data);
13
+ setData(response);
14
14
  }
15
15
  catch {
16
16
  setError(true);
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export { default as ChatProvider } from "./ChatProvider/index.js";
2
2
  export { ConversationsPanel } from "./components/ConversationsPanel/index.js";
3
3
  export { CmndChatContext, InputFieldProps, SendButtonProps, CustomStyles, CMNDChatMemory, ChatbotConversation, ChatbotConversationsResponse, } from "./type.js";
4
4
  export { setCurrentConversationMemory, deleteCurrentConversationMemory, } from "./ChatProvider/index.js";
5
+ export { useCMNDChatContext } from "./ChatProvider/useChatContext.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as ChatProvider } from "./ChatProvider/index.js";
2
2
  export { ConversationsPanel } from "./components/ConversationsPanel/index.js";
3
3
  export { setCurrentConversationMemory, deleteCurrentConversationMemory, } from "./ChatProvider/index.js";
4
+ export { useCMNDChatContext } from "./ChatProvider/useChatContext.js";
@@ -3,5 +3,5 @@ declare const getChatBotConversationsList: ({ organizationId, chatbotId, convers
3
3
  chatbotId: number;
4
4
  conversationIds: number[];
5
5
  baseUrl: string;
6
- }) => Promise<import("axios").AxiosResponse<any, any>>;
6
+ }) => Promise<any>;
7
7
  export default getChatBotConversationsList;
@@ -1,11 +1,12 @@
1
1
  import axios from "axios";
2
2
  import { chatbot } from "../../constants/endpoints.js";
3
- const getChatBotConversationsList = ({ organizationId, chatbotId, conversationIds, baseUrl, }) => {
3
+ const getChatBotConversationsList = async ({ organizationId, chatbotId, conversationIds, baseUrl, }) => {
4
4
  const endpoint = chatbot.getChatBotConversationsList({
5
5
  organizationId,
6
6
  chatbotId,
7
7
  conversationIds,
8
8
  });
9
- return axios.get(`${baseUrl}${endpoint}`);
9
+ const response = await axios.get(`${baseUrl}${endpoint}`);
10
+ return response.data;
10
11
  };
11
12
  export default getChatBotConversationsList;
package/dist/type.d.ts CHANGED
@@ -2,12 +2,37 @@ import { CSSProperties } from "react";
2
2
  import { ConversationProps } from "./components/Conversation.js";
3
3
  export interface CmndChatContext {
4
4
  props: Partial<ConversationProps>;
5
+ /**
6
+ * Create a new conversation (resets state, starts a new chat thread)
7
+ */
8
+ createNewConversation: () => void;
9
+ /**
10
+ * Toggle the sidebar open/collapsed state
11
+ */
12
+ toggleSidebar: () => void;
13
+ /**
14
+ * Open the sidebar (set collapsed to false)
15
+ */
16
+ openSidePanel: () => void;
17
+ /**
18
+ * Close the sidebar (set collapsed to true)
19
+ */
20
+ closeSidePanel: () => void;
21
+ /**
22
+ * Set the message text in the input box
23
+ */
24
+ setMessageText: (text: string) => void;
25
+ /**
26
+ * Send the current message, or set and send if text is provided
27
+ */
28
+ sendMessage: (text?: string) => void;
5
29
  }
6
30
  export interface InputFieldProps {
7
31
  input: string;
8
32
  setInput: (input: string) => void;
9
33
  canSendMessage: boolean;
10
34
  handleSendClick: () => void;
35
+ inputRef?: React.RefObject<HTMLInputElement> | React.RefObject<HTMLTextAreaElement> | React.RefObject<HTMLTextAreaElement>;
11
36
  }
12
37
  export interface SendButtonProps {
13
38
  handleSendClick: () => void;
@@ -1,6 +1,7 @@
1
- declare const saveConversationIdToLocalStorage: ({ chatbotConversationId, chatbotId, organizationId, }: {
1
+ declare const saveConversationIdToLocalStorage: ({ chatbotConversationId, chatbotId, organizationId, chatHistoryStorageKey }: {
2
2
  chatbotConversationId: number;
3
3
  chatbotId: number;
4
4
  organizationId: number;
5
+ chatHistoryStorageKey?: string | undefined;
5
6
  }) => Promise<void>;
6
7
  export default saveConversationIdToLocalStorage;
@@ -1,7 +1,7 @@
1
1
  import getConversationLocalStorageKey from "../getConversationLocalStorageKey/index.js";
2
- const saveConversationIdToLocalStorage = async ({ chatbotConversationId, chatbotId, organizationId, }) => {
2
+ const saveConversationIdToLocalStorage = async ({ chatbotConversationId, chatbotId, organizationId, chatHistoryStorageKey }) => {
3
3
  try {
4
- const key = getConversationLocalStorageKey({
4
+ const key = chatHistoryStorageKey || getConversationLocalStorageKey({
5
5
  organizationId,
6
6
  chatbotId: Number(chatbotId),
7
7
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmnd-ai/chatbot-react",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "main": "dist/index.js",
5
5
  "description": "",
6
6
  "type": "module",