@aslaluroba/help-center-react 3.2.1 → 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.esm.js CHANGED
@@ -16245,10 +16245,19 @@ class ClientAblyService {
16245
16245
  token: ablyToken,
16246
16246
  autoConnect: true
16247
16247
  });
16248
+ _this.client.connection.on('failed', stateChange => {
16249
+ var _a;
16250
+ console.error('[AblyService] Connection state: failed', {
16251
+ reason: (_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message,
16252
+ error: stateChange.reason
16253
+ });
16254
+ });
16248
16255
  // Wait for connection to be established
16249
16256
  yield new Promise((resolve, reject) => {
16250
16257
  if (!_this.client) {
16251
- reject(new Error('Failed to initialize Ably client'));
16258
+ var error = new Error('Failed to initialize Ably client');
16259
+ console.error('[AblyService]', error);
16260
+ reject(error);
16252
16261
  return;
16253
16262
  }
16254
16263
  _this.client.connection.once('connected', () => {
@@ -16257,23 +16266,38 @@ class ClientAblyService {
16257
16266
  resolve();
16258
16267
  });
16259
16268
  _this.client.connection.once('failed', stateChange => {
16260
- var _a;
16261
- reject(new Error("Ably connection failed: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error')));
16269
+ var _a, _b;
16270
+ var error = new Error("Ably connection failed: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error'));
16271
+ console.error('[AblyService] Connection failed', {
16272
+ reason: (_b = stateChange.reason) === null || _b === void 0 ? void 0 : _b.message,
16273
+ error: stateChange.reason
16274
+ });
16275
+ reject(error);
16262
16276
  });
16263
16277
  _this.client.connection.once('disconnected', stateChange => {
16264
- var _a;
16265
- reject(new Error("Ably connection disconnected: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error')));
16278
+ var _a, _b;
16279
+ var error = new Error("Ably connection disconnected: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error'));
16280
+ console.error('[AblyService] Connection disconnected', {
16281
+ reason: (_b = stateChange.reason) === null || _b === void 0 ? void 0 : _b.message
16282
+ });
16283
+ reject(error);
16266
16284
  });
16267
16285
  // Set a timeout for connection
16268
16286
  setTimeout(() => {
16269
16287
  if (!_this.isConnected) {
16270
- reject(new Error('Ably connection timeout'));
16288
+ var _error = new Error('Ably connection timeout');
16289
+ console.error('[AblyService] Connection timeout after 10 seconds');
16290
+ reject(_error);
16271
16291
  }
16272
16292
  }, 10000);
16273
16293
  });
16274
16294
  // Subscribe to the session room
16275
16295
  yield _this.joinChannel(sessionId, onMessageReceived, tenantId);
16276
16296
  } catch (error) {
16297
+ console.error('[AblyService] Error in startConnection', {
16298
+ error,
16299
+ sessionId
16300
+ });
16277
16301
  _this.isConnected = false;
16278
16302
  _this.sessionId = null;
16279
16303
  throw error;
@@ -16284,23 +16308,53 @@ class ClientAblyService {
16284
16308
  var _this2 = this;
16285
16309
  return _asyncToGenerator(function* () {
16286
16310
  if (!_this2.client) {
16287
- throw new Error('Chat client not initialized');
16311
+ var error = new Error('Chat client not initialized');
16312
+ console.error('[AblyService] joinChannel error:', error);
16313
+ throw error;
16288
16314
  }
16289
16315
  var roomName = "session:".concat(tenantId, ":").concat(sessionId);
16290
16316
  // Set up raw channel subscription for server messages
16291
16317
  if (_this2.client) {
16292
16318
  _this2.channel = _this2.client.channels.get(roomName);
16319
+ _this2.channel.on('failed', stateChange => {
16320
+ var _a;
16321
+ console.error('[AblyService] Channel failed', {
16322
+ roomName,
16323
+ reason: (_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message,
16324
+ error: stateChange.reason
16325
+ });
16326
+ });
16293
16327
  // Subscribe to assistant/system responses
16294
16328
  _this2.channel.subscribe('ReceiveMessage', message => {
16295
- var _a, _b, _c, _d, _e, _f;
16329
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16296
16330
  try {
16297
- var messageContent = typeof message.data === 'string' ? message.data : ((_a = message.data) === null || _a === void 0 ? void 0 : _a.content) || ((_b = message.data) === null || _b === void 0 ? void 0 : _b.message);
16298
- var senderType = ((_c = message.data) === null || _c === void 0 ? void 0 : _c.senderType) || 3; // Assistant
16299
- var needsAgent = ((_d = message.data) === null || _d === void 0 ? void 0 : _d.needsAgent) || ((_e = message.data) === null || _e === void 0 ? void 0 : _e.actionType) == "needs_agent" || false;
16300
- var attachments = ((_f = message.data) === null || _f === void 0 ? void 0 : _f.attachments) || [];
16301
- onMessageReceived(messageContent, senderType, needsAgent, attachments);
16331
+ // Ensure messageContent is always a string (default to empty string if undefined)
16332
+ var messageContent = typeof message.data === 'string' ? message.data : (_d = (_b = (_a = message.data) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : (_c = message.data) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : '';
16333
+ var senderType = ((_e = message.data) === null || _e === void 0 ? void 0 : _e.senderType) || 3; // Assistant
16334
+ var needsAgent = ((_f = message.data) === null || _f === void 0 ? void 0 : _f.needsAgent) || ((_g = message.data) === null || _g === void 0 ? void 0 : _g.actionType) == 'needs_agent' || false;
16335
+ var attachments = ((_h = message.data) === null || _h === void 0 ? void 0 : _h.attachments) || [];
16336
+ // Extract downloadUrl from attachments (Ably now returns downloadUrl directly)
16337
+ // Attachments can be: strings (URLs), objects with downloadUrl, or objects with id
16338
+ var attachmentUrls = attachments.map(attachment => {
16339
+ if (typeof attachment === 'string') {
16340
+ // If it's already a string, it's a URL
16341
+ return attachment;
16342
+ } else if (attachment === null || attachment === void 0 ? void 0 : attachment.downloadUrl) {
16343
+ // If it's an object with downloadUrl, use that
16344
+ return attachment.downloadUrl;
16345
+ } else if (attachment === null || attachment === void 0 ? void 0 : attachment.url) {
16346
+ // Fallback to url property
16347
+ return attachment.url;
16348
+ }
16349
+ // If it's an object with id, we'll need to keep it for backward compatibility
16350
+ return null;
16351
+ }).filter(url => url !== null);
16352
+ onMessageReceived(messageContent, senderType, needsAgent, attachmentUrls);
16302
16353
  } catch (error) {
16303
- // Handle error silently
16354
+ console.error('[AblyService] Error processing message', {
16355
+ error,
16356
+ message
16357
+ });
16304
16358
  }
16305
16359
  });
16306
16360
  yield _this2.channel.attach();
@@ -16330,6 +16384,9 @@ class ClientAblyService {
16330
16384
  _this3.isConnected = false;
16331
16385
  _this3.sessionId = null;
16332
16386
  } catch (error) {
16387
+ console.error('[AblyService] Error in stopConnection', {
16388
+ error
16389
+ });
16333
16390
  // Reset state even if there's an error
16334
16391
  _this3.isConnected = false;
16335
16392
  _this3.sessionId = null;
@@ -16482,19 +16539,21 @@ function useTypewriter(text) {
16482
16539
  var onType = arguments.length > 2 ? arguments[2] : undefined;
16483
16540
  var [displayedText, setDisplayedText] = useState('');
16484
16541
  useEffect(() => {
16542
+ // Ensure text is always a string to prevent errors
16543
+ var safeText = text !== null && text !== void 0 ? text : '';
16485
16544
  var index = 0;
16486
16545
  setDisplayedText('');
16487
16546
  var interval = setInterval(() => {
16488
16547
  setDisplayedText(() => {
16489
- var next = text.slice(0, index + 1);
16548
+ var next = safeText.slice(0, index + 1);
16490
16549
  index++;
16491
16550
  if (onType) onType();
16492
- if (index >= text.length) clearInterval(interval);
16551
+ if (index >= safeText.length) clearInterval(interval);
16493
16552
  return next;
16494
16553
  });
16495
16554
  }, speed);
16496
16555
  return () => clearInterval(interval);
16497
- }, [text]);
16556
+ }, [text, onType]);
16498
16557
  return displayedText;
16499
16558
  }
16500
16559
 
@@ -34281,11 +34340,13 @@ var AgentResponse = _ref => {
34281
34340
  messageId,
34282
34341
  onType
34283
34342
  } = _ref;
34343
+ // Ensure messageContent is always a string to prevent errors
34344
+ var safeMessageContent = messageContent !== null && messageContent !== void 0 ? messageContent : '';
34284
34345
  var shouldAnimate = (senderType === 2 || senderType === 3) && !seenMessagesRef.has(messageId);
34285
- var animatedText = useTypewriter(messageContent, 20, onType);
34286
- var finalMessage = shouldAnimate ? animatedText : messageContent;
34346
+ var animatedText = useTypewriter(safeMessageContent, 20, onType);
34347
+ var finalMessage = shouldAnimate ? animatedText : safeMessageContent;
34287
34348
  // Mark message as "seen" after full animation
34288
- if (shouldAnimate && finalMessage === messageContent) {
34349
+ if (shouldAnimate && finalMessage === safeMessageContent) {
34289
34350
  seenMessagesRef.add(messageId);
34290
34351
  }
34291
34352
  return jsx("div", {
@@ -34435,6 +34496,32 @@ var ImagePreviewDialog = _ref => {
34435
34496
  y: 0
34436
34497
  });
34437
34498
  }, []);
34499
+ var handleDownload = useCallback(/*#__PURE__*/_asyncToGenerator(function* () {
34500
+ if (!currentImageUrl) return;
34501
+ try {
34502
+ // Fetch the image as a blob
34503
+ var response = yield fetch(currentImageUrl);
34504
+ var blob = yield response.blob();
34505
+ // Create a temporary URL for the blob
34506
+ var blobUrl = URL.createObjectURL(blob);
34507
+ // Extract filename from URL or use a default
34508
+ var urlParts = currentImageUrl.split('/');
34509
+ var filename = urlParts[urlParts.length - 1].split('?')[0] || 'image.png';
34510
+ // Create a temporary anchor element and trigger download
34511
+ var link = document.createElement('a');
34512
+ link.href = blobUrl;
34513
+ link.download = filename;
34514
+ document.body.appendChild(link);
34515
+ link.click();
34516
+ // Cleanup
34517
+ document.body.removeChild(link);
34518
+ URL.revokeObjectURL(blobUrl);
34519
+ } catch (error) {
34520
+ console.error('Failed to download image:', error);
34521
+ // Fallback: open in new tab if download fails
34522
+ window.open(currentImageUrl, '_blank');
34523
+ }
34524
+ }), [currentImageUrl]);
34438
34525
  var handleClose = useCallback(() => {
34439
34526
  setZoomLevel(1);
34440
34527
  setImagePosition({
@@ -34635,6 +34722,27 @@ var ImagePreviewDialog = _ref => {
34635
34722
  "aria-label": 'Reset zoom',
34636
34723
  type: 'button',
34637
34724
  children: "Reset"
34725
+ }), jsx("div", {
34726
+ className: 'babylai-h-9 babylai-w-px babylai-bg-white/20 babylai-mx-1'
34727
+ }), jsx(Button, {
34728
+ variant: 'ghost',
34729
+ size: 'icon',
34730
+ onClick: handleDownload,
34731
+ className: 'babylai-text-white hover:babylai-text-white/80 hover:babylai-bg-white/10 babylai-h-9 babylai-w-9',
34732
+ "aria-label": 'Download image',
34733
+ type: 'button',
34734
+ children: jsx("svg", {
34735
+ className: 'babylai-w-5 babylai-h-5',
34736
+ fill: 'none',
34737
+ stroke: 'currentColor',
34738
+ viewBox: '0 0 24 24',
34739
+ children: jsx("path", {
34740
+ strokeLinecap: 'round',
34741
+ strokeLinejoin: 'round',
34742
+ strokeWidth: 2,
34743
+ d: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4'
34744
+ })
34745
+ })
34638
34746
  })]
34639
34747
  }), hasMultipleImages && jsx("div", {
34640
34748
  className: cn('babylai-absolute babylai-top-4 babylai-z-[60]', 'babylai-bg-black/50 babylai-backdrop-blur-sm babylai-rounded-lg babylai-px-4 babylai-py-2', dir === 'rtl' ? 'babylai-right-1/2 babylai-translate-x-1/2' : 'babylai-left-1/2 -babylai-translate-x-1/2'),
@@ -34668,6 +34776,7 @@ ImagePreviewDialog.displayName = 'ImagePreviewDialog';
34668
34776
  var ImageAttachment = _ref => {
34669
34777
  var {
34670
34778
  fileId,
34779
+ imageUrl: propImageUrl,
34671
34780
  className,
34672
34781
  enablePreview = true,
34673
34782
  onClick
@@ -34675,30 +34784,39 @@ var ImageAttachment = _ref => {
34675
34784
  var {
34676
34785
  i18n
34677
34786
  } = useLocalTranslation();
34678
- var [imageUrl, setImageUrl] = useState(null);
34679
- var [loading, setLoading] = useState(true);
34787
+ var [imageUrl, setImageUrl] = useState(propImageUrl || null);
34788
+ var [loading, setLoading] = useState(!propImageUrl && !!fileId);
34680
34789
  var [error, setError] = useState(false);
34681
34790
  var [isPreviewOpen, setIsPreviewOpen] = useState(false);
34682
34791
  useEffect(() => {
34683
- var fetchImageUrl = /*#__PURE__*/function () {
34684
- var _ref2 = _asyncToGenerator(function* () {
34685
- try {
34686
- setLoading(true);
34687
- setError(false);
34688
- var response = yield presignDownload(fileId, i18n.language);
34689
- setImageUrl(response.downloadUrl);
34690
- } catch (err) {
34691
- setError(true);
34692
- } finally {
34693
- setLoading(false);
34694
- }
34695
- });
34696
- return function fetchImageUrl() {
34697
- return _ref2.apply(this, arguments);
34698
- };
34699
- }();
34700
- fetchImageUrl();
34701
- }, [fileId, i18n.language]);
34792
+ // If we have a direct URL, use it immediately
34793
+ if (propImageUrl) {
34794
+ setImageUrl(propImageUrl);
34795
+ setLoading(false);
34796
+ return;
34797
+ }
34798
+ // If we only have a fileId, fetch the URL using presignDownload
34799
+ if (fileId) {
34800
+ var fetchImageUrl = /*#__PURE__*/function () {
34801
+ var _ref2 = _asyncToGenerator(function* () {
34802
+ try {
34803
+ setLoading(true);
34804
+ setError(false);
34805
+ var response = yield presignDownload(fileId, i18n.language);
34806
+ setImageUrl(response.downloadUrl);
34807
+ } catch (err) {
34808
+ setError(true);
34809
+ } finally {
34810
+ setLoading(false);
34811
+ }
34812
+ });
34813
+ return function fetchImageUrl() {
34814
+ return _ref2.apply(this, arguments);
34815
+ };
34816
+ }();
34817
+ fetchImageUrl();
34818
+ }
34819
+ }, [fileId, propImageUrl, i18n.language]);
34702
34820
  var handleImageClick = () => {
34703
34821
  if (onClick) {
34704
34822
  onClick();
@@ -38246,95 +38364,31 @@ var ChatWindowFooter = props => {
38246
38364
  var fileInputRef = useRef(null);
38247
38365
  var [selectedFiles, setSelectedFiles] = useState([]);
38248
38366
  var [previewImage, setPreviewImage] = useState(null);
38367
+ var [isSending, setIsSending] = useState(false);
38249
38368
  var handleAttachClick = useCallback(() => {
38250
38369
  var _a;
38251
38370
  (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
38252
38371
  }, []);
38253
- var handleFileSelect = useCallback(/*#__PURE__*/function () {
38254
- var _ref = _asyncToGenerator(function* (e) {
38255
- var files = Array.from(e.target.files || []);
38256
- // Validate that all files are images
38257
- var imageFiles = files.filter(file => file.type.startsWith('image/'));
38258
- // Only image files are allowed
38259
- // Create preview URLs and add to selected files
38260
- var newFiles = imageFiles.map(file => ({
38261
- file,
38262
- previewUrl: URL.createObjectURL(file),
38263
- uploading: false,
38264
- uploadedId: null,
38265
- error: null
38266
- }));
38267
- setSelectedFiles(prev => [...prev, ...newFiles]);
38268
- // Clear the input
38269
- if (fileInputRef.current) {
38270
- fileInputRef.current.value = '';
38271
- }
38272
- // Start uploading immediately
38273
- yield handleUploadFiles(newFiles);
38274
- });
38275
- return function (_x) {
38276
- return _ref.apply(this, arguments);
38277
- };
38278
- }(), []);
38279
- var handleUploadFiles = useCallback(/*#__PURE__*/function () {
38280
- var _ref2 = _asyncToGenerator(function* (filesToUpload) {
38281
- // Get session ID
38282
- var sessionId;
38283
- try {
38284
- sessionId = yield props.onEnsureSession();
38285
- } catch (error) {
38286
- // Mark all files as error
38287
- setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38288
- error: 'Failed to initialize session',
38289
- uploading: false
38290
- }) : f));
38291
- return;
38292
- }
38293
- // Upload each file
38294
- var _loop = function* _loop(fileDto) {
38295
- try {
38296
- // Mark as uploading
38297
- setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38298
- uploading: true,
38299
- error: null
38300
- }) : f));
38301
- // Get presigned URL
38302
- var presignResponse = yield presignUpload(sessionId, fileDto.file, i18n.language);
38303
- // Upload file to presigned URL using axios
38304
- // Important: Content-Type must match the file type (e.g., 'image/png'), not 'multipart/form-data'
38305
- var uploadResponse = yield axios$1.put(presignResponse.uploadUrl, fileDto.file, {
38306
- headers: {
38307
- 'Content-Type': fileDto.file.type
38308
- },
38309
- onUploadProgress: () => {
38310
- // Upload progress tracking (silent)
38311
- }
38312
- });
38313
- if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
38314
- throw new Error("Upload failed with status ".concat(uploadResponse.status));
38315
- }
38316
- // Update with uploaded ID
38317
- setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38318
- uploading: false,
38319
- uploadedId: presignResponse.id,
38320
- error: null
38321
- }) : f));
38322
- } catch (error) {
38323
- setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38324
- uploading: false,
38325
- error: 'Upload failed',
38326
- uploadedId: null
38327
- }) : f));
38328
- }
38329
- };
38330
- for (var fileDto of filesToUpload) {
38331
- yield* _loop(fileDto);
38332
- }
38333
- });
38334
- return function (_x2) {
38335
- return _ref2.apply(this, arguments);
38336
- };
38337
- }(), [props.onEnsureSession, i18n.language]);
38372
+ var handleFileSelect = useCallback(e => {
38373
+ var files = Array.from(e.target.files || []);
38374
+ // Validate that all files are images
38375
+ var imageFiles = files.filter(file => file.type.startsWith('image/'));
38376
+ // Create preview URLs and add to selected files (don't upload yet)
38377
+ var newFiles = imageFiles.map(file => ({
38378
+ file,
38379
+ previewUrl: URL.createObjectURL(file),
38380
+ uploading: false,
38381
+ uploadedId: null,
38382
+ error: null
38383
+ }));
38384
+ setSelectedFiles(prev => [...prev, ...newFiles]);
38385
+ // Clear the input
38386
+ if (fileInputRef.current) {
38387
+ fileInputRef.current.value = '';
38388
+ }
38389
+ // Don't upload files immediately - wait for send button click
38390
+ }, []);
38391
+ // Removed handleUploadFiles - files are now uploaded in handleSendMessageWithAttachments
38338
38392
  var handleRemoveFile = useCallback(previewUrl => {
38339
38393
  setSelectedFiles(prev => {
38340
38394
  var fileToRemove = prev.find(f => f.previewUrl === previewUrl);
@@ -38344,29 +38398,114 @@ var ChatWindowFooter = props => {
38344
38398
  return prev.filter(f => f.previewUrl !== previewUrl);
38345
38399
  });
38346
38400
  }, []);
38347
- var handleSendMessageWithAttachments = useCallback(() => {
38348
- // Only allow sending if all files have finished uploading (either successfully or with error)
38349
- var hasUploadingFiles = selectedFiles.some(f => f.uploading);
38350
- if (hasUploadingFiles) {
38351
- return; // Prevent sending if any files are still uploading
38352
- }
38353
- // Get all successfully uploaded file IDs
38354
- var attachmentIds = selectedFiles.filter(f => f.uploadedId !== null).map(f => f.uploadedId);
38355
- // Call the original send message with attachment IDs
38356
- props.handleSendMessage(attachmentIds);
38357
- // Clear selected files and revoke URLs
38358
- selectedFiles.forEach(f => URL.revokeObjectURL(f.previewUrl));
38359
- setSelectedFiles([]);
38360
- }, [selectedFiles, props]);
38401
+ var handleSendMessageWithAttachments = useCallback(/*#__PURE__*/_asyncToGenerator(function* () {
38402
+ // Prevent sending if already loading
38403
+ if (props.isLoading || isSending) {
38404
+ return;
38405
+ }
38406
+ setIsSending(true);
38407
+ try {
38408
+ // Get files that need to be uploaded (those without uploadedId)
38409
+ var filesToUpload = selectedFiles.filter(f => f.uploadedId === null && !f.error);
38410
+ var alreadyUploadedIds = selectedFiles.filter(f => f.uploadedId !== null).map(f => f.uploadedId);
38411
+ // Declare uploadedIds outside the if block so it's accessible later
38412
+ var uploadedIds = [];
38413
+ // If there are files to upload, upload them first
38414
+ if (filesToUpload.length > 0) {
38415
+ // Get session ID - ensure session exists if needed (for image-only messages)
38416
+ var sessionId = null;
38417
+ try {
38418
+ // Use existing sessionId if available, otherwise ensure session is created
38419
+ if (props.sessionId) {
38420
+ sessionId = props.sessionId;
38421
+ } else {
38422
+ // Ensure session exists before uploading files (allows starting chat with image only)
38423
+ sessionId = yield props.onEnsureSession();
38424
+ }
38425
+ } catch (error) {
38426
+ console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
38427
+ // Mark all files as error
38428
+ setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38429
+ error: 'Failed to initialize session',
38430
+ uploading: false
38431
+ }) : f));
38432
+ setIsSending(false);
38433
+ return;
38434
+ }
38435
+ // Upload each file and collect uploaded IDs
38436
+ uploadedIds = [];
38437
+ var hasUploadErrors = false;
38438
+ var _loop = function* _loop(fileDto) {
38439
+ try {
38440
+ // Mark as uploading
38441
+ setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38442
+ uploading: true,
38443
+ error: null
38444
+ }) : f));
38445
+ // Get presigned URL
38446
+ var presignResponse = yield presignUpload(sessionId, fileDto.file, i18n.language);
38447
+ // Upload file to presigned URL using axios
38448
+ var uploadResponse = yield axios$1.put(presignResponse.uploadUrl, fileDto.file, {
38449
+ headers: {
38450
+ 'Content-Type': fileDto.file.type
38451
+ },
38452
+ onUploadProgress: () => {
38453
+ // Upload progress tracking (silent)
38454
+ }
38455
+ });
38456
+ if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
38457
+ throw new Error("Upload failed with status ".concat(uploadResponse.status));
38458
+ }
38459
+ // Collect uploaded ID
38460
+ uploadedIds.push(presignResponse.id);
38461
+ // Update with uploaded ID
38462
+ setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38463
+ uploading: false,
38464
+ uploadedId: presignResponse.id,
38465
+ error: null
38466
+ }) : f));
38467
+ } catch (error) {
38468
+ console.error('[ChatWindowFooter] File upload failed:', error);
38469
+ hasUploadErrors = true;
38470
+ setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38471
+ uploading: false,
38472
+ error: 'Upload failed',
38473
+ uploadedId: null
38474
+ }) : f));
38475
+ }
38476
+ };
38477
+ for (var fileDto of filesToUpload) {
38478
+ yield* _loop(fileDto);
38479
+ }
38480
+ // If any uploads failed, don't send the message
38481
+ if (hasUploadErrors) {
38482
+ console.error('[ChatWindowFooter] Some files failed to upload, not sending message');
38483
+ setIsSending(false);
38484
+ return;
38485
+ }
38486
+ }
38487
+ // Get all successfully uploaded file IDs (already uploaded + newly uploaded)
38488
+ // Use uploadedIds from the upload loop instead of reading from state
38489
+ var allAttachmentIds = [...alreadyUploadedIds, ...uploadedIds];
38490
+ // Call the original send message with attachment IDs
38491
+ props.handleSendMessage(allAttachmentIds);
38492
+ // Clear selected files and revoke URLs
38493
+ selectedFiles.forEach(f => URL.revokeObjectURL(f.previewUrl));
38494
+ setSelectedFiles([]);
38495
+ setIsSending(false);
38496
+ } catch (error) {
38497
+ console.error('[ChatWindowFooter] Error sending message:', error);
38498
+ setIsSending(false);
38499
+ }
38500
+ }), [selectedFiles, props, i18n.language, isSending]);
38361
38501
  // Check if any files are currently uploading
38362
38502
  var hasUploadingFiles = selectedFiles.some(f => f.uploading);
38363
- // Check if there are files that haven't finished (no uploadedId, no error, not uploading)
38364
- // This shouldn't happen in normal flow, but we check for safety
38365
- var hasPendingFiles = selectedFiles.some(f => !f.uploading && f.uploadedId === null && f.error === null);
38366
- // Check if all files have errors (no successful uploads)
38367
- var hasSuccessfulUploads = selectedFiles.some(f => f.uploadedId !== null);
38368
- var allFilesHaveErrors = selectedFiles.length > 0 && !hasSuccessfulUploads && !hasUploadingFiles && !hasPendingFiles;
38369
- var isSendDisabled = props.isLoading || props.inputMessage.trim() === '' || hasUploadingFiles || hasPendingFiles || allFilesHaveErrors;
38503
+ // Check if there are files with errors
38504
+ var hasFileErrors = selectedFiles.some(f => f.error !== null);
38505
+ // Allow sending if there's text OR files selected (files will be uploaded on send)
38506
+ var hasContentToSend = props.inputMessage.trim() !== '' || selectedFiles.length > 0;
38507
+ var isSendDisabled = props.isLoading || isSending || !hasContentToSend || hasUploadingFiles || hasFileErrors;
38508
+ var showLoading = props.isLoading || isSending || hasUploadingFiles;
38370
38509
  var handleKeyDown = useCallback(e => {
38371
38510
  if (e.key === 'Enter' && !e.shiftKey) {
38372
38511
  e.preventDefault();
@@ -38440,10 +38579,12 @@ var ChatWindowFooter = props => {
38440
38579
  size: 'icon',
38441
38580
  onClick: handleSendMessageWithAttachments,
38442
38581
  disabled: isSendDisabled,
38443
- className: 'babylai-rounded-full babylai-bg-primary-500 babylai-hover:babylai-bg-purple-600 babylai-w-8 babylai-h-8 disabled:babylai-opacity-50',
38582
+ 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',
38444
38583
  type: 'button',
38445
- children: jsx(EnvelopeIcon, {
38446
- className: "babylai-w-4 babylai-h-4 ".concat(dir === 'rtl' ? 'babylai-rotate-270' : '')
38584
+ children: showLoading ? jsx("div", {
38585
+ 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'
38586
+ }) : jsx(EnvelopeIcon, {
38587
+ className: "babylai-w-4 babylai-h-4 babylai-flex-shrink-0 ".concat(dir === 'rtl' ? 'babylai-rotate-270' : '')
38447
38588
  })
38448
38589
  })]
38449
38590
  }), previewImage && jsx(ImagePreviewDialog, {
@@ -38531,10 +38672,12 @@ var MessageComponent = /*#__PURE__*/React__default.memo(_ref => {
38531
38672
  var isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
38532
38673
  var textDirection = message.senderType === 1 ? 'babylai-justify-end' : 'babylai-justify-start';
38533
38674
  var handleImageClick = useCallback(clickedIndex => {
38534
- if (message.attachmentIds && message.attachmentIds.length > 0) {
38535
- onImageClick(message.attachmentIds, clickedIndex);
38675
+ // Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
38676
+ var attachments = message.attachmentUrls || [];
38677
+ if (attachments.length > 0) {
38678
+ onImageClick(attachments, clickedIndex);
38536
38679
  }
38537
- }, [message.attachmentIds, onImageClick]);
38680
+ }, [message.attachmentIds, message.attachmentUrls, onImageClick]);
38538
38681
  return jsxs("div", {
38539
38682
  children: [isFirstHumanAgentMessage && jsx("div", {
38540
38683
  className: 'babylai-flex babylai-justify-center babylai-items-center babylai-my-4',
@@ -38557,14 +38700,21 @@ var MessageComponent = /*#__PURE__*/React__default.memo(_ref => {
38557
38700
  className: 'babylai-flex-shrink-0 babylai-me-3 babylai-w-8'
38558
38701
  }), jsxs("div", {
38559
38702
  className: 'babylai-flex babylai-flex-col babylai-gap-2',
38560
- children: [message.attachmentIds && message.attachmentIds.length > 0 && jsx("div", {
38703
+ children: [message.attachmentUrls && message.attachmentUrls.length > 0 && jsx("div", {
38704
+ className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38705
+ children: message.attachmentUrls.map((attachmentUrl, imgIndex) => jsx(ImageAttachment, {
38706
+ imageUrl: attachmentUrl,
38707
+ enablePreview: false,
38708
+ onClick: () => handleImageClick(imgIndex)
38709
+ }, attachmentUrl))
38710
+ }), message.attachmentIds && message.attachmentIds.length > 0 && jsx("div", {
38561
38711
  className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38562
38712
  children: message.attachmentIds.map((attachmentId, imgIndex) => jsx(ImageAttachment, {
38563
38713
  fileId: attachmentId,
38564
38714
  enablePreview: false,
38565
38715
  onClick: () => handleImageClick(imgIndex)
38566
38716
  }, attachmentId))
38567
- }), jsx(AgentResponse$1, {
38717
+ }), message.messageContent && message.messageContent.trim() !== '' && jsx(AgentResponse$1, {
38568
38718
  messageContent: message.messageContent,
38569
38719
  senderType: message.senderType,
38570
38720
  messageId: message.id,
@@ -38609,7 +38759,8 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38609
38759
  onEnsureSession,
38610
38760
  messages,
38611
38761
  assistantStatus = 'loading',
38612
- needsAgent
38762
+ needsAgent,
38763
+ sessionId
38613
38764
  } = _ref3;
38614
38765
  var {
38615
38766
  i18n
@@ -38652,7 +38803,8 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38652
38803
  };
38653
38804
  }, []);
38654
38805
  var handleSendMessage = useCallback(attachmentIds => {
38655
- if (inputMessage.trim()) {
38806
+ // Allow sending if there's text OR attachments
38807
+ if (inputMessage.trim() || attachmentIds.length > 0) {
38656
38808
  onSendMessage(inputMessage, attachmentIds);
38657
38809
  setInputMessage('');
38658
38810
  }
@@ -38663,13 +38815,32 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38663
38815
  }, [messages]);
38664
38816
  // Handle image gallery opening
38665
38817
  var handleImageClick = useCallback(/*#__PURE__*/function () {
38666
- var _ref4 = _asyncToGenerator(function* (attachmentIds, clickedIndex) {
38667
- if (!attachmentIds || attachmentIds.length === 0) {
38818
+ var _ref4 = _asyncToGenerator(function* (attachmentIdsOrUrls, clickedIndex) {
38819
+ var _a, _b;
38820
+ if (!attachmentIdsOrUrls || attachmentIdsOrUrls.length === 0) {
38668
38821
  return;
38669
38822
  }
38670
38823
  try {
38671
- // Fetch all image URLs with comprehensive error handling
38672
- var imageUrlPromises = attachmentIds.map(fileId => {
38824
+ // Check if the first item is a URL (starts with http:// or https://)
38825
+ // If so, they're all URLs from Ably and can be used directly
38826
+ var isUrl = ((_a = attachmentIdsOrUrls[0]) === null || _a === void 0 ? void 0 : _a.startsWith('http://')) || ((_b = attachmentIdsOrUrls[0]) === null || _b === void 0 ? void 0 : _b.startsWith('https://'));
38827
+ var imageUrls;
38828
+ if (isUrl) {
38829
+ // These are already URLs from Ably, use them directly (no async needed)
38830
+ imageUrls = attachmentIdsOrUrls.filter(url => url !== null && url.length > 0);
38831
+ // Open gallery immediately with URLs
38832
+ if (imageUrls.length > 0) {
38833
+ var _adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
38834
+ setGalleryState({
38835
+ isOpen: true,
38836
+ imageUrls,
38837
+ initialIndex: _adjustedIndex
38838
+ });
38839
+ }
38840
+ return; // Exit early since we don't need to fetch anything
38841
+ }
38842
+ // These are file IDs, need to fetch URLs using presignDownload
38843
+ var imageUrlPromises = attachmentIdsOrUrls.map(fileId => {
38673
38844
  if (!fileId || typeof fileId !== 'string') {
38674
38845
  return Promise.resolve(null);
38675
38846
  }
@@ -38683,7 +38854,7 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38683
38854
  return null;
38684
38855
  });
38685
38856
  });
38686
- var imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38857
+ imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38687
38858
  if (imageUrls.length === 0) {
38688
38859
  return;
38689
38860
  }
@@ -38745,7 +38916,8 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38745
38916
  handleSendMessage: handleSendMessage,
38746
38917
  setInputMessage: setInputMessage,
38747
38918
  isLoading: isLoading,
38748
- onEnsureSession: onEnsureSession
38919
+ onEnsureSession: onEnsureSession,
38920
+ sessionId: sessionId
38749
38921
  }), galleryState.isOpen && galleryState.imageUrls.length > 0 && jsx(ImagePreviewDialog, {
38750
38922
  imageUrls: galleryState.imageUrls,
38751
38923
  initialIndex: galleryState.initialIndex,
@@ -39364,7 +39536,8 @@ var HelpPopup = _ref => {
39364
39536
  }
39365
39537
  }, [onStartChat, setSelectedOption, sessionId, setStartNewChatConfirmation, setTempSelectedOption]);
39366
39538
  var handleSendMessage = useCallback((message, attachmentIds) => {
39367
- if (message.trim()) {
39539
+ // Allow sending if there's text OR attachments
39540
+ if (message.trim() || attachmentIds.length > 0) {
39368
39541
  onSendMessage(message.trim(), attachmentIds);
39369
39542
  }
39370
39543
  }, [onSendMessage]);
@@ -39420,7 +39593,8 @@ var HelpPopup = _ref => {
39420
39593
  messages: memoizedMessages,
39421
39594
  assistantStatus: assistantStatus,
39422
39595
  needsAgent: needsAgent,
39423
- isAblyConnected: isAblyConnected
39596
+ isAblyConnected: isAblyConnected,
39597
+ sessionId: sessionId
39424
39598
  })]
39425
39599
  });
39426
39600
  }
@@ -39559,7 +39733,7 @@ var HelpCenterContent = _ref => {
39559
39733
  sentAt: new Date(),
39560
39734
  isSeen: true
39561
39735
  }, attachments.length > 0 && {
39562
- attachmentIds: attachments
39736
+ attachmentUrls: attachments
39563
39737
  });
39564
39738
  return [...prevMessages, newMessage];
39565
39739
  });
@@ -39688,10 +39862,11 @@ var HelpCenterContent = _ref => {
39688
39862
  var handleEnsureSession = /*#__PURE__*/function () {
39689
39863
  var _ref7 = _asyncToGenerator(function* () {
39690
39864
  // If we already have a session ID and connection, return it
39691
- if (sessionId && isAblyConnected) {
39865
+ // NEVER create a new session if one already exists
39866
+ if (sessionId) {
39692
39867
  return sessionId;
39693
39868
  }
39694
- // If we have a selected option but no session, create one
39869
+ // Only create a new session if we don't have one and have a selected option
39695
39870
  if (selectedOption) {
39696
39871
  var newSessionId = yield startNewChatSession(selectedOption);
39697
39872
  return newSessionId;
@@ -39705,28 +39880,32 @@ var HelpCenterContent = _ref => {
39705
39880
  var handleSendMessage = /*#__PURE__*/function () {
39706
39881
  var _ref8 = _asyncToGenerator(function* (message) {
39707
39882
  var attachmentIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
39708
- if (message.trim() !== '') {
39883
+ // Allow sending if there's text OR attachments
39884
+ if (message.trim() !== '' || attachmentIds.length > 0) {
39709
39885
  try {
39710
39886
  setAssistantStatus('typing');
39711
39887
  var userMessage = {
39712
39888
  id: Date.now(),
39713
39889
  senderType: 1,
39714
- messageContent: message,
39890
+ messageContent: message || '',
39891
+ // Use empty string if message is empty but attachments exist
39715
39892
  sentAt: new Date(),
39716
39893
  isSeen: false,
39717
39894
  attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined
39718
39895
  };
39719
39896
  setMessages(prevMessages => [...prevMessages, userMessage]);
39720
- // Handle session creation if needed
39897
+ // Handle session creation if needed - only create if no session exists
39721
39898
  var currentSessionId = sessionId;
39722
- if (!isAblyConnected && selectedOption) {
39899
+ // Only create a new session if we don't have one and we have a selected option
39900
+ // This ensures session is only created once with the first message
39901
+ if (!currentSessionId && !isAblyConnected && selectedOption) {
39723
39902
  currentSessionId = yield startNewChatSession(selectedOption);
39724
39903
  }
39725
39904
  if (!currentSessionId) {
39726
39905
  throw new Error('No active session available');
39727
39906
  }
39728
39907
  var messageDto = _objectSpread2({
39729
- messageContent: message
39908
+ messageContent: message || ''
39730
39909
  }, attachmentIds.length > 0 && {
39731
39910
  attachmentIds
39732
39911
  });