| |
| import { Trash2 } from 'lucide-react'; |
| import { useState, useEffect } from 'react'; |
| import { useRecoilValue, useRecoilState } from 'recoil'; |
| import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider'; |
| import { Icon, EditPresetDialog } from '~/components/Endpoints'; |
| import EndpointItems from './EndpointItems'; |
| import PresetItems from './PresetItems'; |
| import FileUpload from './FileUpload'; |
| import { |
| Button, |
| DropdownMenu, |
| DropdownMenuContent, |
| DropdownMenuLabel, |
| DropdownMenuRadioGroup, |
| DropdownMenuSeparator, |
| DropdownMenuTrigger, |
| Dialog, |
| DialogTrigger, |
| TooltipProvider, |
| Tooltip, |
| TooltipTrigger, |
| TooltipContent, |
| } from '~/components/ui/'; |
| import DialogTemplate from '~/components/ui/DialogTemplate'; |
| import { cn, cleanupPreset } from '~/utils'; |
| import { useLocalize, useLocalStorage, useConversation, useDefaultConvo } from '~/hooks'; |
| import store from '~/store'; |
|
|
| export default function NewConversationMenu() { |
| const localize = useLocalize(); |
| const getDefaultConversation = useDefaultConvo(); |
| const [menuOpen, setMenuOpen] = useState(false); |
| const [showPresets, setShowPresets] = useState(true); |
| const [showEndpoints, setShowEndpoints] = useState(true); |
| const [presetModelVisible, setPresetModelVisible] = useState(false); |
| const [preset, setPreset] = useState(false); |
| const [conversation, setConversation] = useRecoilState(store.conversation) ?? {}; |
| const [messages, setMessages] = useRecoilState(store.messages); |
| const availableEndpoints = useRecoilValue(store.availableEndpoints); |
|
|
| const [presets, setPresets] = useRecoilState(store.presets); |
| const modularEndpoints = new Set(['gptPlugins', 'anthropic', 'google', 'openAI']); |
|
|
| const { endpoint } = conversation; |
| const { newConversation } = useConversation(); |
|
|
| const deletePresetsMutation = useDeletePresetMutation(); |
| const createPresetMutation = useCreatePresetMutation(); |
|
|
| const importPreset = (jsonData) => { |
| createPresetMutation.mutate( |
| { ...jsonData }, |
| { |
| onSuccess: (data) => { |
| setPresets(data); |
| }, |
| onError: (error) => { |
| console.error('Error uploading the preset:', error); |
| }, |
| }, |
| ); |
| }; |
|
|
| const onFileSelected = (jsonData) => { |
| const jsonPreset = { ...cleanupPreset({ preset: jsonData }), presetId: null }; |
| importPreset(jsonPreset); |
| }; |
|
|
| |
| const [newUser, setNewUser] = useLocalStorage('newUser', true); |
| const [lastModel, setLastModel] = useLocalStorage('lastSelectedModel', {}); |
| const setLastConvo = useLocalStorage('lastConversationSetup', {})[1]; |
| const [lastBingSettings, setLastBingSettings] = useLocalStorage('lastBingSettings', {}); |
| useEffect(() => { |
| if (endpoint && endpoint !== 'bingAI') { |
| const lastModelUpdate = { ...lastModel, [endpoint]: conversation?.model }; |
| if (endpoint === 'gptPlugins') { |
| lastModelUpdate.secondaryModel = conversation.agentOptions.model; |
| } |
| setLastModel(lastModelUpdate); |
| } else if (endpoint === 'bingAI') { |
| const { jailbreak, toneStyle } = conversation; |
| setLastBingSettings({ ...lastBingSettings, jailbreak, toneStyle }); |
| } |
|
|
| setLastConvo(conversation); |
| }, [conversation]); |
|
|
| |
| const onSelectEndpoint = (newEndpoint) => { |
| setMenuOpen(false); |
| if (!newEndpoint) { |
| return; |
| } else { |
| newConversation({}, { endpoint: newEndpoint }); |
| } |
| }; |
|
|
| |
| const isModular = modularEndpoints.has(endpoint); |
| const onSelectPreset = (newPreset) => { |
| setMenuOpen(false); |
| if (!newPreset) { |
| return; |
| } |
|
|
| if ( |
| isModular && |
| modularEndpoints.has(newPreset?.endpoint) && |
| endpoint === newPreset?.endpoint |
| ) { |
| const currentConvo = getDefaultConversation({ |
| conversation, |
| preset: newPreset, |
| }); |
|
|
| setConversation(currentConvo); |
| setMessages(messages); |
| return; |
| } |
|
|
| newConversation({}, newPreset); |
| }; |
|
|
| const onChangePreset = (preset) => { |
| setPresetModelVisible(true); |
| setPreset(preset); |
| }; |
|
|
| const clearAllPresets = () => { |
| deletePresetsMutation.mutate({ arg: {} }); |
| }; |
|
|
| const onDeletePreset = (preset) => { |
| deletePresetsMutation.mutate({ arg: preset }); |
| }; |
|
|
| const icon = Icon({ |
| size: 32, |
| ...conversation, |
| error: false, |
| button: true, |
| }); |
|
|
| const onOpenChange = (open) => { |
| setMenuOpen(open); |
| if (newUser) { |
| setNewUser(false); |
| } |
| }; |
|
|
| return ( |
| <TooltipProvider delayDuration={300}> |
| <Tooltip> |
| <Dialog className="z-[100]"> |
| <DropdownMenu open={menuOpen} onOpenChange={onOpenChange}> |
| <TooltipTrigger asChild> |
| <DropdownMenuTrigger asChild> |
| <Button |
| id="new-conversation-menu" |
| data-testid="new-conversation-menu" |
| variant="outline" |
| className={ |
| 'group relative mb-[-12px] ml-1 mt-[-8px] items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-0 md:ml-[-12px] md:pl-1' |
| } |
| > |
| {icon} |
| </Button> |
| </DropdownMenuTrigger> |
| </TooltipTrigger> |
| <TooltipContent forceMount={newUser} sideOffset={5}> |
| {localize('com_endpoint_open_menu')} |
| </TooltipContent> |
| <DropdownMenuContent |
| className="z-[100] w-[375px] dark:bg-gray-900 md:w-96" |
| onCloseAutoFocus={(event) => event.preventDefault()} |
| side="top" |
| > |
| <DropdownMenuLabel |
| className="cursor-pointer dark:text-gray-300" |
| onClick={() => setShowEndpoints((prev) => !prev)} |
| > |
| {showEndpoints ? localize('com_endpoint_hide') : localize('com_endpoint_show')}{' '} |
| {localize('com_endpoint')} |
| </DropdownMenuLabel> |
| <DropdownMenuSeparator /> |
| <DropdownMenuRadioGroup |
| value={endpoint} |
| onValueChange={onSelectEndpoint} |
| className="flex flex-col gap-1 overflow-y-auto" |
| > |
| {showEndpoints && |
| (availableEndpoints.length ? ( |
| <EndpointItems |
| selectedEndpoint={endpoint} |
| endpoints={availableEndpoints} |
| onSelect={onSelectEndpoint} |
| /> |
| ) : ( |
| <DropdownMenuLabel className="dark:text-gray-300"> |
| {localize('com_endpoint_not_available')} |
| </DropdownMenuLabel> |
| ))} |
| </DropdownMenuRadioGroup> |
| |
| <div className="mt-2 w-full" /> |
| |
| <DropdownMenuLabel className="flex items-center dark:text-gray-300"> |
| <span |
| className="mr-auto cursor-pointer " |
| onClick={() => setShowPresets((prev) => !prev)} |
| > |
| {showPresets ? localize('com_endpoint_hide') : localize('com_endpoint_show')}{' '} |
| {localize('com_endpoint_presets')} |
| </span> |
| <FileUpload onFileSelected={onFileSelected} /> |
| <Dialog> |
| <DialogTrigger asChild> |
| <label |
| htmlFor="file-upload" |
| className="mr-1 flex h-[32px] h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium font-normal text-gray-600 transition-colors hover:bg-slate-200 hover:text-red-700 dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-green-500" |
| > |
| <Trash2 className="mr-1 flex w-[22px] items-center stroke-1" /> |
| {localize('com_ui_clear')} {localize('com_ui_all')} |
| </label> |
| </DialogTrigger> |
| <DialogTemplate |
| title={`${localize('com_ui_clear')} ${localize('com_endpoint_presets')}`} |
| description={localize('com_endpoint_presets_clear_warning')} |
| selection={{ |
| selectHandler: clearAllPresets, |
| selectClasses: 'bg-red-600 hover:bg-red-700 dark:hover:bg-red-800 text-white', |
| selectText: localize('com_ui_clear'), |
| }} |
| className="max-w-[500px]" |
| /> |
| </Dialog> |
| </DropdownMenuLabel> |
| <DropdownMenuSeparator /> |
| <DropdownMenuRadioGroup |
| onValueChange={onSelectPreset} |
| className={cn( |
| 'overflow-y-auto overflow-x-hidden', |
| showEndpoints ? 'max-h-[210px]' : 'max-h-[315px]', |
| )} |
| > |
| {showPresets && |
| (presets.length ? ( |
| <PresetItems |
| presets={presets} |
| onSelect={onSelectPreset} |
| onChangePreset={onChangePreset} |
| onDeletePreset={onDeletePreset} |
| /> |
| ) : ( |
| <DropdownMenuLabel className="dark:text-gray-300"> |
| {localize('com_endpoint_no_presets')} |
| </DropdownMenuLabel> |
| ))} |
| </DropdownMenuRadioGroup> |
| </DropdownMenuContent> |
| </DropdownMenu> |
| <EditPresetDialog |
| open={presetModelVisible} |
| onOpenChange={setPresetModelVisible} |
| preset={preset} |
| /> |
| </Dialog> |
| </Tooltip> |
| </TooltipProvider> |
| ); |
| } |
|
|