mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-20 01:06:11 +01:00
refactor: Settings/Presets UI Restructure, convert many files to TS (#740)
* progress on settings refactor * fix(helpers.js): replace fs.rmdirSync with fs.rm to delete node_modules directory recursively fix(packages.js): delete package-lock.json if it exists before running the script * feat(CrossIcon.tsx): add CrossIcon component * wip: refactor Options for modularity into higher order components, OptionsBar > ModelSelect/Settings * refactor: import more from utils/index, including cardStyle used by model select/settings * refactor(AnthropicOptions): refactor to new format, OpenAI: reduce format to name of endpoint * refactor(AnthropicSettings): refactor to new format, match defaults to API docs * fix: google and anthropic defaults * refactor(conversation/submission atoms): add typing, remove unused code * chore(types.ts): add missing type definitions for TMessages, TMessagesAtom, TConversationAtom, and ModelSelectProps feat(types.ts): make endpoint property nullable in TSubmission, TEndpointOption, TConversation, and TPreset types * refactor(ChatGPT): refactor to new format, add omit settings logic * refactor(EndpointSettings/BingAI): new dir structure and format BingAI options/settings to new * fix: update useUpdateTokenCountMutation to accept an object with a 'text' property instead of a string * fix(endpoints): ensure expected behaviors for preset dialogs * chore(index.ts): add defaultTextProps to utils/index.ts for use in settings components * chore(index.ts): add optionText to utils/index.ts for use in settings components * wip: refactor google settings * wip: progress with Google refactor, needs AdditionalButtons handling and global state setters * refactor(OptionsBar.tsx): The setOption function has been refactored to use the useSetOptions custom hook for setting conversation options. * chore(Anthropic.tsx, BingAI.tsx, Google.tsx, OpenAI.tsx): adjust height of container div in Settings component; chore(Examples.tsx): adjust height in Examples component * refactor(Google): complete google refactor feat(client): add new component PopoverButtons for displaying popover buttons in EndpointPopover feat(data-provider): add types for PopoverButton and EndpointOptionsPopoverProps * fix(OptionsBar.tsx): add useEffect hook to handle opacity class based on messagesTree and advancedMode fix(style.css): rename class from 'openAIOptions-simple-container' to 'options-bar' and update references * refactor(Plugins/OptionsBar): complete refactor of Plugins Select options, consolidate logic from TextChat to OptionsBar * fix(Plugins.tsx): filter lastSelectedTools to remove any tools that are not in the current tools list fix(useSetOptions.ts): remove unnecessary empty line * feat(useSetOptions.ts): add setAgentOption function to update agentOptions in conversation state feat(types.ts): add setAgentOption function to UseSetOptions type * refactor(Settings/Plugins): refactor to new format, refactor(OptionHover): use same component for all endpoints * refactor(OptionHover.tsx): refactor types object to use nested objects for openAI and gptPlugins feat(OptionHover.tsx): add openAI object with specific properties for openAI configuration * refactor(AgentSettings): new format, feat(types.ts): add TAgentOptions type for defining agent options in a conversation * feat(PopoverButtons.tsx): add support for GPT plugin settings button feat(Plugins.tsx): create PluginsView component for displaying plugin settings feat(optionSettings.ts): add showAgentSettings atom for controlling agent settings visibility * feat(client): add support for PluginsSettings in Input/Settings component fix(client): change import path for PluginsSettings in Input/Settings component * refactor(Settings/Plugins): complete refactor, store: refactor to TS, refactor: import defaultTextPropsLabel from utils * feat(EndpointSettings, AgentSettings, Anthropic, Google, types.ts): Add support for Recoil state management and useRecoilValue hook; Pass models from endpointsConfig to various components; Add TModels type and update ModelSelectProps type. fix(AgentSettings, Anthropic, Google, GoogleView, Plugins, OpenAI, Settings.tsx): Change import statements for ModelSelectProps from librechat-data-provider; Add models as a parameter to various components; Add models prop to PluginsView, Settings, and other components. * refactor(EditPresetDialog.jsx): update import statements for Examples and AgentSettings components feat(Settings/index.ts): add export statements for Examples and AgentSettings components * chore(package.json): update eslint-plugin-import to version 2.28.0 * fix(eslint): dependency cycle rule is now working * fix: dependency cycle errors and type errors * refactor(EditPresetDialog.jsx): update import path for DialogTemplate component refactor(NewConversationMenu/index.jsx): update import path for DialogTemplate component refactor(ExportModel.jsx): update import path for DialogTemplate component * refactor: rename NewConversationMenu to EndpointMenu * style: mobile and desktop optimizations * chore: eslint changes * chore(eslintrc.js): update eslint configuration to use 'prettier' plugin chore(postcss.config.cjs): update postcss configuration to use single quotes for require statements fix(helpers.js): fix fs.rmSync function call to delete node_modules directory recursively feat(update.js): add support for skipping git commands with '-g' flag * chore(ModelSelect.tsx): add support for azureOpenAI option component chore(Settings.tsx): add support for azureOpenAI option component chore(package.json): add rebuild:package-lock and update:branch scripts * fix(OptionHover.tsx): fix accessing nested properties in types object feat(OptionHover.tsx): add check for existence of text before rendering HoverCardContent * chore(style.css): update transition duration for options-bar from 0.3s to 0.25s * fix(ScrollToBottom.jsx): fix z-index value for scroll button * style: improve dialogs * fix(Nav.jsx): adjust width and max-width of nav component * chore(Nav.jsx): update max-width class for nav component in different screen sizes chore(Dialog.tsx): update class for DialogFooter component to use flex-row layout * fix(client): fix node_module resolution with path mapping * fix(AdjustToneButton.jsx): add z-index to adjust tone button for proper layering fix(TextChat.jsx): change onClick function to use arrow function to avoid immediate execution fix(mobile.css): update z-index for nav and nav-mask for proper layering chore(package.json): rename update:branch script to reinstall for clarity and consistency * fix(OptionsBar/Settings): add null checks for conversation in BingAI.tsx, ChatGPT.tsx, Plugins.tsx, Settings.tsx * style(TextChat/OptionsBar): match official site styles, setup regen/continue/stop buttons div * chore: Import and apply removeFocusOutlines utility across various components, and rename removeButtonOutline to removeFocusOutlines chore(Settings): Remove unused import and conditionally return null if conversation is falsy * feat(hooks): add useLocalize hook The useLocalize hook is added to the hooks/index.ts file. This hook allows for localization of phrases using the localize function from the ~/localization/Translation module. The hook uses the lang value from the store to determine the current language and returns a function that takes a phraseKey and optional values array as arguments and returns the localized phrase. * refactor(OptionHover.tsx): Update text keys for OptionHover component, use new hook: useLocalize * refactor(useDocumentTitle.ts): refactor to TS * fix(typescript): type issues and update typescript linting deps * refactor: Update ThemeContext and useOnClickOutside to TypeScript chore(useDidMountEffect.js): Remove useDidMountEffect hook * feat: GenerationButtons for stop/continue/regen, remove AdjustToneButton in favor of alternate advanced mode/Settings in OptionsBar * fix(EndpointOptionsPopover.tsx): change switchToSimpleMode function name to closePopover fix(GenerationButtons.tsx): change advancedMode prop name to showPopover fix(OptionsBar.tsx): change advancedMode state name to showPopover feat(OptionsBar.tsx): add logic to show/hide popover based on showPopover state fix(types.ts): change switchToSimpleMode function name to closePopover * chore: remove template button * chore(GenerationButtons.tsx): adjust positioning of the div element chore(Plugins.tsx): adjust width of the MultiSelectDropDown component chore(OptionsBar.tsx): adjust padding of the button element * refactor(EditPresetDialog): use new modular higher order components * chore(newoptionsbar.html): delete unused file newoptionsbar.html * refactor(EditPresetDialog): convert to TS * chore(babel.config.cjs): update babel configuration, linting * chore(EditPresetDialog.tsx): update className for DialogTemplate to include pb-0 chore(EndpointOptionsDialog.jsx): update className for DialogTemplate to include pb-0 chore(PopoverButtons.tsx): add buttonClass prop to PopoverButtons component chore(DialogTemplate.tsx): update className for the footer div to include h-auto chore(Dropdown.jsx): remove id prop from Dropdown component chore(mobile.css): update transition duration for .nav class from 0.2s to 0.15s * refactor(EditPresetDialog.tsx): simplify localization usage with hook * chore(EditPresetDialog.tsx): update containerClassName to include z-index value * fix(endpoints.ts): change type of endpointsConfig atom to TEndpointsConfig refactor(cleanupPreset.ts): convert to TS fix(index.ts): export cleanupPreset utility function fix(types.ts): add missing properties to TPreset type * refactor(EndpointOptionsDialog): convert to TS * fix(EditPresetDialog.tsx): - import cleanupPreset from index - add null check before submitting preset - add null check before exporting preset refactor(SaveAsPresetDialog.tsx): convert to TS fix(usePresetOptions.ts): import cleanupPreset from index fix(types.ts): - make title prop optional in EditPresetProps - change preset prop in CleanupPreset to be partial * chore: reorganize imports in App, EndpointMenu, Messages, and ExportModel components feat(ScreenshotContext.jsx): add ScreenshotContext to hooks/index chore(index.ts): export ThemeContext, ScreenshotContext, ApiErrorBoundaryContext hooks, cleanupPreset, and getIcon functions from utils * wip: add headerClassName for dialog template * chore(EndpointOptionsDialog.tsx): remove unused headerClassName prop chore(EndpointOptionsDialog.tsx): adjust height of main container in mobile and desktop view * fix(react-query-service.ts): change return type of useGetEndpointsQuery to QueryObserverResult<t.TEndpointsConfig> * refactor: imports from index and refactor to TS * refactor: refactor all svg components to TS * refactor: refactor all UI components to TS, remove unused component * fix(SelectDropDown.tsx): remove file extension from import statement for CheckMark component * fix: SaveAsPresetDialog typing issue * fix(OptionsBar): close popover when an endpoint with no settings is selected * chore(ChatGPT.tsx): update width of model select dropdown to 60px refactor(types.ts): decouple ModelSelectProps from SettingsProps * fix(popover Settings): space taken from the options menu for each endpoint * fix:'Set token first' element alignment, add padding to endpointmenu icon in mobile * style: match official site header * refactor(EndpointOptionsDialog): make functionality explicitly saving current convos as presets * fix(useLocalize.ts): change values parameter from an array to rest parameters * refactor(EndpointSettings): Utilize useLocalize hook for all endpoint settings * fix(Popover): correct spacing/center and remove focus outlines for close button * chore: employ use of cn (clsx) in Popover styles * chore(EditPresetDialog.tsx): update className to add padding bottom chore(EndpointOptionsDialog.tsx): update className to add padding bottom * style(EndpointMenu, TextChat): add better styling at diff. breakpoints * refactor(EndpointSettings): consolidate container style to higher order component * refactor(EditPresetDialog.tsx): pass custom style to Settings from here * style: setting dialogs improved in all views * style(EndpointMenu): improve UX for mobile * style(PresetDialog): increase height so scrollbar isn't triggered * chore(EditPresetDialog.tsx): update className to include xl height for DialogTemplate chore(InputNumber.tsx): update className to include max height for InputNumber component * fix: light mode styling * fix(OptionsBar/ScrollToBottom/Popover): quick fix to rework in future: hide scrollToBottom when Popover is open * style: remove bg-gradient around textarea in mobile view * chore(ThemeContext.tsx): refactor ThemeContext to use default context value, also fixes type issue * chore(EditPresetDialog.tsx): adjust grid layout in EditPresetDialog component * style(TextChat): make gradient more opaque/smoother * fix(TextChat.jsx): fix background gradient color based on theme and system preference * test(layout-test-utils.tsx): add mock implementation for window.matchMedia in test setup feat(layout-test-utils.tsx): add authConfig prop to AuthContextProvider in renderWithProvidersWrapper function chore(tsconfig.json): include test directory in tsconfig include section * chore(jest.config.cjs): update test file paths in jest configuration chore(Login.spec.tsx): update test file path in import statement chore(LoginForm.spec.tsx): update test file path in import statement chore(Registration.spec.tsx): update test file path in import statement chore(PluginAuthForm.spec.tsx): update test file path in import statement chore(PluginStoreDialog.spec.tsx): update test file path in import statement chore(layout-test-utils.tsx): move matchMedia mock to separate file chore(tsconfig.json): add path mapping for test files in client directory * test: add import for 'test/matchMedia.mock' in test files The changes in this commit add an import statement for 'test/matchMedia.mock' in multiple test files. This import is necessary for mocking the behavior of the matchMedia function during testing. * style(ClearConvosDialog): remove borders from button and modal, uniform button size * fix(AgentSettings.tsx): overlapping issue * fix(PresetDialogs): improve spacing of top row and dialog content * style(Settings): 2nd column will now dynamically adjust better across all screen sizes * style(ModelSelect): improve styling for mobile/desktop, add hover shadow feat(ModelSelect/Plugins): hide ModelSelect when screen is small * refactor(RowButton, buildTree): convert to TS * style(ModelSelect): add transition effect to shadows on hover
This commit is contained in:
parent
fb99e5a7da
commit
956aa6c674
203 changed files with 5062 additions and 4327 deletions
|
|
@ -1,18 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
export default function AdjustToneButton({ onClick }) {
|
||||
const clickHandler = (e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
};
|
||||
return (
|
||||
<button
|
||||
onClick={clickHandler}
|
||||
className="group absolute bottom-11 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500 lg:-right-11 lg:bottom-0"
|
||||
>
|
||||
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||
<Settings2 size="1em" />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { SelectDropDown, Button } from '~/components';
|
||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||
import Settings from '../../Endpoints/Anthropic/Settings.jsx';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
function AnthropicOptions() {
|
||||
const [advancedMode, setAdvancedMode] = useState(false);
|
||||
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
||||
|
||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||
const { endpoint } = conversation;
|
||||
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } =
|
||||
conversation;
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
|
||||
if (endpoint !== 'anthropic') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const models = endpointsConfig?.['anthropic']?.['availableModels'] || [];
|
||||
|
||||
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
|
||||
|
||||
const switchToSimpleMode = () => {
|
||||
setAdvancedMode(false);
|
||||
};
|
||||
|
||||
const saveAsPreset = () => {
|
||||
setSaveAsDialogShow(true);
|
||||
};
|
||||
|
||||
const setOption = (param) => (newValue) => {
|
||||
let update = {};
|
||||
update[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const cardStyle =
|
||||
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
|
||||
(!advancedMode ? ' show' : '')
|
||||
}
|
||||
>
|
||||
<SelectDropDown
|
||||
value={model}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
|
||||
)}
|
||||
onClick={triggerAdvancedMode}
|
||||
>
|
||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
<EndpointOptionsPopover
|
||||
content={
|
||||
<div className="px-4 py-4">
|
||||
<Settings
|
||||
model={model}
|
||||
modelLabel={modelLabel}
|
||||
promptPrefix={promptPrefix}
|
||||
temperature={temperature}
|
||||
topP={topP}
|
||||
topK={topK}
|
||||
maxOutputTokens={maxOutputTokens}
|
||||
setOption={setOption}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
visible={advancedMode}
|
||||
saveAsPreset={saveAsPreset}
|
||||
switchToSimpleMode={switchToSimpleMode}
|
||||
/>
|
||||
<SaveAsPresetDialog
|
||||
open={saveAsDialogShow}
|
||||
onOpenChange={setSaveAsDialogShow}
|
||||
preset={conversation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnthropicOptions;
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { cn } from '~/utils';
|
||||
import { Button } from '../../ui/Button.tsx';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { Tabs, TabsList, TabsTrigger } from '../../ui/Tabs.tsx';
|
||||
import SelectDropDown from '../../ui/SelectDropDown';
|
||||
import Settings from '../../Endpoints/BingAI/Settings.jsx';
|
||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
function BingAIOptions({ show }) {
|
||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||
const [advancedMode, setAdvancedMode] = useState(false);
|
||||
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
||||
const { endpoint, conversationId } = conversation;
|
||||
const { toneStyle, context, systemMessage, jailbreak } = conversation;
|
||||
|
||||
if (endpoint !== 'bingAI') {
|
||||
return null;
|
||||
}
|
||||
if (conversationId !== 'new' && !show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
|
||||
|
||||
const switchToSimpleMode = () => {
|
||||
setAdvancedMode(false);
|
||||
};
|
||||
|
||||
const saveAsPreset = () => {
|
||||
setSaveAsDialogShow(true);
|
||||
};
|
||||
|
||||
const setOption = (param) => (newValue) => {
|
||||
let update = {};
|
||||
update[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const cardStyle =
|
||||
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||
const defaultClasses =
|
||||
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
|
||||
const defaultSelected = cn(
|
||||
defaultClasses,
|
||||
'font-medium data-[state=active]:text-white text-xs text-white',
|
||||
);
|
||||
const selectedClass = (val) => val + '-tab ' + defaultSelected;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
|
||||
(!advancedMode ? ' show' : '')
|
||||
}
|
||||
>
|
||||
<SelectDropDown
|
||||
title="Mode"
|
||||
value={jailbreak ? 'Sydney' : 'BingAI'}
|
||||
data-testid="bing-select-dropdown"
|
||||
setValue={(value) => setOption('jailbreak')(value === 'Sydney')}
|
||||
availableValues={['BingAI', 'Sydney']}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-36 z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
show ? 'hidden' : null,
|
||||
)}
|
||||
/>
|
||||
|
||||
<Tabs
|
||||
value={toneStyle}
|
||||
className={
|
||||
cardStyle +
|
||||
' z-50 flex h-[40px] flex-none items-center justify-center px-0 hover:bg-slate-50 dark:hover:bg-gray-600'
|
||||
}
|
||||
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
|
||||
>
|
||||
<TabsList className="bg-white/[.60] dark:bg-gray-700">
|
||||
<TabsTrigger
|
||||
value="creative"
|
||||
className={`${toneStyle === 'creative' ? selectedClass('creative') : defaultClasses}`}
|
||||
>
|
||||
{'Creative'}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="fast"
|
||||
className={`${toneStyle === 'fast' ? selectedClass('fast') : defaultClasses}`}
|
||||
>
|
||||
{'Fast'}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="balanced"
|
||||
className={`${toneStyle === 'balanced' ? selectedClass('balanced') : defaultClasses}`}
|
||||
>
|
||||
{'Balanced'}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="precise"
|
||||
className={`${toneStyle === 'precise' ? selectedClass('precise') : defaultClasses}`}
|
||||
>
|
||||
{'Precise'}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
|
||||
show ? 'hidden' : null,
|
||||
)}
|
||||
onClick={triggerAdvancedMode}
|
||||
>
|
||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
<EndpointOptionsPopover
|
||||
content={
|
||||
<div className="z-50 px-4 py-4">
|
||||
<Settings
|
||||
context={context}
|
||||
systemMessage={systemMessage}
|
||||
jailbreak={jailbreak}
|
||||
toneStyle={toneStyle}
|
||||
setOption={setOption}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
visible={advancedMode}
|
||||
saveAsPreset={saveAsPreset}
|
||||
switchToSimpleMode={switchToSimpleMode}
|
||||
/>
|
||||
<SaveAsPresetDialog
|
||||
open={saveAsDialogShow}
|
||||
onOpenChange={setSaveAsDialogShow}
|
||||
preset={conversation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default BingAIOptions;
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import SelectDropDown from '../../ui/SelectDropDown';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
function ChatGPTOptions() {
|
||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||
const { endpoint, conversationId } = conversation;
|
||||
const { model } = conversation;
|
||||
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
|
||||
if (endpoint !== 'chatGPTBrowser') {
|
||||
return null;
|
||||
}
|
||||
if (conversationId !== 'new') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const models = endpointsConfig?.['chatGPTBrowser']?.['availableModels'] || [];
|
||||
|
||||
const setOption = (param) => (newValue) => {
|
||||
let update = {};
|
||||
update[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const cardStyle =
|
||||
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||
|
||||
return (
|
||||
<div className="openAIOptions-simple-container show flex w-full flex-wrap items-center justify-center gap-2">
|
||||
<SelectDropDown
|
||||
value={model}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'z-50 flex h-[40px] w-[260px] min-w-[260px] flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatGPTOptions;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { useState } from 'react';
|
||||
import { DropdownMenuRadioItem } from '~/components';
|
||||
import { Settings } from 'lucide-react';
|
||||
import getIcon from '~/utils/getIcon';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { DropdownMenuRadioItem } from '~/components';
|
||||
import { getIcon } from '~/components/Endpoints';
|
||||
import { SetTokenDialog } from '../SetTokenDialog';
|
||||
|
||||
import store from '~/store';
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import EndpointItem from './EndpointItem.jsx';
|
||||
import EndpointItem from './EndpointItem';
|
||||
|
||||
export default function EndpointItems({ endpoints, onSelect, selectedEndpoint }) {
|
||||
return (
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import cleanupPreset from '~/utils/cleanupPreset.js';
|
||||
import { useRecoilValue, useRecoilState } from 'recoil';
|
||||
import EditPresetDialog from '../../Endpoints/EditPresetDialog';
|
||||
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider';
|
||||
import { getIcon, EditPresetDialog } from '~/components/Endpoints';
|
||||
import EndpointItems from './EndpointItems';
|
||||
import PresetItems from './PresetItems';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import FileUpload from './FileUpload';
|
||||
import getIcon from '~/utils/getIcon';
|
||||
import getDefaultConversation from '~/utils/getDefaultConversation';
|
||||
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider';
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
|
|
@ -18,11 +15,11 @@ import {
|
|||
DropdownMenuRadioGroup,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
DialogTemplate,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
} from '../../ui/';
|
||||
import { cn } from '~/utils/';
|
||||
} from '~/components/ui/';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { cn, cleanupPreset, getDefaultConversation } from '~/utils';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -156,7 +153,7 @@ export default function NewConversationMenu() {
|
|||
id="new-conversation-menu"
|
||||
variant="outline"
|
||||
className={
|
||||
'group relative mb-[-12px] ml-0 mt-[-8px] items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-[-12px] md:pl-1'
|
||||
'group relative mb-[-12px] ml-1 mt-[-8px] items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-0 md:ml-[-12px] md:pl-1'
|
||||
}
|
||||
>
|
||||
{icon}
|
||||
|
|
@ -166,7 +163,7 @@ export default function NewConversationMenu() {
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="z-[100] w-96 dark:bg-gray-900"
|
||||
className="z-[100] w-[375px] dark:bg-gray-900 md:w-96"
|
||||
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<DropdownMenuLabel
|
||||
|
|
@ -7,7 +7,7 @@ type FileUploadProps = {
|
|||
className?: string;
|
||||
successText?: string;
|
||||
invalidText?: string;
|
||||
validator?: ((data: any) => boolean) | null;
|
||||
validator?: ((data: Record<string, unknown>) => boolean) | null;
|
||||
text?: string;
|
||||
id?: string;
|
||||
};
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
|
||||
import EditIcon from '../../svg/EditIcon.jsx';
|
||||
import TrashIcon from '../../svg/TrashIcon.jsx';
|
||||
import getIcon from '~/utils/getIcon';
|
||||
import { DropdownMenuRadioItem, EditIcon, TrashIcon } from '~/components';
|
||||
import { getIcon } from '~/components/Endpoints';
|
||||
|
||||
export default function PresetItem({ preset = {}, value, onChangePreset, onDeletePreset }) {
|
||||
const { endpoint } = preset;
|
||||
|
|
@ -63,30 +61,35 @@ export default function PresetItem({ preset = {}, value, onChangePreset, onDelet
|
|||
return (
|
||||
<DropdownMenuRadioItem
|
||||
value={value}
|
||||
className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||
className="group flex h-10 max-h-[44px] flex-row justify-between dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800 sm:h-auto"
|
||||
>
|
||||
{icon}
|
||||
<small className="text-[11px]">{preset?.title}</small>
|
||||
<small className="ml-2 text-[10px]">({getPresetTitle()})</small>
|
||||
<div className="flex w-4 flex-1" />
|
||||
<button
|
||||
className="invisible m-0 mr-1 rounded-md p-2 text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onChangePreset(preset);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
className="invisible m-0 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onDeletePreset(preset);
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<div className="flex items-center justify-start">
|
||||
{icon}
|
||||
<small className="text-[11px]">{preset?.title}</small>
|
||||
<small className="invisible ml-1 flex w-0 flex-shrink text-[10px] sm:visible sm:w-auto">
|
||||
({getPresetTitle()})
|
||||
</small>
|
||||
</div>
|
||||
<div className="flex h-full items-center justify-end">
|
||||
<button
|
||||
className="m-0 mr-1 h-full rounded-md px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:p-2 sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onChangePreset(preset);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
className="m-0 h-full rounded-md px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:p-2 sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onDeletePreset(preset);
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import PresetItem from './PresetItem.jsx';
|
||||
import PresetItem from './PresetItem';
|
||||
|
||||
export default function PresetItems({ presets, onSelect, onChangePreset, onDeletePreset }) {
|
||||
return (
|
||||
1
client/src/components/Input/EndpointMenu/index.ts
Normal file
1
client/src/components/Input/EndpointMenu/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as EndpointMenu } from './EndpointMenu';
|
||||
47
client/src/components/Input/GenerationButtons.tsx
Normal file
47
client/src/components/Input/GenerationButtons.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { cn, removeFocusOutlines } from '~/utils/';
|
||||
|
||||
type GenerationButtonsProps = {
|
||||
showPopover: boolean;
|
||||
opacityClass: string;
|
||||
};
|
||||
|
||||
export default function GenerationButtons({ showPopover, opacityClass }: GenerationButtonsProps) {
|
||||
return (
|
||||
<div className="absolute bottom-4 right-0 z-[62]">
|
||||
<div className="grow"></div>
|
||||
<div className="flex items-center md:items-end">
|
||||
<div
|
||||
className={cn('option-buttons', showPopover ? '' : opacityClass)}
|
||||
data-projection-id="173"
|
||||
>
|
||||
{/* <button
|
||||
className={cn(
|
||||
'custom-btn btn-neutral relative -z-0 whitespace-nowrap border-0 md:border',
|
||||
removeFocusOutlines,
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-3 w-3 flex-shrink-0"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="1 4 1 10 7 10"></polyline>
|
||||
<polyline points="23 20 23 14 17 14"></polyline>
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
|
||||
</svg>
|
||||
Regenerate
|
||||
</div>
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { SelectDropDown, Button, MessagesSquared } from '~/components';
|
||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||
import Settings from '../../Endpoints/Google/Settings.jsx';
|
||||
import Examples from '../../Endpoints/Google/Examples.jsx';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
function GoogleOptions() {
|
||||
const [advancedMode, setAdvancedMode] = useState(false);
|
||||
const [showExamples, setShowExamples] = useState(false);
|
||||
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
||||
|
||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||
const { endpoint } = conversation;
|
||||
const { model, modelLabel, promptPrefix, examples, temperature, topP, topK, maxOutputTokens } =
|
||||
conversation;
|
||||
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
|
||||
if (endpoint !== 'google') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const models = endpointsConfig?.['google']?.['availableModels'] || [];
|
||||
|
||||
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
|
||||
const triggerExamples = () => setShowExamples((prev) => !prev);
|
||||
|
||||
const switchToSimpleMode = () => {
|
||||
setAdvancedMode(false);
|
||||
};
|
||||
|
||||
const saveAsPreset = () => {
|
||||
setSaveAsDialogShow(true);
|
||||
};
|
||||
|
||||
const setOption = (param) => (newValue) => {
|
||||
let update = {};
|
||||
update[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const setExample = (i, type, newValue = null) => {
|
||||
let update = {};
|
||||
let current = conversation?.examples.slice() || [];
|
||||
let currentExample = { ...current[i] } || {};
|
||||
currentExample[type] = { content: newValue };
|
||||
current[i] = currentExample;
|
||||
update.examples = current;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const addExample = () => {
|
||||
let update = {};
|
||||
let current = conversation?.examples.slice() || [];
|
||||
current.push({ input: { content: '' }, output: { content: '' } });
|
||||
update.examples = current;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const removeExample = () => {
|
||||
let update = {};
|
||||
let current = conversation?.examples.slice() || [];
|
||||
if (current.length <= 1) {
|
||||
update.examples = [{ input: { content: '' }, output: { content: '' } }];
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
current.pop();
|
||||
update.examples = current;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const cardStyle =
|
||||
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||
|
||||
const isCodeChat = model?.startsWith('codechat-');
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
|
||||
(!advancedMode ? ' show' : '')
|
||||
}
|
||||
>
|
||||
<SelectDropDown
|
||||
value={model}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
|
||||
)}
|
||||
onClick={triggerAdvancedMode}
|
||||
>
|
||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
<EndpointOptionsPopover
|
||||
content={
|
||||
<div className="px-4 py-4">
|
||||
{showExamples && !isCodeChat ? (
|
||||
<Examples
|
||||
examples={examples}
|
||||
setExample={setExample}
|
||||
addExample={addExample}
|
||||
removeExample={removeExample}
|
||||
/>
|
||||
) : (
|
||||
<Settings
|
||||
model={model}
|
||||
modelLabel={modelLabel}
|
||||
promptPrefix={promptPrefix}
|
||||
temperature={temperature}
|
||||
topP={topP}
|
||||
topK={topK}
|
||||
maxOutputTokens={maxOutputTokens}
|
||||
setOption={setOption}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
visible={advancedMode}
|
||||
saveAsPreset={saveAsPreset}
|
||||
switchToSimpleMode={switchToSimpleMode}
|
||||
additionalButton={{
|
||||
label: (showExamples ? 'Hide' : 'Show') + ' Examples',
|
||||
buttonClass: isCodeChat ? 'disabled' : '',
|
||||
handler: triggerExamples,
|
||||
icon: <MessagesSquared className="mr-1 w-[14px]" />,
|
||||
}}
|
||||
/>
|
||||
<SaveAsPresetDialog
|
||||
open={saveAsDialogShow}
|
||||
onOpenChange={setSaveAsDialogShow}
|
||||
preset={conversation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GoogleOptions;
|
||||
19
client/src/components/Input/ModelSelect/Anthropic.tsx
Normal file
19
client/src/components/Input/ModelSelect/Anthropic.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { SelectDropDown } from '~/components/ui';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import { ModelSelectProps } from 'librechat-data-provider';
|
||||
|
||||
export default function Anthropic({ conversation, setOption, models }: ModelSelectProps) {
|
||||
return (
|
||||
<SelectDropDown
|
||||
value={conversation?.model ?? ''}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
78
client/src/components/Input/ModelSelect/BingAI.tsx
Normal file
78
client/src/components/Input/ModelSelect/BingAI.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { SelectDropDown, Tabs, TabsList, TabsTrigger } from '~/components/ui';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import { ModelSelectProps } from 'librechat-data-provider';
|
||||
import store from '~/store';
|
||||
|
||||
export default function BingAI({ conversation, setOption, models }: ModelSelectProps) {
|
||||
const showBingToneSetting = useRecoilValue(store.showBingToneSetting);
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
const { conversationId, toneStyle, jailbreak } = conversation;
|
||||
if (conversationId !== 'new' && !showBingToneSetting) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultClasses =
|
||||
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
|
||||
const defaultSelected = cn(
|
||||
defaultClasses,
|
||||
'font-medium data-[state=active]:text-white text-xs text-white',
|
||||
);
|
||||
const selectedClass = (val: string) => val + '-tab ' + defaultSelected;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectDropDown
|
||||
title="Mode"
|
||||
value={jailbreak ? 'Sydney' : 'BingAI'}
|
||||
data-testid="bing-select-dropdown"
|
||||
setValue={(value) => setOption('jailbreak')(value === 'Sydney')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
showBingToneSetting ? 'hidden' : '',
|
||||
)}
|
||||
/>
|
||||
<Tabs
|
||||
value={toneStyle}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'z-50 flex h-[40px] flex-none items-center justify-center px-0 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md dark:hover:bg-gray-600',
|
||||
)}
|
||||
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
|
||||
>
|
||||
<TabsList className="bg-white/[.60] dark:bg-gray-700">
|
||||
<TabsTrigger
|
||||
value="creative"
|
||||
className={`${toneStyle === 'creative' ? selectedClass('creative') : defaultClasses}`}
|
||||
>
|
||||
{'Creative'}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="fast"
|
||||
className={`${toneStyle === 'fast' ? selectedClass('fast') : defaultClasses}`}
|
||||
>
|
||||
{'Fast'}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="balanced"
|
||||
className={`${toneStyle === 'balanced' ? selectedClass('balanced') : defaultClasses}`}
|
||||
>
|
||||
{'Balanced'}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="precise"
|
||||
className={`${toneStyle === 'precise' ? selectedClass('precise') : defaultClasses}`}
|
||||
>
|
||||
{'Precise'}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
27
client/src/components/Input/ModelSelect/ChatGPT.tsx
Normal file
27
client/src/components/Input/ModelSelect/ChatGPT.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { SelectDropDown } from '~/components/ui';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import { ModelSelectProps } from 'librechat-data-provider';
|
||||
|
||||
export default function ChatGPT({ conversation, setOption, models }: ModelSelectProps) {
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
const { conversationId, model } = conversation;
|
||||
if (conversationId !== 'new') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectDropDown
|
||||
value={model ?? ''}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
19
client/src/components/Input/ModelSelect/Google.tsx
Normal file
19
client/src/components/Input/ModelSelect/Google.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { SelectDropDown } from '~/components/ui';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import { ModelSelectProps } from 'librechat-data-provider';
|
||||
|
||||
export default function Google({ conversation, setOption, models }: ModelSelectProps) {
|
||||
return (
|
||||
<SelectDropDown
|
||||
value={conversation?.model ?? ''}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
39
client/src/components/Input/ModelSelect/ModelSelect.tsx
Normal file
39
client/src/components/Input/ModelSelect/ModelSelect.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import OpenAI from './OpenAI';
|
||||
import BingAI from './BingAI';
|
||||
import Google from './Google';
|
||||
import Plugins from './Plugins';
|
||||
import ChatGPT from './ChatGPT';
|
||||
import Anthropic from './Anthropic';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SelectProps, ModelSelectProps } from 'librechat-data-provider';
|
||||
import store from '~/store';
|
||||
|
||||
type OptionComponentType = React.FC<ModelSelectProps>;
|
||||
|
||||
const optionComponents: { [key: string]: OptionComponentType } = {
|
||||
openAI: OpenAI,
|
||||
azureOpenAI: OpenAI,
|
||||
bingAI: BingAI,
|
||||
google: Google,
|
||||
gptPlugins: Plugins,
|
||||
anthropic: Anthropic,
|
||||
chatGPTBrowser: ChatGPT,
|
||||
};
|
||||
|
||||
export default function ModelSelect({ conversation, setOption }: SelectProps) {
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
if (!conversation?.endpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { endpoint } = conversation;
|
||||
const OptionComponent = optionComponents[endpoint];
|
||||
const models = endpointsConfig?.[endpoint]?.['availableModels'] ?? [];
|
||||
|
||||
if (!OptionComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <OptionComponent conversation={conversation} setOption={setOption} models={models} />;
|
||||
}
|
||||
19
client/src/components/Input/ModelSelect/OpenAI.tsx
Normal file
19
client/src/components/Input/ModelSelect/OpenAI.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { SelectDropDown } from '~/components/ui';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import { ModelSelectProps } from 'librechat-data-provider';
|
||||
|
||||
export default function OpenAI({ conversation, setOption, models }: ModelSelectProps) {
|
||||
return (
|
||||
<SelectDropDown
|
||||
value={conversation?.model ?? ''}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
111
client/src/components/Input/ModelSelect/Plugins.tsx
Normal file
111
client/src/components/Input/ModelSelect/Plugins.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
import { ModelSelectProps, useAvailablePluginsQuery, TPlugin } from 'librechat-data-provider';
|
||||
import { SelectDropDown, MultiSelectDropDown, Button } from '~/components/ui';
|
||||
import { useSetOptions, useAuthContext, useMediaQuery } from '~/hooks';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import store from '~/store';
|
||||
|
||||
const pluginStore: TPlugin = {
|
||||
name: 'Plugin store',
|
||||
pluginKey: 'pluginStore',
|
||||
isButton: true,
|
||||
description: '',
|
||||
icon: '',
|
||||
authConfig: [],
|
||||
authenticated: false,
|
||||
};
|
||||
|
||||
export default function Plugins({ conversation, setOption, models }: ModelSelectProps) {
|
||||
const { data: allPlugins } = useAvailablePluginsQuery();
|
||||
const [visible, setVisibility] = useState<boolean>(true);
|
||||
const [availableTools, setAvailableTools] = useRecoilState(store.availableTools);
|
||||
const { checkPluginSelection, setTools } = useSetOptions();
|
||||
const { user } = useAuthContext();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 640px)');
|
||||
|
||||
useEffect(() => {
|
||||
if (isSmallScreen) {
|
||||
setVisibility(false);
|
||||
}
|
||||
}, [isSmallScreen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allPlugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user.plugins || user.plugins.length === 0) {
|
||||
setAvailableTools([pluginStore]);
|
||||
return;
|
||||
}
|
||||
|
||||
const tools = [...user.plugins]
|
||||
.map((el) => allPlugins.find((plugin) => plugin.pluginKey === el))
|
||||
.filter((el): el is TPlugin => el !== undefined);
|
||||
|
||||
/* Filter Last Selected Tools */
|
||||
const lastSelectedTools = JSON.parse(localStorage.getItem('lastSelectedTools') ?? '');
|
||||
const filteredTools = lastSelectedTools.filter((tool: TPlugin) =>
|
||||
tools.some((existingTool) => existingTool.pluginKey === tool.pluginKey),
|
||||
);
|
||||
localStorage.setItem('lastSelectedTools', JSON.stringify(filteredTools));
|
||||
|
||||
setAvailableTools([...tools, pluginStore]);
|
||||
// setAvailableTools is a recoil state setter, so it's safe to use it in useEffect
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [allPlugins, user]);
|
||||
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 transition duration-700 ease-in-out hover:bg-white hover:shadow-md focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
|
||||
)}
|
||||
onClick={() => setVisibility((prev) => !prev)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
!visible ? 'rotate-180 transform' : '',
|
||||
'w-4 text-gray-600 dark:text-white',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
<SelectDropDown
|
||||
value={conversation.model ?? ''}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-60 z-40 flex w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
|
||||
visible ? '' : 'hidden',
|
||||
)}
|
||||
/>
|
||||
<MultiSelectDropDown
|
||||
value={conversation.tools || []}
|
||||
isSelected={checkPluginSelection}
|
||||
setSelected={setTools}
|
||||
availableValues={availableTools}
|
||||
optionValueKey="pluginKey"
|
||||
showAbove={true}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-60 z-50 w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
|
||||
visible ? '' : 'hidden',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
client/src/components/Input/ModelSelect/index.ts
Normal file
1
client/src/components/Input/ModelSelect/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as ModelSelect } from './ModelSelect';
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import SelectDropDown from '../../ui/SelectDropDown';
|
||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||
import { Button } from '../../ui/Button.tsx';
|
||||
import Settings from '../../Endpoints/OpenAI/Settings.jsx';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
function OpenAIOptions() {
|
||||
const [advancedMode, setAdvancedMode] = useState(false);
|
||||
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
||||
|
||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||
const { endpoint } = conversation;
|
||||
const {
|
||||
model,
|
||||
chatGptLabel,
|
||||
promptPrefix,
|
||||
temperature,
|
||||
top_p,
|
||||
presence_penalty,
|
||||
frequency_penalty,
|
||||
} = conversation;
|
||||
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
const isOpenAI = endpoint === 'openAI' || endpoint === 'azureOpenAI';
|
||||
if (!isOpenAI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
|
||||
|
||||
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
|
||||
|
||||
const switchToSimpleMode = () => {
|
||||
setAdvancedMode(false);
|
||||
};
|
||||
|
||||
const saveAsPreset = () => {
|
||||
setSaveAsDialogShow(true);
|
||||
};
|
||||
|
||||
const setOption = (param) => (newValue) => {
|
||||
let update = {};
|
||||
update[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const cardStyle =
|
||||
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
|
||||
(!advancedMode ? ' show' : '')
|
||||
}
|
||||
>
|
||||
<SelectDropDown
|
||||
value={model}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
|
||||
)}
|
||||
onClick={triggerAdvancedMode}
|
||||
>
|
||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
<EndpointOptionsPopover
|
||||
content={
|
||||
<div className="px-4 py-4">
|
||||
<Settings
|
||||
model={model}
|
||||
chatGptLabel={chatGptLabel}
|
||||
promptPrefix={promptPrefix}
|
||||
temperature={temperature}
|
||||
topP={top_p}
|
||||
freqP={presence_penalty}
|
||||
presP={frequency_penalty}
|
||||
setOption={setOption}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
visible={advancedMode}
|
||||
saveAsPreset={saveAsPreset}
|
||||
switchToSimpleMode={switchToSimpleMode}
|
||||
/>
|
||||
<SaveAsPresetDialog
|
||||
open={saveAsDialogShow}
|
||||
onOpenChange={setSaveAsDialogShow}
|
||||
preset={conversation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OpenAIOptions;
|
||||
135
client/src/components/Input/OptionsBar.tsx
Normal file
135
client/src/components/Input/OptionsBar.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { Settings2 } from 'lucide-react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { TPreset } from 'librechat-data-provider';
|
||||
import { PluginStoreDialog } from '~/components';
|
||||
import {
|
||||
EndpointSettings,
|
||||
SaveAsPresetDialog,
|
||||
EndpointOptionsPopover,
|
||||
} from '~/components/Endpoints';
|
||||
import { Button } from '~/components/ui';
|
||||
import { cn, cardStyle } from '~/utils/';
|
||||
import { useSetOptions } from '~/hooks';
|
||||
import { ModelSelect } from './ModelSelect';
|
||||
import GenerationButtons from './GenerationButtons';
|
||||
import store from '~/store';
|
||||
|
||||
export default function OptionsBar() {
|
||||
const conversation = useRecoilValue(store.conversation);
|
||||
const messagesTree = useRecoilValue(store.messagesTree);
|
||||
const latestMessage = useRecoilValue(store.latestMessage);
|
||||
const setShowBingToneSetting = useSetRecoilState(store.showBingToneSetting);
|
||||
const [showPluginStoreDialog, setShowPluginStoreDialog] = useRecoilState(
|
||||
store.showPluginStoreDialog,
|
||||
);
|
||||
const [saveAsDialogShow, setSaveAsDialogShow] = useState<boolean>(false);
|
||||
const [showPopover, setShowPopover] = useRecoilState(store.showPopover);
|
||||
const [opacityClass, setOpacityClass] = useState('full-opacity');
|
||||
const { setOption } = useSetOptions();
|
||||
|
||||
const { endpoint, conversationId, jailbreak } = conversation ?? {};
|
||||
|
||||
const altConditions: { [key: string]: boolean } = {
|
||||
bingAI: !!(latestMessage && conversation?.jailbreak && endpoint === 'bingAI'),
|
||||
};
|
||||
|
||||
const altSettings: { [key: string]: () => void } = {
|
||||
bingAI: () => setShowBingToneSetting((prev) => !prev),
|
||||
};
|
||||
|
||||
const noSettings = useMemo<{ [key: string]: boolean }>(
|
||||
() => ({
|
||||
chatGPTBrowser: true,
|
||||
bingAI: jailbreak ? false : conversationId !== 'new',
|
||||
}),
|
||||
[jailbreak, conversationId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (showPopover) {
|
||||
return;
|
||||
} else if (messagesTree && messagesTree.length >= 1) {
|
||||
setOpacityClass('show');
|
||||
} else {
|
||||
setOpacityClass('full-opacity');
|
||||
}
|
||||
}, [messagesTree, showPopover]);
|
||||
|
||||
useEffect(() => {
|
||||
if (endpoint && noSettings[endpoint]) {
|
||||
setShowPopover(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [endpoint, noSettings]);
|
||||
|
||||
const saveAsPreset = () => {
|
||||
setSaveAsDialogShow(true);
|
||||
};
|
||||
|
||||
if (!endpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const triggerAdvancedMode = altConditions[endpoint]
|
||||
? altSettings[endpoint]
|
||||
: () => setShowPopover((prev) => !prev);
|
||||
return (
|
||||
<div className="relative py-2 last:mb-2 md:mx-4 md:mb-[-16px] md:py-4 md:pt-2 md:last:mb-6 lg:mx-auto lg:mb-[-32px] lg:max-w-2xl lg:pt-6 xl:max-w-3xl">
|
||||
<GenerationButtons showPopover={showPopover} opacityClass={opacityClass} />
|
||||
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
'options-bar z-[61] flex w-full flex-wrap items-center justify-center gap-2',
|
||||
showPopover ? '' : opacityClass,
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
if (showPopover) {
|
||||
return;
|
||||
}
|
||||
setOpacityClass('full-opacity');
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (showPopover) {
|
||||
return;
|
||||
}
|
||||
if (!messagesTree || messagesTree.length === 0) {
|
||||
return;
|
||||
}
|
||||
setOpacityClass('show');
|
||||
}}
|
||||
>
|
||||
<ModelSelect conversation={conversation} setOption={setOption} />
|
||||
{!noSettings[endpoint] && (
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-3 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
|
||||
)}
|
||||
onClick={triggerAdvancedMode}
|
||||
>
|
||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<EndpointOptionsPopover
|
||||
endpoint={endpoint}
|
||||
visible={showPopover}
|
||||
saveAsPreset={saveAsPreset}
|
||||
closePopover={() => setShowPopover(false)}
|
||||
>
|
||||
<div className="px-4 py-4">
|
||||
<EndpointSettings conversation={conversation} setOption={setOption} />
|
||||
</div>
|
||||
</EndpointOptionsPopover>
|
||||
<SaveAsPresetDialog
|
||||
open={saveAsDialogShow}
|
||||
onOpenChange={setSaveAsDialogShow}
|
||||
preset={{ ...conversation } as TPreset}
|
||||
/>
|
||||
<PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
import { useState, useEffect, memo } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Settings2, ChevronDownIcon } from 'lucide-react';
|
||||
import {
|
||||
SelectDropDown,
|
||||
PluginStoreDialog,
|
||||
MultiSelectDropDown,
|
||||
Button,
|
||||
GPTIcon,
|
||||
} from '~/components';
|
||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||
import { Settings, AgentSettings } from '../../Endpoints/Plugins/';
|
||||
import { cn } from '~/utils/';
|
||||
import store from '~/store';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { useAvailablePluginsQuery } from 'librechat-data-provider';
|
||||
|
||||
function PluginsOptions() {
|
||||
const { data: allPlugins } = useAvailablePluginsQuery();
|
||||
const [visibile, setVisibility] = useState(true);
|
||||
const [advancedMode, setAdvancedMode] = useState(false);
|
||||
const [availableTools, setAvailableTools] = useState([]);
|
||||
const [showAgentSettings, setShowAgentSettings] = useState(false);
|
||||
const [showSavePresetDialog, setShowSavePresetDialog] = useState(false);
|
||||
const [showPluginStoreDialog, setShowPluginStoreDialog] = useState(false);
|
||||
const [opacityClass, setOpacityClass] = useState('full-opacity');
|
||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
const messagesTree = useRecoilValue(store.messagesTree);
|
||||
const { user } = useAuthContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (advancedMode) {
|
||||
return;
|
||||
} else if (messagesTree?.length >= 1) {
|
||||
setOpacityClass('show');
|
||||
} else {
|
||||
setOpacityClass('full-opacity');
|
||||
}
|
||||
}, [messagesTree, advancedMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allPlugins && user) {
|
||||
const pluginStore = { name: 'Plugin store', pluginKey: 'pluginStore', isButton: true };
|
||||
if (!user.plugins || user.plugins.length === 0) {
|
||||
setAvailableTools([pluginStore]);
|
||||
return;
|
||||
}
|
||||
const tools = [...user.plugins]
|
||||
.map((el) => {
|
||||
return allPlugins.find((plugin) => plugin.pluginKey === el);
|
||||
})
|
||||
.filter((el) => el);
|
||||
setAvailableTools([...tools, pluginStore]);
|
||||
}
|
||||
}, [allPlugins, user]);
|
||||
|
||||
const triggerAgentSettings = () => setShowAgentSettings((prev) => !prev);
|
||||
const { endpoint, agentOptions } = conversation;
|
||||
|
||||
if (endpoint !== 'gptPlugins') {
|
||||
return null;
|
||||
}
|
||||
const models = endpointsConfig?.['gptPlugins']?.['availableModels'] || [];
|
||||
|
||||
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
|
||||
|
||||
const switchToSimpleMode = () => {
|
||||
setAdvancedMode(false);
|
||||
};
|
||||
|
||||
const saveAsPreset = () => {
|
||||
setShowSavePresetDialog(true);
|
||||
};
|
||||
|
||||
function checkIfSelected(value) {
|
||||
if (!conversation.tools) {
|
||||
return false;
|
||||
}
|
||||
return conversation.tools.find((el) => el.pluginKey === value) ? true : false;
|
||||
}
|
||||
|
||||
const setOption = (param) => (newValue) => {
|
||||
let update = {};
|
||||
update[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const setAgentOption = (param) => (newValue) => {
|
||||
const editableConvo = JSON.stringify(conversation);
|
||||
const convo = JSON.parse(editableConvo);
|
||||
let { agentOptions } = convo;
|
||||
agentOptions[param] = newValue;
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
agentOptions,
|
||||
}));
|
||||
};
|
||||
|
||||
const setTools = (newValue) => {
|
||||
if (newValue === 'pluginStore') {
|
||||
setShowPluginStoreDialog(true);
|
||||
return;
|
||||
}
|
||||
let update = {};
|
||||
let current = conversation.tools || [];
|
||||
let isSelected = checkIfSelected(newValue);
|
||||
let tool = availableTools[availableTools.findIndex((el) => el.pluginKey === newValue)];
|
||||
if (isSelected) {
|
||||
update.tools = current.filter((el) => el.pluginKey !== newValue);
|
||||
} else {
|
||||
update.tools = [...current, tool];
|
||||
}
|
||||
localStorage.setItem('lastSelectedTools', JSON.stringify(update.tools));
|
||||
setConversation((prevState) => ({
|
||||
...prevState,
|
||||
...update,
|
||||
}));
|
||||
};
|
||||
|
||||
const cardStyle =
|
||||
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'pluginOptions flex w-full flex-wrap items-center justify-center gap-2 ' +
|
||||
(!advancedMode ? opacityClass : '')
|
||||
}
|
||||
onMouseEnter={() => {
|
||||
if (advancedMode) {
|
||||
return;
|
||||
}
|
||||
setOpacityClass('full-opacity');
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (advancedMode) {
|
||||
return;
|
||||
}
|
||||
if (!messagesTree || messagesTree.length === 0) {
|
||||
return;
|
||||
}
|
||||
setOpacityClass('show');
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
|
||||
)}
|
||||
onClick={() => setVisibility((prev) => !prev)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
!visibile ? 'rotate-180 transform' : '',
|
||||
'w-4 text-gray-600 dark:text-white',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
<SelectDropDown
|
||||
value={conversation.model}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={true}
|
||||
className={cn(cardStyle, 'min-w-60 z-40 flex w-60', !visibile && 'hidden')}
|
||||
/>
|
||||
<MultiSelectDropDown
|
||||
value={conversation.tools || []}
|
||||
isSelected={checkIfSelected}
|
||||
setSelected={setTools}
|
||||
availableValues={availableTools}
|
||||
optionValueKey="pluginKey"
|
||||
showAbove={true}
|
||||
className={cn(cardStyle, 'min-w-60 z-50 w-60', !visibile && 'hidden')}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
|
||||
!visibile && 'hidden',
|
||||
)}
|
||||
onClick={triggerAdvancedMode}
|
||||
>
|
||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
<EndpointOptionsPopover
|
||||
content={
|
||||
<div className="px-4 py-4">
|
||||
{showAgentSettings ? (
|
||||
<AgentSettings
|
||||
agent={agentOptions.agent}
|
||||
skipCompletion={agentOptions.skipCompletion}
|
||||
model={agentOptions.model}
|
||||
endpoint={agentOptions.endpoint}
|
||||
temperature={agentOptions.temperature}
|
||||
topP={agentOptions.top_p}
|
||||
freqP={agentOptions.presence_penalty}
|
||||
presP={agentOptions.frequency_penalty}
|
||||
setOption={setAgentOption}
|
||||
tools={conversation.tools}
|
||||
/>
|
||||
) : (
|
||||
<Settings
|
||||
model={conversation.model}
|
||||
endpoint={endpoint}
|
||||
chatGptLabel={conversation.chatGptLabel}
|
||||
promptPrefix={conversation.promptPrefix}
|
||||
temperature={conversation.temperature}
|
||||
topP={conversation.top_p}
|
||||
freqP={conversation.presence_penalty}
|
||||
presP={conversation.frequency_penalty}
|
||||
setOption={setOption}
|
||||
tools={conversation.tools}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
visible={advancedMode}
|
||||
saveAsPreset={saveAsPreset}
|
||||
switchToSimpleMode={switchToSimpleMode}
|
||||
additionalButton={{
|
||||
label: `Show ${showAgentSettings ? 'Completion' : 'Agent'} Settings`,
|
||||
handler: triggerAgentSettings,
|
||||
icon: <GPTIcon className="mr-1 mt-[2px] w-[14px]" size={14} />,
|
||||
}}
|
||||
/>
|
||||
<SaveAsPresetDialog
|
||||
open={showSavePresetDialog}
|
||||
onOpenChange={setShowSavePresetDialog}
|
||||
preset={conversation}
|
||||
/>
|
||||
<PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(PluginsOptions);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import FileUpload from '../NewConversationMenu/FileUpload';
|
||||
import FileUpload from '../EndpointMenu/FileUpload';
|
||||
|
||||
const GoogleConfig = ({ setToken }: { setToken: React.Dispatch<React.SetStateAction<string>> }) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ChangeEvent, FC } from 'react';
|
||||
import { Input, Label } from '~/components';
|
||||
import { cn } from '~/utils/';
|
||||
import { cn, defaultTextPropsLabel, removeFocusOutlines } from '~/utils/';
|
||||
|
||||
interface InputWithLabelProps {
|
||||
value: string;
|
||||
|
|
@ -10,9 +10,6 @@ interface InputWithLabelProps {
|
|||
}
|
||||
|
||||
const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id }) => {
|
||||
const defaultTextProps =
|
||||
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label htmlFor={id} className="text-left text-sm font-medium">
|
||||
|
|
@ -26,8 +23,9 @@ const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id })
|
|||
onChange={onChange}
|
||||
placeholder={`Enter ${label}`}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0',
|
||||
defaultTextPropsLabel,
|
||||
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
|
||||
removeFocusOutlines,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
const { getToken } = store.useToken(endpoint);
|
||||
|
||||
useEffect(() => {
|
||||
let oldToken = getToken();
|
||||
const oldToken = getToken();
|
||||
if (isJson(token)) {
|
||||
setShowPanel(true);
|
||||
}
|
||||
|
|
@ -41,14 +41,14 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
|
||||
function getAzure(name: string) {
|
||||
if (isJson(token)) {
|
||||
let newToken = JSON.parse(token);
|
||||
const newToken = JSON.parse(token);
|
||||
return newToken[name];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function setAzure(name: string, value: any) {
|
||||
function setAzure(name: string, value: number | string | boolean) {
|
||||
let newToken = {};
|
||||
if (isJson(token)) {
|
||||
newToken = JSON.parse(token);
|
||||
|
|
@ -64,7 +64,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
<InputWithLabel
|
||||
id={'chatGPTLabel'}
|
||||
value={token || ''}
|
||||
onChange={(e: { target: { value: any } }) => setToken(e.target.value || '')}
|
||||
onChange={(e: { target: { value: string } }) => setToken(e.target.value || '')}
|
||||
label={'OpenAI API Key'}
|
||||
/>
|
||||
</>
|
||||
|
|
@ -73,7 +73,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
<InputWithLabel
|
||||
id={'instanceNameLabel'}
|
||||
value={getAzure('azureOpenAIApiInstanceName') || ''}
|
||||
onChange={(e: { target: { value: any } }) =>
|
||||
onChange={(e: { target: { value: string } }) =>
|
||||
setAzure('azureOpenAIApiInstanceName', e.target.value || '')
|
||||
}
|
||||
label={'Azure OpenAI Instance Name'}
|
||||
|
|
@ -82,7 +82,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
<InputWithLabel
|
||||
id={'deploymentNameLabel'}
|
||||
value={getAzure('azureOpenAIApiDeploymentName') || ''}
|
||||
onChange={(e: { target: { value: any } }) =>
|
||||
onChange={(e: { target: { value: string } }) =>
|
||||
setAzure('azureOpenAIApiDeploymentName', e.target.value || '')
|
||||
}
|
||||
label={'Azure OpenAI Deployment Name'}
|
||||
|
|
@ -91,7 +91,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
<InputWithLabel
|
||||
id={'versionLabel'}
|
||||
value={getAzure('azureOpenAIApiVersion') || ''}
|
||||
onChange={(e: { target: { value: any } }) =>
|
||||
onChange={(e: { target: { value: string } }) =>
|
||||
setAzure('azureOpenAIApiVersion', e.target.value || '')
|
||||
}
|
||||
label={'Azure OpenAI API Version'}
|
||||
|
|
@ -100,7 +100,7 @@ const OpenAIConfig = ({ token, setToken, endpoint }: OpenAIConfigProps) => {
|
|||
<InputWithLabel
|
||||
id={'apiKeyLabel'}
|
||||
value={getAzure('azureOpenAIApiKey') || ''}
|
||||
onChange={(e: { target: { value: any } }) =>
|
||||
onChange={(e: { target: { value: string } }) =>
|
||||
setAzure('azureOpenAIApiKey', e.target.value || '')
|
||||
}
|
||||
label={'Azure OpenAI API Key'}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import HelpText from './HelpText';
|
|||
import GoogleConfig from './GoogleConfig';
|
||||
import OpenAIConfig from './OpenAIConfig';
|
||||
import OtherConfig from './OtherConfig';
|
||||
import { Dialog, DialogTemplate } from '~/components';
|
||||
import { Dialog } from '~/components/ui';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { alternateName } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
|||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTemplate
|
||||
title={`Set Token for ${alternateName[endpoint] ?? endpoint}`}
|
||||
className="w-full max-w-[650px] sm:w-3/4 md:w-3/4 lg:w-3/4"
|
||||
main={
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<EndpointComponent token={token} setToken={setToken} endpoint={endpoint} />
|
||||
|
|
|
|||
|
|
@ -45,11 +45,13 @@ export default function SubmitButton({
|
|||
<button
|
||||
onClick={setToken}
|
||||
type="button"
|
||||
className="group absolute bottom-0 right-0 z-[101] flex h-[100%] w-auto items-center justify-center bg-transparent p-1 text-gray-500"
|
||||
className="group absolute bottom-0 right-0 z-[101] flex h-[100%] w-auto items-center justify-center bg-transparent pr-1 text-gray-500"
|
||||
>
|
||||
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] align-middle text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||
<Settings className="mr-1 inline-block w-[18px]" />
|
||||
Set Token First
|
||||
<div className="flex items-center justify-center rounded-md text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||
<div className="m-0 mr-0 flex items-center justify-center rounded-md p-2 sm:p-2">
|
||||
<Settings className="mr-1 inline-block h-auto w-[18px]" />
|
||||
Set Token First
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<SetTokenDialog
|
||||
|
|
|
|||
|
|
@ -1,37 +1,31 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useRecoilValue, useRecoilState } from 'recoil';
|
||||
import SubmitButton from './SubmitButton';
|
||||
import OpenAIOptions from './OpenAIOptions';
|
||||
import PluginsOptions from './PluginsOptions';
|
||||
import ChatGPTOptions from './ChatGPTOptions';
|
||||
import BingAIOptions from './BingAIOptions';
|
||||
import GoogleOptions from './GoogleOptions';
|
||||
import AnthropicOptions from './AnthropicOptions';
|
||||
import NewConversationMenu from './NewConversationMenu';
|
||||
import AdjustToneButton from './AdjustToneButton';
|
||||
import Footer from './Footer';
|
||||
import React, { useEffect, useContext, useRef } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { useMessageHandler } from '~/utils/handleSubmit';
|
||||
|
||||
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import SubmitButton from './SubmitButton';
|
||||
import OptionsBar from './OptionsBar';
|
||||
import { EndpointMenu } from './EndpointMenu';
|
||||
import Footer from './Footer';
|
||||
import { useMessageHandler, ThemeContext } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function TextChat({ isSearchView = false }) {
|
||||
const inputRef = useRef(null);
|
||||
const isComposing = useRef(false);
|
||||
|
||||
const [text, setText] = useRecoilState(store.text);
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const conversation = useRecoilValue(store.conversation);
|
||||
const latestMessage = useRecoilValue(store.latestMessage);
|
||||
const [text, setText] = useRecoilState(store.text);
|
||||
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
const isSubmitting = useRecoilValue(store.isSubmitting);
|
||||
const setShowBingToneSetting = useSetRecoilState(store.showBingToneSetting);
|
||||
|
||||
// TODO: do we need this?
|
||||
const disabled = false;
|
||||
|
||||
const { ask, stopGenerating } = useMessageHandler();
|
||||
const [showBingToneSetting, setShowBingToneSetting] = useState(false);
|
||||
|
||||
const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error;
|
||||
const { conversationId, jailbreak } = conversation || {};
|
||||
|
||||
|
|
@ -49,6 +43,8 @@ export default function TextChat({ isSearchView = false }) {
|
|||
if (conversationId !== 'search') {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
// setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [conversationId, jailbreak]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -127,38 +123,42 @@ export default function TextChat({ isSearchView = false }) {
|
|||
return '';
|
||||
};
|
||||
|
||||
const handleBingToneSetting = () => {
|
||||
setShowBingToneSetting((show) => !show);
|
||||
};
|
||||
|
||||
if (isSearchView) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
let isDark = theme === 'dark';
|
||||
|
||||
if (theme === 'system') {
|
||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed bottom-0 left-0 w-full border-transparent bg-gradient-to-b from-transparent via-white to-white pt-6 dark:border-white/20 dark:via-gray-800 dark:to-gray-800 md:absolute">
|
||||
<div className="relative py-2 md:mb-[-16px] md:py-4 lg:mb-[-32px]">
|
||||
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
|
||||
<OpenAIOptions />
|
||||
<PluginsOptions />
|
||||
<ChatGPTOptions />
|
||||
<GoogleOptions />
|
||||
<BingAIOptions show={showBingToneSetting} />
|
||||
<AnthropicOptions />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="no-gradient-sm fixed bottom-0 left-0 w-full pt-6 sm:bg-gradient-to-b md:absolute"
|
||||
style={{
|
||||
background: `linear-gradient(to bottom,
|
||||
${isDark ? 'rgba(52, 53, 65, 0)' : 'rgba(255, 255, 255, 0)'},
|
||||
${isDark ? 'rgba(52, 53, 65, 0.08)' : 'rgba(255, 255, 255, 0.08)'},
|
||||
${isDark ? 'rgba(52, 53, 65, 0.38)' : 'rgba(255, 255, 255, 0.38)'},
|
||||
${isDark ? 'rgba(52, 53, 65, 1)' : 'rgba(255, 255, 255, 1)'},
|
||||
${isDark ? '#343541' : '#ffffff'})`,
|
||||
}}
|
||||
>
|
||||
<OptionsBar />
|
||||
<div className="input-panel md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient relative w-full border-t bg-white py-2 dark:border-white/20 dark:bg-gray-800 md:border-t-0 md:border-transparent md:bg-transparent md:dark:border-transparent md:dark:bg-transparent">
|
||||
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
|
||||
<form className="stretch z-[60] mx-2 flex flex-row gap-3 last:mb-2 md:mx-4 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
|
||||
<div className="relative flex h-full flex-1 md:flex-col">
|
||||
<div
|
||||
className={`relative flex flex-grow flex-row rounded-md border border-black/10 ${
|
||||
disabled ? 'bg-gray-100' : 'bg-white'
|
||||
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
|
||||
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
|
||||
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
|
||||
className={cn(
|
||||
'relative flex flex-grow flex-row rounded-xl border border-black/10 py-[10px] md:py-4 md:pl-4',
|
||||
'shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]',
|
||||
'dark:border-gray-900/50 dark:text-white',
|
||||
disabled ? 'bg-gray-100 dark:bg-gray-900' : 'bg-white dark:bg-gray-700',
|
||||
)}
|
||||
>
|
||||
<NewConversationMenu />
|
||||
<EndpointMenu />
|
||||
<TextareaAutosize
|
||||
// set test id for e2e testing
|
||||
data-testid="text-input"
|
||||
|
|
@ -185,9 +185,6 @@ export default function TextChat({ isSearchView = false }) {
|
|||
endpointsConfig={endpointsConfig}
|
||||
endpoint={conversation?.endpoint}
|
||||
/>
|
||||
{latestMessage && conversation?.jailbreak && conversation.endpoint === 'bingAI' ? (
|
||||
<AdjustToneButton onClick={handleBingToneSetting} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
Loading…
Add table
Add a link
Reference in a new issue