| 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); |
|
|