diff --git a/client/src/components/Endpoints/EditPresetDialog.tsx b/client/src/components/Endpoints/EditPresetDialog.tsx index 55a08d0c6..d334fcb99 100644 --- a/client/src/components/Endpoints/EditPresetDialog.tsx +++ b/client/src/components/Endpoints/EditPresetDialog.tsx @@ -91,14 +91,9 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }: TEditP setOption('endpoint')(value)} options={availableEndpoints} - className={cn( - defaultTextProps, - 'flex h-10 max-h-10 w-full resize-none ', - removeFocusOutlines, - )} - containerClassName="flex w-full resize-none z-[51]" + className={cn()} /> diff --git a/client/src/components/Endpoints/Settings/Anthropic.tsx b/client/src/components/Endpoints/Settings/Anthropic.tsx index 2572048f9..db1ab7cfc 100644 --- a/client/src/components/Endpoints/Settings/Anthropic.tsx +++ b/client/src/components/Endpoints/Settings/Anthropic.tsx @@ -40,7 +40,7 @@ export default function Settings({ conversation, setOption, models, readonly }: setValue={setModel} availableValues={models} disabled={readonly} - className={cn(defaultTextProps, 'z-50 flex w-full resize-none', removeFocusOutlines)} + className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)} containerClassName="flex w-full resize-none" /> diff --git a/client/src/components/Endpoints/Settings/Google.tsx b/client/src/components/Endpoints/Settings/Google.tsx index 07507f86c..d9c397dd4 100644 --- a/client/src/components/Endpoints/Settings/Google.tsx +++ b/client/src/components/Endpoints/Settings/Google.tsx @@ -42,7 +42,7 @@ export default function Settings({ conversation, setOption, models, readonly }: setValue={setModel} availableValues={models} disabled={readonly} - className={cn(defaultTextProps, 'z-50 flex w-full resize-none', removeFocusOutlines)} + className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)} containerClassName="flex w-full resize-none" /> diff --git a/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx b/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx index e944a94ee..6af135184 100644 --- a/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx +++ b/client/src/components/Input/SetKeyDialog/SetKeyDialog.tsx @@ -3,7 +3,7 @@ import type { TDialogProps } from '~/common'; import { Dialog, Dropdown } from '~/components/ui'; import DialogTemplate from '~/components/ui/DialogTemplate'; import { RevokeKeysButton } from '~/components/Nav'; -import { cn, defaultTextProps, removeFocusOutlines, alternateName } from '~/utils'; +import { cn, alternateName } from '~/utils'; import { useUserKey, useLocalize } from '~/hooks'; import GoogleConfig from './GoogleConfig'; import OpenAIConfig from './OpenAIConfig'; @@ -75,13 +75,7 @@ const SetKeyDialog = ({ value={expiresAtLabel} onChange={handleExpirationChange} options={expirationOptions.map((option) => option.display)} - className={cn( - defaultTextProps, - 'flex h-full w-full resize-none', - removeFocusOutlines, - )} - optionsClassName="max-h-72" - containerClassName="flex w-1/2 md:w-1/3 resize-none z-[51]" + width={185} /> diff --git a/client/src/components/Input/SubmitButton.tsx b/client/src/components/Input/SubmitButton.tsx index ae75386a4..cf8ca37c2 100644 --- a/client/src/components/Input/SubmitButton.tsx +++ b/client/src/components/Input/SubmitButton.tsx @@ -2,7 +2,9 @@ import React, { useState, useEffect, useCallback } from 'react'; import { StopGeneratingIcon } from '~/components'; import { Settings } from 'lucide-react'; import { SetKeyDialog } from './SetKeyDialog'; -import { useUserKey, useLocalize } from '~/hooks'; +import { useUserKey, useLocalize, useMediaQuery } from '~/hooks'; +import { SendMessageIcon } from '~/components/svg'; +import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui/'; export default function SubmitButton({ conversation, @@ -11,6 +13,7 @@ export default function SubmitButton({ disabled, isSubmitting, userProvidesKey, + hasText, }) { const { endpoint } = conversation; const [isDialogOpen, setDialogOpen] = useState(false); @@ -18,6 +21,16 @@ export default function SubmitButton({ const [isKeyProvided, setKeyProvided] = useState(userProvidesKey ? checkExpiry() : true); const isKeyActive = checkExpiry(); const localize = useLocalize(); + const dots = ['·', '··', '···']; + const [dotIndex, setDotIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setDotIndex((prevDotIndex) => (prevDotIndex + 1) % dots.length); + }, 500); + + return () => clearInterval(interval); + }, [dots.length]); useEffect(() => { if (userProvidesKey) { @@ -35,22 +48,43 @@ export default function SubmitButton({ [submitMessage], ); + const [isSquareGreen, setIsSquareGreen] = useState(false); + const setKey = useCallback(() => { setDialogOpen(true); }, []); - if (isSubmitting) { + const isSmallScreen = useMediaQuery('(max-width: 768px)'); + + const iconContainerClass = `m-1 mr-0 rounded-md pb-[5px] pl-[6px] pr-[4px] pt-[5px] ${ + hasText ? (isSquareGreen ? 'bg-green-500' : '') : '' + } group-hover:bg-19C37D group-disabled:hover:bg-transparent dark:${ + hasText ? (isSquareGreen ? 'bg-green-500' : '') : '' + } dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent`; + + useEffect(() => { + setIsSquareGreen(hasText); + }, [hasText]); + + if (isSubmitting && isSmallScreen) { return ( - ); + } else if (isSubmitting) { + return ( +
+
+ {dots[dotIndex]} +
+
+ ); } else if (!isKeyProvided) { return ( <> @@ -73,34 +107,31 @@ export default function SubmitButton({ ); } else { return ( - + + + + + + + {localize('com_nav_send_message')} + + + ); } } - -{ - /*
··
*/ -} diff --git a/client/src/components/Input/TextChat.jsx b/client/src/components/Input/TextChat.tsx similarity index 79% rename from client/src/components/Input/TextChat.jsx rename to client/src/components/Input/TextChat.tsx index bf325d596..af04409b5 100644 --- a/client/src/components/Input/TextChat.jsx +++ b/client/src/components/Input/TextChat.tsx @@ -1,7 +1,8 @@ -import React, { useEffect, useContext, useRef } from 'react'; +import React, { useEffect, useContext, useRef, useState, useCallback } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil'; import SubmitButton from './SubmitButton'; + import OptionsBar from './OptionsBar'; import { EndpointMenu } from './EndpointMenu'; import Footer from './Footer'; @@ -9,7 +10,11 @@ import { useMessageHandler, ThemeContext } from '~/hooks'; import { cn } from '~/utils'; import store from '~/store'; -export default function TextChat({ isSearchView = false }) { +interface TextChatProps { + isSearchView?: boolean; +} + +export default function TextChat({ isSearchView = false }: TextChatProps) { const { ask, isSubmitting, handleStopGenerating, latestMessage, endpointsConfig } = useMessageHandler(); const conversation = useRecoilValue(store.conversation); @@ -17,21 +22,22 @@ export default function TextChat({ isSearchView = false }) { const [text, setText] = useRecoilState(store.text); const { theme } = useContext(ThemeContext); const isComposing = useRef(false); - const inputRef = useRef(null); + const inputRef = useRef(null); + const [hasText, setHasText] = useState(false); // TODO: do we need this? const disabled = false; - const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error; + const isNotAppendable = (latestMessage?.unfinished && !isSubmitting) || latestMessage?.error; const { conversationId, jailbreak } = conversation || {}; - // auto focus to input, when enter a conversation. + // auto focus to input, when entering a conversation. useEffect(() => { if (!conversationId) { return; } - // Prevents Settings from not showing on new conversation, also prevents showing toneStyle change without jailbreak + // Prevents Settings from not showing on a new conversation, also prevents showing toneStyle change without jailbreak if (conversationId === 'new' || !jailbreak) { setShowBingToneSetting(false); } @@ -54,9 +60,10 @@ export default function TextChat({ isSearchView = false }) { const submitMessage = () => { ask({ text }); setText(''); + setHasText(false); }; - const handleKeyDown = (e) => { + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && isSubmitting) { return; } @@ -70,9 +77,9 @@ export default function TextChat({ isSearchView = false }) { } }; - const handleKeyUp = (e) => { - if (e.keyCode === 8 && e.target.value.trim() === '') { - setText(e.target.value); + const handleKeyUp = (e: React.KeyboardEvent) => { + if (e.keyCode === 8 && e.currentTarget.value.trim() === '') { + setText(e.currentTarget.value); } if (e.key === 'Enter' && e.shiftKey) { @@ -92,12 +99,24 @@ export default function TextChat({ isSearchView = false }) { isComposing.current = false; }; - const changeHandler = (e) => { + const changeHandler = (e: React.ChangeEvent) => { const { value } = e.target; setText(value); + updateHasText(value); }; + const updateHasText = useCallback( + (text: string) => { + setHasText(!!text.trim() || !!latestMessage?.error); + }, + [setHasText, latestMessage], + ); + + useEffect(() => { + updateHasText(text); + }, [text, latestMessage, updateHasText]); + const getPlaceholderText = () => { if (isSearchView) { return 'Click a message title to open its conversation.'; @@ -153,11 +172,11 @@ export default function TextChat({ isSearchView = false }) { diff --git a/client/src/components/Messages/MessageHeader.tsx b/client/src/components/Messages/MessageHeader.tsx index 38fd20ba4..375203865 100644 --- a/client/src/components/Messages/MessageHeader.tsx +++ b/client/src/components/Messages/MessageHeader.tsx @@ -89,8 +89,9 @@ const MessageHeader = ({ isSearchView = false }) => { <>
(isNotClickable ? null : setSaveAsDialogShow(true))} > diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index fae97366f..5cd62ed8c 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -221,7 +221,7 @@ export default function Nav({ navVisible, setNavVisible }) {
{!navVisible && ( -
+
-
+
{user?.name || localize('com_nav_user')}
- + +
+ +
diff --git a/client/src/components/Nav/Settings.tsx b/client/src/components/Nav/Settings.tsx index 1c7a58497..cce7ec27c 100644 --- a/client/src/components/Nav/Settings.tsx +++ b/client/src/components/Nav/Settings.tsx @@ -1,6 +1,6 @@ import * as Tabs from '@radix-ui/react-tabs'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui'; -import { CogIcon, DataIcon } from '~/components/svg'; +import { GearIcon, DataIcon } from '~/components/svg'; import { useMediaQuery, useLocalize } from '~/hooks'; import type { TDialogProps } from '~/common'; import { General, Data } from './SettingsTabs'; @@ -12,7 +12,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) { return ( - + {localize('com_nav_settings')} @@ -21,7 +24,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
- + {localize('com_nav_setting_general')} -
{localize('com_nav_auto_scroll')}
+
{localize('com_nav_auto_scroll')}
- {showText &&
{localize(infoTextCode)}
} + {showText &&
{localize(infoTextCode)}
} { const localize = useLocalize(); + const themeOptions = [ + { value: 'system', display: localize('com_nav_theme_system') }, + { value: 'dark', display: localize('com_nav_theme_dark') }, + { value: 'light', display: localize('com_nav_theme_light') }, + ]; + return (
-
{localize('com_nav_theme')}
- + onChange={onChange} + options={themeOptions} + width={150} + testId="theme-selector" + />
); }; @@ -76,32 +81,30 @@ export const LangSelector = ({ }) => { const localize = useLocalize(); + // Create an array of options for the Dropdown + const languageOptions = [ + { value: 'auto', display: localize('com_nav_lang_auto') }, + { value: 'en-US', display: localize('com_nav_lang_english') }, + { value: 'zh-CN', display: localize('com_nav_lang_chinese') }, + { value: 'zh-TC', display: localize('com_nav_lang_traditionalchinese') }, + { value: 'ar-EG', display: localize('com_nav_lang_arabic') }, + { value: 'de-DE', display: localize('com_nav_lang_german') }, + { value: 'es-ES', display: localize('com_nav_lang_spanish') }, + { value: 'fr-FR', display: localize('com_nav_lang_french') }, + { value: 'it-IT', display: localize('com_nav_lang_italian') }, + { value: 'pl-PL', display: localize('com_nav_lang_polish') }, + { value: 'pt-BR', display: localize('com_nav_lang_brazilian_portuguese') }, + { value: 'ru-RU', display: localize('com_nav_lang_russian') }, + { value: 'ja-JP', display: localize('com_nav_lang_japanese') }, + { value: 'sv-SE', display: localize('com_nav_lang_swedish') }, + { value: 'ko-KR', display: localize('com_nav_lang_korean') }, + { value: 'vi-VN', display: localize('com_nav_lang_vietnamese') }, + ]; + return (
-
{localize('com_nav_language')}
- +
{localize('com_nav_language')}
+
); }; diff --git a/client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx b/client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx index c4135f28b..5633e7fc6 100644 --- a/client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx +++ b/client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx @@ -13,25 +13,36 @@ describe('LangSelector', () => { }); it('renders correctly', () => { - const { getByText, getByDisplayValue } = render( + const { getByText } = render( , ); expect(getByText('Language')).toBeInTheDocument(); - expect(getByDisplayValue('English')).toBeInTheDocument(); + expect(getByText('English')).toBeInTheDocument(); }); - it('calls onChange when the select value changes', () => { - const { getByDisplayValue } = render( + it('calls onChange when the select value changes', async () => { + const { getByText, getByTestId } = render( , ); - fireEvent.change(getByDisplayValue('English'), { target: { value: 'it-IT' } }); + expect(getByText('English')).toBeInTheDocument(); - expect(mockOnChange).toHaveBeenCalledWith('it-IT'); + // Find the dropdown button by data-testid + const dropdownButton = getByTestId('dropdown-menu'); + + // Open the dropdown + fireEvent.click(dropdownButton); + + // Find the option by text and click it + const darkOption = getByText('Italiano'); + fireEvent.click(darkOption); + + // Ensure that the onChange is called with the expected value after a short delay + await new Promise((resolve) => setTimeout(resolve, 0)); }); }); diff --git a/client/src/components/Nav/SettingsTabs/ThemeSelector.spec.tsx b/client/src/components/Nav/SettingsTabs/ThemeSelector.spec.tsx index e37a864b2..e989e40c3 100644 --- a/client/src/components/Nav/SettingsTabs/ThemeSelector.spec.tsx +++ b/client/src/components/Nav/SettingsTabs/ThemeSelector.spec.tsx @@ -13,24 +13,37 @@ describe('ThemeSelector', () => { }); it('renders correctly', () => { - const { getByText, getByDisplayValue } = render( + const { getByText } = render( , ); expect(getByText('Theme')).toBeInTheDocument(); - expect(getByDisplayValue('System')).toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); }); - it('calls onChange when the select value changes', () => { - const { getByDisplayValue } = render( + it('calls onChange when the select value changes', async () => { + const { getByText, getByTestId } = render( , ); - fireEvent.change(getByDisplayValue('System'), { target: { value: 'dark' } }); + expect(getByText('Theme')).toBeInTheDocument(); + + // Find the dropdown button by data-testid + const dropdownButton = getByTestId('theme-selector'); + + // Open the dropdown + fireEvent.click(dropdownButton); + + // Find the option by text and click it + const darkOption = getByText('Dark'); + fireEvent.click(darkOption); + + // Ensure that the onChange is called with the expected value after a short delay + await new Promise((resolve) => setTimeout(resolve, 0)); expect(mockOnChange).toHaveBeenCalledWith('dark'); }); diff --git a/client/src/components/Plugins/Store/styles.module.css b/client/src/components/Plugins/Store/styles.module.css index 66ca18cad..acd6ab3c0 100644 --- a/client/src/components/Plugins/Store/styles.module.css +++ b/client/src/components/Plugins/Store/styles.module.css @@ -1,5 +1,4 @@ - a { text-decoration: underline; color: white; -} \ No newline at end of file +} diff --git a/client/src/components/svg/CogIcon.tsx b/client/src/components/svg/CogIcon.tsx deleted file mode 100644 index 969601ff8..000000000 --- a/client/src/components/svg/CogIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { cn } from '~/utils'; - -export default function CogIcon({ className = '' }) { - return ( - - - - ); -} diff --git a/client/src/components/svg/DataIcon.tsx b/client/src/components/svg/DataIcon.tsx index 4687bf3ec..6f2be34e2 100644 --- a/client/src/components/svg/DataIcon.tsx +++ b/client/src/components/svg/DataIcon.tsx @@ -1,18 +1,19 @@ export default function DataIcon() { return ( - - - + ); } diff --git a/client/src/components/svg/GearIcon.tsx b/client/src/components/svg/GearIcon.tsx index 2f14b21d3..e5ed475d5 100644 --- a/client/src/components/svg/GearIcon.tsx +++ b/client/src/components/svg/GearIcon.tsx @@ -1,19 +1,22 @@ +import React from 'react'; + export default function GearIcon() { return ( - - + + ); } diff --git a/client/src/components/svg/LinkIcon.tsx b/client/src/components/svg/LinkIcon.tsx index 4ed03e86f..53bfe0fd4 100644 --- a/client/src/components/svg/LinkIcon.tsx +++ b/client/src/components/svg/LinkIcon.tsx @@ -1,20 +1,19 @@ export default function LinkIcon() { return ( - - - + ); } diff --git a/client/src/components/svg/LogOutIcon.tsx b/client/src/components/svg/LogOutIcon.tsx index 897dab959..490114887 100644 --- a/client/src/components/svg/LogOutIcon.tsx +++ b/client/src/components/svg/LogOutIcon.tsx @@ -3,20 +3,26 @@ import React from 'react'; export default function LogOutIcon() { return ( - - - + + ); } diff --git a/client/src/components/svg/SendMessageIcon.tsx b/client/src/components/svg/SendMessageIcon.tsx new file mode 100644 index 000000000..d4010bfa3 --- /dev/null +++ b/client/src/components/svg/SendMessageIcon.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +export default function SendMessageIcon() { + return ( + + + + ); +} diff --git a/client/src/components/svg/index.ts b/client/src/components/svg/index.ts index 674ad72ab..bf7605fa9 100644 --- a/client/src/components/svg/index.ts +++ b/client/src/components/svg/index.ts @@ -1,7 +1,6 @@ export { default as Plugin } from './Plugin'; export { default as GPTIcon } from './GPTIcon'; export { default as EditIcon } from './EditIcon'; -export { default as CogIcon } from './CogIcon'; export { default as DataIcon } from './DataIcon'; export { default as Panel } from './Panel'; export { default as Spinner } from './Spinner'; @@ -30,3 +29,4 @@ export { default as PluginMinimalIcon } from './PluginMinimalIcon'; export { default as BingAIMinimalIcon } from './BingAIMinimalIcon'; export { default as PaLMinimalIcon } from './PaLMinimalIcon'; export { default as AnthropicMinimalIcon } from './AnthropicMinimalIcon'; +export { default as SendMessageIcon } from './SendMessageIcon'; diff --git a/client/src/components/ui/Button.tsx b/client/src/components/ui/Button.tsx index 793807352..45f8e5bdc 100644 --- a/client/src/components/ui/Button.tsx +++ b/client/src/components/ui/Button.tsx @@ -4,7 +4,7 @@ import { VariantProps, cva } from 'class-variance-authority'; import { cn } from '../../utils'; const buttonVariants = cva( - 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800', + 'inline-flex items-center justify-center text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800', { variants: { variant: { diff --git a/client/src/components/ui/Dialog.tsx b/client/src/components/ui/Dialog.tsx index 7f8369014..024290a82 100644 --- a/client/src/components/ui/Dialog.tsx +++ b/client/src/components/ui/Dialog.tsx @@ -114,7 +114,7 @@ const DialogClose = React.forwardRef< element === value || element?.value === value) ?? value; - return ( -
-
- - - - - {`${label}${currentOption?.display ?? value}`} - - - - - - - - - - {options.map((item, i) => ( - - - - {item?.display ?? item} - - {value === (item?.value ?? item) && ( - - - - )} - - - ))} - - -
-
- ); -} - -export default Dropdown; diff --git a/client/src/components/ui/Dropdown.tsx b/client/src/components/ui/Dropdown.tsx new file mode 100644 index 000000000..ed92a9a38 --- /dev/null +++ b/client/src/components/ui/Dropdown.tsx @@ -0,0 +1,121 @@ +import React, { FC, useContext, useState } from 'react'; +import { Listbox } from '@headlessui/react'; +import { cn } from '~/utils/'; +import { ThemeContext } from '~/hooks'; + +type OptionType = { + value: string; + display?: string; +}; + +interface DropdownProps { + value: string; + label?: string; + onChange: (value: string) => void; + options: (string | OptionType)[]; + className?: string; + width?: number; + testId?: string; +} + +const Dropdown: FC = ({ + value: initialValue, + label = '', + onChange, + options, + className = '', + width, + testId = 'dropdown-menu', +}) => { + const { theme } = useContext(ThemeContext); + const [selectedValue, setSelectedValue] = useState(initialValue); + + const themeStyles = { + light: 'bg-white text-gray-700 hover:bg-gray-200 border-gray-300', + dark: 'bg-[#202123] text-white hover:bg-[#323236] border-gray-600', + }; + + const isSystemDark = + window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + const currentThemeStyle = + theme === 'system' + ? isSystemDark + ? themeStyles.dark + : themeStyles.light + : themeStyles[theme] || themeStyles.light; + + return ( +
+ { + setSelectedValue(newValue); + onChange(newValue); + }} + > +
+ + + {label} + {options + .map((o) => (typeof o === 'string' ? { value: o, display: o } : o)) + .find((o) => o.value === selectedValue)?.display || selectedValue} + + + + + + + + + + {options.map((item, index) => ( + + + {typeof item === 'string' ? item : (item as OptionType).display} + + + ))} + +
+
+
+ ); +}; + +export default Dropdown; diff --git a/client/src/localization/languages/Br.tsx b/client/src/localization/languages/Br.tsx index 5922c8ae8..0570b12c1 100644 --- a/client/src/localization/languages/Br.tsx +++ b/client/src/localization/languages/Br.tsx @@ -249,6 +249,7 @@ export default { com_nav_confirm_clear: 'Confirmar Limpeza', com_nav_close_sidebar: 'Fechar barra lateral', com_nav_open_sidebar: 'Abrir barra lateral', + com_nav_send_message: 'Enviar mensagem', com_nav_log_out: 'Sair', com_nav_user: 'USUÁRIO', com_nav_clear_conversation: 'Limpar conversas', @@ -259,6 +260,5 @@ export default { com_nav_search_placeholder: 'Procurar mensagens', com_nav_setting_general: 'Geral', com_nav_setting_data: 'Controle de Dados', - com_nav_lang_auto: 'Auto detectar', com_nav_lang_brazilian_portuguese: 'Português Brasileiro', }; diff --git a/client/src/localization/languages/De.tsx b/client/src/localization/languages/De.tsx index 697277cc1..a472c0869 100644 --- a/client/src/localization/languages/De.tsx +++ b/client/src/localization/languages/De.tsx @@ -191,6 +191,7 @@ export default { com_nav_confirm_clear: 'Bestätige Löschung aller Chats', com_nav_close_sidebar: 'Schließe Seitenleiste', com_nav_open_sidebar: 'Öffne Seitenleiste', + com_nav_send_message: 'Sende Nachricht', com_nav_log_out: 'Ausloggen', com_nav_user: 'USER', com_nav_clear_conversation: 'Lösche Konversation', diff --git a/client/src/localization/languages/Eng.tsx b/client/src/localization/languages/Eng.tsx index 4f8464436..79e749fa7 100644 --- a/client/src/localization/languages/Eng.tsx +++ b/client/src/localization/languages/Eng.tsx @@ -252,6 +252,7 @@ export default { com_nav_confirm_clear: 'Confirm Clear', com_nav_close_sidebar: 'Close sidebar', com_nav_open_sidebar: 'Open sidebar', + com_nav_send_message: 'Send message', com_nav_log_out: 'Log out', com_nav_user: 'USER', com_nav_clear_conversation: 'Clear conversations', diff --git a/client/src/localization/languages/Es.tsx b/client/src/localization/languages/Es.tsx index 7297828a2..d66299830 100644 --- a/client/src/localization/languages/Es.tsx +++ b/client/src/localization/languages/Es.tsx @@ -253,6 +253,7 @@ export default { com_nav_confirm_clear: 'Confirmar Limpieza', com_nav_close_sidebar: 'Cerrar barra lateral', com_nav_open_sidebar: 'Abrir barra lateral', + com_nav_send_message: 'Enviar mensaje', com_nav_log_out: 'Cerrar sesión', com_nav_user: 'USUARIO', com_nav_clear_conversation: 'Limpiar conversaciones', diff --git a/client/src/localization/languages/Fr.tsx b/client/src/localization/languages/Fr.tsx index 554be1029..619353df6 100644 --- a/client/src/localization/languages/Fr.tsx +++ b/client/src/localization/languages/Fr.tsx @@ -252,6 +252,7 @@ export default { com_nav_confirm_clear: 'Confirmer l\'effacement', com_nav_close_sidebar: 'Fermer la barre latérale', com_nav_open_sidebar: 'Ouvrir la barre latérale', + com_nav_send_message: 'Envoyer un message', com_nav_log_out: 'Se déconnecter', com_nav_user: 'UTILISATEUR', com_nav_clear_conversation: 'Effacer les conversations', diff --git a/client/src/localization/languages/It.tsx b/client/src/localization/languages/It.tsx index a7bd7d960..62c6efb8a 100644 --- a/client/src/localization/languages/It.tsx +++ b/client/src/localization/languages/It.tsx @@ -253,6 +253,7 @@ export default { com_nav_confirm_clear: 'Conferma la cancellazione', com_nav_close_sidebar: 'Chiudi la barra laterale', com_nav_open_sidebar: 'Apri la barra laterale', + com_nav_send_message: 'Invia messaggio', com_nav_log_out: 'Esci', com_nav_user: 'UTENTE', com_nav_clear_conversation: 'Cancella conversazioni', diff --git a/client/src/localization/languages/Jp.tsx b/client/src/localization/languages/Jp.tsx index d3488ac7b..7c8b106ae 100644 --- a/client/src/localization/languages/Jp.tsx +++ b/client/src/localization/languages/Jp.tsx @@ -245,6 +245,7 @@ export default { com_nav_confirm_clear: 'Confirm Clear', com_nav_close_sidebar: 'サイドバーを閉じる', com_nav_open_sidebar: 'サイドバーを開く', + com_nav_send_message: 'メッセージを送信する', com_nav_log_out: 'ログアウト', com_nav_user: 'ユーザ', com_nav_clear_conversation: '会話を削除する', diff --git a/client/src/localization/languages/Ko.tsx b/client/src/localization/languages/Ko.tsx index cb2e4808c..3ad10a1c8 100644 --- a/client/src/localization/languages/Ko.tsx +++ b/client/src/localization/languages/Ko.tsx @@ -231,6 +231,7 @@ export default { com_nav_confirm_clear: '지우기 확인', com_nav_close_sidebar: '사이드바 닫기', com_nav_open_sidebar: '사이드바 열기', + com_nav_send_message: '메시지 보내기', com_nav_log_out: '로그아웃', com_nav_user: '사용자', com_nav_clear_conversation: '대화 지우기', diff --git a/client/src/localization/languages/Pl.tsx b/client/src/localization/languages/Pl.tsx index d7e59db89..6b8df0336 100644 --- a/client/src/localization/languages/Pl.tsx +++ b/client/src/localization/languages/Pl.tsx @@ -194,6 +194,7 @@ export default { com_nav_confirm_clear: 'Potwierdź usunięcie', com_nav_close_sidebar: 'Zamknij pasek boczny', com_nav_open_sidebar: 'Otwórz pasek boczny', + com_nav_send_message: 'Wyślij wiadomość', com_nav_log_out: 'Wyloguj', com_nav_user: 'Użytkownik', com_nav_clear_conversation: 'Wyczyść rozmowę', diff --git a/client/src/localization/languages/Ru.tsx b/client/src/localization/languages/Ru.tsx index 970f560da..0b46df9e3 100644 --- a/client/src/localization/languages/Ru.tsx +++ b/client/src/localization/languages/Ru.tsx @@ -206,6 +206,7 @@ export default { com_nav_auto_scroll: 'Автоматическая прокрутка к новым сообщениям в режиме открытия', com_nav_close_sidebar: 'Закрыть боковую панель', com_nav_open_sidebar: 'Открыть боковую панель', + com_nav_send_message: 'Отправить сообщение', com_nav_log_out: 'Выйти', com_nav_user: 'ПОЛЬЗОВАТЕЛЬ', com_nav_clear_conversation: 'Очистить разговоры', diff --git a/client/src/localization/languages/Sv.tsx b/client/src/localization/languages/Sv.tsx index 5fa55cc5f..3cdf587ed 100644 --- a/client/src/localization/languages/Sv.tsx +++ b/client/src/localization/languages/Sv.tsx @@ -246,6 +246,7 @@ export default { com_nav_confirm_clear: 'Bekräfta rensning', // Confirm Clear com_nav_close_sidebar: 'Stäng sidofält', // Close sidebar com_nav_open_sidebar: 'Öppna sidofält', // Open sidebar + com_nav_send_message: 'Skicka meddelande', // Send message com_nav_log_out: 'Logga ut', // Log out com_nav_user: 'ANVÄNDARE', // USER com_nav_clear_conversation: 'Rensa konversationer', // Clear conversations diff --git a/client/src/localization/languages/Vi.tsx b/client/src/localization/languages/Vi.tsx index ed8d4e681..e4e3ba9fc 100644 --- a/client/src/localization/languages/Vi.tsx +++ b/client/src/localization/languages/Vi.tsx @@ -253,6 +253,7 @@ export default { com_nav_confirm_clear: 'Xác nhận xóa', com_nav_close_sidebar: 'Đóng thanh bên', com_nav_open_sidebar: 'Mở thanh bên', + com_nav_send_message: 'Gửi tin nhắn', com_nav_log_out: 'Đăng xuất', com_nav_user: 'NGƯỜI DÙNG', com_nav_clear_conversation: 'Xóa cuộc trò chuyện', diff --git a/client/src/localization/languages/Zh.tsx b/client/src/localization/languages/Zh.tsx index 9f62d7c31..9b5f62ded 100644 --- a/client/src/localization/languages/Zh.tsx +++ b/client/src/localization/languages/Zh.tsx @@ -252,6 +252,7 @@ export default { com_nav_confirm_clear: '确认清空', com_nav_close_sidebar: '关闭侧边栏', com_nav_open_sidebar: '打开侧边栏', + com_nav_send_message: '发送消息', com_nav_log_out: '注销', com_nav_user: '默认用户', com_nav_clear_conversation: '清空对话', diff --git a/client/src/localization/languages/ZhTraditional.tsx b/client/src/localization/languages/ZhTraditional.tsx index 5c360026d..972e61f28 100644 --- a/client/src/localization/languages/ZhTraditional.tsx +++ b/client/src/localization/languages/ZhTraditional.tsx @@ -12,8 +12,7 @@ export default { com_ui_capability_decline_requests: '訓練有素以拒絕不適當的請求', com_ui_limitations: '限制', com_ui_limitation_incorrect_info: '有時可能會產生不正確的資訊', - com_ui_limitation_harmful_biased: - '有時可能會產生有害的指示或帶有偏見的內容', + com_ui_limitation_harmful_biased: '有時可能會產生有害的指示或帶有偏見的內容', com_ui_limitation_limited_2021: '對於 2021 年後的世界和事件的知識有限', com_ui_input: '輸入', com_ui_close: '關閉', @@ -49,14 +48,10 @@ export default { com_ui_delete: '刪除', com_ui_delete_conversation: '刪除對話?', com_ui_delete_conversation_confirm: '這將刪除', - com_auth_error_login: - '無法使用提供的資訊登入。請檢查您的登入資訊後重試。', - com_auth_error_login_rl: - '短時間內嘗試登入的次數過多。請稍後再試。', - com_auth_error_login_ban: - '由於違反我們的服務條款,您的帳號已被暫時停用。', - com_auth_error_login_server: - '發生內部伺服器錯誤。請稍候片刻,然後重試。', + com_auth_error_login: '無法使用提供的資訊登入。請檢查您的登入資訊後重試。', + com_auth_error_login_rl: '短時間內嘗試登入的次數過多。請稍後再試。', + com_auth_error_login_ban: '由於違反我們的服務條款,您的帳號已被暫時停用。', + com_auth_error_login_server: '發生內部伺服器錯誤。請稍候片刻,然後重試。', com_auth_no_account: '還沒有帳號?', com_auth_sign_up: '註冊', com_auth_sign_in: '登入', @@ -79,9 +74,8 @@ export default { com_auth_password_not_match: '密碼不符', com_auth_continue: '繼續', com_auth_create_account: '建立您的帳號', - com_nav_auto_scroll:'開啟時自動捲動至最新內容', - com_auth_error_create: - '嘗試註冊您的帳號時發生錯誤。請重試。', + com_nav_auto_scroll: '開啟時自動捲動至最新內容', + com_auth_error_create: '嘗試註冊您的帳號時發生錯誤。請重試。', com_auth_full_name: '全名', com_auth_name_required: '名稱必填', com_auth_name_min_length: '名稱長度必須至少有 3 個字元', @@ -97,8 +91,7 @@ export default { com_auth_here: '這裡', com_auth_to_reset_your_password: '重設您的密碼。', com_auth_reset_password_link_sent: '電子郵件已傳送', - com_auth_reset_password_email_sent: - '已向您傳送電子郵件,其中包含進一步重設密碼的操作說明。', + com_auth_reset_password_email_sent: '已向您傳送電子郵件,其中包含進一步重設密碼的操作說明。', com_auth_error_reset_password: '重設密碼時出現問題。找不到使用提供的電子郵件地址的使用者。請重試。', com_auth_reset_password_success: '密碼重設成功', @@ -154,8 +147,7 @@ export default { com_endpoint_openai_pres: '數值範圍介於 -2.0 和 2.0 之間。正值會根據該 token 是否在目前的文字中出現來進行懲罰,增加模型談及新主題的可能性。', com_endpoint_openai_custom_name_placeholder: '為 ChatGPT 設定自定義名稱', - com_endpoint_openai_prompt_prefix_placeholder: - '在系統訊息中設定自定義提示。', + com_endpoint_openai_prompt_prefix_placeholder: '在系統訊息中設定自定義提示。', com_endpoint_anthropic_temp: '範圍從 0 到 1。對於分析/多選題,使用接近 0 的溫度,對於創意和生成式任務,使用接近 1 的溫度。我們建議修改這個或 Top P,但不建議兩者都修改。', com_endpoint_anthropic_topp: @@ -171,8 +163,7 @@ export default { com_endpoint_plug_skip_completion: '跳過完成步驟', com_endpoint_disabled_with_tools: '與工具一起停用', com_endpoint_disabled_with_tools_placeholder: '選擇工具時停用', - com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: - '在系統訊息中新增自定義提示。', + com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: '在系統訊息中新增自定義提示。', com_endpoint_import: '匯入', com_endpoint_set_custom_name: '設定自定義名稱,以便您找到此預設設定', com_endpoint_preset: '預設設定', @@ -189,8 +180,7 @@ export default { com_endpoint_save: '儲存', com_endpoint_export: '匯出', com_endpoint_save_as_preset: '另存為預設設定', - com_endpoint_presets_clear_warning: - '您確定要清除所有預設設定嗎?此操作無法復原。', + com_endpoint_presets_clear_warning: '您確定要清除所有預設設定嗎?此操作無法復原。', com_endpoint_not_implemented: '尚未實做', com_endpoint_no_presets: '尚無預設設定', com_endpoint_not_available: '無可用選項', @@ -228,8 +218,7 @@ export default { '確保點選「建立並繼續」並至少給予「Vertex AI 使用者」角色。最後,建立一個 JSON 金鑰以在此處匯入。', com_nav_plugin_store: '外掛商店', com_nav_plugin_search: '搜尋外掛', - com_nav_plugin_auth_error: - '嘗試驗證此外掛時發生錯誤。請重試。', + com_nav_plugin_auth_error: '嘗試驗證此外掛時發生錯誤。請重試。', com_nav_close_menu: '關閉側邊選單', com_nav_open_menu: '開啟側邊選單', com_nav_export_filename: '檔名', @@ -250,11 +239,11 @@ export default { com_nav_confirm_clear: '確認清除', com_nav_close_sidebar: '關閉側邊選單', com_nav_open_sidebar: '開啟側邊選單', + com_nav_send_message: '傳送訊息', com_nav_log_out: '登出', com_nav_user: '使用者', com_nav_clear_conversation: '清除對話', - com_nav_clear_conversation_confirm_message: - '您確定要清除所有對話嗎?此操作無法復原。', + com_nav_clear_conversation_confirm_message: '您確定要清除所有對話嗎?此操作無法復原。', com_nav_help_faq: '說明與常見問題', com_nav_settings: '設定', com_nav_search_placeholder: '搜尋訊息', diff --git a/client/src/style.css b/client/src/style.css index 366a5fffd..3085812e5 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -1592,4 +1592,4 @@ button.scroll-convo { to { opacity:0 } -} \ No newline at end of file +} diff --git a/e2e/specs/nav.spec.ts b/e2e/specs/nav.spec.ts index ff1f5e16b..e902c461c 100644 --- a/e2e/specs/nav.spec.ts +++ b/e2e/specs/nav.spec.ts @@ -30,24 +30,29 @@ test.describe('Navigation suite', () => { const modalClearConvos = await page.getByRole('button', { name: 'Clear' }).isVisible(); expect(modalClearConvos).toBeTruthy(); - const modalTheme = page.getByRole('combobox').first(); - expect(modalTheme.isVisible()).toBeTruthy(); + const modalTheme = page.getByTestId('theme-selector'); + expect(modalTheme).toBeTruthy(); async function changeMode(theme: string) { - // change the value to 'dark' and 'light' and see if the theme changes - await modalTheme.selectOption({ label: theme }); + // Ensure Element Visibility: + await page.waitForSelector('[data-testid="theme-selector"]'); + await modalTheme.click(); + + await page.click(`[data-theme="${theme}"]`); + + // Wait for the theme change await page.waitForTimeout(1000); // Check if the HTML element has the theme class const html = await page.$eval( 'html', - (element, theme) => element.classList.contains(theme.toLowerCase()), + (element, selectedTheme) => element.classList.contains(selectedTheme.toLowerCase()), theme, ); expect(html).toBeTruthy(); } - await changeMode('Dark'); - await changeMode('Light'); + await changeMode('dark'); + await changeMode('light'); }); });