mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 19:00:13 +01: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
|
|
@ -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';
|
||||
114
client/src/components/Endpoints/getIcon.jsx
Normal file
114
client/src/components/Endpoints/getIcon.jsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { Plugin, GPTIcon, AnthropicIcon } from '~/components/svg';
|
||||
import { useAuthContext } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const getIcon = (props) => {
|
||||
const { size = 30, isCreatedByUser, button, model, message = true } = props;
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { user } = useAuthContext();
|
||||
|
||||
if (isCreatedByUser) {
|
||||
return (
|
||||
<div
|
||||
title={user?.name || 'User'}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
className={'relative flex items-center justify-center' + props?.className}
|
||||
>
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar ||
|
||||
`https://api.dicebear.com/6.x/initials/svg?seed=${
|
||||
user?.name || 'User'
|
||||
}&fontFamily=Verdana&fontSize=36`
|
||||
}
|
||||
alt="avatar"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (!isCreatedByUser) {
|
||||
const { endpoint, error } = props;
|
||||
|
||||
let icon, bg, name;
|
||||
if (endpoint === 'azureOpenAI') {
|
||||
const { chatGptLabel } = props;
|
||||
icon = <GPTIcon size={size * 0.7} />;
|
||||
bg = 'linear-gradient(0.375turn, #61bde2, #4389d0)';
|
||||
name = chatGptLabel || 'ChatGPT';
|
||||
} else if (endpoint === 'openAI' || (endpoint === 'gptPlugins' && message)) {
|
||||
const { chatGptLabel } = props;
|
||||
icon = <GPTIcon size={size * 0.7} />;
|
||||
bg =
|
||||
model && model.toLowerCase().startsWith('gpt-4')
|
||||
? '#AB68FF'
|
||||
: chatGptLabel
|
||||
? `rgba(16, 163, 127, ${button ? 0.75 : 1})`
|
||||
: `rgba(16, 163, 127, ${button ? 0.75 : 1})`;
|
||||
name = chatGptLabel || 'ChatGPT';
|
||||
} else if (endpoint === 'gptPlugins' && !message) {
|
||||
icon = <Plugin size={size * 0.7} />;
|
||||
bg = `rgba(69, 89, 164, ${button ? 0.75 : 1})`;
|
||||
name = 'Plugins';
|
||||
} else if (endpoint === 'google') {
|
||||
const { modelLabel } = props;
|
||||
icon = <img src="/assets/google-palm.svg" alt="Palm Icon" />;
|
||||
name = modelLabel || 'PaLM2';
|
||||
} else if (endpoint === 'anthropic') {
|
||||
const { modelLabel } = props;
|
||||
icon = <AnthropicIcon size={size * 0.7} />;
|
||||
bg = '#d09a74';
|
||||
name = modelLabel || 'Claude';
|
||||
} else if (endpoint === 'bingAI') {
|
||||
const { jailbreak } = props;
|
||||
if (jailbreak) {
|
||||
icon = <img src="/assets/bingai-jb.png" alt="Bing Icon" />;
|
||||
name = 'Sydney';
|
||||
} else {
|
||||
icon = <img src="/assets/bingai.png" alt="Sydney Icon" />;
|
||||
name = 'BingAI';
|
||||
}
|
||||
} else if (endpoint === 'chatGPTBrowser') {
|
||||
icon = <GPTIcon size={size * 0.7} />;
|
||||
bg =
|
||||
model && model.toLowerCase().startsWith('gpt-4')
|
||||
? '#AB68FF'
|
||||
: `rgba(0, 163, 255, ${button ? 0.75 : 1})`;
|
||||
name = 'ChatGPT';
|
||||
} else if (endpoint === null) {
|
||||
icon = <GPTIcon size={size * 0.7} />;
|
||||
bg = 'grey';
|
||||
name = 'N/A';
|
||||
} else {
|
||||
icon = <GPTIcon size={size * 0.7} />;
|
||||
bg = 'grey';
|
||||
name = 'UNKNOWN';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
title={name}
|
||||
style={{
|
||||
background: bg || 'transparent',
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
className={cn(
|
||||
'relative flex items-center justify-center rounded-sm text-white ',
|
||||
props?.className ?? '',
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
{error && (
|
||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
||||
!
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default getIcon;
|
||||
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,30 +61,35 @@ export default function PresetItem({ preset = {}, value, onChangePreset, onDelet
|
|||
return (
|
||||
<DropdownMenuRadioItem
|
||||
value={value}
|
||||
className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||
className="group flex h-10 max-h-[44px] flex-row justify-between dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800 sm:h-auto"
|
||||
>
|
||||
{icon}
|
||||
<small className="text-[11px]">{preset?.title}</small>
|
||||
<small className="ml-2 text-[10px]">({getPresetTitle()})</small>
|
||||
<div className="flex w-4 flex-1" />
|
||||
<button
|
||||
className="invisible m-0 mr-1 rounded-md p-2 text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onChangePreset(preset);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
className="invisible m-0 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onDeletePreset(preset);
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<div className="flex items-center justify-start">
|
||||
{icon}
|
||||
<small className="text-[11px]">{preset?.title}</small>
|
||||
<small className="invisible ml-1 flex w-0 flex-shrink text-[10px] sm:visible sm:w-auto">
|
||||
({getPresetTitle()})
|
||||
</small>
|
||||
</div>
|
||||
<div className="flex h-full items-center justify-end">
|
||||
<button
|
||||
className="m-0 mr-1 h-full rounded-md px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:p-2 sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onChangePreset(preset);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
className="m-0 h-full rounded-md px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:p-2 sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onDeletePreset(preset);
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
}
|
||||
|
|
@ -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,11 +45,13 @@ export default function SubmitButton({
|
|||
<button
|
||||
onClick={setToken}
|
||||
type="button"
|
||||
className="group absolute bottom-0 right-0 z-[101] flex h-[100%] w-auto items-center justify-center bg-transparent p-1 text-gray-500"
|
||||
className="group absolute bottom-0 right-0 z-[101] flex h-[100%] w-auto items-center justify-center bg-transparent pr-1 text-gray-500"
|
||||
>
|
||||
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] align-middle text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||
<Settings className="mr-1 inline-block w-[18px]" />
|
||||
Set Token First
|
||||
<div className="flex items-center justify-center rounded-md text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||
<div className="m-0 mr-0 flex items-center justify-center rounded-md p-2 sm:p-2">
|
||||
<Settings className="mr-1 inline-block h-auto w-[18px]" />
|
||||
Set Token First
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<SetTokenDialog
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
...prevState,
|
||||
tools,
|
||||
}));
|
||||
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"
|
||||
|
|
@ -1,135 +1,135 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function BingIcon({ size = 25 }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_36_2239)">
|
||||
<path
|
||||
d="M46.9982 35.9868C46.9982 36.5323 46.9689 37.0747 46.9103 37.6092C46.5619 40.8696 45.1683 43.8178 43.0701 46.098C43.3344 45.8007 43.5726 45.4815 43.7815 45.1397C43.9426 44.8799 44.086 44.6091 44.207 44.3266C44.251 44.2337 44.291 44.137 44.3242 44.041C44.3643 43.9481 44.3974 43.8514 44.4267 43.7554C44.4599 43.6664 44.4892 43.5736 44.5146 43.4807C44.54 43.3839 44.5662 43.2879 44.5878 43.1912C44.5917 43.1803 44.5955 43.1685 44.5986 43.1576C44.621 43.0609 44.6387 42.9649 44.6572 42.8681C44.6757 42.7682 44.6942 42.6675 44.7088 42.5677C44.7088 42.5638 44.7088 42.5638 44.7088 42.5606C44.7235 42.4678 44.7343 42.3749 44.742 42.2781C44.7643 42.0589 44.7751 41.8404 44.7751 41.6172C44.7751 40.3624 44.4336 39.1848 43.8363 38.1828C43.7006 37.9487 43.5503 37.7263 43.3853 37.5148C43.1911 37.262 42.9822 37.0247 42.7548 36.8054C42.1898 36.2522 41.5299 35.7988 40.8 35.4796C40.4847 35.3384 40.1548 35.2236 39.8172 35.1378C39.8133 35.1378 39.8064 35.1339 39.8025 35.1339L39.6853 35.0933L37.9764 34.4995V34.4956L33.5056 32.9395C33.491 32.9356 33.4725 32.9356 33.4617 32.9325L33.1826 32.8287C32.2838 32.4721 31.5392 31.8041 31.0736 30.9535L29.4418 26.7387L27.571 21.9114L27.2118 20.9796L27.1201 20.79C27.0175 20.5372 26.962 20.2625 26.962 19.9769C26.962 19.9027 26.962 19.8286 26.9697 19.7615C27.0761 18.6994 27.9672 17.8676 29.0456 17.8676C29.3316 17.8676 29.6068 17.9269 29.8565 18.0346L38.1876 22.3593L39.831 23.2099C40.7005 23.7336 41.5107 24.35 42.2514 25.0446C44.9362 27.5402 46.6968 31.0378 46.9612 34.9482C46.9836 35.2931 46.9982 35.638 46.9982 35.9868Z"
|
||||
fill="url(#paint0_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3341 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
|
||||
fill="url(#paint1_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
|
||||
fill="url(#paint2_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
opacity="0.15"
|
||||
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3349 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
|
||||
fill="url(#paint3_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
|
||||
fill="url(#paint4_linear_36_2239)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_36_2239"
|
||||
x1="24.061"
|
||||
y1="24.49"
|
||||
x2="48.0304"
|
||||
y2="38.1597"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#37BDFF" />
|
||||
<stop offset="0.1832" stopColor="#33BFFD" />
|
||||
<stop offset="0.3576" stopColor="#28C5F5" />
|
||||
<stop offset="0.528" stopColor="#15D0E9" />
|
||||
<stop offset="0.5468" stopColor="#12D1E7" />
|
||||
<stop offset="0.5903" stopColor="#1CD2E5" />
|
||||
<stop offset="0.7679" stopColor="#42D8DC" />
|
||||
<stop offset="0.9107" stopColor="#59DBD6" />
|
||||
<stop offset="1" stopColor="#62DCD4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_36_2239"
|
||||
x1="10.099"
|
||||
y1="45.3798"
|
||||
x2="44.7715"
|
||||
y2="45.3798"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#39D2FF" />
|
||||
<stop offset="0.1501" stopColor="#38CEFE" />
|
||||
<stop offset="0.2931" stopColor="#35C3FA" />
|
||||
<stop offset="0.4327" stopColor="#2FB0F3" />
|
||||
<stop offset="0.5468" stopColor="#299AEB" />
|
||||
<stop offset="0.5827" stopColor="#2692EC" />
|
||||
<stop offset="0.7635" stopColor="#1A6CF1" />
|
||||
<stop offset="0.909" stopColor="#1355F4" />
|
||||
<stop offset="1" stopColor="#104CF5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_36_2239"
|
||||
x1="16.4996"
|
||||
y1="48.4653"
|
||||
x2="16.4996"
|
||||
y2="1.52914"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#1B48EF" />
|
||||
<stop offset="0.1221" stopColor="#1C51F0" />
|
||||
<stop offset="0.3212" stopColor="#1E69F5" />
|
||||
<stop offset="0.5676" stopColor="#2190FB" />
|
||||
<stop offset="1" stopColor="#26B8F4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_36_2239"
|
||||
x1="16.9908"
|
||||
y1="54.0427"
|
||||
x2="38.6508"
|
||||
y2="32.6475"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="white" />
|
||||
<stop offset="0.3726" stopColor="#FDFDFD" />
|
||||
<stop offset="0.5069" stopColor="#F6F6F6" />
|
||||
<stop offset="0.6026" stopColor="#EBEBEB" />
|
||||
<stop offset="0.68" stopColor="#DADADA" />
|
||||
<stop offset="0.7463" stopColor="#C4C4C4" />
|
||||
<stop offset="0.805" stopColor="#A8A8A8" />
|
||||
<stop offset="0.8581" stopColor="#888888" />
|
||||
<stop offset="0.9069" stopColor="#626262" />
|
||||
<stop offset="0.9523" stopColor="#373737" />
|
||||
<stop offset="0.9926" stopColor="#090909" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_36_2239"
|
||||
x1="16.4996"
|
||||
y1="0.696411"
|
||||
x2="16.4996"
|
||||
y2="47.9822"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="white" />
|
||||
<stop offset="0.3726" stopColor="#FDFDFD" />
|
||||
<stop offset="0.5069" stopColor="#F6F6F6" />
|
||||
<stop offset="0.6026" stopColor="#EBEBEB" />
|
||||
<stop offset="0.68" stopColor="#DADADA" />
|
||||
<stop offset="0.7463" stopColor="#C4C4C4" />
|
||||
<stop offset="0.805" stopColor="#A8A8A8" />
|
||||
<stop offset="0.8581" stopColor="#888888" />
|
||||
<stop offset="0.9069" stopColor="#626262" />
|
||||
<stop offset="0.9523" stopColor="#373737" />
|
||||
<stop offset="0.9926" stopColor="#090909" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_36_2239">
|
||||
<rect width="37" height="56" fill="white" transform="translate(10)"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
|
||||
export default function BingIcon({ size = 25 }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_36_2239)">
|
||||
<path
|
||||
d="M46.9982 35.9868C46.9982 36.5323 46.9689 37.0747 46.9103 37.6092C46.5619 40.8696 45.1683 43.8178 43.0701 46.098C43.3344 45.8007 43.5726 45.4815 43.7815 45.1397C43.9426 44.8799 44.086 44.6091 44.207 44.3266C44.251 44.2337 44.291 44.137 44.3242 44.041C44.3643 43.9481 44.3974 43.8514 44.4267 43.7554C44.4599 43.6664 44.4892 43.5736 44.5146 43.4807C44.54 43.3839 44.5662 43.2879 44.5878 43.1912C44.5917 43.1803 44.5955 43.1685 44.5986 43.1576C44.621 43.0609 44.6387 42.9649 44.6572 42.8681C44.6757 42.7682 44.6942 42.6675 44.7088 42.5677C44.7088 42.5638 44.7088 42.5638 44.7088 42.5606C44.7235 42.4678 44.7343 42.3749 44.742 42.2781C44.7643 42.0589 44.7751 41.8404 44.7751 41.6172C44.7751 40.3624 44.4336 39.1848 43.8363 38.1828C43.7006 37.9487 43.5503 37.7263 43.3853 37.5148C43.1911 37.262 42.9822 37.0247 42.7548 36.8054C42.1898 36.2522 41.5299 35.7988 40.8 35.4796C40.4847 35.3384 40.1548 35.2236 39.8172 35.1378C39.8133 35.1378 39.8064 35.1339 39.8025 35.1339L39.6853 35.0933L37.9764 34.4995V34.4956L33.5056 32.9395C33.491 32.9356 33.4725 32.9356 33.4617 32.9325L33.1826 32.8287C32.2838 32.4721 31.5392 31.8041 31.0736 30.9535L29.4418 26.7387L27.571 21.9114L27.2118 20.9796L27.1201 20.79C27.0175 20.5372 26.962 20.2625 26.962 19.9769C26.962 19.9027 26.962 19.8286 26.9697 19.7615C27.0761 18.6994 27.9672 17.8676 29.0456 17.8676C29.3316 17.8676 29.6068 17.9269 29.8565 18.0346L38.1876 22.3593L39.831 23.2099C40.7005 23.7336 41.5107 24.35 42.2514 25.0446C44.9362 27.5402 46.6968 31.0378 46.9612 34.9482C46.9836 35.2931 46.9982 35.638 46.9982 35.9868Z"
|
||||
fill="url(#paint0_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3341 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
|
||||
fill="url(#paint1_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
|
||||
fill="url(#paint2_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
opacity="0.15"
|
||||
d="M44.7717 41.6165C44.7717 42.0472 44.7316 42.4631 44.6576 42.8682C44.6353 42.9758 44.6137 43.0835 44.5883 43.1912C44.5405 43.384 44.4896 43.5697 44.4272 43.7554C44.394 43.8522 44.3609 43.9482 44.3246 44.041C44.2876 44.1378 44.2475 44.2307 44.2075 44.3267C44.0864 44.6092 43.9431 44.8799 43.782 45.1398C43.5731 45.4816 43.3349 45.8008 43.0705 46.0981C41.8564 47.4575 37.7333 49.8813 36.214 50.8661L32.8408 52.9528C30.3695 54.4948 28.0324 55.5858 25.087 55.6599C24.9475 55.6638 24.8119 55.6677 24.6762 55.6677C24.4858 55.6677 24.2985 55.6638 24.1112 55.6568C19.1231 55.464 14.7726 52.753 12.2643 48.7466C11.1165 46.9159 10.3573 44.8144 10.1006 42.5568C10.6394 45.6424 13.2957 47.9819 16.4977 47.9819C17.62 47.9819 18.673 47.6963 19.5933 47.1906C19.6003 47.1867 19.608 47.1828 19.6157 47.1797L19.9456 46.9791L21.2884 46.1769L22.9973 45.1523V45.1039L23.2178 44.9705L38.5095 35.7988L39.6866 35.0934L39.8037 35.134C39.8076 35.134 39.8145 35.1379 39.8184 35.1379C40.156 35.2229 40.4859 35.3384 40.8012 35.4797C41.5311 35.7988 42.191 36.2522 42.756 36.8055C42.9834 37.0248 43.1923 37.262 43.3865 37.5149C43.5515 37.7263 43.7018 37.9495 43.8375 38.1828C44.4302 39.1841 44.7717 40.3616 44.7717 41.6165Z"
|
||||
fill="url(#paint3_linear_36_2239)"
|
||||
/>
|
||||
<path
|
||||
opacity="0.1"
|
||||
d="M23.0013 11.0082L22.9959 45.1507L21.287 46.1761L19.9434 46.9775L19.6127 47.1804C19.6073 47.1804 19.5973 47.1859 19.5927 47.1906C18.6708 47.6931 17.6178 47.9826 16.4947 47.9826C13.2919 47.9826 10.6403 45.6431 10.0984 42.5575C10.0729 42.4155 10.0537 42.268 10.0383 42.126C10.0182 41.8568 10.0036 41.593 9.99817 41.3238V2.8986C9.99817 1.68591 10.971 0.696411 12.1734 0.696411C12.6244 0.696411 13.0453 0.838438 13.3914 1.07177L20.0428 5.47146C20.0783 5.5019 20.1176 5.52765 20.1585 5.55262C21.8782 6.74034 23.0013 8.73963 23.0013 11.0082Z"
|
||||
fill="url(#paint4_linear_36_2239)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_36_2239"
|
||||
x1="24.061"
|
||||
y1="24.49"
|
||||
x2="48.0304"
|
||||
y2="38.1597"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#37BDFF" />
|
||||
<stop offset="0.1832" stopColor="#33BFFD" />
|
||||
<stop offset="0.3576" stopColor="#28C5F5" />
|
||||
<stop offset="0.528" stopColor="#15D0E9" />
|
||||
<stop offset="0.5468" stopColor="#12D1E7" />
|
||||
<stop offset="0.5903" stopColor="#1CD2E5" />
|
||||
<stop offset="0.7679" stopColor="#42D8DC" />
|
||||
<stop offset="0.9107" stopColor="#59DBD6" />
|
||||
<stop offset="1" stopColor="#62DCD4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_36_2239"
|
||||
x1="10.099"
|
||||
y1="45.3798"
|
||||
x2="44.7715"
|
||||
y2="45.3798"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#39D2FF" />
|
||||
<stop offset="0.1501" stopColor="#38CEFE" />
|
||||
<stop offset="0.2931" stopColor="#35C3FA" />
|
||||
<stop offset="0.4327" stopColor="#2FB0F3" />
|
||||
<stop offset="0.5468" stopColor="#299AEB" />
|
||||
<stop offset="0.5827" stopColor="#2692EC" />
|
||||
<stop offset="0.7635" stopColor="#1A6CF1" />
|
||||
<stop offset="0.909" stopColor="#1355F4" />
|
||||
<stop offset="1" stopColor="#104CF5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_36_2239"
|
||||
x1="16.4996"
|
||||
y1="48.4653"
|
||||
x2="16.4996"
|
||||
y2="1.52914"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#1B48EF" />
|
||||
<stop offset="0.1221" stopColor="#1C51F0" />
|
||||
<stop offset="0.3212" stopColor="#1E69F5" />
|
||||
<stop offset="0.5676" stopColor="#2190FB" />
|
||||
<stop offset="1" stopColor="#26B8F4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_36_2239"
|
||||
x1="16.9908"
|
||||
y1="54.0427"
|
||||
x2="38.6508"
|
||||
y2="32.6475"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="white" />
|
||||
<stop offset="0.3726" stopColor="#FDFDFD" />
|
||||
<stop offset="0.5069" stopColor="#F6F6F6" />
|
||||
<stop offset="0.6026" stopColor="#EBEBEB" />
|
||||
<stop offset="0.68" stopColor="#DADADA" />
|
||||
<stop offset="0.7463" stopColor="#C4C4C4" />
|
||||
<stop offset="0.805" stopColor="#A8A8A8" />
|
||||
<stop offset="0.8581" stopColor="#888888" />
|
||||
<stop offset="0.9069" stopColor="#626262" />
|
||||
<stop offset="0.9523" stopColor="#373737" />
|
||||
<stop offset="0.9926" stopColor="#090909" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_36_2239"
|
||||
x1="16.4996"
|
||||
y1="0.696411"
|
||||
x2="16.4996"
|
||||
y2="47.9822"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="white" />
|
||||
<stop offset="0.3726" stopColor="#FDFDFD" />
|
||||
<stop offset="0.5069" stopColor="#F6F6F6" />
|
||||
<stop offset="0.6026" stopColor="#EBEBEB" />
|
||||
<stop offset="0.68" stopColor="#DADADA" />
|
||||
<stop offset="0.7463" stopColor="#C4C4C4" />
|
||||
<stop offset="0.805" stopColor="#A8A8A8" />
|
||||
<stop offset="0.8581" stopColor="#888888" />
|
||||
<stop offset="0.9069" stopColor="#626262" />
|
||||
<stop offset="0.9523" stopColor="#373737" />
|
||||
<stop offset="0.9926" stopColor="#090909" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_36_2239">
|
||||
<rect width="37" height="56" fill="white" transform="translate(10)"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue