@aslaluroba/help-center-react 3.2.1 → 3.2.3
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 +2 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +295 -125
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +295 -125
- package/dist/index.js.map +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/services.esm.js +71 -14
- package/dist/services.esm.js.map +1 -1
- package/dist/services.js +71 -14
- package/dist/services.js.map +1 -1
- package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +1 -0
- package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/ui/agent-response/agent-response.tsx +5 -3
- package/src/components/ui/image-attachment.tsx +29 -17
- package/src/components/ui/image-preview-dialog.tsx +46 -0
- package/src/core/AblyService.ts +59 -12
- package/src/lib/custom-hooks/useTypewriter.ts +5 -3
- package/src/lib/types.ts +2 -1
- package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +71 -44
- package/src/ui/chatbot-popup/chat-window-screen/index.tsx +69 -18
- package/src/ui/help-center.tsx +13 -8
- package/src/ui/help-popup.tsx +3 -1
|
@@ -17,6 +17,7 @@ interface ChatWindowProps {
|
|
|
17
17
|
assistantStatus: string;
|
|
18
18
|
needsAgent: boolean;
|
|
19
19
|
isAblyConnected: boolean;
|
|
20
|
+
sessionId?: string | null;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
// Memoize individual message component to prevent unnecessary re-renders
|
|
@@ -34,7 +35,7 @@ const MessageComponent = React.memo(
|
|
|
34
35
|
messages: Message[];
|
|
35
36
|
firstHumanAgentIndex: number;
|
|
36
37
|
onType: () => void;
|
|
37
|
-
onImageClick: (
|
|
38
|
+
onImageClick: (attachmentIdsOrUrls: string[], clickedIndex: number) => void;
|
|
38
39
|
}) => {
|
|
39
40
|
const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType;
|
|
40
41
|
const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
|
|
@@ -42,11 +43,13 @@ const MessageComponent = React.memo(
|
|
|
42
43
|
|
|
43
44
|
const handleImageClick = useCallback(
|
|
44
45
|
(clickedIndex: number) => {
|
|
45
|
-
if (
|
|
46
|
-
|
|
46
|
+
// Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
|
|
47
|
+
const attachments = message.attachmentUrls || message.attachmentIds || [];
|
|
48
|
+
if (attachments.length > 0) {
|
|
49
|
+
onImageClick(attachments, clickedIndex);
|
|
47
50
|
}
|
|
48
51
|
},
|
|
49
|
-
[message.attachmentIds, onImageClick]
|
|
52
|
+
[message.attachmentIds, message.attachmentUrls, onImageClick]
|
|
50
53
|
);
|
|
51
54
|
|
|
52
55
|
return (
|
|
@@ -75,6 +78,20 @@ const MessageComponent = React.memo(
|
|
|
75
78
|
{!isFirstInSequence && <div className='babylai-flex-shrink-0 babylai-me-3 babylai-w-8'></div>}
|
|
76
79
|
|
|
77
80
|
<div className='babylai-flex babylai-flex-col babylai-gap-2'>
|
|
81
|
+
{/* Display attachment URLs (from Ably) */}
|
|
82
|
+
{message.attachmentUrls && message.attachmentUrls.length > 0 && (
|
|
83
|
+
<div className='babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full'>
|
|
84
|
+
{message.attachmentUrls.map((attachmentUrl, imgIndex) => (
|
|
85
|
+
<ImageAttachment
|
|
86
|
+
key={attachmentUrl}
|
|
87
|
+
imageUrl={attachmentUrl}
|
|
88
|
+
enablePreview={false}
|
|
89
|
+
onClick={() => handleImageClick(imgIndex)}
|
|
90
|
+
/>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
{/* Display attachment IDs (user-sent messages) */}
|
|
78
95
|
{message.attachmentIds && message.attachmentIds.length > 0 && (
|
|
79
96
|
<div className='babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full'>
|
|
80
97
|
{message.attachmentIds.map((attachmentId, imgIndex) => (
|
|
@@ -87,12 +104,15 @@ const MessageComponent = React.memo(
|
|
|
87
104
|
))}
|
|
88
105
|
</div>
|
|
89
106
|
)}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
{/* Only show chat bubble if there's message content */}
|
|
108
|
+
{message.messageContent && message.messageContent.trim() !== '' && (
|
|
109
|
+
<AgentResponse
|
|
110
|
+
messageContent={message.messageContent}
|
|
111
|
+
senderType={message.senderType}
|
|
112
|
+
messageId={message.id}
|
|
113
|
+
onType={onType}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
96
116
|
</div>
|
|
97
117
|
</div>
|
|
98
118
|
</div>
|
|
@@ -123,7 +143,14 @@ const TypingIndicator = React.memo(({ firstHumanAgentIndex }: { firstHumanAgentI
|
|
|
123
143
|
TypingIndicator.displayName = 'TypingIndicator';
|
|
124
144
|
|
|
125
145
|
export const ChatWindow = React.memo(
|
|
126
|
-
({
|
|
146
|
+
({
|
|
147
|
+
onSendMessage,
|
|
148
|
+
onEnsureSession,
|
|
149
|
+
messages,
|
|
150
|
+
assistantStatus = 'loading',
|
|
151
|
+
needsAgent,
|
|
152
|
+
sessionId,
|
|
153
|
+
}: ChatWindowProps) => {
|
|
127
154
|
const { i18n } = useLocalTranslation();
|
|
128
155
|
const [inputMessage, setInputMessage] = useState('');
|
|
129
156
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
@@ -172,7 +199,8 @@ export const ChatWindow = React.memo(
|
|
|
172
199
|
|
|
173
200
|
const handleSendMessage = useCallback(
|
|
174
201
|
(attachmentIds: string[]) => {
|
|
175
|
-
if
|
|
202
|
+
// Allow sending if there's text OR attachments
|
|
203
|
+
if (inputMessage.trim() || attachmentIds.length > 0) {
|
|
176
204
|
onSendMessage(inputMessage, attachmentIds);
|
|
177
205
|
setInputMessage('');
|
|
178
206
|
}
|
|
@@ -187,14 +215,36 @@ export const ChatWindow = React.memo(
|
|
|
187
215
|
|
|
188
216
|
// Handle image gallery opening
|
|
189
217
|
const handleImageClick = useCallback(
|
|
190
|
-
async (
|
|
191
|
-
if (!
|
|
218
|
+
async (attachmentIdsOrUrls: string[], clickedIndex: number) => {
|
|
219
|
+
if (!attachmentIdsOrUrls || attachmentIdsOrUrls.length === 0) {
|
|
192
220
|
return;
|
|
193
221
|
}
|
|
194
222
|
|
|
195
223
|
try {
|
|
196
|
-
//
|
|
197
|
-
|
|
224
|
+
// Check if the first item is a URL (starts with http:// or https://)
|
|
225
|
+
// If so, they're all URLs from Ably and can be used directly
|
|
226
|
+
const isUrl = attachmentIdsOrUrls[0]?.startsWith('http://') || attachmentIdsOrUrls[0]?.startsWith('https://');
|
|
227
|
+
|
|
228
|
+
let imageUrls: string[];
|
|
229
|
+
|
|
230
|
+
if (isUrl) {
|
|
231
|
+
// These are already URLs from Ably, use them directly (no async needed)
|
|
232
|
+
imageUrls = attachmentIdsOrUrls.filter((url): url is string => url !== null && url.length > 0);
|
|
233
|
+
|
|
234
|
+
// Open gallery immediately with URLs
|
|
235
|
+
if (imageUrls.length > 0) {
|
|
236
|
+
const adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
|
|
237
|
+
setGalleryState({
|
|
238
|
+
isOpen: true,
|
|
239
|
+
imageUrls,
|
|
240
|
+
initialIndex: adjustedIndex,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return; // Exit early since we don't need to fetch anything
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// These are file IDs, need to fetch URLs using presignDownload
|
|
247
|
+
const imageUrlPromises = attachmentIdsOrUrls.map((fileId) => {
|
|
198
248
|
if (!fileId || typeof fileId !== 'string') {
|
|
199
249
|
return Promise.resolve(null);
|
|
200
250
|
}
|
|
@@ -212,7 +262,7 @@ export const ChatWindow = React.memo(
|
|
|
212
262
|
});
|
|
213
263
|
});
|
|
214
264
|
|
|
215
|
-
|
|
265
|
+
imageUrls = (await Promise.all(imageUrlPromises)).filter(
|
|
216
266
|
(url): url is string => url !== null && url.length > 0
|
|
217
267
|
);
|
|
218
268
|
|
|
@@ -221,7 +271,7 @@ export const ChatWindow = React.memo(
|
|
|
221
271
|
}
|
|
222
272
|
|
|
223
273
|
// Adjust the initial index if some images failed to load
|
|
224
|
-
|
|
274
|
+
const adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
|
|
225
275
|
|
|
226
276
|
setGalleryState({
|
|
227
277
|
isOpen: true,
|
|
@@ -288,6 +338,7 @@ export const ChatWindow = React.memo(
|
|
|
288
338
|
setInputMessage={setInputMessage}
|
|
289
339
|
isLoading={isLoading}
|
|
290
340
|
onEnsureSession={onEnsureSession}
|
|
341
|
+
sessionId={sessionId}
|
|
291
342
|
/>
|
|
292
343
|
|
|
293
344
|
{/* Gallery Preview Dialog */}
|
package/src/ui/help-center.tsx
CHANGED
|
@@ -83,7 +83,8 @@ const HelpCenterContent = ({
|
|
|
83
83
|
messageContent: message,
|
|
84
84
|
sentAt: new Date(),
|
|
85
85
|
isSeen: true,
|
|
86
|
-
|
|
86
|
+
// Ably now returns downloadUrls directly, not file IDs
|
|
87
|
+
...(attachments.length > 0 && { attachmentUrls: attachments }),
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
return [...prevMessages, newMessage];
|
|
@@ -216,11 +217,12 @@ const HelpCenterContent = ({
|
|
|
216
217
|
|
|
217
218
|
const handleEnsureSession = async (): Promise<string> => {
|
|
218
219
|
// If we already have a session ID and connection, return it
|
|
219
|
-
if
|
|
220
|
+
// NEVER create a new session if one already exists
|
|
221
|
+
if (sessionId) {
|
|
220
222
|
return sessionId;
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
//
|
|
225
|
+
// Only create a new session if we don't have one and have a selected option
|
|
224
226
|
if (selectedOption) {
|
|
225
227
|
const newSessionId = await startNewChatSession(selectedOption);
|
|
226
228
|
return newSessionId;
|
|
@@ -230,14 +232,15 @@ const HelpCenterContent = ({
|
|
|
230
232
|
};
|
|
231
233
|
|
|
232
234
|
const handleSendMessage = async (message: string, attachmentIds: string[] = []) => {
|
|
233
|
-
|
|
235
|
+
// Allow sending if there's text OR attachments
|
|
236
|
+
if (message.trim() !== '' || attachmentIds.length > 0) {
|
|
234
237
|
try {
|
|
235
238
|
setAssistantStatus('typing');
|
|
236
239
|
|
|
237
240
|
const userMessage: Message = {
|
|
238
241
|
id: Date.now(),
|
|
239
242
|
senderType: 1,
|
|
240
|
-
messageContent: message,
|
|
243
|
+
messageContent: message || '', // Use empty string if message is empty but attachments exist
|
|
241
244
|
sentAt: new Date(),
|
|
242
245
|
isSeen: false,
|
|
243
246
|
attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined,
|
|
@@ -245,10 +248,12 @@ const HelpCenterContent = ({
|
|
|
245
248
|
|
|
246
249
|
setMessages((prevMessages) => [...prevMessages, userMessage]);
|
|
247
250
|
|
|
248
|
-
// Handle session creation if needed
|
|
251
|
+
// Handle session creation if needed - only create if no session exists
|
|
249
252
|
let currentSessionId = sessionId;
|
|
250
253
|
|
|
251
|
-
if
|
|
254
|
+
// Only create a new session if we don't have one and we have a selected option
|
|
255
|
+
// This ensures session is only created once with the first message
|
|
256
|
+
if (!currentSessionId && !isAblyConnected && selectedOption) {
|
|
252
257
|
currentSessionId = await startNewChatSession(selectedOption);
|
|
253
258
|
}
|
|
254
259
|
|
|
@@ -257,7 +262,7 @@ const HelpCenterContent = ({
|
|
|
257
262
|
}
|
|
258
263
|
|
|
259
264
|
const messageDto = {
|
|
260
|
-
messageContent: message,
|
|
265
|
+
messageContent: message || '', // Use empty string if message is empty but attachments exist
|
|
261
266
|
...(attachmentIds.length > 0 && { attachmentIds }),
|
|
262
267
|
};
|
|
263
268
|
|
package/src/ui/help-popup.tsx
CHANGED
|
@@ -115,7 +115,8 @@ const HelpPopup = ({
|
|
|
115
115
|
|
|
116
116
|
const handleSendMessage = useCallback(
|
|
117
117
|
(message: string, attachmentIds: string[]) => {
|
|
118
|
-
if
|
|
118
|
+
// Allow sending if there's text OR attachments
|
|
119
|
+
if (message.trim() || attachmentIds.length > 0) {
|
|
119
120
|
onSendMessage(message.trim(), attachmentIds);
|
|
120
121
|
}
|
|
121
122
|
},
|
|
@@ -174,6 +175,7 @@ const HelpPopup = ({
|
|
|
174
175
|
assistantStatus={assistantStatus}
|
|
175
176
|
needsAgent={needsAgent}
|
|
176
177
|
isAblyConnected={isAblyConnected}
|
|
178
|
+
sessionId={sessionId}
|
|
177
179
|
/>
|
|
178
180
|
</>
|
|
179
181
|
);
|