@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.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();
@@ -38272,95 +38390,31 @@ var ChatWindowFooter = props => {
38272
38390
  var fileInputRef = React.useRef(null);
38273
38391
  var [selectedFiles, setSelectedFiles] = React.useState([]);
38274
38392
  var [previewImage, setPreviewImage] = React.useState(null);
38393
+ var [isSending, setIsSending] = React.useState(false);
38275
38394
  var handleAttachClick = React.useCallback(() => {
38276
38395
  var _a;
38277
38396
  (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
38278
38397
  }, []);
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 = '';
38297
- }
38298
- // Start uploading immediately
38299
- yield handleUploadFiles(newFiles);
38300
- });
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;
38309
- try {
38310
- sessionId = yield props.onEnsureSession();
38311
- } catch (error) {
38312
- // Mark all files as error
38313
- setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38314
- error: 'Failed to initialize session',
38315
- uploading: false
38316
- }) : f));
38317
- return;
38318
- }
38319
- // Upload each file
38320
- var _loop = function* _loop(fileDto) {
38321
- try {
38322
- // Mark as uploading
38323
- setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38324
- uploading: true,
38325
- error: null
38326
- }) : f));
38327
- // Get presigned URL
38328
- var presignResponse = yield presignUpload(sessionId, fileDto.file, i18n.language);
38329
- // 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
- var uploadResponse = yield axios$1.put(presignResponse.uploadUrl, fileDto.file, {
38332
- headers: {
38333
- 'Content-Type': fileDto.file.type
38334
- },
38335
- onUploadProgress: () => {
38336
- // Upload progress tracking (silent)
38337
- }
38338
- });
38339
- if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
38340
- throw new Error("Upload failed with status ".concat(uploadResponse.status));
38341
- }
38342
- // Update with uploaded ID
38343
- setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38344
- uploading: false,
38345
- uploadedId: presignResponse.id,
38346
- error: null
38347
- }) : f));
38348
- } catch (error) {
38349
- setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38350
- uploading: false,
38351
- error: 'Upload failed',
38352
- uploadedId: null
38353
- }) : f));
38354
- }
38355
- };
38356
- for (var fileDto of filesToUpload) {
38357
- yield* _loop(fileDto);
38358
- }
38359
- });
38360
- return function (_x2) {
38361
- return _ref2.apply(this, arguments);
38362
- };
38363
- }(), [props.onEnsureSession, i18n.language]);
38398
+ var handleFileSelect = React.useCallback(e => {
38399
+ var files = Array.from(e.target.files || []);
38400
+ // Validate that all files are images
38401
+ var imageFiles = files.filter(file => file.type.startsWith('image/'));
38402
+ // Create preview URLs and add to selected files (don't upload yet)
38403
+ var newFiles = imageFiles.map(file => ({
38404
+ file,
38405
+ previewUrl: URL.createObjectURL(file),
38406
+ uploading: false,
38407
+ uploadedId: null,
38408
+ error: null
38409
+ }));
38410
+ setSelectedFiles(prev => [...prev, ...newFiles]);
38411
+ // Clear the input
38412
+ if (fileInputRef.current) {
38413
+ fileInputRef.current.value = '';
38414
+ }
38415
+ // Don't upload files immediately - wait for send button click
38416
+ }, []);
38417
+ // Removed handleUploadFiles - files are now uploaded in handleSendMessageWithAttachments
38364
38418
  var handleRemoveFile = React.useCallback(previewUrl => {
38365
38419
  setSelectedFiles(prev => {
38366
38420
  var fileToRemove = prev.find(f => f.previewUrl === previewUrl);
@@ -38370,29 +38424,114 @@ var ChatWindowFooter = props => {
38370
38424
  return prev.filter(f => f.previewUrl !== previewUrl);
38371
38425
  });
38372
38426
  }, []);
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);
38381
- // Call the original send message with attachment IDs
38382
- props.handleSendMessage(attachmentIds);
38383
- // Clear selected files and revoke URLs
38384
- selectedFiles.forEach(f => URL.revokeObjectURL(f.previewUrl));
38385
- setSelectedFiles([]);
38386
- }, [selectedFiles, props]);
38427
+ var handleSendMessageWithAttachments = React.useCallback(/*#__PURE__*/_asyncToGenerator(function* () {
38428
+ // Prevent sending if already loading
38429
+ if (props.isLoading || isSending) {
38430
+ return;
38431
+ }
38432
+ setIsSending(true);
38433
+ try {
38434
+ // Get files that need to be uploaded (those without uploadedId)
38435
+ var filesToUpload = selectedFiles.filter(f => f.uploadedId === null && !f.error);
38436
+ var alreadyUploadedIds = selectedFiles.filter(f => f.uploadedId !== null).map(f => f.uploadedId);
38437
+ // Declare uploadedIds outside the if block so it's accessible later
38438
+ var uploadedIds = [];
38439
+ // If there are files to upload, upload them first
38440
+ if (filesToUpload.length > 0) {
38441
+ // Get session ID - ensure session exists if needed (for image-only messages)
38442
+ var sessionId = null;
38443
+ try {
38444
+ // Use existing sessionId if available, otherwise ensure session is created
38445
+ if (props.sessionId) {
38446
+ sessionId = props.sessionId;
38447
+ } else {
38448
+ // Ensure session exists before uploading files (allows starting chat with image only)
38449
+ sessionId = yield props.onEnsureSession();
38450
+ }
38451
+ } catch (error) {
38452
+ console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
38453
+ // Mark all files as error
38454
+ setSelectedFiles(prev => prev.map(f => filesToUpload.some(ftl => ftl.previewUrl === f.previewUrl) ? _objectSpread2(_objectSpread2({}, f), {}, {
38455
+ error: 'Failed to initialize session',
38456
+ uploading: false
38457
+ }) : f));
38458
+ setIsSending(false);
38459
+ return;
38460
+ }
38461
+ // Upload each file and collect uploaded IDs
38462
+ uploadedIds = [];
38463
+ var hasUploadErrors = false;
38464
+ var _loop = function* _loop(fileDto) {
38465
+ try {
38466
+ // Mark as uploading
38467
+ setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38468
+ uploading: true,
38469
+ error: null
38470
+ }) : f));
38471
+ // Get presigned URL
38472
+ var presignResponse = yield presignUpload(sessionId, fileDto.file, i18n.language);
38473
+ // Upload file to presigned URL using axios
38474
+ var uploadResponse = yield axios$1.put(presignResponse.uploadUrl, fileDto.file, {
38475
+ headers: {
38476
+ 'Content-Type': fileDto.file.type
38477
+ },
38478
+ onUploadProgress: () => {
38479
+ // Upload progress tracking (silent)
38480
+ }
38481
+ });
38482
+ if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
38483
+ throw new Error("Upload failed with status ".concat(uploadResponse.status));
38484
+ }
38485
+ // Collect uploaded ID
38486
+ uploadedIds.push(presignResponse.id);
38487
+ // Update with uploaded ID
38488
+ setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38489
+ uploading: false,
38490
+ uploadedId: presignResponse.id,
38491
+ error: null
38492
+ }) : f));
38493
+ } catch (error) {
38494
+ console.error('[ChatWindowFooter] File upload failed:', error);
38495
+ hasUploadErrors = true;
38496
+ setSelectedFiles(prev => prev.map(f => f.previewUrl === fileDto.previewUrl ? _objectSpread2(_objectSpread2({}, f), {}, {
38497
+ uploading: false,
38498
+ error: 'Upload failed',
38499
+ uploadedId: null
38500
+ }) : f));
38501
+ }
38502
+ };
38503
+ for (var fileDto of filesToUpload) {
38504
+ yield* _loop(fileDto);
38505
+ }
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
+ setIsSending(false);
38510
+ return;
38511
+ }
38512
+ }
38513
+ // Get all successfully uploaded file IDs (already uploaded + newly uploaded)
38514
+ // Use uploadedIds from the upload loop instead of reading from state
38515
+ var allAttachmentIds = [...alreadyUploadedIds, ...uploadedIds];
38516
+ // Call the original send message with attachment IDs
38517
+ props.handleSendMessage(allAttachmentIds);
38518
+ // Clear selected files and revoke URLs
38519
+ selectedFiles.forEach(f => URL.revokeObjectURL(f.previewUrl));
38520
+ setSelectedFiles([]);
38521
+ setIsSending(false);
38522
+ } catch (error) {
38523
+ console.error('[ChatWindowFooter] Error sending message:', error);
38524
+ setIsSending(false);
38525
+ }
38526
+ }), [selectedFiles, props, i18n.language, isSending]);
38387
38527
  // Check if any files are currently uploading
38388
38528
  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;
38529
+ // Check if there are files with errors
38530
+ var hasFileErrors = selectedFiles.some(f => f.error !== null);
38531
+ // Allow sending if there's text OR files selected (files will be uploaded on send)
38532
+ var hasContentToSend = props.inputMessage.trim() !== '' || selectedFiles.length > 0;
38533
+ var isSendDisabled = props.isLoading || isSending || !hasContentToSend || hasUploadingFiles || hasFileErrors;
38534
+ var showLoading = props.isLoading || isSending || hasUploadingFiles;
38396
38535
  var handleKeyDown = React.useCallback(e => {
38397
38536
  if (e.key === 'Enter' && !e.shiftKey) {
38398
38537
  e.preventDefault();
@@ -38466,10 +38605,12 @@ var ChatWindowFooter = props => {
38466
38605
  size: 'icon',
38467
38606
  onClick: handleSendMessageWithAttachments,
38468
38607
  disabled: isSendDisabled,
38469
- className: 'babylai-rounded-full babylai-bg-primary-500 babylai-hover:babylai-bg-purple-600 babylai-w-8 babylai-h-8 disabled:babylai-opacity-50',
38608
+ 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',
38470
38609
  type: 'button',
38471
- children: jsxRuntime.jsx(EnvelopeIcon, {
38472
- className: "babylai-w-4 babylai-h-4 ".concat(dir === 'rtl' ? 'babylai-rotate-270' : '')
38610
+ children: showLoading ? jsxRuntime.jsx("div", {
38611
+ 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'
38612
+ }) : jsxRuntime.jsx(EnvelopeIcon, {
38613
+ className: "babylai-w-4 babylai-h-4 babylai-flex-shrink-0 ".concat(dir === 'rtl' ? 'babylai-rotate-270' : '')
38473
38614
  })
38474
38615
  })]
38475
38616
  }), previewImage && jsxRuntime.jsx(ImagePreviewDialog, {
@@ -38557,10 +38698,12 @@ var MessageComponent = /*#__PURE__*/React__default["default"].memo(_ref => {
38557
38698
  var isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
38558
38699
  var textDirection = message.senderType === 1 ? 'babylai-justify-end' : 'babylai-justify-start';
38559
38700
  var handleImageClick = React.useCallback(clickedIndex => {
38560
- if (message.attachmentIds && message.attachmentIds.length > 0) {
38561
- onImageClick(message.attachmentIds, clickedIndex);
38701
+ // Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
38702
+ var attachments = message.attachmentUrls || [];
38703
+ if (attachments.length > 0) {
38704
+ onImageClick(attachments, clickedIndex);
38562
38705
  }
38563
- }, [message.attachmentIds, onImageClick]);
38706
+ }, [message.attachmentIds, message.attachmentUrls, onImageClick]);
38564
38707
  return jsxRuntime.jsxs("div", {
38565
38708
  children: [isFirstHumanAgentMessage && jsxRuntime.jsx("div", {
38566
38709
  className: 'babylai-flex babylai-justify-center babylai-items-center babylai-my-4',
@@ -38583,14 +38726,21 @@ var MessageComponent = /*#__PURE__*/React__default["default"].memo(_ref => {
38583
38726
  className: 'babylai-flex-shrink-0 babylai-me-3 babylai-w-8'
38584
38727
  }), jsxRuntime.jsxs("div", {
38585
38728
  className: 'babylai-flex babylai-flex-col babylai-gap-2',
38586
- children: [message.attachmentIds && message.attachmentIds.length > 0 && jsxRuntime.jsx("div", {
38729
+ children: [message.attachmentUrls && message.attachmentUrls.length > 0 && jsxRuntime.jsx("div", {
38730
+ className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38731
+ children: message.attachmentUrls.map((attachmentUrl, imgIndex) => jsxRuntime.jsx(ImageAttachment, {
38732
+ imageUrl: attachmentUrl,
38733
+ enablePreview: false,
38734
+ onClick: () => handleImageClick(imgIndex)
38735
+ }, attachmentUrl))
38736
+ }), message.attachmentIds && message.attachmentIds.length > 0 && jsxRuntime.jsx("div", {
38587
38737
  className: 'babylai-flex babylai-flex-row babylai-flex-wrap babylai-gap-2 babylai-max-w-full',
38588
38738
  children: message.attachmentIds.map((attachmentId, imgIndex) => jsxRuntime.jsx(ImageAttachment, {
38589
38739
  fileId: attachmentId,
38590
38740
  enablePreview: false,
38591
38741
  onClick: () => handleImageClick(imgIndex)
38592
38742
  }, attachmentId))
38593
- }), jsxRuntime.jsx(AgentResponse$1, {
38743
+ }), message.messageContent && message.messageContent.trim() !== '' && jsxRuntime.jsx(AgentResponse$1, {
38594
38744
  messageContent: message.messageContent,
38595
38745
  senderType: message.senderType,
38596
38746
  messageId: message.id,
@@ -38635,7 +38785,8 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38635
38785
  onEnsureSession,
38636
38786
  messages,
38637
38787
  assistantStatus = 'loading',
38638
- needsAgent
38788
+ needsAgent,
38789
+ sessionId
38639
38790
  } = _ref3;
38640
38791
  var {
38641
38792
  i18n
@@ -38678,7 +38829,8 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38678
38829
  };
38679
38830
  }, []);
38680
38831
  var handleSendMessage = React.useCallback(attachmentIds => {
38681
- if (inputMessage.trim()) {
38832
+ // Allow sending if there's text OR attachments
38833
+ if (inputMessage.trim() || attachmentIds.length > 0) {
38682
38834
  onSendMessage(inputMessage, attachmentIds);
38683
38835
  setInputMessage('');
38684
38836
  }
@@ -38689,13 +38841,32 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38689
38841
  }, [messages]);
38690
38842
  // Handle image gallery opening
38691
38843
  var handleImageClick = React.useCallback(/*#__PURE__*/function () {
38692
- var _ref4 = _asyncToGenerator(function* (attachmentIds, clickedIndex) {
38693
- if (!attachmentIds || attachmentIds.length === 0) {
38844
+ var _ref4 = _asyncToGenerator(function* (attachmentIdsOrUrls, clickedIndex) {
38845
+ var _a, _b;
38846
+ if (!attachmentIdsOrUrls || attachmentIdsOrUrls.length === 0) {
38694
38847
  return;
38695
38848
  }
38696
38849
  try {
38697
- // Fetch all image URLs with comprehensive error handling
38698
- var imageUrlPromises = attachmentIds.map(fileId => {
38850
+ // Check if the first item is a URL (starts with http:// or https://)
38851
+ // If so, they're all URLs from Ably and can be used directly
38852
+ 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://'));
38853
+ var imageUrls;
38854
+ if (isUrl) {
38855
+ // These are already URLs from Ably, use them directly (no async needed)
38856
+ imageUrls = attachmentIdsOrUrls.filter(url => url !== null && url.length > 0);
38857
+ // Open gallery immediately with URLs
38858
+ if (imageUrls.length > 0) {
38859
+ var _adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
38860
+ setGalleryState({
38861
+ isOpen: true,
38862
+ imageUrls,
38863
+ initialIndex: _adjustedIndex
38864
+ });
38865
+ }
38866
+ return; // Exit early since we don't need to fetch anything
38867
+ }
38868
+ // These are file IDs, need to fetch URLs using presignDownload
38869
+ var imageUrlPromises = attachmentIdsOrUrls.map(fileId => {
38699
38870
  if (!fileId || typeof fileId !== 'string') {
38700
38871
  return Promise.resolve(null);
38701
38872
  }
@@ -38709,7 +38880,7 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38709
38880
  return null;
38710
38881
  });
38711
38882
  });
38712
- var imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38883
+ imageUrls = (yield Promise.all(imageUrlPromises)).filter(url => url !== null && url.length > 0);
38713
38884
  if (imageUrls.length === 0) {
38714
38885
  return;
38715
38886
  }
@@ -38771,7 +38942,8 @@ var ChatWindow = /*#__PURE__*/React__default["default"].memo(_ref3 => {
38771
38942
  handleSendMessage: handleSendMessage,
38772
38943
  setInputMessage: setInputMessage,
38773
38944
  isLoading: isLoading,
38774
- onEnsureSession: onEnsureSession
38945
+ onEnsureSession: onEnsureSession,
38946
+ sessionId: sessionId
38775
38947
  }), galleryState.isOpen && galleryState.imageUrls.length > 0 && jsxRuntime.jsx(ImagePreviewDialog, {
38776
38948
  imageUrls: galleryState.imageUrls,
38777
38949
  initialIndex: galleryState.initialIndex,
@@ -39390,7 +39562,8 @@ var HelpPopup = _ref => {
39390
39562
  }
39391
39563
  }, [onStartChat, setSelectedOption, sessionId, setStartNewChatConfirmation, setTempSelectedOption]);
39392
39564
  var handleSendMessage = React.useCallback((message, attachmentIds) => {
39393
- if (message.trim()) {
39565
+ // Allow sending if there's text OR attachments
39566
+ if (message.trim() || attachmentIds.length > 0) {
39394
39567
  onSendMessage(message.trim(), attachmentIds);
39395
39568
  }
39396
39569
  }, [onSendMessage]);
@@ -39446,7 +39619,8 @@ var HelpPopup = _ref => {
39446
39619
  messages: memoizedMessages,
39447
39620
  assistantStatus: assistantStatus,
39448
39621
  needsAgent: needsAgent,
39449
- isAblyConnected: isAblyConnected
39622
+ isAblyConnected: isAblyConnected,
39623
+ sessionId: sessionId
39450
39624
  })]
39451
39625
  });
39452
39626
  }
@@ -39585,7 +39759,7 @@ var HelpCenterContent = _ref => {
39585
39759
  sentAt: new Date(),
39586
39760
  isSeen: true
39587
39761
  }, attachments.length > 0 && {
39588
- attachmentIds: attachments
39762
+ attachmentUrls: attachments
39589
39763
  });
39590
39764
  return [...prevMessages, newMessage];
39591
39765
  });
@@ -39714,10 +39888,11 @@ var HelpCenterContent = _ref => {
39714
39888
  var handleEnsureSession = /*#__PURE__*/function () {
39715
39889
  var _ref7 = _asyncToGenerator(function* () {
39716
39890
  // If we already have a session ID and connection, return it
39717
- if (sessionId && isAblyConnected) {
39891
+ // NEVER create a new session if one already exists
39892
+ if (sessionId) {
39718
39893
  return sessionId;
39719
39894
  }
39720
- // If we have a selected option but no session, create one
39895
+ // Only create a new session if we don't have one and have a selected option
39721
39896
  if (selectedOption) {
39722
39897
  var newSessionId = yield startNewChatSession(selectedOption);
39723
39898
  return newSessionId;
@@ -39731,28 +39906,32 @@ var HelpCenterContent = _ref => {
39731
39906
  var handleSendMessage = /*#__PURE__*/function () {
39732
39907
  var _ref8 = _asyncToGenerator(function* (message) {
39733
39908
  var attachmentIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
39734
- if (message.trim() !== '') {
39909
+ // Allow sending if there's text OR attachments
39910
+ if (message.trim() !== '' || attachmentIds.length > 0) {
39735
39911
  try {
39736
39912
  setAssistantStatus('typing');
39737
39913
  var userMessage = {
39738
39914
  id: Date.now(),
39739
39915
  senderType: 1,
39740
- messageContent: message,
39916
+ messageContent: message || '',
39917
+ // Use empty string if message is empty but attachments exist
39741
39918
  sentAt: new Date(),
39742
39919
  isSeen: false,
39743
39920
  attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined
39744
39921
  };
39745
39922
  setMessages(prevMessages => [...prevMessages, userMessage]);
39746
- // Handle session creation if needed
39923
+ // Handle session creation if needed - only create if no session exists
39747
39924
  var currentSessionId = sessionId;
39748
- if (!isAblyConnected && selectedOption) {
39925
+ // Only create a new session if we don't have one and we have a selected option
39926
+ // This ensures session is only created once with the first message
39927
+ if (!currentSessionId && !isAblyConnected && selectedOption) {
39749
39928
  currentSessionId = yield startNewChatSession(selectedOption);
39750
39929
  }
39751
39930
  if (!currentSessionId) {
39752
39931
  throw new Error('No active session available');
39753
39932
  }
39754
39933
  var messageDto = _objectSpread2({
39755
- messageContent: message
39934
+ messageContent: message || ''
39756
39935
  }, attachmentIds.length > 0 && {
39757
39936
  attachmentIds
39758
39937
  });