@eshal-bot/chat-widget 0.1.45 → 0.1.47

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.
@@ -9062,6 +9062,27 @@
9062
9062
  };
9063
9063
  };
9064
9064
 
9065
+ // Merge the text (SSE) and voice (Bidi) message arrays into one chronological
9066
+ // transcript: tag text messages with a default source, dedupe by id, and sort
9067
+ // by timestamp. Used for BOTH live display (activeMessages) and the
9068
+ // localStorage snapshot — so voice turns persist across reload exactly like
9069
+ // text turns. (Previously the snapshot saved only the text array, so voice
9070
+ // vanished on reload even though it rendered live and was saved server-side
9071
+ // in the Inbox.)
9072
+ const mergeTranscript = (messages, bidiMessages) => {
9073
+ const allMessages = [...messages.map(msg => _objectSpread2(_objectSpread2({}, msg), {}, {
9074
+ metadata: msg.metadata || {
9075
+ source: "sse"
9076
+ }
9077
+ })), ...bidiMessages];
9078
+ const uniqueMessages = Array.from(new Map(allMessages.map(msg => [msg.id, msg])).values());
9079
+ return uniqueMessages.sort((a, b) => {
9080
+ const timeA = a.timestamp instanceof Date ? a.timestamp.getTime() : new Date(a.timestamp).getTime();
9081
+ const timeB = b.timestamp instanceof Date ? b.timestamp.getTime() : new Date(b.timestamp).getTime();
9082
+ return timeA - timeB;
9083
+ });
9084
+ };
9085
+
9065
9086
  // Helper functions for onboarding
9066
9087
  const getOnboardingQuestions = onboardingQuestions => {
9067
9088
  if (onboardingQuestions && onboardingQuestions.length > 0) {
@@ -9457,8 +9478,12 @@
9457
9478
  reactExports.useEffect(() => {
9458
9479
  if (!organizationId || !bidiSessionId) return;
9459
9480
  if (isConversationLoading || isLoading) return;
9460
- saveSessionMessages(organizationId, bidiSessionId, messages, resolvedMessageLimits);
9461
- }, [messages, organizationId, bidiSessionId, isConversationLoading, isLoading, resolvedMessageLimits]);
9481
+ // Persist the MERGED transcript (text + voice). Previously only `messages`
9482
+ // (text) was saved, so bidi/voice turns — which live in `bidiMessages` —
9483
+ // were dropped on reload even though they render live and are saved
9484
+ // server-side (Inbox). Merging here makes voice survive reload like text.
9485
+ saveSessionMessages(organizationId, bidiSessionId, mergeTranscript(messages, bidiMessages), resolvedMessageLimits);
9486
+ }, [messages, bidiMessages, organizationId, bidiSessionId, isConversationLoading, isLoading, resolvedMessageLimits]);
9462
9487
 
9463
9488
  // Show the onboarding form on first load; bypass it if already completed (persisted in session)
9464
9489
  reactExports.useEffect(() => {
@@ -10950,28 +10975,7 @@
10950
10975
 
10951
10976
  // Merge and sort messages from both sources (text and voice) by timestamp
10952
10977
  // This ensures all messages persist regardless of voice session state
10953
- const activeMessages = reactExports.useMemo(() => {
10954
- // Merge both message arrays
10955
- const allMessages = [
10956
- // Convert regular messages to include metadata if not present
10957
- ...messages.map(msg => _objectSpread2(_objectSpread2({}, msg), {}, {
10958
- metadata: msg.metadata || {
10959
- source: "sse"
10960
- }
10961
- })),
10962
- // Include bidi messages
10963
- ...bidiMessages];
10964
-
10965
- // Remove duplicates based on message ID (in case same message exists in both arrays)
10966
- const uniqueMessages = Array.from(new Map(allMessages.map(msg => [msg.id, msg])).values());
10967
-
10968
- // Sort by timestamp to maintain chronological order
10969
- return uniqueMessages.sort((a, b) => {
10970
- const timeA = a.timestamp instanceof Date ? a.timestamp.getTime() : new Date(a.timestamp).getTime();
10971
- const timeB = b.timestamp instanceof Date ? b.timestamp.getTime() : new Date(b.timestamp).getTime();
10972
- return timeA - timeB;
10973
- });
10974
- }, [bidiMessages, messages]);
10978
+ const activeMessages = reactExports.useMemo(() => mergeTranscript(messages, bidiMessages), [bidiMessages, messages]);
10975
10979
 
10976
10980
  // Log message state changes for debugging
10977
10981
  reactExports.useEffect(() => {
@@ -60077,6 +60081,21 @@
60077
60081
  // Handle X button: first click shows form, second click closes
60078
60082
  const handleCloseClick = reactExports.useCallback(() => {
60079
60083
  if (!isCsatEnabled) {
60084
+ // CSAT disabled: closing the chat should also clear out the
60085
+ // conversation, mirroring the post-CSAT clear in
60086
+ // handleResolveSubmission. Without this the X button only hides
60087
+ // the window while the old transcript persists in the session and
60088
+ // re-appears on reopen / reload. Route through onRefreshChat (same
60089
+ // entry point the kebab "Refresh Chat" uses) so any change to the
60090
+ // refresh path applies here too, and clear widgetFilter explicitly.
60091
+ if (onWidgetFilterChange) {
60092
+ onWidgetFilterChange(null);
60093
+ }
60094
+ if (onRefreshChat) {
60095
+ onRefreshChat();
60096
+ } else if (onResetConversation) {
60097
+ onResetConversation();
60098
+ }
60080
60099
  onClose === null || onClose === void 0 || onClose();
60081
60100
  return;
60082
60101
  }
@@ -60088,7 +60107,7 @@
60088
60107
  } else {
60089
60108
  onClose === null || onClose === void 0 || onClose();
60090
60109
  }
60091
- }, [isCsatEnabled, showResolveForm, isConversationResolved, onClose]);
60110
+ }, [isCsatEnabled, showResolveForm, isConversationResolved, onClose, onWidgetFilterChange, onRefreshChat, onResetConversation]);
60092
60111
  reactExports.useEffect(() => {
60093
60112
  if (isCsatEnabled) return;
60094
60113
  if (showResolveForm) setShowResolveForm(false);