refactor: Settings/Presets UI Restructure, convert many files to TS (#740)

* progress on settings refactor

* fix(helpers.js): replace fs.rmdirSync with fs.rm to delete node_modules directory recursively
fix(packages.js): delete package-lock.json if it exists before running the script

* feat(CrossIcon.tsx): add CrossIcon component

* wip: refactor Options for modularity into higher order components, OptionsBar > ModelSelect/Settings

* refactor: import more from utils/index, including cardStyle used by model select/settings

* refactor(AnthropicOptions): refactor to new format, OpenAI: reduce format to name of endpoint

* refactor(AnthropicSettings): refactor to new format, match defaults to API docs

* fix: google and anthropic defaults

* refactor(conversation/submission atoms): add typing, remove unused code

* chore(types.ts): add missing type definitions for TMessages, TMessagesAtom, TConversationAtom, and ModelSelectProps
feat(types.ts): make endpoint property nullable in TSubmission, TEndpointOption, TConversation, and TPreset types

* refactor(ChatGPT): refactor to new format, add omit settings logic

* refactor(EndpointSettings/BingAI): new dir structure and format BingAI options/settings to new

* fix: update useUpdateTokenCountMutation to accept an object with a 'text' property instead of a string

* fix(endpoints): ensure expected behaviors for preset dialogs

* chore(index.ts): add defaultTextProps to utils/index.ts for use in settings components

* chore(index.ts): add optionText to utils/index.ts for use in settings components

* wip: refactor google settings

* wip: progress with Google refactor, needs AdditionalButtons handling and global state setters

* refactor(OptionsBar.tsx): The setOption function has been refactored to use the useSetOptions custom hook for setting conversation options.

* chore(Anthropic.tsx, BingAI.tsx, Google.tsx, OpenAI.tsx): adjust height of container div in Settings component; chore(Examples.tsx): adjust height in Examples component

* refactor(Google): complete google refactor
feat(client): add new component PopoverButtons for displaying popover buttons in EndpointPopover
feat(data-provider): add types for PopoverButton and EndpointOptionsPopoverProps

* fix(OptionsBar.tsx): add useEffect hook to handle opacity class based on messagesTree and advancedMode
fix(style.css): rename class from 'openAIOptions-simple-container' to 'options-bar' and update references

* refactor(Plugins/OptionsBar): complete refactor of Plugins Select options, consolidate logic from TextChat to OptionsBar

* fix(Plugins.tsx): filter lastSelectedTools to remove any tools that are not in the current tools list
fix(useSetOptions.ts): remove unnecessary empty line

* feat(useSetOptions.ts): add setAgentOption function to update agentOptions in conversation state
feat(types.ts): add setAgentOption function to UseSetOptions type

* refactor(Settings/Plugins): refactor to new format, refactor(OptionHover): use same component for all endpoints

* refactor(OptionHover.tsx): refactor types object to use nested objects for openAI and gptPlugins
feat(OptionHover.tsx): add openAI object with specific properties for openAI configuration

* refactor(AgentSettings): new format, feat(types.ts): add TAgentOptions type for defining agent options in a conversation

* feat(PopoverButtons.tsx): add support for GPT plugin settings button
feat(Plugins.tsx): create PluginsView component for displaying plugin settings
feat(optionSettings.ts): add showAgentSettings atom for controlling agent settings visibility

* feat(client): add support for PluginsSettings in Input/Settings component
fix(client): change import path for PluginsSettings in Input/Settings component

* refactor(Settings/Plugins): complete refactor, store: refactor to TS, refactor: import defaultTextPropsLabel from utils

* feat(EndpointSettings, AgentSettings, Anthropic, Google, types.ts): Add support for Recoil state management and useRecoilValue hook; Pass models from endpointsConfig to various components; Add TModels type and update ModelSelectProps type.
fix(AgentSettings, Anthropic, Google, GoogleView, Plugins, OpenAI, Settings.tsx): Change import statements for ModelSelectProps from librechat-data-provider; Add models as a parameter to various components; Add models prop to PluginsView, Settings, and other components.

* refactor(EditPresetDialog.jsx): update import statements for Examples and AgentSettings components
feat(Settings/index.ts): add export statements for Examples and AgentSettings components

* chore(package.json): update eslint-plugin-import to version 2.28.0

* fix(eslint): dependency cycle rule is now working

* fix: dependency cycle errors and type errors

* refactor(EditPresetDialog.jsx): update import path for DialogTemplate component
refactor(NewConversationMenu/index.jsx): update import path for DialogTemplate component
refactor(ExportModel.jsx): update import path for DialogTemplate component

* refactor: rename NewConversationMenu to EndpointMenu

* style: mobile and desktop optimizations

* chore: eslint changes

* chore(eslintrc.js): update eslint configuration to use 'prettier' plugin
chore(postcss.config.cjs): update postcss configuration to use single quotes for require statements
fix(helpers.js): fix fs.rmSync function call to delete node_modules directory recursively
feat(update.js): add support for skipping git commands with '-g' flag

* chore(ModelSelect.tsx): add support for azureOpenAI option component
chore(Settings.tsx): add support for azureOpenAI option component
chore(package.json): add rebuild:package-lock and update:branch scripts

* fix(OptionHover.tsx): fix accessing nested properties in types object
feat(OptionHover.tsx): add check for existence of text before rendering HoverCardContent

* chore(style.css): update transition duration for options-bar from 0.3s to 0.25s

* fix(ScrollToBottom.jsx): fix z-index value for scroll button

* style: improve dialogs

* fix(Nav.jsx): adjust width and max-width of nav component

* chore(Nav.jsx): update max-width class for nav component in different screen sizes
chore(Dialog.tsx): update class for DialogFooter component to use flex-row layout

* fix(client): fix node_module resolution with path mapping

* fix(AdjustToneButton.jsx): add z-index to adjust tone button for proper layering
fix(TextChat.jsx): change onClick function to use arrow function to avoid immediate execution
fix(mobile.css): update z-index for nav and nav-mask for proper layering
chore(package.json): rename update:branch script to reinstall for clarity and consistency

* fix(OptionsBar/Settings): add null checks for conversation in BingAI.tsx, ChatGPT.tsx, Plugins.tsx, Settings.tsx

* style(TextChat/OptionsBar): match official site styles, setup regen/continue/stop buttons div

* chore: Import and apply removeFocusOutlines utility across various components, and rename removeButtonOutline to removeFocusOutlines
chore(Settings): Remove unused import and conditionally return null if conversation is falsy

* feat(hooks): add useLocalize hook

The useLocalize hook is added to the hooks/index.ts file. This hook allows for localization of phrases using the localize function from the ~/localization/Translation module. The hook uses the lang value from the store to determine the current language and returns a function that takes a phraseKey and optional values array as arguments and returns the localized phrase.

* refactor(OptionHover.tsx): Update text keys for OptionHover component, use new hook: useLocalize

* refactor(useDocumentTitle.ts): refactor to TS

* fix(typescript): type issues and update typescript linting deps

* refactor: Update ThemeContext and useOnClickOutside to TypeScript
chore(useDidMountEffect.js): Remove useDidMountEffect hook

* feat: GenerationButtons for stop/continue/regen, remove AdjustToneButton in favor of alternate advanced mode/Settings in OptionsBar

* fix(EndpointOptionsPopover.tsx): change switchToSimpleMode function name to closePopover
fix(GenerationButtons.tsx): change advancedMode prop name to showPopover
fix(OptionsBar.tsx): change advancedMode state name to showPopover
feat(OptionsBar.tsx): add logic to show/hide popover based on showPopover state
fix(types.ts): change switchToSimpleMode function name to closePopover

* chore: remove template button

* chore(GenerationButtons.tsx): adjust positioning of the div element
chore(Plugins.tsx): adjust width of the MultiSelectDropDown component
chore(OptionsBar.tsx): adjust padding of the button element

* refactor(EditPresetDialog): use new modular higher order components

* chore(newoptionsbar.html): delete unused file newoptionsbar.html

* refactor(EditPresetDialog): convert to TS

* chore(babel.config.cjs): update babel configuration, linting

* chore(EditPresetDialog.tsx): update className for DialogTemplate to include pb-0
chore(EndpointOptionsDialog.jsx): update className for DialogTemplate to include pb-0
chore(PopoverButtons.tsx): add buttonClass prop to PopoverButtons component
chore(DialogTemplate.tsx): update className for the footer div to include h-auto
chore(Dropdown.jsx): remove id prop from Dropdown component
chore(mobile.css): update transition duration for .nav class from 0.2s to 0.15s

* refactor(EditPresetDialog.tsx): simplify localization usage with hook

* chore(EditPresetDialog.tsx): update containerClassName to include z-index value

* fix(endpoints.ts): change type of endpointsConfig atom to TEndpointsConfig
refactor(cleanupPreset.ts): convert to TS
fix(index.ts): export cleanupPreset utility function
fix(types.ts): add missing properties to TPreset type

* refactor(EndpointOptionsDialog): convert to TS

* fix(EditPresetDialog.tsx):
  - import cleanupPreset from index
  - add null check before submitting preset
  - add null check before exporting preset

refactor(SaveAsPresetDialog.tsx): convert to TS

fix(usePresetOptions.ts): import cleanupPreset from index

fix(types.ts):
  - make title prop optional in EditPresetProps
  - change preset prop in CleanupPreset to be partial

* chore: reorganize imports in App, EndpointMenu, Messages, and ExportModel components
feat(ScreenshotContext.jsx): add ScreenshotContext to hooks/index
chore(index.ts): export ThemeContext, ScreenshotContext, ApiErrorBoundaryContext hooks, cleanupPreset, and getIcon functions from utils

* wip: add headerClassName for dialog template

* chore(EndpointOptionsDialog.tsx): remove unused headerClassName prop
chore(EndpointOptionsDialog.tsx): adjust height of main container in mobile and desktop view

* fix(react-query-service.ts): change return type of useGetEndpointsQuery to QueryObserverResult<t.TEndpointsConfig>

* refactor: imports from index and refactor to TS

* refactor: refactor all svg components to TS

* refactor: refactor all UI components to TS, remove unused component

* fix(SelectDropDown.tsx): remove file extension from import statement for CheckMark component

* fix: SaveAsPresetDialog typing issue

* fix(OptionsBar): close popover when an endpoint with no settings is selected

* chore(ChatGPT.tsx): update width of model select dropdown to 60px
refactor(types.ts): decouple ModelSelectProps from SettingsProps

* fix(popover Settings): space taken from the options menu for each endpoint

* fix:'Set token first' element alignment, add padding to endpointmenu icon in mobile

* style: match official site header

* refactor(EndpointOptionsDialog): make functionality explicitly saving current convos as presets

* fix(useLocalize.ts): change values parameter from an array to rest parameters

* refactor(EndpointSettings): Utilize useLocalize hook for all endpoint settings

* fix(Popover): correct spacing/center and remove focus outlines for close button

* chore: employ use of cn (clsx) in Popover styles

* chore(EditPresetDialog.tsx): update className to add padding bottom
chore(EndpointOptionsDialog.tsx): update className to add padding bottom

* style(EndpointMenu, TextChat): add better styling at diff. breakpoints

* refactor(EndpointSettings): consolidate container style to higher order component

* refactor(EditPresetDialog.tsx): pass custom style to Settings from here

* style: setting dialogs improved in all views

* style(EndpointMenu): improve UX for mobile

* style(PresetDialog): increase height so scrollbar isn't triggered

* chore(EditPresetDialog.tsx): update className to include xl height for DialogTemplate
chore(InputNumber.tsx): update className to include max height for InputNumber component

* fix: light mode styling

* fix(OptionsBar/ScrollToBottom/Popover): quick fix to rework in future: hide scrollToBottom when Popover is open

* style: remove bg-gradient around textarea in mobile view

* chore(ThemeContext.tsx): refactor ThemeContext to use default context value, also fixes type issue

* chore(EditPresetDialog.tsx): adjust grid layout in EditPresetDialog component

* style(TextChat): make gradient more opaque/smoother

* fix(TextChat.jsx): fix background gradient color based on theme and system preference

* test(layout-test-utils.tsx): add mock implementation for window.matchMedia in test setup
feat(layout-test-utils.tsx): add authConfig prop to AuthContextProvider in renderWithProvidersWrapper function
chore(tsconfig.json): include test directory in tsconfig include section

* chore(jest.config.cjs): update test file paths in jest configuration
chore(Login.spec.tsx): update test file path in import statement
chore(LoginForm.spec.tsx): update test file path in import statement
chore(Registration.spec.tsx): update test file path in import statement
chore(PluginAuthForm.spec.tsx): update test file path in import statement
chore(PluginStoreDialog.spec.tsx): update test file path in import statement
chore(layout-test-utils.tsx): move matchMedia mock to separate file
chore(tsconfig.json): add path mapping for test files in client directory

* test: add import for 'test/matchMedia.mock' in test files

The changes in this commit add an import statement for 'test/matchMedia.mock' in multiple test files. This import is necessary for mocking the behavior of the matchMedia function during testing.

* style(ClearConvosDialog): remove borders from button and modal, uniform button size

* fix(AgentSettings.tsx): overlapping issue

* fix(PresetDialogs): improve spacing of top row and dialog content

* style(Settings): 2nd column will now dynamically adjust better across all screen sizes

* style(ModelSelect): improve styling for mobile/desktop, add hover shadow
feat(ModelSelect/Plugins): hide ModelSelect when screen is small

* refactor(RowButton, buildTree): convert to TS

* style(ModelSelect): add transition effect to shadows on hover
This commit is contained in:
Danny Avila 2023-08-04 13:56:44 -04:00 committed by GitHub
parent fb99e5a7da
commit 956aa6c674
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
203 changed files with 5062 additions and 4327 deletions

View file

@ -1,4 +1,4 @@
import { render, waitFor } from 'layout-test-utils';
import { render, waitFor } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
import Login from '../Login';
import * as mockDataProvider from 'librechat-data-provider';

View file

@ -1,4 +1,4 @@
import { render } from 'layout-test-utils';
import { render } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
import Login from '../LoginForm';

View file

@ -1,4 +1,4 @@
import { render, waitFor } from 'layout-test-utils';
import { render, waitFor } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
import Registration from '../Registration';
import * as mockDataProvider from 'librechat-data-provider';

View file

@ -1,24 +0,0 @@
import React from 'react';
import { HoverCardPortal, HoverCardContent } from '~/components/ui/HoverCard.tsx';
const types = {
temp: 'Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.',
topp: 'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
topk: 'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
maxoutputtokens:
' Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
};
function OptionHover({ type, side }) {
return (
<HoverCardPortal>
<HoverCardContent side={side} className="w-80 ">
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{types[type]}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View file

@ -1,251 +0,0 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize';
import SelectDropDown from '../../ui/SelectDropDown';
import { Input } from '~/components/ui/Input.tsx';
import { Label } from '~/components/ui/Label.tsx';
import { Slider } from '~/components/ui/Slider.tsx';
import { InputNumber } from '~/components/ui/InputNumber.tsx';
import OptionHover from './OptionHover';
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
import { cn } from '~/utils/';
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';
const optionText =
'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors';
import store from '~/store';
function Settings(props) {
const {
readonly,
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxOutputTokens,
setOption,
} = props;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const setModel = setOption('model');
const setModelLabel = setOption('modelLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('topP');
const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens');
const models = endpointsConfig?.['anthropic']?.['availableModels'] || [];
return (
<div className={'h-[490px] overflow-y-auto md:h-[350px]'}>
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<SelectDropDown
value={model}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(
defaultTextProps,
'z-50 flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
Custom Name <small className="opacity-40">(default: blank)</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value || null)}
placeholder="Set a custom name for Claude"
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
Prompt Prefix <small className="opacity-40">(default: blank)</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value || null)}
placeholder="Set custom instructions or context. Ignored if empty."
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
Temperature <small className="opacity-40">(default: 0.7)</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="temp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
Top P <small className="opacity-40">(default: 0.95)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
Top K <small className="opacity-40">(default: 40)</small>
</Label>
<InputNumber
id="top-k-int"
disabled={readonly}
value={topK}
onChange={(value) => setTopK(value)}
max={40}
min={1}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topK]}
onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(0)}
max={40}
min={1}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topk" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
Max Output Tokens <small className="opacity-40">(default: 1024)</small>
</Label>
<InputNumber
id="max-tokens-int"
disabled={readonly}
value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(value)}
max={1024}
min={1}
step={1}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[maxOutputTokens]}
onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(0)}
max={1024}
min={1}
step={1}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="maxoutputtokens" side="left" />
</HoverCard>
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -1,158 +0,0 @@
import { useEffect, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { Label } from '~/components/ui/Label.tsx';
import { Checkbox } from '~/components/ui/Checkbox.tsx';
import SelectDropDown from '../../ui/SelectDropDown';
import { cn } from '~/utils/';
import useDebounce from '~/hooks/useDebounce';
import { useUpdateTokenCountMutation } from 'librechat-data-provider';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
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';
function Settings(props) {
const { readonly, context, systemMessage, jailbreak, toneStyle, setOption } = props;
const [tokenCount, setTokenCount] = useState(0);
const showSystemMessage = jailbreak;
const setContext = setOption('context');
const setSystemMessage = setOption('systemMessage');
const setJailbreak = setOption('jailbreak');
const setToneStyle = (value) => setOption('toneStyle')(value.toLowerCase());
const debouncedContext = useDebounce(context, 250);
const updateTokenCountMutation = useUpdateTokenCountMutation();
const lang = useRecoilValue(store.lang);
useEffect(() => {
if (!debouncedContext || debouncedContext.trim() === '') {
setTokenCount(0);
return;
}
const handleTextChange = (context) => {
updateTokenCountMutation.mutate(
{ text: context },
{
onSuccess: (data) => {
setTokenCount(data.count);
},
},
);
};
handleTextChange(debouncedContext);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedContext]);
return (
<div className="h-[490px] overflow-y-auto md:h-[350px]">
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<Label htmlFor="toneStyle-dropdown" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_tone_style')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_creative')})
</small>
</Label>
<SelectDropDown
id="toneStyle-dropdown"
title={null}
value={`${toneStyle.charAt(0).toUpperCase()}${toneStyle.slice(1)}`}
setValue={setToneStyle}
availableValues={['Creative', 'Fast', 'Balanced', 'Precise']}
disabled={readonly}
className={cn(
defaultTextProps,
'flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="context" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_context')}{' '}
<small className="opacity-40">({localize(lang, 'com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id="context"
disabled={readonly}
value={context || ''}
onChange={(e) => setContext(e.target.value || null)}
placeholder={localize(lang, 'com_endpoint_bing_context_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2',
)}
/>
<small className="mb-5 text-black dark:text-white">{`${localize(
lang,
'com_endpoint_token_count',
)}: ${tokenCount}`}</small>
</div>
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<Label htmlFor="jailbreak" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_bing_enable_sydney')}{' '}
<small className="opacity-40">({localize(lang, 'com_endpoint_default_false')})</small>
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="jailbreak"
disabled={readonly}
checked={jailbreak}
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
onCheckedChange={setJailbreak}
/>
<label
htmlFor="jailbreak"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{localize(lang, 'com_endpoint_bing_jailbreak')}{' '}
<small>{localize(lang, 'com_endpoint_bing_to_enable_sydney')}</small>
</label>
</div>
</div>
{showSystemMessage && (
<div className="grid w-full items-center gap-2">
<Label
htmlFor="systemMessage"
className="text-left text-sm font-medium"
style={{ opacity: showSystemMessage ? '1' : '0' }}
>
<a
href="https://github.com/danny-avila/LibreChat/blob/main/docs/features/bing_jailbreak.md#default-system-message-for-jailbreak-mode-sydney"
target="_blank"
className="text-blue-500 transition-colors duration-200 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-500"
rel="noreferrer"
>
{localize(lang, 'com_endpoint_system_message')}
</a>{' '}
<small className="opacity-40 dark:text-gray-50">
( {localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id="systemMessage"
disabled={readonly}
value={systemMessage || ''}
onChange={(e) => setSystemMessage(e.target.value || null)}
placeholder={localize(lang, 'com_endpoint_bing_system_message_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 placeholder:text-red-400',
)}
/>
</div>
)}
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -1,292 +0,0 @@
import axios from 'axios';
import { useEffect, useState } from 'react';
import Settings from './Settings';
import Examples from './Google/Examples.jsx';
import exportFromJSON from 'export-from-json';
import AgentSettings from './Plugins/AgentSettings.jsx';
import { useSetRecoilState, useRecoilValue } from 'recoil';
import filenamify from 'filenamify';
import {
MessagesSquared,
GPTIcon,
Input,
Label,
Button,
Dropdown,
Dialog,
DialogClose,
DialogButton,
DialogTemplate,
} from '~/components/';
import { cn } from '~/utils/';
import cleanupPreset from '~/utils/cleanupPreset';
import { localize } from '~/localization/Translation';
import store from '~/store';
const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
const lang = useRecoilValue(store.lang);
const [preset, setPreset] = useState(_preset);
const setPresets = useSetRecoilState(store.presets);
const [showExamples, setShowExamples] = useState(false);
const [showAgentSettings, setShowAgentSettings] = useState(false);
const availableEndpoints = useRecoilValue(store.availableEndpoints);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const triggerExamples = () => setShowExamples((prev) => !prev);
const triggerAgentSettings = () => setShowAgentSettings((prev) => !prev);
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}),
);
};
const setAgentOption = (param) => (newValue) => {
let editablePreset = JSON.stringify(_preset);
editablePreset = JSON.parse(editablePreset);
let { agentOptions } = editablePreset;
agentOptions[param] = newValue;
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
agentOptions,
},
endpointsConfig,
}),
);
};
const setExample = (i, type, newValue = null) => {
let update = {};
let current = preset?.examples.slice() || [];
let currentExample = { ...current[i] } || {};
currentExample[type] = { content: newValue };
current[i] = currentExample;
update.examples = current;
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}),
);
};
const addExample = () => {
let update = {};
let current = preset?.examples.slice() || [];
current.push({ input: { content: '' }, output: { content: '' } });
update.examples = current;
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}),
);
};
const removeExample = () => {
let update = {};
let current = preset?.examples.slice() || [];
if (current.length <= 1) {
update.examples = [{ input: { content: '' }, output: { content: '' } }];
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}),
);
return;
}
current.pop();
update.examples = current;
setPreset((prevState) =>
cleanupPreset({
preset: {
...prevState,
...update,
},
endpointsConfig,
}),
);
};
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';
const submitPreset = () => {
axios({
method: 'post',
url: '/api/presets',
data: cleanupPreset({ preset, endpointsConfig }),
withCredentials: true,
}).then((res) => {
setPresets(res?.data);
});
};
const exportPreset = () => {
const fileName = filenamify(preset?.title || 'preset');
exportFromJSON({
data: cleanupPreset({ preset, endpointsConfig }),
fileName,
exportType: exportFromJSON.types.json,
});
};
useEffect(() => {
setPreset(_preset);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
const endpoint = preset?.endpoint;
const isGoogle = endpoint === 'google';
const isGptPlugins = endpoint === 'gptPlugins';
const shouldShowSettings =
(isGoogle && !showExamples) ||
(isGptPlugins && !showAgentSettings) ||
(!isGoogle && !isGptPlugins);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={`${title || localize(lang, 'com_endpoint_edit_preset')} - ${preset?.title}`}
className="h-[675px] max-w-full sm:max-w-4xl "
main={
<div className="flex w-full flex-col items-center gap-2 md:h-[475px]">
<div className="grid w-full gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_preset_name')}
</Label>
<Input
id="chatGptLabel"
value={preset?.title || ''}
onChange={(e) => setOption('title')(e.target.value || '')}
placeholder={localize(lang, 'com_endpoint_set_custom_name')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
/>
</div>
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label htmlFor="endpoint" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint')}
</Label>
<Dropdown
id="endpoint"
value={preset?.endpoint || ''}
onChange={setOption('endpoint')}
options={availableEndpoints}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
{preset?.endpoint === 'google' && (
<Button
type="button"
className="ml-1 flex h-auto w-full bg-transparent px-2 py-1 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={triggerExamples}
>
<MessagesSquared className="mr-1 w-[14px]" />
{(showExamples
? localize(lang, 'com_endpoint_hide')
: localize(lang, 'com_endpoint_show')) +
localize(lang, 'com_endpoint_examples')}
</Button>
)}
{preset?.endpoint === 'gptPlugins' && (
<Button
type="button"
className="ml-1 flex h-auto w-full bg-transparent px-2 py-1 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={triggerAgentSettings}
>
<GPTIcon className="mr-1 mt-[2px] w-[14px]" size={14} />
{`Show ${showAgentSettings ? 'Completion' : 'Agent'} Settings`}
{localize(
lang,
'com_endpoint_show_what_settings',
showAgentSettings
? localize(lang, 'com_endpoint_completion')
: localize(lang, 'com_endpoint_agent'),
)}
</Button>
)}
</div>
</div>
<div className="my-4 w-full border-t border-gray-300 dark:border-gray-500" />
<div className="w-full p-0">
{shouldShowSettings && <Settings preset={preset} setOption={setOption} />}
{preset?.endpoint === 'google' &&
showExamples &&
!preset?.model?.startsWith('codechat-') && (
<Examples
examples={preset.examples}
setExample={setExample}
addExample={addExample}
removeExample={removeExample}
edit={true}
/>
)}
{preset?.endpoint === 'gptPlugins' && showAgentSettings && (
<AgentSettings
agent={preset.agentOptions.agent}
skipCompletion={preset.agentOptions.skipCompletion}
model={preset.agentOptions.model}
endpoint={preset.agentOptions.endpoint}
temperature={preset.agentOptions.temperature}
topP={preset.agentOptions.top_p}
freqP={preset.agentOptions.presence_penalty}
presP={preset.agentOptions.frequency_penalty}
setOption={setAgentOption}
tools={preset.tools}
/>
)}
</div>
</div>
}
buttons={
<>
<DialogClose
onClick={submitPreset}
className="dark:hover:gray-400 border-gray-700 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
>
{localize(lang, 'com_endpoint_save')}
</DialogClose>
</>
}
leftButtons={
<>
<DialogButton onClick={exportPreset} className="dark:hover:gray-400 border-gray-700">
{localize(lang, 'com_endpoint_export')}
</DialogButton>
</>
}
/>
</Dialog>
);
};
export default EditPresetDialog;

View file

@ -0,0 +1,146 @@
import axios from 'axios';
import { useEffect } from 'react';
import filenamify from 'filenamify';
import exportFromJSON from 'export-from-json';
import { useSetRecoilState, useRecoilState, useRecoilValue } from 'recoil';
import { EditPresetProps } from 'librechat-data-provider';
import { useSetOptions, useLocalize } from '~/hooks';
import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import PopoverButtons from './PopoverButtons';
import EndpointSettings from './EndpointSettings';
import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset } from '~/utils/';
import store from '~/store';
const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }: EditPresetProps) => {
const [preset, setPreset] = useRecoilState(store.preset);
const setPresets = useSetRecoilState(store.presets);
const availableEndpoints = useRecoilValue(store.availableEndpoints);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const { setOption } = useSetOptions(_preset);
const localize = useLocalize();
const submitPreset = () => {
if (!preset) {
return;
}
axios({
method: 'post',
url: '/api/presets',
data: cleanupPreset({ preset, endpointsConfig }),
withCredentials: true,
}).then((res) => {
setPresets(res?.data);
});
};
const exportPreset = () => {
if (!preset) {
return;
}
const fileName = filenamify(preset?.title || 'preset');
exportFromJSON({
data: cleanupPreset({ preset, endpointsConfig }),
fileName,
exportType: exportFromJSON.types.json,
});
};
useEffect(() => {
setPreset(_preset);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
const { endpoint } = preset || {};
if (!endpoint) {
return null;
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={`${title || localize('com_endpoint_edit_preset')} - ${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]"
main={
<div className="flex w-full flex-col items-center gap-2 md:h-[530px]">
<div className="grid w-full grid-cols-5 gap-6">
<div className="col-span-4 flex items-start justify-start gap-4">
<div className="flex w-full flex-col">
<Label htmlFor="preset-name" className="mb-1 text-left text-sm font-medium">
{localize('com_endpoint_preset_name')}
</Label>
<Input
id="preset-name"
value={preset?.title || ''}
onChange={(e) => setOption('title')(e.target.value || '')}
placeholder={localize('com_endpoint_set_custom_name')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="flex w-full flex-col">
<Label htmlFor="endpoint" className="mb-1 text-left text-sm font-medium">
{localize('com_endpoint')}
</Label>
<Dropdown
value={endpoint || ''}
onChange={setOption('endpoint')}
options={availableEndpoints}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none ',
removeFocusOutlines,
)}
containerClassName="flex w-full resize-none z-[51]"
/>
</div>
</div>
<div className="col-span-2 flex items-start justify-start gap-4 sm:col-span-1">
<div className="flex w-full flex-col">
<Label
htmlFor="endpoint"
className="mb-1 hidden text-left text-sm font-medium sm:block"
>
{''}
</Label>
<PopoverButtons
endpoint={endpoint}
buttonClass="ml-0 w-full dark:bg-gray-700 dark:hover:bg-gray-800 p-2 h-[40px] justify-center mt-0"
iconClass="hidden lg:block w-4"
/>
</div>
</div>
</div>
<div className="my-4 w-full border-t border-gray-300 dark:border-gray-500" />
<div className="w-full p-0">
<EndpointSettings
conversation={preset}
setOption={setOption}
isPreset={true}
className="h-full md:mb-4 md:h-[440px]"
/>
</div>
</div>
}
buttons={
<div className="mb-6 md:mb-2">
<DialogButton onClick={exportPreset} className="dark:hover:gray-400 border-gray-700">
{localize('com_endpoint_export')}
</DialogButton>
<DialogClose
onClick={submitPreset}
className="dark:hover:gray-400 ml-2 border-gray-700 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
>
{localize('com_endpoint_save')}
</DialogClose>
</div>
}
/>
</Dialog>
);
};
export default EditPresetDialog;

View file

@ -1,88 +0,0 @@
import exportFromJSON from 'export-from-json';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Dialog, DialogButton, DialogTemplate } from '~/components';
import SaveAsPresetDialog from './SaveAsPresetDialog';
import cleanupPreset from '~/utils/cleanupPreset';
import { alternateName } from '~/utils';
import Settings from './Settings';
import store from '~/store';
import { localize } from '~/localization/Translation';
// A preset dialog to show readonly preset values.
const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) => {
const [preset, setPreset] = useState(_preset);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const endpointName = alternateName[preset?.endpoint] ?? 'Endpoint';
const lang = useRecoilValue(store.lang);
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setPreset((prevState) => ({
...prevState,
...update,
}));
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const exportPreset = () => {
exportFromJSON({
data: cleanupPreset({ preset, endpointsConfig }),
fileName: `${preset?.title}.json`,
exportType: exportFromJSON.types.json,
});
};
useEffect(() => {
setPreset(_preset);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={`${title || localize(lang, 'com_endpoint_view_options')} - ${endpointName}`}
className="max-w-full sm:max-w-4xl"
main={
<div className="flex w-full flex-col items-center gap-2">
<div className="w-full p-0">
<Settings preset={preset} readonly={true} setOption={setOption} />
</div>
</div>
}
buttons={
<>
<DialogButton
onClick={saveAsPreset}
className="dark:hover:gray-400 border-gray-700 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
>
{localize(lang, 'com_endpoint_save_as_preset')}
</DialogButton>
</>
}
leftButtons={
<>
<DialogButton onClick={exportPreset} className="dark:hover:gray-400 border-gray-700">
{localize(lang, 'com_endpoint_export')}
</DialogButton>
</>
}
/>
</Dialog>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={preset}
/>
</>
);
};
export default EndpointOptionsDialog;

View file

@ -0,0 +1,109 @@
import exportFromJSON from 'export-from-json';
import { useEffect, useState } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil';
import { EditPresetProps, SetOption, TPreset } from 'librechat-data-provider';
import { Dialog, DialogButton } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate';
import SaveAsPresetDialog from './SaveAsPresetDialog';
import EndpointSettings from './EndpointSettings';
import PopoverButtons from './PopoverButtons';
import { cleanupPreset } from '~/utils';
import { useLocalize } from '~/hooks';
import store from '~/store';
// A preset dialog to show readonly preset values.
const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }: EditPresetProps) => {
const [preset, setPreset] = useRecoilState(store.preset);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const localize = useLocalize();
const setOption: SetOption = (param) => (newValue) => {
const update = {};
update[param] = newValue;
setPreset(
(prevState) =>
({
...prevState,
...update,
} as TPreset),
);
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const exportPreset = () => {
if (!preset) {
return;
}
exportFromJSON({
data: cleanupPreset({ preset, endpointsConfig }),
fileName: `${preset?.title}.json`,
exportType: exportFromJSON.types.json,
});
};
useEffect(() => {
setPreset(_preset);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
const { endpoint } = preset ?? {};
if (!endpoint) {
return null;
}
if (!preset) {
return null;
}
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={`${title || localize('com_endpoint_save_convo_as_preset')}`}
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[680px] md:w-[750px] md:overflow-y-hidden lg:w-[950px]"
// headerClassName="sm:p-2 h-16"
main={
<div className="flex w-full flex-col items-center gap-2 md:h-[530px]">
<div className="w-full p-0">
<PopoverButtons
endpoint={endpoint}
buttonClass="ml-0 mb-4 col-span-2 dark:bg-gray-700 dark:hover:bg-gray-800 p-2"
/>
<EndpointSettings
conversation={preset}
setOption={setOption}
isPreset={true}
className="h-full md:mb-0 md:h-[490px]"
/>
</div>
</div>
}
buttons={
<div className="mb-6 md:mb-2">
<DialogButton onClick={exportPreset} className="dark:hover:gray-400 border-gray-700">
{localize('com_endpoint_export')}
</DialogButton>
<DialogButton
onClick={saveAsPreset}
className="dark:hover:gray-400 ml-2 border-gray-700 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
>
{localize('com_endpoint_save_as_preset')}
</DialogButton>
</div>
}
/>
</Dialog>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={preset}
/>
</>
);
};
export default EndpointOptionsDialog;

View file

@ -1,76 +0,0 @@
import React from 'react';
import { Button } from '../ui/Button.tsx';
import CrossIcon from '../svg/CrossIcon';
// import SaveIcon from '../svg/SaveIcon';
import { Save } from 'lucide-react';
import { cn } from '~/utils/';
import store from '~/store';
import { useRecoilValue } from 'recoil';
import { localize } from '~/localization/Translation';
function EndpointOptionsPopover({
content,
visible,
saveAsPreset,
switchToSimpleMode,
additionalButton = null,
}) {
const lang = useRecoilValue(store.lang);
const cardStyle =
'shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white';
return (
<>
<div
className={
' endpointOptionsPopover-container absolute bottom-[-10px] z-0 flex w-full flex-col items-center md:px-4' +
(visible ? ' show' : '')
}
>
<div
className={
cardStyle +
' border-d-0 flex w-full flex-col overflow-hidden rounded-none border-s-0 border-t bg-slate-200 px-0 pb-[10px] dark:border-white/10 md:rounded-md md:border lg:w-[736px]'
}
>
<div className="flex w-full items-center bg-slate-100 px-2 py-2 dark:bg-gray-800/60">
{/* <span className="text-xs font-medium font-normal">Advanced settings for OpenAI endpoint</span> */}
<Button
type="button"
className="h-auto justify-start bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-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={saveAsPreset}
>
<Save className="mr-1 w-[14px]" />
{localize(lang, 'com_endpoint_save_as_preset')}
</Button>
{additionalButton && (
<Button
type="button"
className={cn(
additionalButton.buttonClass,
'ml-1 h-auto justify-start bg-transparent px-2 py-1 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={additionalButton.handler}
>
{additionalButton.icon}
{additionalButton.label}
</Button>
)}
<Button
type="button"
className="ml-auto h-auto bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white"
onClick={switchToSimpleMode}
>
<CrossIcon className="mr-1" />
{/* Switch to simple mode */}
</Button>
</div>
<div>{content}</div>
</div>
</div>
</>
);
}
export default EndpointOptionsPopover;

View file

@ -0,0 +1,61 @@
import React from 'react';
import { Save } from 'lucide-react';
import { EndpointOptionsPopoverProps } from 'librechat-data-provider';
import { Button } from '~/components/ui';
import { CrossIcon } from '~/components/svg';
import PopoverButtons from './PopoverButtons';
import { cn, removeFocusOutlines } from '~/utils';
import { useLocalize } from '~/hooks';
export default function EndpointOptionsPopover({
children,
endpoint,
visible,
saveAsPreset,
closePopover,
}: EndpointOptionsPopoverProps) {
const localize = useLocalize();
const cardStyle =
'shadow-xl rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white';
return (
<>
<div
className={cn(
'endpointOptionsPopover-container absolute bottom-[-10px] z-0 flex w-full flex-col items-center md:px-4',
visible ? ' show' : '',
)}
>
<div
className={cn(
cardStyle,
'border-d-0 flex w-full flex-col overflow-hidden rounded-none border-s-0 border-t bg-white px-0 pb-[10px] dark:border-white/10 md:rounded-md md:border lg:w-[736px]',
)}
>
<div className="flex w-full items-center bg-slate-100 px-2 py-2 dark:bg-gray-800/60">
<Button
type="button"
className="h-auto justify-start bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-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={saveAsPreset}
>
<Save className="mr-1 w-[14px]" />
{localize('com_endpoint_save_as_preset')}
</Button>
<PopoverButtons endpoint={endpoint} />
<Button
type="button"
className={cn(
'ml-auto h-auto bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white',
removeFocusOutlines,
)}
onClick={closePopover}
>
<CrossIcon />
</Button>
</div>
<div>{children}</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,59 @@
import { useRecoilValue } from 'recoil';
import { OpenAISettings, BingAISettings, AnthropicSettings } from './Settings';
import { GoogleSettings, PluginsSettings } from './Settings/MultiView';
import { SettingsProps, OptionComponent, MultiViewComponent } from 'librechat-data-provider';
import { cn } from '~/utils';
import store from '~/store';
const optionComponents: { [key: string]: OptionComponent } = {
openAI: OpenAISettings,
azureOpenAI: OpenAISettings,
bingAI: BingAISettings,
anthropic: AnthropicSettings,
};
const multiViewComponents: { [key: string]: MultiViewComponent } = {
google: GoogleSettings,
gptPlugins: PluginsSettings,
};
export default function Settings({
conversation,
setOption,
isPreset = false,
className = '',
}: SettingsProps) {
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (!conversation?.endpoint) {
return null;
}
const { endpoint } = conversation;
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
const OptionComponent = optionComponents[endpoint];
if (OptionComponent) {
return (
<div className={cn('h-[480px] overflow-y-auto md:mb-2 md:h-[350px]', className)}>
<OptionComponent
conversation={conversation}
setOption={setOption}
models={models}
isPreset={isPreset}
/>
</div>
);
}
const MultiViewComponent = multiViewComponents[endpoint];
if (!MultiViewComponent) {
return null;
}
return (
<div className={cn('h-[480px] overflow-y-auto md:mb-2 md:h-[350px]', className)}>
<MultiViewComponent conversation={conversation} models={models} isPreset={isPreset} />
</div>
);
}

View file

@ -1,101 +0,0 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { Button } from '~/components/ui/Button.tsx';
import { Label } from '~/components/ui/Label.tsx';
import { Plus, Minus } from 'lucide-react';
import { cn } from '~/utils/';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
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';
function Examples({ readonly, examples, setExample, addExample, removeExample, edit = false }) {
const maxHeight = edit ? 'max-h-[233px]' : 'max-h-[350px]';
const lang = useRecoilValue(store.lang);
return (
<>
<div className={`${maxHeight} overflow-y-auto`}>
<div id="examples-grid" className="grid gap-6 sm:grid-cols-2">
{examples.map((example, idx) => (
<React.Fragment key={idx}>
{/* Input */}
<div
className={`col-span-${
examples.length === 1 ? '1' : 'full'
} flex flex-col items-center justify-start gap-6 sm:col-span-1`}
>
<div className="grid w-full items-center gap-2">
<Label htmlFor={`input-${idx}`} className="text-left text-sm font-medium">
{localize(lang, 'com_ui_input')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id={`input-${idx}`}
disabled={readonly}
value={example?.input?.content || ''}
onChange={(e) => setExample(idx, 'input', e.target.value || null)}
placeholder="Set example input. Example is ignored if empty."
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[75px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
{/* Output */}
<div
className={`col-span-${
examples.length === 1 ? '1' : 'full'
} flex flex-col items-center justify-start gap-6 sm:col-span-1`}
>
<div className="grid w-full items-center gap-2">
<Label htmlFor={`output-${idx}`} className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_output')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id={`output-${idx}`}
disabled={readonly}
value={example?.output?.content || ''}
onChange={(e) => setExample(idx, 'output', e.target.value || null)}
placeholder={'Set example output. Example is ignored if empty.'}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[75px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
</React.Fragment>
))}
</div>
</div>
<div className="flex justify-center">
<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"
onClick={removeExample}
>
<Minus className="w-[16px]" />
</Button>
<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"
onClick={addExample}
>
<Plus className="w-[16px]" />
</Button>
</div>
</>
);
}
export default Examples;

View file

@ -1,36 +0,0 @@
import React from 'react';
import { HoverCardPortal, HoverCardContent } from '~/components/ui/HoverCard.tsx';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
const types = {
temp: 'com_endpoint_google_temp',
topp: 'com_endpoint_google_topp',
topk: 'com_endpoint_google_topk',
maxoutputtokens: 'com_endpoint_google_maxoutputtokens',
};
function OptionHover({ type, side }) {
// const options = {};
// if (type === 'pres') {
// options.sideOffset = 45;
// }
const lang = useRecoilValue(store.lang);
return (
<HoverCardPortal>
<HoverCardContent
side={side}
className="w-80 "
// {...options}
>
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(lang, types[type])}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View file

@ -1,281 +0,0 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize';
import SelectDropDown from '../../ui/SelectDropDown';
import { Input } from '~/components/ui/Input.tsx';
import { Label } from '~/components/ui/Label.tsx';
import { Slider } from '~/components/ui/Slider.tsx';
import { InputNumber } from '~/components/ui/InputNumber.tsx';
import OptionHover from './OptionHover';
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
import { cn } from '~/utils/';
import { localize } from '~/localization/Translation';
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';
const optionText =
'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors';
import store from '~/store';
function Settings(props) {
const {
readonly,
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxOutputTokens,
setOption,
} = props;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const lang = useRecoilValue(store.lang);
const setModel = setOption('model');
const setModelLabel = setOption('modelLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('topP');
const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens');
const models = endpointsConfig?.['google']?.['availableModels'] || [];
const codeChat = model.startsWith('codechat-');
return (
<div className={'h-[490px] overflow-y-auto md:h-[350px]'}>
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<SelectDropDown
value={model}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(
defaultTextProps,
'z-50 flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
</div>
{!codeChat && (
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_custom_name')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value || null)}
placeholder={localize(lang, 'com_endpoint_google_custom_name_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value || null)}
placeholder={localize(lang, 'com_endpoint_google_prompt_prefix_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
)}
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_temperature')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default')}: 0.2)
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="temp" side="left" />
</HoverCard>
{!codeChat && (
<>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_top_p')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 0.95)})
</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_top_k')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 40)})
</small>
</Label>
<InputNumber
id="top-k-int"
disabled={readonly}
value={topK}
onChange={(value) => setTopK(value)}
max={40}
min={1}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topK]}
onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(0)}
max={40}
min={1}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topk" side="left" />
</HoverCard>
</>
)}
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_max_output_tokens')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 1024)})
</small>
</Label>
<InputNumber
id="max-tokens-int"
disabled={readonly}
value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(value)}
max={1024}
min={1}
step={1}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[maxOutputTokens]}
onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(0)}
max={1024}
min={1}
step={1}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="maxoutputtokens" side="left" />
</HoverCard>
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -1,29 +0,0 @@
import React from 'react';
import { HoverCardPortal, HoverCardContent } from '~/components/ui/HoverCard.tsx';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
const types = {
temp: 'com_endpoint_openai_temp',
max: 'com_endpoint_openai_max',
topp: 'com_endpoint_openai_topp',
freq: 'com_endpoint_openai_freq',
pres: 'com_endpoint_openai_pres',
};
function OptionHover({ type, side }) {
const lang = useRecoilValue(store.lang);
return (
<HoverCardPortal>
<HoverCardContent side={side} className="w-80 ">
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(lang, types[type])}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View file

@ -1,278 +0,0 @@
import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize';
import SelectDropDown from '../../ui/SelectDropDown';
import { Input } from '~/components/ui/Input.tsx';
import { Label } from '~/components/ui/Label.tsx';
import { Slider } from '~/components/ui/Slider.tsx';
import { InputNumber } from '~/components/ui/InputNumber.tsx';
import OptionHover from './OptionHover';
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
import { cn } from '~/utils/';
import { localize } from '~/localization/Translation';
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';
const optionText =
'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors';
import store from '~/store';
function Settings(props) {
const {
readonly,
model,
chatGptLabel,
promptPrefix,
temperature,
topP,
freqP,
presP,
setOption,
} = props;
const endpoint = props.endpoint || 'openAI';
const isOpenAI = endpoint === 'openAI' || endpoint === 'azureOpenAI';
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const lang = useRecoilValue(store.lang);
const setModel = setOption('model');
const setChatGptLabel = setOption('chatGptLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('top_p');
const setFreqP = setOption('presence_penalty');
const setPresP = setOption('frequency_penalty');
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
return (
<div className="h-[490px] overflow-y-auto md:h-[350px]">
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<SelectDropDown
value={model}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(
defaultTextProps,
'flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
</div>
{isOpenAI && (
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_custom_name')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<Input
id="chatGptLabel"
disabled={readonly}
value={chatGptLabel || ''}
onChange={(e) => setChatGptLabel(e.target.value || null)}
placeholder={localize(lang, 'com_endpoint_openai_custom_name_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value || null)}
placeholder={localize(lang, 'com_endpoint_openai_prompt_prefix_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
)}
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_temperature')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', isOpenAI ? '1' : '0')})
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value)}
max={2}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={2}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="temp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_top_p')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default')}: 1)
</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_frequency_penalty')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default')}: 0)
</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="freq" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_presence_penalty')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default')}: 0)
</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="pres" side="left" />
</HoverCard>
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -1,260 +0,0 @@
import { cn } from '~/utils/';
import { useRecoilValue } from 'recoil';
import {
Switch,
SelectDropDown,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components';
import OptionHover from './OptionHover';
import { localize } from '~/localization/Translation';
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';
const optionText =
'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors';
import store from '~/store';
function Settings(props) {
const { readonly, agent, skipCompletion, model, temperature, setOption } = props;
const endpoint = 'gptPlugins';
const lang = useRecoilValue(store.lang);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const setModel = setOption('model');
const setTemperature = setOption('temperature');
const setAgent = setOption('agent');
const setSkipCompletion = setOption('skipCompletion');
const onCheckedChangeAgent = (checked) => {
setAgent(checked ? 'functions' : 'classic');
};
const onCheckedChangeSkip = (checked) => {
setSkipCompletion(checked);
};
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
return (
<div className="h-[490px] overflow-y-auto md:h-[350px]">
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<SelectDropDown
title={localize(lang, 'com_endpoint_agent_model')}
value={model}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(
defaultTextProps,
'flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
</div>
<div className="grid w-full grid-cols-2 items-center gap-2">
<HoverCard openDelay={500}>
<HoverCardTrigger className="w-[100px]">
<label
htmlFor="functions-agent"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
<small>{localize(lang, 'com_endpoint_plug_use_functions')}</small>
</label>
<Switch
id="functions-agent"
checked={agent === 'functions'}
onCheckedChange={onCheckedChangeAgent}
disabled={readonly}
className="ml-4 mt-2"
/>
</HoverCardTrigger>
<OptionHover type="func" side="right" />
</HoverCard>
<HoverCard openDelay={500}>
<HoverCardTrigger className="ml-[-60px] w-[100px]">
<label
htmlFor="skip-completion"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
<small>{localize(lang, 'com_endpoint_plug_skip_completion')}</small>
</label>
<Switch
id="skip-completion"
checked={skipCompletion === true}
onCheckedChange={onCheckedChangeSkip}
disabled={readonly}
className="ml-4 mt-2"
/>
</HoverCardTrigger>
<OptionHover type="skip" side="right" />
</HoverCard>
</div>
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_temperature')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default')}: 0)
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value)}
max={2}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={2}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="temp" side="left" />
</HoverCard>
{/* <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
Top P <small className="opacity-40">(default: 1)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
Frequency Penalty <small className="opacity-40">(default: 0)</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="freq" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
Presence Penalty <small className="opacity-40">(default: 0)</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="pres" side="left" />
</HoverCard> */}
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -1,34 +0,0 @@
import { HoverCardPortal, HoverCardContent } from '~/components';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
const types = {
temp: 'com_endpoint_openai_temp',
func: 'com_endpoint_func_hover',
skip: 'com_endpoint_skip_hover',
max: 'com_endpoint_openai_max',
topp: 'com_endpoint_openai_topp',
freq: 'com_endpoint_openai_freq',
pres: 'com_endpoint_openai_pres',
};
function OptionHover({ type, side }) {
const lang = useRecoilValue(store.lang);
return (
<HoverCardPortal>
<HoverCardContent
side={side}
className="w-80 "
// {...options}
>
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(lang, types[type])}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View file

@ -1,293 +0,0 @@
import { cn } from '~/utils/';
import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize';
import {
SelectDropDown,
Input,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components';
import OptionHover from './OptionHover';
import { localize } from '~/localization/Translation';
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';
const optionText =
'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors';
import store from '~/store';
function Settings(props) {
const {
readonly,
model,
chatGptLabel,
promptPrefix,
temperature,
topP,
freqP,
presP,
setOption,
tools,
} = props;
const endpoint = 'gptPlugins';
const lang = useRecoilValue(store.lang);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const setModel = setOption('model');
const setChatGptLabel = setOption('chatGptLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('top_p');
const setFreqP = setOption('presence_penalty');
const setPresP = setOption('frequency_penalty');
const toolsSelected = tools?.length > 0;
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
return (
<div className="h-[490px] overflow-y-auto md:h-[350px]">
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<SelectDropDown
title={localize(lang, 'com_endpoint_completion_model')}
value={model}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(
defaultTextProps,
'flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
containerClassName="flex w-full resize-none"
/>
</div>
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_custom_name')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_empty')} |{' '}
{localize(lang, 'com_endpoint_disabled_with_tools')})
</small>
</Label>
<Input
id="chatGptLabel"
disabled={readonly || toolsSelected}
value={chatGptLabel || ''}
onChange={(e) => setChatGptLabel(e.target.value || null)}
placeholder={
toolsSelected
? localize(lang, 'com_endpoint_disabled_with_tools_placeholder')
: localize(lang, 'com_endpoint_openai_custom_name_placeholder')
}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_empty')} |{' '}
{localize(lang, 'com_endpoint_disabled_with_tools')})
</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly || toolsSelected}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value || null)}
placeholder={
toolsSelected
? localize(lang, 'com_endpoint_disabled_with_tools_placeholder')
: localize(
lang,
'com_endpoint_plug_set_custom_instructions_for_gpt_placeholder',
)
}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_temperature')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 0.8)})
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value)}
max={2}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(0.8)}
max={2}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="temp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_top_p')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 1)})
</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_frequency_penalty')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 0)})
</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="freq" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_presence_penalty')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_with_num', 0)})
</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="pres" side="left" />
</HoverCard>
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -1,3 +0,0 @@
export { default as AgentSettings } from './AgentSettings';
export { default as OptionHover } from './OptionHover';
export { default as Settings } from './Settings';

View file

@ -0,0 +1,66 @@
import { EModelEndpoint, PopoverButton } from 'librechat-data-provider';
import { MessagesSquared, GPTIcon } from '~/components/svg';
import { useRecoilState } from 'recoil';
import { Button } from '~/components';
import { cn } from '~/utils/';
import store from '~/store';
export default function PopoverButtons({
endpoint,
buttonClass,
iconClass = '',
}: {
endpoint: EModelEndpoint;
buttonClass?: string;
iconClass?: string;
}) {
const [optionSettings, setOptionSettings] = useRecoilState(store.optionSettings);
const [showAgentSettings, setShowAgentSettings] = useRecoilState(store.showAgentSettings);
const { showExamples, isCodeChat } = optionSettings;
const triggerExamples = () =>
setOptionSettings((prev) => ({ ...prev, showExamples: !prev.showExamples }));
const buttons: { [key: string]: PopoverButton[] } = {
google: [
{
label: (showExamples ? 'Hide' : 'Show') + ' Examples',
buttonClass: isCodeChat ? 'disabled' : '',
handler: triggerExamples,
icon: <MessagesSquared className={cn('mr-1 w-[14px]', iconClass)} />,
},
],
gptPlugins: [
{
label: `Show ${showAgentSettings ? 'Completion' : 'Agent'} Settings`,
buttonClass: '',
handler: () => setShowAgentSettings((prev) => !prev),
icon: <GPTIcon className={cn('mr-1 mt-[2px] w-[14px]', iconClass)} size={14} />,
},
],
};
const endpointButtons = buttons[endpoint];
if (!endpointButtons) {
return null;
}
return (
<div>
{endpointButtons.map((button, index) => (
<Button
key={`${endpoint}-button-${index}`}
type="button"
className={cn(
button.buttonClass,
'ml-1 h-auto justify-start bg-transparent px-2 py-1 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',
buttonClass ?? '',
)}
onClick={button.handler}
>
{button.icon}
{button.label}
</Button>
))}
</div>
);
}

View file

@ -1,66 +0,0 @@
import React, { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Dialog, DialogTemplate, Input, Label } from '../ui/';
import { cn } from '~/utils/';
import cleanupPreset from '~/utils/cleanupPreset';
import { useCreatePresetMutation } from 'librechat-data-provider';
import store from '~/store';
import { localize } from '~/localization/Translation';
const SaveAsPresetDialog = ({ open, onOpenChange, preset }) => {
const [title, setTitle] = useState(preset?.title || 'My Preset');
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const createPresetMutation = useCreatePresetMutation();
const lang = useRecoilValue(store.lang);
const defaultTextProps =
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
const submitPreset = () => {
const _preset = cleanupPreset({
preset: {
...preset,
title,
},
endpointsConfig,
});
createPresetMutation.mutate(_preset);
};
useEffect(() => {
setTitle(preset?.title || localize(lang, 'com_endpoint_my_preset'));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={localize(lang, 'com_endpoint_save_as_preset')}
main={
<div className="grid w-full items-center gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_preset_name')}
</Label>
<Input
id="chatGptLabel"
value={title || ''}
onChange={(e) => setTitle(e.target.value || '')}
placeholder="Set a custom name for this preset"
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
)}
/>
</div>
}
selection={{
selectHandler: submitPreset,
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
selectText: 'Save',
}}
/>
</Dialog>
);
};
export default SaveAsPresetDialog;

View file

@ -0,0 +1,67 @@
import React, { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useCreatePresetMutation, EditPresetProps, TPreset } from 'librechat-data-provider';
import { Dialog, Input, Label } from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/';
import { useLocalize } from '~/hooks';
import store from '~/store';
const SaveAsPresetDialog = ({ open, onOpenChange, preset }: EditPresetProps) => {
const [title, setTitle] = useState(preset?.title || 'My Preset');
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const createPresetMutation = useCreatePresetMutation();
const localize = useLocalize();
const submitPreset = () => {
const _preset = cleanupPreset({
preset: {
...preset,
title,
},
endpointsConfig,
}) as TPreset;
createPresetMutation.mutate(_preset);
};
useEffect(() => {
setTitle(preset?.title || localize('com_endpoint_my_preset'));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={localize('com_endpoint_save_as_preset')}
className="w-full sm:w-1/4"
main={
<div className="flex w-full flex-col items-center gap-2">
<div className="grid w-full items-center gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize('com_endpoint_preset_name')}
</Label>
<Input
id="chatGptLabel"
value={title || ''}
onChange={(e) => setTitle(e.target.value || '')}
placeholder="Set a custom name for this preset"
className={cn(
defaultTextPropsLabel,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
</div>
}
selection={{
selectHandler: submitPreset,
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
selectText: 'Save',
}}
/>
</Dialog>
);
};
export default SaveAsPresetDialog;

View file

@ -1,85 +0,0 @@
import OpenAISettings from './OpenAI/Settings.jsx';
import BingAISettings from './BingAI/Settings.jsx';
import GoogleSettings from './Google/Settings.jsx';
import PluginsSettings from './Plugins/Settings.jsx';
import AnthropicSettings from './Anthropic/Settings.jsx';
// A preset dialog to show readonly preset values.
const Settings = ({ preset, ...props }) => {
const renderSettings = () => {
const { endpoint } = preset || {};
if (endpoint === 'openAI' || endpoint === 'azureOpenAI') {
return (
<OpenAISettings
model={preset?.model}
chatGptLabel={preset?.chatGptLabel}
promptPrefix={preset?.promptPrefix}
temperature={preset?.temperature}
topP={preset?.top_p}
freqP={preset?.presence_penalty}
presP={preset?.frequency_penalty}
{...props}
/>
);
} else if (endpoint === 'bingAI') {
return (
<BingAISettings
toneStyle={preset?.toneStyle}
context={preset?.context}
systemMessage={preset?.systemMessage}
jailbreak={preset?.jailbreak}
{...props}
/>
);
} else if (endpoint === 'google') {
return (
<GoogleSettings
model={preset?.model}
modelLabel={preset?.modelLabel}
promptPrefix={preset?.promptPrefix}
examples={preset?.examples}
temperature={preset?.temperature}
topP={preset?.topP}
topK={preset?.topK}
maxOutputTokens={preset?.maxOutputTokens}
edit={true}
{...props}
/>
);
} else if (endpoint === 'anthropic') {
return (
<AnthropicSettings
model={preset?.model}
modelLabel={preset?.modelLabel}
promptPrefix={preset?.promptPrefix}
temperature={preset?.temperature}
topP={preset?.topP}
topK={preset?.topK}
maxOutputTokens={preset?.maxOutputTokens}
edit={true}
{...props}
/>
);
} else if (endpoint === 'gptPlugins') {
return (
<PluginsSettings
model={preset?.model}
chatGptLabel={preset?.chatGptLabel}
promptPrefix={preset?.promptPrefix}
temperature={preset?.temperature}
topP={preset?.top_p}
freqP={preset?.presence_penalty}
presP={preset?.frequency_penalty}
{...props}
/>
);
} else {
return <div className="text-black dark:text-white">Not implemented</div>;
}
};
return renderSettings();
};
export default Settings;

View file

@ -0,0 +1,242 @@
import { ModelSelectProps, Side } from 'librechat-data-provider';
import {
Switch,
SelectDropDown,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components';
import OptionHover from './OptionHover';
import { cn, optionText, defaultTextProps, removeFocusOutlines } from '~/utils/';
import { useLocalize } from '~/hooks';
export default function Settings({ conversation, setOption, models, readonly }: ModelSelectProps) {
const localize = useLocalize();
if (!conversation) {
return null;
}
const { agent, skipCompletion, model, temperature } = conversation.agentOptions ?? {};
const setModel = setOption('model');
const setTemperature = setOption('temperature');
const setAgent = setOption('agent');
const setSkipCompletion = setOption('skipCompletion');
const onCheckedChangeAgent = (checked: boolean) => {
setAgent(checked ? 'functions' : 'classic');
};
const onCheckedChangeSkip = (checked: boolean) => {
setSkipCompletion(checked);
};
return (
<div className="grid grid-cols-5 gap-6">
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
<div className="grid w-full items-center gap-2">
<SelectDropDown
title={localize('com_endpoint_agent_model')}
value={model ?? ''}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)}
containerClassName="flex w-full resize-none"
/>
</div>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize('com_endpoint_temperature')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 0)</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(Number(value))}
max={2}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature ?? 0]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={2}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation.endpoint ?? ''} type="temp" side={Side.Left} />
</HoverCard>
<div className="grid w-full grid-cols-2 items-center gap-10">
<HoverCard openDelay={500}>
<HoverCardTrigger className="w-[100px]">
<label
htmlFor="functions-agent"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
<small>{localize('com_endpoint_plug_use_functions')}</small>
</label>
<Switch
id="functions-agent"
checked={agent === 'functions'}
onCheckedChange={onCheckedChangeAgent}
disabled={readonly}
className="ml-4 mt-2"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation.endpoint ?? ''} type="func" side={Side.Bottom} />
</HoverCard>
<HoverCard openDelay={500}>
<HoverCardTrigger className="ml-[-60px] w-[100px]">
<label
htmlFor="skip-completion"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
<small>{localize('com_endpoint_plug_skip_completion')}</small>
</label>
<Switch
id="skip-completion"
checked={skipCompletion === true}
onCheckedChange={onCheckedChangeSkip}
disabled={readonly}
className="ml-4 mt-2"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation.endpoint ?? ''} type="skip" side={Side.Bottom} />
</HoverCard>
</div>
{/* <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
Top P <small className="opacity-40">(default: 1)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
Frequency Penalty <small className="opacity-40">(default: 0)</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="freq" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
Presence Penalty <small className="opacity-40">(default: 0)</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="pres" side="left" />
</HoverCard> */}
</div>
</div>
);
}

View file

@ -0,0 +1,235 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { ModelSelectProps, Side } from 'librechat-data-provider';
import {
Input,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
SelectDropDown,
} from '~/components/ui';
import OptionHover from './OptionHover';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
export default function Settings({ conversation, setOption, models, readonly }: ModelSelectProps) {
if (!conversation) {
return null;
}
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } =
conversation;
const setModel = setOption('model');
const setModelLabel = setOption('modelLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('topP');
const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens');
return (
<div className="grid grid-cols-5 gap-6">
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
<div className="grid w-full items-center gap-2">
<SelectDropDown
value={model ?? ''}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(defaultTextProps, 'z-50 flex w-full resize-none', removeFocusOutlines)}
containerClassName="flex w-full resize-none"
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
Custom Name <small className="opacity-40">(default: blank)</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value ?? null)}
placeholder="Set a custom name for Claude"
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
Prompt Prefix <small className="opacity-40">(default: blank)</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
placeholder="Set custom instructions or context. Ignored if empty."
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
Temperature <small className="opacity-40">(default: 1)</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(Number(value))}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature ?? 1]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
Top P <small className="opacity-40">(default: 0.7)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(Number(value))}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP ?? 0.7]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
Top K <small className="opacity-40">(default: 5)</small>
</Label>
<InputNumber
id="top-k-int"
disabled={readonly}
value={topK}
onChange={(value) => setTopK(Number(value))}
max={40}
min={1}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topK ?? 5]}
onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(0)}
max={40}
min={1}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topk" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
Max Output Tokens <small className="opacity-40">(default: 1024)</small>
</Label>
<InputNumber
id="max-tokens-int"
disabled={readonly}
value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(Number(value))}
max={1024}
min={1}
step={1}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[maxOutputTokens ?? 1024]}
onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(0)}
max={1024}
min={1}
step={1}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover
endpoint={conversation?.endpoint ?? ''}
type="maxoutputtokens"
side={Side.Left}
/>
</HoverCard>
</div>
</div>
);
}

View file

@ -0,0 +1,149 @@
import { useEffect, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import {
useUpdateTokenCountMutation,
TUpdateTokenCountResponse,
SettingsProps,
} from 'librechat-data-provider';
import { Label, Checkbox, SelectDropDown } from '~/components/ui';
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils/';
import { useLocalize, useDebounce } from '~/hooks';
export default function Settings({ conversation, setOption, readonly }: SettingsProps) {
const localize = useLocalize();
const [tokenCount, setTokenCount] = useState(0);
const debouncedContext = useDebounce(conversation?.context?.trim() ?? '', 250);
const updateTokenCountMutation = useUpdateTokenCountMutation();
useEffect(() => {
if (!debouncedContext || debouncedContext === '') {
setTokenCount(0);
return;
}
const handleTextChange = (context: string) => {
updateTokenCountMutation.mutate(
{ text: context },
{
onSuccess: (data: TUpdateTokenCountResponse) => {
setTokenCount(data.count);
},
},
);
};
handleTextChange(debouncedContext);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedContext]);
if (!conversation) {
return null;
}
const { context, systemMessage, jailbreak, toneStyle } = conversation;
const showSystemMessage = jailbreak;
const setContext = setOption('context');
const setSystemMessage = setOption('systemMessage');
const setJailbreak = setOption('jailbreak');
const setToneStyle = (value: string) => setOption('toneStyle')(value.toLowerCase());
return (
<div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<Label htmlFor="toneStyle-dropdown" className="text-left text-sm font-medium">
{localize('com_endpoint_tone_style')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_creative')})</small>
</Label>
<SelectDropDown
id="toneStyle-dropdown"
title={''}
value={`${toneStyle?.charAt(0).toUpperCase()}${toneStyle?.slice(1)}`}
setValue={setToneStyle}
availableValues={['Creative', 'Fast', 'Balanced', 'Precise']}
disabled={readonly}
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)}
containerClassName="flex w-full resize-none"
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="context" className="text-left text-sm font-medium">
{localize('com_endpoint_context')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id="context"
disabled={readonly}
value={context || ''}
onChange={(e) => setContext(e.target.value ?? null)}
placeholder={localize('com_endpoint_bing_context_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2',
)}
/>
<small className="mb-5 text-black dark:text-white">{`${localize(
'com_endpoint_token_count',
)}: ${tokenCount}`}</small>
</div>
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2">
<Label htmlFor="jailbreak" className="text-left text-sm font-medium">
{localize('com_endpoint_bing_enable_sydney')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_false')})</small>
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="jailbreak"
disabled={readonly}
checked={jailbreak}
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
onCheckedChange={setJailbreak}
/>
<label
htmlFor="jailbreak"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{localize('com_endpoint_bing_jailbreak')}{' '}
<small>{localize('com_endpoint_bing_to_enable_sydney')}</small>
</label>
</div>
</div>
{showSystemMessage && (
<div className="grid w-full items-center gap-2">
<Label
htmlFor="systemMessage"
className="text-left text-sm font-medium"
style={{ opacity: showSystemMessage ? '1' : '0' }}
>
<a
href="https://github.com/danny-avila/LibreChat/blob/main/docs/features/bing_jailbreak.md#default-system-message-for-jailbreak-mode-sydney"
target="_blank"
className="text-blue-500 transition-colors duration-200 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-500"
rel="noreferrer"
>
{localize('com_endpoint_system_message')}
</a>{' '}
<small className="opacity-40 dark:text-gray-50">
( {localize('com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id="systemMessage"
disabled={readonly}
value={systemMessage || ''}
onChange={(e) => setSystemMessage(e.target.value ?? null)}
placeholder={localize('com_endpoint_bing_system_message_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 placeholder:text-red-400',
)}
/>
</div>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,88 @@
import React from 'react';
import { Plus, Minus } from 'lucide-react';
import TextareaAutosize from 'react-textarea-autosize';
import { ExamplesProps } from 'librechat-data-provider';
import { Button, Label } from '~/components/ui';
import { cn, defaultTextProps } from '~/utils/';
import { useLocalize } from '~/hooks';
function Examples({ readonly, examples, setExample, addExample, removeExample }: ExamplesProps) {
const localize = useLocalize();
return (
<>
<div id="examples-grid" className="grid gap-6 sm:grid-cols-2">
{examples.map((example, idx) => (
<React.Fragment key={idx}>
{/* Input */}
<div
className={`col-span-${
examples.length === 1 ? '1' : 'full'
} flex flex-col items-center justify-start gap-6 sm:col-span-1`}
>
<div className="grid w-full items-center gap-2">
<Label htmlFor={`input-${idx}`} className="text-left text-sm font-medium">
{localize('com_ui_input')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id={`input-${idx}`}
disabled={readonly}
value={example?.input?.content || ''}
onChange={(e) => setExample(idx, 'input', e.target.value ?? null)}
placeholder="Set example input. Example is ignored if empty."
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[75px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
{/* Output */}
<div
className={`col-span-${
examples.length === 1 ? '1' : 'full'
} flex flex-col items-center justify-start gap-6 sm:col-span-1`}
>
<div className="grid w-full items-center gap-2">
<Label htmlFor={`output-${idx}`} className="text-left text-sm font-medium">
{localize('com_endpoint_output')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id={`output-${idx}`}
disabled={readonly}
value={example?.output?.content || ''}
onChange={(e) => setExample(idx, 'output', e.target.value ?? null)}
placeholder={'Set example output. Example is ignored if empty.'}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[75px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
</React.Fragment>
))}
</div>
<div className="flex justify-center">
<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"
onClick={removeExample}
>
<Minus className="w-[16px]" />
</Button>
<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"
onClick={addExample}
>
<Plus className="w-[16px]" />
</Button>
</div>
</>
);
}
export default Examples;

View file

@ -0,0 +1,259 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { ModelSelectProps, Side } from 'librechat-data-provider';
import {
SelectDropDown,
Input,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components/ui';
import OptionHover from './OptionHover';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
import { useLocalize } from '~/hooks';
export default function Settings({ conversation, setOption, models, readonly }: ModelSelectProps) {
const localize = useLocalize();
if (!conversation) {
return null;
}
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } =
conversation;
const setModel = setOption('model');
const setModelLabel = setOption('modelLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('topP');
const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens');
const codeChat = model?.startsWith('codechat-');
return (
<div className="grid grid-cols-5 gap-6">
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
<div className="grid w-full items-center gap-2">
<SelectDropDown
value={model ?? ''}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(defaultTextProps, 'z-50 flex w-full resize-none', removeFocusOutlines)}
containerClassName="flex w-full resize-none"
/>
</div>
{!codeChat && (
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
{localize('com_endpoint_custom_name')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value ?? null)}
placeholder={localize('com_endpoint_google_custom_name_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize('com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
placeholder={localize('com_endpoint_google_prompt_prefix_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
)}
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize('com_endpoint_temperature')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 0.2)</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value ?? 0.2)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature ?? 0.2]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(0.2)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={Side.Left} />
</HoverCard>
{!codeChat && (
<>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_p')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '0.95')})
</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value ?? '0.95')}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP ?? 0.95]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(0.95)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_k')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '40')})
</small>
</Label>
<InputNumber
id="top-k-int"
disabled={readonly}
value={topK}
onChange={(value) => setTopK(value ?? 40)}
max={40}
min={1}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topK ?? 40]}
onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(40)}
max={40}
min={1}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topk" side={Side.Left} />
</HoverCard>
</>
)}
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
{localize('com_endpoint_max_output_tokens')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '1024')})
</small>
</Label>
<InputNumber
id="max-tokens-int"
disabled={readonly}
value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(value ?? 1024)}
max={1024}
min={1}
step={1}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[maxOutputTokens ?? 1024]}
onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(1024)}
max={1024}
min={1}
step={1}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover
endpoint={conversation?.endpoint ?? ''}
type="maxoutputtokens"
side={Side.Left}
/>
</HoverCard>
</div>
</div>
);
}

View file

@ -0,0 +1,28 @@
import Settings from '../Google';
import Examples from '../Examples';
import { useSetOptions } from '~/hooks';
import { useRecoilValue } from 'recoil';
import store from '~/store';
export default function GoogleView({ conversation, models, isPreset = false }) {
const optionSettings = useRecoilValue(store.optionSettings);
const { setOption, setExample, addExample, removeExample } = useSetOptions(
isPreset ? conversation : null,
);
if (!conversation) {
return null;
}
const { examples } = conversation;
const { showExamples, isCodeChat } = optionSettings;
return showExamples && !isCodeChat ? (
<Examples
examples={examples ?? []}
setExample={setExample}
addExample={addExample}
removeExample={removeExample}
/>
) : (
<Settings conversation={conversation} setOption={setOption} models={models} />
);
}

View file

@ -0,0 +1,19 @@
import Settings from '../Plugins';
import AgentSettings from '../AgentSettings';
import { useSetOptions } from '~/hooks';
import { useRecoilValue } from 'recoil';
import store from '~/store';
export default function PluginsView({ conversation, models, isPreset = false }) {
const showAgentSettings = useRecoilValue(store.showAgentSettings);
const { setOption, setAgentOption } = useSetOptions(isPreset ? conversation : null);
if (!conversation) {
return null;
}
return showAgentSettings ? (
<AgentSettings conversation={conversation} setOption={setAgentOption} models={models} />
) : (
<Settings conversation={conversation} setOption={setOption} models={models} />
);
}

View file

@ -0,0 +1,2 @@
export { default as GoogleSettings } from './Google';
export { default as PluginsSettings } from './Plugins';

View file

@ -0,0 +1,254 @@
import TextareaAutosize from 'react-textarea-autosize';
import { ModelSelectProps, Side } from 'librechat-data-provider';
import {
SelectDropDown,
Input,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components/ui';
import OptionHover from './OptionHover';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
import { useLocalize } from '~/hooks';
export default function Settings({ conversation, setOption, models, readonly }: ModelSelectProps) {
const localize = useLocalize();
if (!conversation) {
return null;
}
const {
model,
chatGptLabel,
promptPrefix,
temperature,
top_p: topP,
frequency_penalty: freqP,
presence_penalty: presP,
} = conversation;
const endpoint = conversation.endpoint || 'openAI';
const isOpenAI = endpoint === 'openAI' || endpoint === 'azureOpenAI';
const setModel = setOption('model');
const setChatGptLabel = setOption('chatGptLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('top_p');
const setFreqP = setOption('frequency_penalty');
const setPresP = setOption('presence_penalty');
return (
<div className="grid grid-cols-5 gap-6">
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
<div className="grid w-full items-center gap-2">
<SelectDropDown
value={model ?? ''}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)}
containerClassName="flex w-full resize-none"
/>
</div>
{isOpenAI && (
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize('com_endpoint_custom_name')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<Input
id="chatGptLabel"
disabled={readonly}
value={chatGptLabel || ''}
onChange={(e) => setChatGptLabel(e.target.value ?? null)}
placeholder={localize('com_endpoint_openai_custom_name_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize('com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
placeholder={localize('com_endpoint_openai_prompt_prefix_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
)}
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize('com_endpoint_temperature')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', isOpenAI ? '1' : '0')})
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(Number(value))}
max={2}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature ?? 1]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={2}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_p')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 1)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(Number(value))}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP ?? 1]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
{localize('com_endpoint_frequency_penalty')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 0)</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(Number(value))}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP ?? 0]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="freq" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
{localize('com_endpoint_presence_penalty')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 0)</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(Number(value))}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP ?? 0]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="pres" side={Side.Left} />
</HoverCard>
</div>
</div>
);
}

View file

@ -0,0 +1,53 @@
import React from 'react';
import { HoverCardPortal, HoverCardContent } from '~/components/ui';
import { OptionHoverProps } from 'librechat-data-provider';
import { useLocalize } from '~/hooks';
const openAI = {
max: 'com_endpoint_openai_max',
temp: 'com_endpoint_openai_temp',
topp: 'com_endpoint_openai_topp',
freq: 'com_endpoint_openai_freq',
pres: 'com_endpoint_openai_pres',
};
const types = {
anthropic: {
temp: 'com_endpoint_anthropic_temp',
topp: 'com_endpoint_anthropic_topp',
topk: 'com_endpoint_anthropic_topk',
maxoutputtokens: 'com_endpoint_anthropic_maxoutputtokens',
},
google: {
temp: 'com_endpoint_google_temp',
topp: 'com_endpoint_google_topp',
topk: 'com_endpoint_google_topk',
maxoutputtokens: 'com_endpoint_google_maxoutputtokens',
},
openAI,
azureOpenAI: openAI,
gptPlugins: {
func: 'com_endpoint_func_hover',
skip: 'com_endpoint_skip_hover',
...openAI,
},
};
function OptionHover({ endpoint, type, side }: OptionHoverProps) {
const localize = useLocalize();
const text = types?.[endpoint]?.[type];
if (!text) {
return null;
}
return (
<HoverCardPortal>
<HoverCardContent side={side} className="w-80 ">
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(text)}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View file

@ -0,0 +1,274 @@
import TextareaAutosize from 'react-textarea-autosize';
import {
SelectDropDown,
Input,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components';
import OptionHover from './OptionHover';
import { ModelSelectProps, Side } from 'librechat-data-provider';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
import { useLocalize } from '~/hooks';
export default function Settings({ conversation, setOption, models, readonly }: ModelSelectProps) {
const localize = useLocalize();
if (!conversation) {
return null;
}
const {
model,
chatGptLabel,
promptPrefix,
temperature,
top_p: topP,
frequency_penalty: freqP,
presence_penalty: presP,
tools,
} = conversation;
const setModel = setOption('model');
const setChatGptLabel = setOption('chatGptLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('top_p');
const setFreqP = setOption('presence_penalty');
const setPresP = setOption('frequency_penalty');
const toolsSelected = tools && tools.length > 0;
return (
<div className="grid grid-cols-5 gap-6">
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
<div className="grid w-full items-center gap-2">
<SelectDropDown
title={localize('com_endpoint_completion_model')}
value={model ?? ''}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)}
containerClassName="flex w-full resize-none"
/>
</div>
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
{localize('com_endpoint_custom_name')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_empty')} |{' '}
{localize('com_endpoint_disabled_with_tools')})
</small>
</Label>
<Input
id="chatGptLabel"
disabled={readonly || toolsSelected}
value={chatGptLabel || ''}
onChange={(e) => setChatGptLabel(e.target.value ?? null)}
placeholder={
toolsSelected
? localize('com_endpoint_disabled_with_tools_placeholder')
: localize('com_endpoint_openai_custom_name_placeholder')
}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize('com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_empty')} |{' '}
{localize('com_endpoint_disabled_with_tools')})
</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly || toolsSelected}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
placeholder={
toolsSelected
? localize('com_endpoint_disabled_with_tools_placeholder')
: localize('com_endpoint_plug_set_custom_instructions_for_gpt_placeholder')
}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize('com_endpoint_temperature')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '0.8')})
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(Number(value))}
max={2}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature ?? 0.8]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(0.8)}
max={2}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_p')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '1')})
</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(Number(value))}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP ?? 1]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topp" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
{localize('com_endpoint_frequency_penalty')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '0')})
</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(Number(value))}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP ?? 0]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="freq" side={Side.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
{localize('com_endpoint_presence_penalty')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '0')})
</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(Number(value))}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP ?? 0]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="pres" side={Side.Left} />
</HoverCard>
</div>
</div>
);
}

View file

@ -0,0 +1,7 @@
export { default as OpenAISettings } from './OpenAI';
export { default as BingAISettings } from './BingAI';
export { default as GoogleSettings } from './Google';
export { default as PluginsSettings } from './Plugins';
export { default as Examples } from './Examples';
export { default as AgentSettings } from './AgentSettings';
export { default as AnthropicSettings } from './Anthropic';

View file

@ -0,0 +1,114 @@
import { Plugin, GPTIcon, AnthropicIcon } from '~/components/svg';
import { useAuthContext } from '~/hooks';
import { cn } from '~/utils';
const getIcon = (props) => {
const { size = 30, isCreatedByUser, button, model, message = true } = props;
// eslint-disable-next-line react-hooks/rules-of-hooks
const { user } = useAuthContext();
if (isCreatedByUser) {
return (
<div
title={user?.name || 'User'}
style={{
width: size,
height: size,
}}
className={'relative flex items-center justify-center' + props?.className}
>
<img
className="rounded-sm"
src={
user?.avatar ||
`https://api.dicebear.com/6.x/initials/svg?seed=${
user?.name || 'User'
}&fontFamily=Verdana&fontSize=36`
}
alt="avatar"
/>
</div>
);
} else if (!isCreatedByUser) {
const { endpoint, error } = props;
let icon, bg, name;
if (endpoint === 'azureOpenAI') {
const { chatGptLabel } = props;
icon = <GPTIcon size={size * 0.7} />;
bg = 'linear-gradient(0.375turn, #61bde2, #4389d0)';
name = chatGptLabel || 'ChatGPT';
} else if (endpoint === 'openAI' || (endpoint === 'gptPlugins' && message)) {
const { chatGptLabel } = props;
icon = <GPTIcon size={size * 0.7} />;
bg =
model && model.toLowerCase().startsWith('gpt-4')
? '#AB68FF'
: chatGptLabel
? `rgba(16, 163, 127, ${button ? 0.75 : 1})`
: `rgba(16, 163, 127, ${button ? 0.75 : 1})`;
name = chatGptLabel || 'ChatGPT';
} else if (endpoint === 'gptPlugins' && !message) {
icon = <Plugin size={size * 0.7} />;
bg = `rgba(69, 89, 164, ${button ? 0.75 : 1})`;
name = 'Plugins';
} else if (endpoint === 'google') {
const { modelLabel } = props;
icon = <img src="/assets/google-palm.svg" alt="Palm Icon" />;
name = modelLabel || 'PaLM2';
} else if (endpoint === 'anthropic') {
const { modelLabel } = props;
icon = <AnthropicIcon size={size * 0.7} />;
bg = '#d09a74';
name = modelLabel || 'Claude';
} else if (endpoint === 'bingAI') {
const { jailbreak } = props;
if (jailbreak) {
icon = <img src="/assets/bingai-jb.png" alt="Bing Icon" />;
name = 'Sydney';
} else {
icon = <img src="/assets/bingai.png" alt="Sydney Icon" />;
name = 'BingAI';
}
} else if (endpoint === 'chatGPTBrowser') {
icon = <GPTIcon size={size * 0.7} />;
bg =
model && model.toLowerCase().startsWith('gpt-4')
? '#AB68FF'
: `rgba(0, 163, 255, ${button ? 0.75 : 1})`;
name = 'ChatGPT';
} else if (endpoint === null) {
icon = <GPTIcon size={size * 0.7} />;
bg = 'grey';
name = 'N/A';
} else {
icon = <GPTIcon size={size * 0.7} />;
bg = 'grey';
name = 'UNKNOWN';
}
return (
<div
title={name}
style={{
background: bg || 'transparent',
width: size,
height: size,
}}
className={cn(
'relative flex items-center justify-center rounded-sm text-white ',
props?.className ?? '',
)}
>
{icon}
{error && (
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
!
</span>
)}
</div>
);
}
};
export default getIcon;

View file

@ -0,0 +1,6 @@
export { default as getIcon } from './getIcon';
export { default as EndpointSettings } from './EndpointSettings';
export { default as EditPresetDialog } from './EditPresetDialog';
export { default as SaveAsPresetDialog } from './SaveAsPresetDialog';
export { default as EndpointOptionsDialog } from './EndpointOptionsDialog';
export { default as EndpointOptionsPopover } from './EndpointOptionsPopover';

View file

@ -1,18 +0,0 @@
import React from 'react';
import { Settings2 } from 'lucide-react';
export default function AdjustToneButton({ onClick }) {
const clickHandler = (e) => {
e.preventDefault();
onClick();
};
return (
<button
onClick={clickHandler}
className="group absolute bottom-11 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500 lg:-right-11 lg:bottom-0"
>
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
<Settings2 size="1em" />
</div>
</button>
);
}

View file

@ -1,108 +0,0 @@
import { useState } from 'react';
import { Settings2 } from 'lucide-react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { SelectDropDown, Button } from '~/components';
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
import Settings from '../../Endpoints/Anthropic/Settings.jsx';
import { cn } from '~/utils/';
import store from '~/store';
function AnthropicOptions() {
const [advancedMode, setAdvancedMode] = useState(false);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const { endpoint } = conversation;
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } =
conversation;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (endpoint !== 'anthropic') {
return null;
}
const models = endpointsConfig?.['anthropic']?.['availableModels'] || [];
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
const switchToSimpleMode = () => {
setAdvancedMode(false);
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const cardStyle =
'transition-colors shadow-md 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';
return (
<>
<div
className={
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
(!advancedMode ? ' show' : '')
}
>
<SelectDropDown
value={model}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 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-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
</div>
<EndpointOptionsPopover
content={
<div className="px-4 py-4">
<Settings
model={model}
modelLabel={modelLabel}
promptPrefix={promptPrefix}
temperature={temperature}
topP={topP}
topK={topK}
maxOutputTokens={maxOutputTokens}
setOption={setOption}
/>
</div>
}
visible={advancedMode}
saveAsPreset={saveAsPreset}
switchToSimpleMode={switchToSimpleMode}
/>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={conversation}
/>
</>
);
}
export default AnthropicOptions;

View file

@ -1,152 +0,0 @@
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { cn } from '~/utils';
import { Button } from '../../ui/Button.tsx';
import { Settings2 } from 'lucide-react';
import { Tabs, TabsList, TabsTrigger } from '../../ui/Tabs.tsx';
import SelectDropDown from '../../ui/SelectDropDown';
import Settings from '../../Endpoints/BingAI/Settings.jsx';
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
import store from '~/store';
function BingAIOptions({ show }) {
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const [advancedMode, setAdvancedMode] = useState(false);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const { endpoint, conversationId } = conversation;
const { toneStyle, context, systemMessage, jailbreak } = conversation;
if (endpoint !== 'bingAI') {
return null;
}
if (conversationId !== 'new' && !show) {
return null;
}
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
const switchToSimpleMode = () => {
setAdvancedMode(false);
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const cardStyle =
'transition-colors shadow-md 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';
const defaultClasses =
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
const defaultSelected = cn(
defaultClasses,
'font-medium data-[state=active]:text-white text-xs text-white',
);
const selectedClass = (val) => val + '-tab ' + defaultSelected;
return (
<>
<div
className={
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
(!advancedMode ? ' show' : '')
}
>
<SelectDropDown
title="Mode"
value={jailbreak ? 'Sydney' : 'BingAI'}
data-testid="bing-select-dropdown"
setValue={(value) => setOption('jailbreak')(value === 'Sydney')}
availableValues={['BingAI', 'Sydney']}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-36 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-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
show ? 'hidden' : null,
)}
/>
<Tabs
value={toneStyle}
className={
cardStyle +
' z-50 flex h-[40px] flex-none items-center justify-center px-0 hover:bg-slate-50 dark:hover:bg-gray-600'
}
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
>
<TabsList className="bg-white/[.60] dark:bg-gray-700">
<TabsTrigger
value="creative"
className={`${toneStyle === 'creative' ? selectedClass('creative') : defaultClasses}`}
>
{'Creative'}
</TabsTrigger>
<TabsTrigger
value="fast"
className={`${toneStyle === 'fast' ? selectedClass('fast') : defaultClasses}`}
>
{'Fast'}
</TabsTrigger>
<TabsTrigger
value="balanced"
className={`${toneStyle === 'balanced' ? selectedClass('balanced') : defaultClasses}`}
>
{'Balanced'}
</TabsTrigger>
<TabsTrigger
value="precise"
className={`${toneStyle === 'precise' ? selectedClass('precise') : defaultClasses}`}
>
{'Precise'}
</TabsTrigger>
</TabsList>
</Tabs>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
show ? 'hidden' : null,
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
</div>
<EndpointOptionsPopover
content={
<div className="z-50 px-4 py-4">
<Settings
context={context}
systemMessage={systemMessage}
jailbreak={jailbreak}
toneStyle={toneStyle}
setOption={setOption}
/>
</div>
}
visible={advancedMode}
saveAsPreset={saveAsPreset}
switchToSimpleMode={switchToSimpleMode}
/>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={conversation}
/>
</>
);
}
export default BingAIOptions;

View file

@ -1,52 +0,0 @@
import { useRecoilState, useRecoilValue } from 'recoil';
import SelectDropDown from '../../ui/SelectDropDown';
import { cn } from '~/utils/';
import store from '~/store';
function ChatGPTOptions() {
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const { endpoint, conversationId } = conversation;
const { model } = conversation;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (endpoint !== 'chatGPTBrowser') {
return null;
}
if (conversationId !== 'new') {
return null;
}
const models = endpointsConfig?.['chatGPTBrowser']?.['availableModels'] || [];
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const cardStyle =
'transition-colors shadow-md 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';
return (
<div className="openAIOptions-simple-container show flex w-full flex-wrap items-center justify-center gap-2">
<SelectDropDown
value={model}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'z-50 flex h-[40px] w-[260px] min-w-[260px] 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-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
</div>
);
}
export default ChatGPTOptions;

View file

@ -1,8 +1,8 @@
import { useState } from 'react';
import { DropdownMenuRadioItem } from '~/components';
import { Settings } from 'lucide-react';
import getIcon from '~/utils/getIcon';
import { useRecoilValue } from 'recoil';
import { Settings } from 'lucide-react';
import { DropdownMenuRadioItem } from '~/components';
import { getIcon } from '~/components/Endpoints';
import { SetTokenDialog } from '../SetTokenDialog';
import store from '~/store';

View file

@ -1,4 +1,4 @@
import EndpointItem from './EndpointItem.jsx';
import EndpointItem from './EndpointItem';
export default function EndpointItems({ endpoints, onSelect, selectedEndpoint }) {
return (

View file

@ -1,15 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { Trash2 } from 'lucide-react';
import { useState, useEffect } from 'react';
import cleanupPreset from '~/utils/cleanupPreset.js';
import { useRecoilValue, useRecoilState } from 'recoil';
import EditPresetDialog from '../../Endpoints/EditPresetDialog';
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider';
import { getIcon, EditPresetDialog } from '~/components/Endpoints';
import EndpointItems from './EndpointItems';
import PresetItems from './PresetItems';
import { Trash2 } from 'lucide-react';
import FileUpload from './FileUpload';
import getIcon from '~/utils/getIcon';
import getDefaultConversation from '~/utils/getDefaultConversation';
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider';
import {
Button,
DropdownMenu,
@ -18,11 +15,11 @@ import {
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuTrigger,
DialogTemplate,
Dialog,
DialogTrigger,
} from '../../ui/';
import { cn } from '~/utils/';
} from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { cn, cleanupPreset, getDefaultConversation } from '~/utils';
import store from '~/store';
@ -156,7 +153,7 @@ export default function NewConversationMenu() {
id="new-conversation-menu"
variant="outline"
className={
'group relative mb-[-12px] ml-0 mt-[-8px] items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-[-12px] md:pl-1'
'group relative mb-[-12px] ml-1 mt-[-8px] items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-0 md:ml-[-12px] md:pl-1'
}
>
{icon}
@ -166,7 +163,7 @@ export default function NewConversationMenu() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="z-[100] w-96 dark:bg-gray-900"
className="z-[100] w-[375px] dark:bg-gray-900 md:w-96"
onCloseAutoFocus={(event) => event.preventDefault()}
>
<DropdownMenuLabel

View file

@ -7,7 +7,7 @@ type FileUploadProps = {
className?: string;
successText?: string;
invalidText?: string;
validator?: ((data: any) => boolean) | null;
validator?: ((data: Record<string, unknown>) => boolean) | null;
text?: string;
id?: string;
};

View file

@ -1,7 +1,5 @@
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
import EditIcon from '../../svg/EditIcon.jsx';
import TrashIcon from '../../svg/TrashIcon.jsx';
import getIcon from '~/utils/getIcon';
import { DropdownMenuRadioItem, EditIcon, TrashIcon } from '~/components';
import { getIcon } from '~/components/Endpoints';
export default function PresetItem({ preset = {}, value, onChangePreset, onDeletePreset }) {
const { endpoint } = preset;
@ -63,30 +61,35 @@ export default function PresetItem({ preset = {}, value, onChangePreset, onDelet
return (
<DropdownMenuRadioItem
value={value}
className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
className="group flex h-10 max-h-[44px] flex-row justify-between dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800 sm:h-auto"
>
{icon}
<small className="text-[11px]">{preset?.title}</small>
<small className="ml-2 text-[10px]">({getPresetTitle()})</small>
<div className="flex w-4 flex-1" />
<button
className="invisible m-0 mr-1 rounded-md p-2 text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
onClick={(e) => {
e.preventDefault();
onChangePreset(preset);
}}
>
<EditIcon />
</button>
<button
className="invisible m-0 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
onClick={(e) => {
e.preventDefault();
onDeletePreset(preset);
}}
>
<TrashIcon />
</button>
<div className="flex items-center justify-start">
{icon}
<small className="text-[11px]">{preset?.title}</small>
<small className="invisible ml-1 flex w-0 flex-shrink text-[10px] sm:visible sm:w-auto">
({getPresetTitle()})
</small>
</div>
<div className="flex h-full items-center justify-end">
<button
className="m-0 mr-1 h-full rounded-md 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:p-2 sm:group-hover:visible"
onClick={(e) => {
e.preventDefault();
onChangePreset(preset);
}}
>
<EditIcon />
</button>
<button
className="m-0 h-full rounded-md 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:p-2 sm:group-hover:visible"
onClick={(e) => {
e.preventDefault();
onDeletePreset(preset);
}}
>
<TrashIcon />
</button>
</div>
</DropdownMenuRadioItem>
);
}

View file

@ -1,5 +1,5 @@
import React from 'react';
import PresetItem from './PresetItem.jsx';
import PresetItem from './PresetItem';
export default function PresetItems({ presets, onSelect, onChangePreset, onDeletePreset }) {
return (

View file

@ -0,0 +1 @@
export { default as EndpointMenu } from './EndpointMenu';

View file

@ -0,0 +1,47 @@
import { cn, removeFocusOutlines } from '~/utils/';
type GenerationButtonsProps = {
showPopover: boolean;
opacityClass: string;
};
export default function GenerationButtons({ showPopover, opacityClass }: GenerationButtonsProps) {
return (
<div className="absolute bottom-4 right-0 z-[62]">
<div className="grow"></div>
<div className="flex items-center md:items-end">
<div
className={cn('option-buttons', showPopover ? '' : opacityClass)}
data-projection-id="173"
>
{/* <button
className={cn(
'custom-btn btn-neutral relative -z-0 whitespace-nowrap border-0 md:border',
removeFocusOutlines,
)}
>
<div className="flex w-full items-center justify-center gap-2">
<svg
stroke="currentColor"
fill="none"
strokeWidth="1.5"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="h-3 w-3 flex-shrink-0"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<polyline points="1 4 1 10 7 10"></polyline>
<polyline points="23 20 23 14 17 14"></polyline>
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
</svg>
Regenerate
</div>
</button> */}
</div>
</div>
</div>
);
}

View file

@ -1,171 +0,0 @@
import { useState } from 'react';
import { Settings2 } from 'lucide-react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { SelectDropDown, Button, MessagesSquared } from '~/components';
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
import Settings from '../../Endpoints/Google/Settings.jsx';
import Examples from '../../Endpoints/Google/Examples.jsx';
import { cn } from '~/utils/';
import store from '~/store';
function GoogleOptions() {
const [advancedMode, setAdvancedMode] = useState(false);
const [showExamples, setShowExamples] = useState(false);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const { endpoint } = conversation;
const { model, modelLabel, promptPrefix, examples, temperature, topP, topK, maxOutputTokens } =
conversation;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (endpoint !== 'google') {
return null;
}
const models = endpointsConfig?.['google']?.['availableModels'] || [];
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
const triggerExamples = () => setShowExamples((prev) => !prev);
const switchToSimpleMode = () => {
setAdvancedMode(false);
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const setExample = (i, type, newValue = null) => {
let update = {};
let current = conversation?.examples.slice() || [];
let currentExample = { ...current[i] } || {};
currentExample[type] = { content: newValue };
current[i] = currentExample;
update.examples = current;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const addExample = () => {
let update = {};
let current = conversation?.examples.slice() || [];
current.push({ input: { content: '' }, output: { content: '' } });
update.examples = current;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const removeExample = () => {
let update = {};
let current = conversation?.examples.slice() || [];
if (current.length <= 1) {
update.examples = [{ input: { content: '' }, output: { content: '' } }];
setConversation((prevState) => ({
...prevState,
...update,
}));
return;
}
current.pop();
update.examples = current;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const cardStyle =
'transition-colors shadow-md 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';
const isCodeChat = model?.startsWith('codechat-');
return (
<>
<div
className={
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
(!advancedMode ? ' show' : '')
}
>
<SelectDropDown
value={model}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 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-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
</div>
<EndpointOptionsPopover
content={
<div className="px-4 py-4">
{showExamples && !isCodeChat ? (
<Examples
examples={examples}
setExample={setExample}
addExample={addExample}
removeExample={removeExample}
/>
) : (
<Settings
model={model}
modelLabel={modelLabel}
promptPrefix={promptPrefix}
temperature={temperature}
topP={topP}
topK={topK}
maxOutputTokens={maxOutputTokens}
setOption={setOption}
/>
)}
</div>
}
visible={advancedMode}
saveAsPreset={saveAsPreset}
switchToSimpleMode={switchToSimpleMode}
additionalButton={{
label: (showExamples ? 'Hide' : 'Show') + ' Examples',
buttonClass: isCodeChat ? 'disabled' : '',
handler: triggerExamples,
icon: <MessagesSquared className="mr-1 w-[14px]" />,
}}
/>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={conversation}
/>
</>
);
}
export default GoogleOptions;

View file

@ -0,0 +1,19 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function Anthropic({ conversation, setOption, models }: ModelSelectProps) {
return (
<SelectDropDown
value={conversation?.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
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',
)}
/>
);
}

View file

@ -0,0 +1,78 @@
import { useRecoilValue } from 'recoil';
import { SelectDropDown, Tabs, TabsList, TabsTrigger } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
import store from '~/store';
export default function BingAI({ conversation, setOption, models }: ModelSelectProps) {
const showBingToneSetting = useRecoilValue(store.showBingToneSetting);
if (!conversation) {
return null;
}
const { conversationId, toneStyle, jailbreak } = conversation;
if (conversationId !== 'new' && !showBingToneSetting) {
return null;
}
const defaultClasses =
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
const defaultSelected = cn(
defaultClasses,
'font-medium data-[state=active]:text-white text-xs text-white',
);
const selectedClass = (val: string) => val + '-tab ' + defaultSelected;
return (
<>
<SelectDropDown
title="Mode"
value={jailbreak ? 'Sydney' : 'BingAI'}
data-testid="bing-select-dropdown"
setValue={(value) => setOption('jailbreak')(value === 'Sydney')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
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',
showBingToneSetting ? 'hidden' : '',
)}
/>
<Tabs
value={toneStyle}
className={cn(
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',
)}
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
>
<TabsList className="bg-white/[.60] dark:bg-gray-700">
<TabsTrigger
value="creative"
className={`${toneStyle === 'creative' ? selectedClass('creative') : defaultClasses}`}
>
{'Creative'}
</TabsTrigger>
<TabsTrigger
value="fast"
className={`${toneStyle === 'fast' ? selectedClass('fast') : defaultClasses}`}
>
{'Fast'}
</TabsTrigger>
<TabsTrigger
value="balanced"
className={`${toneStyle === 'balanced' ? selectedClass('balanced') : defaultClasses}`}
>
{'Balanced'}
</TabsTrigger>
<TabsTrigger
value="precise"
className={`${toneStyle === 'precise' ? selectedClass('precise') : defaultClasses}`}
>
{'Precise'}
</TabsTrigger>
</TabsList>
</Tabs>
</>
);
}

View file

@ -0,0 +1,27 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function ChatGPT({ conversation, setOption, models }: ModelSelectProps) {
if (!conversation) {
return null;
}
const { conversationId, model } = conversation;
if (conversationId !== 'new') {
return null;
}
return (
<SelectDropDown
value={model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
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',
)}
/>
);
}

View file

@ -0,0 +1,19 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function Google({ conversation, setOption, models }: ModelSelectProps) {
return (
<SelectDropDown
value={conversation?.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
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',
)}
/>
);
}

View file

@ -0,0 +1,39 @@
import React from 'react';
import OpenAI from './OpenAI';
import BingAI from './BingAI';
import Google from './Google';
import Plugins from './Plugins';
import ChatGPT from './ChatGPT';
import Anthropic from './Anthropic';
import { useRecoilValue } from 'recoil';
import { SelectProps, ModelSelectProps } from 'librechat-data-provider';
import store from '~/store';
type OptionComponentType = React.FC<ModelSelectProps>;
const optionComponents: { [key: string]: OptionComponentType } = {
openAI: OpenAI,
azureOpenAI: OpenAI,
bingAI: BingAI,
google: Google,
gptPlugins: Plugins,
anthropic: Anthropic,
chatGPTBrowser: ChatGPT,
};
export default function ModelSelect({ conversation, setOption }: SelectProps) {
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (!conversation?.endpoint) {
return null;
}
const { endpoint } = conversation;
const OptionComponent = optionComponents[endpoint];
const models = endpointsConfig?.[endpoint]?.['availableModels'] ?? [];
if (!OptionComponent) {
return null;
}
return <OptionComponent conversation={conversation} setOption={setOption} models={models} />;
}

View file

@ -0,0 +1,19 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function OpenAI({ conversation, setOption, models }: ModelSelectProps) {
return (
<SelectDropDown
value={conversation?.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
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',
)}
/>
);
}

View file

@ -0,0 +1,111 @@
import { useRecoilState } from 'recoil';
import { useState, useEffect } from 'react';
import { ChevronDownIcon } from 'lucide-react';
import { ModelSelectProps, useAvailablePluginsQuery, TPlugin } from 'librechat-data-provider';
import { SelectDropDown, MultiSelectDropDown, Button } from '~/components/ui';
import { useSetOptions, useAuthContext, useMediaQuery } from '~/hooks';
import { cn, cardStyle } from '~/utils/';
import store from '~/store';
const pluginStore: TPlugin = {
name: 'Plugin store',
pluginKey: 'pluginStore',
isButton: true,
description: '',
icon: '',
authConfig: [],
authenticated: false,
};
export default function Plugins({ conversation, setOption, models }: ModelSelectProps) {
const { data: allPlugins } = useAvailablePluginsQuery();
const [visible, setVisibility] = useState<boolean>(true);
const [availableTools, setAvailableTools] = useRecoilState(store.availableTools);
const { checkPluginSelection, setTools } = useSetOptions();
const { user } = useAuthContext();
const isSmallScreen = useMediaQuery('(max-width: 640px)');
useEffect(() => {
if (isSmallScreen) {
setVisibility(false);
}
}, [isSmallScreen]);
useEffect(() => {
if (!user) {
return;
}
if (!allPlugins) {
return;
}
if (!user.plugins || user.plugins.length === 0) {
setAvailableTools([pluginStore]);
return;
}
const tools = [...user.plugins]
.map((el) => allPlugins.find((plugin) => plugin.pluginKey === el))
.filter((el): el is TPlugin => el !== undefined);
/* Filter Last Selected Tools */
const lastSelectedTools = JSON.parse(localStorage.getItem('lastSelectedTools') ?? '');
const filteredTools = lastSelectedTools.filter((tool: TPlugin) =>
tools.some((existingTool) => existingTool.pluginKey === tool.pluginKey),
);
localStorage.setItem('lastSelectedTools', JSON.stringify(filteredTools));
setAvailableTools([...tools, pluginStore]);
// setAvailableTools is a recoil state setter, so it's safe to use it in useEffect
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allPlugins, user]);
if (!conversation) {
return null;
}
return (
<>
<Button
type="button"
className={cn(
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',
)}
onClick={() => setVisibility((prev) => !prev)}
>
<ChevronDownIcon
className={cn(
!visible ? 'rotate-180 transform' : '',
'w-4 text-gray-600 dark:text-white',
)}
/>
</Button>
<SelectDropDown
value={conversation.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
className={cn(
cardStyle,
'min-w-60 z-40 flex w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
visible ? '' : 'hidden',
)}
/>
<MultiSelectDropDown
value={conversation.tools || []}
isSelected={checkPluginSelection}
setSelected={setTools}
availableValues={availableTools}
optionValueKey="pluginKey"
showAbove={true}
className={cn(
cardStyle,
'min-w-60 z-50 w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
visible ? '' : 'hidden',
)}
/>
</>
);
}

View file

@ -0,0 +1 @@
export { default as ModelSelect } from './ModelSelect';

View file

@ -1,117 +0,0 @@
import { useState } from 'react';
import { Settings2 } from 'lucide-react';
import { useRecoilState, useRecoilValue } from 'recoil';
import SelectDropDown from '../../ui/SelectDropDown';
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
import { Button } from '../../ui/Button.tsx';
import Settings from '../../Endpoints/OpenAI/Settings.jsx';
import { cn } from '~/utils/';
import store from '~/store';
function OpenAIOptions() {
const [advancedMode, setAdvancedMode] = useState(false);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const { endpoint } = conversation;
const {
model,
chatGptLabel,
promptPrefix,
temperature,
top_p,
presence_penalty,
frequency_penalty,
} = conversation;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const isOpenAI = endpoint === 'openAI' || endpoint === 'azureOpenAI';
if (!isOpenAI) {
return null;
}
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
const switchToSimpleMode = () => {
setAdvancedMode(false);
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const cardStyle =
'transition-colors shadow-md 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';
return (
<>
<div
className={
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
(!advancedMode ? ' show' : '')
}
>
<SelectDropDown
value={model}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 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-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
</div>
<EndpointOptionsPopover
content={
<div className="px-4 py-4">
<Settings
model={model}
chatGptLabel={chatGptLabel}
promptPrefix={promptPrefix}
temperature={temperature}
topP={top_p}
freqP={presence_penalty}
presP={frequency_penalty}
setOption={setOption}
/>
</div>
}
visible={advancedMode}
saveAsPreset={saveAsPreset}
switchToSimpleMode={switchToSimpleMode}
/>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={conversation}
/>
</>
);
}
export default OpenAIOptions;

View file

@ -0,0 +1,135 @@
import { Settings2 } from 'lucide-react';
import { useState, useEffect, useMemo } from 'react';
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
import { TPreset } from 'librechat-data-provider';
import { PluginStoreDialog } from '~/components';
import {
EndpointSettings,
SaveAsPresetDialog,
EndpointOptionsPopover,
} from '~/components/Endpoints';
import { Button } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { useSetOptions } from '~/hooks';
import { ModelSelect } from './ModelSelect';
import GenerationButtons from './GenerationButtons';
import store from '~/store';
export default function OptionsBar() {
const conversation = useRecoilValue(store.conversation);
const messagesTree = useRecoilValue(store.messagesTree);
const latestMessage = useRecoilValue(store.latestMessage);
const setShowBingToneSetting = useSetRecoilState(store.showBingToneSetting);
const [showPluginStoreDialog, setShowPluginStoreDialog] = useRecoilState(
store.showPluginStoreDialog,
);
const [saveAsDialogShow, setSaveAsDialogShow] = useState<boolean>(false);
const [showPopover, setShowPopover] = useRecoilState(store.showPopover);
const [opacityClass, setOpacityClass] = useState('full-opacity');
const { setOption } = useSetOptions();
const { endpoint, conversationId, jailbreak } = conversation ?? {};
const altConditions: { [key: string]: boolean } = {
bingAI: !!(latestMessage && conversation?.jailbreak && endpoint === 'bingAI'),
};
const altSettings: { [key: string]: () => void } = {
bingAI: () => setShowBingToneSetting((prev) => !prev),
};
const noSettings = useMemo<{ [key: string]: boolean }>(
() => ({
chatGPTBrowser: true,
bingAI: jailbreak ? false : conversationId !== 'new',
}),
[jailbreak, conversationId],
);
useEffect(() => {
if (showPopover) {
return;
} else if (messagesTree && messagesTree.length >= 1) {
setOpacityClass('show');
} else {
setOpacityClass('full-opacity');
}
}, [messagesTree, showPopover]);
useEffect(() => {
if (endpoint && noSettings[endpoint]) {
setShowPopover(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [endpoint, noSettings]);
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
if (!endpoint) {
return null;
}
const triggerAdvancedMode = altConditions[endpoint]
? altSettings[endpoint]
: () => setShowPopover((prev) => !prev);
return (
<div className="relative py-2 last:mb-2 md:mx-4 md:mb-[-16px] md:py-4 md:pt-2 md:last:mb-6 lg:mx-auto lg:mb-[-32px] lg:max-w-2xl lg:pt-6 xl:max-w-3xl">
<GenerationButtons showPopover={showPopover} opacityClass={opacityClass} />
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
<div
className={cn(
'options-bar z-[61] flex w-full flex-wrap items-center justify-center gap-2',
showPopover ? '' : opacityClass,
)}
onMouseEnter={() => {
if (showPopover) {
return;
}
setOpacityClass('full-opacity');
}}
onMouseLeave={() => {
if (showPopover) {
return;
}
if (!messagesTree || messagesTree.length === 0) {
return;
}
setOpacityClass('show');
}}
>
<ModelSelect conversation={conversation} setOption={setOption} />
{!noSettings[endpoint] && (
<Button
type="button"
className={cn(
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',
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
)}
</div>
<EndpointOptionsPopover
endpoint={endpoint}
visible={showPopover}
saveAsPreset={saveAsPreset}
closePopover={() => setShowPopover(false)}
>
<div className="px-4 py-4">
<EndpointSettings conversation={conversation} setOption={setOption} />
</div>
</EndpointOptionsPopover>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={{ ...conversation } as TPreset}
/>
<PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} />
</span>
</div>
);
}

View file

@ -1,245 +0,0 @@
import { useState, useEffect, memo } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Settings2, ChevronDownIcon } from 'lucide-react';
import {
SelectDropDown,
PluginStoreDialog,
MultiSelectDropDown,
Button,
GPTIcon,
} from '~/components';
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
import { Settings, AgentSettings } from '../../Endpoints/Plugins/';
import { cn } from '~/utils/';
import store from '~/store';
import { useAuthContext } from '~/hooks/AuthContext';
import { useAvailablePluginsQuery } from 'librechat-data-provider';
function PluginsOptions() {
const { data: allPlugins } = useAvailablePluginsQuery();
const [visibile, setVisibility] = useState(true);
const [advancedMode, setAdvancedMode] = useState(false);
const [availableTools, setAvailableTools] = useState([]);
const [showAgentSettings, setShowAgentSettings] = useState(false);
const [showSavePresetDialog, setShowSavePresetDialog] = useState(false);
const [showPluginStoreDialog, setShowPluginStoreDialog] = useState(false);
const [opacityClass, setOpacityClass] = useState('full-opacity');
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const messagesTree = useRecoilValue(store.messagesTree);
const { user } = useAuthContext();
useEffect(() => {
if (advancedMode) {
return;
} else if (messagesTree?.length >= 1) {
setOpacityClass('show');
} else {
setOpacityClass('full-opacity');
}
}, [messagesTree, advancedMode]);
useEffect(() => {
if (allPlugins && user) {
const pluginStore = { name: 'Plugin store', pluginKey: 'pluginStore', isButton: true };
if (!user.plugins || user.plugins.length === 0) {
setAvailableTools([pluginStore]);
return;
}
const tools = [...user.plugins]
.map((el) => {
return allPlugins.find((plugin) => plugin.pluginKey === el);
})
.filter((el) => el);
setAvailableTools([...tools, pluginStore]);
}
}, [allPlugins, user]);
const triggerAgentSettings = () => setShowAgentSettings((prev) => !prev);
const { endpoint, agentOptions } = conversation;
if (endpoint !== 'gptPlugins') {
return null;
}
const models = endpointsConfig?.['gptPlugins']?.['availableModels'] || [];
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
const switchToSimpleMode = () => {
setAdvancedMode(false);
};
const saveAsPreset = () => {
setShowSavePresetDialog(true);
};
function checkIfSelected(value) {
if (!conversation.tools) {
return false;
}
return conversation.tools.find((el) => el.pluginKey === value) ? true : false;
}
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const setAgentOption = (param) => (newValue) => {
const editableConvo = JSON.stringify(conversation);
const convo = JSON.parse(editableConvo);
let { agentOptions } = convo;
agentOptions[param] = newValue;
setConversation((prevState) => ({
...prevState,
agentOptions,
}));
};
const setTools = (newValue) => {
if (newValue === 'pluginStore') {
setShowPluginStoreDialog(true);
return;
}
let update = {};
let current = conversation.tools || [];
let isSelected = checkIfSelected(newValue);
let tool = availableTools[availableTools.findIndex((el) => el.pluginKey === newValue)];
if (isSelected) {
update.tools = current.filter((el) => el.pluginKey !== newValue);
} else {
update.tools = [...current, tool];
}
localStorage.setItem('lastSelectedTools', JSON.stringify(update.tools));
setConversation((prevState) => ({
...prevState,
...update,
}));
};
const cardStyle =
'transition-colors shadow-md 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';
return (
<>
<div
className={
'pluginOptions flex w-full flex-wrap items-center justify-center gap-2 ' +
(!advancedMode ? opacityClass : '')
}
onMouseEnter={() => {
if (advancedMode) {
return;
}
setOpacityClass('full-opacity');
}}
onMouseLeave={() => {
if (advancedMode) {
return;
}
if (!messagesTree || messagesTree.length === 0) {
return;
}
setOpacityClass('show');
}}
>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
)}
onClick={() => setVisibility((prev) => !prev)}
>
<ChevronDownIcon
className={cn(
!visibile ? 'rotate-180 transform' : '',
'w-4 text-gray-600 dark:text-white',
)}
/>
</Button>
<SelectDropDown
value={conversation.model}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
className={cn(cardStyle, 'min-w-60 z-40 flex w-60', !visibile && 'hidden')}
/>
<MultiSelectDropDown
value={conversation.tools || []}
isSelected={checkIfSelected}
setSelected={setTools}
availableValues={availableTools}
optionValueKey="pluginKey"
showAbove={true}
className={cn(cardStyle, 'min-w-60 z-50 w-60', !visibile && 'hidden')}
/>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
!visibile && 'hidden',
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
</div>
<EndpointOptionsPopover
content={
<div className="px-4 py-4">
{showAgentSettings ? (
<AgentSettings
agent={agentOptions.agent}
skipCompletion={agentOptions.skipCompletion}
model={agentOptions.model}
endpoint={agentOptions.endpoint}
temperature={agentOptions.temperature}
topP={agentOptions.top_p}
freqP={agentOptions.presence_penalty}
presP={agentOptions.frequency_penalty}
setOption={setAgentOption}
tools={conversation.tools}
/>
) : (
<Settings
model={conversation.model}
endpoint={endpoint}
chatGptLabel={conversation.chatGptLabel}
promptPrefix={conversation.promptPrefix}
temperature={conversation.temperature}
topP={conversation.top_p}
freqP={conversation.presence_penalty}
presP={conversation.frequency_penalty}
setOption={setOption}
tools={conversation.tools}
/>
)}
</div>
}
visible={advancedMode}
saveAsPreset={saveAsPreset}
switchToSimpleMode={switchToSimpleMode}
additionalButton={{
label: `Show ${showAgentSettings ? 'Completion' : 'Agent'} Settings`,
handler: triggerAgentSettings,
icon: <GPTIcon className="mr-1 mt-[2px] w-[14px]" size={14} />,
}}
/>
<SaveAsPresetDialog
open={showSavePresetDialog}
onOpenChange={setShowSavePresetDialog}
preset={conversation}
/>
<PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} />
</>
);
}
export default memo(PluginsOptions);

View file

@ -1,5 +1,5 @@
import React from 'react';
import FileUpload from '../NewConversationMenu/FileUpload';
import FileUpload from '../EndpointMenu/FileUpload';
const GoogleConfig = ({ setToken }: { setToken: React.Dispatch<React.SetStateAction<string>> }) => {
return (

View file

@ -1,6 +1,6 @@
import React, { ChangeEvent, FC } from 'react';
import { Input, Label } from '~/components';
import { cn } from '~/utils/';
import { cn, defaultTextPropsLabel, removeFocusOutlines } from '~/utils/';
interface InputWithLabelProps {
value: string;
@ -10,9 +10,6 @@ interface InputWithLabelProps {
}
const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id }) => {
const defaultTextProps =
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
return (
<>
<Label htmlFor={id} className="text-left text-sm font-medium">
@ -26,8 +23,9 @@ const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id })
onChange={onChange}
placeholder={`Enter ${label}`}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
defaultTextPropsLabel,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</>

View file

@ -26,7 +26,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
const { getToken } = store.useToken(endpoint);
useEffect(() => {
let oldToken = getToken();
const oldToken = getToken();
if (isJson(token)) {
setShowPanel(true);
}
@ -41,14 +41,14 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
function getAzure(name: string) {
if (isJson(token)) {
let newToken = JSON.parse(token);
const newToken = JSON.parse(token);
return newToken[name];
} else {
return '';
}
}
function setAzure(name: string, value: any) {
function setAzure(name: string, value: number | string | boolean) {
let newToken = {};
if (isJson(token)) {
newToken = JSON.parse(token);
@ -64,7 +64,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
<InputWithLabel
id={'chatGPTLabel'}
value={token || ''}
onChange={(e: { target: { value: any } }) => setToken(e.target.value || '')}
onChange={(e: { target: { value: string } }) => setToken(e.target.value || '')}
label={'OpenAI API Key'}
/>
</>
@ -73,7 +73,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
<InputWithLabel
id={'instanceNameLabel'}
value={getAzure('azureOpenAIApiInstanceName') || ''}
onChange={(e: { target: { value: any } }) =>
onChange={(e: { target: { value: string } }) =>
setAzure('azureOpenAIApiInstanceName', e.target.value || '')
}
label={'Azure OpenAI Instance Name'}
@ -82,7 +82,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
<InputWithLabel
id={'deploymentNameLabel'}
value={getAzure('azureOpenAIApiDeploymentName') || ''}
onChange={(e: { target: { value: any } }) =>
onChange={(e: { target: { value: string } }) =>
setAzure('azureOpenAIApiDeploymentName', e.target.value || '')
}
label={'Azure OpenAI Deployment Name'}
@ -91,7 +91,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
<InputWithLabel
id={'versionLabel'}
value={getAzure('azureOpenAIApiVersion') || ''}
onChange={(e: { target: { value: any } }) =>
onChange={(e: { target: { value: string } }) =>
setAzure('azureOpenAIApiVersion', e.target.value || '')
}
label={'Azure OpenAI API Version'}
@ -100,7 +100,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
<InputWithLabel
id={'apiKeyLabel'}
value={getAzure('azureOpenAIApiKey') || ''}
onChange={(e: { target: { value: any } }) =>
onChange={(e: { target: { value: string } }) =>
setAzure('azureOpenAIApiKey', e.target.value || '')
}
label={'Azure OpenAI API Key'}

View file

@ -3,7 +3,8 @@ import HelpText from './HelpText';
import GoogleConfig from './GoogleConfig';
import OpenAIConfig from './OpenAIConfig';
import OtherConfig from './OtherConfig';
import { Dialog, DialogTemplate } from '~/components';
import { Dialog } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { alternateName } from '~/utils';
import store from '~/store';
@ -30,6 +31,7 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={`Set Token for ${alternateName[endpoint] ?? endpoint}`}
className="w-full max-w-[650px] sm:w-3/4 md:w-3/4 lg:w-3/4"
main={
<div className="grid w-full items-center gap-2">
<EndpointComponent token={token} setToken={setToken} endpoint={endpoint} />

View file

@ -45,11 +45,13 @@ export default function SubmitButton({
<button
onClick={setToken}
type="button"
className="group absolute bottom-0 right-0 z-[101] flex h-[100%] w-auto items-center justify-center bg-transparent p-1 text-gray-500"
className="group absolute bottom-0 right-0 z-[101] flex h-[100%] w-auto items-center justify-center bg-transparent pr-1 text-gray-500"
>
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] align-middle text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
<Settings className="mr-1 inline-block w-[18px]" />
Set Token First
<div className="flex items-center justify-center rounded-md text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
<div className="m-0 mr-0 flex items-center justify-center rounded-md p-2 sm:p-2">
<Settings className="mr-1 inline-block h-auto w-[18px]" />
Set Token First
</div>
</div>
</button>
<SetTokenDialog

View file

@ -1,37 +1,31 @@
import React, { useEffect, useRef, useState } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil';
import SubmitButton from './SubmitButton';
import OpenAIOptions from './OpenAIOptions';
import PluginsOptions from './PluginsOptions';
import ChatGPTOptions from './ChatGPTOptions';
import BingAIOptions from './BingAIOptions';
import GoogleOptions from './GoogleOptions';
import AnthropicOptions from './AnthropicOptions';
import NewConversationMenu from './NewConversationMenu';
import AdjustToneButton from './AdjustToneButton';
import Footer from './Footer';
import React, { useEffect, useContext, useRef } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { useMessageHandler } from '~/utils/handleSubmit';
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
import SubmitButton from './SubmitButton';
import OptionsBar from './OptionsBar';
import { EndpointMenu } from './EndpointMenu';
import Footer from './Footer';
import { useMessageHandler, ThemeContext } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
export default function TextChat({ isSearchView = false }) {
const inputRef = useRef(null);
const isComposing = useRef(false);
const [text, setText] = useRecoilState(store.text);
const { theme } = useContext(ThemeContext);
const conversation = useRecoilValue(store.conversation);
const latestMessage = useRecoilValue(store.latestMessage);
const [text, setText] = useRecoilState(store.text);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const isSubmitting = useRecoilValue(store.isSubmitting);
const setShowBingToneSetting = useSetRecoilState(store.showBingToneSetting);
// TODO: do we need this?
const disabled = false;
const { ask, stopGenerating } = useMessageHandler();
const [showBingToneSetting, setShowBingToneSetting] = useState(false);
const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error;
const { conversationId, jailbreak } = conversation || {};
@ -49,6 +43,8 @@ export default function TextChat({ isSearchView = false }) {
if (conversationId !== 'search') {
inputRef.current?.focus();
}
// setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [conversationId, jailbreak]);
useEffect(() => {
@ -127,38 +123,42 @@ export default function TextChat({ isSearchView = false }) {
return '';
};
const handleBingToneSetting = () => {
setShowBingToneSetting((show) => !show);
};
if (isSearchView) {
return <></>;
}
let isDark = theme === 'dark';
if (theme === 'system') {
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
}
return (
<>
<div className="fixed bottom-0 left-0 w-full border-transparent bg-gradient-to-b from-transparent via-white to-white pt-6 dark:border-white/20 dark:via-gray-800 dark:to-gray-800 md:absolute">
<div className="relative py-2 md:mb-[-16px] md:py-4 lg:mb-[-32px]">
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
<OpenAIOptions />
<PluginsOptions />
<ChatGPTOptions />
<GoogleOptions />
<BingAIOptions show={showBingToneSetting} />
<AnthropicOptions />
</span>
</div>
<div
className="no-gradient-sm fixed bottom-0 left-0 w-full pt-6 sm:bg-gradient-to-b md:absolute"
style={{
background: `linear-gradient(to bottom,
${isDark ? 'rgba(52, 53, 65, 0)' : 'rgba(255, 255, 255, 0)'},
${isDark ? 'rgba(52, 53, 65, 0.08)' : 'rgba(255, 255, 255, 0.08)'},
${isDark ? 'rgba(52, 53, 65, 0.38)' : 'rgba(255, 255, 255, 0.38)'},
${isDark ? 'rgba(52, 53, 65, 1)' : 'rgba(255, 255, 255, 1)'},
${isDark ? '#343541' : '#ffffff'})`,
}}
>
<OptionsBar />
<div className="input-panel md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient relative w-full border-t bg-white py-2 dark:border-white/20 dark:bg-gray-800 md:border-t-0 md:border-transparent md:bg-transparent md:dark:border-transparent md:dark:bg-transparent">
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
<form className="stretch z-[60] mx-2 flex flex-row gap-3 last:mb-2 md:mx-4 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
<div className="relative flex h-full flex-1 md:flex-col">
<div
className={`relative flex flex-grow flex-row rounded-md border border-black/10 ${
disabled ? 'bg-gray-100' : 'bg-white'
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
className={cn(
'relative flex flex-grow flex-row rounded-xl border border-black/10 py-[10px] md:py-4 md:pl-4',
'shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]',
'dark:border-gray-900/50 dark:text-white',
disabled ? 'bg-gray-100 dark:bg-gray-900' : 'bg-white dark:bg-gray-700',
)}
>
<NewConversationMenu />
<EndpointMenu />
<TextareaAutosize
// set test id for e2e testing
data-testid="text-input"
@ -185,9 +185,6 @@ export default function TextChat({ isSearchView = false }) {
endpointsConfig={endpointsConfig}
endpoint={conversation?.endpoint}
/>
{latestMessage && conversation?.jailbreak && conversation.endpoint === 'bingAI' ? (
<AdjustToneButton onClick={handleBingToneSetting} />
) : null}
</div>
</div>
</form>

View file

@ -9,7 +9,7 @@ import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import CodeBlock from './CodeBlock';
import store from '~/store';
import { langSubset } from '~/utils/languages.mjs';
import { langSubset } from '~/utils';
const code = React.memo((props) => {
const { inline, className, children } = props;

View file

@ -8,12 +8,11 @@ import Content from './Content/Content';
import MultiMessage from './MultiMessage';
import HoverButtons from './HoverButtons';
import SiblingSwitch from './SiblingSwitch';
import getIcon from '~/utils/getIcon';
import { useMessageHandler } from '~/utils/handleSubmit';
import { getIcon } from '~/components/Endpoints';
import { useMessageHandler } from '~/hooks';
import { useGetConversationByIdQuery } from 'librechat-data-provider';
import { cn } from '~/utils/';
import { cn, getError } from '~/utils/';
import store from '~/store';
import getError from '~/utils/getError';
export default function Message({
conversation,

View file

@ -10,16 +10,16 @@ const MessageHeader = ({ isSearchView = false }) => {
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const conversation = useRecoilValue(store.conversation);
const searchQuery = useRecoilValue(store.searchQuery);
const { endpoint } = conversation;
const isNotClickable = endpoint === 'chatGPTBrowser' || endpoint === 'gptPlugins';
const { model } = conversation;
const { endpoint, model } = conversation;
const isNotClickable = endpoint === 'chatGPTBrowser';
const plugins = (
<>
<Plugin /> <span className="px-1"></span>
<span className="py-0.25 ml-1 rounded bg-blue-200 px-1 text-[10px] font-semibold uppercase text-[#4559A4]">
{/* <span className="py-0.25 ml-1 rounded bg-blue-200 px-1 text-[10px] font-semibold uppercase text-[#4559A4]">
beta
</span>
<span className="px-1"></span>
<span className="px-1"></span> */}
Model: {model}
</>
);
@ -76,12 +76,12 @@ const MessageHeader = ({ isSearchView = false }) => {
<>
<div
className={cn(
'dark:text-gray-450 w-full gap-1 border-b border-black/10 bg-gray-50 text-sm text-gray-500 transition-all hover:bg-gray-100 hover:bg-opacity-30 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-500 dark:hover:bg-gray-600 dark:hover:bg-opacity-100',
'flex min-h-[60px] w-full flex-wrap items-center justify-between gap-3 border-b border-black/10 bg-white text-sm text-gray-500 transition-all hover:bg-gray-50 hover:bg-opacity-30 dark:border-gray-900/50 dark:bg-gray-800 dark:hover:bg-gray-700 dark:hover:bg-opacity-100',
isNotClickable ? '' : 'cursor-pointer ',
)}
onClick={() => (isNotClickable ? null : setSaveAsDialogShow(true))}
>
<div className="d-block flex w-full items-center justify-center p-3">
<div className="flex flex-1 flex-grow items-center justify-center gap-1 p-1 text-gray-600 dark:text-gray-200 sm:p-0">
{getConversationTitle()}
</div>
</div>

View file

@ -4,7 +4,7 @@ export default function ScrollToBottom({ scrollHandler }) {
return (
<button
onClick={scrollHandler}
className="absolute bottom-[124px] right-6 z-10 cursor-pointer rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 md:bottom-[120px]"
className="absolute bottom-[124px] right-6 z-[62] cursor-pointer rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 md:bottom-[120px]"
>
<svg
stroke="currentColor"

View file

@ -6,7 +6,7 @@ import { CSSTransition } from 'react-transition-group';
import ScrollToBottom from './ScrollToBottom';
import MultiMessage from './MultiMessage';
import MessageHeader from './MessageHeader';
import { useScreenshot } from '~/utils/screenshotContext.jsx';
import { useScreenshot } from '~/hooks';
import store from '~/store';
@ -17,6 +17,7 @@ export default function Messages({ isSearchView = false }) {
const messagesEndRef = useRef(null);
const messagesTree = useRecoilValue(store.messagesTree);
const showPopover = useRecoilValue(store.showPopover);
const searchResultMessagesTree = useRecoilValue(store.searchResultMessagesTree);
const _messagesTree = isSearchView ? searchResultMessagesTree : messagesTree;
@ -115,7 +116,10 @@ export default function Messages({ isSearchView = false }) {
unmountOnExit={false}
// appear
>
{() => showScrollButton && <ScrollToBottom scrollHandler={scrollHandler} />}
{() =>
showScrollButton &&
!showPopover && <ScrollToBottom scrollHandler={scrollHandler} />
}
</CSSTransition>
</>
)}

View file

@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { Dialog, DialogTemplate } from '../ui/';
import { Dialog } from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { ClearChatsButton } from './SettingsTabs/';
import { useClearConversationsMutation } from 'librechat-data-provider';
import store from '~/store';
@ -34,9 +35,16 @@ const ClearConvos = ({ open, onOpenChange }) => {
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
title={localize(lang, 'com_nav_clear_conversation')}
className="w-full max-w-[650px] sm:w-3/4 md:w-3/4 lg:w-3/4"
headerClassName="border-none"
description={localize(lang, 'com_nav_clear_conversation_confirm_message')}
leftButtons={
<ClearChatsButton showText={false} confirmClear={confirmClear} onClick={clearConvos} />
buttons={
<ClearChatsButton
showText={false}
confirmClear={confirmClear}
onClick={clearConvos}
className="w-[77px]"
/>
}
/>
</Dialog>

View file

@ -3,24 +3,15 @@ import { useRecoilValue, useRecoilCallback } from 'recoil';
import filenamify from 'filenamify';
import exportFromJSON from 'export-from-json';
import download from 'downloadjs';
import {
Dialog,
DialogButton,
DialogTemplate,
Input,
Label,
Checkbox,
Dropdown,
} from '~/components/ui/';
import { cn } from '~/utils/';
import { useScreenshot } from '~/utils/screenshotContext';
import { Dialog, DialogButton, Input, Label, Checkbox, Dropdown } from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset } from '~/utils/';
import { useScreenshot, useLocalize } from '~/hooks';
import store from '~/store';
import cleanupPreset from '~/utils/cleanupPreset.js';
import { localize } from '~/localization/Translation';
export default function ExportModel({ open, onOpenChange }) {
const { captureScreenshot } = useScreenshot();
const localize = useLocalize();
const [filename, setFileName] = useState('');
const [type, setType] = useState('');
@ -33,8 +24,6 @@ export default function ExportModel({ open, onOpenChange }) {
const messagesTree = useRecoilValue(store.messagesTree) || [];
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const lang = useRecoilValue(store.lang);
const getSiblingIdx = useRecoilCallback(
({ snapshot }) =>
async (messageId) =>
@ -339,9 +328,6 @@ export default function ExportModel({ open, onOpenChange }) {
}
};
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';
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTemplate
@ -352,22 +338,23 @@ export default function ExportModel({ open, onOpenChange }) {
<div className="grid w-full gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label htmlFor="filename" className="text-left text-sm font-medium">
{localize(lang, 'com_nav_export_filename')}
{localize('com_nav_export_filename')}
</Label>
<Input
id="filename"
value={filename}
onChange={(e) => setFileName(filenamify(e.target.value || ''))}
placeholder={localize(lang, 'com_nav_export_filename_placeholder')}
placeholder={localize('com_nav_export_filename_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label htmlFor="type" className="text-left text-sm font-medium">
{localize(lang, 'com_nav_export_type')}
{localize('com_nav_export_type')}
</Label>
<Dropdown
id="type"
@ -376,7 +363,8 @@ export default function ExportModel({ open, onOpenChange }) {
options={typeOptions}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
'flex h-10 max-h-10 w-full resize-none',
removeFocusOutlines,
)}
containerClassName="flex w-full resize-none"
/>
@ -386,7 +374,7 @@ export default function ExportModel({ open, onOpenChange }) {
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<div className="grid w-full items-center gap-2">
<Label htmlFor="includeOptions" className="text-left text-sm font-medium">
{localize(lang, 'com_nav_export_include_endpoint_options')}
{localize('com_nav_export_include_endpoint_options')}
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
@ -401,15 +389,15 @@ export default function ExportModel({ open, onOpenChange }) {
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{exportOptionsSupport
? localize(lang, 'com_nav_enabled')
: localize(lang, 'com_nav_not_supported')}
? localize('com_nav_enabled')
: localize('com_nav_not_supported')}
</label>
</div>
</div>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="exportBranches" className="text-left text-sm font-medium">
{localize(lang, 'com_nav_export_all_message_branches')}
{localize('com_nav_export_all_message_branches')}
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
@ -424,15 +412,15 @@ export default function ExportModel({ open, onOpenChange }) {
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{exportBranchesSupport
? localize(lang, 'com_nav_enabled')
: localize(lang, 'com_nav_not_supported')}
? localize('com_nav_enabled')
: localize('com_nav_not_supported')}
</label>
</div>
</div>
{type === 'json' ? (
<div className="grid w-full items-center gap-2">
<Label htmlFor="recursive" className="text-left text-sm font-medium">
{localize(lang, 'com_nav_export_recursive_or_sequential')}
{localize('com_nav_export_recursive_or_sequential')}
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
@ -445,7 +433,7 @@ export default function ExportModel({ open, onOpenChange }) {
htmlFor="recursive"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{localize(lang, 'com_nav_export_recursive')}
{localize('com_nav_export_recursive')}
</label>
</div>
</div>
@ -459,7 +447,7 @@ export default function ExportModel({ open, onOpenChange }) {
onClick={exportConversation}
className="dark:hover:gray-400 border-gray-700 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
>
{localize(lang, 'com_endpoint_export')}
{localize('com_endpoint_export')}
</DialogButton>
</>
}

View file

@ -1,4 +1,5 @@
import { NewChat, NavLinks } from './';
import NewChat from './NewChat';
import NavLinks from './NavLinks';
import { Panel, Spinner } from '~/components';
import { Conversations, Pages } from '../Conversations';
import { useCallback, useEffect, useRef, useState } from 'react';
@ -131,13 +132,13 @@ export default function Nav({ navVisible, setNavVisible }) {
return (
<>
<div
className="nav active dark flex-shrink-0 overflow-x-hidden bg-gray-900 transition-all duration-200 ease-in-out"
className="nav active dark max-w-[320px] flex-shrink-0 overflow-x-hidden bg-gray-900 transition-all duration-200 ease-in-out md:max-w-[260px]"
style={{
width: navVisible ? '260px' : '0px',
width: navVisible ? '100%' : '0%',
visibility: navVisible ? 'visible' : 'hidden',
}}
>
<div className="h-full w-[260px]">
<div className="h-full w-[320px] md:w-[260px]">
<div className="flex h-full min-h-0 flex-col ">
<div className="scrollbar-trigger relative flex h-full w-full flex-1 items-start border-white/20">
<nav className="relative flex h-full flex-1 flex-col space-y-1 p-2">

View file

@ -2,7 +2,12 @@ import { Download } from 'lucide-react';
import { useRecoilValue } from 'recoil';
import { Fragment, useState } from 'react';
import { Menu, Transition } from '@headlessui/react';
import { SearchBar, ClearConvos, Settings, NavLink, Logout, ExportModel } from './';
import SearchBar from './SearchBar';
import ClearConvos from './ClearConvos';
import Settings from './Settings';
import NavLink from './NavLink';
import Logout from './Logout';
import { ExportModel } from './ExportConversation';
import { LinkIcon, DotsIcon, GearIcon, TrashIcon } from '~/components';
import { localize } from '~/localization/Translation';
import { useAuthContext } from '~/hooks/AuthContext';

View file

@ -1,6 +1,6 @@
import * as Tabs from '@radix-ui/react-tabs';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui';
import { General } from './';
import { General } from './SettingsTabs';
import { CogIcon } from '~/components/svg';
import { useEffect, useState } from 'react';
import { cn } from '~/utils/';
@ -45,7 +45,7 @@ export default function Settings({ open, onOpenChange }) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={cn('shadow-2xl dark:bg-gray-900 dark:text-white')}>
<DialogContent className={cn('shadow-2xl dark:bg-gray-900 dark:text-white md:w-[680px] ')}>
<DialogHeader>
<DialogTitle className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
{localize(lang, 'com_nav_settings')}
@ -69,9 +69,9 @@ export default function Settings({ open, onOpenChange }) {
>
<Tabs.Trigger
className={cn(
'radix-state-active:bg-gray-800 radix-state-active:text-white flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm',
'flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm radix-state-active:bg-gray-800 radix-state-active:text-white',
isMobile &&
'dark:radix-state-active:text-white group flex-1 items-center justify-center text-sm dark:text-gray-500',
'group flex-1 items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white',
)}
value="general"
>

View file

@ -1,3 +1,4 @@
import 'test/matchMedia.mock';
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

View file

@ -1,10 +1,12 @@
import * as Tabs from '@radix-ui/react-tabs';
import { CheckIcon } from 'lucide-react';
import { ThemeContext } from '~/hooks/ThemeContext';
import { DialogButton } from '~/components/ui';
import React, { useState, useContext, useEffect, useCallback } from 'react';
import { useClearConversationsMutation } from 'librechat-data-provider';
import { useRecoilValue, useRecoilState } from 'recoil';
import store from '~/store';
import { ThemeContext } from '~/hooks';
import { cn } from '~/utils';
import { localize } from '~/localization/Translation';
export const ThemeSelector = ({
@ -34,10 +36,12 @@ export const ThemeSelector = ({
export const ClearChatsButton = ({
confirmClear,
className = '',
showText = true,
onClick,
}: {
confirmClear: boolean;
className?: string;
showText: boolean;
onClick: () => void;
}) => {
@ -46,12 +50,20 @@ export const ClearChatsButton = ({
return (
<div className="flex items-center justify-between">
{showText && <div>{localize(lang, 'com_nav_clear_all_chats')}</div>}
<button
className="btn relative bg-red-600 text-white hover:bg-red-800"
<DialogButton
id="clearConvosBtn"
onClick={onClick}
className={cn(
' btn btn-danger relative border-none bg-red-700 text-white hover:bg-red-800 dark:hover:bg-red-800',
className,
)}
>
{/* <button
className="btn mt-2 inline-flex h-10 items-center justify-center rounded-md relative bg-red-600 text-white hover:bg-red-800"
type="button"
id="clearConvosBtn"
onClick={onClick}
>
> */}
{confirmClear ? (
<div className="flex w-full items-center justify-center gap-2" id="clearConvosTxt">
<CheckIcon className="h-5 w-5" /> {localize(lang, 'com_nav_confirm_clear')}
@ -61,7 +73,9 @@ export const ClearChatsButton = ({
{localize(lang, 'com_nav_clear')}
</div>
)}
</button>
{/* </button> */}
</DialogButton>
</div>
);
};

View file

@ -1,3 +1,4 @@
import 'test/matchMedia.mock';
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

View file

@ -1,3 +1,4 @@
import 'test/matchMedia.mock';
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

View file

@ -1,9 +1,8 @@
import { TPlugin, TPluginAuthConfig } from 'librechat-data-provider';
import { TPlugin, TPluginAuthConfig, TPluginAction } from 'librechat-data-provider';
import { Save } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { TPluginAction } from './PluginStoreDialog';
import { HoverCard, HoverCardTrigger } from '~/components/ui';
import { PluginTooltip } from '.';
import PluginTooltip from './PluginTooltip';
type TPluginAuthFormProps = {
plugin: TPlugin | undefined;

View file

@ -3,11 +3,16 @@ import { Dialog } from '@headlessui/react';
import { useRecoilState } from 'recoil';
import { X } from 'lucide-react';
import store from '~/store';
import { PluginStoreItem, PluginPagination, PluginAuthForm } from '.';
import PluginStoreItem from './PluginStoreItem';
import PluginPagination from './PluginPagination';
import PluginAuthForm from './PluginAuthForm';
import {
useAvailablePluginsQuery,
useUpdateUserPluginsMutation,
TPlugin,
TPluginAction,
TConversation,
TError,
} from 'librechat-data-provider';
import { useAuthContext } from '~/hooks/AuthContext';
@ -16,12 +21,6 @@ type TPluginStoreDialogProps = {
setIsOpen: (open: boolean) => void;
};
export type TPluginAction = {
pluginKey: string;
action: 'install' | 'uninstall';
auth?: unknown;
};
function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
const { data: availablePlugins } = useAvailablePluginsQuery();
const { user } = useAuthContext();
@ -36,7 +35,7 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
const [error, setError] = useState<boolean>(false);
const [errorMessage, setErrorMessage] = useState<string>('');
const handleInstallError = (error: any) => {
const handleInstallError = (error: TError) => {
setError(true);
if (error.response?.data?.message) {
setErrorMessage(error.response?.data?.message);
@ -49,8 +48,8 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
const handleInstall = (pluginAction: TPluginAction) => {
updateUserPlugins.mutate(pluginAction, {
onError: (error) => {
handleInstallError(error);
onError: (error: unknown) => {
handleInstallError(error as TError);
},
});
setShowPluginAuthForm(false);
@ -60,8 +59,8 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
updateUserPlugins.mutate(
{ pluginKey: plugin, action: 'uninstall', auth: null },
{
onError: (error: any) => {
handleInstallError(error);
onError: (error: unknown) => {
handleInstallError(error as TError);
},
onSuccess: () => {
//@ts-ignore - can't set a default convo or it will break routing
@ -70,10 +69,13 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
return t.pluginKey !== plugin;
});
localStorage.setItem('lastSelectedTools', JSON.stringify(tools));
setConversation((prevState: any) => ({
...prevState,
tools,
}));
setConversation(
(prevState) =>
({
...prevState,
tools,
} as TConversation),
);
},
},
);

View file

@ -1,4 +1,4 @@
import { render, screen } from 'layout-test-utils';
import { render, screen } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
import PluginAuthForm from '../PluginAuthForm';

View file

@ -1,3 +1,4 @@
import 'test/matchMedia.mock';
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

View file

@ -1,4 +1,4 @@
import { render } from 'layout-test-utils';
import { render } from 'test/layout-test-utils';
import PluginStoreDialog from '../PluginStoreDialog';
import userEvent from '@testing-library/user-event';
import * as mockDataProvider from 'librechat-data-provider';

View file

@ -1,5 +1,7 @@
import 'test/matchMedia.mock';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TPlugin } from 'librechat-data-provider';
import PluginStoreItem from '../PluginStoreItem';
const mockPlugin = {
@ -10,14 +12,32 @@ const mockPlugin = {
describe('PluginStoreItem', () => {
it('renders the plugin name and description', () => {
render(<PluginStoreItem plugin={mockPlugin} onInstall={() => {}} onUninstall={() => {}} />);
render(
<PluginStoreItem
plugin={mockPlugin as TPlugin}
onInstall={() => {
return;
}}
onUninstall={() => {
return;
}}
/>,
);
expect(screen.getByText('Test Plugin')).toBeInTheDocument();
expect(screen.getByText('This is a test plugin')).toBeInTheDocument();
});
it('calls onInstall when the install button is clicked', async () => {
const onInstall = jest.fn();
render(<PluginStoreItem plugin={mockPlugin} onInstall={onInstall} onUninstall={() => {}} />);
render(
<PluginStoreItem
plugin={mockPlugin as TPlugin}
onInstall={onInstall}
onUninstall={() => {
return;
}}
/>,
);
await userEvent.click(screen.getByText('Install'));
expect(onInstall).toHaveBeenCalled();
});
@ -26,8 +46,10 @@ describe('PluginStoreItem', () => {
const onUninstall = jest.fn();
render(
<PluginStoreItem
plugin={mockPlugin}
onInstall={() => {}}
plugin={mockPlugin as TPlugin}
onInstall={() => {
return;
}}
onUninstall={onUninstall}
isInstalled
/>,

View file

@ -4,10 +4,8 @@ export default function AnthropicIcon({ size = 25 }) {
<g
style={{
transform: 'translateX(13px) rotateZ(0deg)',
transformorigin: '4.775px 7.73501px',
transformOrigin: '4.775px 7.73501px',
}}
// eslint-disable-next-line react/no-unknown-property
transformorigin="4.7750020027160645px 7.735011100769043px"
>
<path
shapeRendering="geometricPrecision"
@ -19,11 +17,9 @@ export default function AnthropicIcon({ size = 25 }) {
<g
style={{
transform: 'none',
transformorigin: '7.935px 7.73501px',
transformOrigin: '7.935px 7.73501px',
}}
opacity="1"
// eslint-disable-next-line react/no-unknown-property
transformorigin="7.93500280380249px 7.735011100769043px"
>
<path
shapeRendering="geometricPrecision"

View file

@ -1,135 +1,135 @@
import React from 'react';
export default function BingIcon({ size = 25 }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 56 56"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_36_2239)">
<path
d="M46.9982 35.9868C46.9982 36.5323 46.9689 37.0747 46.9103 37.6092C46.5619 40.8696 45.1683 43.8178 43.0701 46.098C43.3344 45.8007 43.5726 45.4815 43.7815 45.1397C43.9426 44.8799 44.086 44.6091 44.207 44.3266C44.251 44.2337 44.291 44.137 44.3242 44.041C44.3643 43.9481 44.3974 43.8514 44.4267 43.7554C44.4599 43.6664 44.4892 43.5736 44.5146 43.4807C44.54 43.3839 44.5662 43.2879 44.5878 43.1912C44.5917 43.1803 44.5955 43.1685 44.5986 43.1576C44.621 43.0609 44.6387 42.9649 44.6572 42.8681C44.6757 42.7682 44.6942 42.6675 44.7088 42.5677C44.7088 42.5638 44.7088 42.5638 44.7088 42.5606C44.7235 42.4678 44.7343 42.3749 44.742 42.2781C44.7643 42.0589 44.7751 41.8404 44.7751 41.6172C44.7751 40.3624 44.4336 39.1848 43.8363 38.1828C43.7006 37.9487 43.5503 37.7263 43.3853 37.5148C43.1911 37.262 42.9822 37.0247 42.7548 36.8054C42.1898 36.2522 41.5299 35.7988 40.8 35.4796C40.4847 35.3384 40.1548 35.2236 39.8172 35.1378C39.8133 35.1378 39.8064 35.1339 39.8025 35.1339L39.6853 35.0933L37.9764 34.4995V34.4956L33.5056 32.9395C33.491 32.9356 33.4725 32.9356 33.4617 32.9325L33.1826 32.8287C32.2838 32.4721 31.5392 31.8041 31.0736 30.9535L29.4418 26.7387L27.571 21.9114L27.2118 20.9796L27.1201 20.79C27.0175 20.5372 26.962 20.2625 26.962 19.9769C26.962 19.9027 26.962 19.8286 26.9697 19.7615C27.0761 18.6994 27.9672 17.8676 29.0456 17.8676C29.3316 17.8676 29.6068 17.9269 29.8565 18.0346L38.1876 22.3593L39.831 23.2099C40.7005 23.7336 41.5107 24.35 42.2514 25.0446C44.9362 27.5402 46.6968 31.0378 46.9612 34.9482C46.9836 35.2931 46.9982 35.638 46.9982 35.9868Z"
fill="url(#paint0_linear_36_2239)"
/>
<path
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3341 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
fill="url(#paint1_linear_36_2239)"
/>
<path
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
fill="url(#paint2_linear_36_2239)"
/>
<path
opacity="0.15"
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3349 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
fill="url(#paint3_linear_36_2239)"
/>
<path
opacity="0.1"
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
fill="url(#paint4_linear_36_2239)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_36_2239"
x1="24.061"
y1="24.49"
x2="48.0304"
y2="38.1597"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#37BDFF" />
<stop offset="0.1832" stopColor="#33BFFD" />
<stop offset="0.3576" stopColor="#28C5F5" />
<stop offset="0.528" stopColor="#15D0E9" />
<stop offset="0.5468" stopColor="#12D1E7" />
<stop offset="0.5903" stopColor="#1CD2E5" />
<stop offset="0.7679" stopColor="#42D8DC" />
<stop offset="0.9107" stopColor="#59DBD6" />
<stop offset="1" stopColor="#62DCD4" />
</linearGradient>
<linearGradient
id="paint1_linear_36_2239"
x1="10.099"
y1="45.3798"
x2="44.7715"
y2="45.3798"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#39D2FF" />
<stop offset="0.1501" stopColor="#38CEFE" />
<stop offset="0.2931" stopColor="#35C3FA" />
<stop offset="0.4327" stopColor="#2FB0F3" />
<stop offset="0.5468" stopColor="#299AEB" />
<stop offset="0.5827" stopColor="#2692EC" />
<stop offset="0.7635" stopColor="#1A6CF1" />
<stop offset="0.909" stopColor="#1355F4" />
<stop offset="1" stopColor="#104CF5" />
</linearGradient>
<linearGradient
id="paint2_linear_36_2239"
x1="16.4996"
y1="48.4653"
x2="16.4996"
y2="1.52914"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#1B48EF" />
<stop offset="0.1221" stopColor="#1C51F0" />
<stop offset="0.3212" stopColor="#1E69F5" />
<stop offset="0.5676" stopColor="#2190FB" />
<stop offset="1" stopColor="#26B8F4" />
</linearGradient>
<linearGradient
id="paint3_linear_36_2239"
x1="16.9908"
y1="54.0427"
x2="38.6508"
y2="32.6475"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="0.3726" stopColor="#FDFDFD" />
<stop offset="0.5069" stopColor="#F6F6F6" />
<stop offset="0.6026" stopColor="#EBEBEB" />
<stop offset="0.68" stopColor="#DADADA" />
<stop offset="0.7463" stopColor="#C4C4C4" />
<stop offset="0.805" stopColor="#A8A8A8" />
<stop offset="0.8581" stopColor="#888888" />
<stop offset="0.9069" stopColor="#626262" />
<stop offset="0.9523" stopColor="#373737" />
<stop offset="0.9926" stopColor="#090909" />
<stop offset="1" />
</linearGradient>
<linearGradient
id="paint4_linear_36_2239"
x1="16.4996"
y1="0.696411"
x2="16.4996"
y2="47.9822"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="0.3726" stopColor="#FDFDFD" />
<stop offset="0.5069" stopColor="#F6F6F6" />
<stop offset="0.6026" stopColor="#EBEBEB" />
<stop offset="0.68" stopColor="#DADADA" />
<stop offset="0.7463" stopColor="#C4C4C4" />
<stop offset="0.805" stopColor="#A8A8A8" />
<stop offset="0.8581" stopColor="#888888" />
<stop offset="0.9069" stopColor="#626262" />
<stop offset="0.9523" stopColor="#373737" />
<stop offset="0.9926" stopColor="#090909" />
<stop offset="1" />
</linearGradient>
<clipPath id="clip0_36_2239">
<rect width="37" height="56" fill="white" transform="translate(10)"></rect>
</clipPath>
</defs>
</svg>
);
}
import React from 'react';
export default function BingIcon({ size = 25 }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 56 56"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_36_2239)">
<path
d="M46.9982 35.9868C46.9982 36.5323 46.9689 37.0747 46.9103 37.6092C46.5619 40.8696 45.1683 43.8178 43.0701 46.098C43.3344 45.8007 43.5726 45.4815 43.7815 45.1397C43.9426 44.8799 44.086 44.6091 44.207 44.3266C44.251 44.2337 44.291 44.137 44.3242 44.041C44.3643 43.9481 44.3974 43.8514 44.4267 43.7554C44.4599 43.6664 44.4892 43.5736 44.5146 43.4807C44.54 43.3839 44.5662 43.2879 44.5878 43.1912C44.5917 43.1803 44.5955 43.1685 44.5986 43.1576C44.621 43.0609 44.6387 42.9649 44.6572 42.8681C44.6757 42.7682 44.6942 42.6675 44.7088 42.5677C44.7088 42.5638 44.7088 42.5638 44.7088 42.5606C44.7235 42.4678 44.7343 42.3749 44.742 42.2781C44.7643 42.0589 44.7751 41.8404 44.7751 41.6172C44.7751 40.3624 44.4336 39.1848 43.8363 38.1828C43.7006 37.9487 43.5503 37.7263 43.3853 37.5148C43.1911 37.262 42.9822 37.0247 42.7548 36.8054C42.1898 36.2522 41.5299 35.7988 40.8 35.4796C40.4847 35.3384 40.1548 35.2236 39.8172 35.1378C39.8133 35.1378 39.8064 35.1339 39.8025 35.1339L39.6853 35.0933L37.9764 34.4995V34.4956L33.5056 32.9395C33.491 32.9356 33.4725 32.9356 33.4617 32.9325L33.1826 32.8287C32.2838 32.4721 31.5392 31.8041 31.0736 30.9535L29.4418 26.7387L27.571 21.9114L27.2118 20.9796L27.1201 20.79C27.0175 20.5372 26.962 20.2625 26.962 19.9769C26.962 19.9027 26.962 19.8286 26.9697 19.7615C27.0761 18.6994 27.9672 17.8676 29.0456 17.8676C29.3316 17.8676 29.6068 17.9269 29.8565 18.0346L38.1876 22.3593L39.831 23.2099C40.7005 23.7336 41.5107 24.35 42.2514 25.0446C44.9362 27.5402 46.6968 31.0378 46.9612 34.9482C46.9836 35.2931 46.9982 35.638 46.9982 35.9868Z"
fill="url(#paint0_linear_36_2239)"
/>
<path
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3341 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
fill="url(#paint1_linear_36_2239)"
/>
<path
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
fill="url(#paint2_linear_36_2239)"
/>
<path
opacity="0.15"
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3349 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
fill="url(#paint3_linear_36_2239)"
/>
<path
opacity="0.1"
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
fill="url(#paint4_linear_36_2239)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_36_2239"
x1="24.061"
y1="24.49"
x2="48.0304"
y2="38.1597"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#37BDFF" />
<stop offset="0.1832" stopColor="#33BFFD" />
<stop offset="0.3576" stopColor="#28C5F5" />
<stop offset="0.528" stopColor="#15D0E9" />
<stop offset="0.5468" stopColor="#12D1E7" />
<stop offset="0.5903" stopColor="#1CD2E5" />
<stop offset="0.7679" stopColor="#42D8DC" />
<stop offset="0.9107" stopColor="#59DBD6" />
<stop offset="1" stopColor="#62DCD4" />
</linearGradient>
<linearGradient
id="paint1_linear_36_2239"
x1="10.099"
y1="45.3798"
x2="44.7715"
y2="45.3798"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#39D2FF" />
<stop offset="0.1501" stopColor="#38CEFE" />
<stop offset="0.2931" stopColor="#35C3FA" />
<stop offset="0.4327" stopColor="#2FB0F3" />
<stop offset="0.5468" stopColor="#299AEB" />
<stop offset="0.5827" stopColor="#2692EC" />
<stop offset="0.7635" stopColor="#1A6CF1" />
<stop offset="0.909" stopColor="#1355F4" />
<stop offset="1" stopColor="#104CF5" />
</linearGradient>
<linearGradient
id="paint2_linear_36_2239"
x1="16.4996"
y1="48.4653"
x2="16.4996"
y2="1.52914"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#1B48EF" />
<stop offset="0.1221" stopColor="#1C51F0" />
<stop offset="0.3212" stopColor="#1E69F5" />
<stop offset="0.5676" stopColor="#2190FB" />
<stop offset="1" stopColor="#26B8F4" />
</linearGradient>
<linearGradient
id="paint3_linear_36_2239"
x1="16.9908"
y1="54.0427"
x2="38.6508"
y2="32.6475"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="0.3726" stopColor="#FDFDFD" />
<stop offset="0.5069" stopColor="#F6F6F6" />
<stop offset="0.6026" stopColor="#EBEBEB" />
<stop offset="0.68" stopColor="#DADADA" />
<stop offset="0.7463" stopColor="#C4C4C4" />
<stop offset="0.805" stopColor="#A8A8A8" />
<stop offset="0.8581" stopColor="#888888" />
<stop offset="0.9069" stopColor="#626262" />
<stop offset="0.9523" stopColor="#373737" />
<stop offset="0.9926" stopColor="#090909" />
<stop offset="1" />
</linearGradient>
<linearGradient
id="paint4_linear_36_2239"
x1="16.4996"
y1="0.696411"
x2="16.4996"
y2="47.9822"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="0.3726" stopColor="#FDFDFD" />
<stop offset="0.5069" stopColor="#F6F6F6" />
<stop offset="0.6026" stopColor="#EBEBEB" />
<stop offset="0.68" stopColor="#DADADA" />
<stop offset="0.7463" stopColor="#C4C4C4" />
<stop offset="0.805" stopColor="#A8A8A8" />
<stop offset="0.8581" stopColor="#888888" />
<stop offset="0.9069" stopColor="#626262" />
<stop offset="0.9523" stopColor="#373737" />
<stop offset="0.9926" stopColor="#090909" />
<stop offset="1" />
</linearGradient>
<clipPath id="clip0_36_2239">
<rect width="37" height="56" fill="white" transform="translate(10)"></rect>
</clipPath>
</defs>
</svg>
);
}

Some files were not shown because too many files have changed in this diff Show more