mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
feat: Implement Default Preset Selection for Conversations 📌 (#1275)
* fix: type issues with icons * refactor: use react query for presets, show toasts on preset crud, refactor mutations, remove presetsQuery from Root (breaking change) * refactor: change preset titling * refactor: update preset schemas and methods for necessary new properties `order` and `defaultPreset` * feat: add `defaultPreset` Recoil value * refactor(getPresetTitle): make logic cleaner and more concise * feat: complete UI portion of defaultPreset feature, with animations added to preset items * chore: remove console.logs() * feat: complete default preset handling * refactor: remove user sensitive values on logout * fix: allow endpoint selection without default preset overwriting
This commit is contained in:
parent
fdb65366d7
commit
ca64efec1b
32 changed files with 681 additions and 270 deletions
|
|
@ -14,24 +14,53 @@ module.exports = {
|
||||||
getPreset,
|
getPreset,
|
||||||
getPresets: async (user, filter) => {
|
getPresets: async (user, filter) => {
|
||||||
try {
|
try {
|
||||||
return await Preset.find({ ...filter, user }).lean();
|
const presets = await Preset.find({ ...filter, user }).lean();
|
||||||
|
const defaultValue = 10000;
|
||||||
|
|
||||||
|
presets.sort((a, b) => {
|
||||||
|
let orderA = a.order !== undefined ? a.order : defaultValue;
|
||||||
|
let orderB = b.order !== undefined ? b.order : defaultValue;
|
||||||
|
|
||||||
|
if (orderA !== orderB) {
|
||||||
|
return orderA - orderB;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.updatedAt - a.updatedAt;
|
||||||
|
});
|
||||||
|
|
||||||
|
return presets;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return { message: 'Error retrieving presets' };
|
return { message: 'Error retrieving presets' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
savePreset: async (user, { presetId, newPresetId, ...preset }) => {
|
savePreset: async (user, { presetId, newPresetId, defaultPreset, ...preset }) => {
|
||||||
try {
|
try {
|
||||||
|
const setter = { $set: {} };
|
||||||
const update = { presetId, ...preset };
|
const update = { presetId, ...preset };
|
||||||
if (newPresetId) {
|
if (newPresetId) {
|
||||||
update.presetId = newPresetId;
|
update.presetId = newPresetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Preset.findOneAndUpdate(
|
if (defaultPreset) {
|
||||||
{ presetId, user },
|
update.defaultPreset = defaultPreset;
|
||||||
{ $set: update },
|
update.order = 0;
|
||||||
{ new: true, upsert: true },
|
|
||||||
);
|
const currentDefault = await Preset.findOne({ defaultPreset: true, user });
|
||||||
|
|
||||||
|
if (currentDefault && currentDefault.presetId !== presetId) {
|
||||||
|
await Preset.findByIdAndUpdate(currentDefault._id, {
|
||||||
|
$unset: { defaultPreset: '', order: '' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (defaultPreset === false) {
|
||||||
|
update.defaultPreset = undefined;
|
||||||
|
update.order = undefined;
|
||||||
|
setter['$unset'] = { defaultPreset: '', order: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
setter.$set = update;
|
||||||
|
return await Preset.findOneAndUpdate({ presetId, user }, setter, { new: true, upsert: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return { message: 'Error saving preset' };
|
return { message: 'Error saving preset' };
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ const presetSchema = mongoose.Schema(
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
defaultPreset: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
// google only
|
// google only
|
||||||
examples: [{ type: mongoose.Schema.Types.Mixed }],
|
examples: [{ type: mongoose.Schema.Types.Mixed }],
|
||||||
...conversationPreset,
|
...conversationPreset,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ const crypto = require('crypto');
|
||||||
const requireJwtAuth = require('../middleware/requireJwtAuth');
|
const requireJwtAuth = require('../middleware/requireJwtAuth');
|
||||||
|
|
||||||
router.get('/', requireJwtAuth, async (req, res) => {
|
router.get('/', requireJwtAuth, async (req, res) => {
|
||||||
const presets = (await getPresets(req.user.id)).map((preset) => {
|
const presets = (await getPresets(req.user.id)).map((preset) => preset);
|
||||||
return preset;
|
|
||||||
});
|
|
||||||
res.status(200).send(presets);
|
res.status(200).send(presets);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -17,12 +15,8 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||||
update.presetId = update?.presetId || crypto.randomUUID();
|
update.presetId = update?.presetId || crypto.randomUUID();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await savePreset(req.user.id, update);
|
const preset = await savePreset(req.user.id, update);
|
||||||
|
res.status(201).send(preset);
|
||||||
const presets = (await getPresets(req.user.id)).map((preset) => {
|
|
||||||
return preset;
|
|
||||||
});
|
|
||||||
res.status(201).send(presets);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
res.status(500).send(error);
|
res.status(500).send(error);
|
||||||
|
|
@ -31,7 +25,7 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||||
|
|
||||||
router.post('/delete', requireJwtAuth, async (req, res) => {
|
router.post('/delete', requireJwtAuth, async (req, res) => {
|
||||||
let filter = {};
|
let filter = {};
|
||||||
const { presetId } = req.body.arg || {};
|
const { presetId } = req.body || {};
|
||||||
|
|
||||||
if (presetId) {
|
if (presetId) {
|
||||||
filter = { presetId };
|
filter = { presetId };
|
||||||
|
|
@ -40,9 +34,8 @@ router.post('/delete', requireJwtAuth, async (req, res) => {
|
||||||
console.log('delete preset filter', filter);
|
console.log('delete preset filter', filter);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deletePresets(req.user.id, filter);
|
const deleteCount = await deletePresets(req.user.id, filter);
|
||||||
const presets = await getPresets(req.user.id);
|
res.status(201).send(deleteCount);
|
||||||
res.status(201).send(presets);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
res.status(500).send(error);
|
res.status(500).send(error);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-flip-toolkit": "^7.1.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.43.9",
|
||||||
"react-lazy-load-image-component": "^1.6.0",
|
"react-lazy-load-image-component": "^1.6.0",
|
||||||
"react-markdown": "^8.0.6",
|
"react-markdown": "^8.0.6",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import axios from 'axios';
|
import { useRecoilState } from 'recoil';
|
||||||
import filenamify from 'filenamify';
|
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
import exportFromJSON from 'export-from-json';
|
|
||||||
import { useGetEndpointsQuery } from 'librechat-data-provider';
|
import { useGetEndpointsQuery } from 'librechat-data-provider';
|
||||||
import type { TEditPresetProps } from '~/common';
|
import { cn, defaultTextProps, removeFocusOutlines, mapEndpoints } from '~/utils';
|
||||||
import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset, mapEndpoints } from '~/utils';
|
|
||||||
import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/';
|
import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/';
|
||||||
import PopoverButtons from '~/components/Endpoints/PopoverButtons';
|
import PopoverButtons from '~/components/Endpoints/PopoverButtons';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||||
|
|
@ -13,42 +9,21 @@ import { EndpointSettings } from '~/components/Endpoints';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
const EditPresetDialog = ({ open, onOpenChange, title }: Omit<TEditPresetProps, 'preset'>) => {
|
const EditPresetDialog = ({
|
||||||
|
exportPreset,
|
||||||
|
submitPreset,
|
||||||
|
}: {
|
||||||
|
exportPreset: () => void;
|
||||||
|
submitPreset: () => void;
|
||||||
|
}) => {
|
||||||
|
const localize = useLocalize();
|
||||||
const { preset } = useChatContext();
|
const { preset } = useChatContext();
|
||||||
|
const { setOption } = useSetIndexOptions(preset);
|
||||||
|
const [presetModalVisible, setPresetModalVisible] = useRecoilState(store.presetModalVisible);
|
||||||
|
|
||||||
// TODO: use React Query for presets data
|
|
||||||
const setPresets = useSetRecoilState(store.presets);
|
|
||||||
const { data: availableEndpoints = [] } = useGetEndpointsQuery({
|
const { data: availableEndpoints = [] } = useGetEndpointsQuery({
|
||||||
select: mapEndpoints,
|
select: mapEndpoints,
|
||||||
});
|
});
|
||||||
const { setOption } = useSetIndexOptions(preset);
|
|
||||||
const localize = useLocalize();
|
|
||||||
|
|
||||||
const submitPreset = () => {
|
|
||||||
if (!preset) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
axios({
|
|
||||||
method: 'post',
|
|
||||||
url: '/api/presets',
|
|
||||||
data: cleanupPreset({ preset }),
|
|
||||||
withCredentials: true,
|
|
||||||
}).then((res) => {
|
|
||||||
setPresets(res?.data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportPreset = () => {
|
|
||||||
if (!preset) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fileName = filenamify(preset?.title || 'preset');
|
|
||||||
exportFromJSON({
|
|
||||||
data: cleanupPreset({ preset }),
|
|
||||||
fileName,
|
|
||||||
exportType: exportFromJSON.types.json,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const { endpoint } = preset || {};
|
const { endpoint } = preset || {};
|
||||||
if (!endpoint) {
|
if (!endpoint) {
|
||||||
|
|
@ -56,9 +31,9 @@ const EditPresetDialog = ({ open, onOpenChange, title }: Omit<TEditPresetProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={presetModalVisible} onOpenChange={setPresetModalVisible}>
|
||||||
<DialogTemplate
|
<DialogTemplate
|
||||||
title={`${title || localize('com_ui_edit') + ' ' + localize('com_endpoint_preset')} - ${
|
title={`${localize('com_ui_edit') + ' ' + localize('com_endpoint_preset')} - ${
|
||||||
preset?.title
|
preset?.title
|
||||||
}`}
|
}`}
|
||||||
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden lg:w-[950px] xl:h-[720px]"
|
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden lg:w-[950px] xl:h-[720px]"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
import type { FC } from 'react';
|
|
||||||
import { Trash2 } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Close } from '@radix-ui/react-popover';
|
import { Close } from '@radix-ui/react-popover';
|
||||||
|
import { Flipper, Flipped } from 'react-flip-toolkit';
|
||||||
|
import type { FC } from 'react';
|
||||||
import type { TPreset } from 'librechat-data-provider';
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
import FileUpload from '~/components/Input/EndpointMenu/FileUpload';
|
import FileUpload from '~/components/Input/EndpointMenu/FileUpload';
|
||||||
|
import { PinIcon, EditIcon, TrashIcon } from '~/components/svg';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||||
import { Dialog, DialogTrigger } from '~/components/ui/';
|
import { Dialog, DialogTrigger } from '~/components/ui/';
|
||||||
import { EditIcon, TrashIcon } from '~/components/svg';
|
|
||||||
import { MenuSeparator, MenuItem } from '../UI';
|
import { MenuSeparator, MenuItem } from '../UI';
|
||||||
import { icons } from '../Endpoints/Icons';
|
import { icons } from '../Endpoints/Icons';
|
||||||
import { getPresetTitle } from '~/utils';
|
import { getPresetTitle } from '~/utils';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
const PresetItems: FC<{
|
const PresetItems: FC<{
|
||||||
presets: TPreset[];
|
presets: TPreset[];
|
||||||
|
onSetDefaultPreset: (preset: TPreset, remove?: boolean) => void;
|
||||||
onSelectPreset: (preset: TPreset) => void;
|
onSelectPreset: (preset: TPreset) => void;
|
||||||
onChangePreset: (preset: TPreset) => void;
|
onChangePreset: (preset: TPreset) => void;
|
||||||
onDeletePreset: (preset: TPreset) => void;
|
onDeletePreset: (preset: TPreset) => void;
|
||||||
|
|
@ -20,12 +24,14 @@ const PresetItems: FC<{
|
||||||
onFileSelected: (jsonData: Record<string, unknown>) => void;
|
onFileSelected: (jsonData: Record<string, unknown>) => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
presets,
|
presets,
|
||||||
|
onSetDefaultPreset,
|
||||||
onSelectPreset,
|
onSelectPreset,
|
||||||
onChangePreset,
|
onChangePreset,
|
||||||
onDeletePreset,
|
onDeletePreset,
|
||||||
clearAllPresets,
|
clearAllPresets,
|
||||||
onFileSelected,
|
onFileSelected,
|
||||||
}) => {
|
}) => {
|
||||||
|
const defaultPreset = useRecoilValue(store.defaultPreset);
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -35,11 +41,19 @@ const PresetItems: FC<{
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<div className="flex h-full grow items-center justify-end gap-2">
|
<div className="flex h-full grow items-center justify-end gap-2">
|
||||||
|
<label
|
||||||
|
htmlFor="default-preset"
|
||||||
|
className="w-40 truncate rounded bg-transparent px-2 py-1 text-xs font-medium font-normal text-gray-600 transition-colors dark:bg-transparent dark:text-gray-300 sm:w-72"
|
||||||
|
>
|
||||||
|
{defaultPreset
|
||||||
|
? `${localize('com_endpoint_preset_default_item')} ${defaultPreset.title}`
|
||||||
|
: localize('com_endpoint_preset_default_none')}
|
||||||
|
</label>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<label
|
<label
|
||||||
htmlFor="file-upload"
|
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"
|
className="mr-1 flex h-[32px] 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" />
|
<Trash2 className="mr-1 flex w-[22px] items-center stroke-1" />
|
||||||
{localize('com_ui_clear')} {localize('com_ui_all')}
|
{localize('com_ui_clear')} {localize('com_ui_all')}
|
||||||
|
|
@ -71,29 +85,41 @@ const PresetItems: FC<{
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Flipper flipKey={presets.map(({ presetId }) => presetId).join('.')}>
|
||||||
{presets &&
|
{presets &&
|
||||||
presets.length > 0 &&
|
presets.length > 0 &&
|
||||||
presets.map((preset, i) => {
|
presets.map((preset, i) => {
|
||||||
if (!preset) {
|
if (!preset || !preset.presetId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Close asChild key={`preset-${preset.presetId}`}>
|
<Close asChild key={`preset-${preset.presetId}`}>
|
||||||
<div key={`preset-${preset.presetId}`}>
|
<div key={`preset-${preset.presetId}`}>
|
||||||
|
<Flipped flipId={preset.presetId}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={`preset-item-${preset.presetId}`}
|
key={`preset-item-${preset.presetId}`}
|
||||||
textClassName="text-xs max-w-[200px] truncate md:max-w-full "
|
textClassName="text-xs max-w-[150px] sm:max-w-[200px] truncate md:max-w-full "
|
||||||
title={getPresetTitle(preset)}
|
title={getPresetTitle(preset)}
|
||||||
disableHover={true}
|
disableHover={true}
|
||||||
onClick={() => onSelectPreset(preset)}
|
onClick={() => onSelectPreset(preset)}
|
||||||
icon={icons[preset.endpoint ?? 'unknown']({ className: 'icon-md mr-1 ' })}
|
icon={icons[preset.endpoint ?? 'unknown']({
|
||||||
// value={preset.presetId}
|
className: 'icon-md mr-1 dark:text-white',
|
||||||
|
})}
|
||||||
selected={false}
|
selected={false}
|
||||||
data-testid={`preset-item-${preset}`}
|
data-testid={`preset-item-${preset}`}
|
||||||
// description="With DALL·E, browsing and analysis"
|
|
||||||
>
|
>
|
||||||
<div className="flex h-full items-center justify-end gap-1">
|
<div className="flex h-full items-center justify-end gap-1">
|
||||||
|
<button
|
||||||
|
className="m-0 h-full rounded-md p-2 px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onSetDefaultPreset(preset, defaultPreset?.presetId === preset.presetId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PinIcon unpin={defaultPreset?.presetId === preset.presetId} />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="m-0 h-full rounded-md p-2 px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
className="m-0 h-full rounded-md p-2 px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
@ -116,11 +142,13 @@ const PresetItems: FC<{
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
</Flipped>
|
||||||
{i !== presets.length - 1 && <MenuSeparator />}
|
{i !== presets.length - 1 && <MenuSeparator />}
|
||||||
</div>
|
</div>
|
||||||
</Close>
|
</Close>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</Flipper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,25 @@
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useState } from 'react';
|
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import { BookCopy } from 'lucide-react';
|
import { BookCopy } from 'lucide-react';
|
||||||
import {
|
|
||||||
modularEndpoints,
|
|
||||||
useDeletePresetMutation,
|
|
||||||
useCreatePresetMutation,
|
|
||||||
} from 'librechat-data-provider';
|
|
||||||
import type { TPreset } from 'librechat-data-provider';
|
|
||||||
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||||
import { useLocalize, useDefaultConvo, useNavigateToConvo } from '~/hooks';
|
|
||||||
import { useChatContext, useToastContext } from '~/Providers';
|
|
||||||
import { EditPresetDialog, PresetItems } from './Presets';
|
import { EditPresetDialog, PresetItems } from './Presets';
|
||||||
import { cleanupPreset, cn } from '~/utils';
|
import { useLocalize, usePresets } from '~/hooks';
|
||||||
import store from '~/store';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
const PresetsMenu: FC = () => {
|
const PresetsMenu: FC = () => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const {
|
||||||
const { conversation, newConversation, setPreset } = useChatContext();
|
presetsQuery,
|
||||||
const { navigateToConvo } = useNavigateToConvo();
|
onSetDefaultPreset,
|
||||||
const getDefaultConversation = useDefaultConvo();
|
onFileSelected,
|
||||||
|
onSelectPreset,
|
||||||
const [presetModalVisible, setPresetModalVisible] = useState(false);
|
onChangePreset,
|
||||||
// TODO: rely on react query for presets data
|
clearAllPresets,
|
||||||
const [presets, setPresets] = useRecoilState(store.presets);
|
onDeletePreset,
|
||||||
|
submitPreset,
|
||||||
const deletePresetsMutation = useDeletePresetMutation();
|
exportPreset,
|
||||||
const createPresetMutation = useCreatePresetMutation();
|
} = usePresets();
|
||||||
|
|
||||||
const { endpoint } = conversation ?? {};
|
|
||||||
|
|
||||||
const importPreset = (jsonPreset: TPreset) => {
|
|
||||||
createPresetMutation.mutate(
|
|
||||||
{ ...jsonPreset },
|
|
||||||
{
|
|
||||||
onSuccess: (data) => {
|
|
||||||
setPresets(data);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error('Error uploading the preset:', error);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const onFileSelected = (jsonData: Record<string, unknown>) => {
|
|
||||||
const jsonPreset = { ...cleanupPreset({ preset: jsonData }), presetId: null };
|
|
||||||
importPreset(jsonPreset);
|
|
||||||
};
|
|
||||||
const onSelectPreset = (newPreset: TPreset) => {
|
|
||||||
if (!newPreset) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showToast({
|
|
||||||
message: localize('com_endpoint_preset_selected'),
|
|
||||||
showIcon: false,
|
|
||||||
duration: 750,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
modularEndpoints.has(endpoint ?? '') &&
|
|
||||||
modularEndpoints.has(newPreset?.endpoint ?? '') &&
|
|
||||||
endpoint === newPreset?.endpoint
|
|
||||||
) {
|
|
||||||
const currentConvo = getDefaultConversation({
|
|
||||||
conversation: conversation ?? {},
|
|
||||||
preset: newPreset,
|
|
||||||
});
|
|
||||||
|
|
||||||
/* We don't reset the latest message, only when changing settings mid-converstion */
|
|
||||||
navigateToConvo(currentConvo, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('preset', newPreset, endpoint);
|
|
||||||
newConversation({ preset: newPreset });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangePreset = (preset: TPreset) => {
|
|
||||||
setPreset(preset);
|
|
||||||
setPresetModalVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearAllPresets = () => {
|
|
||||||
deletePresetsMutation.mutate({ arg: {} });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDeletePreset = (preset: TPreset) => {
|
|
||||||
deletePresetsMutation.mutate({ arg: preset });
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const presets = presetsQuery.data || [];
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<Trigger asChild>
|
<Trigger asChild>
|
||||||
|
|
@ -125,6 +54,7 @@ const PresetsMenu: FC = () => {
|
||||||
>
|
>
|
||||||
<PresetItems
|
<PresetItems
|
||||||
presets={presets}
|
presets={presets}
|
||||||
|
onSetDefaultPreset={onSetDefaultPreset}
|
||||||
onSelectPreset={onSelectPreset}
|
onSelectPreset={onSelectPreset}
|
||||||
onChangePreset={onChangePreset}
|
onChangePreset={onChangePreset}
|
||||||
onDeletePreset={onDeletePreset}
|
onDeletePreset={onDeletePreset}
|
||||||
|
|
@ -134,7 +64,7 @@ const PresetsMenu: FC = () => {
|
||||||
</Content>
|
</Content>
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
<EditPresetDialog open={presetModalVisible} onOpenChange={setPresetModalVisible} />
|
<EditPresetDialog submitPreset={submitPreset} exportPreset={exportPreset} />
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ type MenuItemProps = {
|
||||||
textClassName?: string;
|
textClassName?: string;
|
||||||
disableHover?: boolean;
|
disableHover?: boolean;
|
||||||
// hoverContent?: string;
|
// hoverContent?: string;
|
||||||
};
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
const MenuItem: FC<MenuItemProps> = ({
|
const MenuItem: FC<MenuItemProps> = ({
|
||||||
title,
|
title,
|
||||||
|
|
@ -30,6 +30,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
||||||
disableHover = false,
|
disableHover = false,
|
||||||
children,
|
children,
|
||||||
onClick,
|
onClick,
|
||||||
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -40,6 +41,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
||||||
)}
|
)}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
<div className="flex grow items-center justify-between gap-2">
|
<div className="flex grow items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useCreatePresetMutation } from 'librechat-data-provider';
|
import { useCreatePresetMutation } from 'librechat-data-provider';
|
||||||
import type { TEditPresetProps } from '~/common';
|
import type { TEditPresetProps } from '~/common';
|
||||||
import { Dialog, Input, Label } from '~/components/ui/';
|
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
|
||||||
import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/';
|
import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/';
|
||||||
|
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||||
|
import { Dialog, Input, Label } from '~/components/ui/';
|
||||||
|
import { NotificationSeverity } from '~/common';
|
||||||
|
import { useToastContext } from '~/Providers';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) => {
|
const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) => {
|
||||||
const [title, setTitle] = useState<string>(preset?.title || 'My Preset');
|
const [title, setTitle] = useState<string>(preset?.title || 'My Preset');
|
||||||
const createPresetMutation = useCreatePresetMutation();
|
const createPresetMutation = useCreatePresetMutation();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
const submitPreset = () => {
|
const submitPreset = () => {
|
||||||
|
|
@ -18,7 +21,24 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
||||||
title,
|
title,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
createPresetMutation.mutate(_preset);
|
|
||||||
|
const toastTitle = _preset.title
|
||||||
|
? `\`${_preset.title}\``
|
||||||
|
: localize('com_endpoint_preset_title');
|
||||||
|
|
||||||
|
createPresetMutation.mutate(_preset, {
|
||||||
|
onSuccess: () => {
|
||||||
|
showToast({
|
||||||
|
message: `${toastTitle} ${localize('com_endpoint_preset_saved')}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_endpoint_preset_save_error'),
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ const Logout = forwardRef(() => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem('lastConversationSetup');
|
||||||
|
localStorage.removeItem('lastSelectedTools');
|
||||||
|
localStorage.removeItem('lastAssistant');
|
||||||
|
localStorage.removeItem('autoScroll');
|
||||||
logout();
|
logout();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
export default function AnthropicIcon({ size = 25, className = '' }) {
|
export default function AnthropicIcon({
|
||||||
|
size = 25,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 24 16"
|
viewBox="0 0 24 16"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
/* eslint-disable indent */
|
/* eslint-disable indent */
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
export default function AzureMinimalIcon({ size = 25, className = 'h-4 w-4' }) {
|
export default function AzureMinimalIcon({
|
||||||
|
size = 25,
|
||||||
|
className = 'h-4 w-4',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
const height = size;
|
const height = size;
|
||||||
const width = size;
|
const width = size;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
export default function GPTIcon({ size = 25, className = '' }) {
|
export default function GPTIcon({
|
||||||
|
size = 25,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
const unit = '41';
|
const unit = '41';
|
||||||
const height = size;
|
const height = size;
|
||||||
const width = size;
|
const width = size;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
export default function MinimalPlugin({ size, className = 'icon-md' }) {
|
export default function MinimalPlugin({
|
||||||
|
size,
|
||||||
|
className = 'icon-md',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width={size}
|
width={size}
|
||||||
|
|
|
||||||
53
client/src/components/svg/PinIcon.tsx
Normal file
53
client/src/components/svg/PinIcon.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
export default function PinIcon({ unpin = false }: { unpin?: boolean }) {
|
||||||
|
if (unpin) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="icon-sm"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15 15V17.5585C15 18.4193 14.4491 19.1836 13.6325 19.4558L13.1726 19.6091C12.454 19.8487 11.6616 19.6616 11.126 19.126L4.87403 12.874C4.33837 12.3384 4.15132 11.546 4.39088 10.8274L4.54415 10.3675C4.81638 9.55086 5.58066 9 6.44152 9H9M12 6.2L13.6277 3.92116C14.3461 2.91549 15.7955 2.79552 16.6694 3.66942L20.3306 7.33058C21.2045 8.20448 21.0845 9.65392 20.0788 10.3723L18 11.8571"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 16L3 21"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4 4L20 20"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="icon-sm"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M17.4845 2.8798C16.1773 1.57258 14.0107 1.74534 12.9272 3.24318L9.79772 7.56923C9.60945 7.82948 9.30775 7.9836 8.98654 7.9836H6.44673C3.74061 7.9836 2.27414 11.6759 4.16948 13.5713L6.59116 15.993L2.29324 20.2909C1.90225 20.6819 1.90225 21.3158 2.29324 21.7068C2.68422 22.0977 3.31812 22.0977 3.70911 21.7068L8.00703 17.4088L10.4287 19.8305C12.3241 21.7259 16.0164 20.2594 16.0164 17.5533V15.0135C16.0164 14.6923 16.1705 14.3906 16.4308 14.2023L20.7568 11.0728C22.2547 9.98926 22.4274 7.8227 21.1202 6.51549L17.4845 2.8798ZM11.8446 18.4147C12.4994 19.0694 14.0141 18.4928 14.0141 17.5533V15.0135C14.0141 14.0499 14.4764 13.1447 15.2572 12.58L19.5832 9.45047C20.0825 9.08928 20.1401 8.3671 19.7043 7.93136L16.0686 4.29567C15.6329 3.85993 14.9107 3.91751 14.5495 4.4168L11.4201 8.74285C10.8553 9.52359 9.95016 9.98594 8.98654 9.98594H6.44673C5.5072 9.98594 4.93059 11.5006 5.58535 12.1554L11.8446 18.4147Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ export { default as SendIcon } from './SendIcon';
|
||||||
export { default as LinkIcon } from './LinkIcon';
|
export { default as LinkIcon } from './LinkIcon';
|
||||||
export { default as DotsIcon } from './DotsIcon';
|
export { default as DotsIcon } from './DotsIcon';
|
||||||
export { default as GearIcon } from './GearIcon';
|
export { default as GearIcon } from './GearIcon';
|
||||||
|
export { default as PinIcon } from './PinIcon';
|
||||||
export { default as TrashIcon } from './TrashIcon';
|
export { default as TrashIcon } from './TrashIcon';
|
||||||
export { default as MinimalPlugin } from './MinimalPlugin';
|
export { default as MinimalPlugin } from './MinimalPlugin';
|
||||||
export { default as AzureMinimalIcon } from './AzureMinimalIcon';
|
export { default as AzureMinimalIcon } from './AzureMinimalIcon';
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ import type {
|
||||||
DeleteFilesResponse,
|
DeleteFilesResponse,
|
||||||
DeleteFilesBody,
|
DeleteFilesBody,
|
||||||
DeleteMutationOptions,
|
DeleteMutationOptions,
|
||||||
|
UpdatePresetOptions,
|
||||||
|
DeletePresetOptions,
|
||||||
|
PresetDeleteResponse,
|
||||||
|
TPreset,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import { dataService, MutationKeys } from 'librechat-data-provider';
|
import { dataService, MutationKeys } from 'librechat-data-provider';
|
||||||
|
|
||||||
|
|
@ -37,3 +41,31 @@ export const useDeleteFilesMutation = (
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdatePresetMutation = (
|
||||||
|
options?: UpdatePresetOptions,
|
||||||
|
): UseMutationResult<
|
||||||
|
TPreset, // response data
|
||||||
|
unknown,
|
||||||
|
TPreset,
|
||||||
|
unknown
|
||||||
|
> => {
|
||||||
|
return useMutation([MutationKeys.updatePreset], {
|
||||||
|
mutationFn: (preset: TPreset) => dataService.updatePreset(preset),
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeletePresetMutation = (
|
||||||
|
options?: DeletePresetOptions,
|
||||||
|
): UseMutationResult<
|
||||||
|
PresetDeleteResponse, // response data
|
||||||
|
unknown,
|
||||||
|
TPreset | undefined,
|
||||||
|
unknown
|
||||||
|
> => {
|
||||||
|
return useMutation([MutationKeys.deletePreset], {
|
||||||
|
mutationFn: (preset: TPreset | undefined) => dataService.deletePreset(preset),
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
||||||
1
client/src/hooks/Conversations/index.ts
Normal file
1
client/src/hooks/Conversations/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as usePresets } from './usePresets';
|
||||||
223
client/src/hooks/Conversations/usePresets.ts
Normal file
223
client/src/hooks/Conversations/usePresets.ts
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
import {
|
||||||
|
QueryKeys,
|
||||||
|
modularEndpoints,
|
||||||
|
useGetPresetsQuery,
|
||||||
|
useCreatePresetMutation,
|
||||||
|
} from 'librechat-data-provider';
|
||||||
|
import filenamify from 'filenamify';
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
import exportFromJSON from 'export-from-json';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
|
import { useUpdatePresetMutation, useDeletePresetMutation } from '~/data-provider';
|
||||||
|
import { useChatContext, useToastContext } from '~/Providers';
|
||||||
|
import useNavigateToConvo from '~/hooks/useNavigateToConvo';
|
||||||
|
import useDefaultConvo from '~/hooks/useDefaultConvo';
|
||||||
|
import { useAuthContext } from '~/hooks/AuthContext';
|
||||||
|
import { NotificationSeverity } from '~/common';
|
||||||
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
|
import { cleanupPreset } from '~/utils';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function usePresets() {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { user } = useAuthContext();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const hasLoaded = useRef(false);
|
||||||
|
|
||||||
|
const [_defaultPreset, setDefaultPreset] = useRecoilState(store.defaultPreset);
|
||||||
|
const setPresetModalVisible = useSetRecoilState(store.presetModalVisible);
|
||||||
|
const { preset, conversation, newConversation, setPreset } = useChatContext();
|
||||||
|
const presetsQuery = useGetPresetsQuery({ enabled: !!user });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (_defaultPreset || !presetsQuery.data || hasLoaded.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPreset = presetsQuery.data.find((p) => p.defaultPreset);
|
||||||
|
if (!defaultPreset) {
|
||||||
|
hasLoaded.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDefaultPreset(defaultPreset);
|
||||||
|
if (!conversation?.conversationId || conversation.conversationId === 'new') {
|
||||||
|
newConversation({ preset: defaultPreset });
|
||||||
|
}
|
||||||
|
hasLoaded.current = true;
|
||||||
|
// dependencies are stable and only needed once
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [presetsQuery.data]);
|
||||||
|
|
||||||
|
const setPresets = useCallback(
|
||||||
|
(presets: TPreset[]) => {
|
||||||
|
queryClient.setQueryData<TPreset[]>([QueryKeys.presets], presets);
|
||||||
|
},
|
||||||
|
[queryClient],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletePresetsMutation = useDeletePresetMutation({
|
||||||
|
onMutate: (preset) => {
|
||||||
|
if (!preset) {
|
||||||
|
setPresets([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const previousPresets = presetsQuery.data ?? [];
|
||||||
|
if (previousPresets) {
|
||||||
|
setPresets(previousPresets.filter((p) => p.presetId !== preset?.presetId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries([QueryKeys.presets]);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
queryClient.invalidateQueries([QueryKeys.presets]);
|
||||||
|
console.error('Error deleting the preset:', error);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_endpoint_preset_delete_error'),
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const createPresetMutation = useCreatePresetMutation();
|
||||||
|
const updatePreset = useUpdatePresetMutation({
|
||||||
|
onSuccess: (data, preset) => {
|
||||||
|
const toastTitle = data.title ? `"${data.title}"` : localize('com_endpoint_preset_title');
|
||||||
|
let message = `${toastTitle} ${localize('com_endpoint_preset_saved')}`;
|
||||||
|
if (data.defaultPreset && data.presetId !== _defaultPreset?.presetId) {
|
||||||
|
message = `${toastTitle} ${localize('com_endpoint_preset_default')}`;
|
||||||
|
setDefaultPreset(data);
|
||||||
|
newConversation({ preset: data });
|
||||||
|
} else if (preset?.defaultPreset === false) {
|
||||||
|
setDefaultPreset(null);
|
||||||
|
message = `${toastTitle} ${localize('com_endpoint_preset_default_removed')}`;
|
||||||
|
}
|
||||||
|
showToast({
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries([QueryKeys.presets]);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error updating the preset:', error);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_endpoint_preset_save_error'),
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { navigateToConvo } = useNavigateToConvo();
|
||||||
|
const getDefaultConversation = useDefaultConvo();
|
||||||
|
|
||||||
|
const { endpoint } = conversation ?? {};
|
||||||
|
|
||||||
|
const importPreset = (jsonPreset: TPreset) => {
|
||||||
|
createPresetMutation.mutate(
|
||||||
|
{ ...jsonPreset },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_endpoint_preset_import'),
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries([QueryKeys.presets]);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error uploading the preset:', error);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_endpoint_preset_import_error'),
|
||||||
|
severity: NotificationSeverity.ERROR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileSelected = (jsonData: Record<string, unknown>) => {
|
||||||
|
const jsonPreset = { ...cleanupPreset({ preset: jsonData }), presetId: null };
|
||||||
|
importPreset(jsonPreset);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectPreset = (newPreset: TPreset) => {
|
||||||
|
if (!newPreset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastTitle = newPreset.title
|
||||||
|
? `"${newPreset.title}"`
|
||||||
|
: localize('com_endpoint_preset_title');
|
||||||
|
|
||||||
|
showToast({
|
||||||
|
message: `${toastTitle} ${localize('com_endpoint_preset_selected_title')}`,
|
||||||
|
showIcon: false,
|
||||||
|
duration: 750,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
modularEndpoints.has(endpoint ?? '') &&
|
||||||
|
modularEndpoints.has(newPreset?.endpoint ?? '') &&
|
||||||
|
endpoint === newPreset?.endpoint
|
||||||
|
) {
|
||||||
|
const currentConvo = getDefaultConversation({
|
||||||
|
conversation: conversation ?? {},
|
||||||
|
preset: newPreset,
|
||||||
|
});
|
||||||
|
|
||||||
|
/* We don't reset the latest message, only when changing settings mid-converstion */
|
||||||
|
navigateToConvo(currentConvo, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newConversation({ preset: newPreset });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangePreset = (preset: TPreset) => {
|
||||||
|
setPreset(preset);
|
||||||
|
setPresetModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllPresets = () => deletePresetsMutation.mutate(undefined);
|
||||||
|
|
||||||
|
const onDeletePreset = (preset: TPreset) => {
|
||||||
|
if (!confirm(localize('com_endpoint_preset_delete_confirm'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deletePresetsMutation.mutate(preset);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitPreset = () => {
|
||||||
|
if (!preset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePreset.mutate(cleanupPreset({ preset }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSetDefaultPreset = (preset: TPreset, remove = false) => {
|
||||||
|
updatePreset.mutate({ ...preset, defaultPreset: !remove });
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportPreset = () => {
|
||||||
|
if (!preset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fileName = filenamify(preset?.title || 'preset');
|
||||||
|
exportFromJSON({
|
||||||
|
data: cleanupPreset({ preset }),
|
||||||
|
fileName,
|
||||||
|
exportType: exportFromJSON.types.json,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
presetsQuery,
|
||||||
|
onSetDefaultPreset,
|
||||||
|
onFileSelected,
|
||||||
|
onSelectPreset,
|
||||||
|
onChangePreset,
|
||||||
|
clearAllPresets,
|
||||||
|
onDeletePreset,
|
||||||
|
submitPreset,
|
||||||
|
exportPreset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './Messages';
|
export * from './Messages';
|
||||||
export * from './Input';
|
export * from './Input';
|
||||||
|
export * from './Conversations';
|
||||||
|
|
||||||
export * from './AuthContext';
|
export * from './AuthContext';
|
||||||
export * from './ThemeContext';
|
export * from './ThemeContext';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useGetEndpointsQuery } from 'librechat-data-provider';
|
import { useGetEndpointsQuery } from 'librechat-data-provider';
|
||||||
import { useSetRecoilState, useResetRecoilState, useRecoilCallback, useRecoilState } from 'recoil';
|
import {
|
||||||
|
useSetRecoilState,
|
||||||
|
useResetRecoilState,
|
||||||
|
useRecoilCallback,
|
||||||
|
useRecoilState,
|
||||||
|
useRecoilValue,
|
||||||
|
} from 'recoil';
|
||||||
import type { TConversation, TSubmission, TPreset, TModelsConfig } from 'librechat-data-provider';
|
import type { TConversation, TSubmission, TPreset, TModelsConfig } from 'librechat-data-provider';
|
||||||
import { buildDefaultConvo, getDefaultEndpoint } from '~/utils';
|
import { buildDefaultConvo, getDefaultEndpoint } from '~/utils';
|
||||||
import { useDeleteFilesMutation } from '~/data-provider';
|
import { useDeleteFilesMutation } from '~/data-provider';
|
||||||
|
|
@ -11,6 +17,7 @@ import store from '~/store';
|
||||||
const useNewConvo = (index = 0) => {
|
const useNewConvo = (index = 0) => {
|
||||||
const setStorage = useSetStorage();
|
const setStorage = useSetStorage();
|
||||||
const navigate = useOriginNavigate();
|
const navigate = useOriginNavigate();
|
||||||
|
const defaultPreset = useRecoilValue(store.defaultPreset);
|
||||||
const { setConversation } = store.useCreateConversationAtom(index);
|
const { setConversation } = store.useCreateConversationAtom(index);
|
||||||
const [files, setFiles] = useRecoilState(store.filesByIndex(index));
|
const [files, setFiles] = useRecoilState(store.filesByIndex(index));
|
||||||
const setSubmission = useSetRecoilState<TSubmission | null>(store.submissionByIndex(index));
|
const setSubmission = useSetRecoilState<TSubmission | null>(store.submissionByIndex(index));
|
||||||
|
|
@ -36,17 +43,29 @@ const useNewConvo = (index = 0) => {
|
||||||
) => {
|
) => {
|
||||||
const modelsConfig = modelsData ?? snapshot.getLoadable(store.modelsConfig).contents;
|
const modelsConfig = modelsData ?? snapshot.getLoadable(store.modelsConfig).contents;
|
||||||
const { endpoint = null } = conversation;
|
const { endpoint = null } = conversation;
|
||||||
|
const buildDefaultConversation = endpoint === null || buildDefault;
|
||||||
|
const activePreset =
|
||||||
|
// use default preset only when it's defined,
|
||||||
|
// preset is not provided,
|
||||||
|
// endpoint matches or is null (to allow endpoint change),
|
||||||
|
// and buildDefaultConversation is true
|
||||||
|
defaultPreset &&
|
||||||
|
!preset &&
|
||||||
|
(defaultPreset.endpoint === endpoint || !endpoint) &&
|
||||||
|
buildDefaultConversation
|
||||||
|
? defaultPreset
|
||||||
|
: preset;
|
||||||
|
|
||||||
if (endpoint === null || buildDefault) {
|
if (buildDefaultConversation) {
|
||||||
const defaultEndpoint = getDefaultEndpoint({
|
const defaultEndpoint = getDefaultEndpoint({
|
||||||
convoSetup: preset ?? conversation,
|
convoSetup: activePreset ?? conversation,
|
||||||
endpointsConfig,
|
endpointsConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
const models = modelsConfig?.[defaultEndpoint] ?? [];
|
const models = modelsConfig?.[defaultEndpoint] ?? [];
|
||||||
conversation = buildDefaultConvo({
|
conversation = buildDefaultConvo({
|
||||||
conversation,
|
conversation,
|
||||||
lastConversationSetup: preset as TConversation,
|
lastConversationSetup: activePreset as TConversation,
|
||||||
endpoint: defaultEndpoint,
|
endpoint: defaultEndpoint,
|
||||||
models,
|
models,
|
||||||
});
|
});
|
||||||
|
|
@ -61,7 +80,7 @@ const useNewConvo = (index = 0) => {
|
||||||
navigate('new');
|
navigate('new');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[endpointsConfig],
|
[endpointsConfig, defaultPreset],
|
||||||
);
|
);
|
||||||
|
|
||||||
const newConversation = useCallback(
|
const newConversation = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -178,9 +178,22 @@ export default {
|
||||||
'Set custom instructions to include in System Message. Default: none',
|
'Set custom instructions to include in System Message. Default: none',
|
||||||
com_endpoint_import: 'Import',
|
com_endpoint_import: 'Import',
|
||||||
com_endpoint_set_custom_name: 'Set a custom name, in case you can find this preset',
|
com_endpoint_set_custom_name: 'Set a custom name, in case you can find this preset',
|
||||||
|
com_endpoint_preset_delete_confirm: 'Are you sure you want to delete this preset?',
|
||||||
|
com_endpoint_preset_clear_all_confirm: 'Are you sure you want to delete all of your presets?',
|
||||||
|
com_endpoint_preset_import: 'Preset Imported!',
|
||||||
|
com_endpoint_preset_import_error: 'There was an error importing your preset. Please try again.',
|
||||||
|
com_endpoint_preset_save_error: 'There was an error saving your preset. Please try again.',
|
||||||
|
com_endpoint_preset_delete_error: 'There was an error deleting your preset. Please try again.',
|
||||||
|
com_endpoint_preset_default_removed: 'is no longer the default preset.',
|
||||||
|
com_endpoint_preset_default_item: 'Default:',
|
||||||
|
com_endpoint_preset_default_none: 'No default preset active.',
|
||||||
|
com_endpoint_preset_title: 'Preset',
|
||||||
|
com_endpoint_preset_saved: 'Saved!',
|
||||||
|
com_endpoint_preset_default: 'is now the default preset.',
|
||||||
com_endpoint_preset: 'preset',
|
com_endpoint_preset: 'preset',
|
||||||
com_endpoint_presets: 'presets',
|
com_endpoint_presets: 'presets',
|
||||||
com_endpoint_preset_selected: 'Preset Active!',
|
com_endpoint_preset_selected: 'Preset Active!',
|
||||||
|
com_endpoint_preset_selected_title: 'Active!',
|
||||||
com_endpoint_preset_name: 'Preset Name',
|
com_endpoint_preset_name: 'Preset Name',
|
||||||
com_endpoint_new_topic: 'New Topic',
|
com_endpoint_new_topic: 'New Topic',
|
||||||
com_endpoint: 'Endpoint',
|
com_endpoint: 'Endpoint',
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,7 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
import {
|
import { useGetModelsQuery, useGetSearchEnabledQuery } from 'librechat-data-provider';
|
||||||
useGetModelsQuery,
|
|
||||||
useGetPresetsQuery,
|
|
||||||
useGetSearchEnabledQuery,
|
|
||||||
} from 'librechat-data-provider';
|
|
||||||
import type { ContextType } from '~/common';
|
import type { ContextType } from '~/common';
|
||||||
import { Nav, MobileNav } from '~/components/Nav';
|
import { Nav, MobileNav } from '~/components/Nav';
|
||||||
import { useAuthContext, useServerStream, useConversation } from '~/hooks';
|
import { useAuthContext, useServerStream, useConversation } from '~/hooks';
|
||||||
|
|
@ -15,7 +11,7 @@ import store from '~/store';
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { newConversation } = useConversation();
|
const { newConversation } = useConversation();
|
||||||
const { user, isAuthenticated } = useAuthContext();
|
const { isAuthenticated } = useAuthContext();
|
||||||
const [navVisible, setNavVisible] = useState(() => {
|
const [navVisible, setNavVisible] = useState(() => {
|
||||||
const savedNavVisible = localStorage.getItem('navVisible');
|
const savedNavVisible = localStorage.getItem('navVisible');
|
||||||
return savedNavVisible !== null ? JSON.parse(savedNavVisible) : false;
|
return savedNavVisible !== null ? JSON.parse(savedNavVisible) : false;
|
||||||
|
|
@ -24,13 +20,11 @@ export default function Root() {
|
||||||
const submission = useRecoilValue(store.submission);
|
const submission = useRecoilValue(store.submission);
|
||||||
useServerStream(submission ?? null);
|
useServerStream(submission ?? null);
|
||||||
|
|
||||||
const setPresets = useSetRecoilState(store.presets);
|
|
||||||
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
|
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
|
||||||
const setModelsConfig = useSetRecoilState(store.modelsConfig);
|
const setModelsConfig = useSetRecoilState(store.modelsConfig);
|
||||||
|
|
||||||
const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated });
|
const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated });
|
||||||
const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated });
|
const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated });
|
||||||
const presetsQuery = useGetPresetsQuery({ enabled: !!user });
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('navVisible', JSON.stringify(navVisible));
|
localStorage.setItem('navVisible', JSON.stringify(navVisible));
|
||||||
|
|
@ -48,14 +42,6 @@ export default function Root() {
|
||||||
}
|
}
|
||||||
}, [modelsQuery.data, modelsQuery.isError]);
|
}, [modelsQuery.data, modelsQuery.isError]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (presetsQuery.data) {
|
|
||||||
setPresets(presetsQuery.data);
|
|
||||||
} else if (presetsQuery.isError) {
|
|
||||||
console.error('Failed to get presets', presetsQuery.error);
|
|
||||||
}
|
|
||||||
}, [presetsQuery.data, presetsQuery.isError]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchEnabledQuery.data) {
|
if (searchEnabledQuery.data) {
|
||||||
setIsSearchEnabled(searchEnabledQuery.data);
|
setIsSearchEnabled(searchEnabledQuery.data);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,19 @@ const preset = atom<TPreset | null>({
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultPreset = atom<TPreset | null>({
|
||||||
|
key: 'defaultPreset',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const presetModalVisible = atom<boolean>({
|
||||||
|
key: 'presetModalVisible',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
preset,
|
preset,
|
||||||
presets,
|
presets,
|
||||||
|
defaultPreset,
|
||||||
|
presetModalVisible,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { TPreset } from 'librechat-data-provider';
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
import { EModelEndpoint, alternateName } from 'librechat-data-provider';
|
import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
|
|
||||||
export const getPresetIcon = (preset: TPreset, Icon) => {
|
export const getPresetIcon = (preset: TPreset, Icon) => {
|
||||||
return Icon({
|
return Icon({
|
||||||
|
|
@ -13,43 +13,34 @@ export const getPresetIcon = (preset: TPreset, Icon) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPresetTitle = (preset: TPreset) => {
|
export const getPresetTitle = (preset: TPreset) => {
|
||||||
const { endpoint } = preset;
|
const {
|
||||||
let _title = `${alternateName[endpoint ?? '']}`;
|
endpoint,
|
||||||
const { chatGptLabel, modelLabel, model, jailbreak, toneStyle } = preset;
|
title: presetTitle,
|
||||||
|
model,
|
||||||
|
chatGptLabel,
|
||||||
|
modelLabel,
|
||||||
|
jailbreak,
|
||||||
|
toneStyle,
|
||||||
|
} = preset;
|
||||||
|
let title = '';
|
||||||
|
let modelInfo = model || '';
|
||||||
|
let label = '';
|
||||||
|
|
||||||
if (endpoint === EModelEndpoint.azureOpenAI || endpoint === EModelEndpoint.openAI) {
|
if (endpoint && [EModelEndpoint.azureOpenAI, EModelEndpoint.openAI].includes(endpoint)) {
|
||||||
if (chatGptLabel) {
|
label = chatGptLabel || '';
|
||||||
_title = chatGptLabel;
|
} else if (endpoint && [EModelEndpoint.google, EModelEndpoint.anthropic].includes(endpoint)) {
|
||||||
}
|
label = modelLabel || '';
|
||||||
if (model) {
|
|
||||||
_title += `: ${model}`;
|
|
||||||
}
|
|
||||||
} else if (endpoint === EModelEndpoint.google || endpoint === EModelEndpoint.anthropic) {
|
|
||||||
if (modelLabel) {
|
|
||||||
_title = modelLabel;
|
|
||||||
}
|
|
||||||
if (model) {
|
|
||||||
_title += `: ${model}`;
|
|
||||||
}
|
|
||||||
} else if (endpoint === EModelEndpoint.bingAI) {
|
} else if (endpoint === EModelEndpoint.bingAI) {
|
||||||
if (jailbreak) {
|
modelInfo = jailbreak ? 'Sydney' : modelInfo;
|
||||||
_title = 'Sydney';
|
label = toneStyle ? `: ${toneStyle}` : '';
|
||||||
}
|
}
|
||||||
if (toneStyle) {
|
|
||||||
_title += `: ${toneStyle}`;
|
if (label && presetTitle && label.toLowerCase().includes(presetTitle.toLowerCase())) {
|
||||||
|
title = label + ': ';
|
||||||
|
label = '';
|
||||||
|
} else if (presetTitle && presetTitle.trim() !== 'New Chat') {
|
||||||
|
title = presetTitle + ': ';
|
||||||
}
|
}
|
||||||
} else if (endpoint === EModelEndpoint.chatGPTBrowser) {
|
|
||||||
if (model) {
|
return `${title}${modelInfo}${label ? ` (${label})` : ''}`.trim();
|
||||||
_title += `: ${model}`;
|
|
||||||
}
|
|
||||||
} else if (endpoint === EModelEndpoint.gptPlugins) {
|
|
||||||
if (model) {
|
|
||||||
_title += `: ${model}`;
|
|
||||||
}
|
|
||||||
} else if (endpoint === null) {
|
|
||||||
null;
|
|
||||||
} else {
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
return _title;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
35
package-lock.json
generated
35
package-lock.json
generated
|
|
@ -681,6 +681,7 @@
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-flip-toolkit": "^7.1.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.43.9",
|
||||||
"react-lazy-load-image-component": "^1.6.0",
|
"react-lazy-load-image-component": "^1.6.0",
|
||||||
"react-markdown": "^8.0.6",
|
"react-markdown": "^8.0.6",
|
||||||
|
|
@ -12470,6 +12471,18 @@
|
||||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/flip-toolkit": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/flip-toolkit/-/flip-toolkit-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-tvids+uibr8gVFUp1xHMkNSIFvM4++Xr4jAJouUVsId2hv3YvhvC4Ht2FJzdxBZHhI4AeULPFAF6z9fhc20XWQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"rematrix": "0.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fn.name": {
|
"node_modules/fn.name": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
|
||||||
|
|
@ -20605,6 +20618,23 @@
|
||||||
"react": "^18.2.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-flip-toolkit": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-flip-toolkit/-/react-flip-toolkit-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-KJ2IecKpYOJWgtXY9myyJzzC96FJaE9/8pFSAKgIoG54tiUAZ64ksDpmB+QmMofqFTa06RK7xWb9Rfavf8qz4Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"flip-toolkit": "7.1.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.x",
|
||||||
|
"react-dom": ">= 16.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.46.1",
|
"version": "7.46.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.46.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.46.1.tgz",
|
||||||
|
|
@ -21153,6 +21183,11 @@
|
||||||
"unist-util-visit": "^4.0.0"
|
"unist-util-visit": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rematrix": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rematrix/-/rematrix-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-agFFS3RzrLXJl5LY5xg/xYyXvUuVAnkhgKO7RaO9J1Ssth6yvbO+PIiV67V59MB5NCdAK2flvGvNT4mdKVniFA=="
|
||||||
|
},
|
||||||
"node_modules/remove-accents": {
|
"node_modules/remove-accents": {
|
||||||
"version": "0.4.2",
|
"version": "0.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as f from './types/files';
|
import * as f from './types/files';
|
||||||
|
import * as p from './types/presets';
|
||||||
import * as a from './types/assistants';
|
import * as a from './types/assistants';
|
||||||
import * as t from './types';
|
import * as t from './types';
|
||||||
import * as s from './schemas';
|
import * as s from './schemas';
|
||||||
|
|
@ -73,15 +74,15 @@ export function getPresets(): Promise<s.TPreset[]> {
|
||||||
return request.get(endpoints.presets());
|
return request.get(endpoints.presets());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPreset(payload: s.TPreset): Promise<s.TPreset[]> {
|
export function createPreset(payload: s.TPreset): Promise<s.TPreset> {
|
||||||
return request.post(endpoints.presets(), payload);
|
return request.post(endpoints.presets(), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updatePreset(payload: s.TPreset): Promise<s.TPreset[]> {
|
export function updatePreset(payload: s.TPreset): Promise<s.TPreset> {
|
||||||
return request.post(endpoints.presets(), payload);
|
return request.post(endpoints.presets(), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deletePreset(arg: s.TPreset | object): Promise<s.TPreset[]> {
|
export function deletePreset(arg: s.TPreset | undefined): Promise<p.PresetDeleteResponse> {
|
||||||
return request.post(endpoints.deletePreset(), arg);
|
return request.post(endpoints.deletePreset(), arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './types/assistants';
|
export * from './types/assistants';
|
||||||
export * from './types/files';
|
export * from './types/files';
|
||||||
|
export * from './types/presets';
|
||||||
/*
|
/*
|
||||||
* react query
|
* react query
|
||||||
* TODO: move to client, or move schemas/types to their own package
|
* TODO: move to client, or move schemas/types to their own package
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,6 @@ export enum QueryKeys {
|
||||||
export enum MutationKeys {
|
export enum MutationKeys {
|
||||||
imageUpload = 'imageUpload',
|
imageUpload = 'imageUpload',
|
||||||
fileDelete = 'fileDelete',
|
fileDelete = 'fileDelete',
|
||||||
|
updatePreset = 'updatePreset',
|
||||||
|
deletePreset = 'deletePreset',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
import * as t from './types';
|
import * as t from './types';
|
||||||
import * as s from './schemas';
|
import * as s from './schemas';
|
||||||
|
import * as p from './types/presets';
|
||||||
import * as dataService from './data-service';
|
import * as dataService from './data-service';
|
||||||
import request from './request';
|
import request from './request';
|
||||||
import { QueryKeys } from './keys';
|
import { QueryKeys } from './keys';
|
||||||
|
|
@ -276,7 +277,7 @@ export const useGetModelsQuery = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreatePresetMutation = (): UseMutationResult<
|
export const useCreatePresetMutation = (): UseMutationResult<
|
||||||
s.TPreset[],
|
s.TPreset,
|
||||||
unknown,
|
unknown,
|
||||||
s.TPreset,
|
s.TPreset,
|
||||||
unknown
|
unknown
|
||||||
|
|
@ -290,7 +291,7 @@ export const useCreatePresetMutation = (): UseMutationResult<
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdatePresetMutation = (): UseMutationResult<
|
export const useUpdatePresetMutation = (): UseMutationResult<
|
||||||
s.TPreset[],
|
s.TPreset,
|
||||||
unknown,
|
unknown,
|
||||||
s.TPreset,
|
s.TPreset,
|
||||||
unknown
|
unknown
|
||||||
|
|
@ -315,13 +316,13 @@ export const useGetPresetsQuery = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDeletePresetMutation = (): UseMutationResult<
|
export const useDeletePresetMutation = (): UseMutationResult<
|
||||||
s.TPreset[],
|
p.PresetDeleteResponse,
|
||||||
unknown,
|
unknown,
|
||||||
s.TPreset | object,
|
s.TPreset | undefined,
|
||||||
unknown
|
unknown
|
||||||
> => {
|
> => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation((payload: s.TPreset | object) => dataService.deletePreset(payload), {
|
return useMutation((payload: s.TPreset | undefined) => dataService.deletePreset(payload), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries([QueryKeys.presets]);
|
queryClient.invalidateQueries([QueryKeys.presets]);
|
||||||
},
|
},
|
||||||
|
|
@ -369,6 +370,9 @@ export const useLoginUserMutation = (): UseMutationResult<
|
||||||
return useMutation((payload: t.TLoginUser) => dataService.login(payload), {
|
return useMutation((payload: t.TLoginUser) => dataService.login(payload), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries([QueryKeys.user]);
|
queryClient.invalidateQueries([QueryKeys.user]);
|
||||||
|
queryClient.invalidateQueries([QueryKeys.presets]);
|
||||||
|
queryClient.invalidateQueries([QueryKeys.conversation]);
|
||||||
|
queryClient.invalidateQueries([QueryKeys.allConversations]);
|
||||||
},
|
},
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
queryClient.invalidateQueries([QueryKeys.models]);
|
queryClient.invalidateQueries([QueryKeys.models]);
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,8 @@ export const tPresetSchema = tConversationSchema
|
||||||
conversationId: z.string().optional(),
|
conversationId: z.string().optional(),
|
||||||
presetId: z.string().nullable().optional(),
|
presetId: z.string().nullable().optional(),
|
||||||
title: z.string().nullable().optional(),
|
title: z.string().nullable().optional(),
|
||||||
|
defaultPreset: z.boolean().optional(),
|
||||||
|
order: z.number().optional(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
22
packages/data-provider/src/types/presets.ts
Normal file
22
packages/data-provider/src/types/presets.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { TPreset } from '../types';
|
||||||
|
|
||||||
|
export type PresetDeleteResponse = {
|
||||||
|
acknowledged: boolean;
|
||||||
|
deletedCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdatePresetOptions = {
|
||||||
|
onSuccess?: (data: TPreset, variables: TPreset, context?: unknown) => void;
|
||||||
|
onMutate?: (variables: TPreset) => void | Promise<unknown>;
|
||||||
|
onError?: (error: unknown, variables: TPreset, context?: unknown) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeletePresetOptions = {
|
||||||
|
onSuccess?: (
|
||||||
|
data: PresetDeleteResponse,
|
||||||
|
variables: TPreset | undefined,
|
||||||
|
context?: unknown,
|
||||||
|
) => void;
|
||||||
|
onMutate?: (variables: TPreset | undefined) => void | Promise<unknown>;
|
||||||
|
onError?: (error: unknown, variables: TPreset | undefined, context?: unknown) => void;
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue