@aslaluroba/help-center-react 3.0.3 → 3.0.4

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.
@@ -5,7 +5,6 @@ interface ChatWindowFooterProps {
5
5
  handleKeyDown: (e: React.KeyboardEvent) => void;
6
6
  handleSendMessage: () => void;
7
7
  isLoading: boolean;
8
- isAblyConnected: boolean;
9
8
  }
10
9
  declare const ChatWindowFooter: React.FC<ChatWindowFooterProps>;
11
10
  export default ChatWindowFooter;
@@ -7,5 +7,5 @@ interface ChatWindowProps {
7
7
  needsAgent: boolean;
8
8
  isAblyConnected: boolean;
9
9
  }
10
- export declare const ChatWindow: React.MemoExoticComponent<({ onSendMessage, messages, assistantStatus, isAblyConnected }: ChatWindowProps) => React.JSX.Element>;
10
+ export declare const ChatWindow: React.MemoExoticComponent<({ onSendMessage, messages, assistantStatus }: ChatWindowProps) => React.JSX.Element>;
11
11
  export {};
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "main": "dist/index.js",
4
4
  "module": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
- "version": "3.0.3",
6
+ "version": "3.0.4",
7
7
  "description": "BabylAI Help Center Widget for React and Next.js",
8
8
  "private": false,
9
9
  "exports": {
@@ -9,7 +9,6 @@ interface ChatWindowFooterProps {
9
9
  handleKeyDown: (e: React.KeyboardEvent) => void;
10
10
  handleSendMessage: () => void;
11
11
  isLoading: boolean;
12
- isAblyConnected: boolean;
13
12
  }
14
13
 
15
14
  const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
@@ -28,7 +27,7 @@ const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
28
27
  variant='default'
29
28
  size='icon'
30
29
  onClick={props.handleSendMessage}
31
- disabled={props?.isLoading || !props.isAblyConnected}
30
+ disabled={props?.isLoading}
32
31
  className='babylai-rounded-full babylai-bg-purple-500 babylai-hover:babylai-bg-purple-600 babylai-w-8 babylai-h-8 babylai-disabled:babylai-opacity-50'
33
32
  >
34
33
  <EnvelopeIcon className={`babylai-w-4 babylai-h-4 ${dir === 'rtl' ? 'babylai-rotate-270' : ''}`} />
@@ -93,114 +93,111 @@ const TypingIndicator = React.memo(({ firstHumanAgentIndex }: { firstHumanAgentI
93
93
 
94
94
  TypingIndicator.displayName = 'TypingIndicator';
95
95
 
96
- export const ChatWindow = React.memo(
97
- ({ onSendMessage, messages, assistantStatus = 'loading', isAblyConnected }: ChatWindowProps) => {
98
- const [inputMessage, setInputMessage] = useState('');
99
- const messagesEndRef = useRef<HTMLDivElement>(null);
100
- const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
101
- const lastMessageCountRef = useRef(messages.length);
102
-
103
- // Debounced scroll to bottom function
104
- const scrollToBottom = useCallback(() => {
96
+ export const ChatWindow = React.memo(({ onSendMessage, messages, assistantStatus = 'loading' }: ChatWindowProps) => {
97
+ const [inputMessage, setInputMessage] = useState('');
98
+ const messagesEndRef = useRef<HTMLDivElement>(null);
99
+ const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
100
+ const lastMessageCountRef = useRef(messages.length);
101
+
102
+ // Debounced scroll to bottom function
103
+ const scrollToBottom = useCallback(() => {
104
+ if (scrollTimeoutRef.current) {
105
+ clearTimeout(scrollTimeoutRef.current);
106
+ }
107
+
108
+ scrollTimeoutRef.current = setTimeout(() => {
109
+ messagesEndRef.current?.scrollIntoView({
110
+ behavior: 'smooth',
111
+ block: 'end',
112
+ });
113
+ }, 100);
114
+ }, []);
115
+
116
+ // Only scroll when new messages are added or status changes
117
+ useEffect(() => {
118
+ if (messages.length !== lastMessageCountRef.current || assistantStatus === 'typing') {
119
+ lastMessageCountRef.current = messages.length;
120
+ scrollToBottom();
121
+ }
122
+ }, [messages.length, assistantStatus, scrollToBottom]);
123
+
124
+ // Cleanup timeout on unmount
125
+ useEffect(() => {
126
+ return () => {
105
127
  if (scrollTimeoutRef.current) {
106
128
  clearTimeout(scrollTimeoutRef.current);
107
129
  }
108
-
109
- scrollTimeoutRef.current = setTimeout(() => {
110
- messagesEndRef.current?.scrollIntoView({
111
- behavior: 'smooth',
112
- block: 'end',
113
- });
114
- }, 100);
115
- }, []);
116
-
117
- // Only scroll when new messages are added or status changes
118
- useEffect(() => {
119
- if (messages.length !== lastMessageCountRef.current || assistantStatus === 'typing') {
120
- lastMessageCountRef.current = messages.length;
121
- scrollToBottom();
122
- }
123
- }, [messages.length, assistantStatus, scrollToBottom]);
124
-
125
- // Cleanup timeout on unmount
126
- useEffect(() => {
127
- return () => {
128
- if (scrollTimeoutRef.current) {
129
- clearTimeout(scrollTimeoutRef.current);
130
+ };
131
+ }, []);
132
+
133
+ const handleSendMessage = useCallback(() => {
134
+ if (inputMessage.trim()) {
135
+ onSendMessage(inputMessage);
136
+ setInputMessage('');
137
+ }
138
+ }, [inputMessage, onSendMessage]);
139
+
140
+ const handleKeyDown = useCallback(
141
+ (e: React.KeyboardEvent) => {
142
+ if (e.key === 'Enter' && !e.shiftKey) {
143
+ e.preventDefault();
144
+ if (inputMessage.trim() && assistantStatus !== 'typing') {
145
+ onSendMessage(inputMessage);
146
+ setInputMessage('');
130
147
  }
131
- };
132
- }, []);
133
-
134
- const handleSendMessage = useCallback(() => {
135
- if (inputMessage.trim()) {
136
- onSendMessage(inputMessage);
137
- setInputMessage('');
138
148
  }
139
- }, [inputMessage, onSendMessage]);
140
-
141
- const handleKeyDown = useCallback(
142
- (e: React.KeyboardEvent) => {
143
- if (e.key === 'Enter' && !e.shiftKey && isAblyConnected) {
144
- e.preventDefault();
145
- if (inputMessage.trim() && assistantStatus !== 'typing') {
146
- onSendMessage(inputMessage);
147
- setInputMessage('');
148
- }
149
- }
150
- },
151
- [inputMessage, onSendMessage, assistantStatus, isAblyConnected]
152
- );
153
-
154
- // Memoize the first human agent index calculation
155
- const firstHumanAgentIndex = useMemo(() => {
156
- return messages.findIndex((message) => message.senderType === 2);
157
- }, [messages]);
158
-
159
- // Memoize the message list to prevent unnecessary re-renders
160
- const messagesList = useMemo(() => {
161
- return messages.map((message, index) => (
162
- <MessageComponent
163
- key={`${message.id}-${index}`}
164
- message={message}
165
- index={index}
166
- messages={messages}
167
- firstHumanAgentIndex={firstHumanAgentIndex}
168
- onType={scrollToBottom}
169
- />
170
- ));
171
- }, [messages, firstHumanAgentIndex, scrollToBottom]);
172
-
173
- // Memoize loading state check
174
- const isLoading = useMemo(() => {
175
- return (
176
- assistantStatus === 'typing' ||
177
- assistantStatus === 'loading' ||
178
- assistantStatus === 'error' ||
179
- inputMessage.trim() === ''
180
- );
181
- }, [assistantStatus, inputMessage]);
149
+ },
150
+ [inputMessage, onSendMessage, assistantStatus]
151
+ );
182
152
 
153
+ // Memoize the first human agent index calculation
154
+ const firstHumanAgentIndex = useMemo(() => {
155
+ return messages.findIndex((message) => message.senderType === 2);
156
+ }, [messages]);
157
+
158
+ // Memoize the message list to prevent unnecessary re-renders
159
+ const messagesList = useMemo(() => {
160
+ return messages.map((message, index) => (
161
+ <MessageComponent
162
+ key={`${message.id}-${index}`}
163
+ message={message}
164
+ index={index}
165
+ messages={messages}
166
+ firstHumanAgentIndex={firstHumanAgentIndex}
167
+ onType={scrollToBottom}
168
+ />
169
+ ));
170
+ }, [messages, firstHumanAgentIndex, scrollToBottom]);
171
+
172
+ // Memoize loading state check
173
+ const isLoading = useMemo(() => {
183
174
  return (
184
- <>
185
- <div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
186
- {messagesList}
175
+ assistantStatus === 'typing' ||
176
+ assistantStatus === 'loading' ||
177
+ assistantStatus === 'error' ||
178
+ inputMessage.trim() === ''
179
+ );
180
+ }, [assistantStatus, inputMessage]);
187
181
 
188
- {assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
182
+ return (
183
+ <>
184
+ <div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
185
+ {messagesList}
189
186
 
190
- <div ref={messagesEndRef} />
191
- </div>
187
+ {assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
192
188
 
193
- <ChatWindowFooter
194
- inputMessage={inputMessage}
195
- handleKeyDown={handleKeyDown}
196
- handleSendMessage={handleSendMessage}
197
- setInputMessage={setInputMessage}
198
- isLoading={isLoading}
199
- isAblyConnected={isAblyConnected}
200
- />
201
- </>
202
- );
203
- }
204
- );
189
+ <div ref={messagesEndRef} />
190
+ </div>
191
+
192
+ <ChatWindowFooter
193
+ inputMessage={inputMessage}
194
+ handleKeyDown={handleKeyDown}
195
+ handleSendMessage={handleSendMessage}
196
+ setInputMessage={setInputMessage}
197
+ isLoading={isLoading}
198
+ />
199
+ </>
200
+ );
201
+ });
205
202
 
206
203
  ChatWindow.displayName = 'ChatWindow';
@@ -98,7 +98,6 @@ export function HelpCenter({
98
98
  setMessages([]);
99
99
 
100
100
  setIsReviewDialogOpen(true);
101
-
102
101
  } catch (error) {
103
102
  console.error('Error ending chat:', error);
104
103
  setError('Failed to end chat session');
@@ -119,7 +118,7 @@ export function HelpCenter({
119
118
  const response = await apiRequest(`Client/ClientChatSession/${reviewSessionId}/review`, 'POST', payload);
120
119
  if (!response.ok) throw new Error('Failed to send chat review');
121
120
 
122
- handleCloseChatReview()
121
+ handleCloseChatReview();
123
122
  } catch (error) {
124
123
  console.error('Error sending chat review:', error);
125
124
  setError('Failed to send chat review');
@@ -132,15 +131,23 @@ export function HelpCenter({
132
131
  };
133
132
 
134
133
  const handleStartChat = async (option: Option) => {
135
- await startNewChatSession(option);
134
+ setMessages([
135
+ {
136
+ id: Date.now(),
137
+ senderType: 3,
138
+ messageContent: option?.assistant?.greeting || 'مرحباً! كيف يمكنني مساعدتك اليوم؟',
139
+ sentAt: new Date(),
140
+ isSeen: true,
141
+ },
142
+ ]);
143
+ setIsChatClosed(false);
144
+ setStatus('succeeded');
136
145
  };
137
146
 
138
- const startNewChatSession = async (option: Option) => {
139
-
147
+ const startNewChatSession = async (option: Option): Promise<string> => {
140
148
  try {
141
149
  setStatus('loading');
142
150
  setError('');
143
- setMessages([]);
144
151
 
145
152
  const chatSessionCreateDto = {
146
153
  helpScreenId: helpScreenId,
@@ -161,32 +168,12 @@ export function HelpCenter({
161
168
  }
162
169
 
163
170
  const responseData = await response.json();
164
- setSessionId(responseData.chatSession.id);
165
-
166
- if (responseData.chatSession.assistant?.greeting) {
167
- setMessages([
168
- {
169
- id: Date.now(),
170
- senderType: 3,
171
- messageContent: responseData.chatSession.assistant.greeting,
172
- sentAt: new Date(),
173
- isSeen: true,
174
- },
175
- ]);
176
- } else {
177
- setMessages([
178
- {
179
- id: Date.now(),
180
- senderType: 3,
181
- messageContent: 'Hello! How can I assist you today?\nمرحباً! كيف يمكنني مساعدتك اليوم؟',
182
- sentAt: new Date(),
183
- isSeen: true,
184
- },
185
- ]);
186
- }
171
+ const newSessionId = responseData.chatSession.id;
172
+
173
+ setSessionId(newSessionId);
187
174
 
188
175
  await ClientAblyService.startConnection(
189
- responseData.chatSession.id,
176
+ newSessionId,
190
177
  responseData.ablyToken,
191
178
  handleReceiveMessage,
192
179
  responseData.chatSession.tenantId
@@ -200,21 +187,16 @@ export function HelpCenter({
200
187
  setIsAblyConnected(true);
201
188
  setIsChatClosed(false);
202
189
  setStatus('succeeded');
190
+
191
+ return newSessionId; // Return the session ID
203
192
  } catch (error) {
204
193
  setError(error instanceof Error ? error.message : 'Failed to start chat session');
205
194
  setStatus('failed');
195
+ throw error; // Re-throw to handle in calling function
206
196
  }
207
197
  };
208
198
 
209
199
  const handleSendMessage = async (message: string) => {
210
- // Only send message if Ably is connected
211
- if (!isAblyConnected) {
212
- setError('Connection lost. Please try again.');
213
- return;
214
- }
215
-
216
- let currentSessionId = sessionId;
217
-
218
200
  if (message.trim() !== '') {
219
201
  try {
220
202
  setAssistantStatus('typing');
@@ -229,35 +211,15 @@ export function HelpCenter({
229
211
 
230
212
  setMessages((prevMessages) => [...prevMessages, userMessage]);
231
213
 
214
+ // Handle session creation if needed
215
+ let currentSessionId = sessionId;
216
+
217
+ if (!isAblyConnected && selectedOption) {
218
+ currentSessionId = await startNewChatSession(selectedOption);
219
+ }
220
+
232
221
  if (!currentSessionId) {
233
- const chatSessionCreateDto = {
234
- helpScreenId: helpScreenId,
235
- optionId: selectedOption?.id,
236
- user: {
237
- id: user?.id,
238
- name: user?.name,
239
- email: user?.email,
240
- },
241
- };
242
-
243
- const response = await apiRequest('Client/ClientChatSession/create-session', 'POST', chatSessionCreateDto);
244
-
245
- if (!response.ok) {
246
- throw new Error('Failed to create chat session');
247
- }
248
-
249
- const responseData = await response.json();
250
- setSessionId(responseData.chatSession.id);
251
- currentSessionId = responseData.chatSession.id;
252
-
253
- await ClientAblyService.startConnection(
254
- responseData.chatSession.id,
255
- responseData.ablyToken,
256
- handleReceiveMessage,
257
- responseData.chatSession.tenantId
258
- );
259
-
260
- setIsAblyConnected(true);
222
+ throw new Error('No active session available');
261
223
  }
262
224
 
263
225
  const messageDto = { messageContent: message };
@@ -110,11 +110,11 @@ export function HelpPopup({
110
110
 
111
111
  const handleSendMessage = useCallback(
112
112
  (message: string) => {
113
- if (message.trim() && isAblyConnected) {
113
+ if (message.trim()) {
114
114
  onSendMessage(message.trim());
115
115
  }
116
116
  },
117
- [onSendMessage, isAblyConnected]
117
+ [onSendMessage]
118
118
  );
119
119
 
120
120
  const hideEndChatConfirmation = useCallback(() => {
@@ -133,7 +133,7 @@ export function HelpPopup({
133
133
  })
134
134
  .finally(() => {
135
135
  setTempSelectedOption(null);
136
- })
136
+ });
137
137
  }
138
138
  };
139
139