@iblai/web-utils 1.6.9 → 1.7.0

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
@@ -9859,6 +9859,7 @@ function TenantProvider({ children, fallback, onAuthSuccess, onAuthFailure, curr
9859
9859
  is_advertising: !!((_d = tenantMetadata === null || tenantMetadata === void 0 ? void 0 : tenantMetadata.metadata) === null || _d === void 0 ? void 0 : _d.is_advertising),
9860
9860
  is_enterprise: tenant.is_enterprise,
9861
9861
  show_paywall: tenant.show_paywall,
9862
+ enable_monetization: tenant.enable_monetization,
9862
9863
  });
9863
9864
  const rbacPermissions = await loadPlatformPermissions(tenant.key);
9864
9865
  onLoadPlatformPermissions === null || onLoadPlatformPermissions === void 0 ? void 0 : onLoadPlatformPermissions(rbacPermissions);
@@ -11040,8 +11041,16 @@ const chatSlice = createSlice({
11040
11041
  state.chats = action.payload;
11041
11042
  },
11042
11043
  appendMessageToActiveTab: (state) => {
11043
- const lastMessageInActiveTabMessages = state.chats[state.activeTab][state.chats[state.activeTab].length - 1];
11044
- if (lastMessageInActiveTabMessages.id !== state.currentStreamingMessage.id) {
11044
+ const activeMessages = state.chats[state.activeTab];
11045
+ // Defensive: if the active tab has no messages yet (e.g. a stale
11046
+ // `setNewMessages([])` raced ahead of the user-message dispatch),
11047
+ // skip the lookup that would throw on `undefined.id` and rely on
11048
+ // the no-last-message branch below to append the streaming message.
11049
+ const lastMessageInActiveTabMessages = activeMessages && activeMessages.length > 0
11050
+ ? activeMessages[activeMessages.length - 1]
11051
+ : undefined;
11052
+ if (!lastMessageInActiveTabMessages ||
11053
+ lastMessageInActiveTabMessages.id !== state.currentStreamingMessage.id) {
11045
11054
  const temp = {
11046
11055
  id: state.currentStreamingMessage.id,
11047
11056
  role: "assistant",
@@ -13550,7 +13559,7 @@ function useMentorSettings({ mentorId, tenantKey, isPublicRoute = false, mainTen
13550
13559
  };
13551
13560
  }
13552
13561
 
13553
- function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, token, wsUrl, stopGenerationWsUrl, redirectToAuthSpa, errorHandler, sendMessageToParentWebsite, isPreviewMode, mentorShareableToken, on402Error, cachedSessionId, onStartNewChat, onOAuthRequired, onOAuthResolved, isOffline = false, onOfflineWithoutLocalLLM, isPublicRoute, }) {
13562
+ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, token, wsUrl, stopGenerationWsUrl, redirectToAuthSpa, errorHandler, sendMessageToParentWebsite, isPreviewMode, mentorShareableToken, on402Error, cachedSessionId, onStartNewChat, onOAuthRequired, onOAuthResolved, isOffline = false, onOfflineWithoutLocalLLM, isPublicRoute, initialPrompt, }) {
13554
13563
  var _a, _b, _c, _d;
13555
13564
  const dispatch = useDispatch();
13556
13565
  const [createSessionId, { isLoading: isLoadingSessionIds }] = dataLayer.useCreateSessionIdMutation();
@@ -13605,10 +13614,41 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13605
13614
  onOfflineWithoutLocalLLM,
13606
13615
  });
13607
13616
  const [isLoadingChats, setIsLoadingChats] = React.useState(true);
13617
+ // Tracks whether the optional `initialPrompt` prop has already been
13618
+ // auto-submitted for this hook instance. Set to `true` the moment the
13619
+ // effect decides to act (either to send or because the prompt is already
13620
+ // the most recent user message) so subsequent re-renders never re-fire.
13621
+ const initialPromptInjectedRef = React.useRef(false);
13622
+ // Tracks whether a `startNewChat()` call is already mid-flight. The
13623
+ // auto-start `useEffect` below re-runs whenever any of its deps change
13624
+ // (`shouldStartNewChat` in particular toggles after mount in many
13625
+ // consumers), and the existing `!cachedSessionId?.[mentorId]` guard is
13626
+ // not enough on its own because `cachedSessionId` only updates AFTER
13627
+ // `createSessionId` resolves. Without this ref, dep churn during the
13628
+ // pending API call triggers a second `startNewChat`, producing two
13629
+ // sessions, two `getChats` calls, and an extra `setNewMessages([])`
13630
+ // that wipes anything dispatched (like `initialPrompt`'s
13631
+ // `addUserMessage`) between the two `getChats` resolutions.
13632
+ const startNewChatInFlightRef = React.useRef(false);
13633
+ // Synchronous mirror of `isLoadingChats`. The state setter is async, so a
13634
+ // consumer effect running in the SAME render cycle as `getChats()`
13635
+ // invocation sees a stale `isLoadingChats=false` in its closure even
13636
+ // though `setIsLoadingChats(true)` has been scheduled. The ref flips
13637
+ // immediately (before `getChats` hits its first `await`), giving the
13638
+ // `initialPrompt` injection effect a reliable "fetch in progress" signal
13639
+ // it can read synchronously inside its body.
13640
+ const isLoadingChatsRef = React.useRef(true);
13608
13641
  const [getSessionChats] = dataLayer.useLazyGetSessionIdQuery();
13609
13642
  const [getSharedSessionChats] = dataLayer.useLazyGetSharedSessionIdQuery();
13610
13643
  const getChats = React.useCallback(async () => {
13611
13644
  var _a, _b, _c, _d;
13645
+ // Mark the fetch as in-flight so consumers gated on `isLoadingChats`
13646
+ // (e.g. the `initialPrompt` injection effect) defer until the resulting
13647
+ // `setNewMessages` has landed. The state setter alone is not enough —
13648
+ // it's async and the consumer effect may run in the same render cycle
13649
+ // with the stale closure — so flip the synchronous ref FIRST.
13650
+ isLoadingChatsRef.current = true;
13651
+ setIsLoadingChats(true);
13612
13652
  let data;
13613
13653
  try {
13614
13654
  if (showingSharedChat) {
@@ -13693,6 +13733,7 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13693
13733
  }
13694
13734
  finally {
13695
13735
  setIsLoadingChats(false);
13736
+ isLoadingChatsRef.current = false;
13696
13737
  }
13697
13738
  }, [
13698
13739
  showingSharedChat,
@@ -13704,21 +13745,30 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13704
13745
  getSessionChats,
13705
13746
  dispatch,
13706
13747
  ]);
13748
+ // Depend on the indexed string session id, not the whole `cachedSessionId`
13749
+ // object. Consumers (e.g. `useLocalStorage`) often re-emit a new object
13750
+ // reference on every render even when the underlying value is unchanged,
13751
+ // which would otherwise cause this effect to re-fire `getChats()` per
13752
+ // render — and each `setNewMessages` would wipe any user message
13753
+ // dispatched in between (the URL `initialPrompt` race).
13754
+ const currentMentorSessionId = cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId];
13707
13755
  React.useEffect(() => {
13708
- if (cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId]) {
13709
- dispatch(chatActions.updateSessionIds(cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId]));
13756
+ if (currentMentorSessionId) {
13757
+ dispatch(chatActions.updateSessionIds(currentMentorSessionId));
13710
13758
  // Skip fetching previous chats when offline
13711
13759
  if (!isOffline) {
13712
13760
  getChats();
13713
13761
  }
13714
13762
  else {
13715
13763
  setIsLoadingChats(false);
13764
+ isLoadingChatsRef.current = false;
13716
13765
  }
13717
13766
  }
13718
13767
  else {
13719
13768
  setIsLoadingChats(false);
13769
+ isLoadingChatsRef.current = false;
13720
13770
  }
13721
- }, [cachedSessionId, isOffline]);
13771
+ }, [currentMentorSessionId, isOffline]);
13722
13772
  const startNewChat = React.useCallback(async () => {
13723
13773
  // Reset all chat state
13724
13774
  if (!showingSharedChat) {
@@ -13781,10 +13831,15 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13781
13831
  errorHandler,
13782
13832
  ]);
13783
13833
  React.useEffect(() => {
13834
+ if (startNewChatInFlightRef.current)
13835
+ return;
13784
13836
  if (!showingSharedChat || mentorSettings.allowAnonymous) {
13785
13837
  if (mentorSettings.allowAnonymous !== undefined &&
13786
13838
  !(cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId])) {
13787
- startNewChat();
13839
+ startNewChatInFlightRef.current = true;
13840
+ startNewChat().finally(() => {
13841
+ startNewChatInFlightRef.current = false;
13842
+ });
13788
13843
  }
13789
13844
  }
13790
13845
  }, [shouldStartNewChat, showingSharedChat, mentorSettings.allowAnonymous]);
@@ -13795,6 +13850,76 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13795
13850
  resetConnection();
13796
13851
  }
13797
13852
  }, [sessionIds, activeTab, sessionId, resetConnection]);
13853
+ // Auto-submit `initialPrompt` exactly once per mount after the chat
13854
+ // lifecycle has stabilized. Matches the existing proactive-prompt
13855
+ // precedent in `changeTab` (sendMessage(tab, content)) but is gated on
13856
+ // URL-driven deep-link signals instead of tab transitions.
13857
+ React.useEffect(() => {
13858
+ var _a;
13859
+ if (initialPromptInjectedRef.current)
13860
+ return;
13861
+ const trimmedPrompt = initialPrompt === null || initialPrompt === void 0 ? void 0 : initialPrompt.trim();
13862
+ if (!trimmedPrompt)
13863
+ return;
13864
+ // Bail while any state would make auto-submission unsafe or premature.
13865
+ if (isPreviewMode)
13866
+ return;
13867
+ if (showingSharedChat)
13868
+ return;
13869
+ if (isOffline)
13870
+ return;
13871
+ // Two-layer guard: the state catches re-runs after `getChats` settles
13872
+ // (which schedule a new render). The ref catches the in-render race
13873
+ // where this effect runs in the same cycle that `getChats` was invoked
13874
+ // — the state setter is async and the closure has a stale `false`.
13875
+ if (isLoadingChats)
13876
+ return;
13877
+ if (isLoadingChatsRef.current)
13878
+ return;
13879
+ if (!sessionIds[activeTab])
13880
+ return;
13881
+ if (streaming)
13882
+ return;
13883
+ if (isPending)
13884
+ return;
13885
+ // Dedup: scan back through the loaded history for the most recent
13886
+ // user message and compare its content. If it matches, this session
13887
+ // already has the prompt as a user turn (typical case: reload of a
13888
+ // `?prompt=` URL — the cached history ends with the assistant reply,
13889
+ // so the matching user message is one step back).
13890
+ const messages = chats[activeTab];
13891
+ let lastUserMessage;
13892
+ if (messages && messages.length > 0) {
13893
+ for (let i = messages.length - 1; i >= 0; i--) {
13894
+ if (((_a = messages[i]) === null || _a === void 0 ? void 0 : _a.role) === "user") {
13895
+ lastUserMessage = messages[i];
13896
+ break;
13897
+ }
13898
+ }
13899
+ }
13900
+ if (lastUserMessage &&
13901
+ typeof lastUserMessage.content === "string" &&
13902
+ lastUserMessage.content === trimmedPrompt) {
13903
+ initialPromptInjectedRef.current = true;
13904
+ return;
13905
+ }
13906
+ // Mark BEFORE dispatching: sendMessage triggers state updates that may
13907
+ // re-run this effect, and we want the guard active before any re-fire.
13908
+ initialPromptInjectedRef.current = true;
13909
+ sendMessage(activeTab, trimmedPrompt);
13910
+ }, [
13911
+ initialPrompt,
13912
+ isPreviewMode,
13913
+ showingSharedChat,
13914
+ isOffline,
13915
+ isLoadingChats,
13916
+ sessionIds,
13917
+ activeTab,
13918
+ chats,
13919
+ streaming,
13920
+ isPending,
13921
+ sendMessage,
13922
+ ]);
13798
13923
  async function changeTab(tab) {
13799
13924
  var _a, _b, _c, _d;
13800
13925
  // while responding to a message, do not change the tab