LibreChat/client/src/components/Nav/SettingsTabs/General/General.tsx
Danny Avila 711d21365d
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
🪟 refactor: Portal Control for Shared View Settings and Google Balance Support (#10786)
* feat: Add support for Google model endpoint in balance check

* feat: Add portal prop to ThemeSelector and LangSelector in ShareView
2025-12-03 10:29:23 -05:00

202 lines
6.4 KiB
TypeScript

import React, { useContext, useCallback } from 'react';
import Cookies from 'js-cookie';
import { useRecoilState } from 'recoil';
import { Dropdown, ThemeContext } from '@librechat/client';
import ArchivedChats from './ArchivedChats';
import ToggleSwitch from '../ToggleSwitch';
import { useLocalize } from '~/hooks';
import store from '~/store';
const toggleSwitchConfigs = [
{
stateAtom: store.enableUserMsgMarkdown,
localizationKey: 'com_nav_user_msg_markdown',
switchId: 'enableUserMsgMarkdown',
hoverCardText: undefined,
key: 'enableUserMsgMarkdown',
},
{
stateAtom: store.autoScroll,
localizationKey: 'com_nav_auto_scroll',
switchId: 'autoScroll',
hoverCardText: undefined,
key: 'autoScroll',
},
{
stateAtom: store.hideSidePanel,
localizationKey: 'com_nav_hide_panel',
switchId: 'hideSidePanel',
hoverCardText: undefined,
key: 'hideSidePanel',
},
{
stateAtom: store.keepScreenAwake,
localizationKey: 'com_nav_keep_screen_awake',
switchId: 'keepScreenAwake',
hoverCardText: undefined,
key: 'keepScreenAwake',
},
];
export const ThemeSelector = ({
theme,
onChange,
portal = true,
}: {
theme: string;
onChange: (value: string) => void;
portal?: boolean;
}) => {
const localize = useLocalize();
const themeOptions = [
{ value: 'system', label: localize('com_nav_theme_system') },
{ value: 'dark', label: localize('com_nav_theme_dark') },
{ value: 'light', label: localize('com_nav_theme_light') },
];
const labelId = 'theme-selector-label';
return (
<div className="flex items-center justify-between">
<div id={labelId}>{localize('com_nav_theme')}</div>
<Dropdown
value={theme}
onChange={onChange}
options={themeOptions}
sizeClasses="w-[180px]"
testId="theme-selector"
className="z-50"
aria-labelledby={labelId}
portal={portal}
/>
</div>
);
};
export const LangSelector = ({
langcode,
onChange,
portal = true,
}: {
langcode: string;
onChange: (value: string) => void;
portal?: boolean;
}) => {
const localize = useLocalize();
const languageOptions = [
{ value: 'auto', label: localize('com_nav_lang_auto') },
{ value: 'en-US', label: localize('com_nav_lang_english') },
{ value: 'zh-Hans', label: localize('com_nav_lang_chinese') },
{ value: 'zh-Hant', label: localize('com_nav_lang_traditional_chinese') },
{ value: 'ar-EG', label: localize('com_nav_lang_arabic') },
{ value: 'bs', label: localize('com_nav_lang_bosnian') },
{ value: 'da-DK', label: localize('com_nav_lang_danish') },
{ value: 'de-DE', label: localize('com_nav_lang_german') },
{ value: 'es-ES', label: localize('com_nav_lang_spanish') },
{ value: 'ca-ES', label: localize('com_nav_lang_catalan') },
{ value: 'et-EE', label: localize('com_nav_lang_estonian') },
{ value: 'fa-IR', label: localize('com_nav_lang_persian') },
{ value: 'fr-FR', label: localize('com_nav_lang_french') },
{ value: 'he-HE', label: localize('com_nav_lang_hebrew') },
{ value: 'hu-HU', label: localize('com_nav_lang_hungarian') },
{ value: 'hy-AM', label: localize('com_nav_lang_armenian') },
{ value: 'it-IT', label: localize('com_nav_lang_italian') },
{ value: 'nb', label: localize('com_nav_lang_norwegian_bokmal') },
{ value: 'pl-PL', label: localize('com_nav_lang_polish') },
{ value: 'pt-BR', label: localize('com_nav_lang_brazilian_portuguese') },
{ value: 'pt-PT', label: localize('com_nav_lang_portuguese') },
{ value: 'ru-RU', label: localize('com_nav_lang_russian') },
{ value: 'ja-JP', label: localize('com_nav_lang_japanese') },
{ value: 'ka-GE', label: localize('com_nav_lang_georgian') },
{ value: 'cs-CZ', label: localize('com_nav_lang_czech') },
{ value: 'sv-SE', label: localize('com_nav_lang_swedish') },
{ value: 'ko-KR', label: localize('com_nav_lang_korean') },
{ value: 'lv-LV', label: localize('com_nav_lang_latvian') },
{ value: 'vi-VN', label: localize('com_nav_lang_vietnamese') },
{ value: 'th-TH', label: localize('com_nav_lang_thai') },
{ value: 'tr-TR', label: localize('com_nav_lang_turkish') },
{ value: 'ug', label: localize('com_nav_lang_uyghur') },
{ value: 'nl-NL', label: localize('com_nav_lang_dutch') },
{ value: 'id-ID', label: localize('com_nav_lang_indonesia') },
{ value: 'fi-FI', label: localize('com_nav_lang_finnish') },
{ value: 'sl', label: localize('com_nav_lang_slovenian') },
{ value: 'bo', label: localize('com_nav_lang_tibetan') },
{ value: 'uk-UA', label: localize('com_nav_lang_ukrainian') },
];
const labelId = 'language-selector-label';
return (
<div className="flex items-center justify-between">
<div id={labelId}>{localize('com_nav_language')}</div>
<Dropdown
value={langcode}
onChange={onChange}
sizeClasses="[--anchor-max-height:256px] max-h-[60vh]"
options={languageOptions}
className="z-50"
aria-labelledby={labelId}
portal={portal}
/>
</div>
);
};
function General() {
const { theme, setTheme } = useContext(ThemeContext);
const [langcode, setLangcode] = useRecoilState(store.lang);
const changeTheme = useCallback(
(value: string) => {
setTheme(value);
},
[setTheme],
);
const changeLang = useCallback(
(value: string) => {
let userLang = value;
if (value === 'auto') {
userLang = navigator.language || navigator.languages[0];
}
requestAnimationFrame(() => {
document.documentElement.lang = userLang;
});
setLangcode(userLang);
Cookies.set('lang', userLang, { expires: 365 });
},
[setLangcode],
);
return (
<div className="flex flex-col gap-3 p-1 text-sm text-text-primary">
<div className="pb-3">
<ThemeSelector theme={theme} onChange={changeTheme} />
</div>
<div className="pb-3">
<LangSelector langcode={langcode} onChange={changeLang} />
</div>
{toggleSwitchConfigs.map((config) => (
<div key={config.key} className="pb-3">
<ToggleSwitch
stateAtom={config.stateAtom}
localizationKey={config.localizationKey}
hoverCardText={config.hoverCardText}
switchId={config.switchId}
/>
</div>
))}
<div className="pb-3">
<ArchivedChats />
</div>
</div>
);
}
export default React.memo(General);