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

* progress on settings refactor

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

* feat(CrossIcon.tsx): add CrossIcon component

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

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

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

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

* fix: google and anthropic defaults

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

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

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

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

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

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

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

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

* wip: refactor google settings

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: dependency cycle errors and type errors

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

* refactor: rename NewConversationMenu to EndpointMenu

* style: mobile and desktop optimizations

* chore: eslint changes

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

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

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

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

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

* style: improve dialogs

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

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

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

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

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

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

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

* feat(hooks): add useLocalize hook

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

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

* refactor(useDocumentTitle.ts): refactor to TS

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

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

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

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

* chore: remove template button

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

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

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

* refactor(EditPresetDialog): convert to TS

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

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

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

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

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

* refactor(EndpointOptionsDialog): convert to TS

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

refactor(SaveAsPresetDialog.tsx): convert to TS

fix(usePresetOptions.ts): import cleanupPreset from index

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

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

* wip: add headerClassName for dialog template

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

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

* refactor: imports from index and refactor to TS

* refactor: refactor all svg components to TS

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

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

* fix: SaveAsPresetDialog typing issue

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

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

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

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

* style: match official site header

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

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

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

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

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

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

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

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

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

* style: setting dialogs improved in all views

* style(EndpointMenu): improve UX for mobile

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

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

* fix: light mode styling

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

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

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

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

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

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

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

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

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

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

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

* fix(AgentSettings.tsx): overlapping issue

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

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

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

* refactor(RowButton, buildTree): convert to TS

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,19 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function Anthropic({ conversation, setOption, models }: ModelSelectProps) {
return (
<SelectDropDown
value={conversation?.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
);
}

View file

@ -0,0 +1,78 @@
import { useRecoilValue } from 'recoil';
import { SelectDropDown, Tabs, TabsList, TabsTrigger } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
import store from '~/store';
export default function BingAI({ conversation, setOption, models }: ModelSelectProps) {
const showBingToneSetting = useRecoilValue(store.showBingToneSetting);
if (!conversation) {
return null;
}
const { conversationId, toneStyle, jailbreak } = conversation;
if (conversationId !== 'new' && !showBingToneSetting) {
return null;
}
const defaultClasses =
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
const defaultSelected = cn(
defaultClasses,
'font-medium data-[state=active]:text-white text-xs text-white',
);
const selectedClass = (val: string) => val + '-tab ' + defaultSelected;
return (
<>
<SelectDropDown
title="Mode"
value={jailbreak ? 'Sydney' : 'BingAI'}
data-testid="bing-select-dropdown"
setValue={(value) => setOption('jailbreak')(value === 'Sydney')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
showBingToneSetting ? 'hidden' : '',
)}
/>
<Tabs
value={toneStyle}
className={cn(
cardStyle,
'z-50 flex h-[40px] flex-none items-center justify-center px-0 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md dark:hover:bg-gray-600',
)}
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
>
<TabsList className="bg-white/[.60] dark:bg-gray-700">
<TabsTrigger
value="creative"
className={`${toneStyle === 'creative' ? selectedClass('creative') : defaultClasses}`}
>
{'Creative'}
</TabsTrigger>
<TabsTrigger
value="fast"
className={`${toneStyle === 'fast' ? selectedClass('fast') : defaultClasses}`}
>
{'Fast'}
</TabsTrigger>
<TabsTrigger
value="balanced"
className={`${toneStyle === 'balanced' ? selectedClass('balanced') : defaultClasses}`}
>
{'Balanced'}
</TabsTrigger>
<TabsTrigger
value="precise"
className={`${toneStyle === 'precise' ? selectedClass('precise') : defaultClasses}`}
>
{'Precise'}
</TabsTrigger>
</TabsList>
</Tabs>
</>
);
}

View file

@ -0,0 +1,27 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function ChatGPT({ conversation, setOption, models }: ModelSelectProps) {
if (!conversation) {
return null;
}
const { conversationId, model } = conversation;
if (conversationId !== 'new') {
return null;
}
return (
<SelectDropDown
value={model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
);
}

View file

@ -0,0 +1,19 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function Google({ conversation, setOption, models }: ModelSelectProps) {
return (
<SelectDropDown
value={conversation?.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
);
}

View file

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

View file

@ -0,0 +1,19 @@
import { SelectDropDown } from '~/components/ui';
import { cn, cardStyle } from '~/utils/';
import { ModelSelectProps } from 'librechat-data-provider';
export default function OpenAI({ conversation, setOption, models }: ModelSelectProps) {
return (
<SelectDropDown
value={conversation?.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
)}
/>
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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