@aslaluroba/help-center-react 3.2.3 → 3.2.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.css +1 -1
- package/dist/index.esm.js +95 -86
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +95 -86
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/api.ts +0 -1
- package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +107 -96
- package/src/ui/chatbot-popup/chat-window-screen/index.tsx +1 -1
package/package.json
CHANGED
package/src/core/api.ts
CHANGED
|
@@ -238,7 +238,6 @@ export async function presignUpload(
|
|
|
238
238
|
|
|
239
239
|
return await response.json();
|
|
240
240
|
}
|
|
241
|
-
|
|
242
241
|
export async function presignDownload(fileId: string, language: string): Promise<PresignDownloadResponse> {
|
|
243
242
|
try {
|
|
244
243
|
const response = await apiRequest(`NewFile/${fileId}/presign-download`, 'GET', null, {
|
|
@@ -30,6 +30,7 @@ const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
|
|
|
30
30
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
31
31
|
const [selectedFiles, setSelectedFiles] = useState<SelectedFileDto[]>([]);
|
|
32
32
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
|
33
|
+
const [isSending, setIsSending] = useState(false);
|
|
33
34
|
|
|
34
35
|
const handleAttachClick = useCallback(() => {
|
|
35
36
|
fileInputRef.current?.click();
|
|
@@ -74,119 +75,122 @@ const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
|
|
|
74
75
|
|
|
75
76
|
const handleSendMessageWithAttachments = useCallback(async () => {
|
|
76
77
|
// Prevent sending if already loading
|
|
77
|
-
if (props.isLoading) {
|
|
78
|
+
if (props.isLoading || isSending) {
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
setIsSending(true);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Get files that need to be uploaded (those without uploadedId)
|
|
86
|
+
const filesToUpload = selectedFiles.filter((f) => f.uploadedId === null && !f.error);
|
|
87
|
+
const alreadyUploadedIds = selectedFiles.filter((f) => f.uploadedId !== null).map((f) => f.uploadedId as string);
|
|
88
|
+
|
|
89
|
+
// Declare uploadedIds outside the if block so it's accessible later
|
|
90
|
+
let uploadedIds: string[] = [];
|
|
91
|
+
|
|
92
|
+
// If there are files to upload, upload them first
|
|
93
|
+
if (filesToUpload.length > 0) {
|
|
94
|
+
// Get session ID - ensure session exists if needed (for image-only messages)
|
|
95
|
+
let sessionId: string | null = null;
|
|
96
|
+
try {
|
|
97
|
+
// Use existing sessionId if available, otherwise ensure session is created
|
|
98
|
+
if (props.sessionId) {
|
|
99
|
+
sessionId = props.sessionId;
|
|
100
|
+
} else {
|
|
101
|
+
// Ensure session exists before uploading files (allows starting chat with image only)
|
|
102
|
+
sessionId = await props.onEnsureSession();
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
|
|
97
106
|
// Mark all files as error
|
|
98
107
|
setSelectedFiles((prev) =>
|
|
99
108
|
prev.map((f) =>
|
|
100
109
|
filesToUpload.some((ftl) => ftl.previewUrl === f.previewUrl)
|
|
101
|
-
? { ...f, error: '
|
|
110
|
+
? { ...f, error: 'Failed to initialize session', uploading: false }
|
|
102
111
|
: f
|
|
103
112
|
)
|
|
104
113
|
);
|
|
114
|
+
setIsSending(false);
|
|
105
115
|
return;
|
|
106
116
|
}
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
|
|
109
|
-
// Mark all files as error
|
|
110
|
-
setSelectedFiles((prev) =>
|
|
111
|
-
prev.map((f) =>
|
|
112
|
-
filesToUpload.some((ftl) => ftl.previewUrl === f.previewUrl)
|
|
113
|
-
? { ...f, error: 'Failed to initialize session', uploading: false }
|
|
114
|
-
: f
|
|
115
|
-
)
|
|
116
|
-
);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Upload each file and collect uploaded IDs
|
|
121
|
-
uploadedIds = [];
|
|
122
|
-
let hasUploadErrors = false;
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
118
|
+
// Upload each file and collect uploaded IDs
|
|
119
|
+
uploadedIds = [];
|
|
120
|
+
let hasUploadErrors = false;
|
|
121
|
+
|
|
122
|
+
for (const fileDto of filesToUpload) {
|
|
123
|
+
try {
|
|
124
|
+
// Mark as uploading
|
|
125
|
+
setSelectedFiles((prev) =>
|
|
126
|
+
prev.map((f) => (f.previewUrl === fileDto.previewUrl ? { ...f, uploading: true, error: null } : f))
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Get presigned URL
|
|
130
|
+
const presignResponse = await presignUpload(sessionId, fileDto.file, i18n.language);
|
|
131
|
+
|
|
132
|
+
// Upload file to presigned URL using axios
|
|
133
|
+
const uploadResponse = await axios.put(presignResponse.uploadUrl, fileDto.file, {
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': fileDto.file.type,
|
|
136
|
+
},
|
|
137
|
+
onUploadProgress: () => {
|
|
138
|
+
// Upload progress tracking (silent)
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
|
|
143
|
+
throw new Error(`Upload failed with status ${uploadResponse.status}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Collect uploaded ID
|
|
147
|
+
uploadedIds.push(presignResponse.id);
|
|
148
|
+
|
|
149
|
+
// Update with uploaded ID
|
|
150
|
+
setSelectedFiles((prev) =>
|
|
151
|
+
prev.map((f) =>
|
|
152
|
+
f.previewUrl === fileDto.previewUrl
|
|
153
|
+
? { ...f, uploading: false, uploadedId: presignResponse.id, error: null }
|
|
154
|
+
: f
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('[ChatWindowFooter] File upload failed:', error);
|
|
159
|
+
hasUploadErrors = true;
|
|
160
|
+
setSelectedFiles((prev) =>
|
|
161
|
+
prev.map((f) =>
|
|
162
|
+
f.previewUrl === fileDto.previewUrl
|
|
163
|
+
? { ...f, uploading: false, error: 'Upload failed', uploadedId: null }
|
|
164
|
+
: f
|
|
165
|
+
)
|
|
166
|
+
);
|
|
146
167
|
}
|
|
147
|
-
|
|
148
|
-
// Collect uploaded ID
|
|
149
|
-
uploadedIds.push(presignResponse.id);
|
|
150
|
-
|
|
151
|
-
// Update with uploaded ID
|
|
152
|
-
setSelectedFiles((prev) =>
|
|
153
|
-
prev.map((f) =>
|
|
154
|
-
f.previewUrl === fileDto.previewUrl
|
|
155
|
-
? { ...f, uploading: false, uploadedId: presignResponse.id, error: null }
|
|
156
|
-
: f
|
|
157
|
-
)
|
|
158
|
-
);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.error('[ChatWindowFooter] File upload failed:', error);
|
|
161
|
-
hasUploadErrors = true;
|
|
162
|
-
setSelectedFiles((prev) =>
|
|
163
|
-
prev.map((f) =>
|
|
164
|
-
f.previewUrl === fileDto.previewUrl
|
|
165
|
-
? { ...f, uploading: false, error: 'Upload failed', uploadedId: null }
|
|
166
|
-
: f
|
|
167
|
-
)
|
|
168
|
-
);
|
|
169
168
|
}
|
|
170
|
-
}
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
// If any uploads failed, don't send the message
|
|
171
|
+
if (hasUploadErrors) {
|
|
172
|
+
console.error('[ChatWindowFooter] Some files failed to upload, not sending message');
|
|
173
|
+
setIsSending(false);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
176
|
}
|
|
177
|
-
}
|
|
178
177
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
// Get all successfully uploaded file IDs (already uploaded + newly uploaded)
|
|
179
|
+
// Use uploadedIds from the upload loop instead of reading from state
|
|
180
|
+
const allAttachmentIds = [...alreadyUploadedIds, ...uploadedIds];
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
// Call the original send message with attachment IDs
|
|
183
|
+
props.handleSendMessage(allAttachmentIds);
|
|
185
184
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
// Clear selected files and revoke URLs
|
|
186
|
+
selectedFiles.forEach((f) => URL.revokeObjectURL(f.previewUrl));
|
|
187
|
+
setSelectedFiles([]);
|
|
188
|
+
setIsSending(false);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('[ChatWindowFooter] Error sending message:', error);
|
|
191
|
+
setIsSending(false);
|
|
192
|
+
}
|
|
193
|
+
}, [selectedFiles, props, i18n.language, isSending]);
|
|
190
194
|
|
|
191
195
|
// Check if any files are currently uploading
|
|
192
196
|
const hasUploadingFiles = selectedFiles.some((f) => f.uploading);
|
|
@@ -196,7 +200,8 @@ const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
|
|
|
196
200
|
|
|
197
201
|
// Allow sending if there's text OR files selected (files will be uploaded on send)
|
|
198
202
|
const hasContentToSend = props.inputMessage.trim() !== '' || selectedFiles.length > 0;
|
|
199
|
-
const isSendDisabled = props.isLoading || !hasContentToSend || hasUploadingFiles || hasFileErrors;
|
|
203
|
+
const isSendDisabled = props.isLoading || isSending || !hasContentToSend || hasUploadingFiles || hasFileErrors;
|
|
204
|
+
const showLoading = props.isLoading || isSending || hasUploadingFiles;
|
|
200
205
|
|
|
201
206
|
const handleKeyDown = useCallback(
|
|
202
207
|
(e: React.KeyboardEvent) => {
|
|
@@ -279,10 +284,16 @@ const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
|
|
|
279
284
|
size='icon'
|
|
280
285
|
onClick={handleSendMessageWithAttachments}
|
|
281
286
|
disabled={isSendDisabled}
|
|
282
|
-
className='babylai-rounded-full babylai-bg-primary-500 babylai-hover:babylai-bg-purple-600 babylai-w-8 babylai-h-8 disabled:babylai-opacity-50'
|
|
287
|
+
className='babylai-rounded-full babylai-bg-primary-500 babylai-hover:babylai-bg-purple-600 babylai-w-8 babylai-h-8 !babylai-p-0 babylai-flex babylai-items-center babylai-justify-center disabled:babylai-opacity-50'
|
|
283
288
|
type='button'
|
|
284
289
|
>
|
|
285
|
-
|
|
290
|
+
{showLoading ? (
|
|
291
|
+
<div className='babylai-inline-block babylai-animate-spin babylai-rounded-full babylai-h-4 babylai-w-4 babylai-aspect-square babylai-border-2 babylai-border-white babylai-border-t-transparent babylai-box-border'></div>
|
|
292
|
+
) : (
|
|
293
|
+
<EnvelopeIcon
|
|
294
|
+
className={`babylai-w-4 babylai-h-4 babylai-flex-shrink-0 ${dir === 'rtl' ? 'babylai-rotate-270' : ''}`}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
286
297
|
</Button>
|
|
287
298
|
</div>
|
|
288
299
|
|
|
@@ -44,7 +44,7 @@ const MessageComponent = React.memo(
|
|
|
44
44
|
const handleImageClick = useCallback(
|
|
45
45
|
(clickedIndex: number) => {
|
|
46
46
|
// Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
|
|
47
|
-
const attachments = message.attachmentUrls ||
|
|
47
|
+
const attachments = message.attachmentUrls || [];
|
|
48
48
|
if (attachments.length > 0) {
|
|
49
49
|
onImageClick(attachments, clickedIndex);
|
|
50
50
|
}
|