@aslaluroba/help-center-react 3.0.20 → 3.2.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.
- package/dist/components/ui/image-attachment.d.ts +9 -0
- package/dist/components/ui/image-preview-dialog.d.ts +10 -0
- package/dist/components/ui/index.d.ts +4 -0
- package/dist/core/api.d.ts +5 -2
- package/dist/index.css +1 -1
- package/dist/index.esm.js +4928 -551
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +4928 -551
- package/dist/index.js.map +1 -1
- package/dist/lib/types.d.ts +23 -0
- package/dist/services.esm.js +23 -16
- package/dist/services.esm.js.map +1 -1
- package/dist/services.js +23 -16
- package/dist/services.js.map +1 -1
- package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +3 -2
- package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +3 -2
- package/dist/ui/help-popup.d.ts +3 -2
- package/package.json +5 -5
- package/src/.DS_Store +0 -0
- package/src/assets/icons/paperclip.svg +3 -0
- package/src/assets/icons/x.svg +4 -0
- package/src/components/ui/image-attachment.tsx +107 -0
- package/src/components/ui/image-preview-dialog.tsx +354 -0
- package/src/components/ui/index.ts +4 -0
- package/src/core/AblyService.ts +2 -2
- package/src/core/ApiService.ts +0 -2
- package/src/core/api.ts +81 -11
- package/src/lib/types.ts +110 -84
- package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +259 -22
- package/src/ui/chatbot-popup/chat-window-screen/index.tsx +206 -101
- package/src/ui/help-center.tsx +52 -18
- package/src/ui/help-popup.tsx +11 -9
- package/src/ui/powered-by.tsx +10 -9
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AgentResponse from '@/components/ui/agent-response/agent-response';
|
|
2
|
+
import { ImageAttachment, ImagePreviewDialog } from '@/components/ui';
|
|
2
3
|
import { Message } from '@/lib/types';
|
|
3
4
|
import ChatWindowFooter from '@/ui/chatbot-popup/chat-window-screen/footer';
|
|
4
5
|
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
@@ -6,9 +7,12 @@ import LoadingGif from './../../../assets/animatedLogo.gif';
|
|
|
6
7
|
import Seperator from './../../../assets/icons/seperator.svg';
|
|
7
8
|
import LogoIcon from './../../../assets/logo.svg';
|
|
8
9
|
import HumanIcon from './../../../assets/icons/user.svg';
|
|
10
|
+
import { presignDownload } from '@/core/api';
|
|
11
|
+
import { useLocalTranslation } from '../../../useLocalTranslation';
|
|
9
12
|
|
|
10
13
|
interface ChatWindowProps {
|
|
11
|
-
onSendMessage: (message: string) => void;
|
|
14
|
+
onSendMessage: (message: string, attachmentIds: string[]) => void;
|
|
15
|
+
onEnsureSession: () => Promise<string>;
|
|
12
16
|
messages: Message[];
|
|
13
17
|
assistantStatus: string;
|
|
14
18
|
needsAgent: boolean;
|
|
@@ -23,17 +27,28 @@ const MessageComponent = React.memo(
|
|
|
23
27
|
messages,
|
|
24
28
|
firstHumanAgentIndex,
|
|
25
29
|
onType,
|
|
30
|
+
onImageClick,
|
|
26
31
|
}: {
|
|
27
32
|
message: Message;
|
|
28
33
|
index: number;
|
|
29
34
|
messages: Message[];
|
|
30
35
|
firstHumanAgentIndex: number;
|
|
31
36
|
onType: () => void;
|
|
37
|
+
onImageClick: (attachmentIds: string[], clickedIndex: number) => void;
|
|
32
38
|
}) => {
|
|
33
39
|
const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType;
|
|
34
40
|
const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
|
|
35
41
|
const textDirection = message.senderType === 1 ? 'babylai-justify-end' : 'babylai-justify-start';
|
|
36
42
|
|
|
43
|
+
const handleImageClick = useCallback(
|
|
44
|
+
(clickedIndex: number) => {
|
|
45
|
+
if (message.attachmentIds && message.attachmentIds.length > 0) {
|
|
46
|
+
onImageClick(message.attachmentIds, clickedIndex);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[message.attachmentIds, onImageClick]
|
|
50
|
+
);
|
|
51
|
+
|
|
37
52
|
return (
|
|
38
53
|
<div key={message.id}>
|
|
39
54
|
{isFirstHumanAgentMessage && (
|
|
@@ -59,12 +74,26 @@ const MessageComponent = React.memo(
|
|
|
59
74
|
)}
|
|
60
75
|
{!isFirstInSequence && <div className='babylai-flex-shrink-0 babylai-me-3 babylai-w-8'></div>}
|
|
61
76
|
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
<div className='babylai-flex babylai-flex-col babylai-gap-2'>
|
|
78
|
+
{message.attachmentIds && message.attachmentIds.length > 0 && (
|
|
79
|
+
<div className='babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full'>
|
|
80
|
+
{message.attachmentIds.map((attachmentId, imgIndex) => (
|
|
81
|
+
<ImageAttachment
|
|
82
|
+
key={attachmentId}
|
|
83
|
+
fileId={attachmentId}
|
|
84
|
+
enablePreview={false}
|
|
85
|
+
onClick={() => handleImageClick(imgIndex)}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
<AgentResponse
|
|
91
|
+
messageContent={message.messageContent}
|
|
92
|
+
senderType={message.senderType}
|
|
93
|
+
messageId={message.id}
|
|
94
|
+
onType={onType}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
68
97
|
</div>
|
|
69
98
|
</div>
|
|
70
99
|
);
|
|
@@ -93,111 +122,187 @@ const TypingIndicator = React.memo(({ firstHumanAgentIndex }: { firstHumanAgentI
|
|
|
93
122
|
|
|
94
123
|
TypingIndicator.displayName = 'TypingIndicator';
|
|
95
124
|
|
|
96
|
-
export const ChatWindow = React.memo(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 () => {
|
|
125
|
+
export const ChatWindow = React.memo(
|
|
126
|
+
({ onSendMessage, onEnsureSession, messages, assistantStatus = 'loading', needsAgent }: ChatWindowProps) => {
|
|
127
|
+
const { i18n } = useLocalTranslation();
|
|
128
|
+
const [inputMessage, setInputMessage] = useState('');
|
|
129
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
130
|
+
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
131
|
+
const lastMessageCountRef = useRef(messages.length);
|
|
132
|
+
const [galleryState, setGalleryState] = useState<{
|
|
133
|
+
isOpen: boolean;
|
|
134
|
+
imageUrls: string[];
|
|
135
|
+
initialIndex: number;
|
|
136
|
+
}>({
|
|
137
|
+
isOpen: false,
|
|
138
|
+
imageUrls: [],
|
|
139
|
+
initialIndex: 0,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Debounced scroll to bottom function
|
|
143
|
+
const scrollToBottom = useCallback(() => {
|
|
127
144
|
if (scrollTimeoutRef.current) {
|
|
128
145
|
clearTimeout(scrollTimeoutRef.current);
|
|
129
146
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
|
|
148
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
149
|
+
messagesEndRef.current?.scrollIntoView({
|
|
150
|
+
behavior: 'smooth',
|
|
151
|
+
block: 'end',
|
|
152
|
+
});
|
|
153
|
+
}, 100);
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
// Only scroll when new messages are added or status changes
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (messages.length !== lastMessageCountRef.current || assistantStatus === 'typing') {
|
|
159
|
+
lastMessageCountRef.current = messages.length;
|
|
160
|
+
scrollToBottom();
|
|
161
|
+
}
|
|
162
|
+
}, [messages.length, assistantStatus, scrollToBottom]);
|
|
163
|
+
|
|
164
|
+
// Cleanup timeout on unmount
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
return () => {
|
|
167
|
+
if (scrollTimeoutRef.current) {
|
|
168
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
const handleSendMessage = useCallback(
|
|
174
|
+
(attachmentIds: string[]) => {
|
|
175
|
+
if (inputMessage.trim()) {
|
|
176
|
+
onSendMessage(inputMessage, attachmentIds);
|
|
146
177
|
setInputMessage('');
|
|
147
178
|
}
|
|
179
|
+
},
|
|
180
|
+
[inputMessage, onSendMessage]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Memoize the first human agent index calculation
|
|
184
|
+
const firstHumanAgentIndex = useMemo(() => {
|
|
185
|
+
return messages.findIndex((message) => message.senderType === 2);
|
|
186
|
+
}, [messages]);
|
|
187
|
+
|
|
188
|
+
// Handle image gallery opening
|
|
189
|
+
const handleImageClick = useCallback(
|
|
190
|
+
async (attachmentIds: string[], clickedIndex: number) => {
|
|
191
|
+
if (!attachmentIds || attachmentIds.length === 0) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// Fetch all image URLs with comprehensive error handling
|
|
197
|
+
const imageUrlPromises = attachmentIds.map((fileId) => {
|
|
198
|
+
if (!fileId || typeof fileId !== 'string') {
|
|
199
|
+
return Promise.resolve(null);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return presignDownload(fileId, i18n.language)
|
|
203
|
+
.then((response) => {
|
|
204
|
+
if (!response || !response.downloadUrl) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return response.downloadUrl;
|
|
208
|
+
})
|
|
209
|
+
.catch(() => {
|
|
210
|
+
// Return null for failed downloads so we can filter them out
|
|
211
|
+
return null;
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const imageUrls = (await Promise.all(imageUrlPromises)).filter(
|
|
216
|
+
(url): url is string => url !== null && url.length > 0
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (imageUrls.length === 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Adjust the initial index if some images failed to load
|
|
224
|
+
let adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
|
|
225
|
+
|
|
226
|
+
setGalleryState({
|
|
227
|
+
isOpen: true,
|
|
228
|
+
imageUrls,
|
|
229
|
+
initialIndex: adjustedIndex,
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
// Handle unexpected errors silently
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
[i18n.language]
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const handleCloseGallery = useCallback(() => {
|
|
239
|
+
setGalleryState({
|
|
240
|
+
isOpen: false,
|
|
241
|
+
imageUrls: [],
|
|
242
|
+
initialIndex: 0,
|
|
243
|
+
});
|
|
244
|
+
}, []);
|
|
245
|
+
|
|
246
|
+
// Memoize loading state check
|
|
247
|
+
// When a human agent has joined, don't disable based on assistantStatus
|
|
248
|
+
const hasHumanAgent = useMemo(() => {
|
|
249
|
+
return needsAgent || messages.some((msg) => msg.senderType === 2);
|
|
250
|
+
}, [needsAgent, messages]);
|
|
251
|
+
|
|
252
|
+
const isLoading = useMemo(() => {
|
|
253
|
+
// If human agent has joined, don't disable file uploads based on assistantStatus
|
|
254
|
+
if (hasHumanAgent) {
|
|
255
|
+
return false;
|
|
148
256
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
257
|
+
return assistantStatus === 'typing' || assistantStatus === 'loading' || assistantStatus === 'error';
|
|
258
|
+
}, [assistantStatus, hasHumanAgent]);
|
|
259
|
+
|
|
260
|
+
// Memoize the message list to prevent unnecessary re-renders
|
|
261
|
+
const messagesListWithGallery = useMemo(() => {
|
|
262
|
+
return messages.map((message, index) => (
|
|
263
|
+
<MessageComponent
|
|
264
|
+
key={`${message.id}-${index}`}
|
|
265
|
+
message={message}
|
|
266
|
+
index={index}
|
|
267
|
+
messages={messages}
|
|
268
|
+
firstHumanAgentIndex={firstHumanAgentIndex}
|
|
269
|
+
onType={scrollToBottom}
|
|
270
|
+
onImageClick={handleImageClick}
|
|
271
|
+
/>
|
|
272
|
+
));
|
|
273
|
+
}, [messages, firstHumanAgentIndex, scrollToBottom, handleImageClick]);
|
|
152
274
|
|
|
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(() => {
|
|
174
275
|
return (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
inputMessage.trim() === ''
|
|
179
|
-
);
|
|
180
|
-
}, [assistantStatus, inputMessage]);
|
|
276
|
+
<>
|
|
277
|
+
<div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
|
|
278
|
+
{messagesListWithGallery}
|
|
181
279
|
|
|
182
|
-
|
|
183
|
-
<>
|
|
184
|
-
<div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
|
|
185
|
-
{messagesList}
|
|
280
|
+
{assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
|
|
186
281
|
|
|
187
|
-
|
|
282
|
+
<div ref={messagesEndRef} />
|
|
283
|
+
</div>
|
|
188
284
|
|
|
189
|
-
<
|
|
190
|
-
|
|
285
|
+
<ChatWindowFooter
|
|
286
|
+
inputMessage={inputMessage}
|
|
287
|
+
handleSendMessage={handleSendMessage}
|
|
288
|
+
setInputMessage={setInputMessage}
|
|
289
|
+
isLoading={isLoading}
|
|
290
|
+
onEnsureSession={onEnsureSession}
|
|
291
|
+
/>
|
|
191
292
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
293
|
+
{/* Gallery Preview Dialog */}
|
|
294
|
+
{galleryState.isOpen && galleryState.imageUrls.length > 0 && (
|
|
295
|
+
<ImagePreviewDialog
|
|
296
|
+
imageUrls={galleryState.imageUrls}
|
|
297
|
+
initialIndex={galleryState.initialIndex}
|
|
298
|
+
isOpen={galleryState.isOpen}
|
|
299
|
+
onClose={handleCloseGallery}
|
|
300
|
+
alt='Image gallery preview'
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
303
|
+
</>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
);
|
|
202
307
|
|
|
203
308
|
ChatWindow.displayName = 'ChatWindow';
|
package/src/ui/help-center.tsx
CHANGED
|
@@ -38,7 +38,8 @@ const HelpCenterContent = ({
|
|
|
38
38
|
primaryColor,
|
|
39
39
|
secondaryColor,
|
|
40
40
|
logoUrl,
|
|
41
|
-
|
|
41
|
+
language,
|
|
42
|
+
}: HelpCenterProps) => {
|
|
42
43
|
const { t } = useLocalTranslation();
|
|
43
44
|
const [isOpen, setIsOpen] = useState(false);
|
|
44
45
|
const [showArrowAnimation, setShowArrowAnimation] = useState(showArrow);
|
|
@@ -54,7 +55,6 @@ const HelpCenterContent = ({
|
|
|
54
55
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
55
56
|
const [needsAgent, setNeedsAgent] = useState(false);
|
|
56
57
|
const [assistantStatus, setAssistantStatus] = useState('idle');
|
|
57
|
-
|
|
58
58
|
const [isReviewDialogOpen, setIsReviewDialogOpen] = useState(false);
|
|
59
59
|
|
|
60
60
|
const handleTogglePopup = () => {
|
|
@@ -66,18 +66,24 @@ const HelpCenterContent = ({
|
|
|
66
66
|
setShowArrowAnimation(false);
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
const handleReceiveMessage = (
|
|
69
|
+
const handleReceiveMessage = (
|
|
70
|
+
message: string,
|
|
71
|
+
senderType: number,
|
|
72
|
+
needsAgent: boolean,
|
|
73
|
+
attachments: string[] = []
|
|
74
|
+
) => {
|
|
70
75
|
if (needsAgent) {
|
|
71
76
|
setNeedsAgent(true);
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
setMessages((prevMessages) => {
|
|
75
|
-
const newMessage = {
|
|
80
|
+
const newMessage: Message = {
|
|
76
81
|
id: Date.now(),
|
|
77
82
|
senderType: senderType,
|
|
78
83
|
messageContent: message,
|
|
79
84
|
sentAt: new Date(),
|
|
80
85
|
isSeen: true,
|
|
86
|
+
...(attachments.length > 0 && { attachmentIds: attachments }),
|
|
81
87
|
};
|
|
82
88
|
|
|
83
89
|
return [...prevMessages, newMessage];
|
|
@@ -94,7 +100,9 @@ const HelpCenterContent = ({
|
|
|
94
100
|
setIsAblyConnected(false);
|
|
95
101
|
setAssistantStatus('idle');
|
|
96
102
|
|
|
97
|
-
const response = await apiRequest(`Client/ClientChatSession/${sessionId}/close`, 'POST'
|
|
103
|
+
const response = await apiRequest(`Client/ClientChatSession/${sessionId}/close`, 'POST', null, {
|
|
104
|
+
language: language,
|
|
105
|
+
});
|
|
98
106
|
if (!response.ok) throw new Error('Failed to close chat session');
|
|
99
107
|
|
|
100
108
|
// Store sessionId for review before clearing the main sessionId
|
|
@@ -107,7 +115,6 @@ const HelpCenterContent = ({
|
|
|
107
115
|
|
|
108
116
|
setIsReviewDialogOpen(true);
|
|
109
117
|
} catch (error) {
|
|
110
|
-
console.error('Error ending chat:', error);
|
|
111
118
|
setError('Failed to end chat session');
|
|
112
119
|
setAssistantStatus('idle');
|
|
113
120
|
// Even if there's an error, clear the session state to prevent stuck state
|
|
@@ -123,12 +130,13 @@ const HelpCenterContent = ({
|
|
|
123
130
|
const payload = { rating, comment };
|
|
124
131
|
|
|
125
132
|
try {
|
|
126
|
-
const response = await apiRequest(`Client/ClientChatSession/${reviewSessionId}/review`, 'POST', payload
|
|
133
|
+
const response = await apiRequest(`Client/ClientChatSession/${reviewSessionId}/review`, 'POST', payload, {
|
|
134
|
+
language,
|
|
135
|
+
});
|
|
127
136
|
if (!response.ok) throw new Error('Failed to send chat review');
|
|
128
137
|
|
|
129
138
|
handleCloseChatReview();
|
|
130
139
|
} catch (error) {
|
|
131
|
-
console.error('Error sending chat review:', error);
|
|
132
140
|
setError('Failed to send chat review');
|
|
133
141
|
}
|
|
134
142
|
};
|
|
@@ -169,7 +177,9 @@ const HelpCenterContent = ({
|
|
|
169
177
|
}),
|
|
170
178
|
};
|
|
171
179
|
|
|
172
|
-
const response = await apiRequest('Client/ClientChatSession/create-session', 'POST', chatSessionCreateDto
|
|
180
|
+
const response = await apiRequest('Client/ClientChatSession/create-session', 'POST', chatSessionCreateDto, {
|
|
181
|
+
language,
|
|
182
|
+
});
|
|
173
183
|
|
|
174
184
|
if (!response.ok) {
|
|
175
185
|
throw new Error('Failed to create chat session');
|
|
@@ -204,17 +214,33 @@ const HelpCenterContent = ({
|
|
|
204
214
|
}
|
|
205
215
|
};
|
|
206
216
|
|
|
207
|
-
const
|
|
217
|
+
const handleEnsureSession = async (): Promise<string> => {
|
|
218
|
+
// If we already have a session ID and connection, return it
|
|
219
|
+
if (sessionId && isAblyConnected) {
|
|
220
|
+
return sessionId;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// If we have a selected option but no session, create one
|
|
224
|
+
if (selectedOption) {
|
|
225
|
+
const newSessionId = await startNewChatSession(selectedOption);
|
|
226
|
+
return newSessionId;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
throw new Error('No option selected');
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const handleSendMessage = async (message: string, attachmentIds: string[] = []) => {
|
|
208
233
|
if (message.trim() !== '') {
|
|
209
234
|
try {
|
|
210
235
|
setAssistantStatus('typing');
|
|
211
236
|
|
|
212
|
-
const userMessage = {
|
|
237
|
+
const userMessage: Message = {
|
|
213
238
|
id: Date.now(),
|
|
214
239
|
senderType: 1,
|
|
215
240
|
messageContent: message,
|
|
216
241
|
sentAt: new Date(),
|
|
217
242
|
isSeen: false,
|
|
243
|
+
attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined,
|
|
218
244
|
};
|
|
219
245
|
|
|
220
246
|
setMessages((prevMessages) => [...prevMessages, userMessage]);
|
|
@@ -230,11 +256,16 @@ const HelpCenterContent = ({
|
|
|
230
256
|
throw new Error('No active session available');
|
|
231
257
|
}
|
|
232
258
|
|
|
233
|
-
const messageDto = {
|
|
259
|
+
const messageDto = {
|
|
260
|
+
messageContent: message,
|
|
261
|
+
...(attachmentIds.length > 0 && { attachmentIds }),
|
|
262
|
+
};
|
|
263
|
+
|
|
234
264
|
const response = await apiRequest(
|
|
235
265
|
`Client/ClientChatSession/${currentSessionId}/send-message`,
|
|
236
266
|
'POST',
|
|
237
|
-
messageDto
|
|
267
|
+
messageDto,
|
|
268
|
+
{ language }
|
|
238
269
|
);
|
|
239
270
|
|
|
240
271
|
if (!response.ok) {
|
|
@@ -245,10 +276,9 @@ const HelpCenterContent = ({
|
|
|
245
276
|
return prevMessages.map((msg) => (msg.senderType === 1 && !msg.isSeen ? { ...msg, isSeen: true } : msg));
|
|
246
277
|
});
|
|
247
278
|
} catch (error) {
|
|
248
|
-
console.error('Error in handleSendMessage:', error);
|
|
249
279
|
setAssistantStatus('idle');
|
|
250
280
|
|
|
251
|
-
const errorMessage = {
|
|
281
|
+
const errorMessage: Message = {
|
|
252
282
|
id: Date.now(),
|
|
253
283
|
senderType: 3,
|
|
254
284
|
messageContent:
|
|
@@ -266,7 +296,9 @@ const HelpCenterContent = ({
|
|
|
266
296
|
if (isOpen && helpScreenId) {
|
|
267
297
|
setStatus('loading');
|
|
268
298
|
|
|
269
|
-
apiRequest(`client/clientHelpScreen/${helpScreenId}
|
|
299
|
+
apiRequest(`client/clientHelpScreen/${helpScreenId}`, 'GET', null, {
|
|
300
|
+
language,
|
|
301
|
+
})
|
|
270
302
|
.then((res) => res.json())
|
|
271
303
|
.then((data) => {
|
|
272
304
|
setHelpScreenData(data);
|
|
@@ -300,6 +332,7 @@ const HelpCenterContent = ({
|
|
|
300
332
|
user={user}
|
|
301
333
|
onStartChat={handleStartChat}
|
|
302
334
|
onSendMessage={handleSendMessage}
|
|
335
|
+
onEnsureSession={handleEnsureSession}
|
|
303
336
|
onEndChat={handleEndChat}
|
|
304
337
|
messages={messages}
|
|
305
338
|
needsAgent={needsAgent}
|
|
@@ -318,7 +351,7 @@ const HelpCenterContent = ({
|
|
|
318
351
|
)}
|
|
319
352
|
</div>
|
|
320
353
|
);
|
|
321
|
-
}
|
|
354
|
+
};
|
|
322
355
|
|
|
323
356
|
// Main HelpCenter component that provides the language context
|
|
324
357
|
const HelpCenter = ({
|
|
@@ -335,6 +368,7 @@ const HelpCenter = ({
|
|
|
335
368
|
return (
|
|
336
369
|
<LanguageProvider language={language}>
|
|
337
370
|
<HelpCenterContent
|
|
371
|
+
language={language}
|
|
338
372
|
helpScreenId={helpScreenId}
|
|
339
373
|
user={user}
|
|
340
374
|
showArrow={showArrow}
|
|
@@ -346,6 +380,6 @@ const HelpCenter = ({
|
|
|
346
380
|
/>
|
|
347
381
|
</LanguageProvider>
|
|
348
382
|
);
|
|
349
|
-
}
|
|
383
|
+
};
|
|
350
384
|
|
|
351
385
|
export default HelpCenter;
|
package/src/ui/help-popup.tsx
CHANGED
|
@@ -21,7 +21,8 @@ type HelpPopupProps = {
|
|
|
21
21
|
error: string | null;
|
|
22
22
|
user: any;
|
|
23
23
|
onStartChat: (option: Option) => void;
|
|
24
|
-
onSendMessage: (message: string) => void;
|
|
24
|
+
onSendMessage: (message: string, attachmentIds: string[]) => void;
|
|
25
|
+
onEnsureSession: () => Promise<string>;
|
|
25
26
|
onEndChat: (option?: Option) => void;
|
|
26
27
|
messages: Message[];
|
|
27
28
|
needsAgent: boolean;
|
|
@@ -42,6 +43,7 @@ const HelpPopup = ({
|
|
|
42
43
|
error,
|
|
43
44
|
onStartChat,
|
|
44
45
|
onSendMessage,
|
|
46
|
+
onEnsureSession,
|
|
45
47
|
onEndChat,
|
|
46
48
|
messages,
|
|
47
49
|
assistantStatus,
|
|
@@ -112,9 +114,9 @@ const HelpPopup = ({
|
|
|
112
114
|
);
|
|
113
115
|
|
|
114
116
|
const handleSendMessage = useCallback(
|
|
115
|
-
(message: string) => {
|
|
117
|
+
(message: string, attachmentIds: string[]) => {
|
|
116
118
|
if (message.trim()) {
|
|
117
|
-
onSendMessage(message.trim());
|
|
119
|
+
onSendMessage(message.trim(), attachmentIds);
|
|
118
120
|
}
|
|
119
121
|
},
|
|
120
122
|
[onSendMessage]
|
|
@@ -167,6 +169,7 @@ const HelpPopup = ({
|
|
|
167
169
|
/>
|
|
168
170
|
<ChatWindow
|
|
169
171
|
onSendMessage={handleSendMessage}
|
|
172
|
+
onEnsureSession={onEnsureSession}
|
|
170
173
|
messages={memoizedMessages}
|
|
171
174
|
assistantStatus={assistantStatus}
|
|
172
175
|
needsAgent={needsAgent}
|
|
@@ -188,11 +191,7 @@ const HelpPopup = ({
|
|
|
188
191
|
/>
|
|
189
192
|
);
|
|
190
193
|
}
|
|
191
|
-
return <HomeScreen
|
|
192
|
-
setIsShowList={setIsShowList}
|
|
193
|
-
onClose={onClose}
|
|
194
|
-
logoUrl={logoUrl}
|
|
195
|
-
/>;
|
|
194
|
+
return <HomeScreen setIsShowList={setIsShowList} onClose={onClose} logoUrl={logoUrl} />;
|
|
196
195
|
}, [
|
|
197
196
|
showChat,
|
|
198
197
|
selectedOption,
|
|
@@ -202,6 +201,7 @@ const HelpPopup = ({
|
|
|
202
201
|
isShowList,
|
|
203
202
|
setIsShowList,
|
|
204
203
|
handleSendMessage,
|
|
204
|
+
onEnsureSession,
|
|
205
205
|
memoizedMessages,
|
|
206
206
|
assistantStatus,
|
|
207
207
|
needsAgent,
|
|
@@ -210,6 +210,8 @@ const HelpPopup = ({
|
|
|
210
210
|
setExpandedOption,
|
|
211
211
|
handleStartChat,
|
|
212
212
|
showHelpScreen,
|
|
213
|
+
isAblyConnected,
|
|
214
|
+
logoUrl,
|
|
213
215
|
]);
|
|
214
216
|
|
|
215
217
|
// Memoize confirmation modal
|
|
@@ -311,6 +313,6 @@ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-roun
|
|
|
311
313
|
</div>
|
|
312
314
|
</div>
|
|
313
315
|
);
|
|
314
|
-
}
|
|
316
|
+
};
|
|
315
317
|
|
|
316
318
|
export default HelpPopup;
|
package/src/ui/powered-by.tsx
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { useLocalTranslation } from
|
|
1
|
+
import { useLocalTranslation } from '@/useLocalTranslation';
|
|
2
2
|
|
|
3
3
|
const PoweredBy: React.FC = () => {
|
|
4
4
|
const { t } = useLocalTranslation();
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
|
-
<section className=
|
|
7
|
+
<section className='dark:babylai-text-white babylai-font-bold babylai-text-center babylai-px-8 babylai-py-2 babylai-w-full'>
|
|
8
8
|
<a
|
|
9
|
-
href=
|
|
10
|
-
target=
|
|
11
|
-
rel=
|
|
12
|
-
className=
|
|
9
|
+
href='https://www.babylai.net/'
|
|
10
|
+
target='_blank'
|
|
11
|
+
rel='noopener noreferrer'
|
|
12
|
+
className='babylai-text-xs babylai-opacity-60 hover:babylai-opacity-100 babylai-transition-all'
|
|
13
|
+
>
|
|
13
14
|
{t('homeSdk.poweredBy')} BabylAI © 2025
|
|
14
15
|
</a>
|
|
15
16
|
</section>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
17
|
+
);
|
|
18
|
+
};
|
|
18
19
|
|
|
19
|
-
export default PoweredBy
|
|
20
|
+
export default PoweredBy;
|