feat: support edit preset,

feat: support view current conversation options.
feat: save current conversation as a preset.
feat: preset delete all.
This commit is contained in:
Wentao Lyu 2023-04-04 01:12:14 +08:00
parent dae0c2d5e3
commit d2579b44d1
15 changed files with 340 additions and 269 deletions

View file

@ -16,7 +16,7 @@ const optionText =
function Settings(props) { function Settings(props) {
// const [showSystemMessage, setShowSystemMessage] = React.useState(false); // 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; const showSystemMessage = jailbreak;
return ( return (
@ -32,6 +32,7 @@ function Settings(props) {
</Label> </Label>
<TextareaAutosize <TextareaAutosize
id="context" id="context"
disabled={readonly}
value={context || ''} value={context || ''}
onChange={e => setContext(e.target.value || null)} onChange={e => setContext(e.target.value || null)}
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
@ -47,6 +48,7 @@ function Settings(props) {
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<Checkbox <Checkbox
id="jailbreak" id="jailbreak"
disabled={readonly}
checked={jailbreak} checked={jailbreak}
className="dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 focus:ring-opacity-20 dark:focus:ring-opacity-50 dark:focus:ring-offset-0 dark:focus:ring-gray-600" className="dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 focus:ring-opacity-20 dark:focus:ring-opacity-50 dark:focus:ring-offset-0 dark:focus:ring-gray-600"
// onCheckedChange={setJailbreak} // onCheckedChange={setJailbreak}
@ -79,6 +81,7 @@ function Settings(props) {
</Label> */} </Label> */}
<TextareaAutosize <TextareaAutosize
id="systemMessage" id="systemMessage"
disabled={readonly}
value={systemMessage || ''} value={systemMessage || ''}
onChange={e => setSystemMessage(e.target.value || null)} onChange={e => setSystemMessage(e.target.value || null)}
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"

View file

@ -8,7 +8,10 @@ import { Input } from '../ui/Input.tsx';
import { Label } from '../ui/Label.tsx'; import { Label } from '../ui/Label.tsx';
import Dropdown from '../ui/Dropdown'; import Dropdown from '../ui/Dropdown';
import { cn } from '~/utils/'; import { cn } from '~/utils/';
import cleanupPreset from '~/utils/cleanupPreset';
import OpenAISettings from './OpenAI/Settings'; import OpenAISettings from './OpenAI/Settings';
import BingAISettings from './BingAI/Settings.jsx';
import store from '~/store'; import store from '~/store';
@ -50,39 +53,29 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => {
setPresP={setOption('frequency_penalty')} setPresP={setOption('frequency_penalty')}
/> />
); );
else if (endpoint === 'bingAI')
return (
<BingAISettings
readonly={true}
context={preset?.context}
setContext={setOption('context')}
systemMessage={preset?.systemMessage}
setSystemMessage={setOption('systemMessage')}
jailbreak={preset?.jailbreak}
setJailbreak={setOption('jailbreak')}
/>
);
else return null; else return null;
}; };
const defaultTextProps = 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'; '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 = () => { const submitPreset = () => {
axios({ axios({
method: 'post', method: 'post',
url: '/api/presets', url: '/api/presets',
data: clearPreset(), data: cleanupPreset(preset),
withCredentials: true withCredentials: true
}).then(res => { }).then(res => {
setPresets(res?.data); setPresets(res?.data);
@ -91,7 +84,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => {
const exportPreset = () => { const exportPreset = () => {
exportFromJSON({ exportFromJSON({
data: clearPreset(), data: cleanupPreset(preset),
fileName: `${preset?.title}.json`, fileName: `${preset?.title}.json`,
exportType: exportFromJSON.types.json exportType: exportFromJSON.types.json
}); });
@ -168,7 +161,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => {
<> <>
<DialogButton <DialogButton
onClick={exportPreset} onClick={exportPreset}
className="dark:hover:gray-400 bg-red border-gray-700" className="dark:hover:gray-400 border-gray-700"
> >
Export Export
</DialogButton> </DialogButton>

View file

@ -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 (
<OpenAISettings
readonly={true}
model={preset?.model}
setModel={setOption('model')}
chatGptLabel={preset?.chatGptLabel}
setChatGptLabel={setOption('chatGptLabel')}
promptPrefix={preset?.promptPrefix}
setPromptPrefix={setOption('promptPrefix')}
temperature={preset?.temperature}
setTemperature={setOption('temperature')}
topP={preset?.top_p}
setTopP={setOption('top_p')}
freqP={preset?.presence_penalty}
setFreqP={setOption('presence_penalty')}
presP={preset?.frequency_penalty}
setPresP={setOption('frequency_penalty')}
/>
);
else if (endpoint === 'bingAI')
return (
<BingAISettings
readonly={true}
context={preset?.context}
setContext={setOption('context')}
systemMessage={preset?.systemMessage}
setSystemMessage={setOption('systemMessage')}
jailbreak={preset?.jailbreak}
setJailbreak={setOption('jailbreak')}
/>
);
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 (
<>
<Dialog
open={open}
onOpenChange={onOpenChange}
>
<DialogTemplate
title={title || 'View Options'}
className="max-w-full sm:max-w-4xl"
main={
<div className="flex w-full flex-col items-center gap-2">
<div className="w-full p-0">{renderSettings()}</div>
</div>
}
buttons={
<>
<DialogButton
onClick={saveAsPreset}
className="dark:hover:gray-400 border-gray-700 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
>
Save As Preset
</DialogButton>
</>
}
leftButtons={
<>
<DialogButton
onClick={exportPreset}
className="dark:hover:gray-400 border-gray-700"
>
Export
</DialogButton>
</>
}
/>
</Dialog>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={preset}
/>
</>
);
};
export default EndpointOptionsDialog;

View file

@ -4,6 +4,7 @@ import ModelDropDown from '../../ui/ModelDropDown';
import { Input } from '~/components/ui/Input.tsx'; import { Input } from '~/components/ui/Input.tsx';
import { Label } from '~/components/ui/Label.tsx'; import { Label } from '~/components/ui/Label.tsx';
import { Slider } from '~/components/ui/Slider.tsx'; import { Slider } from '~/components/ui/Slider.tsx';
import { InputNumber } from '../../ui/InputNumber';
import OptionHover from './OptionHover'; import OptionHover from './OptionHover';
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx'; import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
import { cn } from '~/utils/'; import { cn } from '~/utils/';
@ -15,6 +16,7 @@ const optionText =
function Settings(props) { function Settings(props) {
const { const {
readonly,
model, model,
setModel, setModel,
chatGptLabel, chatGptLabel,
@ -38,6 +40,7 @@ function Settings(props) {
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<ModelDropDown <ModelDropDown
model={model} model={model}
disabled={readonly}
setModel={setModel} setModel={setModel}
endpoint="openAI" endpoint="openAI"
className={cn( className={cn(
@ -73,6 +76,7 @@ function Settings(props) {
</Label> </Label>
<Input <Input
id="chatGptLabel" id="chatGptLabel"
disabled={readonly}
value={chatGptLabel || ''} value={chatGptLabel || ''}
// ref={inputRef} // ref={inputRef}
onChange={e => setChatGptLabel(e.target.value || null)} onChange={e => setChatGptLabel(e.target.value || null)}
@ -92,6 +96,7 @@ function Settings(props) {
</Label> </Label>
<TextareaAutosize <TextareaAutosize
id="promptPrefix" id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''} value={promptPrefix || ''}
onChange={e => setPromptPrefix(e.target.value || null)} onChange={e => setPromptPrefix(e.target.value || null)}
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
@ -123,6 +128,7 @@ function Settings(props) {
</Label> </Label>
<Input <Input
id="temp-int" id="temp-int"
disabled
value={temperature} value={temperature}
onChange={e => setTemperature(e.target.value)} onChange={e => setTemperature(e.target.value)}
className={cn( className={cn(
@ -132,6 +138,7 @@ function Settings(props) {
/> />
</div> </div>
<Slider <Slider
disabled={readonly}
value={[temperature]} value={[temperature]}
onValueChange={value => setTemperature(value[0])} onValueChange={value => setTemperature(value[0])}
max={2} max={2}
@ -157,6 +164,7 @@ function Settings(props) {
</Label> </Label>
<Input <Input
id="max-tokens-int" id="max-tokens-int"
disabled
value={maxTokens} value={maxTokens}
onChange={e => setMaxTokens(e.target.value)} onChange={e => setMaxTokens(e.target.value)}
className={cn( className={cn(
@ -166,6 +174,7 @@ function Settings(props) {
/> />
</div> </div>
<Slider <Slider
disabled={readonly}
value={[maxTokens]} value={[maxTokens]}
onValueChange={value => setMaxTokens(value[0])} onValueChange={value => setMaxTokens(value[0])}
max={2048} // should be dynamic to the currently selected model max={2048} // should be dynamic to the currently selected model
@ -191,6 +200,7 @@ function Settings(props) {
</Label> </Label>
<Input <Input
id="top-p-int" id="top-p-int"
disabled
value={topP} value={topP}
onChange={e => setTopP(e.target.value)} onChange={e => setTopP(e.target.value)}
className={cn( className={cn(
@ -200,6 +210,7 @@ function Settings(props) {
/> />
</div> </div>
<Slider <Slider
disabled={readonly}
value={[topP]} value={[topP]}
onValueChange={value => setTopP(value[0])} onValueChange={value => setTopP(value[0])}
max={1} max={1}
@ -225,6 +236,7 @@ function Settings(props) {
</Label> </Label>
<Input <Input
id="freq-penalty-int" id="freq-penalty-int"
disabled
value={freqP} value={freqP}
onChange={e => setFreqP(e.target.value)} onChange={e => setFreqP(e.target.value)}
className={cn( className={cn(
@ -234,6 +246,7 @@ function Settings(props) {
/> />
</div> </div>
<Slider <Slider
disabled={readonly}
value={[freqP]} value={[freqP]}
onValueChange={value => setFreqP(value[0])} onValueChange={value => setFreqP(value[0])}
max={2} max={2}
@ -259,6 +272,7 @@ function Settings(props) {
</Label> </Label>
<Input <Input
id="pres-penalty-int" id="pres-penalty-int"
disabled
value={presP} value={presP}
onChange={e => setPresP(e.target.value)} onChange={e => setPresP(e.target.value)}
className={cn( className={cn(
@ -268,6 +282,7 @@ function Settings(props) {
/> />
</div> </div>
<Slider <Slider
disabled={readonly}
value={[presP]} value={[presP]}
onValueChange={value => setPresP(value[0])} onValueChange={value => setPresP(value[0])}
max={2} max={2}
@ -281,158 +296,6 @@ function Settings(props) {
side="left" side="left"
/> />
</HoverCard> </HoverCard>
{/* <div className="flex justify-around">
<HoverCard>
<HoverCardTrigger className="group/temp mr-4 flex w-full items-center justify-end gap-4">
<Label
htmlFor="temperature"
className="mr-2 text-right"
>
Temperature
</Label>
<Input
id="temp-int"
value={temperature}
onChange={e => setTemperature(e.target.value)}
className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/temp:border-gray-200'))}
/>
</HoverCardTrigger>
<OptionHover
type="temp"
side="right"
/>
</HoverCard>
<HoverCard>
<HoverCardTrigger className="group/max mr-4 flex w-full items-center justify-end gap-4">
<Label
htmlFor="max-tokens"
className="mr-2 w-full text-right"
>
Max tokens
</Label>
<Input
id="max-tokens-int"
value={maxTokens}
onChange={e => setMaxTokens(e.target.value)}
className={cn(defaultTextProps, cn(optionText, 'w-11 group-hover/max:border-gray-200'))}
/>
</HoverCardTrigger>
<OptionHover
type="max"
side="left"
/>
</HoverCard>
</div>
<div className="grid grid-cols-2 items-center gap-5">
<Slider
value={[maxTokens]}
onValueChange={value => setMaxTokens(value)}
max={2048} // should be dynamic to the currently selected model
min={1}
step={1}
className="w-full"
/>
</div>
<div className="flex justify-around">
<HoverCard>
<HoverCardTrigger className="group/top mr-4 flex w-full items-center justify-end gap-4">
<Label
htmlFor="top-p"
className="mr-2 text-right"
>
Top P
</Label>
<Input
id="top-p-int"
value={topP}
onChange={e => setTopP(e.target.value)}
className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/top:border-gray-200'))}
/>
<OptionHover
type="top-p"
side="right"
/>
</HoverCardTrigger>
</HoverCard>
<HoverCard>
<HoverCardTrigger className="group/freq mr-4 flex w-full items-center justify-end gap-4">
<Label
htmlFor="freq-penalty"
className="mr-2 w-full text-right"
>
Frequency Penalty
</Label>
<Input
id="freq-penalty-int"
value={freqP}
onChange={e => setFreqP(e.target.value)}
className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/freq:border-gray-200'))}
/>
</HoverCardTrigger>
<OptionHover
type="freq"
side="left"
/>
</HoverCard>
</div>
<div className="grid grid-cols-2 items-center gap-5">
<Slider
value={[topP]}
onValueChange={value => setTopP(value)}
max={1}
min={0}
step={0.01}
className="w-full"
/>
<Slider
value={[freqP]}
onValueChange={value => setFreqP(value)}
max={2}
min={-2}
step={0.01}
className="w-full"
/>
</div>
<div className="flex justify-end">
<HoverCard>
<HoverCardTrigger className="group/pres mr-4 flex items-center justify-end gap-4">
<Label
htmlFor="pres-penalty"
className="mr-2 text-right"
>
Presence Penalty
</Label>
<Input
id="pres-penalty-int"
value={presP}
onChange={e => setPresP(e.target.value)}
className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/pres:border-gray-200'))}
/>
</HoverCardTrigger>
<OptionHover
type="pres"
side="left"
/>
</HoverCard>
</div>
<div className="grid grid-cols-2 items-center gap-5">
<Slider
value={[presP]}
onValueChange={value => setPresP(value)}
max={2}
min={-2}
step={0.01}
className="w-full opacity-0"
/>
<Slider
value={[presP]}
onValueChange={value => setPresP(value)}
max={2}
min={-2}
step={0.01}
className="w-full"
/>
</div> */}
</div> </div>
</div> </div>
</> </>

View file

@ -6,11 +6,11 @@ import { Dialog } from '../ui/Dialog.tsx';
import { Input } from '../ui/Input.tsx'; import { Input } from '../ui/Input.tsx';
import { Label } from '../ui/Label.tsx'; import { Label } from '../ui/Label.tsx';
import { cn } from '~/utils/'; import { cn } from '~/utils/';
import buildPresetByConversation from '~/utils/buildPresetByConversation'; import cleanupPreset from '~/utils/cleanupPreset';
import store from '~/store'; import store from '~/store';
const SaveAsPresetDialog = ({ open, onOpenChange, conversation }) => { const SaveAsPresetDialog = ({ open, onOpenChange, preset }) => {
const [title, setTitle] = useState('My Preset'); const [title, setTitle] = useState('My Preset');
const setPresets = useSetRecoilState(store.presets); 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'; '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 submitPreset = () => {
const preset = buildPresetByConversation({ const _preset = cleanupPreset({
title, ...preset,
conversation title
}); });
axios({ axios({
method: 'post', method: 'post',
url: '/api/presets', url: '/api/presets',
data: preset, data: _preset,
withCredentials: true withCredentials: true
}).then(res => { }).then(res => {
setPresets(res?.data); setPresets(res?.data);

View file

@ -131,7 +131,7 @@ function BingAIOptions() {
<SaveAsPresetDialog <SaveAsPresetDialog
open={saveAsDialogShow} open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow} onOpenChange={setSaveAsDialogShow}
conversation={conversation} preset={conversation}
/> />
</> </>
); );

View file

@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue, useRecoilState } from 'recoil';
import EditPresetDialog from '../../Endpoints/EditPresetDialog'; import EditPresetDialog from '../../Endpoints/EditPresetDialog';
import EndpointItems from './EndpointItems'; import EndpointItems from './EndpointItems';
import PresetItems from './PresetItems'; import PresetItems from './PresetItems';
import getIcon from '~/utils/getIcon'; import getIcon from '~/utils/getIcon';
import manualSWR from '~/utils/fetchers';
import { Button } from '../../ui/Button.tsx'; import { Button } from '../../ui/Button.tsx';
import { import {
@ -27,13 +28,18 @@ export default function NewConversationMenu() {
// const models = useRecoilValue(store.models); // const models = useRecoilValue(store.models);
const availableEndpoints = useRecoilValue(store.availableEndpoints); const availableEndpoints = useRecoilValue(store.availableEndpoints);
// const setCustomGPTModels = useSetRecoilState(store.customGPTModels); // const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
const presets = useRecoilValue(store.presets); const [presets, setPresets] = useRecoilState(store.presets);
const conversation = useRecoilValue(store.conversation) || {}; const conversation = useRecoilValue(store.conversation) || {};
const { endpoint, conversationId } = conversation; const { endpoint, conversationId } = conversation;
// const { model, promptPrefix, chatGptLabel, conversationId } = conversation; // const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
const { newConversation } = store.useConversation(); 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 // update the default model when availableModels changes
// typically, availableModels changes => modelsFilter or customGPTModels changes // typically, availableModels changes => modelsFilter or customGPTModels changes
useEffect(() => { useEffect(() => {
@ -73,6 +79,10 @@ export default function NewConversationMenu() {
setPreset(preset); setPreset(preset);
}; };
const clearPreset = () => {
clearPresetsTrigger({});
};
const icon = getIcon({ const icon = getIcon({
size: 32, size: 32,
...conversation, ...conversation,
@ -99,7 +109,7 @@ export default function NewConversationMenu() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
className="min-w-56 dark:bg-gray-700" className="min-w-[300px] dark:bg-gray-700"
onCloseAutoFocus={event => event.preventDefault()} onCloseAutoFocus={event => event.preventDefault()}
> >
<DropdownMenuLabel className="dark:text-gray-300">Select an Endpoint</DropdownMenuLabel> <DropdownMenuLabel className="dark:text-gray-300">Select an Endpoint</DropdownMenuLabel>
@ -121,7 +131,17 @@ export default function NewConversationMenu() {
<div className="mt-6 w-full" /> <div className="mt-6 w-full" />
<DropdownMenuLabel className="dark:text-gray-300">Select a Preset</DropdownMenuLabel> <DropdownMenuLabel className="flex items-center dark:text-gray-300">
<span>Select a Preset</span>
<div className="flex-1" />
<Button
type="button"
className="h-auto bg-transparent px-2 py-1 text-xs font-medium font-normal text-red-700 hover:bg-slate-200 hover:text-red-700 dark:bg-transparent dark:text-red-400 dark:hover:bg-gray-800 dark:hover:text-red-400"
onClick={clearPreset}
>
Clear All
</Button>
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
onValueChange={onSelectPreset} onValueChange={onSelectPreset}

View file

@ -136,7 +136,7 @@ function OpenAIOptions() {
<SaveAsPresetDialog <SaveAsPresetDialog
open={saveAsDialogShow} open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow} onOpenChange={setSaveAsDialogShow}
conversation={conversation} preset={conversation}
/> />
</> </>
); );

View file

@ -1,5 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import EndpointOptionsDialog from '../Endpoints/EndpointOptionsDialog';
import { Button } from '../ui/Button.tsx';
import store from '~/store'; import store from '~/store';
@ -14,6 +16,7 @@ const clipPromptPrefix = str => {
}; };
const MessageHeader = ({ isSearchView = false }) => { const MessageHeader = ({ isSearchView = false }) => {
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const [extended, setExtended] = useState(false); const [extended, setExtended] = useState(false);
const conversation = useRecoilValue(store.conversation); const conversation = useRecoilValue(store.conversation);
const searchQuery = useRecoilValue(store.searchQuery); const searchQuery = useRecoilValue(store.searchQuery);
@ -64,30 +67,39 @@ const MessageHeader = ({ isSearchView = false }) => {
}; };
return ( return (
<div <>
className={ <div
'dark:text-gray-450 w-full gap-1 border-b border-black/10 bg-gray-50 text-sm text-gray-500 transition-all hover:bg-gray-100 dark:border-gray-900/50 dark:bg-gray-700 dark:hover:bg-gray-600' + className={
(extended ? ' max-h-[500px]' : ' max-h-[45px]') 'dark:text-gray-450 w-full gap-1 border-b border-black/10 bg-gray-50 text-sm text-gray-500 transition-all hover:bg-gray-100 dark:border-gray-900/50 dark:bg-gray-700 dark:hover:bg-gray-600' +
} (extended ? ' max-h-[500px]' : ' max-h-[45px]')
onClick={triggerExtend} }
> onClick={triggerExtend}
<div className="d-block flex w-full items-center justify-center p-3">{getConversationTitle()}</div> >
<div className="d-block flex w-full items-center justify-center p-3">{getConversationTitle()}</div>
{extended ? ( {extended ? (
<div className="d-block relative w-full border-t border-black/10 p-3 dark:border-gray-900/50 "> <div className="d-block relative w-full border-t border-black/10 p-3 dark:border-gray-900/50 ">
<div className="relative m-auto flex flex-wrap items-center justify-start md:max-w-2xl lg:max-w-2xl xl:max-w-3xl"> <div className="relative m-auto flex flex-wrap items-center justify-start md:max-w-2xl lg:max-w-2xl xl:max-w-3xl">
{options.map(([key, value]) => ( {options.map(([key, value]) => (
<div <div
key={key} key={key}
className="w-1/2 xl:w-1/3" className="w-1/2 xl:w-1/3"
> >
<strong>{key}:</strong> {value || 'null'} <strong>{key}:</strong> {value || 'null'}
</div> </div>
))} ))}
</div>
<Button onClick={() => setSaveAsDialogShow(true)}>View</Button>
</div> </div>
</div> ) : null}
) : null} </div>
</div>
<EndpointOptionsDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={conversation}
/>
</>
); );
}; };

View file

@ -52,7 +52,7 @@ export default function DialogTemplate({
{main ? main : null} {main ? main : null}
<DialogFooter> <DialogFooter>
<div>{leftButtons ? leftButtons : null}</div> <div>{leftButtons ? leftButtons : null}</div>
<div> <div className="flex gap-2">
<DialogClose className="dark:hover:gray-400 border-gray-700">Cancel</DialogClose> <DialogClose className="dark:hover:gray-400 border-gray-700">Cancel</DialogClose>
{buttons ? buttons : null} {buttons ? buttons : null}
{selection ? ( {selection ? (

View file

@ -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<typeof InputNumber>, InputNumberPrimitive.InputNumberProps>(
// ({ className, ...props }, ref) => {
// return (
// <InputNumber
// className={cn(
// "flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
// className
// )}
// ref={ref}
// {...props}
// />
// )
// }
// )
// _InputNumber.displayName = "Input"
// console.log(_InputNumber);
const InputNumber = React.forwardRef(({ className, ...props }, ref) => {
return (
<NumericFormat
className={cn(
'flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',
className
)}
ref={ref}
{...props}
/>
);
});
export { InputNumber };

View file

@ -7,6 +7,7 @@ import store from '~/store';
function ModelDropDown({ function ModelDropDown({
model, model,
disabled,
setModel, setModel,
endpoint, endpoint,
showAbove = false, showAbove = false,
@ -23,6 +24,7 @@ function ModelDropDown({
<Listbox <Listbox
value={model} value={model}
onChange={setModel} onChange={setModel}
disabled={disabled}
> >
{({ open }) => ( {({ open }) => (
<> <>

View file

@ -22,6 +22,8 @@ import getDefaultConversation from '~/utils/getDefaultConversation';
// frequency_penalty: 0, // frequency_penalty: 0,
// // for bingAI only // // for bingAI only
// jailbreak: false, // jailbreak: false,
// context: null,
// systemMessage: null,
// jailbreakConversationId: null, // jailbreakConversationId: null,
// conversationSignature: null, // conversationSignature: null,
// clientId: null, // clientId: null,

View file

@ -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;

View file

@ -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;