@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.
- package/dist/index.esm.js +30 -58
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +30 -58
- package/dist/index.js.map +1 -1
- package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +0 -1
- package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +1 -2
- package/src/ui/chatbot-popup/chat-window-screen/index.tsx +96 -99
- package/src/ui/help-center.tsx +28 -66
- package/src/ui/help-popup.tsx +3 -3
|
@@ -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
|
|
10
|
+
export declare const ChatWindow: React.MemoExoticComponent<({ onSendMessage, messages, assistantStatus }: ChatWindowProps) => React.JSX.Element>;
|
|
11
11
|
export {};
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
},
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
186
|
-
|
|
175
|
+
assistantStatus === 'typing' ||
|
|
176
|
+
assistantStatus === 'loading' ||
|
|
177
|
+
assistantStatus === 'error' ||
|
|
178
|
+
inputMessage.trim() === ''
|
|
179
|
+
);
|
|
180
|
+
}, [assistantStatus, inputMessage]);
|
|
187
181
|
|
|
188
|
-
|
|
182
|
+
return (
|
|
183
|
+
<>
|
|
184
|
+
<div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
|
|
185
|
+
{messagesList}
|
|
189
186
|
|
|
190
|
-
|
|
191
|
-
</div>
|
|
187
|
+
{assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
|
|
192
188
|
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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';
|
package/src/ui/help-center.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/src/ui/help-popup.tsx
CHANGED
|
@@ -110,11 +110,11 @@ export function HelpPopup({
|
|
|
110
110
|
|
|
111
111
|
const handleSendMessage = useCallback(
|
|
112
112
|
(message: string) => {
|
|
113
|
-
if (message.trim()
|
|
113
|
+
if (message.trim()) {
|
|
114
114
|
onSendMessage(message.trim());
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
|
-
[onSendMessage
|
|
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
|
|