mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
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:
parent
fb99e5a7da
commit
956aa6c674
203 changed files with 5062 additions and 4327 deletions
19
.eslintrc.js
19
.eslintrc.js
|
@ -42,7 +42,6 @@ module.exports = {
|
|||
ignoreComments: true,
|
||||
},
|
||||
],
|
||||
'import/no-cycle': 'error',
|
||||
'linebreak-style': 0,
|
||||
curly: ['error', 'all'],
|
||||
semi: ['error', 'always'],
|
||||
|
@ -53,6 +52,8 @@ module.exports = {
|
|||
// "arrow-parens": [2, "as-needed", { requireForBlockBody: true }],
|
||||
// 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
|
||||
'no-console': 'off',
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-self-import': 'error',
|
||||
'import/extensions': 'off',
|
||||
'no-promise-executor-return': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
|
@ -100,7 +101,7 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
{
|
||||
files: '**/*.+(ts)',
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './client/tsconfig.json',
|
||||
|
@ -110,6 +111,9 @@ module.exports = {
|
|||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: './packages/data-provider/**/*.ts',
|
||||
|
@ -132,5 +136,16 @@ module.exports = {
|
|||
fragment: 'Fragment', // Fragment to use (may be a property of <pragma>), default to "Fragment"
|
||||
version: 'detect', // React version. "detect" automatically picks the version you have installed.
|
||||
},
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: ['./client/tsconfig.json'],
|
||||
},
|
||||
node: {
|
||||
project: ['./client/tsconfig.json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -32,10 +32,10 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
|||
token: req.body?.token ?? null,
|
||||
modelOptions: {
|
||||
model: req.body?.model ?? 'claude-1',
|
||||
temperature: req.body?.temperature ?? 0.7,
|
||||
temperature: req.body?.temperature ?? 1,
|
||||
maxOutputTokens: req.body?.maxOutputTokens ?? 1024,
|
||||
topP: req.body?.topP ?? 0.7,
|
||||
topK: req.body?.topK ?? 40,
|
||||
topK: req.body?.topK ?? 5,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -158,7 +158,10 @@ router.get('/', async function (req, res) {
|
|||
}
|
||||
: false;
|
||||
const bingAI = process.env.BINGAI_TOKEN
|
||||
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
|
||||
? {
|
||||
availableModels: ['BingAI', 'Sydney'],
|
||||
userProvide: process.env.BINGAI_TOKEN == 'user_provided',
|
||||
}
|
||||
: false;
|
||||
const chatGPTBrowser = process.env.CHATGPT_TOKEN
|
||||
? {
|
||||
|
|
|
@ -9,12 +9,9 @@ const requireJwtAuth = require('../../middleware/requireJwtAuth');
|
|||
router.post('/', requireJwtAuth, async (req, res) => {
|
||||
try {
|
||||
const { arg } = req.body;
|
||||
|
||||
// console.log('context:', arg, req.body);
|
||||
// console.log(typeof req.body === 'object' ? { ...req.body, ...req.query } : req.query);
|
||||
const model = await load(registry[models['gpt-3.5-turbo']]);
|
||||
const encoder = new Tiktoken(model.bpe_ranks, model.special_tokens, model.pat_str);
|
||||
const tokens = encoder.encode(arg.text);
|
||||
const tokens = encoder.encode(arg?.text ?? arg);
|
||||
encoder.free();
|
||||
res.send({ count: tokens.length });
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
["@babel/preset-env", { "targets": { "node": "current" } }], //compiling ES2015+ syntax
|
||||
['@babel/preset-react', {runtime: 'automatic'}],
|
||||
"@babel/preset-typescript"
|
||||
['@babel/preset-env', { 'targets': { 'node': 'current' } }], //compiling ES2015+ syntax
|
||||
['@babel/preset-react', { runtime: 'automatic' }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
/*
|
||||
Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
|
||||
*/
|
||||
plugins: [
|
||||
"@babel/plugin-transform-runtime",
|
||||
'@babel/plugin-transform-runtime',
|
||||
'babel-plugin-transform-import-meta',
|
||||
'babel-plugin-transform-vite-meta-env',
|
||||
'babel-plugin-replace-ts-export-assignment',
|
||||
[
|
||||
"babel-plugin-root-import",
|
||||
'babel-plugin-root-import',
|
||||
{
|
||||
"rootPathPrefix": "~/",
|
||||
"rootPathSuffix": "./src"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
'rootPathPrefix': '~/',
|
||||
'rootPathSuffix': './src',
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = {
|
|||
'\\.(css)$': 'identity-obj-proxy',
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'jest-file-loader',
|
||||
'layout-test-utils': '<rootDir>/test/layout-test-utils',
|
||||
'^test/(.*)$': '<rootDir>/test/$1',
|
||||
'^~/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
restoreMocks: true,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require("postcss-import"),
|
||||
require("postcss-preset-env"),
|
||||
require("tailwindcss"),
|
||||
require("autoprefixer"),
|
||||
]
|
||||
require('postcss-import'),
|
||||
require('postcss-preset-env'),
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { RouterProvider } from 'react-router-dom';
|
||||
import { ScreenshotProvider } from './utils/screenshotContext.jsx';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { QueryClient, QueryClientProvider, QueryCache } from '@tanstack/react-query';
|
||||
import { ThemeProvider } from './hooks/ThemeContext';
|
||||
import { useApiErrorBoundary } from './hooks/ApiErrorBoundaryContext';
|
||||
import { ScreenshotProvider, ThemeProvider, useApiErrorBoundary } from './hooks';
|
||||
import { router } from './routes';
|
||||
|
||||
const App = () => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
146
client/src/components/Endpoints/EditPresetDialog.tsx
Normal file
146
client/src/components/Endpoints/EditPresetDialog.tsx
Normal 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;
|
|
@ -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;
|
109
client/src/components/Endpoints/EndpointOptionsDialog.tsx
Normal file
109
client/src/components/Endpoints/EndpointOptionsDialog.tsx
Normal 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;
|
|
@ -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;
|
61
client/src/components/Endpoints/EndpointOptionsPopover.tsx
Normal file
61
client/src/components/Endpoints/EndpointOptionsPopover.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
59
client/src/components/Endpoints/EndpointSettings.tsx
Normal file
59
client/src/components/Endpoints/EndpointSettings.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,3 +0,0 @@
|
|||
export { default as AgentSettings } from './AgentSettings';
|
||||
export { default as OptionHover } from './OptionHover';
|
||||
export { default as Settings } from './Settings';
|
66
client/src/components/Endpoints/PopoverButtons.tsx
Normal file
66
client/src/components/Endpoints/PopoverButtons.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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;
|
67
client/src/components/Endpoints/SaveAsPresetDialog.tsx
Normal file
67
client/src/components/Endpoints/SaveAsPresetDialog.tsx
Normal 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;
|
|
@ -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;
|
242
client/src/components/Endpoints/Settings/AgentSettings.tsx
Normal file
242
client/src/components/Endpoints/Settings/AgentSettings.tsx
Normal 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>
|
||||
);
|
||||
}
|
235
client/src/components/Endpoints/Settings/Anthropic.tsx
Normal file
235
client/src/components/Endpoints/Settings/Anthropic.tsx
Normal 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>
|
||||
);
|
||||
}
|
149
client/src/components/Endpoints/Settings/BingAI.tsx
Normal file
149
client/src/components/Endpoints/Settings/BingAI.tsx
Normal 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>
|
||||
);
|
||||
}
|
88
client/src/components/Endpoints/Settings/Examples.tsx
Normal file
88
client/src/components/Endpoints/Settings/Examples.tsx
Normal 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;
|
259
client/src/components/Endpoints/Settings/Google.tsx
Normal file
259
client/src/components/Endpoints/Settings/Google.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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} />
|
||||
);
|
||||
}
|
|
@ -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} />
|
||||
);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { default as GoogleSettings } from './Google';
|
||||
export { default as PluginsSettings } from './Plugins';
|
254
client/src/components/Endpoints/Settings/OpenAI.tsx
Normal file
254
client/src/components/Endpoints/Settings/OpenAI.tsx
Normal 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>
|
||||
);
|
||||
}
|
53
client/src/components/Endpoints/Settings/OptionHover.tsx
Normal file
53
client/src/components/Endpoints/Settings/OptionHover.tsx
Normal 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;
|
274
client/src/components/Endpoints/Settings/Plugins.tsx
Normal file
274
client/src/components/Endpoints/Settings/Plugins.tsx
Normal 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>
|
||||
);
|
||||
}
|
7
client/src/components/Endpoints/Settings/index.ts
Normal file
7
client/src/components/Endpoints/Settings/index.ts
Normal 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';
|
|
@ -1,5 +1,5 @@
|
|||
import { Plugin, GPTIcon, AnthropicIcon } from '~/components/svg';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { useAuthContext } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const getIcon = (props) => {
|
6
client/src/components/Endpoints/index.ts
Normal file
6
client/src/components/Endpoints/index.ts
Normal 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';
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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';
|
|
@ -1,4 +1,4 @@
|
|||
import EndpointItem from './EndpointItem.jsx';
|
||||
import EndpointItem from './EndpointItem';
|
||||
|
||||
export default function EndpointItems({ endpoints, onSelect, selectedEndpoint }) {
|
||||
return (
|
|
@ -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
|
|
@ -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;
|
||||
};
|
|
@ -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,14 +61,18 @@ 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"
|
||||
>
|
||||
<div className="flex items-center justify-start">
|
||||
{icon}
|
||||
<small className="text-[11px]">{preset?.title}</small>
|
||||
<small className="ml-2 text-[10px]">({getPresetTitle()})</small>
|
||||
<div className="flex w-4 flex-1" />
|
||||
<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="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 "
|
||||
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);
|
||||
|
@ -79,7 +81,7 @@ export default function PresetItem({ preset = {}, value, onChangePreset, onDelet
|
|||
<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 "
|
||||
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);
|
||||
|
@ -87,6 +89,7 @@ export default function PresetItem({ preset = {}, value, onChangePreset, onDelet
|
|||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
}
|
|
@ -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 (
|
1
client/src/components/Input/EndpointMenu/index.ts
Normal file
1
client/src/components/Input/EndpointMenu/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as EndpointMenu } from './EndpointMenu';
|
47
client/src/components/Input/GenerationButtons.tsx
Normal file
47
client/src/components/Input/GenerationButtons.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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;
|
19
client/src/components/Input/ModelSelect/Anthropic.tsx
Normal file
19
client/src/components/Input/ModelSelect/Anthropic.tsx
Normal 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',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
78
client/src/components/Input/ModelSelect/BingAI.tsx
Normal file
78
client/src/components/Input/ModelSelect/BingAI.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
27
client/src/components/Input/ModelSelect/ChatGPT.tsx
Normal file
27
client/src/components/Input/ModelSelect/ChatGPT.tsx
Normal 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',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
19
client/src/components/Input/ModelSelect/Google.tsx
Normal file
19
client/src/components/Input/ModelSelect/Google.tsx
Normal 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',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
39
client/src/components/Input/ModelSelect/ModelSelect.tsx
Normal file
39
client/src/components/Input/ModelSelect/ModelSelect.tsx
Normal 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} />;
|
||||
}
|
19
client/src/components/Input/ModelSelect/OpenAI.tsx
Normal file
19
client/src/components/Input/ModelSelect/OpenAI.tsx
Normal 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',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
111
client/src/components/Input/ModelSelect/Plugins.tsx
Normal file
111
client/src/components/Input/ModelSelect/Plugins.tsx
Normal 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',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
1
client/src/components/Input/ModelSelect/index.ts
Normal file
1
client/src/components/Input/ModelSelect/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as ModelSelect } from './ModelSelect';
|
|
@ -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;
|
135
client/src/components/Input/OptionsBar.tsx
Normal file
135
client/src/components/Input/OptionsBar.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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);
|
|
@ -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 (
|
||||
|
|
|
@ -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,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -45,12 +45,14 @@ 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]" />
|
||||
<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
|
||||
open={setTokenDialogOpen}
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => ({
|
||||
setConversation(
|
||||
(prevState) =>
|
||||
({
|
||||
...prevState,
|
||||
tools,
|
||||
}));
|
||||
} as TConversation),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
/>,
|
||||
|
|
|
@ -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"
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue