@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/index.js CHANGED
@@ -16271,10 +16271,19 @@ class ClientAblyService {
16271
16271
  token: ablyToken,
16272
16272
  autoConnect: true
16273
16273
  });
16274
+ _this.client.connection.on('failed', stateChange => {
16275
+ var _a;
16276
+ console.error('[AblyService] Connection state: failed', {
16277
+ reason: (_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message,
16278
+ error: stateChange.reason
16279
+ });
16280
+ });
16274
16281
  // Wait for connection to be established
16275
16282
  yield new Promise((resolve, reject) => {
16276
16283
  if (!_this.client) {
16277
- reject(new Error('Failed to initialize Ably client'));
16284
+ var error = new Error('Failed to initialize Ably client');
16285
+ console.error('[AblyService]', error);
16286
+ reject(error);
16278
16287
  return;
16279
16288
  }
16280
16289
  _this.client.connection.once('connected', () => {
@@ -16283,23 +16292,38 @@ class ClientAblyService {
16283
16292
  resolve();
16284
16293
  });
16285
16294
  _this.client.connection.once('failed', stateChange => {
16286
- var _a;
16287
- reject(new Error("Ably connection failed: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error')));
16295
+ var _a, _b;
16296
+ var error = new Error("Ably connection failed: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error'));
16297
+ console.error('[AblyService] Connection failed', {
16298
+ reason: (_b = stateChange.reason) === null || _b === void 0 ? void 0 : _b.message,
16299
+ error: stateChange.reason
16300
+ });
16301
+ reject(error);
16288
16302
  });
16289
16303
  _this.client.connection.once('disconnected', stateChange => {
16290
- var _a;
16291
- reject(new Error("Ably connection disconnected: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error')));
16304
+ var _a, _b;
16305
+ var error = new Error("Ably connection disconnected: ".concat(((_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error'));
16306
+ console.error('[AblyService] Connection disconnected', {
16307
+ reason: (_b = stateChange.reason) === null || _b === void 0 ? void 0 : _b.message
16308
+ });
16309
+ reject(error);
16292
16310
  });
16293
16311
  // Set a timeout for connection
16294
16312
  setTimeout(() => {
16295
16313
  if (!_this.isConnected) {
16296
- reject(new Error('Ably connection timeout'));
16314
+ var _error = new Error('Ably connection timeout');
16315
+ console.error('[AblyService] Connection timeout after 10 seconds');
16316
+ reject(_error);
16297
16317
  }
16298
16318
  }, 10000);
16299
16319
  });
16300
16320
  // Subscribe to the session room
16301
16321
  yield _this.joinChannel(sessionId, onMessageReceived, tenantId);
16302
16322
  } catch (error) {
16323
+ console.error('[AblyService] Error in startConnection', {
16324
+ error,
16325
+ sessionId
16326
+ });
16303
16327
  _this.isConnected = false;
16304
16328
  _this.sessionId = null;
16305
16329
  throw error;
@@ -16310,23 +16334,53 @@ class ClientAblyService {
16310
16334
  var _this2 = this;
16311
16335
  return _asyncToGenerator(function* () {
16312
16336
  if (!_this2.client) {
16313
- throw new Error('Chat client not initialized');
16337
+ var error = new Error('Chat client not initialized');
16338
+ console.error('[AblyService] joinChannel error:', error);
16339
+ throw error;
16314
16340
  }
16315
16341
  var roomName = "session:".concat(tenantId, ":").concat(sessionId);
16316
16342
  // Set up raw channel subscription for server messages
16317
16343
  if (_this2.client) {
16318
16344
  _this2.channel = _this2.client.channels.get(roomName);
16345
+ _this2.channel.on('failed', stateChange => {
16346
+ var _a;
16347
+ console.error('[AblyService] Channel failed', {
16348
+ roomName,
16349
+ reason: (_a = stateChange.reason) === null || _a === void 0 ? void 0 : _a.message,
16350
+ error: stateChange.reason
16351
+ });
16352
+ });
16319
16353
  // Subscribe to assistant/system responses
16320
16354
  _this2.channel.subscribe('ReceiveMessage', message => {
16321
- var _a, _b, _c, _d, _e, _f;
16355
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16322
16356
  try {
16323
- 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);
16324
- var senderType = ((_c = message.data) === null || _c === void 0 ? void 0 : _c.senderType) || 3; // Assistant
16325
- 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;
16326
- var attachments = ((_f = message.data) === null || _f === void 0 ? void 0 : _f.attachments) || [];
16327
- onMessageReceived(messageContent, senderType, needsAgent, attachments);
16357
+ // Ensure messageContent is always a string (default to empty string if undefined)
16358
+ 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 : '';
16359
+ var senderType = ((_e = message.data) === null || _e === void 0 ? void 0 : _e.senderType) || 3; // Assistant
16360
+ 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;
16361
+ var attachments = ((_h = message.data) === null || _h === void 0 ? void 0 : _h.attachments) || [];
16362
+ // Extract downloadUrl from attachments (Ably now returns downloadUrl directly)
16363
+ // Attachments can be: strings (URLs), objects with downloadUrl, or objects with id
16364
+ var attachmentUrls = attachments.map(attachment => {
16365
+ if (typeof attachment === 'string') {
16366
+ // If it's already a string, it's a URL
16367
+ return attachment;
16368
+ } else if (attachment === null || attachment === void 0 ? void 0 : attachment.downloadUrl) {
16369
+ // If it's an object with downloadUrl, use that
16370
+ return attachment.downloadUrl;
16371
+ } else if (attachment === null || attachment === void 0 ? void 0 : attachment.url) {
16372
+ // Fallback to url property
16373
+ return attachment.url;
16374
+ }
16375
+ // If it's an object with id, we'll need to keep it for backward compatibility
16376
+ return null;
16377
+ }).filter(url => url !== null);
16378
+ onMessageReceived(messageContent, senderType, needsAgent, attachmentUrls);
16328
16379
  } catch (error) {
16329
- // Handle error silently
16380
+ console.error('[AblyService] Error processing message', {
16381
+ error,
16382
+ message
16383
+ });
16330
16384
  }
16331
16385
  });
16332
16386
  yield _this2.channel.attach();
@@ -16356,6 +16410,9 @@ class ClientAblyService {
16356
16410
  _this3.isConnected = false;
16357
16411
  _this3.sessionId = null;
16358
16412
  } catch (error) {
16413
+ console.error('[AblyService] Error in stopConnection', {
16414
+ error
16415
+ });
16359
16416
  // Reset state even if there's an error
16360
16417
  _this3.isConnected = false;
16361
16418
  _this3.sessionId = null;
@@ -16508,19 +16565,21 @@ function useTypewriter(text) {
16508
16565
  var onType = arguments.length > 2 ? arguments[2] : undefined;
16509
16566
  var [displayedText, setDisplayedText] = React.useState('');
16510
16567
  React.useEffect(() => {
16568
+ // Ensure text is always a string to prevent errors
16569
+ var safeText = text !== null && text !== void 0 ? text : '';
16511
16570
  var index = 0;
16512
16571
  setDisplayedText('');
16513
16572
  var interval = setInterval(() => {
16514
16573
  setDisplayedText(() => {
16515
- var next = text.slice(0, index + 1);
16574
+ var next = safeText.slice(0, index + 1);
16516
16575
  index++;
16517
16576
  if (onType) onType();
16518
- if (index >= text.length) clearInterval(interval);
16577
+ if (index >= safeText.length) clearInterval(interval);
16519
16578
  return next;
16520
16579
  });
16521
16580
  }, speed);
16522
16581
  return () => clearInterval(interval);
16523
- }, [text]);
16582
+ }, [text, onType]);
16524
16583
  return displayedText;
16525
16584
  }
16526
16585
 
@@ -34307,11 +34366,13 @@ var AgentResponse = _ref => {
34307
34366
  messageId,
34308
34367
  onType
34309
34368
  } = _ref;
34369
+ // Ensure messageContent is always a string to prevent errors
34370
+ var safeMessageContent = messageContent !== null && messageContent !== void 0 ? messageContent : '';
34310
34371
  var shouldAnimate = (senderType === 2 || senderType === 3) && !seenMessagesRef.has(messageId);
34311
- var animatedText = useTypewriter(messageContent, 20, onType);
34312
- var finalMessage = shouldAnimate ? animatedText : messageContent;
34372
+ var animatedText = useTypewriter(safeMessageContent, 20, onType);
34373
+ var finalMessage = shouldAnimate ? animatedText : safeMessageContent;
34313
34374
  // Mark message as "seen" after full animation
34314
- if (shouldAnimate && finalMessage === messageContent) {
34375
+ if (shouldAnimate && finalMessage === safeMessageContent) {
34315
34376
  seenMessagesRef.add(messageId);
34316
34377
  }
34317
34378
  return jsxRuntime.jsx("div", {
@@ -34461,6 +34522,32 @@ var ImagePreviewDialog = _ref => {
34461
34522
  y: 0
34462
34523
  });
34463
34524
  }, []);
34525
+ var handleDownload = React.useCallback(/*#__PURE__*/_asyncToGenerator(function* () {
34526
+ if (!currentImageUrl) return;
34527
+ try {
34528
+ // Fetch the image as a blob
34529
+ var response = yield fetch(currentImageUrl);
34530
+ var blob = yield response.blob();
34531
+ // Create a temporary URL for the blob
34532
+ var blobUrl = URL.createObjectURL(blob);
34533
+ // Extract filename from URL or use a default
34534
+ var urlParts = currentImageUrl.split('/');
34535
+ var filename = urlParts[urlParts.length - 1].split('?')[0] || 'image.png';
34536
+ // Create a temporary anchor element and trigger download
34537
+ var link = document.createElement('a');
34538
+ link.href = blobUrl;
34539
+ link.download = filename;
34540
+ document.body.appendChild(link);
34541
+ link.click();
34542
+ // Cleanup
34543
+ document.body.removeChild(link);
34544
+ URL.revokeObjectURL(blobUrl);
34545
+ } catch (error) {
34546
+ console.error('Failed to download image:', error);
34547
+ // Fallback: open in new tab if download fails
34548
+ window.open(currentImageUrl, '_blank');
34549
+ }
34550
+ }), [currentImageUrl]);
34464
34551
  var handleClose = React.useCallback(() => {
34465
34552
  setZoomLevel(1);
34466
34553
  setImagePosition({
@@ -34661,6 +34748,27 @@ var ImagePreviewDialog = _ref => {
34661
34748
  "aria-label": 'Reset zoom',
34662
34749
  type: 'button',
34663
34750
  children: "Reset"
34751
+ }), jsxRuntime.jsx("div", {
34752
+ className: 'babylai-h-9 babylai-w-px babylai-bg-white/20 babylai-mx-1'
34753
+ }), jsxRuntime.jsx(Button, {
34754
+ variant: 'ghost',
34755
+ size: 'icon',
34756
+ onClick: handleDownload,
34757
+ className: 'babylai-text-white hover:babylai-text-white/80 hover:babylai-bg-white/10 babylai-h-9 babylai-w-9',
34758
+ "aria-label": 'Download image',
34759
+ type: 'button',
34760
+ children: jsxRuntime.jsx("svg", {
34761
+ className: 'babylai-w-5 babylai-h-5',
34762
+ fill: 'none',
34763
+ stroke: 'currentColor',
34764
+ viewBox: '0 0 24 24',
34765
+ children: jsxRuntime.jsx("path", {
34766
+ strokeLinecap: 'round',
34767
+ strokeLinejoin: 'round',
34768
+ strokeWidth: 2,
34769
+ d: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4'
34770
+ })
34771
+ })
34664
34772
  })]
34665
34773
  }), hasMultipleImages && jsxRuntime.jsx("div", {
34666
34774
  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'),
@@ -34694,6 +34802,7 @@ ImagePreviewDialog.displayName = 'ImagePreviewDialog';
34694
34802
  var ImageAttachment = _ref => {
34695
34803
  var {
34696
34804
  fileId,
34805
+ imageUrl: propImageUrl,
34697
34806
  className,
34698
34807
  enablePreview = true,
34699
34808
  onClick
@@ -34701,30 +34810,39 @@ var ImageAttachment = _ref => {
34701
34810
  var {
34702
34811
  i18n
34703
34812
  } = useLocalTranslation();
34704
- var [imageUrl, setImageUrl] = React.useState(null);
34705
- var [loading, setLoading] = React.useState(true);
34813
+ var [imageUrl, setImageUrl] = React.useState(propImageUrl || null);
34814
+ var [loading, setLoading] = React.useState(!propImageUrl && !!fileId);
34706
34815
  var [error, setError] = React.useState(false);
34707
34816
  var [isPreviewOpen, setIsPreviewOpen] = React.useState(false);
34708
34817
  React.useEffect(() => {
34709
- var fetchImageUrl = /*#__PURE__*/function () {
34710
- var _ref2 = _asyncToGenerator(function* () {
34711
- try {
34712
- setLoading(true);
34713
- setError(false);
34714
- var response = yield presignDownload(fileId, i18n.language);
34715
- setImageUrl(response.downloadUrl);
34716
- } catch (err) {
34717
- setError(true);
34718
- } finally {
34719
- setLoading(false);
34720
- }
34721
- });
34722
- return function fetchImageUrl() {
34723
- return _ref2.apply(this, arguments);
34724
- };
34725
- }();
34726
- fetchImageUrl();
34727
- }, [fileId, i18n.language]);
34818
+ // If we have a direct URL, use it immediately
34819
+ if (propImageUrl) {
34820
+ setImageUrl(propImageUrl);
34821
+ setLoading(false);
34822
+ return;
34823
+ }
34824
+ // If we only have a fileId, fetch the URL using presignDownload
34825
+ if (fileId) {
34826
+ var fetchImageUrl = /*#__PURE__*/function () {
34827
+ var _ref2 = _asyncToGenerator(function* () {
34828
+ try {
34829
+ setLoading(true);
34830
+ setError(false);
34831
+ var response = yield presignDownload(fileId, i18n.language);
34832
+ setImageUrl(response.downloadUrl);
34833
+ } catch (err) {
34834
+ setError(true);
34835
+ } finally {
34836
+ setLoading(false);
34837
+ }
34838
+ });
34839
+ return function fetchImageUrl() {
34840
+ return _ref2.apply(this, arguments);
34841
+ };
34842
+ }();
34843
+ fetchImageUrl();
34844
+ }
34845
+ }, [fileId, propImageUrl, i18n.language]);
34728
34846
  var handleImageClick = () => {
34729
34847
  if (onClick) {
34730
34848
  onClick();
@@ -38276,39 +38394,63 @@ var ChatWindowFooter = props => {
38276
38394
  var _a;
38277
38395
  (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
38278
38396
  }, []);
38279
- var handleFileSelect = React.useCallback(/*#__PURE__*/function () {
38280
- var _ref = _asyncToGenerator(function* (e) {
38281
- var files = Array.from(e.target.files || []);
38282
- // Validate that all files are images
38283
- var imageFiles = files.filter(file => file.type.startsWith('image/'));
38284
- // Only image files are allowed
38285
- // Create preview URLs and add to selected files
38286
- var newFiles = imageFiles.map(file => ({
38287
- file,
38288
- previewUrl: URL.createObjectURL(file),
38289
- uploading: false,
38290
- uploadedId: null,
38291
- error: null
38292
- }));
38293
- setSelectedFiles(prev => [...prev, ...newFiles]);
38294
- // Clear the input
38295
- if (fileInputRef.current) {
38296
- fileInputRef.current.value = '';
38397
+ var handleFileSelect = React.useCallback(e => {
38398
+ var files = Array.from(e.target.files || []);
38399
+ // Validate that all files are images
38400
+ var imageFiles = files.filter(file => file.type.startsWith('image/'));
38401
+ // Create preview URLs and add to selected files (don't upload yet)
38402
+ var newFiles = imageFiles.map(file => ({
38403
+ file,
38404
+ previewUrl: URL.createObjectURL(file),
38405
+ uploading: false,
38406
+ uploadedId: null,
38407
+ error: null
38408
+ }));
38409
+ setSelectedFiles(prev => [...prev, ...newFiles]);
38410
+ // Clear the input
38411
+ if (fileInputRef.current) {
38412
+ fileInputRef.current.value = '';
38413
+ }
38414
+ // Don't upload files immediately - wait for send button click
38415
+ }, []);
38416
+ // Removed handleUploadFiles - files are now uploaded in handleSendMessageWithAttachments
38417
+ var handleRemoveFile = React.useCallback(previewUrl => {
38418
+ setSelectedFiles(prev => {
38419
+ var fileToRemove = prev.find(f => f.previewUrl === previewUrl);
38420
+ if (fileToRemove) {
38421
+ URL.revokeObjectURL(fileToRemove.previewUrl);
38297
38422
  }
38298
- // Start uploading immediately
38299
- yield handleUploadFiles(newFiles);
38423
+ return prev.filter(f => f.previewUrl !== previewUrl);
38300
38424
  });
38301
- return function (_x) {
38302
- return _ref.apply(this, arguments);
38303
- };
38304
- }(), []);
38305
- var handleUploadFiles = React.useCallback(/*#__PURE__*/function () {
38306
- var _ref2 = _asyncToGenerator(function* (filesToUpload) {
38307
- // Get session ID
38308
- var sessionId;
38425
+ }, []);
38426
+ var handleSendMessageWithAttachments = React.useCallback(/*#__PURE__*/_asyncToGenerator(function* () {
38427
+ // Prevent sending if already loading
38428
+ if (props.isLoading) {
38429
+ return;
38430
+ }
38431
+ // Get files that need to be uploaded (those without uploadedId)
38432
+ var filesToUpload = selectedFiles.filter(f => f.uploadedId === null && !f.error);
38433
+ var alreadyUploadedIds = selectedFiles.filter(f => f.uploadedId !== null).map(f => f.uploadedId);
38434
+ // Declare uploadedIds outside the if block so it's accessible later
38435
+ var uploadedIds = [];
38436
+ // If there are files to upload, upload them first
38437
+ if (filesToUpload.length > 0) {
38438
+ // Get session ID - only use existing, never create new one
38439
+ var sessionId = null;
38309
38440
  try {
38310
- sessionId = yield props.onEnsureSession();
38441
+ // Only use existing sessionId, never call onEnsureSession
38442
+ if (props.sessionId) {
38443
+ sessionId = props.sessionId;
38444
+ } else {
38445
+ // Mark all files as error
38446
+ setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38447
+ error: 'No session available',
38448
+ uploading: false
38449
+ }) : f));
38450
+ return;
38451
+ }
38311
38452
  } catch (error) {
38453
+ console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
38312
38454
  // Mark all files as error
38313
38455
  setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38314
38456
  error: 'Failed to initialize session',
@@ -38316,7 +38458,9 @@ var ChatWindowFooter = props => {
38316
38458
  }) : f));
38317
38459
  return;
38318
38460
  }
38319
- // Upload each file
38461
+ // Upload each file and collect uploaded IDs
38462
+ uploadedIds = [];
38463
+ var hasUploadErrors = false;
38320
38464
  var _loop = function* _loop(fileDto) {
38321
38465
  try {
38322
38466
  // Mark as uploading
@@ -38327,7 +38471,6 @@ var ChatWindowFooter = props => {
38327
38471
  // Get presigned URL
38328
38472
  var presignResponse = yield presignUpload(sessionId, fileDto.file, i18n.language);
38329
38473
  // Upload file to presigned URL using axios
38330
- // Important: Content-Type must match the file type (e.g., 'image/png'), not 'multipart/form-data'
38331
38474
  var uploadResponse = yield axios$1.put(presignResponse.uploadUrl, fileDto.file, {
38332
38475
  headers: {
38333
38476
  'Content-Type': fileDto.file.type
@@ -38339,6 +38482,8 @@ var ChatWindowFooter = props => {
38339
38482
  if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
38340
38483
  throw new Error("Upload failed with status ".concat(uploadResponse.status));
38341
38484
  }
38485
+ // Collect uploaded ID
38486
+ uploadedIds.push(presignResponse.id);
38342
38487
  // Update with uploaded ID
38343
38488
  setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38344
38489
  uploading: false,
@@ -38346,6 +38491,8 @@ var ChatWindowFooter = props => {
38346
38491
  error: null
38347
38492
  }) : f));
38348
38493
  } catch (error) {
38494
+ console.error('[ChatWindowFooter] File upload failed:', error);
38495
+ hasUploadErrors = true;
38349
38496
  setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38350
38497
  uploading: false,
38351
38498
  error: 'Upload failed',
@@ -38356,43 +38503,28 @@ var ChatWindowFooter = props => {
38356
38503
  for (var fileDto of filesToUpload) {
38357
38504
  yield* _loop(fileDto);
38358
38505
  }
38359
- });
38360
- return function (_x2) {
38361
- return _ref2.apply(this, arguments);
38362
- };
38363
- }(), [props.onEnsureSession, i18n.language]);
38364
- var handleRemoveFile = React.useCallback(previewUrl => {
38365
- setSelectedFiles(prev => {
38366
- var fileToRemove = prev.find(f => f.previewUrl === previewUrl);
38367
- if (fileToRemove) {
38368
- URL.revokeObjectURL(fileToRemove.previewUrl);
38506
+ // If any uploads failed, don't send the message
38507
+ if (hasUploadErrors) {
38508
+ console.error('[ChatWindowFooter] Some files failed to upload, not sending message');
38509
+ return;
38369
38510
  }
38370
- return prev.filter(f => f.previewUrl !== previewUrl);
38371
- });
38372
- }, []);
38373
- var handleSendMessageWithAttachments = React.useCallback(() => {
38374
- // Only allow sending if all files have finished uploading (either successfully or with error)
38375
- var hasUploadingFiles = selectedFiles.some(f => f.uploading);
38376
- if (hasUploadingFiles) {
38377
- return; // Prevent sending if any files are still uploading
38378
- }
38379
- // Get all successfully uploaded file IDs
38380
- var attachmentIds = selectedFiles.filter(f => f.uploadedId !== null).map(f => f.uploadedId);
38511
+ }
38512
+ // Get all successfully uploaded file IDs (already uploaded + newly uploaded)
38513
+ // Use uploadedIds from the upload loop instead of reading from state
38514
+ var allAttachmentIds = [...alreadyUploadedIds, ...uploadedIds];
38381
38515
  // Call the original send message with attachment IDs
38382
- props.handleSendMessage(attachmentIds);
38516
+ props.handleSendMessage(allAttachmentIds);
38383
38517
  // Clear selected files and revoke URLs
38384
38518
  selectedFiles.forEach(f => URL.revokeObjectURL(f.previewUrl));
38385
38519
  setSelectedFiles([]);
38386
- }, [selectedFiles, props]);
38520
+ }), [selectedFiles, props, i18n.language]);
38387
38521
  // Check if any files are currently uploading
38388
38522
  var hasUploadingFiles = selectedFiles.some(f => f.uploading);
38389
- // Check if there are files that haven't finished (no uploadedId, no error, not uploading)
38390
- // This shouldn't happen in normal flow, but we check for safety
38391
- var hasPendingFiles = selectedFiles.some(f => !f.uploading && f.uploadedId === null && f.error === null);
38392
- // Check if all files have errors (no successful uploads)
38393
- var hasSuccessfulUploads = selectedFiles.some(f => f.uploadedId !== null);
38394
- var allFilesHaveErrors = selectedFiles.length > 0 && !hasSuccessfulUploads && !hasUploadingFiles && !hasPendingFiles;
38395
- var isSendDisabled = props.isLoading || props.inputMessage.trim() === '' || hasUploadingFiles || hasPendingFiles || allFilesHaveErrors;
38523
+ // Check if there are files with errors
38524
+ var hasFileErrors = selectedFiles.some(f => f.error !== null);
38525
+ // Allow sending if there's text OR files selected (files will be uploaded on send)
38526
+ var hasContentToSend = props.inputMessage.trim() !== '' || selectedFiles.length > 0;
38527
+ var isSendDisabled = props.isLoading || !hasContentToSend || hasUploadingFiles || hasFileErrors;
38396
38528
  var handleKeyDown = React.useCallback(e => {
38397
38529
  if (e.key === 'Enter' && !e.shiftKey) {
38398
38530
  e.preventDefault();
@@ -38557,10 +38689,12 @@ var MessageComponent = /*#__PURE__*/React__default["default"].memo(_ref => {
38557
38689
  var isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
38558
38690
  var textDirection = message.senderType === 1 ? 'babylai-justify-end' : 'babylai-justify-start';
38559
38691
  var handleImageClick = React.useCallback(clickedIndex => {
38560
- if (message.attachmentIds && message.attachmentIds.length > 0) {
38561
- onImageClick(message.attachmentIds, clickedIndex);
38692
+ // Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
38693
+ var attachments = message.attachmentUrls || message.attachmentIds || [];
38694
+ if (attachments.length > 0) {
38695
+ onImageClick(attachments, clickedIndex);
38562
38696
  }
38563
- }, [message.attachmentIds, onImageClick]);
38697
+ }, [message.attachmentIds, message.attachmentUrls, onImageClick]);
38564
38698
  return jsxRuntime.jsxs("div", {
38565
38699
  children: [isFirstHumanAgentMessage && jsxRuntime.jsx("div", {
38566
38700
  className: 'babylai-flex babylai-justify-center babylai-items-center babylai-my-4',
@@ -38583,14 +38717,21 @@ var MessageComponent = /*#__PURE__*/React__default["default"].memo(_ref => {
38583
38717
  className: 'babylai-flex-shrink-0 babylai-me-3 babylai-w-8'
38584
38718
  }), jsxRuntime.jsxs("div", {
38585
38719
  className: 'babylai-flex babylai-flex-col babylai-gap-2',
38586
- children: [message.attachmentIds && message.attachmentIds.length > 0 && jsxRuntime.jsx("div", {
38720
+ children: [message.attachmentUrls && message.attachmentUrls.length > 0 && jsxRuntime.jsx("div", {
38721
+ className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38722
+ children: message.attachmentUrls.map((attachmentUrl, imgIndex) => jsxRuntime.jsx(ImageAttachment, {
38723
+ imageUrl: attachmentUrl,
38724
+ enablePreview: false,
38725
+ onClick: () => handleImageClick(imgIndex)
38726
+ }, attachmentUrl))
38727
+ }), message.attachmentIds && message.attachmentIds.length > 0 && jsxRuntime.jsx("div", {
38587
38728
  className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38588
38729
  children: message.attachmentIds.map((attachmentId, imgIndex) => jsxRuntime.jsx(ImageAttachment, {
38589
38730
  fileId: attachmentId,
38590
38731
  enablePreview: false,
38591
38732
  onClick: () => handleImageClick(imgIndex)
38592
38733
  }, attachmentId))
38593
- }), jsxRuntime.jsx(AgentResponse$1, {
38734
+ }), message.messageContent && message.messageContent.trim() !== '' && jsxRuntime.jsx(AgentResponse$1, {
38594
38735
  messageContent: message.messageContent,
38595
38736
  senderType: message.senderType,
38596
38737
  messageId: message.id,
@@ -38635,7 +38776,8 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38635
38776
  onEnsureSession,
38636
38777
  messages,
38637
38778
  assistantStatus = 'loading',
38638
- needsAgent
38779
+ needsAgent,
38780
+ sessionId
38639
38781
  } = _ref3;
38640
38782
  var {
38641
38783
  i18n
@@ -38678,7 +38820,8 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38678
38820
  };
38679
38821
  }, []);
38680
38822
  var handleSendMessage = React.useCallback(attachmentIds => {
38681
- if (inputMessage.trim()) {
38823
+ // Allow sending if there's text OR attachments
38824
+ if (inputMessage.trim() || attachmentIds.length > 0) {
38682
38825
  onSendMessage(inputMessage, attachmentIds);
38683
38826
  setInputMessage('');
38684
38827
  }
@@ -38689,13 +38832,32 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38689
38832
  }, [messages]);
38690
38833
  // Handle image gallery opening
38691
38834
  var handleImageClick = React.useCallback(/*#__PURE__*/function () {
38692
- var _ref4 = _asyncToGenerator(function* (attachmentIds, clickedIndex) {
38693
- if (!attachmentIds || attachmentIds.length === 0) {
38835
+ var _ref4 = _asyncToGenerator(function* (attachmentIdsOrUrls, clickedIndex) {
38836
+ var _a, _b;
38837
+ if (!attachmentIdsOrUrls || attachmentIdsOrUrls.length === 0) {
38694
38838
  return;
38695
38839
  }
38696
38840
  try {
38697
- // Fetch all image URLs with comprehensive error handling
38698
- var imageUrlPromises = attachmentIds.map(fileId => {
38841
+ // Check if the first item is a URL (starts with http:// or https://)
38842
+ // If so, they're all URLs from Ably and can be used directly
38843
+ 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://'));
38844
+ var imageUrls;
38845
+ if (isUrl) {
38846
+ // These are already URLs from Ably, use them directly (no async needed)
38847
+ imageUrls = attachmentIdsOrUrls.filter(url => url !== null && url.length > 0);
38848
+ // Open gallery immediately with URLs
38849
+ if (imageUrls.length > 0) {
38850
+ var _adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
38851
+ setGalleryState({
38852
+ isOpen: true,
38853
+ imageUrls,
38854
+ initialIndex: _adjustedIndex
38855
+ });
38856
+ }
38857
+ return; // Exit early since we don't need to fetch anything
38858
+ }
38859
+ // These are file IDs, need to fetch URLs using presignDownload
38860
+ var imageUrlPromises = attachmentIdsOrUrls.map(fileId => {
38699
38861
  if (!fileId || typeof fileId !== 'string') {
38700
38862
  return Promise.resolve(null);
38701
38863
  }
@@ -38709,7 +38871,7 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38709
38871
  return null;
38710
38872
  });
38711
38873
  });
38712
- var imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38874
+ imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38713
38875
  if (imageUrls.length === 0) {
38714
38876
  return;
38715
38877
  }
@@ -38771,7 +38933,8 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38771
38933
  handleSendMessage: handleSendMessage,
38772
38934
  setInputMessage: setInputMessage,
38773
38935
  isLoading: isLoading,
38774
- onEnsureSession: onEnsureSession
38936
+ onEnsureSession: onEnsureSession,
38937
+ sessionId: sessionId
38775
38938
  }), galleryState.isOpen && galleryState.imageUrls.length > 0 && jsxRuntime.jsx(ImagePreviewDialog, {
38776
38939
  imageUrls: galleryState.imageUrls,
38777
38940
  initialIndex: galleryState.initialIndex,
@@ -39390,7 +39553,8 @@ var HelpPopup = _ref => {
39390
39553
  }
39391
39554
  }, [onStartChat, setSelectedOption, sessionId, setStartNewChatConfirmation, setTempSelectedOption]);
39392
39555
  var handleSendMessage = React.useCallback((message, attachmentIds) => {
39393
- if (message.trim()) {
39556
+ // Allow sending if there's text OR attachments
39557
+ if (message.trim() || attachmentIds.length > 0) {
39394
39558
  onSendMessage(message.trim(), attachmentIds);
39395
39559
  }
39396
39560
  }, [onSendMessage]);
@@ -39446,7 +39610,8 @@ var HelpPopup = _ref => {
39446
39610
  messages: memoizedMessages,
39447
39611
  assistantStatus: assistantStatus,
39448
39612
  needsAgent: needsAgent,
39449
- isAblyConnected: isAblyConnected
39613
+ isAblyConnected: isAblyConnected,
39614
+ sessionId: sessionId
39450
39615
  })]
39451
39616
  });
39452
39617
  }
@@ -39585,7 +39750,7 @@ var HelpCenterContent = _ref => {
39585
39750
  sentAt: new Date(),
39586
39751
  isSeen: true
39587
39752
  }, attachments.length > 0 && {
39588
- attachmentIds: attachments
39753
+ attachmentUrls: attachments
39589
39754
  });
39590
39755
  return [...prevMessages, newMessage];
39591
39756
  });
@@ -39714,10 +39879,11 @@ var HelpCenterContent = _ref => {
39714
39879
  var handleEnsureSession = /*#__PURE__*/function () {
39715
39880
  var _ref7 = _asyncToGenerator(function* () {
39716
39881
  // If we already have a session ID and connection, return it
39717
- if (sessionId && isAblyConnected) {
39882
+ // NEVER create a new session if one already exists
39883
+ if (sessionId) {
39718
39884
  return sessionId;
39719
39885
  }
39720
- // If we have a selected option but no session, create one
39886
+ // Only create a new session if we don't have one and have a selected option
39721
39887
  if (selectedOption) {
39722
39888
  var newSessionId = yield startNewChatSession(selectedOption);
39723
39889
  return newSessionId;
@@ -39731,28 +39897,32 @@ var HelpCenterContent = _ref => {
39731
39897
  var handleSendMessage = /*#__PURE__*/function () {
39732
39898
  var _ref8 = _asyncToGenerator(function* (message) {
39733
39899
  var attachmentIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
39734
- if (message.trim() !== '') {
39900
+ // Allow sending if there's text OR attachments
39901
+ if (message.trim() !== '' || attachmentIds.length > 0) {
39735
39902
  try {
39736
39903
  setAssistantStatus('typing');
39737
39904
  var userMessage = {
39738
39905
  id: Date.now(),
39739
39906
  senderType: 1,
39740
- messageContent: message,
39907
+ messageContent: message || '',
39908
+ // Use empty string if message is empty but attachments exist
39741
39909
  sentAt: new Date(),
39742
39910
  isSeen: false,
39743
39911
  attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined
39744
39912
  };
39745
39913
  setMessages(prevMessages => [...prevMessages, userMessage]);
39746
- // Handle session creation if needed
39914
+ // Handle session creation if needed - only create if no session exists
39747
39915
  var currentSessionId = sessionId;
39748
- if (!isAblyConnected && selectedOption) {
39916
+ // Only create a new session if we don't have one and we have a selected option
39917
+ // This ensures session is only created once with the first message
39918
+ if (!currentSessionId && !isAblyConnected && selectedOption) {
39749
39919
  currentSessionId = yield startNewChatSession(selectedOption);
39750
39920
  }
39751
39921
  if (!currentSessionId) {
39752
39922
  throw new Error('No active session available');
39753
39923
  }
39754
39924
  var messageDto = _objectSpread2({
39755
- messageContent: message
39925
+ messageContent: message || ''
39756
39926
  }, attachmentIds.length > 0 && {
39757
39927
  attachmentIds
39758
39928
  });