refactor(types): use zod for better type safety, style(Messages): new scroll behavior, style(Buttons): match ChatGPT (#761)

* feat: add zod schemas for better type safety

* refactor(useSetOptions): remove 'as Type' in favor of zod schema

* fix: descendant console error, change <p> tag to <div> tag for content in PluginTooltip component

* style(MessagesView): instant/snappier scroll behavior matching official site

* fix(Messages): add null check for scrollableRef before accessing its properties in handleScroll and useEffect

* fix(messageSchema.js): change type of invocationId from string to number
fix(schemas.ts): make authenticated property in tPluginSchema optional
fix(schemas.ts): make isButton property in tPluginSchema optional
fix(schemas.ts): make messages property in tConversationSchema optional and change its type to array of strings
fix(schemas.ts): make systemMessage property in tConversationSchema nullable and optional
fix(schemas.ts): make modelLabel property in tConversationSchema nullable and optional
fix(schemas.ts): make chatGptLabel property in tConversationSchema nullable and optional
fix(schemas.ts): make promptPrefix property in tConversationSchema nullable and optional
fix(schemas.ts): make context property in tConversationSchema nullable and optional
fix(schemas.ts): make jailbreakConversationId property in tConversationSchema nullable and optional
fix(schemas.ts): make conversationSignature property in tConversationSchema nullable and optional
fix(schemas.ts): make clientId property

* refactor(types): replace main types with zod schemas and inferred types

* refactor(types/schemas): use schemas for better type safety of main types

* style(ModelSelect/Buttons): remove shadow and transition

* style(ModelSelect): button changes to closer match OpenAI

* style(ModelSelect): remove green rings which flicker

* style(scrollToBottom): add two separate scrolling functions

* fix(OptionsBar.tsx): handle onFocus and onBlur events to update opacityClass
fix(Messages/index.jsx): increase debounce time for scrollIntoView function
This commit is contained in:
Danny Avila 2023-08-05 12:10:36 -04:00 committed by GitHub
parent 173b8ce2da
commit 5828200197
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 329 additions and 317 deletions

View file

@ -25,7 +25,7 @@ const messageSchema = mongoose.Schema(
type: String, type: String,
}, },
invocationId: { invocationId: {
type: String, type: Number,
}, },
parentMessageId: { parentMessageId: {
type: String, type: String,

View file

@ -1,7 +1,7 @@
import exportFromJSON from 'export-from-json'; import exportFromJSON from 'export-from-json';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil'; import { useRecoilValue, useRecoilState } from 'recoil';
import { EditPresetProps, SetOption, TPreset } from 'librechat-data-provider'; import { EditPresetProps, SetOption, tPresetSchema } from 'librechat-data-provider';
import { Dialog, DialogButton } from '~/components/ui'; import { Dialog, DialogButton } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate'; import DialogTemplate from '~/components/ui/DialogTemplate';
import SaveAsPresetDialog from './SaveAsPresetDialog'; import SaveAsPresetDialog from './SaveAsPresetDialog';
@ -21,12 +21,11 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }: E
const setOption: SetOption = (param) => (newValue) => { const setOption: SetOption = (param) => (newValue) => {
const update = {}; const update = {};
update[param] = newValue; update[param] = newValue;
setPreset( setPreset((prevState) =>
(prevState) => tPresetSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TPreset),
); );
}; };

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useCreatePresetMutation, EditPresetProps, TPreset } from 'librechat-data-provider'; import { useCreatePresetMutation, EditPresetProps } from 'librechat-data-provider';
import { Dialog, Input, Label } from '~/components/ui/'; import { Dialog, Input, Label } from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate'; import DialogTemplate from '~/components/ui/DialogTemplate';
import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/'; import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/';
@ -20,7 +20,7 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: EditPresetProps) =>
title, title,
}, },
endpointsConfig, endpointsConfig,
}) as TPreset; });
createPresetMutation.mutate(_preset); createPresetMutation.mutate(_preset);
}; };

View file

@ -68,14 +68,14 @@ function Examples({ readonly, examples, setExample, addExample, removeExample }:
<div className="flex justify-center"> <div className="flex justify-center">
<Button <Button
type="button" type="button"
className="mr-2 mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0" className="mr-2 mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={removeExample} onClick={removeExample}
> >
<Minus className="w-[16px]" /> <Minus className="w-[16px]" />
</Button> </Button>
<Button <Button
type="button" type="button"
className="mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0" className="mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={addExample} onClick={addExample}
> >
<Plus className="w-[16px]" /> <Plus className="w-[16px]" />

View file

@ -12,7 +12,7 @@ export default function Anthropic({ conversation, setOption, models }: ModelSele
showLabel={false} showLabel={false}
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600', 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer',
)} )}
/> />
); );

View file

@ -34,15 +34,15 @@ export default function BingAI({ conversation, setOption, models }: ModelSelectP
showLabel={false} showLabel={false}
className={cn( className={cn(
cardStyle, cardStyle,
'z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600', 'z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-800 dark:hover:bg-gray-700 dark:data-[state=open]:bg-gray-600',
showBingToneSetting ? 'hidden' : '', showBingToneSetting ? 'hidden' : '',
)} )}
/> />
<Tabs <Tabs
value={toneStyle} value={toneStyle ?? 'creative'}
className={cn( className={cn(
cardStyle, cardStyle,
'z-50 flex h-[40px] flex-none items-center justify-center px-0 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md dark:hover:bg-gray-600', 'z-50 flex h-[40px] flex-none items-center justify-center px-0 hover:bg-slate-50 dark:hover:bg-gray-700',
)} )}
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())} onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
> >

View file

@ -20,7 +20,7 @@ export default function ChatGPT({ conversation, setOption, models }: ModelSelect
showLabel={false} showLabel={false}
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600', 'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer',
)} )}
/> />
); );

View file

@ -12,7 +12,7 @@ export default function Google({ conversation, setOption, models }: ModelSelectP
showLabel={false} showLabel={false}
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600', 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer',
)} )}
/> />
); );

View file

@ -12,7 +12,7 @@ export default function OpenAI({ conversation, setOption, models }: ModelSelectP
showLabel={false} showLabel={false}
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600', 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 hover:cursor-pointer',
)} )}
/> />
); );

View file

@ -46,7 +46,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
} }
const tools = [...user.plugins] const tools = [...user.plugins]
.map((el) => allPlugins.find((plugin) => plugin.pluginKey === el)) .map((el) => allPlugins.find((plugin: TPlugin) => plugin.pluginKey === el))
.filter((el): el is TPlugin => el !== undefined); .filter((el): el is TPlugin => el !== undefined);
/* Filter Last Selected Tools */ /* Filter Last Selected Tools */
@ -71,7 +71,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
type="button" type="button"
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 transition duration-700 ease-in-out hover:bg-white hover:shadow-md focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700', 'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
)} )}
onClick={() => setVisibility((prev) => !prev)} onClick={() => setVisibility((prev) => !prev)}
> >
@ -87,11 +87,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
setValue={setOption('model')} setValue={setOption('model')}
availableValues={models} availableValues={models}
showAbove={true} showAbove={true}
className={cn( className={cn(cardStyle, 'min-w-60 z-40 flex w-64 sm:w-48', visible ? '' : 'hidden')}
cardStyle,
'min-w-60 z-40 flex w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
visible ? '' : 'hidden',
)}
/> />
<MultiSelectDropDown <MultiSelectDropDown
value={conversation.tools || []} value={conversation.tools || []}
@ -100,11 +96,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
availableValues={availableTools} availableValues={availableTools}
optionValueKey="pluginKey" optionValueKey="pluginKey"
showAbove={true} showAbove={true}
className={cn( className={cn(cardStyle, 'min-w-60 z-50 w-64 sm:w-48', visible ? '' : 'hidden')}
cardStyle,
'min-w-60 z-50 w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
visible ? '' : 'hidden',
)}
/> />
</> </>
); );

View file

@ -1,7 +1,7 @@
import { Settings2 } from 'lucide-react'; import { Settings2 } from 'lucide-react';
import { useState, useEffect, useMemo } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
import { TPreset } from 'librechat-data-provider'; import { tPresetSchema } from 'librechat-data-provider';
import { PluginStoreDialog } from '~/components'; import { PluginStoreDialog } from '~/components';
import { import {
EndpointSettings, EndpointSettings,
@ -98,6 +98,21 @@ export default function OptionsBar() {
} }
setOpacityClass('show'); setOpacityClass('show');
}} }}
onFocus={() => {
if (showPopover) {
return;
}
setOpacityClass('full-opacity');
}}
onBlur={() => {
if (showPopover) {
return;
}
if (!messagesTree || messagesTree.length === 0) {
return;
}
setOpacityClass('show');
}}
> >
<ModelSelect conversation={conversation} setOption={setOption} /> <ModelSelect conversation={conversation} setOption={setOption} />
{!noSettings[endpoint] && ( {!noSettings[endpoint] && (
@ -105,7 +120,7 @@ export default function OptionsBar() {
type="button" type="button"
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-3 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600', 'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-3 focus:ring-0 focus:ring-offset-0',
)} )}
onClick={triggerAdvancedMode} onClick={triggerAdvancedMode}
> >
@ -126,7 +141,7 @@ export default function OptionsBar() {
<SaveAsPresetDialog <SaveAsPresetDialog
open={saveAsDialogShow} open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow} onOpenChange={setSaveAsDialogShow}
preset={{ ...conversation } as TPreset} preset={tPresetSchema.parse({ ...conversation })}
/> />
<PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} /> <PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} />
</span> </span>

View file

@ -28,6 +28,9 @@ export default function Messages({ isSearchView = false }) {
const { screenshotTargetRef } = useScreenshot(); const { screenshotTargetRef } = useScreenshot();
const handleScroll = () => { const handleScroll = () => {
if (!scrollableRef.current) {
return;
}
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current; const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
const diff = Math.abs(scrollHeight - scrollTop); const diff = Math.abs(scrollHeight - scrollTop);
const percent = Math.abs(clientHeight - diff) / clientHeight; const percent = Math.abs(clientHeight - diff) / clientHeight;
@ -40,6 +43,9 @@ export default function Messages({ isSearchView = false }) {
useEffect(() => { useEffect(() => {
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
if (!scrollableRef.current) {
return;
}
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current; const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
const diff = Math.abs(scrollHeight - scrollTop); const diff = Math.abs(scrollHeight - scrollTop);
const percent = Math.abs(clientHeight - diff) / clientHeight; const percent = Math.abs(clientHeight - diff) / clientHeight;
@ -58,6 +64,19 @@ export default function Messages({ isSearchView = false }) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const scrollToBottom = useCallback( const scrollToBottom = useCallback(
throttle(
() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'instant' });
setShowScrollButton(false);
},
450,
{ leading: true },
),
[messagesEndRef],
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const scrollToBottomSmooth = useCallback(
throttle( throttle(
() => { () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
@ -77,7 +96,7 @@ export default function Messages({ isSearchView = false }) {
const scrollHandler = (e) => { const scrollHandler = (e) => {
e.preventDefault(); e.preventDefault();
scrollToBottom(); scrollToBottomSmooth();
}; };
return ( return (

View file

@ -11,7 +11,7 @@ import {
useUpdateUserPluginsMutation, useUpdateUserPluginsMutation,
TPlugin, TPlugin,
TPluginAction, TPluginAction,
TConversation, tConversationSchema,
TError, TError,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { useAuthContext } from '~/hooks/AuthContext'; import { useAuthContext } from '~/hooks/AuthContext';
@ -69,12 +69,11 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
return t.pluginKey !== plugin; return t.pluginKey !== plugin;
}); });
localStorage.setItem('lastSelectedTools', JSON.stringify(tools)); localStorage.setItem('lastSelectedTools', JSON.stringify(tools));
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, tools,
tools, }),
} as TConversation),
); );
}, },
}, },

View file

@ -11,9 +11,9 @@ function PluginTooltip({ content, position }: TPluginTooltipProps) {
<HoverCardPortal> <HoverCardPortal>
<HoverCardContent side={position} className="w-80 "> <HoverCardContent side={position} className="w-80 ">
<div className="space-y-2"> <div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300"> <div className="text-sm text-gray-600 dark:text-gray-300">
<div dangerouslySetInnerHTML={{ __html: content }} /> <div dangerouslySetInnerHTML={{ __html: content }} />
</p> </div>
</div> </div>
</HoverCardContent> </HoverCardContent>
</HoverCardPortal> </HoverCardPortal>

View file

@ -12,7 +12,7 @@ function Dropdown({ value, onChange, options, className, containerClassName }) {
<Listbox value={value} onChange={onChange}> <Listbox value={value} onChange={onChange}>
<Listbox.Button <Listbox.Button
className={cn( className={cn(
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm', 'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-1 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
className || '', className || '',
)} )}
> >

View file

@ -43,7 +43,7 @@ function MultiSelectDropDown({
<> <>
<Listbox.Button <Listbox.Button
className={cn( className={cn(
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm', 'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
className ?? '', className ?? '',
)} )}
id={excludeIds[0]} id={excludeIds[0]}

View file

@ -41,7 +41,7 @@ function SelectDropDown({
<> <>
<Listbox.Button <Listbox.Button
className={cn( className={cn(
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm', 'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
className ?? '', className ?? '',
)} )}
> >

View file

@ -10,7 +10,7 @@ const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef< const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>, React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => ( >(({ className = '', ...props }, ref) => (
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} ref={ref}
className={cn( className={cn(
@ -25,7 +25,7 @@ TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef< const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>, React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => ( >(({ className = '', ...props }, ref) => (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
className={cn( className={cn(
'inline-flex min-w-[100px] items-center justify-center rounded-[0.185rem] px-3 py-1.5 text-sm font-medium text-gray-700 transition-all disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm dark:text-gray-200 dark:data-[state=active]:bg-gray-700 dark:data-[state=active]:text-gray-100', 'inline-flex min-w-[100px] items-center justify-center rounded-[0.185rem] px-3 py-1.5 text-sm font-medium text-gray-700 transition-all disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm dark:text-gray-200 dark:data-[state=active]:bg-gray-700 dark:data-[state=active]:text-gray-100',
@ -40,7 +40,7 @@ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef< const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>, React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => ( >(({ className = '', ...props }, ref) => (
<TabsPrimitive.Content <TabsPrimitive.Content
className={cn('mt-2 rounded-md border border-gray-200 p-6 dark:border-gray-700', className)} className={cn('mt-2 rounded-md border border-gray-200 p-6 dark:border-gray-700', className)}
{...props} {...props}

View file

@ -13,15 +13,14 @@ const usePresetOptions: UsePresetOptions = (_preset) => {
const setOption: SetOption = (param) => (newValue) => { const setOption: SetOption = (param) => (newValue) => {
const update = {}; const update = {};
update[param] = newValue; update[param] = newValue;
setPreset( setPreset((prevState) =>
(prevState) => cleanupPreset({
cleanupPreset({ preset: {
preset: { ...prevState,
...prevState, ...update,
...update, },
}, endpointsConfig,
endpointsConfig, }),
}) as TPreset,
); );
}; };
@ -32,15 +31,14 @@ const usePresetOptions: UsePresetOptions = (_preset) => {
currentExample[type] = { content: newValue }; currentExample[type] = { content: newValue };
current[i] = currentExample; current[i] = currentExample;
update['examples'] = current; update['examples'] = current;
setPreset( setPreset((prevState) =>
(prevState) => cleanupPreset({
cleanupPreset({ preset: {
preset: { ...prevState,
...prevState, ...update,
...update, },
}, endpointsConfig,
endpointsConfig, }),
}) as TPreset,
); );
}; };
@ -49,15 +47,14 @@ const usePresetOptions: UsePresetOptions = (_preset) => {
const current = preset?.examples?.slice() || []; const current = preset?.examples?.slice() || [];
current.push({ input: { content: '' }, output: { content: '' } }); current.push({ input: { content: '' }, output: { content: '' } });
update['examples'] = current; update['examples'] = current;
setPreset( setPreset((prevState) =>
(prevState) => cleanupPreset({
cleanupPreset({ preset: {
preset: { ...prevState,
...prevState, ...update,
...update, },
}, endpointsConfig,
endpointsConfig, }),
}) as TPreset,
); );
}; };
@ -66,29 +63,27 @@ const usePresetOptions: UsePresetOptions = (_preset) => {
const current = preset?.examples?.slice() || []; const current = preset?.examples?.slice() || [];
if (current.length <= 1) { if (current.length <= 1) {
update['examples'] = [{ input: { content: '' }, output: { content: '' } }]; update['examples'] = [{ input: { content: '' }, output: { content: '' } }];
setPreset( setPreset((prevState) =>
(prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}) as TPreset,
);
return;
}
current.pop();
update['examples'] = current;
setPreset(
(prevState) =>
cleanupPreset({ cleanupPreset({
preset: { preset: {
...prevState, ...prevState,
...update, ...update,
}, },
endpointsConfig, endpointsConfig,
}) as TPreset, }),
);
return;
}
current.pop();
update['examples'] = current;
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}),
); );
}; };
@ -96,15 +91,14 @@ const usePresetOptions: UsePresetOptions = (_preset) => {
const editablePreset = JSON.parse(JSON.stringify(_preset)); const editablePreset = JSON.parse(JSON.stringify(_preset));
const { agentOptions } = editablePreset; const { agentOptions } = editablePreset;
agentOptions[param] = newValue; agentOptions[param] = newValue;
setPreset( setPreset((prevState) =>
(prevState) => cleanupPreset({
cleanupPreset({ preset: {
preset: { ...prevState,
...prevState, agentOptions,
agentOptions, },
}, endpointsConfig,
endpointsConfig, }),
}) as TPreset,
); );
}; };

View file

@ -4,6 +4,7 @@ import {
SetOption, SetOption,
SetExample, SetExample,
TPlugin, TPlugin,
tConversationSchema,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import usePresetOptions from './usePresetOptions'; import usePresetOptions from './usePresetOptions';
@ -23,12 +24,11 @@ const useSetOptions: UseSetOptions = (preset = false) => {
const setOption: SetOption = (param) => (newValue) => { const setOption: SetOption = (param) => (newValue) => {
const update = {}; const update = {};
update[param] = newValue; update[param] = newValue;
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TConversation),
); );
}; };
@ -39,12 +39,11 @@ const useSetOptions: UseSetOptions = (preset = false) => {
currentExample[type] = { content: newValue }; currentExample[type] = { content: newValue };
current[i] = currentExample; current[i] = currentExample;
update['examples'] = current; update['examples'] = current;
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TConversation),
); );
}; };
@ -53,12 +52,11 @@ const useSetOptions: UseSetOptions = (preset = false) => {
const current = conversation?.examples?.slice() || []; const current = conversation?.examples?.slice() || [];
current.push({ input: { content: '' }, output: { content: '' } }); current.push({ input: { content: '' }, output: { content: '' } });
update['examples'] = current; update['examples'] = current;
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TConversation),
); );
}; };
@ -67,23 +65,21 @@ const useSetOptions: UseSetOptions = (preset = false) => {
const current = conversation?.examples?.slice() || []; const current = conversation?.examples?.slice() || [];
if (current.length <= 1) { if (current.length <= 1) {
update['examples'] = [{ input: { content: '' }, output: { content: '' } }]; update['examples'] = [{ input: { content: '' }, output: { content: '' } }];
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TConversation),
); );
return; return;
} }
current.pop(); current.pop();
update['examples'] = current; update['examples'] = current;
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TConversation),
); );
}; };
@ -101,12 +97,11 @@ const useSetOptions: UseSetOptions = (preset = false) => {
const convo = JSON.parse(editableConvo); const convo = JSON.parse(editableConvo);
const { agentOptions } = convo; const { agentOptions } = convo;
agentOptions[param] = newValue; agentOptions[param] = newValue;
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, agentOptions,
agentOptions, }),
} as TConversation),
); );
}; };
@ -128,12 +123,11 @@ const useSetOptions: UseSetOptions = (preset = false) => {
} }
localStorage.setItem('lastSelectedTools', JSON.stringify(update['tools'])); localStorage.setItem('lastSelectedTools', JSON.stringify(update['tools']));
setConversation( setConversation((prevState) =>
(prevState) => tConversationSchema.parse({
({ ...prevState,
...prevState, ...update,
...update, }),
} as TConversation),
); );
}; };

View file

@ -1,9 +1,9 @@
import { CleanupPreset } from 'librechat-data-provider'; import { CleanupPreset, TPreset } from 'librechat-data-provider';
const cleanupPreset = ({ preset: _preset, endpointsConfig = {} }: CleanupPreset) => { const cleanupPreset = ({ preset: _preset, endpointsConfig = {} }: CleanupPreset): TPreset => {
const { endpoint } = _preset; const { endpoint } = _preset;
let preset = {}; let preset = {} as TPreset;
let models = []; let models = [];
if (endpoint) { if (endpoint) {
models = endpointsConfig[endpoint]?.availableModels || []; models = endpointsConfig[endpoint]?.availableModels || [];

View file

@ -55,7 +55,7 @@ export const removeFocusOutlines =
'focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'; 'focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0';
export const cardStyle = export const cardStyle =
'transition-colors rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white'; 'transition-colors rounded-md min-w-[75px] border font-normal bg-white hover:bg-slate-50 dark:border-gray-600 dark:hover:bg-gray-700 dark:bg-gray-800 text-black dark:text-gray-600 focus:outline-none data-[state=open]:bg-slate-50 dark:data-[state=open]:bg-gray-700';
export const defaultTextProps = export 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';

View file

@ -15,6 +15,10 @@ async function authenticate(config: FullConfig, user: User) {
const browser = await chromium.launch(); const browser = await chromium.launch();
const page = await browser.newPage(); const page = await browser.newPage();
console.log('🤖: 🗝 authenticating user:', user.username); console.log('🤖: 🗝 authenticating user:', user.username);
if (!baseURL) {
throw new Error('🤖: baseURL is not defined');
}
await page.goto(baseURL); await page.goto(baseURL);
await login(page, user); await login(page, user);
await page.locator('h1:has-text("LibreChat")').waitFor(); await page.locator('h1:has-text("LibreChat")').waitFor();

6
package-lock.json generated
View file

@ -26948,7 +26948,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@tanstack/react-query": "^4.28.0", "@tanstack/react-query": "^4.28.0",
"axios": "^1.3.4" "axios": "^1.3.4",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.21.5", "@babel/preset-env": "^7.21.5",
@ -39519,7 +39520,8 @@
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"rollup": "^3.26.0", "rollup": "^3.26.0",
"rollup-plugin-typescript2": "^0.35.0", "rollup-plugin-typescript2": "^0.35.0",
"typescript": "^5.0.4" "typescript": "^5.0.4",
"zod": "*"
}, },
"dependencies": { "dependencies": {
"brace-expansion": { "brace-expansion": {

View file

@ -1,6 +1,6 @@
{ {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.1.3", "version": "0.1.4",
"description": "data services for librechat apps", "description": "data services for librechat apps",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",
@ -25,7 +25,8 @@
"homepage": "https://github.com/danny-avila/LibreChat#readme", "homepage": "https://github.com/danny-avila/LibreChat#readme",
"dependencies": { "dependencies": {
"@tanstack/react-query": "^4.28.0", "@tanstack/react-query": "^4.28.0",
"axios": "^1.3.4" "axios": "^1.3.4",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.21.5", "@babel/preset-env": "^7.21.5",

View file

@ -1,8 +1,9 @@
import type { TConversation, TSubmission, EModelEndpoint } from './types'; import { tConversationSchema } from './schemas';
import type { TSubmission, EModelEndpoint } from './types';
export default function createPayload(submission: TSubmission) { export default function createPayload(submission: TSubmission) {
const { conversation, message, endpointOption } = submission; const { conversation, message, endpointOption } = submission;
const { conversationId } = conversation as TConversation; const { conversationId } = tConversationSchema.parse(conversation);
const { endpoint } = endpointOption as { endpoint: EModelEndpoint }; const { endpoint } = endpointOption as { endpoint: EModelEndpoint };
const endpointUrlMap = { const endpointUrlMap = {

View file

@ -1,4 +1,5 @@
import * as t from './types'; import * as t from './types';
import * as s from './schemas';
import request from './request'; import request from './request';
import * as endpoints from './api-endpoints'; import * as endpoints from './api-endpoints';
@ -23,11 +24,11 @@ export function clearAllConversations(): Promise<unknown> {
return request.post(endpoints.deleteConversation(), { arg: {} }); return request.post(endpoints.deleteConversation(), { arg: {} });
} }
export function getMessagesByConvoId(id: string): Promise<t.TMessage[]> { export function getMessagesByConvoId(id: string): Promise<s.TMessage[]> {
return request.get(endpoints.messages(id)); return request.get(endpoints.messages(id));
} }
export function getConversationById(id: string): Promise<t.TConversation> { export function getConversationById(id: string): Promise<s.TConversation> {
return request.get(endpoints.conversationById(id)); return request.get(endpoints.conversationById(id));
} }
@ -37,19 +38,19 @@ export function updateConversation(
return request.post(endpoints.updateConversation(), { arg: payload }); return request.post(endpoints.updateConversation(), { arg: payload });
} }
export function getPresets(): Promise<t.TPreset[]> { export function getPresets(): Promise<s.TPreset[]> {
return request.get(endpoints.presets()); return request.get(endpoints.presets());
} }
export function createPreset(payload: t.TPreset): Promise<t.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: t.TPreset): Promise<t.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: t.TPreset | object): Promise<t.TPreset[]> { export function deletePreset(arg: s.TPreset | object): Promise<s.TPreset[]> {
return request.post(endpoints.deletePreset(), arg); return request.post(endpoints.deletePreset(), arg);
} }
@ -106,7 +107,7 @@ export const resetPassword = (payload: t.TResetPassword) => {
return request.post(endpoints.resetPassword(), payload); return request.post(endpoints.resetPassword(), payload);
}; };
export const getAvailablePlugins = (): Promise<t.TPlugin[]> => { export const getAvailablePlugins = (): Promise<s.TPlugin[]> => {
return request.get(endpoints.plugins()); return request.get(endpoints.plugins());
}; };

View file

@ -7,6 +7,7 @@ import {
QueryObserverResult, QueryObserverResult,
} 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 dataService from './data-service'; import * as dataService from './data-service';
export enum QueryKeys { export enum QueryKeys {
@ -47,9 +48,9 @@ export const useGetUserQuery = (
export const useGetMessagesByConvoId = ( export const useGetMessagesByConvoId = (
id: string, id: string,
config?: UseQueryOptions<t.TMessage[]>, config?: UseQueryOptions<s.TMessage[]>,
): QueryObserverResult<t.TMessage[]> => { ): QueryObserverResult<s.TMessage[]> => {
return useQuery<t.TMessage[]>( return useQuery<s.TMessage[]>(
[QueryKeys.messages, id], [QueryKeys.messages, id],
() => dataService.getMessagesByConvoId(id), () => dataService.getMessagesByConvoId(id),
{ {
@ -63,9 +64,9 @@ export const useGetMessagesByConvoId = (
export const useGetConversationByIdQuery = ( export const useGetConversationByIdQuery = (
id: string, id: string,
config?: UseQueryOptions<t.TConversation>, config?: UseQueryOptions<s.TConversation>,
): QueryObserverResult<t.TConversation> => { ): QueryObserverResult<s.TConversation> => {
return useQuery<t.TConversation>( return useQuery<s.TConversation>(
[QueryKeys.conversation, id], [QueryKeys.conversation, id],
() => dataService.getConversationById(id), () => dataService.getConversationById(id),
{ {
@ -79,10 +80,10 @@ export const useGetConversationByIdQuery = (
//This isn't ideal because its just a query and we're using mutation, but it was the only way //This isn't ideal because its just a query and we're using mutation, but it was the only way
//to make it work with how the Chat component is structured //to make it work with how the Chat component is structured
export const useGetConversationByIdMutation = (id: string): UseMutationResult<t.TConversation> => { export const useGetConversationByIdMutation = (id: string): UseMutationResult<s.TConversation> => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation(() => dataService.getConversationById(id), { return useMutation(() => dataService.getConversationById(id), {
// onSuccess: (res: t.TConversation) => { // onSuccess: (res: s.TConversation) => {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries([QueryKeys.conversation, id]); queryClient.invalidateQueries([QueryKeys.conversation, id]);
}, },
@ -174,13 +175,13 @@ export const useGetEndpointsQuery = (): QueryObserverResult<t.TEndpointsConfig>
}; };
export const useCreatePresetMutation = (): UseMutationResult< export const useCreatePresetMutation = (): UseMutationResult<
t.TPreset[], s.TPreset[],
unknown, unknown,
t.TPreset, s.TPreset,
unknown unknown
> => { > => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation((payload: t.TPreset) => dataService.createPreset(payload), { return useMutation((payload: s.TPreset) => dataService.createPreset(payload), {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries([QueryKeys.presets]); queryClient.invalidateQueries([QueryKeys.presets]);
}, },
@ -188,13 +189,13 @@ export const useCreatePresetMutation = (): UseMutationResult<
}; };
export const useUpdatePresetMutation = (): UseMutationResult< export const useUpdatePresetMutation = (): UseMutationResult<
t.TPreset[], s.TPreset[],
unknown, unknown,
t.TPreset, s.TPreset,
unknown unknown
> => { > => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation((payload: t.TPreset) => dataService.updatePreset(payload), { return useMutation((payload: s.TPreset) => dataService.updatePreset(payload), {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries([QueryKeys.presets]); queryClient.invalidateQueries([QueryKeys.presets]);
}, },
@ -202,9 +203,9 @@ export const useUpdatePresetMutation = (): UseMutationResult<
}; };
export const useGetPresetsQuery = ( export const useGetPresetsQuery = (
config?: UseQueryOptions<t.TPreset[]>, config?: UseQueryOptions<s.TPreset[]>,
): QueryObserverResult<t.TPreset[], unknown> => { ): QueryObserverResult<s.TPreset[], unknown> => {
return useQuery<t.TPreset[]>([QueryKeys.presets], () => dataService.getPresets(), { return useQuery<s.TPreset[]>([QueryKeys.presets], () => dataService.getPresets(), {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnReconnect: false, refetchOnReconnect: false,
refetchOnMount: false, refetchOnMount: false,
@ -213,13 +214,13 @@ export const useGetPresetsQuery = (
}; };
export const useDeletePresetMutation = (): UseMutationResult< export const useDeletePresetMutation = (): UseMutationResult<
t.TPreset[], s.TPreset[],
unknown, unknown,
t.TPreset | object, s.TPreset | object,
unknown unknown
> => { > => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation((payload: t.TPreset | object) => dataService.deletePreset(payload), { return useMutation((payload: s.TPreset | object) => dataService.deletePreset(payload), {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries([QueryKeys.presets]); queryClient.invalidateQueries([QueryKeys.presets]);
}, },
@ -323,8 +324,8 @@ export const useResetPasswordMutation = (): UseMutationResult<
return useMutation((payload: t.TResetPassword) => dataService.resetPassword(payload)); return useMutation((payload: t.TResetPassword) => dataService.resetPassword(payload));
}; };
export const useAvailablePluginsQuery = (): QueryObserverResult<t.TPlugin[]> => { export const useAvailablePluginsQuery = (): QueryObserverResult<s.TPlugin[]> => {
return useQuery<t.TPlugin[]>( return useQuery<s.TPlugin[]>(
[QueryKeys.availablePlugins], [QueryKeys.availablePlugins],
() => dataService.getAvailablePlugins(), () => dataService.getAvailablePlugins(),
{ {

View file

@ -0,0 +1,121 @@
import { z } from 'zod';
export enum EModelEndpoint {
azureOpenAI = 'azureOpenAI',
openAI = 'openAI',
bingAI = 'bingAI',
chatGPT = 'chatGPT',
chatGPTBrowser = 'chatGPTBrowser',
google = 'google',
gptPlugins = 'gptPlugins',
anthropic = 'anthropic',
}
export const eModelEndpointSchema = z.nativeEnum(EModelEndpoint);
export const tMessageSchema = z.object({
messageId: z.string(),
conversationId: z.string(),
clientId: z.string(),
parentMessageId: z.string(),
sender: z.string(),
text: z.string(),
isCreatedByUser: z.boolean(),
error: z.boolean(),
createdAt: z.string(),
updatedAt: z.string(),
});
export type TMessage = z.infer<typeof tMessageSchema>;
export const tPluginAuthConfigSchema = z.object({
authField: z.string(),
label: z.string(),
description: z.string(),
});
export type TPluginAuthConfig = z.infer<typeof tPluginAuthConfigSchema>;
export const tPluginSchema = z.object({
name: z.string(),
pluginKey: z.string(),
description: z.string(),
icon: z.string(),
authConfig: z.array(tPluginAuthConfigSchema),
authenticated: z.boolean().optional(),
isButton: z.boolean().optional(),
});
export type TPlugin = z.infer<typeof tPluginSchema>;
export const tExampleSchema = z.object({
input: z.object({
content: z.string(),
}),
output: z.object({
content: z.string(),
}),
});
export type TExample = z.infer<typeof tExampleSchema>;
export const tAgentOptionsSchema = z.object({
agent: z.string(),
skipCompletion: z.boolean(),
model: z.string(),
temperature: z.number(),
});
export const tConversationSchema = z.object({
conversationId: z.string().nullable(),
title: z.string(),
user: z.string().optional(),
endpoint: eModelEndpointSchema.nullable(),
suggestions: z.array(z.string()).optional(),
messages: z.array(z.string()).optional(),
tools: z.array(tPluginSchema).optional(),
createdAt: z.string(),
updatedAt: z.string(),
systemMessage: z.string().nullable().optional(),
modelLabel: z.string().nullable().optional(),
examples: z.array(tExampleSchema).optional(),
chatGptLabel: z.string().nullable().optional(),
userLabel: z.string().optional(),
model: z.string().optional(),
promptPrefix: z.string().nullable().optional(),
temperature: z.number().optional(),
topP: z.number().optional(),
topK: z.number().optional(),
context: z.string().nullable().optional(),
top_p: z.number().optional(),
frequency_penalty: z.number().optional(),
presence_penalty: z.number().optional(),
jailbreak: z.boolean().optional(),
jailbreakConversationId: z.string().nullable().optional(),
conversationSignature: z.string().nullable().optional(),
parentMessageId: z.string().optional(),
clientId: z.string().nullable().optional(),
invocationId: z.number().nullable().optional(),
toneStyle: z.string().nullable().optional(),
maxOutputTokens: z.number().optional(),
agentOptions: tAgentOptionsSchema.nullable().optional(),
});
export type TConversation = z.infer<typeof tConversationSchema>;
export const tPresetSchema = tConversationSchema
.omit({
conversationId: true,
createdAt: true,
updatedAt: true,
title: true,
})
.merge(
z.object({
conversationId: z.string().optional(),
presetId: z.string().nullable().optional(),
title: z.string().nullable().optional(),
}),
);
export type TPreset = z.infer<typeof tPresetSchema>;

View file

@ -1,42 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { TExample, TMessage, EModelEndpoint, TPlugin, TConversation, TPreset } from './schemas';
export type TMessage = { export * from './schemas';
messageId: string;
conversationId: string;
clientId: string;
parentMessageId: string;
sender: string;
text: string;
isCreatedByUser: boolean;
error: boolean;
createdAt: string;
updatedAt: string;
};
export type TMessages = TMessage[]; export type TMessages = TMessage[];
export type TMessagesAtom = TMessages | null; export type TMessagesAtom = TMessages | null;
export type TExample = {
input: {
content: string;
};
output: {
content: string;
};
};
export enum EModelEndpoint {
azureOpenAI = 'azureOpenAI',
openAI = 'openAI',
bingAI = 'bingAI',
chatGPT = 'chatGPT',
chatGPTBrowser = 'chatGPTBrowser',
google = 'google',
gptPlugins = 'gptPlugins',
anthropic = 'anthropic',
}
export type TSubmission = { export type TSubmission = {
clientId?: string; clientId?: string;
context?: string; context?: string;
@ -73,22 +43,6 @@ export type TEndpointOption = {
temperature?: number; temperature?: number;
}; };
export type TPluginAuthConfig = {
authField: string;
label: string;
description: string;
};
export type TPlugin = {
name: string;
pluginKey: string;
description: string;
icon: string;
authConfig: TPluginAuthConfig[];
authenticated: boolean;
isButton?: boolean;
};
export type TPluginAction = { export type TPluginAction = {
pluginKey: string; pluginKey: string;
action: 'install' | 'uninstall'; action: 'install' | 'uninstall';
@ -105,91 +59,6 @@ export type TUpdateUserPlugins = {
auth?: unknown; auth?: unknown;
}; };
export type TAgentOptions = {
agent: string;
skipCompletion: boolean;
model: string;
temperature: number;
};
export type TConversation = {
conversationId: string | null;
title: string;
user?: string;
endpoint: EModelEndpoint | null;
suggestions?: string[];
messages?: TMessage[];
tools?: TPlugin[];
createdAt: string;
updatedAt: string;
// google only
systemMessage?: string;
modelLabel?: string;
examples?: TExample[];
// for azureOpenAI, openAI only
chatGptLabel?: string;
userLabel?: string;
model?: string;
promptPrefix?: string;
temperature?: number;
topP?: number;
topK?: number;
// bing and google
context?: string;
top_p?: number;
frequency_penalty?: number;
presence_penalty?: number;
// for bingAI only
jailbreak?: boolean;
jailbreakConversationId?: string;
conversationSignature?: string;
parentMessageId?: string;
clientId?: string;
invocationId?: string;
toneStyle?: string;
maxOutputTokens?: number;
// plugins only
agentOptions?: TAgentOptions;
};
export type TPreset = {
title: string;
conversationId?: string;
endpoint: EModelEndpoint | null;
conversationSignature?: string;
createdAt?: string;
updatedAt?: string;
presetId?: string;
tools?: TPlugin[];
user?: string;
modelLabel?: string;
maxOutputTokens?: number;
topP?: number;
topK?: number;
context?: string;
systemMessage?: string;
// for azureOpenAI, openAI only
chatGptLabel?: string;
frequence_penalty?: number;
model?: string;
presence_penalty?: number;
frequency_penalty?: number;
promptPrefix?: string;
temperature?: number;
top_p?: number;
//for BingAI
clientId?: string;
invocationId?: number;
jailbreak?: boolean;
jailbreakPresetId?: string;
presetSignature?: string;
toneStyle?: string;
// plugins only
agentOptions?: TAgentOptions;
// google only
examples?: TExample[];
};
export type TOptionSettings = { export type TOptionSettings = {
showExamples?: boolean; showExamples?: boolean;
isCodeChat?: boolean; isCodeChat?: boolean;