🧪 feat: Experimental: Enable Switching Endpoints Mid-Conversation (#1483)

* fix: load all existing conversation settings on refresh

* refactor(buildDefaultConvo): use `lastConversationSetup.endpointType` before `conversation.endpointType`

* refactor(TMessage/messageSchema): add `endpoint` field to messages to differentiate generation origin

* feat(useNewConvo): `keepLatestMessage` param to prevent reseting the `latestMessage` mid-conversation

* style(Settings): adjust height styling to allow more space in dialog for additional settings

* feat: Modular Chat: experimental setting to Enable switching Endpoints mid-conversation

* fix(ChatRoute): fix potential parsing issue with tPresetSchema
This commit is contained in:
Danny Avila 2024-01-03 19:17:42 -05:00 committed by GitHub
parent 4befee829b
commit e1a529b5ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 129 additions and 26 deletions

View file

@ -1,11 +1,14 @@
import { useState } from 'react';
import { Settings } from 'lucide-react';
import { useRecoilValue } from 'recoil';
import { EModelEndpoint } from 'librechat-data-provider';
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
import type { FC } from 'react';
import { useLocalize, useUserKey } from '~/hooks';
import type { TPreset } from 'librechat-data-provider';
import { useLocalize, useUserKey, useDefaultConvo } from '~/hooks';
import { SetKeyDialog } from '~/components/Input/SetKeyDialog';
import { useChatContext } from '~/Providers';
import store from '~/store';
import { icons } from './Icons';
import { cn } from '~/utils';
@ -27,10 +30,12 @@ const MenuItem: FC<MenuItemProps> = ({
userProvidesKey,
...rest
}) => {
const { data: endpointsConfig } = useGetEndpointsQuery();
const modularChat = useRecoilValue(store.modularChat);
const [isDialogOpen, setDialogOpen] = useState(false);
const { newConversation } = useChatContext();
const { data: endpointsConfig } = useGetEndpointsQuery();
const { conversation, newConversation } = useChatContext();
const getDefaultConversation = useDefaultConvo();
const { getExpiry } = useUserKey(endpoint);
const localize = useLocalize();
const expiryTime = getExpiry();
@ -42,7 +47,22 @@ const MenuItem: FC<MenuItemProps> = ({
if (!expiryTime) {
setDialogOpen(true);
}
newConversation({ template: { endpoint: newEndpoint, conversationId: 'new' } });
const template: Partial<TPreset> = { endpoint: newEndpoint, conversationId: 'new' };
const { conversationId } = conversation ?? {};
if (modularChat && conversationId && conversationId !== 'new') {
template.endpointType = endpointsConfig?.[newEndpoint]?.type;
const currentConvo = getDefaultConversation({
/* target endpointType is necessary to avoid endpoint mixing */
conversation: { ...(conversation ?? {}), endpointType: template.endpointType },
preset: template,
});
/* We don't reset the latest message, only when changing settings mid-converstion */
newConversation({ template: currentConvo, keepLatestMessage: true });
return;
}
newConversation({ template });
}
};

View file

@ -1,9 +1,9 @@
import * as Tabs from '@radix-ui/react-tabs';
import type { TDialogProps } from '~/common';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui';
import { GearIcon, DataIcon, UserIcon } from '~/components/svg';
import { useMediaQuery, useLocalize } from '~/hooks';
import type { TDialogProps } from '~/common';
import { General, Data, Account } from './SettingsTabs';
import { useMediaQuery, useLocalize } from '~/hooks';
import { cn } from '~/utils';
export default function Settings({ open, onOpenChange }: TDialogProps) {
@ -13,7 +13,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className={cn('shadow-2xl dark:bg-gray-900 dark:text-white md:h-[373px] md:w-[680px]')}
className={cn('shadow-2xl dark:bg-gray-900 dark:text-white md:min-h-[373px] md:w-[680px]')}
style={{ borderRadius: '12px' }}
>
<DialogHeader>

View file

@ -12,9 +12,10 @@ import {
} from '~/hooks';
import type { TDangerButtonProps } from '~/common';
import AutoScrollSwitch from './AutoScrollSwitch';
import DangerButton from '../DangerButton';
import store from '~/store';
import { Dropdown } from '~/components/ui';
import DangerButton from '../DangerButton';
import ModularChat from './ModularChat';
import store from '~/store';
export const ThemeSelector = ({
theme,
@ -188,6 +189,9 @@ function General() {
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoScrollSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<ModularChat />
</div>
</div>
</Tabs.Content>
);

View file

@ -0,0 +1,35 @@
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';
export default function ModularChatSwitch({
onCheckedChange,
}: {
onCheckedChange?: (value: boolean) => void;
}) {
const [modularChat, setModularChat] = useRecoilState<boolean>(store.modularChat);
const localize = useLocalize();
const handleCheckedChange = (value: boolean) => {
setModularChat(value);
if (onCheckedChange) {
onCheckedChange(value);
}
};
return (
<div className="flex items-center justify-between">
<div>
{`[${localize('com_ui_experimental')}]`} {localize('com_nav_modular_chat')}{' '}
</div>
<Switch
id="modularChat"
checked={modularChat}
onCheckedChange={handleCheckedChange}
className="ml-4 mt-2"
data-testid="modularChat"
/>
</div>
);
}