@eshal-bot/chat-widget 0.1.28 → 0.1.29

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.
@@ -8594,20 +8594,20 @@
8594
8594
  }
8595
8595
  if (!apiBaseUrl) {
8596
8596
  // Default fallback if no API base URL is provided
8597
- return 'wss://dev.eshal.ai/ws/v1/bidi';
8597
+ return 'wss://dev.eshal.ai/knowledge-api/ws';
8598
8598
  }
8599
8599
  try {
8600
8600
  const url = new URL(apiBaseUrl);
8601
8601
  // Replace http/https with ws/wss
8602
8602
  url.protocol = url.protocol.replace(/^http/, 'ws');
8603
- // Append the bidi WebSocket path
8604
- url.pathname = '/ws/v1/bidi';
8603
+ // Append the WebSocket specific path
8604
+ url.pathname = '/knowledge-api/ws';
8605
8605
  // Ensure it returns a clean URL without extra slashes
8606
8606
  return url.toString().replace(/\/$/, '');
8607
8607
  } catch (error) {
8608
8608
  console.error("Invalid API base URL provided for WebSocket:", error);
8609
8609
  // Fallback to default if URL parsing fails
8610
- return 'wss://dev.eshal.ai/ws/v1/bidi';
8610
+ return 'wss://dev.eshal.ai/knowledge-api/ws';
8611
8611
  }
8612
8612
  };
8613
8613
 
@@ -8916,7 +8916,6 @@
8916
8916
  openDelay,
8917
8917
  darkMode,
8918
8918
  enableVoiceInteraction = false,
8919
- voiceGender = 'male',
8920
8919
  onboardingQuestions = [],
8921
8920
  onboardingEnabled = false,
8922
8921
  collectionPrompt,
@@ -9002,14 +9001,14 @@
9002
9001
  const userHasSpokenRef = reactExports.useRef(false); // Track if user has sent first message - prevents automatic greetings
9003
9002
  const turnHasTranscriptionRef = reactExports.useRef(false); // True when outputTranscription received this turn — skip content.parts text
9004
9003
  const stopVoiceSessionRef = reactExports.useRef(null); // Ref to avoid referencing stopVoiceSession before it is defined in handleBidiEvent
9004
+ const pendingVoiceSourcesRef = reactExports.useRef(null); // Hold sources until the assistant bubble for this turn exists
9005
9005
 
9006
9006
  const websocketUrl = reactExports.useMemo(() => {
9007
9007
  if (!organizationId) return null;
9008
9008
  // Construct WebSocket URL from the API base URL
9009
9009
  const resolvedWsBaseUrl = getWebSocketUrl(apiBaseUrl, wsBaseUrl);
9010
- const gender = voiceGender || 'male';
9011
- return "".concat(resolvedWsBaseUrl, "/").concat(organizationId, "/").concat(bidiSessionId, "?voice_gender=").concat(gender);
9012
- }, [apiBaseUrl, organizationId, bidiSessionId, voiceGender]);
9010
+ return "".concat(resolvedWsBaseUrl, "/").concat(organizationId, "/").concat(bidiSessionId);
9011
+ }, [apiBaseUrl, organizationId, bidiSessionId]);
9013
9012
  const getNextMessageId = reactExports.useCallback(() => {
9014
9013
  const id = "msg-".concat(messageIdRef.current);
9015
9014
  messageIdRef.current += 1;
@@ -9719,6 +9718,10 @@
9719
9718
  source: "bidi"
9720
9719
  }
9721
9720
  });
9721
+ if (Array.isArray(pendingVoiceSourcesRef.current) && pendingVoiceSourcesRef.current.length > 0) {
9722
+ assistantMessage.sources = pendingVoiceSourcesRef.current;
9723
+ pendingVoiceSourcesRef.current = null;
9724
+ }
9722
9725
 
9723
9726
  // CRITICAL: Double-check message doesn't already exist
9724
9727
  const messageExists = result.some(m => m.id === newId);
@@ -9735,7 +9738,12 @@
9735
9738
  result = result.map(msg => msg.id === newId ? _objectSpread2(_objectSpread2({}, msg), {}, {
9736
9739
  content: "".concat(msg.content || "").concat(text),
9737
9740
  isProcessing: !finished
9738
- }) : msg);
9741
+ }, Array.isArray(pendingVoiceSourcesRef.current) && pendingVoiceSourcesRef.current.length > 0 ? {
9742
+ sources: pendingVoiceSourcesRef.current
9743
+ } : {}) : msg);
9744
+ if (Array.isArray(pendingVoiceSourcesRef.current) && pendingVoiceSourcesRef.current.length > 0) {
9745
+ pendingVoiceSourcesRef.current = null;
9746
+ }
9739
9747
  // Set ref to the existing message
9740
9748
  currentAssistantMessageIdRef.current = newId;
9741
9749
  }
@@ -9760,8 +9768,13 @@
9760
9768
  // CRITICAL: Don't update timestamp when appending content - preserve original timestamp for ordering
9761
9769
  // timestamp: new Date(), // Removed - preserve original timestamp
9762
9770
  isProcessing: !finished
9763
- });
9771
+ }, Array.isArray(pendingVoiceSourcesRef.current) && pendingVoiceSourcesRef.current.length > 0 ? {
9772
+ sources: pendingVoiceSourcesRef.current
9773
+ } : {});
9764
9774
  });
9775
+ if (Array.isArray(pendingVoiceSourcesRef.current) && pendingVoiceSourcesRef.current.length > 0) {
9776
+ pendingVoiceSourcesRef.current = null;
9777
+ }
9765
9778
  } else {
9766
9779
  console.error("[BIDI] appendAssistantContent: ERROR - assistant message not found!", {
9767
9780
  expectedId: currentAssistantMessageIdRef.current,
@@ -9779,6 +9792,10 @@
9779
9792
  source: "bidi"
9780
9793
  }
9781
9794
  });
9795
+ if (Array.isArray(pendingVoiceSourcesRef.current) && pendingVoiceSourcesRef.current.length > 0) {
9796
+ assistantMessage.sources = pendingVoiceSourcesRef.current;
9797
+ pendingVoiceSourcesRef.current = null;
9798
+ }
9782
9799
  result = [...result, assistantMessage];
9783
9800
  console.log("[BIDI] appendAssistantContent: recreated lost assistant message", {
9784
9801
  messageId: newId
@@ -9909,6 +9926,7 @@
9909
9926
  currentAssistantMessageIdRef.current = null;
9910
9927
  currentInputMessageIdRef.current = null;
9911
9928
  turnHasTranscriptionRef.current = false; // reset for next turn
9929
+ pendingVoiceSourcesRef.current = null;
9912
9930
  }, []);
9913
9931
  const handleInterrupted = reactExports.useCallback(() => {
9914
9932
  setIsLoading(false);
@@ -9919,6 +9937,7 @@
9919
9937
  }
9920
9938
  currentAssistantMessageIdRef.current = null;
9921
9939
  currentInputMessageIdRef.current = null;
9940
+ pendingVoiceSourcesRef.current = null;
9922
9941
  setBidiMessages(prev => prev.map(message => {
9923
9942
  var _message$metadata2;
9924
9943
  return ((_message$metadata2 = message.metadata) === null || _message$metadata2 === void 0 ? void 0 : _message$metadata2.source) === "bidi" ? _objectSpread2(_objectSpread2({}, message), {}, {
@@ -9939,6 +9958,7 @@
9939
9958
 
9940
9959
  // Reset userHasSpoken flag when stopping session
9941
9960
  userHasSpokenRef.current = false;
9961
+ pendingVoiceSourcesRef.current = null;
9942
9962
  if (websocketRef.current && websocketRef.current.readyState === WebSocket.OPEN) {
9943
9963
  websocketRef.current.close();
9944
9964
  }
@@ -9956,7 +9976,7 @@
9956
9976
  isAudioReadyRef.current = false;
9957
9977
  }, []);
9958
9978
  const handleBidiEvent = reactExports.useCallback(event => {
9959
- var _event$content, _event$conclusion_det, _event$outputTranscri2, _event$content3;
9979
+ var _event$content, _event$conclusion_det, _event$sources, _event$outputTranscri2, _event$content3;
9960
9980
  console.log("[BIDI] handleBidiEvent called", {
9961
9981
  event,
9962
9982
  isVoiceSessionActive: isVoiceSessionActiveRef.current,
@@ -10028,20 +10048,43 @@
10028
10048
  // Handle sources that arrive as a separate message after turnComplete.
10029
10049
  // Must be checked before the !userHasSpokenRef gate so they are always
10030
10050
  // attached regardless of session state.
10031
- if (Array.isArray(event.sources) && event.sources.length > 0) {
10051
+ //
10052
+ // NOTE: voice_chat.py emits each source with `sourceid` (no underscore)
10053
+ // to match the Milvus/MongoDB field name, but MessageSources /
10054
+ // handleFileDownload read `source.source_id`. Without normalization the
10055
+ // download button appears but the click is a silent no-op. The same
10056
+ // mapping is already applied on the history-load path above (~line 192)
10057
+ // — mirror it here so live and reloaded conversations produce the same
10058
+ // shape.
10059
+ const rawSources = (_event$sources = event.sources) !== null && _event$sources !== void 0 ? _event$sources : event.Sources;
10060
+ if (Array.isArray(rawSources) && rawSources.length > 0) {
10061
+ const normalizedSources = rawSources.map(s => {
10062
+ var _ref5, _s$source_type2, _ref6, _s$source_id2, _ref7, _s$source_name2;
10063
+ return {
10064
+ source_type: (_ref5 = (_s$source_type2 = s.source_type) !== null && _s$source_type2 !== void 0 ? _s$source_type2 : s.sourceType) !== null && _ref5 !== void 0 ? _ref5 : 'file',
10065
+ source_id: (_ref6 = (_s$source_id2 = s.source_id) !== null && _s$source_id2 !== void 0 ? _s$source_id2 : s.sourceid) !== null && _ref6 !== void 0 ? _ref6 : s.sourceId,
10066
+ source_name: (_ref7 = (_s$source_name2 = s.source_name) !== null && _s$source_name2 !== void 0 ? _s$source_name2 : s.sourceName) !== null && _ref7 !== void 0 ? _ref7 : '',
10067
+ url: s.url
10068
+ };
10069
+ }).filter(s => s.source_id || s.url);
10070
+ if (normalizedSources.length === 0) return;
10032
10071
  setBidiMessages(prev => {
10033
10072
  const updated = [...prev];
10073
+ let attached = false;
10034
10074
  for (let i = updated.length - 1; i >= 0; i--) {
10035
10075
  if (updated[i].role === 'assistant') {
10036
10076
  updated[i] = _objectSpread2(_objectSpread2({}, updated[i]), {}, {
10037
- sources: event.sources
10077
+ sources: normalizedSources
10038
10078
  });
10079
+ attached = true;
10039
10080
  break;
10040
10081
  }
10041
10082
  }
10083
+ if (!attached) {
10084
+ pendingVoiceSourcesRef.current = normalizedSources;
10085
+ }
10042
10086
  return updated;
10043
10087
  });
10044
- return;
10045
10088
  }
10046
10089
 
10047
10090
  // CRITICAL: Ignore assistant responses until user has spoken
@@ -62846,6 +62889,7 @@
62846
62889
  const hasUserMessages = messages === null || messages === void 0 || (_messages$some = messages.some) === null || _messages$some === void 0 ? void 0 : _messages$some.call(messages, msg => msg.role === "user");
62847
62890
  const shouldShowQuickQuestions = !hasUserMessages && quickQuestions.length > 0 && quickQuestionsEnabled;
62848
62891
  const hasOnboardingQuestions = onboardingQuestions && onboardingQuestions.length > 0;
62892
+ const isCsatEnabled = Boolean(csatEnabled);
62849
62893
  const shouldHideResolveForOnboarding = onboardingEnabled && hasOnboardingQuestions && !onboardingCompleted;
62850
62894
  const handleCsatRating = async (rating, format) => {
62851
62895
  setCsatSubmitted(true); // optimistic UI
@@ -62883,6 +62927,7 @@
62883
62927
 
62884
62928
  // Handle Resolve feedback submission
62885
62929
  const handleResolveSubmission = reactExports.useCallback(async () => {
62930
+ if (!isCsatEnabled) return;
62886
62931
  if (isSubmittingResolve || isConversationResolved) return;
62887
62932
  if (resolveRating === 0) return;
62888
62933
  setIsSubmittingResolve(true);
@@ -62932,10 +62977,14 @@
62932
62977
  } finally {
62933
62978
  setIsSubmittingResolve(false);
62934
62979
  }
62935
- }, [isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, organizationId, conciergeId, apiBaseUrl, csatFormat, onResetConversation]);
62980
+ }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, organizationId, conciergeId, apiBaseUrl, csatFormat, onResetConversation]);
62936
62981
 
62937
62982
  // Handle X button: first click shows form, second click closes
62938
62983
  const handleCloseClick = reactExports.useCallback(() => {
62984
+ if (!isCsatEnabled) {
62985
+ onClose === null || onClose === void 0 || onClose();
62986
+ return;
62987
+ }
62939
62988
  if (showResolveForm) {
62940
62989
  setShowResolveForm(false);
62941
62990
  onClose === null || onClose === void 0 || onClose();
@@ -62944,7 +62993,14 @@
62944
62993
  } else {
62945
62994
  onClose === null || onClose === void 0 || onClose();
62946
62995
  }
62947
- }, [showResolveForm, isConversationResolved, onClose]);
62996
+ }, [isCsatEnabled, showResolveForm, isConversationResolved, onClose]);
62997
+ reactExports.useEffect(() => {
62998
+ if (isCsatEnabled) return;
62999
+ if (showResolveForm) setShowResolveForm(false);
63000
+ if (isConversationResolved) setIsConversationResolved(false);
63001
+ if (resolveRating !== 0) setResolveRating(0);
63002
+ if (resolveFeedbackText) setResolveFeedbackText("");
63003
+ }, [isCsatEnabled, showResolveForm, isConversationResolved, resolveRating, resolveFeedbackText]);
62948
63004
  const latestPromptSuggestions = reactExports.useMemo(() => {
62949
63005
  if (!messages || !Array.isArray(messages)) return [];
62950
63006
  for (let i = messages.length - 1; i >= 0; i -= 1) {
@@ -63209,7 +63265,7 @@
63209
63265
  userMessageBubbleFontSize: userMessageBubbleFontSize,
63210
63266
  fontSize: fontSize,
63211
63267
  isRtl: isRtlLanguage,
63212
- csatVisible: showCsat && csatEnabled && !csatSubmitted || showResolveForm || isConversationResolved,
63268
+ csatVisible: showCsat && csatEnabled && !csatSubmitted || isCsatEnabled && (showResolveForm || isConversationResolved),
63213
63269
  enableVoiceInteraction: enableVoiceInteraction,
63214
63270
  isVoiceSessionActive: isVoiceSessionActive,
63215
63271
  voiceStatus: voiceStatus,
@@ -63230,8 +63286,11 @@
63230
63286
  userTextColor: userTextColor,
63231
63287
  promptSuggestions: showResolveForm || isConversationResolved || autoPromptEnabled === false || isLoading ? [] : stablePromptSuggestions,
63232
63288
  onPromptSuggestionClick: onPromptSuggestionClick !== null && onPromptSuggestionClick !== void 0 ? onPromptSuggestionClick : text => onDirectSend === null || onDirectSend === void 0 ? void 0 : onDirectSend(text),
63233
- showResolveButton: hasUserMessages && !isConversationResolved && !showResolveForm && !showCsat && !shouldHideResolveForOnboarding,
63234
- onResolve: () => setShowResolveForm(true)
63289
+ showResolveButton: isCsatEnabled && hasUserMessages && !isConversationResolved && !showResolveForm && !showCsat && !shouldHideResolveForOnboarding,
63290
+ onResolve: () => {
63291
+ if (!isCsatEnabled) return;
63292
+ setShowResolveForm(true);
63293
+ }
63235
63294
  })]
63236
63295
  })]
63237
63296
  });
@@ -63350,7 +63409,7 @@
63350
63409
 
63351
63410
  // Map API response to widget props (use props as overrides if provided)
63352
63411
  const widgetConfig = reactExports.useMemo(() => {
63353
- var _ref2, _ref3, _ref4, _ref5, _agentConfig$chatbotA, _agentConfig$chatbotA2, _ref6, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _agentConfig$voiceGen, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _agentConfig$inactivi, _agentConfig$inactivi2, _ref26, _ref27, _ref28, _ref29, _ref30, _ref31, _ref32, _ref33, _ref34, _ref35, _ref36, _ref37, _agentConfig$csatEnab, _agentConfig$csatForm, _agentConfig$csatProm, _agentConfig$csatTrig, _agentConfig$csatIdle, _agentConfig$csatFoll, _agentConfig$csatFoll2, _agentConfig$autoProm, _agentConfig$allowFee;
63412
+ var _ref2, _ref3, _ref4, _ref5, _agentConfig$chatbotA, _agentConfig$chatbotA2, _ref6, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _agentConfig$inactivi, _agentConfig$inactivi2, _ref26, _ref27, _ref28, _ref29, _ref30, _ref31, _ref32, _ref33, _ref34, _ref35, _ref36, _ref37, _agentConfig$csatEnab, _agentConfig$csatForm, _agentConfig$csatProm, _agentConfig$csatTrig, _agentConfig$csatIdle, _agentConfig$csatFoll, _agentConfig$csatFoll2, _agentConfig$autoProm, _agentConfig$allowFee;
63354
63413
  if (!agentConfig) {
63355
63414
  return null;
63356
63415
  }
@@ -63378,7 +63437,6 @@
63378
63437
  headerTextBold: (_ref16 = headerTextBold !== null && headerTextBold !== void 0 ? headerTextBold : agentConfig.headerTextBold) !== null && _ref16 !== void 0 ? _ref16 : false,
63379
63438
  headerTextItalic: (_ref17 = headerTextItalic !== null && headerTextItalic !== void 0 ? headerTextItalic : agentConfig.headerTextItalic) !== null && _ref17 !== void 0 ? _ref17 : false,
63380
63439
  enableVoiceInteraction: (_ref18 = enableVoiceInteraction !== null && enableVoiceInteraction !== void 0 ? enableVoiceInteraction : agentConfig.enableVoiceInteraction) !== null && _ref18 !== void 0 ? _ref18 : false,
63381
- voiceGender: (_agentConfig$voiceGen = agentConfig.voiceGender) !== null && _agentConfig$voiceGen !== void 0 ? _agentConfig$voiceGen : 'male',
63382
63440
  disclaimerEnabled: (_ref19 = disclaimerEnabled !== null && disclaimerEnabled !== void 0 ? disclaimerEnabled : agentConfig.disclaimerEnabled) !== null && _ref19 !== void 0 ? _ref19 : false,
63383
63441
  disclaimerText: disclaimerText !== null && disclaimerText !== void 0 ? disclaimerText : agentConfig.disclaimerText,
63384
63442
  disclaimerPosition: (_ref20 = disclaimerPosition !== null && disclaimerPosition !== void 0 ? disclaimerPosition : agentConfig.disclaimerPosition) !== null && _ref20 !== void 0 ? _ref20 : "top",
@@ -63480,7 +63538,6 @@
63480
63538
  openDelay,
63481
63539
  darkMode: widgetConfig.darkMode,
63482
63540
  enableVoiceInteraction: widgetConfig.enableVoiceInteraction,
63483
- voiceGender: widgetConfig.voiceGender,
63484
63541
  onboardingQuestions: widgetConfig.onboardingQuestions,
63485
63542
  onboardingEnabled: widgetConfig.onboardingEnabled,
63486
63543
  collectionPrompt: widgetConfig.collectionPrompt,