@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.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();
@@ -38250,39 +38368,63 @@ var ChatWindowFooter = props => {
38250
38368
  var _a;
38251
38369
  (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
38252
38370
  }, []);
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 = '';
38371
+ var handleFileSelect = useCallback(e => {
38372
+ var files = Array.from(e.target.files || []);
38373
+ // Validate that all files are images
38374
+ var imageFiles = files.filter(file => file.type.startsWith('image/'));
38375
+ // Create preview URLs and add to selected files (don't upload yet)
38376
+ var newFiles = imageFiles.map(file => ({
38377
+ file,
38378
+ previewUrl: URL.createObjectURL(file),
38379
+ uploading: false,
38380
+ uploadedId: null,
38381
+ error: null
38382
+ }));
38383
+ setSelectedFiles(prev => [...prev, ...newFiles]);
38384
+ // Clear the input
38385
+ if (fileInputRef.current) {
38386
+ fileInputRef.current.value = '';
38387
+ }
38388
+ // Don't upload files immediately - wait for send button click
38389
+ }, []);
38390
+ // Removed handleUploadFiles - files are now uploaded in handleSendMessageWithAttachments
38391
+ var handleRemoveFile = useCallback(previewUrl => {
38392
+ setSelectedFiles(prev => {
38393
+ var fileToRemove = prev.find(f => f.previewUrl === previewUrl);
38394
+ if (fileToRemove) {
38395
+ URL.revokeObjectURL(fileToRemove.previewUrl);
38271
38396
  }
38272
- // Start uploading immediately
38273
- yield handleUploadFiles(newFiles);
38397
+ return prev.filter(f => f.previewUrl !== previewUrl);
38274
38398
  });
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;
38399
+ }, []);
38400
+ var handleSendMessageWithAttachments = useCallback(/*#__PURE__*/_asyncToGenerator(function* () {
38401
+ // Prevent sending if already loading
38402
+ if (props.isLoading) {
38403
+ return;
38404
+ }
38405
+ // Get files that need to be uploaded (those without uploadedId)
38406
+ var filesToUpload = selectedFiles.filter(f => f.uploadedId === null && !f.error);
38407
+ var alreadyUploadedIds = selectedFiles.filter(f => f.uploadedId !== null).map(f => f.uploadedId);
38408
+ // Declare uploadedIds outside the if block so it's accessible later
38409
+ var uploadedIds = [];
38410
+ // If there are files to upload, upload them first
38411
+ if (filesToUpload.length > 0) {
38412
+ // Get session ID - only use existing, never create new one
38413
+ var sessionId = null;
38283
38414
  try {
38284
- sessionId = yield props.onEnsureSession();
38415
+ // Only use existing sessionId, never call onEnsureSession
38416
+ if (props.sessionId) {
38417
+ sessionId = props.sessionId;
38418
+ } else {
38419
+ // Mark all files as error
38420
+ setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38421
+ error: 'No session available',
38422
+ uploading: false
38423
+ }) : f));
38424
+ return;
38425
+ }
38285
38426
  } catch (error) {
38427
+ console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
38286
38428
  // Mark all files as error
38287
38429
  setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38288
38430
  error: 'Failed to initialize session',
@@ -38290,7 +38432,9 @@ var ChatWindowFooter = props => {
38290
38432
  }) : f));
38291
38433
  return;
38292
38434
  }
38293
- // Upload each file
38435
+ // Upload each file and collect uploaded IDs
38436
+ uploadedIds = [];
38437
+ var hasUploadErrors = false;
38294
38438
  var _loop = function* _loop(fileDto) {
38295
38439
  try {
38296
38440
  // Mark as uploading
@@ -38301,7 +38445,6 @@ var ChatWindowFooter = props => {
38301
38445
  // Get presigned URL
38302
38446
  var presignResponse = yield presignUpload(sessionId, fileDto.file, i18n.language);
38303
38447
  // 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
38448
  var uploadResponse = yield axios$1.put(presignResponse.uploadUrl, fileDto.file, {
38306
38449
  headers: {
38307
38450
  'Content-Type': fileDto.file.type
@@ -38313,6 +38456,8 @@ var ChatWindowFooter = props => {
38313
38456
  if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
38314
38457
  throw new Error("Upload failed with status ".concat(uploadResponse.status));
38315
38458
  }
38459
+ // Collect uploaded ID
38460
+ uploadedIds.push(presignResponse.id);
38316
38461
  // Update with uploaded ID
38317
38462
  setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38318
38463
  uploading: false,
@@ -38320,6 +38465,8 @@ var ChatWindowFooter = props => {
38320
38465
  error: null
38321
38466
  }) : f));
38322
38467
  } catch (error) {
38468
+ console.error('[ChatWindowFooter] File upload failed:', error);
38469
+ hasUploadErrors = true;
38323
38470
  setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38324
38471
  uploading: false,
38325
38472
  error: 'Upload failed',
@@ -38330,43 +38477,28 @@ var ChatWindowFooter = props => {
38330
38477
  for (var fileDto of filesToUpload) {
38331
38478
  yield* _loop(fileDto);
38332
38479
  }
38333
- });
38334
- return function (_x2) {
38335
- return _ref2.apply(this, arguments);
38336
- };
38337
- }(), [props.onEnsureSession, i18n.language]);
38338
- var handleRemoveFile = useCallback(previewUrl => {
38339
- setSelectedFiles(prev => {
38340
- var fileToRemove = prev.find(f => f.previewUrl === previewUrl);
38341
- if (fileToRemove) {
38342
- URL.revokeObjectURL(fileToRemove.previewUrl);
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
+ return;
38343
38484
  }
38344
- return prev.filter(f => f.previewUrl !== previewUrl);
38345
- });
38346
- }, []);
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);
38485
+ }
38486
+ // Get all successfully uploaded file IDs (already uploaded + newly uploaded)
38487
+ // Use uploadedIds from the upload loop instead of reading from state
38488
+ var allAttachmentIds = [...alreadyUploadedIds, ...uploadedIds];
38355
38489
  // Call the original send message with attachment IDs
38356
- props.handleSendMessage(attachmentIds);
38490
+ props.handleSendMessage(allAttachmentIds);
38357
38491
  // Clear selected files and revoke URLs
38358
38492
  selectedFiles.forEach(f => URL.revokeObjectURL(f.previewUrl));
38359
38493
  setSelectedFiles([]);
38360
- }, [selectedFiles, props]);
38494
+ }), [selectedFiles, props, i18n.language]);
38361
38495
  // Check if any files are currently uploading
38362
38496
  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;
38497
+ // Check if there are files with errors
38498
+ var hasFileErrors = selectedFiles.some(f => f.error !== null);
38499
+ // Allow sending if there's text OR files selected (files will be uploaded on send)
38500
+ var hasContentToSend = props.inputMessage.trim() !== '' || selectedFiles.length > 0;
38501
+ var isSendDisabled = props.isLoading || !hasContentToSend || hasUploadingFiles || hasFileErrors;
38370
38502
  var handleKeyDown = useCallback(e => {
38371
38503
  if (e.key === 'Enter' && !e.shiftKey) {
38372
38504
  e.preventDefault();
@@ -38531,10 +38663,12 @@ var MessageComponent = /*#__PURE__*/React__default.memo(_ref => {
38531
38663
  var isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
38532
38664
  var textDirection = message.senderType === 1 ? 'babylai-justify-end' : 'babylai-justify-start';
38533
38665
  var handleImageClick = useCallback(clickedIndex => {
38534
- if (message.attachmentIds && message.attachmentIds.length > 0) {
38535
- onImageClick(message.attachmentIds, clickedIndex);
38666
+ // Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
38667
+ var attachments = message.attachmentUrls || message.attachmentIds || [];
38668
+ if (attachments.length > 0) {
38669
+ onImageClick(attachments, clickedIndex);
38536
38670
  }
38537
- }, [message.attachmentIds, onImageClick]);
38671
+ }, [message.attachmentIds, message.attachmentUrls, onImageClick]);
38538
38672
  return jsxs("div", {
38539
38673
  children: [isFirstHumanAgentMessage && jsx("div", {
38540
38674
  className: 'babylai-flex babylai-justify-center babylai-items-center babylai-my-4',
@@ -38557,14 +38691,21 @@ var MessageComponent = /*#__PURE__*/React__default.memo(_ref => {
38557
38691
  className: 'babylai-flex-shrink-0 babylai-me-3 babylai-w-8'
38558
38692
  }), jsxs("div", {
38559
38693
  className: 'babylai-flex babylai-flex-col babylai-gap-2',
38560
- children: [message.attachmentIds && message.attachmentIds.length > 0 && jsx("div", {
38694
+ children: [message.attachmentUrls && message.attachmentUrls.length > 0 && jsx("div", {
38695
+ className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38696
+ children: message.attachmentUrls.map((attachmentUrl, imgIndex) => jsx(ImageAttachment, {
38697
+ imageUrl: attachmentUrl,
38698
+ enablePreview: false,
38699
+ onClick: () => handleImageClick(imgIndex)
38700
+ }, attachmentUrl))
38701
+ }), message.attachmentIds && message.attachmentIds.length > 0 && jsx("div", {
38561
38702
  className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38562
38703
  children: message.attachmentIds.map((attachmentId, imgIndex) => jsx(ImageAttachment, {
38563
38704
  fileId: attachmentId,
38564
38705
  enablePreview: false,
38565
38706
  onClick: () => handleImageClick(imgIndex)
38566
38707
  }, attachmentId))
38567
- }), jsx(AgentResponse$1, {
38708
+ }), message.messageContent && message.messageContent.trim() !== '' && jsx(AgentResponse$1, {
38568
38709
  messageContent: message.messageContent,
38569
38710
  senderType: message.senderType,
38570
38711
  messageId: message.id,
@@ -38609,7 +38750,8 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38609
38750
  onEnsureSession,
38610
38751
  messages,
38611
38752
  assistantStatus = 'loading',
38612
- needsAgent
38753
+ needsAgent,
38754
+ sessionId
38613
38755
  } = _ref3;
38614
38756
  var {
38615
38757
  i18n
@@ -38652,7 +38794,8 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38652
38794
  };
38653
38795
  }, []);
38654
38796
  var handleSendMessage = useCallback(attachmentIds => {
38655
- if (inputMessage.trim()) {
38797
+ // Allow sending if there's text OR attachments
38798
+ if (inputMessage.trim() || attachmentIds.length > 0) {
38656
38799
  onSendMessage(inputMessage, attachmentIds);
38657
38800
  setInputMessage('');
38658
38801
  }
@@ -38663,13 +38806,32 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38663
38806
  }, [messages]);
38664
38807
  // Handle image gallery opening
38665
38808
  var handleImageClick = useCallback(/*#__PURE__*/function () {
38666
- var _ref4 = _asyncToGenerator(function* (attachmentIds, clickedIndex) {
38667
- if (!attachmentIds || attachmentIds.length === 0) {
38809
+ var _ref4 = _asyncToGenerator(function* (attachmentIdsOrUrls, clickedIndex) {
38810
+ var _a, _b;
38811
+ if (!attachmentIdsOrUrls || attachmentIdsOrUrls.length === 0) {
38668
38812
  return;
38669
38813
  }
38670
38814
  try {
38671
- // Fetch all image URLs with comprehensive error handling
38672
- var imageUrlPromises = attachmentIds.map(fileId => {
38815
+ // Check if the first item is a URL (starts with http:// or https://)
38816
+ // If so, they're all URLs from Ably and can be used directly
38817
+ 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://'));
38818
+ var imageUrls;
38819
+ if (isUrl) {
38820
+ // These are already URLs from Ably, use them directly (no async needed)
38821
+ imageUrls = attachmentIdsOrUrls.filter(url => url !== null && url.length > 0);
38822
+ // Open gallery immediately with URLs
38823
+ if (imageUrls.length > 0) {
38824
+ var _adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
38825
+ setGalleryState({
38826
+ isOpen: true,
38827
+ imageUrls,
38828
+ initialIndex: _adjustedIndex
38829
+ });
38830
+ }
38831
+ return; // Exit early since we don't need to fetch anything
38832
+ }
38833
+ // These are file IDs, need to fetch URLs using presignDownload
38834
+ var imageUrlPromises = attachmentIdsOrUrls.map(fileId => {
38673
38835
  if (!fileId || typeof fileId !== 'string') {
38674
38836
  return Promise.resolve(null);
38675
38837
  }
@@ -38683,7 +38845,7 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38683
38845
  return null;
38684
38846
  });
38685
38847
  });
38686
- var imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38848
+ imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38687
38849
  if (imageUrls.length === 0) {
38688
38850
  return;
38689
38851
  }
@@ -38745,7 +38907,8 @@ var ChatWindow = /*#__PURE__*/React__default.memo(_ref3 => {
38745
38907
  handleSendMessage: handleSendMessage,
38746
38908
  setInputMessage: setInputMessage,
38747
38909
  isLoading: isLoading,
38748
- onEnsureSession: onEnsureSession
38910
+ onEnsureSession: onEnsureSession,
38911
+ sessionId: sessionId
38749
38912
  }), galleryState.isOpen && galleryState.imageUrls.length > 0 && jsx(ImagePreviewDialog, {
38750
38913
  imageUrls: galleryState.imageUrls,
38751
38914
  initialIndex: galleryState.initialIndex,
@@ -39364,7 +39527,8 @@ var HelpPopup = _ref => {
39364
39527
  }
39365
39528
  }, [onStartChat, setSelectedOption, sessionId, setStartNewChatConfirmation, setTempSelectedOption]);
39366
39529
  var handleSendMessage = useCallback((message, attachmentIds) => {
39367
- if (message.trim()) {
39530
+ // Allow sending if there's text OR attachments
39531
+ if (message.trim() || attachmentIds.length > 0) {
39368
39532
  onSendMessage(message.trim(), attachmentIds);
39369
39533
  }
39370
39534
  }, [onSendMessage]);
@@ -39420,7 +39584,8 @@ var HelpPopup = _ref => {
39420
39584
  messages: memoizedMessages,
39421
39585
  assistantStatus: assistantStatus,
39422
39586
  needsAgent: needsAgent,
39423
- isAblyConnected: isAblyConnected
39587
+ isAblyConnected: isAblyConnected,
39588
+ sessionId: sessionId
39424
39589
  })]
39425
39590
  });
39426
39591
  }
@@ -39559,7 +39724,7 @@ var HelpCenterContent = _ref => {
39559
39724
  sentAt: new Date(),
39560
39725
  isSeen: true
39561
39726
  }, attachments.length > 0 && {
39562
- attachmentIds: attachments
39727
+ attachmentUrls: attachments
39563
39728
  });
39564
39729
  return [...prevMessages, newMessage];
39565
39730
  });
@@ -39688,10 +39853,11 @@ var HelpCenterContent = _ref => {
39688
39853
  var handleEnsureSession = /*#__PURE__*/function () {
39689
39854
  var _ref7 = _asyncToGenerator(function* () {
39690
39855
  // If we already have a session ID and connection, return it
39691
- if (sessionId && isAblyConnected) {
39856
+ // NEVER create a new session if one already exists
39857
+ if (sessionId) {
39692
39858
  return sessionId;
39693
39859
  }
39694
- // If we have a selected option but no session, create one
39860
+ // Only create a new session if we don't have one and have a selected option
39695
39861
  if (selectedOption) {
39696
39862
  var newSessionId = yield startNewChatSession(selectedOption);
39697
39863
  return newSessionId;
@@ -39705,28 +39871,32 @@ var HelpCenterContent = _ref => {
39705
39871
  var handleSendMessage = /*#__PURE__*/function () {
39706
39872
  var _ref8 = _asyncToGenerator(function* (message) {
39707
39873
  var attachmentIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
39708
- if (message.trim() !== '') {
39874
+ // Allow sending if there's text OR attachments
39875
+ if (message.trim() !== '' || attachmentIds.length > 0) {
39709
39876
  try {
39710
39877
  setAssistantStatus('typing');
39711
39878
  var userMessage = {
39712
39879
  id: Date.now(),
39713
39880
  senderType: 1,
39714
- messageContent: message,
39881
+ messageContent: message || '',
39882
+ // Use empty string if message is empty but attachments exist
39715
39883
  sentAt: new Date(),
39716
39884
  isSeen: false,
39717
39885
  attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined
39718
39886
  };
39719
39887
  setMessages(prevMessages => [...prevMessages, userMessage]);
39720
- // Handle session creation if needed
39888
+ // Handle session creation if needed - only create if no session exists
39721
39889
  var currentSessionId = sessionId;
39722
- if (!isAblyConnected && selectedOption) {
39890
+ // Only create a new session if we don't have one and we have a selected option
39891
+ // This ensures session is only created once with the first message
39892
+ if (!currentSessionId && !isAblyConnected && selectedOption) {
39723
39893
  currentSessionId = yield startNewChatSession(selectedOption);
39724
39894
  }
39725
39895
  if (!currentSessionId) {
39726
39896
  throw new Error('No active session available');
39727
39897
  }
39728
39898
  var messageDto = _objectSpread2({
39729
- messageContent: message
39899
+ messageContent: message || ''
39730
39900
  }, attachmentIds.length > 0 && {
39731
39901
  attachmentIds
39732
39902
  });