mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-29 06:38:50 +01:00
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:
parent
dae0c2d5e3
commit
d2579b44d1
15 changed files with 340 additions and 269 deletions
|
|
@ -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) {
|
|||
</Label>
|
||||
<TextareaAutosize
|
||||
id="context"
|
||||
disabled={readonly}
|
||||
value={context || ''}
|
||||
onChange={e => 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) {
|
|||
<div className="flex items-center space-x-3">
|
||||
<Checkbox
|
||||
id="jailbreak"
|
||||
disabled={readonly}
|
||||
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"
|
||||
// onCheckedChange={setJailbreak}
|
||||
|
|
@ -79,6 +81,7 @@ function Settings(props) {
|
|||
</Label> */}
|
||||
<TextareaAutosize
|
||||
id="systemMessage"
|
||||
disabled={readonly}
|
||||
value={systemMessage || ''}
|
||||
onChange={e => setSystemMessage(e.target.value || null)}
|
||||
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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 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 }) => {
|
|||
<>
|
||||
<DialogButton
|
||||
onClick={exportPreset}
|
||||
className="dark:hover:gray-400 bg-red border-gray-700"
|
||||
className="dark:hover:gray-400 border-gray-700"
|
||||
>
|
||||
Export
|
||||
</DialogButton>
|
||||
|
|
|
|||
134
client/src/components/Endpoints/EndpointOptionsDialog.jsx
Normal file
134
client/src/components/Endpoints/EndpointOptionsDialog.jsx
Normal 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;
|
||||
|
|
@ -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) {
|
|||
<div className="grid w-full items-center gap-2">
|
||||
<ModelDropDown
|
||||
model={model}
|
||||
disabled={readonly}
|
||||
setModel={setModel}
|
||||
endpoint="openAI"
|
||||
className={cn(
|
||||
|
|
@ -73,6 +76,7 @@ function Settings(props) {
|
|||
</Label>
|
||||
<Input
|
||||
id="chatGptLabel"
|
||||
disabled={readonly}
|
||||
value={chatGptLabel || ''}
|
||||
// ref={inputRef}
|
||||
onChange={e => setChatGptLabel(e.target.value || null)}
|
||||
|
|
@ -92,6 +96,7 @@ function Settings(props) {
|
|||
</Label>
|
||||
<TextareaAutosize
|
||||
id="promptPrefix"
|
||||
disabled={readonly}
|
||||
value={promptPrefix || ''}
|
||||
onChange={e => 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) {
|
|||
</Label>
|
||||
<Input
|
||||
id="temp-int"
|
||||
disabled
|
||||
value={temperature}
|
||||
onChange={e => setTemperature(e.target.value)}
|
||||
className={cn(
|
||||
|
|
@ -132,6 +138,7 @@ function Settings(props) {
|
|||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[temperature]}
|
||||
onValueChange={value => setTemperature(value[0])}
|
||||
max={2}
|
||||
|
|
@ -157,6 +164,7 @@ function Settings(props) {
|
|||
</Label>
|
||||
<Input
|
||||
id="max-tokens-int"
|
||||
disabled
|
||||
value={maxTokens}
|
||||
onChange={e => setMaxTokens(e.target.value)}
|
||||
className={cn(
|
||||
|
|
@ -166,6 +174,7 @@ function Settings(props) {
|
|||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[maxTokens]}
|
||||
onValueChange={value => setMaxTokens(value[0])}
|
||||
max={2048} // should be dynamic to the currently selected model
|
||||
|
|
@ -191,6 +200,7 @@ function Settings(props) {
|
|||
</Label>
|
||||
<Input
|
||||
id="top-p-int"
|
||||
disabled
|
||||
value={topP}
|
||||
onChange={e => setTopP(e.target.value)}
|
||||
className={cn(
|
||||
|
|
@ -200,6 +210,7 @@ function Settings(props) {
|
|||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[topP]}
|
||||
onValueChange={value => setTopP(value[0])}
|
||||
max={1}
|
||||
|
|
@ -225,6 +236,7 @@ function Settings(props) {
|
|||
</Label>
|
||||
<Input
|
||||
id="freq-penalty-int"
|
||||
disabled
|
||||
value={freqP}
|
||||
onChange={e => setFreqP(e.target.value)}
|
||||
className={cn(
|
||||
|
|
@ -234,6 +246,7 @@ function Settings(props) {
|
|||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[freqP]}
|
||||
onValueChange={value => setFreqP(value[0])}
|
||||
max={2}
|
||||
|
|
@ -259,6 +272,7 @@ function Settings(props) {
|
|||
</Label>
|
||||
<Input
|
||||
id="pres-penalty-int"
|
||||
disabled
|
||||
value={presP}
|
||||
onChange={e => setPresP(e.target.value)}
|
||||
className={cn(
|
||||
|
|
@ -268,6 +282,7 @@ function Settings(props) {
|
|||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[presP]}
|
||||
onValueChange={value => setPresP(value[0])}
|
||||
max={2}
|
||||
|
|
@ -281,158 +296,6 @@ function Settings(props) {
|
|||
side="left"
|
||||
/>
|
||||
</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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue