@iblai/web-utils 1.6.9 → 1.6.10

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