diff --git a/client/src/components/Endpoints/BingAI/Settings.jsx b/client/src/components/Endpoints/BingAI/Settings.jsx index 85645ac4cc..e8cd219f40 100644 --- a/client/src/components/Endpoints/BingAI/Settings.jsx +++ b/client/src/components/Endpoints/BingAI/Settings.jsx @@ -16,7 +16,7 @@ const optionText = function Settings(props) { // const [showSystemMessage, setShowSystemMessage] = React.useState(false); - const { context, setContext, systemMessage, setSystemMessage, jailbreak, setJailbreak } = props; + const { readonly, context, setContext, systemMessage, setSystemMessage, jailbreak, setJailbreak } = props; const showSystemMessage = jailbreak; return ( @@ -32,6 +32,7 @@ function Settings(props) { setContext(e.target.value || null)} placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" @@ -47,6 +48,7 @@ function Settings(props) {
*/} setSystemMessage(e.target.value || null)} placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" diff --git a/client/src/components/Endpoints/EditPresetDialog.jsx b/client/src/components/Endpoints/EditPresetDialog.jsx index 39c8b989ad..3e5cedee71 100644 --- a/client/src/components/Endpoints/EditPresetDialog.jsx +++ b/client/src/components/Endpoints/EditPresetDialog.jsx @@ -8,7 +8,10 @@ import { Input } from '../ui/Input.tsx'; import { Label } from '../ui/Label.tsx'; import Dropdown from '../ui/Dropdown'; import { cn } from '~/utils/'; +import cleanupPreset from '~/utils/cleanupPreset'; + import OpenAISettings from './OpenAI/Settings'; +import BingAISettings from './BingAI/Settings.jsx'; import store from '~/store'; @@ -50,39 +53,29 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => { setPresP={setOption('frequency_penalty')} /> ); + else if (endpoint === 'bingAI') + return ( + + ); else return null; }; const defaultTextProps = 'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 focus:dark:bg-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0'; - // in order not to use wrong data from other endpoint - const clearPreset = () => { - if (preset?.endpoint === 'openAI') - return { - title: preset?.title, - endpoint: preset?.endpoint, - model: preset?.model, - chatGptLabel: preset?.chatGptLabel, - promptPrefix: preset?.promptPrefix, - temperature: preset?.temperature, - top_p: preset?.top_p, - presence_penalty: preset?.presence_penalty, - frequency_penalty: preset?.frequency_penalty - }; - else - return { - title: preset?.title, - endpoint: preset?.endpoint - }; - // TODO: else - }; - const submitPreset = () => { axios({ method: 'post', url: '/api/presets', - data: clearPreset(), + data: cleanupPreset(preset), withCredentials: true }).then(res => { setPresets(res?.data); @@ -91,7 +84,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => { const exportPreset = () => { exportFromJSON({ - data: clearPreset(), + data: cleanupPreset(preset), fileName: `${preset?.title}.json`, exportType: exportFromJSON.types.json }); @@ -168,7 +161,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => { <> Export diff --git a/client/src/components/Endpoints/EndpointOptionsDialog.jsx b/client/src/components/Endpoints/EndpointOptionsDialog.jsx new file mode 100644 index 0000000000..0266b7966d --- /dev/null +++ b/client/src/components/Endpoints/EndpointOptionsDialog.jsx @@ -0,0 +1,134 @@ +import React, { useEffect, useState } from 'react'; +import { useSetRecoilState, useRecoilValue } from 'recoil'; +import axios from 'axios'; +import exportFromJSON from 'export-from-json'; +import DialogTemplate from '../ui/DialogTemplate.jsx'; +import { Dialog, DialogClose, DialogButton } from '../ui/Dialog.tsx'; +import { Input } from '../ui/Input.tsx'; +import { Label } from '../ui/Label.tsx'; +import Dropdown from '../ui/Dropdown.jsx'; +import SaveAsPresetDialog from './SaveAsPresetDialog'; +import { cn } from '~/utils/'; +import cleanupPreset from '~/utils/cleanupPreset'; + +import OpenAISettings from './OpenAI/Settings.jsx'; +import BingAISettings from './BingAI/Settings.jsx'; + +import store from '~/store'; + +// A preset dialog to show readonly preset values. +const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) => { + // const [title, setTitle] = useState('My Preset'); + const [preset, setPreset] = useState({}); + + const [saveAsDialogShow, setSaveAsDialogShow] = useState(false); + + const setOption = param => newValue => { + let update = {}; + update[param] = newValue; + setPreset(prevState => ({ + ...prevState, + ...update + })); + }; + + const renderSettings = () => { + const { endpoint } = preset || {}; + + if (endpoint === 'openAI') + return ( + + ); + else if (endpoint === 'bingAI') + return ( + + ); + else return null; + }; + + const saveAsPreset = () => { + setSaveAsDialogShow(true); + }; + + const exportPreset = () => { + exportFromJSON({ + data: cleanupPreset(preset), + fileName: `${preset?.title}.json`, + exportType: exportFromJSON.types.json + }); + }; + + useEffect(() => { + setPreset(_preset); + }, [open]); + + return ( + <> + + +
{renderSettings()}
+
+ } + buttons={ + <> + + Save As Preset + + + } + leftButtons={ + <> + + Export + + + } + /> + + + + ); +}; + +export default EndpointOptionsDialog; diff --git a/client/src/components/Endpoints/OpenAI/Settings.jsx b/client/src/components/Endpoints/OpenAI/Settings.jsx index b1d98bf6fd..6c2ae65e38 100644 --- a/client/src/components/Endpoints/OpenAI/Settings.jsx +++ b/client/src/components/Endpoints/OpenAI/Settings.jsx @@ -4,6 +4,7 @@ import ModelDropDown from '../../ui/ModelDropDown'; import { Input } from '~/components/ui/Input.tsx'; import { Label } from '~/components/ui/Label.tsx'; import { Slider } from '~/components/ui/Slider.tsx'; +import { InputNumber } from '../../ui/InputNumber'; import OptionHover from './OptionHover'; import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx'; import { cn } from '~/utils/'; @@ -15,6 +16,7 @@ const optionText = function Settings(props) { const { + readonly, model, setModel, chatGptLabel, @@ -38,6 +40,7 @@ function Settings(props) {
setChatGptLabel(e.target.value || null)} @@ -92,6 +96,7 @@ function Settings(props) { setPromptPrefix(e.target.value || null)} placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" @@ -123,6 +128,7 @@ function Settings(props) { setTemperature(e.target.value)} className={cn( @@ -132,6 +138,7 @@ function Settings(props) { />
setTemperature(value[0])} max={2} @@ -157,6 +164,7 @@ function Settings(props) { setMaxTokens(e.target.value)} className={cn( @@ -166,6 +174,7 @@ function Settings(props) { /> setMaxTokens(value[0])} max={2048} // should be dynamic to the currently selected model @@ -191,6 +200,7 @@ function Settings(props) { setTopP(e.target.value)} className={cn( @@ -200,6 +210,7 @@ function Settings(props) { /> setTopP(value[0])} max={1} @@ -225,6 +236,7 @@ function Settings(props) { setFreqP(e.target.value)} className={cn( @@ -234,6 +246,7 @@ function Settings(props) { /> setFreqP(value[0])} max={2} @@ -259,6 +272,7 @@ function Settings(props) { setPresP(e.target.value)} className={cn( @@ -268,6 +282,7 @@ function Settings(props) { /> setPresP(value[0])} max={2} @@ -281,158 +296,6 @@ function Settings(props) { side="left" /> - {/*
- - - - setTemperature(e.target.value)} - className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/temp:border-gray-200'))} - /> - - - - - - - setMaxTokens(e.target.value)} - className={cn(defaultTextProps, cn(optionText, 'w-11 group-hover/max:border-gray-200'))} - /> - - - -
-
- setMaxTokens(value)} - max={2048} // should be dynamic to the currently selected model - min={1} - step={1} - className="w-full" - /> -
-
- - - - setTopP(e.target.value)} - className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/top:border-gray-200'))} - /> - - - - - - - setFreqP(e.target.value)} - className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/freq:border-gray-200'))} - /> - - - -
-
- setTopP(value)} - max={1} - min={0} - step={0.01} - className="w-full" - /> - setFreqP(value)} - max={2} - min={-2} - step={0.01} - className="w-full" - /> -
-
- - - - setPresP(e.target.value)} - className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/pres:border-gray-200'))} - /> - - - -
-
- setPresP(value)} - max={2} - min={-2} - step={0.01} - className="w-full opacity-0" - /> - setPresP(value)} - max={2} - min={-2} - step={0.01} - className="w-full" - /> -
*/} diff --git a/client/src/components/Endpoints/SaveAsPresetDialog.jsx b/client/src/components/Endpoints/SaveAsPresetDialog.jsx index 7d663467e5..73c697516d 100644 --- a/client/src/components/Endpoints/SaveAsPresetDialog.jsx +++ b/client/src/components/Endpoints/SaveAsPresetDialog.jsx @@ -6,11 +6,11 @@ import { Dialog } from '../ui/Dialog.tsx'; import { Input } from '../ui/Input.tsx'; import { Label } from '../ui/Label.tsx'; import { cn } from '~/utils/'; -import buildPresetByConversation from '~/utils/buildPresetByConversation'; +import cleanupPreset from '~/utils/cleanupPreset'; import store from '~/store'; -const SaveAsPresetDialog = ({ open, onOpenChange, conversation }) => { +const SaveAsPresetDialog = ({ open, onOpenChange, preset }) => { const [title, setTitle] = useState('My Preset'); const setPresets = useSetRecoilState(store.presets); @@ -18,15 +18,15 @@ const SaveAsPresetDialog = ({ open, onOpenChange, conversation }) => { 'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0'; const submitPreset = () => { - const preset = buildPresetByConversation({ - title, - conversation + const _preset = cleanupPreset({ + ...preset, + title }); axios({ method: 'post', url: '/api/presets', - data: preset, + data: _preset, withCredentials: true }).then(res => { setPresets(res?.data); diff --git a/client/src/components/Input/BingAIOptions/index.jsx b/client/src/components/Input/BingAIOptions/index.jsx index ad8be67c41..00068051db 100644 --- a/client/src/components/Input/BingAIOptions/index.jsx +++ b/client/src/components/Input/BingAIOptions/index.jsx @@ -131,7 +131,7 @@ function BingAIOptions() { ); diff --git a/client/src/components/Input/Endpoints/NewConversationMenu.jsx b/client/src/components/Input/Endpoints/NewConversationMenu.jsx index 2ff5adbb1b..3df97e32fc 100644 --- a/client/src/components/Input/Endpoints/NewConversationMenu.jsx +++ b/client/src/components/Input/Endpoints/NewConversationMenu.jsx @@ -1,9 +1,10 @@ import React, { useState, useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValue, useRecoilState } from 'recoil'; import EditPresetDialog from '../../Endpoints/EditPresetDialog'; import EndpointItems from './EndpointItems'; import PresetItems from './PresetItems'; import getIcon from '~/utils/getIcon'; +import manualSWR from '~/utils/fetchers'; import { Button } from '../../ui/Button.tsx'; import { @@ -27,13 +28,18 @@ export default function NewConversationMenu() { // const models = useRecoilValue(store.models); const availableEndpoints = useRecoilValue(store.availableEndpoints); // const setCustomGPTModels = useSetRecoilState(store.customGPTModels); - const presets = useRecoilValue(store.presets); + const [presets, setPresets] = useRecoilState(store.presets); const conversation = useRecoilValue(store.conversation) || {}; const { endpoint, conversationId } = conversation; // const { model, promptPrefix, chatGptLabel, conversationId } = conversation; const { newConversation } = store.useConversation(); + const { trigger: clearPresetsTrigger } = manualSWR(`/api/presets/delete`, 'post', data => { + console.log(data); + setPresets(data); + }); + // update the default model when availableModels changes // typically, availableModels changes => modelsFilter or customGPTModels changes useEffect(() => { @@ -73,6 +79,10 @@ export default function NewConversationMenu() { setPreset(preset); }; + const clearPreset = () => { + clearPresetsTrigger({}); + }; + const icon = getIcon({ size: 32, ...conversation, @@ -99,7 +109,7 @@ export default function NewConversationMenu() { event.preventDefault()} > Select an Endpoint @@ -121,7 +131,17 @@ export default function NewConversationMenu() {
- Select a Preset + + Select a Preset +
+ + ); diff --git a/client/src/components/Messages/MessageHeader.jsx b/client/src/components/Messages/MessageHeader.jsx index a61e71ab82..cc1ce5be2d 100644 --- a/client/src/components/Messages/MessageHeader.jsx +++ b/client/src/components/Messages/MessageHeader.jsx @@ -1,5 +1,7 @@ import React, { useState } from 'react'; import { useRecoilValue } from 'recoil'; +import EndpointOptionsDialog from '../Endpoints/EndpointOptionsDialog'; +import { Button } from '../ui/Button.tsx'; import store from '~/store'; @@ -14,6 +16,7 @@ const clipPromptPrefix = str => { }; const MessageHeader = ({ isSearchView = false }) => { + const [saveAsDialogShow, setSaveAsDialogShow] = useState(false); const [extended, setExtended] = useState(false); const conversation = useRecoilValue(store.conversation); const searchQuery = useRecoilValue(store.searchQuery); @@ -64,30 +67,39 @@ const MessageHeader = ({ isSearchView = false }) => { }; return ( -
-
{getConversationTitle()}
+ <> +
+
{getConversationTitle()}
- {extended ? ( -
-
- {options.map(([key, value]) => ( -
- {key}: {value || 'null'} -
- ))} + {extended ? ( +
+
+ {options.map(([key, value]) => ( +
+ {key}: {value || 'null'} +
+ ))} +
+
-
- ) : null} -
+ ) : null} +
+ + + ); }; diff --git a/client/src/components/ui/DialogTemplate.jsx b/client/src/components/ui/DialogTemplate.jsx index 21a8f04723..2e46bbc49f 100644 --- a/client/src/components/ui/DialogTemplate.jsx +++ b/client/src/components/ui/DialogTemplate.jsx @@ -52,7 +52,7 @@ export default function DialogTemplate({ {main ? main : null}
{leftButtons ? leftButtons : null}
-
+
Cancel {buttons ? buttons : null} {selection ? ( diff --git a/client/src/components/ui/InputNumber.jsx b/client/src/components/ui/InputNumber.jsx new file mode 100644 index 0000000000..6efd3d236e --- /dev/null +++ b/client/src/components/ui/InputNumber.jsx @@ -0,0 +1,43 @@ +'use client'; + +import * as React from 'react'; + +import { NumericFormat } from 'react-number-format'; + +// import * as InputNumberPrimitive from 'rc-input-number'; + +import { cn } from '../../utils/index.jsx'; + +// TODO help needed +// const _InputNumber = React.forwardRef< React.ElementRef, InputNumberPrimitive.InputNumberProps>( +// ({ className, ...props }, ref) => { +// return ( +// +// ) +// } +// ) +// _InputNumber.displayName = "Input" + +// console.log(_InputNumber); + +const InputNumber = React.forwardRef(({ className, ...props }, ref) => { + return ( + + ); +}); + +export { InputNumber }; diff --git a/client/src/components/ui/ModelDropDown.jsx b/client/src/components/ui/ModelDropDown.jsx index 9fa64bda19..24b58f5ec5 100644 --- a/client/src/components/ui/ModelDropDown.jsx +++ b/client/src/components/ui/ModelDropDown.jsx @@ -7,6 +7,7 @@ import store from '~/store'; function ModelDropDown({ model, + disabled, setModel, endpoint, showAbove = false, @@ -23,6 +24,7 @@ function ModelDropDown({ {({ open }) => ( <> diff --git a/client/src/store/conversation.js b/client/src/store/conversation.js index 0e312a633d..06520b2751 100644 --- a/client/src/store/conversation.js +++ b/client/src/store/conversation.js @@ -22,6 +22,8 @@ import getDefaultConversation from '~/utils/getDefaultConversation'; // frequency_penalty: 0, // // for bingAI only // jailbreak: false, +// context: null, +// systemMessage: null, // jailbreakConversationId: null, // conversationSignature: null, // clientId: null, diff --git a/client/src/utils/buildPresetByConversation.js b/client/src/utils/buildPresetByConversation.js deleted file mode 100644 index 36f991068b..0000000000 --- a/client/src/utils/buildPresetByConversation.js +++ /dev/null @@ -1,56 +0,0 @@ -const buildPresetByConversation = ({ title, conversation, ...others }) => { - const { endpoint } = conversation; - - let preset = {}; - if (endpoint === 'azureOpenAI' || endpoint === 'openAI') { - preset = { - endpoint, - model: conversation?.model || 'gpt-3.5-turbo', - chatGptLabel: conversation?.chatGptLabel || null, - promptPrefix: conversation?.promptPrefix || null, - temperature: conversation?.temperature || 1, - top_p: conversation?.top_p || 1, - presence_penalty: conversation?.presence_penalty || 0, - frequency_penalty: conversation?.frequency_penalty || 0, - title, - ...others - }; - } else if (endpoint === 'bingAI') { - preset = { - endpoint, - jailbreak: conversation?.jailbreak || false, - jailbreakConversationId: conversation?.jailbreakConversationId || null, - conversationSignature: null, - clientId: null, - invocationId: 1, - toneStyle: conversation?.toneStyle || 'fast', - title, - ...others - }; - } else if (endpoint === 'chatGPTBrowser') { - preset = { - endpoint, - model: conversation?.model || 'Default (GPT-3.5)', - title, - ...others - }; - } else if (endpoint === null) { - preset = { - ...conversation, - endpoint, - title, - ...others - }; - } else { - console.error(`Unknown endpoint ${endpoint}`); - preset = { - endpoint: null, - title, - ...others - }; - } - - return preset; -}; - -export default buildPresetByConversation; diff --git a/client/src/utils/cleanupPreset.js b/client/src/utils/cleanupPreset.js new file mode 100644 index 0000000000..0e98fa88c2 --- /dev/null +++ b/client/src/utils/cleanupPreset.js @@ -0,0 +1,55 @@ +const cleanupPreset = _preset => { + const { endpoint } = _preset; + + let preset = {}; + if (endpoint === 'azureOpenAI' || endpoint === 'openAI') { + preset = { + endpoint, + presetId: _preset?.presetId || null, + model: _preset?.model || 'gpt-3.5-turbo', + chatGptLabel: _preset?.chatGptLabel || null, + promptPrefix: _preset?.promptPrefix || null, + temperature: _preset?.temperature || 1, + top_p: _preset?.top_p || 1, + presence_penalty: _preset?.presence_penalty || 0, + frequency_penalty: _preset?.frequency_penalty || 0, + title: _preset?.title || 'New Preset' + }; + } else if (endpoint === 'bingAI') { + preset = { + endpoint, + presetId: _preset?.presetId || null, + jailbreak: _preset?.jailbreak || false, + jailbreakpresetId: _preset?._jailbreakpresetId || null, + presetSignature: null, + clientId: null, + invocationId: 1, + toneStyle: _preset?.toneStyle || 'fast', + title: _preset?.title || 'New Preset' + }; + } else if (endpoint === 'chatGPTBrowser') { + preset = { + endpoint, + presetId: _preset?.presetId || null, + model: _preset?.model || 'Default (GPT-3.5)', + title: _preset?.title || 'New Preset' + }; + } else if (endpoint === null) { + preset = { + endpoint, + presetId: _preset?.presetId || null, + title: _preset?.title || 'New Preset' + }; + } else { + console.error(`Unknown endpoint ${endpoint}`); + preset = { + endpoint: null, + presetId: _preset?.presetId || null, + title: _preset?.title || 'New Preset' + }; + } + + return preset; +}; + +export default cleanupPreset;