Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions client/src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RefObject } from 'react';
import { FileSources, isEphemeralAgentId } from 'librechat-data-provider';
import type { PaneOptionSettings } from '@librechat/chat/react';
import type { UseMutationResult } from '@tanstack/react-query';
import type * as t from 'librechat-data-provider';
import type { LucideIcon } from 'lucide-react';
Expand Down Expand Up @@ -506,10 +507,7 @@ export type PromptOption = MentionOption & {
id: string;
};

export type TOptionSettings = {
showExamples?: boolean;
isCodeChat?: boolean;
};
export type TOptionSettings = PaneOptionSettings;

export interface ExtendedFile {
file?: File;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ function Harness() {
ask: () => undefined,
regenerate: () => undefined,
setSiblingIdx: () => undefined,
showPopover: false,
setShowPopover: () => undefined,
abortScroll: false,
setAbortScroll: () => undefined,
preset: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ function Harness() {
},
regenerate: () => undefined,
setSiblingIdx: () => undefined,
showPopover: false,
setShowPopover: () => undefined,
abortScroll: false,
setAbortScroll: () => undefined,
preset: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ function Harness({ enterToSend }: { enterToSend: boolean }) {
ask: () => undefined,
regenerate: () => undefined,
setSiblingIdx: () => undefined,
showPopover: false,
setShowPopover: () => undefined,
abortScroll: false,
setAbortScroll: () => undefined,
preset: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ function Harness() {
ask: mockAsk,
regenerate: () => undefined,
setSiblingIdx: () => undefined,
showPopover: false,
setShowPopover: () => undefined,
abortScroll: false,
setAbortScroll: () => undefined,
preset: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ function Harness({
ask: onAsk,
regenerate: () => undefined,
setSiblingIdx: () => undefined,
showPopover: false,
setShowPopover: () => undefined,
abortScroll: false,
setAbortScroll: () => undefined,
preset: null,
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Chat/Menus/PresetsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useRef, useState } from 'react';
import { useAtomValue } from 'jotai';
import { Trans } from 'react-i18next';
import { useRecoilValue } from 'recoil';
import { BookCopy } from 'lucide-react';
import { presetFamily } from '@librechat/chat/react';
import { tConvoUpdateSchema } from 'librechat-data-provider';
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
import {
Expand All @@ -18,7 +19,6 @@ import { SaveAsPresetDialog } from '~/components/Endpoints';
import { EditPresetDialog, PresetItems } from './Presets';
import { useLocalize, usePresets } from '~/hooks';
import { useChatContext } from '~/Providers';
import store from '~/store';

const PresetsMenu: FC = () => {
const localize = useLocalize();
Expand All @@ -41,7 +41,7 @@ const PresetsMenu: FC = () => {
presetToDelete,
confirmDeletePreset,
} = usePresets();
const presetToEdit = useRecoilValue(store.presetByIndex(0));
const presetToEdit = useAtomValue(presetFamily(0));

const openSaveAsDialog = () => {
if (!conversation) {
Expand Down
2 changes: 0 additions & 2 deletions client/src/hooks/Chat/__tests__/facade.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ const createContract = (overrides: Partial<ChatContract> = {}): ChatContract =>
setFiles: noop,
filesLoading: false,
setFilesLoading: noop,
showPopover: false,
setShowPopover: noop,
feedbackEnabled: false,
...overrides,
};
Expand Down
2 changes: 0 additions & 2 deletions client/src/hooks/Chat/__tests__/provider.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ const contract: ChatContract = {
setFiles: noop,
filesLoading: false,
setFilesLoading: noop,
showPopover: false,
setShowPopover: noop,
feedbackEnabled: false,
};

Expand Down
8 changes: 4 additions & 4 deletions client/src/hooks/Chat/__tests__/submitting.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ describe('useIsSubmitting beside a Recoil atom', () => {
const commits: Array<{ text: string; submitting: boolean }> = [];
const { result } = renderHook(
() => {
const preset = useRecoilValue(store.presetByIndex(0));
const prompt = useRecoilValue(store.activePromptByIndex(0));
const submitting = useIsSubmitting(0);
commits.push({ text: preset?.title ?? '', submitting });
return useSetRecoilState(store.presetByIndex(0));
commits.push({ text: prompt ?? '', submitting });
return useSetRecoilState(store.activePromptByIndex(0));
},
{ wrapper },
);

act(() => {
jotaiStore.set(isSubmittingFamily(0), false);
result.current({ endpoint: null, title: 'next' });
result.current('next');
});

const withNewText = commits.filter(({ text }) => text === 'next');
Expand Down
4 changes: 0 additions & 4 deletions client/src/hooks/Chat/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,6 @@ export type ChatFilesContract = {

/** Chat-scoped view state that has no AI SDK counterpart. */
export type ChatViewContract = {
/** Whether the pane's settings popover is open. */
showPopover: boolean;
/** Opens or closes the pane's settings popover. */
setShowPopover: Dispatch<SetStateAction<boolean>>;
/** Whether message feedback controls are enabled by the startup config. */
feedbackEnabled: boolean;
};
Expand Down
11 changes: 4 additions & 7 deletions client/src/hooks/Chat/useChatHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { useAtom, useSetAtom, useStore, useAtomValue } from 'jotai';
import { Constants, QueryKeys, isAssistantsEndpoint } from 'librechat-data-provider';
import {
presetFamily,
optionSettingsFamily,
activeGenerationCreatedAtByConvoId,
activeGenerationProtocolVersionByConvoId,
isSubmittingFamily,
Expand Down Expand Up @@ -382,10 +384,9 @@ export default function useChatHelpers(index = 0, paramId?: string): ChatContrac
[continueGeneration],
);

const [preset, setPreset] = useRecoilState(store.presetByIndex(index));
const [showPopover, setShowPopover] = useRecoilState(store.showPopoverFamily(index));
const [preset, setPreset] = useAtom(presetFamily(index));
const [abortScroll, setAbortScroll] = useAtom(abortScrollFamily(index));
const [optionSettings, setOptionSettings] = useRecoilState(store.optionSettingsFamily(index));
const [optionSettings, setOptionSettings] = useAtom(optionSettingsFamily(index));

/** Read once per chat rather than per message row: message rows never unmount, so a
* per-row settings read would accumulate for the length of the conversation. */
Expand All @@ -412,8 +413,6 @@ export default function useChatHelpers(index = 0, paramId?: string): ChatContrac
handleStopGenerating,
handleRegenerate,
handleContinue,
showPopover,
setShowPopover,
abortScroll,
setAbortScroll,
preset,
Expand Down Expand Up @@ -445,8 +444,6 @@ export default function useChatHelpers(index = 0, paramId?: string): ChatContrac
handleStopGenerating,
handleRegenerate,
handleContinue,
showPopover,
setShowPopover,
abortScroll,
setAbortScroll,
preset,
Expand Down
32 changes: 32 additions & 0 deletions client/src/hooks/Config/__tests__/useClearStates.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { act, renderHook } from '@testing-library/react';
import { createStore, Provider as JotaiProvider } from 'jotai';
import { Constants, ReasoningEffort } from 'librechat-data-provider';
import {
presetFamily,
abortScrollFamily,
optionSettingsFamily,
showStopButtonByIndex,
submissionStartFamily,
} from '@librechat/chat/react';
import type { TPreset } from 'librechat-data-provider';
import {
pendingQuotesByConvoId,
pendingManualSkillsByConvoId,
Expand Down Expand Up @@ -119,4 +122,33 @@ describe('useClearStates', () => {
expected.map((kept) => (kept ? 1000 : null)),
);
});
/* The preset and option toggles of each pane are per-session choices: a pane
that survives sign-out must not hand them to the next account. */
it.each([
[false, [null, null]],
[true, ['kept', null]],
])('resets per-pane preset and option settings (skipFirst %s)', async (skipFirst, expected) => {
const jotaiStore = createStore();
for (const key of [0, 1]) {
jotaiStore.set(presetFamily(key), { title: 'kept' } as TPreset);
jotaiStore.set(optionSettingsFamily(key), { showExamples: true });
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot initializeState={({ set }) => set(store.conversationKeysAtom, [0, 1])}>
{children}
</RecoilRoot>
</JotaiProvider>
);

const { result } = renderHook(() => useClearStates(), { wrapper });
await act(async () => {
await result.current(skipFirst);
});

expect([0, 1].map((key) => jotaiStore.get(presetFamily(key))?.title ?? null)).toEqual(expected);
expect(
[0, 1].map((key) => jotaiStore.get(optionSettingsFamily(key)).showExamples ?? null),
).toEqual(expected.map((kept) => (kept ? true : null)));
});
});
10 changes: 6 additions & 4 deletions client/src/hooks/Config/useClearStates.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
import { Constants } from 'librechat-data-provider';
import { isSubmittingFamily, useClearSubmissions } from '@librechat/chat/react';
import {
presetFamily,
abortScrollFamily,
isSubmittingFamily,
optionSettingsFamily,
showStopButtonByIndex,
submissionStartFamily,
useClearSubmissions,
} from '@librechat/chat/react';
import {
getReasoningStateKey,
Expand Down Expand Up @@ -57,13 +60,12 @@ export default function useClearStates() {
}

reset(store.filesByIndex(key));
reset(store.presetByIndex(key));
jotaiStore.set(presetFamily(key), null);
jotaiStore.set(showStopButtonByIndex(key), false);
jotaiStore.set(abortScrollFamily(key), false);
jotaiStore.set(submissionStartFamily(key), null);
jotaiStore.set(isSubmittingFamily(key), false);
reset(store.optionSettingsFamily(key));
reset(store.showPopoverFamily(key));
jotaiStore.set(optionSettingsFamily(key), {});
reset(store.showMentionPopoverFamily(key));
reset(store.showPlusPopoverFamily(key));
reset(store.showPromptsPopoverFamily(key));
Expand Down
6 changes: 4 additions & 2 deletions client/src/hooks/Conversations/usePresets.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import filenamify from 'filenamify';
import exportFromJSON from 'export-from-json';
import { useAtomValue, useSetAtom } from 'jotai';
import { useToastContext } from '@librechat/client';
import { QueryKeys } from 'librechat-data-provider';
import { presetFamily } from '@librechat/chat/react';
import { useQueryClient } from '@tanstack/react-query';
import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil';
import { useCreatePresetMutation, useGetModelsQuery } from 'librechat-data-provider/react-query';
Expand Down Expand Up @@ -41,8 +43,8 @@ export default function usePresets(index = 0) {
const setPresetModalVisible = useSetRecoilState(store.presetModalVisible);
const [_defaultPreset, setDefaultPreset] = useRecoilState(store.defaultPreset);
const presetsQuery = useGetPresetsQuery({ enabled: !!user && isAuthenticated });
const preset = useRecoilValue(store.presetByIndex(index));
const setPreset = useSetRecoilState(store.presetByIndex(index));
const preset = useAtomValue(presetFamily(index));
const setPreset = useSetAtom(presetFamily(index));
const conversationId = useRecoilValue(store.conversationIdByIndex(index));
const { data: modelsData } = useGetModelsQuery();
const { newConversation } = useNewConvo(index);
Expand Down
22 changes: 2 additions & 20 deletions client/src/store/families.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
useSetRecoilState,
useRecoilCallback,
} from 'recoil';
import type { EModelEndpoint, TConversation, TPreset } from 'librechat-data-provider';
import type { TOptionSettings, ExtendedFile } from '~/common';
import type { EModelEndpoint, TConversation } from 'librechat-data-provider';
import type { ExtendedFile } from '~/common';
import {
clearModelForNonEphemeralAgent,
createChatSearchParams,
Expand Down Expand Up @@ -159,21 +159,6 @@ const conversationAssistantIdByIndex = selectorFamily<string | null, string | nu
get(conversationByIndex(index))?.assistant_id ?? null,
});

const presetByIndex = atomFamily<TPreset | null, string | number>({
key: 'presetByIndex',
default: null,
});

const optionSettingsFamily = atomFamily<TOptionSettings, string | number>({
key: 'optionSettingsByIndex',
default: {},
});

const showPopoverFamily = atomFamily({
key: 'showPopoverByIndex',
default: false,
});

const activePromptByIndex = atomFamily<string | undefined, string | number | null>({
key: 'activePromptByIndex',
default: undefined,
Expand Down Expand Up @@ -320,9 +305,6 @@ export default {
conversationKeysAtom,
conversationByIndex,
filesByIndex,
presetByIndex,
optionSettingsFamily,
showPopoverFamily,
conversationIdByIndex,
conversationEndpointByIndex,
effectiveEndpointByIndex,
Expand Down
2 changes: 2 additions & 0 deletions packages/chat/src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ export type {
SteerNotification,
SteerRequestCallbacks,
} from './react/steer/host';
export { presetFamily, optionSettingsFamily } from './react/pane';
export type { PaneOptionSettings } from './react/pane';
22 changes: 22 additions & 0 deletions packages/chat/src/react/pane/__tests__/pane.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createStore } from 'jotai';
import type { TPreset } from 'librechat-data-provider';
import { presetFamily, optionSettingsFamily } from '../index';

describe('pane families', () => {
it('start empty for every pane', () => {
const store = createStore();
expect(store.get(presetFamily(0))).toBeNull();
expect(store.get(optionSettingsFamily(0))).toEqual({});
});

it('keep each pane independent', () => {
const store = createStore();
store.set(presetFamily(0), { title: 'first' } as TPreset);
store.set(optionSettingsFamily(0), { showExamples: true });

expect(store.get(presetFamily(1))).toBeNull();
expect(store.get(optionSettingsFamily(1))).toEqual({});
expect(store.get(presetFamily(0))?.title).toBe('first');
expect(store.get(optionSettingsFamily(0)).showExamples).toBe(true);
});
});
3 changes: 3 additions & 0 deletions packages/chat/src/react/pane/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { presetFamily } from './preset';
export { optionSettingsFamily } from './options';
export type { PaneOptionSettings } from './options';
17 changes: 17 additions & 0 deletions packages/chat/src/react/pane/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { atom } from 'jotai';
import { atomFamily } from 'jotai/utils';
import type { PrimitiveAtom } from 'jotai';
import type { Family } from '../steer/family';

/** Legacy per-pane option toggles (examples, code chat). */
export type PaneOptionSettings = {
showExamples?: boolean;
isCodeChat?: boolean;
};

/** Option toggles of one chat pane, keyed by pane index. */
export const optionSettingsFamily: Family<
PaneOptionSettings,
PrimitiveAtom<PaneOptionSettings>,
string | number
> = atomFamily((_index: string | number) => atom<PaneOptionSettings>({}));
12 changes: 12 additions & 0 deletions packages/chat/src/react/pane/preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { atom } from 'jotai';
import { atomFamily } from 'jotai/utils';
import type { TPreset } from 'librechat-data-provider';
import type { PrimitiveAtom } from 'jotai';
import type { Family } from '../steer/family';

/** The preset applied to one chat pane, keyed by pane index. */
export const presetFamily: Family<
TPreset | null,
PrimitiveAtom<TPreset | null>,
string | number
> = atomFamily((_index: string | number) => atom<TPreset | null>(null));
Loading