diff --git a/.gitignore b/.gitignore index ca823b202..035aa5f46 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,8 @@ src/style - official.css /playwright/.cache/ .DS_Store *.code-workspace +.idx +monospace.json .idea *.iml *.pem diff --git a/client/public/assets/logo.svg b/client/public/assets/logo.svg new file mode 100644 index 000000000..36a536d65 --- /dev/null +++ b/client/public/assets/logo.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/components/Auth/Login.tsx b/client/src/components/Auth/Login.tsx index c811ac0a1..8bff10c25 100644 --- a/client/src/components/Auth/Login.tsx +++ b/client/src/components/Auth/Login.tsx @@ -96,7 +96,7 @@ function Login() { const privacyPolicyRender = privacyPolicy?.externalUrl && ( -
+
+
+ Logo +
+
-
-

- {localize('com_auth_welcome_back')} -

- {error && ( -
+
+

- {localize(getLoginError(error))} -

- )} - {startupConfig.emailLoginEnabled && } - {startupConfig.registrationEnabled && ( -

- {' '} - {localize('com_auth_no_account')}{' '} - - {localize('com_auth_sign_up')} - -

- )} - {startupConfig.socialLoginEnabled && ( - <> - {startupConfig.emailLoginEnabled && ( - <> -
-
- Or -
-
-
- - )} -
- {socialLogins.map((provider) => providerComponents[provider] || null)} + {localize('com_auth_welcome_back')} + + {error && ( +
+ {localize(getLoginError(error))}
- - )} + )} + {startupConfig.emailLoginEnabled && } + {startupConfig.registrationEnabled && ( +

+ {' '} + {localize('com_auth_no_account')}{' '} + + {localize('com_auth_sign_up')} + +

+ )} + {startupConfig.socialLoginEnabled && ( + <> + {startupConfig.emailLoginEnabled && ( + <> +
+
+ Or +
+
+
+ + )} +
+ {socialLogins.map((provider) => providerComponents[provider] || null)} +
+ + )} +
-
+
{privacyPolicyRender} {privacyPolicyRender && termsOfServiceRender && ( -
+
)} {termsOfServiceRender}
diff --git a/client/src/components/Auth/LoginForm.tsx b/client/src/components/Auth/LoginForm.tsx index 102c48265..9cff6c736 100644 --- a/client/src/components/Auth/LoginForm.tsx +++ b/client/src/components/Auth/LoginForm.tsx @@ -18,7 +18,7 @@ const LoginForm: React.FC = ({ onSubmit }) => { const renderError = (fieldName: string) => { const errorMessage = errors[fieldName]?.message; return errorMessage ? ( - + {String(errorMessage)} ) : null; @@ -44,12 +44,12 @@ const LoginForm: React.FC = ({ onSubmit }) => { pattern: { value: /\S+@\S+\.\S+/, message: localize('com_auth_email_pattern') }, })} aria-invalid={!!errors.email} - className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500" + className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500" placeholder=" " /> @@ -69,19 +69,19 @@ const LoginForm: React.FC = ({ onSubmit }) => { maxLength: { value: 128, message: localize('com_auth_password_max_length') }, })} aria-invalid={!!errors.password} - className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500" + className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500" placeholder=" " />
{renderError('password')}
- + {localize('com_auth_password_forgot')}
diff --git a/client/src/components/Auth/Registration.tsx b/client/src/components/Auth/Registration.tsx index 02e462a0f..15d2f6489 100644 --- a/client/src/components/Auth/Registration.tsx +++ b/client/src/components/Auth/Registration.tsx @@ -64,19 +64,19 @@ const Registration: React.FC = () => { validation, )} aria-invalid={!!errors[id]} - className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500" + className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500" placeholder=" " data-testid={id} >
{errors[id] && ( - + {String(errors[id]?.message) ?? ''} )} @@ -147,117 +147,154 @@ const Registration: React.FC = () => { ), }; + const privacyPolicy = startupConfig.interface?.privacyPolicy; + const termsOfService = startupConfig.interface?.termsOfService; + + const privacyPolicyRender = privacyPolicy?.externalUrl && ( + + {localize('com_ui_privacy_policy')} + + ); + + const termsOfServiceRender = termsOfService?.externalUrl && ( + + {localize('com_ui_terms_of_service')} + + ); + return ( -
-
+
+
+ Logo +
+
-
-

- {localize('com_auth_create_account')} -

- {error && ( -
+
+

- {localize('com_auth_error_create')} {errorMessage} -

- )} -
- {renderInput('name', 'com_auth_full_name', 'text', { - required: localize('com_auth_name_required'), - minLength: { - value: 3, - message: localize('com_auth_name_min_length'), - }, - maxLength: { - value: 80, - message: localize('com_auth_name_max_length'), - }, - })} - {renderInput('username', 'com_auth_username', 'text', { - minLength: { - value: 2, - message: localize('com_auth_username_min_length'), - }, - maxLength: { - value: 80, - message: localize('com_auth_username_max_length'), - }, - })} - {renderInput('email', 'com_auth_email', 'email', { - required: localize('com_auth_email_required'), - minLength: { - value: 1, - message: localize('com_auth_email_min_length'), - }, - maxLength: { - value: 120, - message: localize('com_auth_email_max_length'), - }, - pattern: { - value: /\S+@\S+\.\S+/, - message: localize('com_auth_email_pattern'), - }, - })} - {renderInput('password', 'com_auth_password', 'password', { - required: localize('com_auth_password_required'), - minLength: { - value: 8, - message: localize('com_auth_password_min_length'), - }, - maxLength: { - value: 128, - message: localize('com_auth_password_max_length'), - }, - })} - {renderInput('confirm_password', 'com_auth_password_confirm', 'password', { - validate: (value) => value === password || localize('com_auth_password_not_match'), - })} -
- -
-
-

- {localize('com_auth_already_have_account')}{' '} - - {localize('com_auth_login')} - -

- {startupConfig.socialLoginEnabled && ( - <> - {startupConfig.emailLoginEnabled && ( - <> -
-
- Or -
-
-
- - )} -
- {socialLogins.map((provider) => providerComponents[provider] || null)} + {localize('com_auth_error_create')} {errorMessage}
- + )} +
+ {renderInput('name', 'com_auth_full_name', 'text', { + required: localize('com_auth_name_required'), + minLength: { + value: 3, + message: localize('com_auth_name_min_length'), + }, + maxLength: { + value: 80, + message: localize('com_auth_name_max_length'), + }, + })} + {renderInput('username', 'com_auth_username', 'text', { + minLength: { + value: 2, + message: localize('com_auth_username_min_length'), + }, + maxLength: { + value: 80, + message: localize('com_auth_username_max_length'), + }, + })} + {renderInput('email', 'com_auth_email', 'email', { + required: localize('com_auth_email_required'), + minLength: { + value: 1, + message: localize('com_auth_email_min_length'), + }, + maxLength: { + value: 120, + message: localize('com_auth_email_max_length'), + }, + pattern: { + value: /\S+@\S+\.\S+/, + message: localize('com_auth_email_pattern'), + }, + })} + {renderInput('password', 'com_auth_password', 'password', { + required: localize('com_auth_password_required'), + minLength: { + value: 8, + message: localize('com_auth_password_min_length'), + }, + maxLength: { + value: 128, + message: localize('com_auth_password_max_length'), + }, + })} + {renderInput('confirm_password', 'com_auth_password_confirm', 'password', { + validate: (value) => value === password || localize('com_auth_password_not_match'), + })} +
+ +
+
+

+ {localize('com_auth_already_have_account')}{' '} + + {localize('com_auth_login')} + +

+ {startupConfig.socialLoginEnabled && ( + <> + {startupConfig.emailLoginEnabled && ( + <> +
+
+ Or +
+
+
+ + )} +
+ {socialLogins.map((provider) => providerComponents[provider] || null)} +
+ + )} +
+
+
+ {privacyPolicyRender} + {privacyPolicyRender && termsOfServiceRender && ( +
)} + {termsOfServiceRender}
); diff --git a/client/src/components/Auth/RequestPasswordReset.tsx b/client/src/components/Auth/RequestPasswordReset.tsx index ded90c7db..185b6789c 100644 --- a/client/src/components/Auth/RequestPasswordReset.tsx +++ b/client/src/components/Auth/RequestPasswordReset.tsx @@ -49,7 +49,7 @@ function RequestPasswordReset() { setBodyText( {localize('com_auth_click')}{' '} - + {localize('com_auth_here')} {' '} {localize('com_auth_to_reset_your_password')} @@ -66,7 +66,7 @@ function RequestPasswordReset() { if (bodyText) { return (
{bodyText} @@ -103,18 +103,18 @@ function RequestPasswordReset() { }, })} aria-invalid={!!errors.email} - className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500" + className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500" placeholder=" " >
{errors.email && ( - + {/* @ts-ignore not sure why */} {errors.email.message} @@ -129,7 +129,7 @@ function RequestPasswordReset() { {localize('com_auth_continue')} @@ -139,24 +139,61 @@ function RequestPasswordReset() { } }; + const privacyPolicy = config.data?.interface?.privacyPolicy; + const termsOfService = config.data?.interface?.termsOfService; + + const privacyPolicyRender = privacyPolicy?.externalUrl && ( + + {localize('com_ui_privacy_policy')} + + ); + + const termsOfServiceRender = termsOfService?.externalUrl && ( + + {localize('com_ui_terms_of_service')} + + ); + return ( -
-
+
+
+ Logo +
+
-
-

- {headerText} -

- {requestError && ( -
- {localize('com_auth_error_reset_password')} -
+
+
+

+ {headerText} +

+ {requestError && ( +
+ {localize('com_auth_error_reset_password')} +
+ )} + {renderFormContent()} +
+
+
+ {privacyPolicyRender} + {privacyPolicyRender && termsOfServiceRender && ( +
)} - {renderFormContent()} + {termsOfServiceRender}
); diff --git a/client/src/components/Auth/ResetPassword.tsx b/client/src/components/Auth/ResetPassword.tsx index 52560828f..75390286c 100644 --- a/client/src/components/Auth/ResetPassword.tsx +++ b/client/src/components/Auth/ResetPassword.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { useResetPasswordMutation } from 'librechat-data-provider/react-query'; +import { useGetStartupConfig, useResetPasswordMutation } from 'librechat-data-provider/react-query'; import type { TResetPassword } from 'librechat-data-provider'; import { ThemeSelector } from '~/components/ui'; import { useLocalize } from '~/hooks'; @@ -15,6 +15,7 @@ function ResetPassword() { formState: { errors }, } = useForm(); const resetPassword = useResetPasswordMutation(); + const config = useGetStartupConfig(); const [resetError, setResetError] = useState(false); const [params] = useSearchParams(); const navigate = useNavigate(); @@ -28,6 +29,31 @@ function ResetPassword() { }); }; + const privacyPolicy = config.data?.interface?.privacyPolicy; + const termsOfService = config.data?.interface?.termsOfService; + + const privacyPolicyRender = privacyPolicy?.externalUrl && ( + + {localize('com_ui_privacy_policy')} + + ); + + const termsOfServiceRender = termsOfService?.externalUrl && ( + + {localize('com_ui_terms_of_service')} + + ); + if (resetPassword.isSuccess) { return (
@@ -56,134 +82,146 @@ function ResetPassword() { ); } else { return ( -
-
+
+
+ Logo +
+
-
-

- {localize('com_auth_reset_password')} -

- {resetError && ( -
- {localize('com_auth_error_invalid_reset_token')}{' '} - - {localize('com_auth_click_here')} - {' '} - {localize('com_auth_to_try_again')} -
- )} -
-
-
- - - - -
- - {errors.password && ( - - {/* @ts-ignore not sure why */} - {errors.password.message} - - )} -
-
-
- - value === password || localize('com_auth_password_not_match'), - })} - aria-invalid={!!errors.confirm_password} - className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500" - placeholder=" " - > - -
- {errors.confirm_password && ( - - {/* @ts-ignore not sure why */} - {errors.confirm_password.message} - - )} - {errors.token && ( - - {/* @ts-ignore not sure why */} - {errors.token.message} - - )} - {errors.userId && ( - - {/* @ts-ignore not sure why */} - {errors.userId.message} - - )} -
-
- -
-
+ {localize('com_auth_error_invalid_reset_token')}{' '} + + {localize('com_auth_click_here')} + {' '} + {localize('com_auth_to_try_again')} +
+ )} +
+
+
+ + + + +
+ + {errors.password && ( + + {/* @ts-ignore not sure why */} + {errors.password.message} + + )} +
+
+
+ + value === password || localize('com_auth_password_not_match'), + })} + aria-invalid={!!errors.confirm_password} + className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500" + placeholder=" " + > + +
+ {errors.confirm_password && ( + + {/* @ts-ignore not sure why */} + {errors.confirm_password.message} + + )} + {errors.token && ( + + {/* @ts-ignore not sure why */} + {errors.token.message} + + )} + {errors.userId && ( + + {/* @ts-ignore not sure why */} + {errors.userId.message} + + )} +
+
+ +
+
+
+
+
+ {privacyPolicyRender} + {privacyPolicyRender && termsOfServiceRender && ( +
+ )} + {termsOfServiceRender}
); diff --git a/client/src/components/Auth/SocialButton.tsx b/client/src/components/Auth/SocialButton.tsx index 7e76c6f76..8b8318ac3 100644 --- a/client/src/components/Auth/SocialButton.tsx +++ b/client/src/components/Auth/SocialButton.tsx @@ -15,6 +15,7 @@ const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) => const handleMouseLeave = () => { setIsHovered(false); + if (isPressed) {setIsPressed(false);} }; const handleMouseDown = () => { @@ -28,7 +29,7 @@ const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) => const getButtonStyles = () => { // Define Tailwind CSS classes based on state - const baseStyles = 'border border-solid border-gray-300 dark:border-gray-800 transition-colors'; + const baseStyles = 'border border-solid border-gray-300 dark:border-gray-600 transition-colors'; const pressedStyles = 'bg-blue-200 border-blue-200 dark:bg-blue-900 dark:border-blue-600'; const hoverStyles = 'bg-gray-100 dark:bg-gray-700'; diff --git a/client/src/components/Chat/ExportButton.tsx b/client/src/components/Chat/ExportButton.tsx new file mode 100644 index 000000000..48acd20ec --- /dev/null +++ b/client/src/components/Chat/ExportButton.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import { useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import type { TConversation } from 'librechat-data-provider'; +import { Download } from 'lucide-react'; +import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui'; +import { useLocalize } from '~/hooks'; +import { ExportModal } from '../Nav'; +import { useRecoilValue } from 'recoil'; +import store from '~/store'; + +function ExportButton() { + const localize = useLocalize(); + const location = useLocation(); + + const [showExports, setShowExports] = useState(false); + + const activeConvo = useRecoilValue(store.conversationByIndex(0)); + const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation); + + let conversation: TConversation | null | undefined; + if (location.state?.from?.pathname.includes('/chat')) { + conversation = globalConvo; + } else { + conversation = activeConvo; + } + + const clickHandler = () => { + if (exportable) { + setShowExports(true); + } + }; + + const exportable = + conversation && + conversation.conversationId && + conversation.conversationId !== 'new' && + conversation.conversationId !== 'search'; + + return ( + <> + {exportable && ( +
+ + + + + + + {localize('com_nav_export_conversation')} + + + +
+ )} + {showExports && ( + + )} + + ); +} + +export default ExportButton; diff --git a/client/src/components/Chat/Header.tsx b/client/src/components/Chat/Header.tsx index cc76bbbc6..ab40ace0b 100644 --- a/client/src/components/Chat/Header.tsx +++ b/client/src/components/Chat/Header.tsx @@ -5,6 +5,7 @@ import { useGetStartupConfig } from 'librechat-data-provider/react-query'; import type { ContextType } from '~/common'; import { EndpointsMenu, ModelSpecsMenu, PresetsMenu, HeaderNewChat } from './Menus'; import HeaderOptions from './Input/HeaderOptions'; +import ExportButton from './ExportButton'; const defaultInterface = getConfigDefaults().interface; @@ -19,12 +20,15 @@ export default function Header() { return (
-
- {!navVisible && } - {interfaceConfig.endpointsMenu && } - {modelSpecs?.length > 0 && } - {} - {interfaceConfig.presets && } +
+
+ {!navVisible && } + {interfaceConfig.endpointsMenu && } + {modelSpecs?.length > 0 && } + {} + {interfaceConfig.presets && } +
+
{/* Empty div for spacing */}
diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 16343cd0e..f05fd7279 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -100,7 +100,7 @@ const ChatForm = ({ index = 0 }) => { {showMentionPopover && ( )} -
+
- + {localize('com_nav_my_files')} diff --git a/client/src/components/Chat/Input/Files/Table/DataTable.tsx b/client/src/components/Chat/Input/Files/Table/DataTable.tsx index 23e7b8ea7..75bbb3823 100644 --- a/client/src/components/Chat/Input/Files/Table/DataTable.tsx +++ b/client/src/components/Chat/Input/Files/Table/DataTable.tsx @@ -220,7 +220,7 @@ export default function DataTable({ columns, data }: DataTablePro )}
+ = ({ children }: EditMenuButtonPro align="start" className={cn( 'popover radix-side-bottom:animate-slideUpAndFade radix-side-left:animate-slideRightAndFade radix-side-right:animate-slideLeftAndFade radix-side-top:animate-slideDownAndFade overflow-hidden rounded-lg shadow-lg', - 'border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-700 dark:text-white', + 'border border-gray-200 bg-white dark:border-gray-600 dark:bg-gray-700 dark:text-white', 'flex min-w-[200px] max-w-xs flex-wrap', )} > diff --git a/client/src/components/Conversations/HoverToggle.tsx b/client/src/components/Conversations/HoverToggle.tsx index 404b5caef..fab8bb2a2 100644 --- a/client/src/components/Conversations/HoverToggle.tsx +++ b/client/src/components/Conversations/HoverToggle.tsx @@ -5,11 +5,14 @@ import { cn } from '~/utils'; const HoverToggle = ({ children, isActiveConvo, + isPopoverActive, + setIsPopoverActive, }: { children: React.ReactNode; isActiveConvo: boolean; + isPopoverActive: boolean; + setIsPopoverActive: (isActive: boolean) => void; }) => { - const [isPopoverActive, setIsPopoverActive] = useState(false); const setPopoverActive = (value: boolean) => setIsPopoverActive(value); return ( diff --git a/client/src/components/Conversations/RenameButton.tsx b/client/src/components/Conversations/RenameButton.tsx index 5db040ab0..cd00273b1 100644 --- a/client/src/components/Conversations/RenameButton.tsx +++ b/client/src/components/Conversations/RenameButton.tsx @@ -1,6 +1,7 @@ import type { MouseEvent, ReactElement } from 'react'; import { EditIcon, CheckMark } from '~/components/svg'; import { useLocalize } from '~/hooks'; +import { cn } from '~/utils'; interface RenameButtonProps { renaming: boolean; @@ -21,7 +22,13 @@ export default function RenameButton({ const handler = renaming ? onRename : renameHandler; return ( -
{user?.name || user?.username || localize('com_nav_user')} @@ -109,24 +88,23 @@ function NavLinks() { leaveFrom="translate-y-0 opacity-100" leaveTo="translate-y-2 opacity-0" > - + +
+ {user?.email || localize('com_nav_user')} +
+
+ {startupConfig?.checkBalance && + balanceQuery.data && + !isNaN(parseFloat(balanceQuery.data)) && ( + <> +
+ {`Balance: ${parseFloat(balanceQuery.data).toFixed(2)}`} +
+
+ + )} } - text={localize('com_nav_export_conversation')} - clickHandler={clickHandler} - /> - -
- - } text={localize('com_nav_my_files')} clickHandler={() => setShowFiles(true)} @@ -135,7 +113,6 @@ function NavLinks() { {startupConfig?.helpAndFaqURL !== '/' && ( } text={localize('com_nav_help_faq')} clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')} @@ -144,13 +121,12 @@ function NavLinks() { )} } text={localize('com_nav_settings')} clickHandler={() => setShowSettings(true)} /> -
+
@@ -159,9 +135,6 @@ function NavLinks() { )} - {showExports && ( - - )} {showFiles && } {showSettings && } diff --git a/client/src/components/Nav/Settings.tsx b/client/src/components/Nav/Settings.tsx index f83810667..45d4e0e2b 100644 --- a/client/src/components/Nav/Settings.tsx +++ b/client/src/components/Nav/Settings.tsx @@ -16,8 +16,8 @@ export default function Settings({ open, onOpenChange }: TDialogProps) { @@ -25,19 +25,19 @@ export default function Settings({ open, onOpenChange }: TDialogProps) { {localize('com_nav_settings')} -
+
@@ -112,11 +112,13 @@ export default function Settings({ open, onOpenChange }: TDialogProps) { {localize('com_nav_setting_account')} - - - - - +
+ + + + + +
diff --git a/client/src/components/Nav/SettingsTabs/Account/Account.tsx b/client/src/components/Nav/SettingsTabs/Account/Account.tsx index c4d7c86fa..73bf10f7f 100644 --- a/client/src/components/Nav/SettingsTabs/Account/Account.tsx +++ b/client/src/components/Nav/SettingsTabs/Account/Account.tsx @@ -22,10 +22,10 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
-
+
@@ -39,7 +39,7 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo />
-
+
); } diff --git a/client/src/components/Nav/SettingsTabs/Beta/Beta.tsx b/client/src/components/Nav/SettingsTabs/Beta/Beta.tsx index 699def557..869e8dcec 100644 --- a/client/src/components/Nav/SettingsTabs/Beta/Beta.tsx +++ b/client/src/components/Nav/SettingsTabs/Beta/Beta.tsx @@ -9,13 +9,13 @@ function Beta() {
-
+
-
+
diff --git a/client/src/components/Nav/SettingsTabs/Data/Data.tsx b/client/src/components/Nav/SettingsTabs/Data/Data.tsx index 69e4e4887..65704d8c1 100644 --- a/client/src/components/Nav/SettingsTabs/Data/Data.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/Data.tsx @@ -100,18 +100,18 @@ function Data() {
-
+
-
+
-
+
@@ -103,7 +105,13 @@ export const LangSelector = ({ return (
{localize('com_nav_language')}
- +
); }; @@ -142,26 +150,26 @@ function General() {
-
+
-
+
-
+
-
+
-
+
- {/*
+ {/*
*/}
diff --git a/client/src/components/Nav/SettingsTabs/Messages/ForkSettings.tsx b/client/src/components/Nav/SettingsTabs/Messages/ForkSettings.tsx index 7776781f8..eb19532e4 100644 --- a/client/src/components/Nav/SettingsTabs/Messages/ForkSettings.tsx +++ b/client/src/components/Nav/SettingsTabs/Messages/ForkSettings.tsx @@ -18,7 +18,7 @@ export const ForkSettings = () => { return ( <> -
+
{localize('com_ui_fork_change_default')}
{ onChange={setForkSetting} options={forkOptions} width={200} + position={'left'} + maxHeight="199px" testId="fork-setting-dropdown" />
-
+
{localize('com_ui_fork_default')}
{ />
-
+
{localize('com_ui_fork_split_target_setting')}
+
-
+
-
+
diff --git a/client/src/components/SidePanel/Files/PanelFileCell.tsx b/client/src/components/SidePanel/Files/PanelFileCell.tsx index 7753452b0..4d5d02979 100644 --- a/client/src/components/SidePanel/Files/PanelFileCell.tsx +++ b/client/src/components/SidePanel/Files/PanelFileCell.tsx @@ -92,7 +92,7 @@ export default function PanelFileCell({ row }: { row: Row }) { return (
{fileType && } {file.filename} diff --git a/client/src/components/svg/Clipboard.tsx b/client/src/components/svg/Clipboard.tsx index e18a9f665..3ad1f01d2 100644 --- a/client/src/components/svg/Clipboard.tsx +++ b/client/src/components/svg/Clipboard.tsx @@ -3,21 +3,18 @@ import React from 'react'; export default function Clipboard() { return ( ); diff --git a/client/src/components/svg/EditIcon.tsx b/client/src/components/svg/EditIcon.tsx index 3884875b4..e73e584bd 100644 --- a/client/src/components/svg/EditIcon.tsx +++ b/client/src/components/svg/EditIcon.tsx @@ -6,7 +6,7 @@ export default function EditIcon() { viewBox="0 0 24 24" strokeLinecap="round" strokeLinejoin="round" - className="h-4 w-4" + className="icon-md" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" diff --git a/client/src/components/svg/RegenerateIcon.tsx b/client/src/components/svg/RegenerateIcon.tsx index f4e5c8ce1..d048a6e29 100644 --- a/client/src/components/svg/RegenerateIcon.tsx +++ b/client/src/components/svg/RegenerateIcon.tsx @@ -3,21 +3,16 @@ import { cn } from '~/utils'; export default function RegenerateIcon({ className = '' }: { className?: string }) { return ( ); diff --git a/client/src/components/svg/TrashIcon.tsx b/client/src/components/svg/TrashIcon.tsx index 56c851698..1eb2fd4a1 100644 --- a/client/src/components/svg/TrashIcon.tsx +++ b/client/src/components/svg/TrashIcon.tsx @@ -6,7 +6,7 @@ export default function TrashIcon() { viewBox="0 0 24 24" strokeLinecap="round" strokeLinejoin="round" - className="h-4 w-4" + className="icon-md" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" diff --git a/client/src/components/ui/Dropdown.tsx b/client/src/components/ui/Dropdown.tsx index b2176fc24..b23731b14 100644 --- a/client/src/components/ui/Dropdown.tsx +++ b/client/src/components/ui/Dropdown.tsx @@ -7,13 +7,17 @@ type OptionType = { display?: string; }; +type DropdownPosition = 'left' | 'right'; + interface DropdownProps { value: string; label?: string; onChange: (value: string) => void; options: (string | OptionType)[]; className?: string; + position?: DropdownPosition; width?: number; + maxHeight?: string; testId?: string; } @@ -23,11 +27,18 @@ const Dropdown: FC = ({ onChange, options, className = '', + position = 'right', width, + maxHeight = 'auto', testId = 'dropdown-menu', }) => { const [selectedValue, setSelectedValue] = useState(initialValue); + const positionClasses = { + right: 'origin-bottom-left left-0', + left: 'origin-bottom-right right-0', + }; + return (
= ({ = ({ {options.map((item, index) => ( diff --git a/client/src/components/ui/ThemeSelector.tsx b/client/src/components/ui/ThemeSelector.tsx index 2aae70478..62cbcf9a3 100644 --- a/client/src/components/ui/ThemeSelector.tsx +++ b/client/src/components/ui/ThemeSelector.tsx @@ -28,7 +28,7 @@ const ThemeSelector = () => { ); return ( -
+
diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index b6511a992..315fad50b 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -144,7 +144,7 @@ export default { com_ui_fork_success: 'Successfully forked conversation', com_ui_fork_processing: 'Forking conversation...', com_ui_fork_error: 'There was an error forking the conversation', - com_ui_fork_change_default: 'Change default fork option', + com_ui_fork_change_default: 'Default fork option', com_ui_fork_default: 'Use default fork option', com_ui_fork_remember: 'Remember', com_ui_fork_split_target_setting: 'Start fork from target message by default', @@ -431,8 +431,8 @@ export default { 'Make sure to click \'Create and Continue\' to give at least the \'Vertex AI User\' role. Lastly, create a JSON key to import here.', com_nav_welcome_assistant: 'Please Select an Assistant', com_nav_welcome_message: 'How can I help you today?', - com_nav_auto_scroll: 'Auto-scroll to Newest on Open', - com_nav_hide_panel: 'Hide Right-most Side Panel', + com_nav_auto_scroll: 'Auto-Scroll to latest message on chat open', + com_nav_hide_panel: 'Hide right-most side panel', com_nav_modular_chat: 'Enable switching Endpoints mid-conversation', com_nav_latex_parsing: 'Parsing LaTeX in messages (may affect performance)', com_nav_profile_picture: 'Profile Picture', diff --git a/client/src/style.css b/client/src/style.css index c0f8fd460..92da6e9e7 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -1725,10 +1725,13 @@ html { height:1rem; width:1rem } -.icon-md { +.icon-md, .icon-md-heavy { stroke-width:1.5; - height:1.25rem; - width:1.25rem + height:1.125rem; + width:1.125rem +} +.icon-md-heavy { + stroke-width: 2.5; } .icon-lg { stroke-width:1.5; diff --git a/client/src/utils/cn.ts b/client/src/utils/cn.ts index daa125f19..de809354c 100644 --- a/client/src/utils/cn.ts +++ b/client/src/utils/cn.ts @@ -1,11 +1,11 @@ import { twMerge } from 'tailwind-merge'; -import { clsx } from 'clsx'; +import { type ClassValue, clsx } from 'clsx'; /** * Merges the tailwind clases (using twMerge). Conditionally removes false values * @param inputs The tailwind classes to merge * @returns className string to apply to an element or HOC */ -export default function cn(...inputs: Array) { +export default function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }