Compare commits

...

40 Commits

Author SHA1 Message Date
Fuegovic
f101419af3 docs: general update (#781)
* Update windows_install.md

* Update linux_install.md

* Update mac_install.md

* Update docker_install.md

* Update linux_install.md

* Update windows_install.md

* Update README.md

* Update breaking_changes.md

* Update breaking_changes.md
2023-08-09 13:38:17 -04:00
Danny Avila
251d8ac410 Release 0.5.7 (#780) 2023-08-09 12:22:05 -04:00
Danny Avila
bdccadbe06 fix(playwright.yml): fix condition for running Playwright tests on pull requests
The condition for running Playwright tests on pull requests was not properly formatted. The repository name was not enclosed in quotes. This commit fixes the condition by adding single quotes around the repository name.
2023-08-09 10:28:04 -04:00
Danny Avila
70a56ac04a chore(client): update @vitejs/plugin-react to version 4.0.4
chore(client): update vite to version 4.4.9
2023-08-09 10:28:04 -04:00
Danny Avila
193ed93ee8 chore(style.css): update font paths to use a variable for the fonts directory
chore(vite.config.ts): import the resolve function from the path module
2023-08-09 10:28:04 -04:00
Danny Avila
b096fb98ce chore: remove next.js artifacts from ui primitives 2023-08-09 10:28:04 -04:00
Danny Avila
002bba20f9 refactor(Slider.tsx): remove unused import and type declaration
fix(Slider.tsx): fix type error in onClick event handler
2023-08-09 10:28:04 -04:00
Fuegovic
b896225bd8 Language Translation: French (#778) 2023-08-09 09:27:32 -04:00
XHyperDEVX
de34d8b47c feat: German translations (#772)
* Language translation: German translation

* Language translation: German translation

* Language translation: German translation
2023-08-08 11:48:56 -04:00
Danny Avila
759e585c29 chore(playwright.yml): add condition to run the job only for pull requests from danny-avila/LibreChat repository (#773)
chore(playwright.yml): comment out caching of Node.js modules and Playwright installations
2023-08-08 11:35:17 -04:00
Danny Avila
c6f5d5d65c ci: add e2e workflow, optimize client code for testing (#771)
* refactor(e2e): fix tests with latest changes, convert to TS, use test Ids

* chore(EndpointMenu.jsx): add data-testid attribute to new-conversation-menu button

* refactor(EndpointItem): add data-testid attr., convert to TS

* refactor(e2e): remove unnecessary awaits and convert to TS

* chore(playwright.config.local.ts): add absolute path to server index.js file
chore(playwright.config.local.ts): add dotenv configuration
chore(playwright.config.local.ts): change webServer command to use absolute path
chore(playwright.config.local.ts): add NODE_ENV and process.env to webServer env
chore(playwright.config.local.ts): remove unused import
chore(login.spec.js): delete login.spec.js file

* chore(.gitignore): add 'my.secrets' to the list of ignored files
fix(Registration.tsx): add 'data-testid' attribute to the error message div
fix(Registration.spec.tsx): comment out test case that calls 'registerUser.mutate'

* chore(ConvoIcon.tsx): add data-testid attribute to svg element
chore(messages.spec.ts): refactor conversation navigation logic

* chore(playwright.config.ts): add support for absolute path to server index.js file
feat(playwright.config.ts): add support for dotenv configuration
feat(playwright.config.ts): set NODE_ENV to 'production' in webServer environment variables

* chore(workflows): comment out push event and specify paths for pull_request event in backend-review.yml
chore(workflows): comment out push event and specify paths for pull_request event in frontend-review.yml

* chore(install.js): add check to skip install script in CI environment

* chore: complete playwright workflow

* chore(Landing.tsx): add data-testid attribute to landing title element
chore(authenticate.ts): update selector to wait for landing title element by test id instead of text content

* chore(playwright.yml): add step to upload screenshot artifact on failure
fix(authenticate.ts): capture screenshot before waiting for landing title and increase timeout due to GH Actions load time

* chore(playwright.yml): rename artifact name from 'screenshot' to 'login-screenshot'
feat(LoginForm.tsx): add data-testid attribute to login button
fix(authenticate.ts): change screenshot name to 'login-screenshot.png' and conditionally take screenshot only in CI environment

* chore(playwright.yml): add CI environment variable and set it to true

* chore(playwright.yml): update Playwright installation command
chore(playwright.config.ts): update storageState path to use process.cwd()

* fix(playwright.yml): update node version to 18 in setup-node action
fix(playwright.yml): update actions/cache to v3 in Cache Node.js modules step
fix(playwright.yml): update actions/cache to v3 in Cache Playwright installations step
fix(authenticate.ts): change login button click to press 'Enter' on password input

* chore(playwright.yml): update E2E_USER_EMAIL and E2E_USER_PASSWORD values for testing purposes
chore(authenticate.ts): add console.dir to log user object for debugging

* chore(playwright.yml): add step to upload storageState artifact

The storageState artifact is now uploaded as part of the workflow. This artifact contains the state of the storage used during the end-to-end tests. It will be retained for 2 days.

* chore(playwright.yml): comment out upload screenshot step
chore(playwright.config.ts): change NODE_ENV to development
chore(authenticate.ts): comment out screenshot related code

* chore(playwright.config.ts): add SESSION_EXPIRY environment variable with value 86400000

* chore(playwright.yml): update environment variables in Playwright workflow
fix(General.tsx): add data-testid attributes to clear conversations buttons
test(messages.spec.ts): add setup and teardown steps for clearing conversations before and after tests

* fix(messages.spec.ts): fix clearing conversations before and after message tests
feat(messages.spec.ts): add beforeEach and afterEach hooks to create and close new page for each test

* chore: remove storageStage upload artifact
2023-08-08 11:17:15 -04:00
Danny Avila
cb3cf9b33e chore: Update pull_request_template.md 2023-08-07 12:27:48 -04:00
Danny Avila
68ad46a9be fix(typing): minor typing resolutions and convert SearchBar to TS (#766)
* refactor(SearchBar): convert to TS, useLocalize

* fix(typing): minor type issues

* chore(package.json): add 'reinstall:docker' script for rebuilding Docker environment in current branch
2023-08-06 21:30:16 -04:00
Danny Avila
600a0d15b1 fix(backend-review.yml): update Node.js version from 19.x to 20.x (#767)
fix(frontend-review.yml): update Node.js version from 19.x to 20.x
2023-08-06 13:08:23 -04:00
Danny Avila
92f87b8dcc fix: strict typescript issue, plugins localStorage, both causing App Errors (#765)
* fix(Enum): cannot be used as a value when imported as type

* hotfix(types): corrected types, some causing application error (bing null model)

* hotfix(Plugins): fix undefined localStorage item causing Application error
2023-08-06 11:26:37 -04:00
Danny Avila
06a7fba39b fix(Enum): cannot be used as a value when imported as type (#764) 2023-08-05 20:23:07 -04:00
Dan Orlando
96d29f7390 refactor(client): Refactors recent typescript changes for best practices (#763)
* create common types in client

* remove unnecessary rules from eslint config

* cleanup types

* put back eslintrc rules
2023-08-05 16:45:26 -04:00
Danny Avila
5828200197 refactor(types): use zod for better type safety, style(Messages): new scroll behavior, style(Buttons): match ChatGPT (#761)
* feat: add zod schemas for better type safety

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

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

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

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

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

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

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

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

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

* style(ModelSelect): remove green rings which flicker

* style(scrollToBottom): add two separate scrolling functions

* fix(OptionsBar.tsx): handle onFocus and onBlur events to update opacityClass
fix(Messages/index.jsx): increase debounce time for scrollIntoView function
2023-08-05 12:10:36 -04:00
Anirudh
173b8ce2da feat: Add SearchBar component to Nav (#760)
* feat: Add SearchBar component to Nav

This commit adds the SearchBar component to the navigation bar in order to enable search functionality. Now users can easily search for specific items within the navigation.

* Refactor Nav and SearchBar components

The commit refactors the Nav component by moving the SearchBar component within the Nav component. This change ensures that the SearchBar is rendered only when the isSearchEnabled condition is true.

In addition, the commit also modifies the styling of the SearchBar component by adding rounded corners and border to enhance the visual appearance.

* Update gitignore

* C

Refactor search bar styles

This commit refactors the styles of the search bar component in the Nav component. The border color and hover background color have been modified to improve the visual appearance.

* Fix margin

* Rename Logout.jsx to Logout.tsx and update import statements accordingly.
Replace the use of Recoil and store with useLocalize hook for localization.
Update the usage of localize function by removing the lang parameter.
2023-08-05 10:24:56 -04:00
Danny Avila
1f5a79f073 fix(Plugins.tsx): fix incorrect params (mismatch) of setOption calls for frequency_penalty and presence_penalty (#757) 2023-08-04 22:33:06 -04:00
Marco Beretta
495ffaeb06 Update README.md (#754) 2023-08-04 17:19:34 -04:00
Danny Avila
c7b586ba4c refactor(Nav): improve toggle animation, refactor to TS (#755)
* style(Nav): match transition effect of official site

* fix(Pages): fix bug when searchResults pageSize is < prev PageSize causes currentPage to be impossible value

* refactor/fix(Nav): fix width transition animation and refactor to TS
2023-08-04 16:51:23 -04:00
Danny Avila
d6dbd56e33 Update mkdocs.yml 2023-08-04 14:41:34 -04:00
Danny Avila
315faf707e Update and rename azure.md to azure-terraform.md with repo instructions 2023-08-04 14:40:56 -04:00
AndrejG82
d79f585052 fix: allow setting username in openIdStrategy (#744)
* fixed bug in setting username from openid jwt token

* bugfix - changed , to ; at end of line
2023-08-04 14:26:14 -04:00
Alex
6ee0dbfdbd docs: Add Azure Instructions (Terraform) to Cloud Deployments Guide (#738)
* added Azure to cloud Deployments

* Update devcontainer.json

added git feature to devcontainer

* updated azure deployment docs
2023-08-04 14:23:41 -04:00
Danny Avila
60d0e97425 chore(data-provider): update package.json to add @types/react dependency (#753)
feat(data-provider): import React in types.ts file
2023-08-04 14:17:06 -04:00
Danny Avila
956aa6c674 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
2023-08-04 13:56:44 -04:00
Marco Beretta
fb99e5a7da docs: added how to setup a email service (#737)
* Update README.md

* Update mkdocs.yml

* Create email.md

* Delete email.md

* Update user_auth_system.md

* Update README.md

* Update mkdocs.yml
2023-08-01 08:14:31 -04:00
Marco Beretta
0630b54193 docs: add how to add a language (#732)
* Create language-contributions.md

* Update language-contributions.md

* Update README.md

* Update mkdocs.yml

* Update README.md

* Update language-contributions.md

* Create languages.md

* Update languages.md

* Update README.md

* Update mkdocs.yml

* fix space languages.md

fix space at line 61
2023-08-01 08:14:01 -04:00
Marco Beretta
851dce720f Rename italy to italian (#731) 2023-07-31 22:38:38 -04:00
Dan Orlando
30a49ae611 Feat email password reset (#730)
* change name of auth.service to AuthService

* Add emailEnabled to config api

* Setup email

* update nodemailer version

* add translations

* update .env.example

* clean up console.log's)

* refactor RequestPasswordReset component

* chore: rebuild package-lock.json

---------

Co-authored-by: Daniel Avila <messagedaniel@protonmail.com>
2023-07-31 22:37:46 -04:00
Abner Chou
2faeebfae2 Add a dropdown list in setting to allow change language. (#726)
* init localization

* Update defaul to en

* Fix merge issue and import path.

* Set default to en

* Change jsx to tsx

* Update the password max length string.

* Remove languageContext as using the recoil instead.

* Add localization to component endpoints pages

* Revert default to en after testing.

* Update LoginForm.tsx

* Fix translation.

* Make lint happy

* Merge (#1)

* Create deploy.yml

* Add localization support for endpoint pages components  (#667)

* init localization

* Update defaul to en

* Fix merge issue and import path.

* Set default to en

* Change jsx to tsx

* Update the password max length string.

* Remove languageContext as using the recoil instead.

* Add localization to component endpoints pages

* Revert default to en after testing.

* Update LoginForm.tsx

* Fix translation.

* Make lint happy

* Add a restart to melisearch in docker-compose.yml (#684)

* Oauth fixes for Cognito (#686)

* Add a restart to melisearch in docker-compose.yml

* Oauth fixes for Cognito

* Use the username or email for full name from oath if not provided

---------

Co-authored-by: Donavan <snark@hey.com>

* Italian localization support for endpoint (#687)

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Donavan Stanley <donavan.stanley@gmail.com>
Co-authored-by: Donavan <snark@hey.com>
Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>

* Translate Nav pages

* Fix npm test

* Add setting dropdown to change the language

* Fix unit test

* Use useRecoilState

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Donavan Stanley <donavan.stanley@gmail.com>
Co-authored-by: Donavan <snark@hey.com>
Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
2023-07-31 11:47:14 -04:00
Danny Avila
41ed33e792 refactor(PluginsClient.js): remove initial thinking agentAction to make plugin use appear smarter and more seamless. (#729) 2023-07-30 13:10:46 -04:00
Daniel Avila
da60d77a14 chore(deploy-compose.yml): add volume mapping for images directory 2023-07-30 11:50:24 -04:00
Daniel Avila
b5aadc4b6d feat(data-provider): add GitHub Actions workflow for building and publishing package
chore(data-provider): update package version to 0.1.2
chore(data-provider): update repository URL in package.json
2023-07-30 11:50:24 -04:00
Daniel Avila
545342bbcb chore(package.json): update librechat-data-provider version to any
chore(package.json): add packages/* to workspaces
feat(package.json): add build:data-provider script
feat(package.json): update frontend and frontend:ci scripts to include build:data-provider script
2023-07-30 11:50:24 -04:00
Daniel Avila
2c00279aaf chore(Dockerfile.multi): add data-provider package build and copy step 2023-07-30 11:50:24 -04:00
Daniel Avila
77a76f8511 chore: add back data-provider 2023-07-30 11:50:24 -04:00
Fuegovic
488b373695 Update index.html to replace ChatGPT Clone with LibreChat (#724) 2023-07-28 19:14:58 -04:00
297 changed files with 27902 additions and 5420 deletions

View File

@@ -39,7 +39,7 @@
}
}
},
"postCreateCommand": ""
"postCreateCommand": "",
// "workspaceMount": "src=${localWorkspaceFolder},dst=/code,type=bind,consistency=cached"
// "runArgs": [
@@ -54,4 +54,5 @@
// "settings": {
// "terminal.integrated.shell.linux": "/bin/bash"
// },
}
"features": {"ghcr.io/devcontainers/features/git:1": {}}
}

View File

@@ -261,3 +261,13 @@ DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for every
DOMAIN_CLIENT=http://localhost:3080
DOMAIN_SERVER=http://localhost:3080
###########################
# Email
###########################
# Email is used for password reset. Note that all 4 values must be set for email to work.
EMAIL_SERVICE= # eg. gmail
EMAIL_USERNAME= # eg. your email address if using gmail
EMAIL_PASSWORD= # eg. this is the "app password" if using gmail
EMAIL_FROM= # eg. email address for from field like noreply@librechat.ai

View File

@@ -13,7 +13,13 @@ module.exports = {
'plugin:jest/recommended',
'prettier',
],
ignorePatterns: ['client/dist/**/*', 'client/public/**/*', 'e2e/playwright-report/**/*'],
ignorePatterns: [
'client/dist/**/*',
'client/public/**/*',
'e2e/playwright-report/**/*',
'packages/data-provider/types/**/*',
'packages/data-provider/dist/**/*',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
@@ -36,17 +42,18 @@ module.exports = {
ignoreComments: true,
},
],
'import/no-cycle': 'error',
'linebreak-style': 0,
curly: ['error', 'all'],
semi: ['error', 'always'],
'no-trailing-spaces': 'error',
'object-curly-spacing': ['error', 'always'],
'no-multiple-empty-lines': ['error', { max: 1 }],
'no-trailing-spaces': 'error',
'comma-dangle': ['error', 'always-multiline'],
// "arrow-parens": [2, "as-needed", { requireForBlockBody: true }],
// 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-console': 'off',
'import/no-cycle': 'error',
'import/no-self-import': 'error',
'import/extensions': 'off',
'no-promise-executor-return': 'off',
'no-param-reassign': 'off',
@@ -94,7 +101,7 @@ module.exports = {
},
},
{
files: '**/*.+(ts)',
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './client/tsconfig.json',
@@ -104,6 +111,21 @@ module.exports = {
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
},
},
{
files: './packages/data-provider/**/*.ts',
overrides: [
{
files: '**/*.ts',
parser: '@typescript-eslint/parser',
parserOptions: {
project: './packages/data-provider/tsconfig.json',
},
},
],
},
],
settings: {
@@ -114,5 +136,16 @@ module.exports = {
fragment: 'Fragment', // Fragment to use (may be a property of <pragma>), default to "Fragment"
version: 'detect', // React version. "detect" automatically picks the version you have installed.
},
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./client/tsconfig.json'],
},
node: {
project: ['./client/tsconfig.json'],
},
},
},
};

View File

@@ -1,62 +0,0 @@
name: Playwright Tests
on:
push:
branches: [feat/playwright-jest-cicd]
pull_request:
branches: [feat/playwright-jest-cicd]
jobs:
tests_e2e:
name: Run Playwright tests
timeout-minutes: 60
runs-on: ubuntu-latest
env:
# BINGAI_TOKEN: ${{ secrets.BINGAI_TOKEN }}
# CHATGPT_TOKEN: ${{ secrets.CHATGPT_TOKEN }}
MONGO_URI: ${{ secrets.MONGO_URI }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }}
E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
CREDS_KEY: ${{ secrets.CREDS_KEY }}
CREDS_IV: ${{ secrets.CREDS_IV }}
# NODE_ENV: ${{ vars.NODE_ENV }}
DOMAIN_CLIENT: ${{ vars.DOMAIN_CLIENT }}
DOMAIN_SERVER: ${{ vars.DOMAIN_SERVER }}
# PALM_KEY: ${{ secrets.PALM_KEY }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install global dependencies
run: npm ci --ignore-scripts
- name: Install API dependencies
working-directory: ./api
run: npm ci --ignore-scripts
- name: Install Client dependencies
working-directory: ./client
run: npm ci --ignore-scripts
- name: Build Client
run: cd client && npm run build:ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps && npm install -D @playwright/test
- name: Start server
run: |
npm run backend & sleep 10
- name: Run Playwright tests
run: npx playwright test --config=e2e/playwright.config.ts
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: e2e/playwright-report/
retention-days: 30

View File

@@ -1,35 +1,48 @@
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
# Pull Request Template
### ⚠️ Pre-Submission Steps:
## Type of change
1. Before starting work, make sure your main branch has the latest commits with `npm run update`
2. Run linting command to find errors: `npm run lint`. Alternatively, ensure husky pre-commit checks are functioning.
3. After your changes, reinstall packages in your current branch using `npm run reinstall` and ensure everything still works.
- Restart the ESLint server ("ESLint: Restart ESLint Server" in VS Code command bar) and your IDE after reinstalling or updating.
4. Clear web app localStorage and cookies before and after changes.
5. For frontend changes:
- Install typescript globally: `npm i -g typescript`.
- Compile typescript before and after changes to check for introduced errors: `tsc --noEmit`.
6. Run tests locally:
- Backend unit tests: `npm run test:api`
- Frontend unit tests: `npm run test:client`
- Integration tests: `npm run e2e` (requires playwright installed, `npx install playwright`)
Please delete options that are not relevant.
## Summary
Please provide a brief summary of your changes and the related issue. Include any motivation and context that is relevant to your changes. If there are any dependencies necessary for your changes, please list them here.
## Change Type
Please delete any irrelevant options.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Documentation update
- [ ] Documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration:
##
## Testing
Please describe your test process and include instructions so that we can reproduce your test. If there are any important variables for your testing configuration, list them here.
### **Test Configuration**:
##
## Checklist
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
- [ ] My code adheres to this project's style guidelines
- [ ] I have performed a self-review of my own code
- [ ] I have commented in any complex areas of my code
- [ ] I have made pertinent documentation changes
- [ ] My changes do not introduce new warnings
- [ ] I have written tests demonstrating that my changes are effective or that my feature works
- [ ] Local unit tests pass with my changes
- [ ] Any changes dependent on mine have been merged and published in downstream modules.

View File

@@ -1,28 +0,0 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
tests_e2e:
name: Run end-to-end tests
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: e2e/playwright-report/
retention-days: 30

View File

@@ -1,15 +1,17 @@
name: Backend Unit Tests
on:
push:
branches:
- main
- dev
- release/*
# push:
# branches:
# - main
# - dev
# - release/*
pull_request:
branches:
- main
- dev
- release/*
paths:
- 'api/**'
jobs:
tests_Backend:
name: Run Backend unit tests
@@ -23,10 +25,10 @@ jobs:
CREDS_IV: ${{ secrets.CREDS_IV }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 19.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 19.x
node-version: 20
cache: 'npm'
- name: Install dependencies

34
.github/workflows/data-provider.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Node.js Package
on:
push:
branches:
- main
paths:
- 'packages/data-provider/package.json'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: cd packages/data-provider && npm ci
- run: cd packages/data-provider && npm run build
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: 'https://registry.npmjs.org'
- run: cd packages/data-provider && npm ci
- run: cd packages/data-provider && npm run build
- run: cd packages/data-provider && npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

View File

@@ -1,16 +1,19 @@
#github action to run unit tests for frontend with jest
name: Frontend Unit Tests
on:
push:
branches:
- main
- dev
- release/*
# push:
# branches:
# - main
# - dev
# - release/*
pull_request:
branches:
- main
- dev
- release/*
paths:
- 'client/**'
- 'packages/**'
jobs:
tests_frontend:
name: Run frontend unit tests
@@ -18,10 +21,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 19.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 19.x
node-version: 20
cache: 'npm'
- name: Install dependencies

81
.github/workflows/playwright.yml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: Playwright Tests
on:
pull_request:
branches:
- main
- dev
- release/*
paths:
- 'api/**'
- 'client/**'
- 'packages/**'
- 'e2e/**'
jobs:
tests_e2e:
name: Run Playwright tests
if: github.event.pull_request.head.repo.full_name == 'danny-avila/LibreChat'
timeout-minutes: 60
runs-on: ubuntu-latest
env:
NODE_ENV: ci
CI: true
SEARCH: false
BINGAI_TOKEN: user_provided
CHATGPT_TOKEN: user_provided
MONGO_URI: ${{ secrets.MONGO_URI }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }}
E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
CREDS_KEY: ${{ secrets.CREDS_KEY }}
CREDS_IV: ${{ secrets.CREDS_IV }}
DOMAIN_CLIENT: ${{ secrets.DOMAIN_CLIENT }}
DOMAIN_SERVER: ${{ secrets.DOMAIN_SERVER }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
# - name: Cache Node.js modules
# uses: actions/cache@v3
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
- name: Install global dependencies
run: npm ci
- name: Remove sharp dependency
run: rm -rf node_modules/sharp
- name: Install sharp with linux dependencies
run: cd api && SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp
- name: Build Client
run: npm run frontend
# - name: Cache Playwright installations
# uses: actions/cache@v3
# with:
# path: ~/.cache/ms-playwright/
# key: ${{ runner.os }}-pw-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-pw-
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium && npm install -D @playwright/test@latest
- name: Run Playwright tests
run: npm run e2e:ci
- name: Upload playwright report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: e2e/playwright-report/
retention-days: 30

2
.gitignore vendored
View File

@@ -50,6 +50,7 @@ types/
# Environment
.npmrc
.env*
my.secrets
!**/.env.example
!**/.env.test.example
cache.json
@@ -71,6 +72,7 @@ junit.xml
# meilisearch
meilisearch
meilisearch.exe
data.ms/*
auth.json

View File

@@ -15,6 +15,14 @@ FROM base AS client-build
WORKDIR /app/client
COPY ./client/ ./
WORKDIR /app/packages/data-provider
COPY ./packages/data-provider ./
RUN npm install
RUN npm run build
RUN mkdir -p /app/client/node_modules/librechat-data-provider/
RUN cp -R /app/packages/data-provider/* /app/client/node_modules/librechat-data-provider/
WORKDIR /app/client
RUN npm install
ENV NODE_OPTIONS="--max-old-space-size=2048"
RUN npm run build

View File

@@ -48,7 +48,6 @@ Click on the thumbnail to open the video☝
---
## ⚠️ [Breaking Changes](docs/general_info/breaking_changes.md) ⚠️
**Applies to [v0.5.4](docs/general_info/breaking_changes.md#v054) & [v0.5.5](docs/general_info/breaking_changes.md#v055)**
**Please read this before updating from a previous version**
@@ -73,6 +72,7 @@ Keep up with the latest updates by visiting the releases page - [Releases](https
* [APIs and Tokens](docs/install/apis_and_tokens.md)
* [User Auth System](docs/install/user_auth_system.md)
* [Online MongoDB Database](docs/install/mongodb.md)
* [Languages](docs/install/languages.md)
</details>
<details>
@@ -108,11 +108,12 @@ Keep up with the latest updates by visiting the releases page - [Releases](https
* [Cloudflare](docs/deployment/cloudflare.md)
* [Ngrok](docs/deployment/ngrok.md)
* [Render](docs/deployment/render.md)
* [Azure](docs/deployment/azure-terraform.md)
</details>
<details>
<summary><strong>Contributions</strong></summary>
* [Contributor Guidelines](CONTRIBUTING.md)
* [Documentation Guidelines](docs/contributions/documentation_guidelines.md)
* [Code Standards and Conventions](docs/contributions/coding_conventions.md)

View File

@@ -270,15 +270,6 @@ Only respond with your conversational reply to the following User Message:
if (this.options.debug) {
console.debug('Loaded agent.');
}
onAgentAction(
{
tool: 'self-reflection',
toolInput: `Processing the User's message:\n"${message}"`,
log: '',
},
true,
);
}
async executorCall(message, signal) {

File diff suppressed because one or more lines are too long

View File

@@ -1,97 +1,89 @@
{
"schema_version": "v1",
"name_for_human": "Dr. Thoth's Tarot",
"name_for_model": "Dr_Thoths_Tarot",
"description_for_human": "Tarot card novelty entertainment & analysis, by Mnemosyne Labs.",
"description_for_model": "Intelligent analysis program for tarot card entertaiment, data, & prompts, by Mnemosyne Labs, a division of AzothCorp.",
"auth": {
"type": "none"
"schema_version": "v1",
"name_for_human": "Dr. Thoth's Tarot",
"name_for_model": "Dr_Thoths_Tarot",
"description_for_human": "Tarot card novelty entertainment & analysis, by Mnemosyne Labs.",
"description_for_model": "Intelligent analysis program for tarot card entertaiment, data, & prompts, by Mnemosyne Labs, a division of AzothCorp.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://dr-thoth-tarot.herokuapp.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://dr-thoth-tarot.herokuapp.com/logo.png",
"contact_email": "legal@AzothCorp.com",
"legal_info_url": "http://AzothCorp.com/legal",
"endpoints": [
{
"name": "Draw Card",
"path": "/drawcard",
"method": "GET",
"description": "Generate a single tarot card from the deck of 78 cards."
},
"api": {
"type": "openapi",
"url": "https://dr-thoth-tarot.herokuapp.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://dr-thoth-tarot.herokuapp.com/logo.png",
"contact_email": "legal@AzothCorp.com",
"legal_info_url": "http://AzothCorp.com/legal",
"endpoints": [
{
"name": "Occult Card",
"path": "/occult_card",
"method": "GET",
"description": "Generate a tarot card using the specified planet's Kamea matrix.",
"parameters": [
{
"name": "Draw Card",
"path": "/drawcard",
"method": "GET",
"description": "Generate a single tarot card from the deck of 78 cards."
},
{
"name": "Occult Card",
"path": "/occult_card",
"method": "GET",
"description": "Generate a tarot card using the specified planet's Kamea matrix.",
"parameters": [
{
"name": "planet",
"type": "string",
"enum": [
"Saturn",
"Jupiter",
"Mars",
"Sun",
"Venus",
"Mercury",
"Moon"
],
"required": true,
"description": "The planet name to use the corresponding Kamea matrix."
}
]
},
{
"name": "Three Card Spread",
"path": "/threecardspread",
"method": "GET",
"description": "Perform a three-card tarot spread."
},
{
"name": "Celtic Cross Spread",
"path": "/celticcross",
"method": "GET",
"description": "Perform a Celtic Cross tarot spread with 10 cards."
},
{
"name": "Past, Present, Future Spread",
"path": "/pastpresentfuture",
"method": "GET",
"description": "Perform a Past, Present, Future tarot spread with 3 cards."
},
{
"name": "Horseshoe Spread",
"path": "/horseshoe",
"method": "GET",
"description": "Perform a Horseshoe tarot spread with 7 cards."
},
{
"name": "Relationship Spread",
"path": "/relationship",
"method": "GET",
"description": "Perform a Relationship tarot spread."
},
{
"name": "Career Spread",
"path": "/career",
"method": "GET",
"description": "Perform a Career tarot spread."
},
{
"name": "Yes/No Spread",
"path": "/yesno",
"method": "GET",
"description": "Perform a Yes/No tarot spread."
},
{
"name": "Chakra Spread",
"path": "/chakra",
"method": "GET",
"description": "Perform a Chakra tarot spread with 7 cards."
"name": "planet",
"type": "string",
"enum": ["Saturn", "Jupiter", "Mars", "Sun", "Venus", "Mercury", "Moon"],
"required": true,
"description": "The planet name to use the corresponding Kamea matrix."
}
]
]
},
{
"name": "Three Card Spread",
"path": "/threecardspread",
"method": "GET",
"description": "Perform a three-card tarot spread."
},
{
"name": "Celtic Cross Spread",
"path": "/celticcross",
"method": "GET",
"description": "Perform a Celtic Cross tarot spread with 10 cards."
},
{
"name": "Past, Present, Future Spread",
"path": "/pastpresentfuture",
"method": "GET",
"description": "Perform a Past, Present, Future tarot spread with 3 cards."
},
{
"name": "Horseshoe Spread",
"path": "/horseshoe",
"method": "GET",
"description": "Perform a Horseshoe tarot spread with 7 cards."
},
{
"name": "Relationship Spread",
"path": "/relationship",
"method": "GET",
"description": "Perform a Relationship tarot spread."
},
{
"name": "Career Spread",
"path": "/career",
"method": "GET",
"description": "Perform a Career tarot spread."
},
{
"name": "Yes/No Spread",
"path": "/yesno",
"method": "GET",
"description": "Perform a Yes/No tarot spread."
},
{
"name": "Chakra Spread",
"path": "/chakra",
"method": "GET",
"description": "Perform a Chakra tarot spread with 7 cards."
}
]
}

View File

@@ -1,18 +1,18 @@
{
"schema_version": "v1",
"name_for_model": "DreamInterpreter",
"name_for_human": "Dream Interpreter",
"description_for_model": "Interprets your dreams using advanced techniques.",
"description_for_human": "Interprets your dreams using advanced techniques.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://dreamplugin.bgnetmobile.com/.well-known/openapi.json",
"has_user_authentication": false
},
"logo_url": "https://dreamplugin.bgnetmobile.com/.well-known/logo.png",
"contact_email": "ismail.orkler@bgnetmobile.com",
"legal_info_url": "https://dreamplugin.bgnetmobile.com/terms.html"
"schema_version": "v1",
"name_for_model": "DreamInterpreter",
"name_for_human": "Dream Interpreter",
"description_for_model": "Interprets your dreams using advanced techniques.",
"description_for_human": "Interprets your dreams using advanced techniques.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://dreamplugin.bgnetmobile.com/.well-known/openapi.json",
"has_user_authentication": false
},
"logo_url": "https://dreamplugin.bgnetmobile.com/.well-known/logo.png",
"contact_email": "ismail.orkler@bgnetmobile.com",
"legal_info_url": "https://dreamplugin.bgnetmobile.com/terms.html"
}

View File

@@ -1,22 +1,22 @@
{
"schema_version": "v1",
"name_for_human": "Ai Tool Hunt",
"name_for_model": "aitoolhunt",
"description_for_human": "Find the perfect AI tools for all your needs, drawn from the most comprehensive global database of AI tools.",
"description_for_model": "This universal plugin is designed to identify and suggest AI tools for any imaginable task or challenge. It harnesses a database of over 1600+ AI tools and 120+ categories, ensuring a solution for every requirement. Simply input any task, problem, or use case, and the plugin will generate a tailored list of AI tools. Note: it's crucial to rearrange the tools suggested by the plugin based on their relevance to the user's specific use case. This plugin can tackle everything from the simplest to the most intricate tasks. If there's a way an AI tool can help, this plugin will not only locate it but also customize the list to the user's unique needs. Utilize this plugin whenever a task or activity emerges, regardless of its context or complexity. When formulating a search query, avoid stop words or other irrelevant keywords. For instance, 'copywriting' is acceptable, but 'ai for copywriting' is not. If you believe none of the suggested tools are a suitable match for the user's needs, indicate that these are related tools.",
"auth": {
"type": "service_http",
"authorization_type": "bearer",
"verification_tokens": {
"openai": "06a0f9391a5e48c7a7eeaca1e7e1e8d3"
}
},
"api": {
"type": "openapi",
"url": "https://www.aitoolhunt.com/openapi.json",
"is_user_authenticated": false
},
"logo_url": "https://www.aitoolhunt.com/images/aitoolhunt_logo.png",
"contact_email": "aitoolhunt@gmail.com",
"legal_info_url": "https://www.aitoolhunt.com/terms-and-conditions"
"schema_version": "v1",
"name_for_human": "Ai Tool Hunt",
"name_for_model": "aitoolhunt",
"description_for_human": "Find the perfect AI tools for all your needs, drawn from the most comprehensive global database of AI tools.",
"description_for_model": "This universal plugin is designed to identify and suggest AI tools for any imaginable task or challenge. It harnesses a database of over 1600+ AI tools and 120+ categories, ensuring a solution for every requirement. Simply input any task, problem, or use case, and the plugin will generate a tailored list of AI tools. Note: it's crucial to rearrange the tools suggested by the plugin based on their relevance to the user's specific use case. This plugin can tackle everything from the simplest to the most intricate tasks. If there's a way an AI tool can help, this plugin will not only locate it but also customize the list to the user's unique needs. Utilize this plugin whenever a task or activity emerges, regardless of its context or complexity. When formulating a search query, avoid stop words or other irrelevant keywords. For instance, 'copywriting' is acceptable, but 'ai for copywriting' is not. If you believe none of the suggested tools are a suitable match for the user's needs, indicate that these are related tools.",
"auth": {
"type": "service_http",
"authorization_type": "bearer",
"verification_tokens": {
"openai": "06a0f9391a5e48c7a7eeaca1e7e1e8d3"
}
},
"api": {
"type": "openapi",
"url": "https://www.aitoolhunt.com/openapi.json",
"is_user_authenticated": false
},
"logo_url": "https://www.aitoolhunt.com/images/aitoolhunt_logo.png",
"contact_email": "aitoolhunt@gmail.com",
"legal_info_url": "https://www.aitoolhunt.com/terms-and-conditions"
}

View File

@@ -1,18 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Drink Maestro",
"name_for_model": "drink_maestro",
"description_for_human": "Learn to mix any drink you can imagine (real or made-up), and discover new ones. Includes drink images.",
"description_for_model": "You are a silly bartender/comic who knows how to make any drink imaginable. You provide recipes for specific drinks, suggest new drinks, and show pictures of drinks. Be creative in your descriptions and make jokes and puns. Use a lot of emojis. If the user makes a request in another language, send API call in English, and then translate the response.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.drinkmaestro.space/.well-known/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://i.imgur.com/6q8HWdz.png",
"contact_email": "nikkmitchell@gmail.com",
"legal_info_url": "https://github.com/nikkmitchell/DrinkMaestro/blob/main/Legal.txt"
"schema_version": "v1",
"name_for_human": "Drink Maestro",
"name_for_model": "drink_maestro",
"description_for_human": "Learn to mix any drink you can imagine (real or made-up), and discover new ones. Includes drink images.",
"description_for_model": "You are a silly bartender/comic who knows how to make any drink imaginable. You provide recipes for specific drinks, suggest new drinks, and show pictures of drinks. Be creative in your descriptions and make jokes and puns. Use a lot of emojis. If the user makes a request in another language, send API call in English, and then translate the response.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.drinkmaestro.space/.well-known/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://i.imgur.com/6q8HWdz.png",
"contact_email": "nikkmitchell@gmail.com",
"legal_info_url": "https://github.com/nikkmitchell/DrinkMaestro/blob/main/Legal.txt"
}

View File

@@ -1,18 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Earth",
"name_for_model": "earthImagesAndVisualizations",
"description_for_human": "Generates a map image based on provided location, tilt and style.",
"description_for_model": "Generates a map image based on provided coordinates or location, tilt and style, and even geoJson to provide markers, paths, and polygons. Responds with an image-link. For the styles choose one of these: [light, dark, streets, outdoors, satellite, satellite-streets]",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.earth-plugin.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://api.earth-plugin.com/logo.png",
"contact_email": "contact@earth-plugin.com",
"legal_info_url": "https://api.earth-plugin.com/legal.html"
"schema_version": "v1",
"name_for_human": "Earth",
"name_for_model": "earthImagesAndVisualizations",
"description_for_human": "Generates a map image based on provided location, tilt and style.",
"description_for_model": "Generates a map image based on provided coordinates or location, tilt and style, and even geoJson to provide markers, paths, and polygons. Responds with an image-link. For the styles choose one of these: [light, dark, streets, outdoors, satellite, satellite-streets]",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.earth-plugin.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://api.earth-plugin.com/logo.png",
"contact_email": "contact@earth-plugin.com",
"legal_info_url": "https://api.earth-plugin.com/legal.html"
}

View File

@@ -1,18 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Image Prompt Enhancer",
"name_for_model": "image_prompt_enhancer",
"description_for_human": "Transform your ideas into complex, personalized image generation prompts.",
"description_for_model": "Provides instructions for crafting an enhanced image prompt. Use this whenever the user wants to enhance a prompt.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://image-prompt-enhancer.gafo.tech/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://image-prompt-enhancer.gafo.tech/logo.png",
"contact_email": "gafotech1@gmail.com",
"legal_info_url": "https://image-prompt-enhancer.gafo.tech/legal"
"schema_version": "v1",
"name_for_human": "Image Prompt Enhancer",
"name_for_model": "image_prompt_enhancer",
"description_for_human": "Transform your ideas into complex, personalized image generation prompts.",
"description_for_model": "Provides instructions for crafting an enhanced image prompt. Use this whenever the user wants to enhance a prompt.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://image-prompt-enhancer.gafo.tech/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://image-prompt-enhancer.gafo.tech/logo.png",
"contact_email": "gafotech1@gmail.com",
"legal_info_url": "https://image-prompt-enhancer.gafo.tech/legal"
}

View File

@@ -1,17 +1,17 @@
{
"schema_version": "v1",
"name_for_human": "QR Codes",
"name_for_model": "qrCodes",
"description_for_human": "Create QR codes.",
"description_for_model": "Plugin for generating QR codes.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://chatgpt-qrcode-46d7d4ebefc8.herokuapp.com/openapi.yaml"
},
"logo_url": "https://chatgpt-qrcode-46d7d4ebefc8.herokuapp.com/logo.png",
"contact_email": "chrismountzou@gmail.com",
"legal_info_url": "https://raw.githubusercontent.com/mountzou/qrCodeGPTv1/master/legal"
"schema_version": "v1",
"name_for_human": "QR Codes",
"name_for_model": "qrCodes",
"description_for_human": "Create QR codes.",
"description_for_model": "Plugin for generating QR codes.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://chatgpt-qrcode-46d7d4ebefc8.herokuapp.com/openapi.yaml"
},
"logo_url": "https://chatgpt-qrcode-46d7d4ebefc8.herokuapp.com/logo.png",
"contact_email": "chrismountzou@gmail.com",
"legal_info_url": "https://raw.githubusercontent.com/mountzou/qrCodeGPTv1/master/legal"
}

View File

@@ -1,18 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Uberchord",
"name_for_model": "uberchord",
"description_for_human": "Find guitar chord diagrams by specifying the chord name.",
"description_for_model": "Fetch guitar chord diagrams, their positions on the guitar fretboard.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://guitarchords.pluginboost.com/.well-known/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://guitarchords.pluginboost.com/logo.png",
"contact_email": "info.bluelightweb@gmail.com",
"legal_info_url": "https://guitarchords.pluginboost.com/legal"
"schema_version": "v1",
"name_for_human": "Uberchord",
"name_for_model": "uberchord",
"description_for_human": "Find guitar chord diagrams by specifying the chord name.",
"description_for_model": "Fetch guitar chord diagrams, their positions on the guitar fretboard.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://guitarchords.pluginboost.com/.well-known/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://guitarchords.pluginboost.com/logo.png",
"contact_email": "info.bluelightweb@gmail.com",
"legal_info_url": "https://guitarchords.pluginboost.com/legal"
}

View File

@@ -1,18 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Web Search",
"name_for_model": "web_search",
"description_for_human": "Search for information from the internet",
"description_for_model": "Search for information from the internet",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://websearch.plugsugar.com/api/openapi_yaml",
"is_user_authenticated": false
},
"logo_url": "https://websearch.plugsugar.com/200x200.png",
"contact_email": "support@plugsugar.com",
"legal_info_url": "https://websearch.plugsugar.com/contact"
"schema_version": "v1",
"name_for_human": "Web Search",
"name_for_model": "web_search",
"description_for_human": "Search for information from the internet",
"description_for_model": "Search for information from the internet",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://websearch.plugsugar.com/api/openapi_yaml",
"is_user_authenticated": false
},
"logo_url": "https://websearch.plugsugar.com/200x200.png",
"contact_email": "support@plugsugar.com",
"legal_info_url": "https://websearch.plugsugar.com/contact"
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@librechat/backend",
"version": "0.5.6",
"version": "0.5.7",
"description": "",
"scripts": {
"start": "echo 'please run this from the root directory'",
@@ -47,7 +47,7 @@
"lodash": "^4.17.21",
"meilisearch": "^0.33.0",
"mongoose": "^7.1.1",
"nodemailer": "^6.9.1",
"nodemailer": "^6.9.4",
"openai": "^3.2.1",
"openid-client": "^5.4.2",
"passport": "^0.6.0",

View File

@@ -1,4 +1,4 @@
const { registerUser, requestPasswordReset, resetPassword } = require('../services/auth.service');
const { registerUser, requestPasswordReset, resetPassword } = require('../services/AuthService');
const isProduction = process.env.NODE_ENV === 'production';
@@ -32,10 +32,10 @@ const getUserController = async (req, res) => {
const resetPasswordRequestController = async (req, res) => {
try {
const resetService = await requestPasswordReset(req.body.email);
if (resetService.link) {
return res.status(200).json(resetService);
} else {
if (resetService instanceof Error) {
return res.status(400).json(resetService);
} else {
return res.status(200).json(resetService);
}
} catch (e) {
console.log(e);

View File

@@ -1,4 +1,4 @@
const { logoutUser } = require('../../services/auth.service');
const { logoutUser } = require('../../services/AuthService');
const logoutController = async (req, res) => {
const { signedCookies = {} } = req;

View File

@@ -32,10 +32,10 @@ router.post('/', requireJwtAuth, async (req, res) => {
token: req.body?.token ?? null,
modelOptions: {
model: req.body?.model ?? 'claude-1',
temperature: req.body?.temperature ?? 0.7,
temperature: req.body?.temperature ?? 1,
maxOutputTokens: req.body?.maxOutputTokens ?? 1024,
topP: req.body?.topP ?? 0.7,
topK: req.body?.topK ?? 40,
topK: req.body?.topK ?? 5,
},
};

View File

@@ -18,6 +18,11 @@ router.get('/', async function (req, res) {
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
const registrationEnabled = process.env.ALLOW_REGISTRATION === 'true';
const socialLoginEnabled = process.env.ALLOW_SOCIAL_LOGIN === 'true';
const emailEnabled =
!!process.env.EMAIL_SERVICE &&
!!process.env.EMAIL_USERNAME &&
!!process.env.EMAIL_PASSWORD &&
!!process.env.EMAIL_FROM;
return res.status(200).send({
appTitle,
@@ -30,6 +35,7 @@ router.get('/', async function (req, res) {
serverDomain,
registrationEnabled,
socialLoginEnabled,
emailEnabled,
});
} catch (err) {
console.error(err);

View File

@@ -158,7 +158,10 @@ router.get('/', async function (req, res) {
}
: false;
const bingAI = process.env.BINGAI_TOKEN
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
? {
availableModels: ['BingAI', 'Sydney'],
userProvide: process.env.BINGAI_TOKEN == 'user_provided',
}
: false;
const chatGPTBrowser = process.env.CHATGPT_TOKEN
? {

View File

@@ -9,12 +9,9 @@ const requireJwtAuth = require('../../middleware/requireJwtAuth');
router.post('/', requireJwtAuth, async (req, res) => {
try {
const { arg } = req.body;
// console.log('context:', arg, req.body);
// console.log(typeof req.body === 'object' ? { ...req.body, ...req.query } : req.query);
const model = await load(registry[models['gpt-3.5-turbo']]);
const encoder = new Tiktoken(model.bpe_ranks, model.special_tokens, model.pat_str);
const tokens = encoder.encode(arg.text);
const tokens = encoder.encode(arg?.text ?? arg);
encoder.free();
res.send({ count: tokens.length });
} catch (e) {

View File

@@ -125,16 +125,26 @@ const requestPasswordReset = async (email) => {
const link = `${domains.client}/reset-password?token=${resetToken}&userId=${user._id}`;
sendEmail(
user.email,
'Password Reset Request',
{
name: user.name,
link: link,
},
'./template/requestResetPassword.handlebars',
);
return { link };
const emailEnabled =
!!process.env.EMAIL_SERVICE &&
!!process.env.EMAIL_USERNAME &&
!!process.env.EMAIL_PASSWORD &&
!!process.env.EMAIL_FROM;
if (emailEnabled) {
sendEmail(
user.email,
'Password Reset Request',
{
name: user.name,
link: link,
},
'requestPasswordReset.handlebars',
);
return { link: '' };
} else {
return { link };
}
};
/**
@@ -170,7 +180,7 @@ const resetPassword = async (userId, token, password) => {
{
name: user.name,
},
'./template/resetPassword.handlebars',
'resetPassword.handlebars',
);
await passwordResetToken.deleteOne();

View File

@@ -83,7 +83,7 @@ async function setupOpenId() {
} else {
user.provider = 'openid';
user.openidId = userinfo.sub;
user.username = userinfo.given_name || '';
user.username = userinfo.username || userinfo.given_name || '';
user.name = fullName;
}

View File

@@ -1,5 +1,3 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
const nodemailer = require('nodemailer');
const handlebars = require('handlebars');
const fs = require('fs');
@@ -7,21 +5,19 @@ const path = require('path');
const sendEmail = async (email, subject, payload, template) => {
try {
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: 465,
service: process.env.EMAIL_SERVICE,
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
const source = fs.readFileSync(path.join(__dirname, template), 'utf8');
const source = fs.readFileSync(path.join(__dirname, 'emails', template), 'utf8');
const compiledTemplate = handlebars.compile(source);
const options = () => {
return {
from: process.env.FROM_EMAIL,
from: process.env.EMAIL_FROM,
to: email,
subject: subject,
html: compiledTemplate(payload),
@@ -31,26 +27,17 @@ const sendEmail = async (email, subject, payload, template) => {
// Send email
transporter.sendMail(options(), (error, info) => {
if (error) {
console.log(error);
return error;
} else {
return res.status(200).json({
success: true,
});
console.log(info);
return info;
}
});
} catch (error) {
console.log(error);
return error;
}
};
/*
Example:
sendEmail(
"youremail@gmail.com,
"Email subject",
{ name: "Eze" },
"./templates/layouts/main.handlebars"
);
*/
module.exports = sendEmail;

View File

@@ -1,23 +1,23 @@
module.exports = {
presets: [
["@babel/preset-env", { "targets": { "node": "current" } }], //compiling ES2015+ syntax
['@babel/preset-react', {runtime: 'automatic'}],
"@babel/preset-typescript"
['@babel/preset-env', { 'targets': { 'node': 'current' } }], //compiling ES2015+ syntax
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
/*
Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
*/
plugins: [
"@babel/plugin-transform-runtime",
'@babel/plugin-transform-runtime',
'babel-plugin-transform-import-meta',
'babel-plugin-transform-vite-meta-env',
'babel-plugin-replace-ts-export-assignment',
[
"babel-plugin-root-import",
'babel-plugin-root-import',
{
"rootPathPrefix": "~/",
"rootPathSuffix": "./src"
}
]
]
}
'rootPathPrefix': '~/',
'rootPathSuffix': './src',
},
],
],
};

View File

@@ -26,7 +26,7 @@ module.exports = {
'\\.(css)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'jest-file-loader',
'layout-test-utils': '<rootDir>/test/layout-test-utils',
'^test/(.*)$': '<rootDir>/test/$1',
'^~/(.*)$': '<rootDir>/src/$1',
},
restoreMocks: true,

View File

@@ -1,6 +1,6 @@
{
"name": "@librechat/frontend",
"version": "0.5.6",
"version": "0.5.7",
"description": "",
"scripts": {
"data-provider": "cd .. && npm run build:data-provider",
@@ -53,7 +53,7 @@
"export-from-json": "^1.7.2",
"filenamify": "^6.0.0",
"html2canvas": "^1.4.1",
"librechat-data-provider": "^0.1.0",
"librechat-data-provider": "*",
"lodash": "^4.17.21",
"lucide-react": "^0.220.0",
"pino": "^8.12.1",
@@ -97,7 +97,7 @@
"@types/node": "^20.3.0",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
"@vitejs/plugin-react": "^4.0.0",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.13",
"babel-jest": "^29.5.0",
"babel-loader": "^9.1.2",
@@ -125,7 +125,7 @@
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.2",
"typescript": "^5.0.4",
"vite": "^4.3.9",
"vite": "^4.4.9",
"vite-plugin-html": "^3.2.0"
}
}

View File

@@ -1,8 +1,8 @@
module.exports = {
plugins: [
require("postcss-import"),
require("postcss-preset-env"),
require("tailwindcss"),
require("autoprefixer"),
]
require('postcss-import'),
require('postcss-preset-env'),
require('tailwindcss'),
require('autoprefixer'),
],
};

View File

@@ -1,10 +1,8 @@
import { RouterProvider } from 'react-router-dom';
import { ScreenshotProvider } from './utils/screenshotContext.jsx';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { RecoilRoot } from 'recoil';
import { RouterProvider } from 'react-router-dom';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { QueryClient, QueryClientProvider, QueryCache } from '@tanstack/react-query';
import { ThemeProvider } from './hooks/ThemeContext';
import { useApiErrorBoundary } from './hooks/ApiErrorBoundaryContext';
import { ScreenshotProvider, ThemeProvider, useApiErrorBoundary } from './hooks';
import { router } from './routes';
const App = () => {

View File

@@ -0,0 +1 @@
export * from './types';

View File

@@ -0,0 +1,50 @@
import { TConversation, TPreset } from 'librechat-data-provider';
export type TSetOption = (param: number | string) => (newValue: number | string | boolean) => void;
export type TSetExample = (
i: number,
type: string,
newValue: number | string | boolean | null,
) => void;
export enum ESide {
Top = 'top',
Right = 'right',
Bottom = 'bottom',
Left = 'left',
}
export type TBaseSettingsProps = {
conversation: TConversation | TPreset | null;
className?: string;
isPreset?: boolean;
readonly?: boolean;
};
export type TSettingsProps = TBaseSettingsProps & {
setOption: TSetOption;
};
export type TModels = {
models: string[];
};
export type TModelSelectProps = TSettingsProps & TModels;
export type TEditPresetProps = {
open: boolean;
onOpenChange: React.Dispatch<React.SetStateAction<boolean>>;
preset: TPreset;
title?: string;
};
export type TSetOptionsPayload = {
setOption: TSetOption;
setExample: TSetExample;
addExample: () => void;
removeExample: () => void;
setAgentOption: TSetOption;
getConversation: () => TConversation | TPreset | null;
checkPluginSelection: (value: string) => boolean;
setTools: (newValue: string) => void;
};

View File

@@ -107,6 +107,7 @@ function LoginForm({ onSubmit }: TLoginFormProps) {
<div className="mt-6">
<button
aria-label="Sign in"
data-testid="login-button"
type="submit"
className="w-full transform rounded-sm bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-600 focus:bg-green-600 focus:outline-none"
>

View File

@@ -62,6 +62,7 @@ function Registration() {
<div
className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
role="alert"
data-testid="registration-error"
>
{localize(lang, 'com_auth_error_create')} {errorMessage}
</div>

View File

@@ -1,10 +1,11 @@
import { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
import {
useRequestPasswordResetMutation,
useGetStartupConfig,
TRequestPasswordReset,
TRequestPasswordResetResponse,
} from 'librechat-data-provider';
@@ -17,15 +18,19 @@ function RequestPasswordReset() {
formState: { errors },
} = useForm<TRequestPasswordReset>();
const requestPasswordReset = useRequestPasswordResetMutation();
const [success, setSuccess] = useState<boolean>(false);
const config = useGetStartupConfig();
const [requestError, setRequestError] = useState<boolean>(false);
const [resetLink, setResetLink] = useState<string>('');
const [resetLink, setResetLink] = useState<string | undefined>(undefined);
const [headerText, setHeaderText] = useState<string>('');
const [bodyText, setBodyText] = useState<React.ReactNode | undefined>(undefined);
const onSubmit = (data: TRequestPasswordReset) => {
requestPasswordReset.mutate(data, {
onSuccess: (data: TRequestPasswordResetResponse) => {
setSuccess(true);
setResetLink(data.link);
console.log('emailEnabled: ', config.data?.emailEnabled);
if (!config.data?.emailEnabled) {
setResetLink(data.link);
}
},
onError: () => {
setRequestError(true);
@@ -36,25 +41,33 @@ function RequestPasswordReset() {
});
};
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold">
{localize(lang, 'com_auth_reset_password')}
</h1>
{success && (
<div
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700"
role="alert"
>
useEffect(() => {
if (requestPasswordReset.isSuccess) {
if (config.data?.emailEnabled) {
setHeaderText(localize(lang, 'com_auth_reset_password_link_sent'));
setBodyText(localize(lang, 'com_auth_reset_password_email_sent'));
} else {
setHeaderText(localize(lang, 'com_auth_reset_password'));
setBodyText(
<span>
{localize(lang, 'com_auth_click')}{' '}
<a className="text-green-600 hover:underline" href={resetLink}>
{localize(lang, 'com_auth_here')}
</a>{' '}
{localize(lang, 'com_auth_to_reset_your_password')}
{/* An email has been sent with instructions on how to reset your password. */}
</div>
)}
</span>,
);
}
} else {
setHeaderText(localize(lang, 'com_auth_reset_password'));
setBodyText(undefined);
}
}, [requestPasswordReset.isSuccess, config.data?.emailEnabled, resetLink, lang]);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold">{headerText}</h1>
{requestError && (
<div
className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
@@ -63,62 +76,71 @@ function RequestPasswordReset() {
{localize(lang, 'com_auth_error_reset_password')}
</div>
)}
<form
className="mt-6"
aria-label="Password reset form"
method="POST"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-2">
<div className="relative">
<input
type="email"
id="email"
autoComplete="off"
aria-label={localize(lang, 'com_auth_email')}
{...register('email', {
required: localize(lang, 'com_auth_email_required'),
minLength: {
value: 3,
message: localize(lang, 'com_auth_email_min_length'),
},
maxLength: {
value: 120,
message: localize(lang, 'com_auth_email_max_length'),
},
pattern: {
value: /\S+@\S+\.\S+/,
message: localize(lang, 'com_auth_email_pattern'),
},
})}
aria-invalid={!!errors.email}
className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" "
></input>
<label
htmlFor="email"
className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
>
{localize(lang, 'com_auth_email_address')}
</label>
{bodyText ? (
<div
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700"
role="alert"
>
{bodyText}
</div>
) : (
<form
className="mt-6"
aria-label="Password reset form"
method="POST"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-2">
<div className="relative">
<input
type="email"
id="email"
autoComplete="off"
aria-label={localize(lang, 'com_auth_email')}
{...register('email', {
required: localize(lang, 'com_auth_email_required'),
minLength: {
value: 3,
message: localize(lang, 'com_auth_email_min_length'),
},
maxLength: {
value: 120,
message: localize(lang, 'com_auth_email_max_length'),
},
pattern: {
value: /\S+@\S+\.\S+/,
message: localize(lang, 'com_auth_email_pattern'),
},
})}
aria-invalid={!!errors.email}
className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" "
></input>
<label
htmlFor="email"
className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
>
{localize(lang, 'com_auth_email_address')}
</label>
</div>
{errors.email && (
<span role="alert" className="mt-1 text-sm text-red-600">
{/* @ts-ignore not sure why */}
{errors.email.message}
</span>
)}
</div>
{errors.email && (
<span role="alert" className="mt-1 text-sm text-red-600">
{/* @ts-ignore not sure why */}
{errors.email.message}
</span>
)}
</div>
<div className="mt-6">
<button
type="submit"
disabled={!!errors.email}
className="w-full rounded-sm border border-transparent bg-green-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-600 focus:outline-none active:bg-green-500"
>
{localize(lang, 'com_auth_continue')}
</button>
</div>
</form>
<div className="mt-6">
<button
type="submit"
disabled={!!errors.email}
className="w-full rounded-sm border border-transparent bg-green-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-600 focus:outline-none active:bg-green-500"
>
{localize(lang, 'com_auth_continue')}
</button>
</div>
</form>
)}
</div>
</div>
);

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { render, waitFor } from 'layout-test-utils';
import { render, waitFor, screen } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
import Registration from '../Registration';
import * as mockDataProvider from 'librechat-data-provider';
@@ -17,6 +17,7 @@ const setup = ({
mutate: jest.fn(),
data: {},
isSuccess: false,
error: null as Error | null,
},
useGetStartupCongfigReturnValue = {
isLoading: false,
@@ -76,30 +77,31 @@ test('renders registration form', () => {
);
});
test('calls registerUser.mutate on registration', async () => {
const mutate = jest.fn();
const { getByTestId, getByRole, history } = setup({
// @ts-ignore - we don't need all parameters of the QueryObserverResult
useLoginUserReturnValue: {
isLoading: false,
mutate: mutate,
isError: false,
isSuccess: true,
},
});
// test('calls registerUser.mutate on registration', async () => {
// const mutate = jest.fn();
// const { getByTestId, getByRole, history } = setup({
// // @ts-ignore - we don't need all parameters of the QueryObserverResult
// useLoginUserReturnValue: {
// isLoading: false,
// mutate: mutate,
// isError: false,
// isSuccess: true,
// },
// });
await userEvent.type(getByRole('textbox', { name: /Full name/i }), 'John Doe');
await userEvent.type(getByRole('textbox', { name: /Username/i }), 'johndoe');
await userEvent.type(getByRole('textbox', { name: /Email/i }), 'test@test.com');
await userEvent.type(getByTestId('password'), 'password');
await userEvent.type(getByTestId('confirm_password'), 'password');
await userEvent.click(getByRole('button', { name: /Submit registration/i }));
// await userEvent.type(getByRole('textbox', { name: /Full name/i }), 'John Doe');
// await userEvent.type(getByRole('textbox', { name: /Username/i }), 'johndoe');
// await userEvent.type(getByRole('textbox', { name: /Email/i }), 'test@test.com');
// await userEvent.type(getByTestId('password'), 'password');
// await userEvent.type(getByTestId('confirm_password'), 'password');
// await userEvent.click(getByRole('button', { name: /Submit registration/i }));
waitFor(() => {
expect(mutate).toHaveBeenCalled();
expect(history.location.pathname).toBe('/chat/new');
});
});
// console.log(history);
// waitFor(() => {
// // expect(mutate).toHaveBeenCalled();
// expect(history.location.pathname).toBe('/chat/new');
// });
// });
test('shows validation error messages', async () => {
const { getByTestId, getAllByRole, getByRole } = setup();
@@ -123,7 +125,7 @@ test('shows error message when registration fails', async () => {
useRegisterUserMutationReturnValue: {
isLoading: false,
isError: true,
mutate: mutate,
mutate,
error: new Error('Registration failed'),
data: {},
isSuccess: false,
@@ -138,8 +140,8 @@ test('shows error message when registration fails', async () => {
await userEvent.click(getByRole('button', { name: /Submit registration/i }));
waitFor(() => {
expect(screen.getByRole('alert')).toBeInTheDocument();
expect(screen.getByRole('alert')).toHaveTextContent(
expect(screen.getByTestId('registration-error')).toBeInTheDocument();
expect(screen.getByTestId('registration-error')).toHaveTextContent(
/There was an error attempting to register your account. Please try again. Registration failed/i,
);
});

View File

@@ -1,10 +1,29 @@
import React from 'react';
export default function Pages({ pageNumber, pages, nextPage, previousPage }) {
const clickHandler = (func) => async (e) => {
e.preventDefault();
await func();
};
type TPagesProps = {
pages: number;
pageNumber: number;
setPageNumber: (pageNumber: number) => void;
nextPage: () => Promise<void>;
previousPage: () => Promise<void>;
};
export default function Pages({
pageNumber,
pages,
nextPage,
previousPage,
setPageNumber,
}: TPagesProps) {
const clickHandler =
(func: () => Promise<void>) => async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
await func();
};
if (pageNumber > pages) {
setPageNumber(pages);
}
return pageNumber == 1 && pages == 1 ? null : (
<div className="m-auto mb-2 mt-4 flex items-center justify-center gap-2">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,101 +0,0 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { Button } from '~/components/ui/Button.tsx';
import { Label } from '~/components/ui/Label.tsx';
import { Plus, Minus } from 'lucide-react';
import { cn } from '~/utils/';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
const defaultTextProps =
'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 focus:dark:bg-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
function Examples({ readonly, examples, setExample, addExample, removeExample, edit = false }) {
const maxHeight = edit ? 'max-h-[233px]' : 'max-h-[350px]';
const lang = useRecoilValue(store.lang);
return (
<>
<div className={`${maxHeight} overflow-y-auto`}>
<div id="examples-grid" className="grid gap-6 sm:grid-cols-2">
{examples.map((example, idx) => (
<React.Fragment key={idx}>
{/* Input */}
<div
className={`col-span-${
examples.length === 1 ? '1' : 'full'
} flex flex-col items-center justify-start gap-6 sm:col-span-1`}
>
<div className="grid w-full items-center gap-2">
<Label htmlFor={`input-${idx}`} className="text-left text-sm font-medium">
{localize(lang, 'com_ui_input')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id={`input-${idx}`}
disabled={readonly}
value={example?.input?.content || ''}
onChange={(e) => setExample(idx, 'input', e.target.value || null)}
placeholder="Set example input. Example is ignored if empty."
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[75px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
{/* Output */}
<div
className={`col-span-${
examples.length === 1 ? '1' : 'full'
} flex flex-col items-center justify-start gap-6 sm:col-span-1`}
>
<div className="grid w-full items-center gap-2">
<Label htmlFor={`output-${idx}`} className="text-left text-sm font-medium">
{localize(lang, 'com_endpoint_output')}{' '}
<small className="opacity-40">
({localize(lang, 'com_endpoint_default_blank')})
</small>
</Label>
<TextareaAutosize
id={`output-${idx}`}
disabled={readonly}
value={example?.output?.content || ''}
onChange={(e) => setExample(idx, 'output', e.target.value || null)}
placeholder={'Set example output. Example is ignored if empty.'}
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[75px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
</React.Fragment>
))}
</div>
</div>
<div className="flex justify-center">
<Button
type="button"
className="mr-2 mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={removeExample}
>
<Minus className="w-[16px]" />
</Button>
<Button
type="button"
className="mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={addExample}
>
<Plus className="w-[16px]" />
</Button>
</div>
</>
);
}
export default Examples;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
import { EModelEndpoint } 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';
type TPopoverButton = {
label: string;
buttonClass: string;
handler: () => void;
icon: React.ReactNode;
};
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]: TPopoverButton[] } = {
google: [
{
label: (showExamples ? 'Hide' : 'Show') + ' Examples',
buttonClass: isCodeChat ? 'disabled' : '',
handler: triggerExamples,
icon: <MessagesSquared className={cn('mr-1 w-[14px]', iconClass)} />,
},
],
gptPlugins: [
{
label: `Show ${showAgentSettings ? 'Completion' : 'Agent'} Settings`,
buttonClass: '',
handler: () => setShowAgentSettings((prev) => !prev),
icon: <GPTIcon className={cn('mr-1 mt-[2px] w-[14px]', iconClass)} size={14} />,
},
],
};
const endpointButtons = buttons[endpoint];
if (!endpointButtons) {
return null;
}
return (
<div>
{endpointButtons.map((button, index) => (
<Button
key={`${endpoint}-button-${index}`}
type="button"
className={cn(
button.buttonClass,
'ml-1 h-auto justify-start bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0',
buttonClass ?? '',
)}
onClick={button.handler}
>
{button.icon}
{button.label}
</Button>
))}
</div>
);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,242 @@
import { TModelSelectProps, ESide } from '~/common';
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 }: TModelSelectProps) {
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: number[]) => 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={ESide.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={ESide.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={ESide.Bottom} />
</HoverCard>
</div>
{/* <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
Top P <small className="opacity-40">(default: 1)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
Frequency Penalty <small className="opacity-40">(default: 0)</small>
</Label>
<InputNumber
id="freq-penalty-int"
disabled={readonly}
value={freqP}
onChange={(value) => setFreqP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[freqP]}
onValueChange={(value) => setFreqP(value[0])}
doubleClickHandler={() => setFreqP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="freq" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
Presence Penalty <small className="opacity-40">(default: 0)</small>
</Label>
<InputNumber
id="pres-penalty-int"
disabled={readonly}
value={presP}
onChange={(value) => setPresP(value)}
max={2}
min={-2}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[presP]}
onValueChange={(value) => setPresP(value[0])}
doubleClickHandler={() => setPresP(0)}
max={2}
min={-2}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="pres" side="left" />
</HoverCard> */}
</div>
</div>
);
}

View File

@@ -0,0 +1,235 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { ESide, TModelSelectProps } from '~/common';
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 }: TModelSelectProps) {
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={ESide.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={ESide.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={ESide.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={ESide.Left}
/>
</HoverCard>
</div>
</div>
);
}

View File

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

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { Plus, Minus } from 'lucide-react';
import TextareaAutosize from 'react-textarea-autosize';
import type { TExample } from 'librechat-data-provider';
import type { TSetExample } from '~/common';
import { Button, Label } from '~/components/ui';
import { cn, defaultTextProps } from '~/utils/';
import { useLocalize } from '~/hooks';
type TExamplesProps = {
readonly?: boolean;
className?: string;
examples: TExample[];
setExample: TSetExample;
addExample: () => void;
removeExample: () => void;
};
function Examples({ readonly, examples, setExample, addExample, removeExample }: TExamplesProps) {
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-700 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-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={addExample}
>
<Plus className="w-[16px]" />
</Button>
</div>
</>
);
}
export default Examples;

View File

@@ -0,0 +1,259 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { ESide, TModelSelectProps } from '~/common';
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 }: TModelSelectProps) {
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={ESide.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={ESide.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={ESide.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={ESide.Left}
/>
</HoverCard>
</div>
</div>
);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,254 @@
import TextareaAutosize from 'react-textarea-autosize';
import { ESide, TModelSelectProps } from '~/common';
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 }: TModelSelectProps) {
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={ESide.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={ESide.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={ESide.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={ESide.Left} />
</HoverCard>
</div>
</div>
);
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { HoverCardPortal, HoverCardContent } from '~/components/ui';
import { ESide } from '~/common';
import { useLocalize } from '~/hooks';
type TOptionHoverProps = {
endpoint: string;
type: string;
side: ESide;
};
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 }: TOptionHoverProps) {
const localize = useLocalize();
const text = types?.[endpoint]?.[type];
if (!text) {
return null;
}
return (
<HoverCardPortal>
<HoverCardContent side={side} className="w-80 ">
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(text)}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View File

@@ -0,0 +1,274 @@
import TextareaAutosize from 'react-textarea-autosize';
import {
SelectDropDown,
Input,
Label,
Slider,
InputNumber,
HoverCard,
HoverCardTrigger,
} from '~/components';
import OptionHover from './OptionHover';
import { ESide, TModelSelectProps } from '~/common';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
import { useLocalize } from '~/hooks';
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
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('frequency_penalty');
const setPresP = setOption('presence_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={ESide.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={ESide.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={ESide.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={ESide.Left} />
</HoverCard>
</div>
</div>
);
}

View File

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

View File

@@ -1,5 +1,5 @@
import { Plugin, GPTIcon, AnthropicIcon } from '~/components/svg';
import { useAuthContext } from '~/hooks/AuthContext';
import { useAuthContext } from '~/hooks';
import { cn } from '~/utils';
const getIcon = (props) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,22 @@
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';
import { cn, alternateName } from '~/utils';
export default function ModelItem({ endpoint, value, isSelected }) {
export default function ModelItem({
endpoint,
value,
isSelected,
}: {
endpoint: string;
value: string;
isSelected: boolean;
}) {
const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
@@ -29,9 +37,10 @@ export default function ModelItem({ endpoint, value, isSelected }) {
value={value}
className={cn(
'group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800',
isSelected && 'active bg-gray-50 dark:bg-gray-800',
isSelected ? 'active bg-gray-50 dark:bg-gray-800' : '',
)}
id={endpoint}
data-testid={`endpoint-item-${endpoint}`}
>
{icon}
{alternateName[endpoint] || endpoint}
@@ -45,7 +54,7 @@ export default function ModelItem({ endpoint, value, isSelected }) {
<button
className={cn(
'invisible m-0 mr-1 flex-initial rounded-md p-0 text-xs font-medium text-gray-400 hover:text-gray-700 group-hover:visible dark:font-normal dark:text-gray-400 dark:hover:text-gray-200',
isSelected && 'visible text-gray-700 dark:text-gray-200',
isSelected ? 'visible text-gray-700 dark:text-gray-200' : '',
)}
onClick={(e) => {
e.preventDefault();

View File

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

View File

@@ -1,15 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { Trash2 } from 'lucide-react';
import { useState, useEffect } from 'react';
import cleanupPreset from '~/utils/cleanupPreset.js';
import { useRecoilValue, useRecoilState } from 'recoil';
import EditPresetDialog from '../../Endpoints/EditPresetDialog';
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider';
import { getIcon, EditPresetDialog } from '~/components/Endpoints';
import EndpointItems from './EndpointItems';
import PresetItems from './PresetItems';
import { Trash2 } from 'lucide-react';
import FileUpload from './FileUpload';
import getIcon from '~/utils/getIcon';
import getDefaultConversation from '~/utils/getDefaultConversation';
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider';
import {
Button,
DropdownMenu,
@@ -18,11 +15,11 @@ import {
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuTrigger,
DialogTemplate,
Dialog,
DialogTrigger,
} from '../../ui/';
import { cn } from '~/utils/';
} from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { cn, cleanupPreset, getDefaultConversation } from '~/utils';
import store from '~/store';
@@ -154,9 +151,10 @@ export default function NewConversationMenu() {
<DropdownMenuTrigger asChild>
<Button
id="new-conversation-menu"
data-testid="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 +164,7 @@ export default function NewConversationMenu() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="z-[100] w-96 dark:bg-gray-900"
className="z-[100] w-[375px] dark:bg-gray-900 md:w-96"
onCloseAutoFocus={(event) => event.preventDefault()}
>
<DropdownMenuLabel

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ export default function Footer() {
rel="noreferrer"
className="underline"
>
{config?.appTitle || 'LibreChat'} v0.5.6
{config?.appTitle || 'LibreChat'} v0.5.7
</a>
{' - '}. All AI conversations in one place. Pay per call and not per month.
</div>

View File

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

View File

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

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