@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.d.ts CHANGED
@@ -761,6 +761,7 @@ interface Tenant {
761
761
  is_advertising?: boolean;
762
762
  is_enterprise?: boolean;
763
763
  show_paywall?: boolean;
764
+ enable_monetization?: boolean;
764
765
  }
765
766
 
766
767
  /**
@@ -1438,8 +1439,9 @@ type Props$7 = {
1438
1439
  isOffline?: boolean;
1439
1440
  onOfflineWithoutLocalLLM?: () => void;
1440
1441
  isPublicRoute?: boolean;
1442
+ initialPrompt?: string;
1441
1443
  };
1442
- declare function useAdvancedChat({ tenantKey, mentorId, username, token, wsUrl, stopGenerationWsUrl, redirectToAuthSpa, errorHandler, sendMessageToParentWebsite, isPreviewMode, mentorShareableToken, on402Error, cachedSessionId, onStartNewChat, onOAuthRequired, onOAuthResolved, isOffline, onOfflineWithoutLocalLLM, isPublicRoute, }: Props$7): {
1444
+ declare function useAdvancedChat({ tenantKey, mentorId, username, token, wsUrl, stopGenerationWsUrl, redirectToAuthSpa, errorHandler, sendMessageToParentWebsite, isPreviewMode, mentorShareableToken, on402Error, cachedSessionId, onStartNewChat, onOAuthRequired, onOAuthResolved, isOffline, onOfflineWithoutLocalLLM, isPublicRoute, initialPrompt, }: Props$7): {
1443
1445
  messages: Message$1[];
1444
1446
  isStreaming: boolean;
1445
1447
  status: ChatStatus;
package/dist/index.esm.js CHANGED
@@ -9839,6 +9839,7 @@ function TenantProvider({ children, fallback, onAuthSuccess, onAuthFailure, curr
9839
9839
  is_advertising: !!((_d = tenantMetadata === null || tenantMetadata === void 0 ? void 0 : tenantMetadata.metadata) === null || _d === void 0 ? void 0 : _d.is_advertising),
9840
9840
  is_enterprise: tenant.is_enterprise,
9841
9841
  show_paywall: tenant.show_paywall,
9842
+ enable_monetization: tenant.enable_monetization,
9842
9843
  });
9843
9844
  const rbacPermissions = await loadPlatformPermissions(tenant.key);
9844
9845
  onLoadPlatformPermissions === null || onLoadPlatformPermissions === void 0 ? void 0 : onLoadPlatformPermissions(rbacPermissions);
@@ -11020,8 +11021,16 @@ const chatSlice = createSlice({
11020
11021
  state.chats = action.payload;
11021
11022
  },
11022
11023
  appendMessageToActiveTab: (state) => {
11023
- const lastMessageInActiveTabMessages = state.chats[state.activeTab][state.chats[state.activeTab].length - 1];
11024
- if (lastMessageInActiveTabMessages.id !== state.currentStreamingMessage.id) {
11024
+ const activeMessages = state.chats[state.activeTab];
11025
+ // Defensive: if the active tab has no messages yet (e.g. a stale
11026
+ // `setNewMessages([])` raced ahead of the user-message dispatch),
11027
+ // skip the lookup that would throw on `undefined.id` and rely on
11028
+ // the no-last-message branch below to append the streaming message.
11029
+ const lastMessageInActiveTabMessages = activeMessages && activeMessages.length > 0
11030
+ ? activeMessages[activeMessages.length - 1]
11031
+ : undefined;
11032
+ if (!lastMessageInActiveTabMessages ||
11033
+ lastMessageInActiveTabMessages.id !== state.currentStreamingMessage.id) {
11025
11034
  const temp = {
11026
11035
  id: state.currentStreamingMessage.id,
11027
11036
  role: "assistant",
@@ -13530,7 +13539,7 @@ function useMentorSettings({ mentorId, tenantKey, isPublicRoute = false, mainTen
13530
13539
  };
13531
13540
  }
13532
13541
 
13533
- function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, token, wsUrl, stopGenerationWsUrl, redirectToAuthSpa, errorHandler, sendMessageToParentWebsite, isPreviewMode, mentorShareableToken, on402Error, cachedSessionId, onStartNewChat, onOAuthRequired, onOAuthResolved, isOffline = false, onOfflineWithoutLocalLLM, isPublicRoute, }) {
13542
+ 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, }) {
13534
13543
  var _a, _b, _c, _d;
13535
13544
  const dispatch = useDispatch();
13536
13545
  const [createSessionId, { isLoading: isLoadingSessionIds }] = useCreateSessionIdMutation();
@@ -13585,10 +13594,41 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13585
13594
  onOfflineWithoutLocalLLM,
13586
13595
  });
13587
13596
  const [isLoadingChats, setIsLoadingChats] = useState(true);
13597
+ // Tracks whether the optional `initialPrompt` prop has already been
13598
+ // auto-submitted for this hook instance. Set to `true` the moment the
13599
+ // effect decides to act (either to send or because the prompt is already
13600
+ // the most recent user message) so subsequent re-renders never re-fire.
13601
+ const initialPromptInjectedRef = React__default.useRef(false);
13602
+ // Tracks whether a `startNewChat()` call is already mid-flight. The
13603
+ // auto-start `useEffect` below re-runs whenever any of its deps change
13604
+ // (`shouldStartNewChat` in particular toggles after mount in many
13605
+ // consumers), and the existing `!cachedSessionId?.[mentorId]` guard is
13606
+ // not enough on its own because `cachedSessionId` only updates AFTER
13607
+ // `createSessionId` resolves. Without this ref, dep churn during the
13608
+ // pending API call triggers a second `startNewChat`, producing two
13609
+ // sessions, two `getChats` calls, and an extra `setNewMessages([])`
13610
+ // that wipes anything dispatched (like `initialPrompt`'s
13611
+ // `addUserMessage`) between the two `getChats` resolutions.
13612
+ const startNewChatInFlightRef = React__default.useRef(false);
13613
+ // Synchronous mirror of `isLoadingChats`. The state setter is async, so a
13614
+ // consumer effect running in the SAME render cycle as `getChats()`
13615
+ // invocation sees a stale `isLoadingChats=false` in its closure even
13616
+ // though `setIsLoadingChats(true)` has been scheduled. The ref flips
13617
+ // immediately (before `getChats` hits its first `await`), giving the
13618
+ // `initialPrompt` injection effect a reliable "fetch in progress" signal
13619
+ // it can read synchronously inside its body.
13620
+ const isLoadingChatsRef = React__default.useRef(true);
13588
13621
  const [getSessionChats] = useLazyGetSessionIdQuery();
13589
13622
  const [getSharedSessionChats] = useLazyGetSharedSessionIdQuery();
13590
13623
  const getChats = React__default.useCallback(async () => {
13591
13624
  var _a, _b, _c, _d;
13625
+ // Mark the fetch as in-flight so consumers gated on `isLoadingChats`
13626
+ // (e.g. the `initialPrompt` injection effect) defer until the resulting
13627
+ // `setNewMessages` has landed. The state setter alone is not enough —
13628
+ // it's async and the consumer effect may run in the same render cycle
13629
+ // with the stale closure — so flip the synchronous ref FIRST.
13630
+ isLoadingChatsRef.current = true;
13631
+ setIsLoadingChats(true);
13592
13632
  let data;
13593
13633
  try {
13594
13634
  if (showingSharedChat) {
@@ -13673,6 +13713,7 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13673
13713
  }
13674
13714
  finally {
13675
13715
  setIsLoadingChats(false);
13716
+ isLoadingChatsRef.current = false;
13676
13717
  }
13677
13718
  }, [
13678
13719
  showingSharedChat,
@@ -13684,21 +13725,30 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13684
13725
  getSessionChats,
13685
13726
  dispatch,
13686
13727
  ]);
13728
+ // Depend on the indexed string session id, not the whole `cachedSessionId`
13729
+ // object. Consumers (e.g. `useLocalStorage`) often re-emit a new object
13730
+ // reference on every render even when the underlying value is unchanged,
13731
+ // which would otherwise cause this effect to re-fire `getChats()` per
13732
+ // render — and each `setNewMessages` would wipe any user message
13733
+ // dispatched in between (the URL `initialPrompt` race).
13734
+ const currentMentorSessionId = cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId];
13687
13735
  useEffect(() => {
13688
- if (cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId]) {
13689
- dispatch(chatActions.updateSessionIds(cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId]));
13736
+ if (currentMentorSessionId) {
13737
+ dispatch(chatActions.updateSessionIds(currentMentorSessionId));
13690
13738
  // Skip fetching previous chats when offline
13691
13739
  if (!isOffline) {
13692
13740
  getChats();
13693
13741
  }
13694
13742
  else {
13695
13743
  setIsLoadingChats(false);
13744
+ isLoadingChatsRef.current = false;
13696
13745
  }
13697
13746
  }
13698
13747
  else {
13699
13748
  setIsLoadingChats(false);
13749
+ isLoadingChatsRef.current = false;
13700
13750
  }
13701
- }, [cachedSessionId, isOffline]);
13751
+ }, [currentMentorSessionId, isOffline]);
13702
13752
  const startNewChat = React__default.useCallback(async () => {
13703
13753
  // Reset all chat state
13704
13754
  if (!showingSharedChat) {
@@ -13761,10 +13811,15 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13761
13811
  errorHandler,
13762
13812
  ]);
13763
13813
  React__default.useEffect(() => {
13814
+ if (startNewChatInFlightRef.current)
13815
+ return;
13764
13816
  if (!showingSharedChat || mentorSettings.allowAnonymous) {
13765
13817
  if (mentorSettings.allowAnonymous !== undefined &&
13766
13818
  !(cachedSessionId === null || cachedSessionId === void 0 ? void 0 : cachedSessionId[mentorId])) {
13767
- startNewChat();
13819
+ startNewChatInFlightRef.current = true;
13820
+ startNewChat().finally(() => {
13821
+ startNewChatInFlightRef.current = false;
13822
+ });
13768
13823
  }
13769
13824
  }
13770
13825
  }, [shouldStartNewChat, showingSharedChat, mentorSettings.allowAnonymous]);
@@ -13775,6 +13830,76 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13775
13830
  resetConnection();
13776
13831
  }
13777
13832
  }, [sessionIds, activeTab, sessionId, resetConnection]);
13833
+ // Auto-submit `initialPrompt` exactly once per mount after the chat
13834
+ // lifecycle has stabilized. Matches the existing proactive-prompt
13835
+ // precedent in `changeTab` (sendMessage(tab, content)) but is gated on
13836
+ // URL-driven deep-link signals instead of tab transitions.
13837
+ React__default.useEffect(() => {
13838
+ var _a;
13839
+ if (initialPromptInjectedRef.current)
13840
+ return;
13841
+ const trimmedPrompt = initialPrompt === null || initialPrompt === void 0 ? void 0 : initialPrompt.trim();
13842
+ if (!trimmedPrompt)
13843
+ return;
13844
+ // Bail while any state would make auto-submission unsafe or premature.
13845
+ if (isPreviewMode)
13846
+ return;
13847
+ if (showingSharedChat)
13848
+ return;
13849
+ if (isOffline)
13850
+ return;
13851
+ // Two-layer guard: the state catches re-runs after `getChats` settles
13852
+ // (which schedule a new render). The ref catches the in-render race
13853
+ // where this effect runs in the same cycle that `getChats` was invoked
13854
+ // — the state setter is async and the closure has a stale `false`.
13855
+ if (isLoadingChats)
13856
+ return;
13857
+ if (isLoadingChatsRef.current)
13858
+ return;
13859
+ if (!sessionIds[activeTab])
13860
+ return;
13861
+ if (streaming)
13862
+ return;
13863
+ if (isPending)
13864
+ return;
13865
+ // Dedup: scan back through the loaded history for the most recent
13866
+ // user message and compare its content. If it matches, this session
13867
+ // already has the prompt as a user turn (typical case: reload of a
13868
+ // `?prompt=` URL — the cached history ends with the assistant reply,
13869
+ // so the matching user message is one step back).
13870
+ const messages = chats[activeTab];
13871
+ let lastUserMessage;
13872
+ if (messages && messages.length > 0) {
13873
+ for (let i = messages.length - 1; i >= 0; i--) {
13874
+ if (((_a = messages[i]) === null || _a === void 0 ? void 0 : _a.role) === "user") {
13875
+ lastUserMessage = messages[i];
13876
+ break;
13877
+ }
13878
+ }
13879
+ }
13880
+ if (lastUserMessage &&
13881
+ typeof lastUserMessage.content === "string" &&
13882
+ lastUserMessage.content === trimmedPrompt) {
13883
+ initialPromptInjectedRef.current = true;
13884
+ return;
13885
+ }
13886
+ // Mark BEFORE dispatching: sendMessage triggers state updates that may
13887
+ // re-run this effect, and we want the guard active before any re-fire.
13888
+ initialPromptInjectedRef.current = true;
13889
+ sendMessage(activeTab, trimmedPrompt);
13890
+ }, [
13891
+ initialPrompt,
13892
+ isPreviewMode,
13893
+ showingSharedChat,
13894
+ isOffline,
13895
+ isLoadingChats,
13896
+ sessionIds,
13897
+ activeTab,
13898
+ chats,
13899
+ streaming,
13900
+ isPending,
13901
+ sendMessage,
13902
+ ]);
13778
13903
  async function changeTab(tab) {
13779
13904
  var _a, _b, _c, _d;
13780
13905
  // while responding to a message, do not change the tab