mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🎨 style: settings tab update (#3088)
* style: settings UI update * style: update UI * style: update button style * fix: scroll settings on mobile * feat: `?` for settings
This commit is contained in:
parent
4319c62e66
commit
0424f8fe55
36 changed files with 359 additions and 173 deletions
|
|
@ -303,6 +303,7 @@ export type TDangerButtonProps = {
|
|||
actionTextCode: string;
|
||||
dataTestIdInitial: string;
|
||||
dataTestIdConfirm: string;
|
||||
infoDescriptionCode?: string;
|
||||
confirmActionTextCode?: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default function OptionsPopover({
|
|||
{presetsDisabled ? null : (
|
||||
<Button
|
||||
type="button"
|
||||
className="h-auto w-[150px] justify-start rounded-md border border-gray-300/50 bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:focus:ring-green-500"
|
||||
className="h-auto w-[150px] justify-start rounded-md border border-gray-300/50 bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:focus:ring-white"
|
||||
onClick={saveAsPreset}
|
||||
>
|
||||
<Save className="mr-1 w-[14px]" />
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
disableScroll={isSmallScreen}
|
||||
className={cn(
|
||||
'overflow-hidden shadow-2xl md:min-h-[373px] md:w-[680px]',
|
||||
isSmallScreen ? 'top-5 -translate-y-0' : '',
|
||||
'max-h-[90vh] overflow-auto shadow-2xl md:min-h-[500px] md:w-[680px]',
|
||||
isSmallScreen ? 'min-h-[200px]' : '',
|
||||
)}
|
||||
>
|
||||
<DialogHeader>
|
||||
|
|
@ -28,7 +29,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
||||
<Tabs.Root
|
||||
defaultValue={SettingsTabValues.GENERAL}
|
||||
className="flex flex-col gap-10 md:flex-row"
|
||||
className="flex flex-col gap-3 md:flex-row"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Tabs.List
|
||||
|
|
@ -36,17 +37,18 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
role="tablist"
|
||||
aria-orientation="horizontal"
|
||||
className={cn(
|
||||
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-wrap overflow-auto sm:max-w-none',
|
||||
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-800' : '',
|
||||
isSmallScreen
|
||||
? 'hide-scrollbar flex flex-row space-x-4 overflow-x-auto'
|
||||
: 'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-col flex-wrap overflow-auto sm:max-w-none',
|
||||
)}
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.GENERAL}
|
||||
|
|
@ -57,10 +59,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.MESSAGES}
|
||||
|
|
@ -71,10 +73,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.BETA}
|
||||
|
|
@ -85,10 +87,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.SPEECH}
|
||||
|
|
@ -99,10 +101,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.DATA}
|
||||
|
|
@ -113,10 +115,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.ACCOUNT}
|
||||
|
|
@ -126,7 +128,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
{localize('com_nav_setting_account')}
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<div className="h-screen max-h-[373px] overflow-auto sm:w-full sm:max-w-none">
|
||||
<div className="h-auto min-h-[280px] overflow-auto sm:w-full sm:max-w-none">
|
||||
<General />
|
||||
<Messages />
|
||||
<Beta />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { useRecoilState } from 'recoil';
|
||||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
import { SettingsTabValues } from 'librechat-data-provider';
|
||||
import HoverCardSettings from '../HoverCardSettings';
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import { Switch } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -25,7 +26,7 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
|
|||
role="tabpanel"
|
||||
className="w-full md:min-h-[271px]"
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<Avatar />
|
||||
</div>
|
||||
|
|
@ -33,7 +34,10 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
|
|||
<DeleteAccount />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{localize('com_nav_user_name_display')}</div>
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_user_name_display" />
|
||||
</div>
|
||||
<Switch
|
||||
id="UsernameDisplay"
|
||||
checked={UsernameDisplay}
|
||||
|
|
|
|||
|
|
@ -80,11 +80,8 @@ function Avatar() {
|
|||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{localize('com_nav_profile_picture')}</span>
|
||||
<label
|
||||
htmlFor={'file-upload-avatar'}
|
||||
className="flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-normal transition-colors hover:bg-gray-100 hover:text-green-700 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-green-500"
|
||||
>
|
||||
<FileImage className="mr-1 flex w-[22px] items-center stroke-1" />
|
||||
<label htmlFor={'file-upload-avatar'} className="btn btn-neutral relative">
|
||||
<FileImage className="mr-2 flex w-[22px] items-center stroke-1" />
|
||||
<span>{localize('com_nav_change_picture')}</span>
|
||||
<input
|
||||
id={'file-upload-avatar'}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogButton,
|
||||
Input,
|
||||
} from '~/components/ui';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, Input } from '~/components/ui';
|
||||
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
|
||||
import { useDeleteUserMutation } from '~/data-provider';
|
||||
import { Spinner, LockIcon } from '~/components/svg';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import DangerButton from '../DangerButton';
|
||||
|
||||
const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolean }) => {
|
||||
const localize = useLocalize();
|
||||
|
|
@ -50,16 +44,19 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea
|
|||
<div className="flex items-center justify-between">
|
||||
<span>{localize('com_nav_delete_account')}</span>
|
||||
<label>
|
||||
<DialogButton
|
||||
<DangerButton
|
||||
id={'delete-user-account'}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
actionTextCode="com_ui_delete"
|
||||
className={cn(
|
||||
'btn btn-danger relative border-none bg-red-700 text-white hover:bg-red-800 dark:hover:bg-red-800',
|
||||
'btn relative border-none bg-red-500 text-white hover:bg-red-700 dark:hover:bg-red-700',
|
||||
)}
|
||||
>
|
||||
{localize('com_ui_delete')}
|
||||
</DialogButton>
|
||||
confirmClear={false}
|
||||
infoTextCode={''}
|
||||
dataTestIdInitial={''}
|
||||
dataTestIdConfirm={''}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Dialog open={isDialogOpen} onOpenChange={() => setDialogOpen(false)}>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function Beta() {
|
|||
role="tabpanel"
|
||||
className="w-full md:min-h-[271px]"
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<ModularChat />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import HoverCardSettings from '../HoverCardSettings';
|
||||
import { Switch } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
|
@ -20,7 +21,10 @@ export default function LaTeXParsingSwitch({
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{localize('com_nav_latex_parsing')}</div>
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_latex_parsing" />
|
||||
</div>
|
||||
<Switch
|
||||
id="LaTeXParsing"
|
||||
checked={LaTeXParsing}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Spinner } from '~/components/svg';
|
|||
import type { TDangerButtonProps } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import HoverCardSettings from './HoverCardSettings';
|
||||
|
||||
const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonElement>) => {
|
||||
const {
|
||||
|
|
@ -20,6 +21,7 @@ const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonEle
|
|||
showText = true,
|
||||
dataTestIdInitial,
|
||||
dataTestIdConfirm,
|
||||
infoDescriptionCode,
|
||||
confirmActionTextCode = 'com_ui_confirm_action',
|
||||
} = props;
|
||||
const localize = useLocalize();
|
||||
|
|
@ -33,14 +35,19 @@ const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonEle
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
{showText && <div> {localize(infoTextCode)} </div>}
|
||||
{showText && (
|
||||
<div className={`flex items-center ${infoDescriptionCode ? 'space-x-2' : ''}`}>
|
||||
<div>{localize(infoTextCode)}</div>
|
||||
{infoDescriptionCode && <HoverCardSettings side="bottom" text={infoDescriptionCode} />}
|
||||
</div>
|
||||
)}
|
||||
<DialogButton
|
||||
id={id}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
' btn btn-danger relative min-w-[70px] border-none bg-red-700 text-white hover:bg-red-800 dark:hover:bg-red-800',
|
||||
'btn relative border-none bg-red-500 text-white hover:bg-red-700 dark:hover:bg-red-700',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function Data() {
|
|||
className="w-full md:min-h-[271px]"
|
||||
ref={dataTabRef}
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<ImportConversations />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export const DeleteCacheButton = ({
|
|||
id={'delete-cache'}
|
||||
actionTextCode={'com_ui_delete'}
|
||||
infoTextCode={'com_nav_delete_cache_storage'}
|
||||
infoDescriptionCode={'com_nav_info_delete_cache_storage'}
|
||||
dataTestIdInitial={'delete-cache-initial'}
|
||||
dataTestIdConfirm={'delete-cache-confirm'}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -68,11 +68,8 @@ function ImportConversations() {
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{localize('com_ui_import_conversation_info')}</span>
|
||||
<label
|
||||
htmlFor={'import-conversations-file'}
|
||||
className="flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-3 text-xs font-medium transition-colors hover:bg-gray-100 hover:text-green-700 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-green-500"
|
||||
>
|
||||
<div>{localize('com_ui_import_conversation_info')}</div>
|
||||
<label htmlFor={'import-conversations-file'} className="btn btn-neutral relative">
|
||||
{allowImport ? (
|
||||
<Import className="mr-1 flex h-4 w-4 items-center stroke-1" />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export const RevokeKeysButton = ({
|
|||
id={'revoke-all-user-keys'}
|
||||
actionTextCode={'com_ui_revoke'}
|
||||
infoTextCode={'com_ui_revoke_info'}
|
||||
infoDescriptionCode={'com_nav_info_revoke'}
|
||||
dataTestIdInitial={'revoke-all-keys-initial'}
|
||||
dataTestIdConfirm={'revoke-all-keys-confirm'}
|
||||
mutation={all ? revokeKeysMutation : revokeKeyMutation}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocalize } from '~/hooks';
|
||||
import { Dialog, DialogTrigger } from '~/components/ui';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { Dialog, DialogTrigger } from '~/components/ui';
|
||||
|
||||
import ArchivedChatsTable from './ArchivedChatsTable';
|
||||
|
||||
|
|
@ -10,7 +10,6 @@ export default function ArchivedChats() {
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_archived_chats')}</div>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className="btn btn-neutral relative ">
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ export const ThemeSelector = ({
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_theme')}</div>
|
||||
|
||||
<Dropdown
|
||||
value={theme}
|
||||
onChange={onChange}
|
||||
options={themeOptions}
|
||||
width={220}
|
||||
width={180}
|
||||
position={'left'}
|
||||
maxHeight="200px"
|
||||
testId="theme-selector"
|
||||
|
|
@ -64,6 +65,7 @@ export const ClearChatsButton = ({
|
|||
confirmActionTextCode="com_nav_confirm_clear"
|
||||
dataTestIdInitial="clear-convos-initial"
|
||||
dataTestIdConfirm="clear-convos-confirm"
|
||||
infoDescriptionCode="com_nav_info_clear_all_chats"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
|
|
@ -105,6 +107,7 @@ export const LangSelector = ({
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_language')}</div>
|
||||
|
||||
<Dropdown
|
||||
value={langcode}
|
||||
onChange={onChange}
|
||||
|
|
@ -153,7 +156,7 @@ function General() {
|
|||
className="w-full md:min-h-[271px]"
|
||||
ref={contentRef}
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<ThemeSelector theme={theme} onChange={changeTheme} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default function HideSidePanelSwitch({
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_hide_panel')}</div>
|
||||
|
||||
<Switch
|
||||
id="hideSidePanel"
|
||||
checked={hideSidePanel}
|
||||
|
|
|
|||
25
client/src/components/Nav/SettingsTabs/HoverCardSettings.tsx
Normal file
25
client/src/components/Nav/SettingsTabs/HoverCardSettings.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { HoverCard, HoverCardTrigger, HoverCardPortal, HoverCardContent } from '~/components/ui';
|
||||
import { CircleHelpIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const HoverCardSettings = ({ side, text }) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<HoverCard openDelay={500}>
|
||||
<HoverCardTrigger>
|
||||
<CircleHelpIcon className="h-5 w-5 text-gray-500" />{' '}
|
||||
</HoverCardTrigger>
|
||||
<HoverCardPortal>
|
||||
<HoverCardContent side={side} className="z-[999] w-80">
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(text)}</p>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCardPortal>
|
||||
</HoverCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default HoverCardSettings;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import HoverCardSettings from '../HoverCardSettings';
|
||||
import { Switch } from '~/components/ui/Switch';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import store from '~/store';
|
||||
|
|
@ -20,7 +21,10 @@ export default function SendMessageKeyEnter({
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{localize('com_nav_enter_to_send')}</div>
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_enter_to_send" />
|
||||
</div>
|
||||
<Switch
|
||||
id="enterToSend"
|
||||
checked={enterToSend}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import HoverCardSettings from '../HoverCardSettings';
|
||||
import { ForkOptions } from 'librechat-data-provider';
|
||||
import { Dropdown, Switch } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -20,12 +21,14 @@ export const ForkSettings = () => {
|
|||
<>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{localize('com_ui_fork_change_default')}</div>
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_fork_change_default" />
|
||||
</div>
|
||||
<Dropdown
|
||||
value={forkSetting}
|
||||
onChange={setForkSetting}
|
||||
options={forkOptions}
|
||||
width={200}
|
||||
position={'left'}
|
||||
maxHeight="199px"
|
||||
testId="fork-setting-dropdown"
|
||||
|
|
@ -46,7 +49,10 @@ export const ForkSettings = () => {
|
|||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{localize('com_ui_fork_split_target_setting')}</div>
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_fork_split_target_setting" />
|
||||
</div>
|
||||
<Switch
|
||||
id="splitAtTarget"
|
||||
checked={splitAtTarget}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import SaveDraft from './SaveDraft';
|
|||
function Messages() {
|
||||
return (
|
||||
<Tabs.Content value={SettingsTabValues.MESSAGES} role="tabpanel" className="md: w-full">
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<SendMessageKeyEnter />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '~/components/ui/Switch';
|
||||
import HoverCardSettings from '../HoverCardSettings';
|
||||
import { Switch } from '~/components/ui';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -20,7 +21,10 @@ export default function SaveDraft({
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{localize('com_nav_save_drafts')}</div>
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_save_draft" />
|
||||
</div>
|
||||
<Switch
|
||||
id="saveDrafts"
|
||||
checked={saveDrafts}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import HoverCardSettings from '../HoverCardSettings';
|
||||
import { Switch } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default function ConversationModeSwitch({
|
|||
}) {
|
||||
const localize = useLocalize();
|
||||
const [conversationMode, setConversationMode] = useRecoilState<boolean>(store.conversationMode);
|
||||
const [advancedMode, setAdvancedMode] = useRecoilState<boolean>(store.advancedMode);
|
||||
const [advancedMode] = useRecoilState<boolean>(store.advancedMode);
|
||||
const [textToSpeech] = useRecoilState<boolean>(store.TextToSpeech);
|
||||
const [, setAutoSendText] = useRecoilState<boolean>(store.autoSendText);
|
||||
const [, setDecibelValue] = useRecoilState(store.decibelValue);
|
||||
|
|
@ -34,13 +34,6 @@ export default function ConversationModeSwitch({
|
|||
<strong>{localize('com_nav_conversation_mode')}</strong>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label
|
||||
className="flex h-auto cursor-pointer items-center rounded border border-gray-500/70 bg-transparent px-2 py-1 text-xs font-medium font-normal transition-colors hover:border-gray-500/95 hover:bg-gray-100 hover:text-green-700 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-green-500"
|
||||
onClick={() => setAdvancedMode(!advancedMode)}
|
||||
>
|
||||
<span>{advancedMode ? 'Advanced Mode' : 'Simple Mode'}</span>
|
||||
</label>
|
||||
<div className="w-2" />
|
||||
<Switch
|
||||
id="ConversationMode"
|
||||
checked={conversationMode}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default function EngineSTTDropdown() {
|
|||
value={endpointSTT}
|
||||
onChange={handleSelect}
|
||||
options={endpointOptions}
|
||||
width={220}
|
||||
width={180}
|
||||
position={'left'}
|
||||
testId="EngineSTTDropdown"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import * as Tabs from '@radix-ui/react-tabs';
|
|||
import { SettingsTabValues } from 'librechat-data-provider';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useOnClickOutside } from '~/hooks';
|
||||
import { Lightbulb, Cog } from 'lucide-react';
|
||||
import { useOnClickOutside, useMediaQuery } from '~/hooks';
|
||||
import store from '~/store';
|
||||
import { cn } from '~/utils';
|
||||
import ConversationModeSwitch from './ConversationModeSwitch';
|
||||
import {
|
||||
TextToSpeechSwitch,
|
||||
|
|
@ -22,9 +24,10 @@ import {
|
|||
} from './STT';
|
||||
|
||||
function Speech() {
|
||||
const [confirmClear, setConfirmClear] = useState(false);
|
||||
const [advancedMode] = useRecoilState<boolean>(store.advancedMode);
|
||||
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
||||
const [advancedMode, setAdvancedMode] = useRecoilState<boolean>(store.advancedMode);
|
||||
const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
|
||||
const [confirmClear, setConfirmClear] = useState(false);
|
||||
|
||||
const contentRef = useRef(null);
|
||||
useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []);
|
||||
|
|
@ -36,7 +39,48 @@ function Speech() {
|
|||
className="w-full px-4 md:min-h-[300px]"
|
||||
ref={contentRef}
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
|
||||
<Tabs.Root
|
||||
defaultValue={'simple'}
|
||||
orientation="horizontal"
|
||||
value={advancedMode ? 'advanced' : 'simple'}
|
||||
>
|
||||
<div className="sticky top-0 z-50 bg-white dark:bg-gray-700">
|
||||
<Tabs.List className="sticky top-0 mb-4 flex justify-center bg-white dark:bg-gray-700">
|
||||
<Tabs.Trigger
|
||||
onClick={() => setAdvancedMode(false)}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm text-gray-700 radix-state-active:bg-gray-100 radix-state-active:text-black dark:text-gray-300 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-100 dark:bg-gray-700',
|
||||
'w-full',
|
||||
)}
|
||||
value="simple"
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<Lightbulb />
|
||||
Simple
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
onClick={() => setAdvancedMode(true)}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm text-gray-700 radix-state-active:bg-gray-100 radix-state-active:text-black dark:text-gray-300 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-100 dark:bg-gray-700',
|
||||
'w-full',
|
||||
)}
|
||||
value="advanced"
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<Cog />
|
||||
Advanced
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
|
||||
<Tabs.Content value={'simple'}>
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<ConversationModeSwitch />
|
||||
</div>
|
||||
|
|
@ -47,21 +91,6 @@ function Speech() {
|
|||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<EngineSTTDropdown />
|
||||
</div>
|
||||
{advancedMode && (
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutoTranscribeAudioSwitch />
|
||||
</div>
|
||||
)}
|
||||
{autoTranscribeAudio && advancedMode && (
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<DecibelSelector />
|
||||
</div>
|
||||
)}
|
||||
{advancedMode && (
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutoSendTextSwitch />
|
||||
</div>
|
||||
)}
|
||||
<div className="h-px bg-black/20 bg-white/20" role="none" />
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<TextToSpeechSwitch />
|
||||
|
|
@ -75,18 +104,55 @@ function Speech() {
|
|||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<VoiceDropdown />
|
||||
</div>
|
||||
{advancedMode && (
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value={'advanced'}>
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<ConversationModeSwitch />
|
||||
</div>
|
||||
<div className="h-px bg-black/20 bg-white/20" role="none" />
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<SpeechToTextSwitch />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<EngineSTTDropdown />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutoTranscribeAudioSwitch />
|
||||
</div>
|
||||
{autoTranscribeAudio && (
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<DecibelSelector />
|
||||
</div>
|
||||
)}
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutoSendTextSwitch />
|
||||
</div>
|
||||
<div className="h-px bg-black/20 bg-white/20" role="none" />
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<TextToSpeechSwitch />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<AutomaticPlayback />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<EngineTTSDropdown />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<VoiceDropdown />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<PlaybackRate />
|
||||
</div>
|
||||
)}
|
||||
{advancedMode && (
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<CacheTTSSwitch />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</Tabs.Content>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default function EngineTTSDropdown() {
|
|||
value={endpointTTS}
|
||||
onChange={handleSelect}
|
||||
options={endpointOptions}
|
||||
width={220}
|
||||
width={180}
|
||||
position={'left'}
|
||||
testId="EngineTTSDropdown"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ export default function VoiceDropdown() {
|
|||
value={voice}
|
||||
onChange={(value: string) => setVoice(value)}
|
||||
options={voiceOptions}
|
||||
width={220}
|
||||
position={'left'}
|
||||
testId="VoiceDropdown"
|
||||
/>
|
||||
|
|
|
|||
22
client/src/components/svg/CircleHelpIcon.tsx
Normal file
22
client/src/components/svg/CircleHelpIcon.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { cn } from '~/utils/';
|
||||
|
||||
export default function CircleHelpIcon({ className = 'icon-md-heavy', size = '1em' }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={size}
|
||||
width={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={cn(className)}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
||||
<path d="M12 17h.01" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -55,3 +55,4 @@ export { default as AssistantIcon } from './AssistantIcon';
|
|||
export { default as Sparkles } from './Sparkles';
|
||||
export { default as SpeechIcon } from './SpeechIcon';
|
||||
export { default as SaveIcon } from './SaveIcon';
|
||||
export { default as CircleHelpIcon } from './CircleHelpIcon';
|
||||
|
|
|
|||
|
|
@ -35,10 +35,19 @@ const DialogOverlay = React.forwardRef<
|
|||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
type DialogContentProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||
showCloseButton?: boolean;
|
||||
disableScroll?: boolean;
|
||||
};
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { showCloseButton?: boolean }
|
||||
>(({ className, children = true, showCloseButton = true, ...props }, ref) => {
|
||||
DialogContentProps
|
||||
>(
|
||||
(
|
||||
{ className, children = true, showCloseButton = true, disableScroll = false, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
return (
|
||||
<DialogPortal>
|
||||
|
|
@ -51,13 +60,14 @@ const DialogContent = React.forwardRef<
|
|||
isSmallScreen
|
||||
? 'fixed left-1/2 top-1/2 z-[999] m-auto grid w-11/12 -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-white pb-6'
|
||||
: '',
|
||||
disableScroll ? 'overflow-hidden' : '',
|
||||
className ?? '',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close className="absolute right-6 top-[1.6rem] rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900 dark:data-[state=open]:bg-gray-800">
|
||||
<DialogPrimitive.Close className="absolute right-6 top-[1.6rem] rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 dark:focus:ring-white dark:focus:ring-offset-gray-700 dark:data-[state=open]:bg-gray-800">
|
||||
<X className="h-5 w-5 text-black dark:text-white" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
|
|
@ -65,7 +75,8 @@ const DialogContent = React.forwardRef<
|
|||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
|
|
|
|||
|
|
@ -52,10 +52,11 @@ const Dropdown: FC<DropdownProps> = ({
|
|||
<Listbox.Button
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
'relative inline-flex items-center justify-between rounded-md border-gray-300 bg-white py-2 pl-3 pr-8 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
|
||||
'relative inline-flex items-center justify-between rounded-md border-gray-50 bg-white py-2 pl-3 pr-8 text-black transition-all duration-100 ease-in-out hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-white dark:focus:ring-offset-gray-700',
|
||||
'w-auto',
|
||||
className,
|
||||
)}
|
||||
aria-label="Select an option"
|
||||
>
|
||||
<span className="block truncate">
|
||||
{label}
|
||||
|
|
@ -70,7 +71,7 @@ const Dropdown: FC<DropdownProps> = ({
|
|||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
className="h-4 w-5 rotate-0 transform text-gray-400 transition-transform duration-300 ease-in-out"
|
||||
className="h-4 w-5 rotate-0 transform text-black transition-transform duration-300 ease-in-out dark:text-gray-50"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
|
|
@ -78,24 +79,46 @@ const Dropdown: FC<DropdownProps> = ({
|
|||
</Listbox.Button>
|
||||
<Listbox.Options
|
||||
className={cn(
|
||||
`absolute z-50 mt-1 flex max-h-[40vh] flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-300 bg-white p-1.5 text-gray-700 shadow-lg transition-opacity focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white ${positionClasses[position]}`,
|
||||
`absolute z-50 mt-1 flex max-h-[40vh] flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-100 bg-white p-1.5 text-black shadow-lg transition-opacity dark:border-gray-600 dark:bg-gray-700 dark:text-white ${positionClasses[position]}`,
|
||||
className,
|
||||
)}
|
||||
style={{ width: width ? `${width}px` : 'auto', maxHeight: maxHeight }}
|
||||
aria-label="List of options"
|
||||
>
|
||||
{options.map((item, index) => (
|
||||
<Listbox.Option
|
||||
key={index}
|
||||
value={typeof item === 'string' ? item : item.value}
|
||||
className={cn(
|
||||
'relative cursor-pointer select-none rounded border-gray-300 bg-white py-2.5 pl-3 pr-6 text-gray-700 hover:bg-gray-100 dark:border-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
|
||||
'duration-50 relative cursor-pointer select-none rounded border-gray-50 bg-white py-2.5 pl-3 pr-2 text-black transition-all ease-in-out hover:bg-gray-100 focus:bg-gray-200 dark:border-gray-50 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:bg-gray-500',
|
||||
)}
|
||||
style={{ width: '100%' }}
|
||||
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span className="block truncate">
|
||||
{typeof item === 'string' ? item : (item as OptionType).display}
|
||||
</span>
|
||||
{selectedValue === (typeof item === 'string' ? item : item.value) && (
|
||||
<span className="ml-auto pl-2">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="icon-md block group-hover:hidden"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM16.0755 7.93219C16.5272 8.25003 16.6356 8.87383 16.3178 9.32549L11.5678 16.0755C11.3931 16.3237 11.1152 16.4792 10.8123 16.4981C10.5093 16.517 10.2142 16.3973 10.0101 16.1727L7.51006 13.4227C7.13855 13.014 7.16867 12.3816 7.57733 12.0101C7.98598 11.6386 8.61843 11.6687 8.98994 12.0773L10.6504 13.9039L14.6822 8.17451C15 7.72284 15.6238 7.61436 16.0755 7.93219Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const Switch = React.forwardRef<
|
|||
>(({ className = '', ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
'focus-visible:ring-ring focus-visible:ring-offset-background peer inline-flex h-[19.2px] w-[32px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-green-500 data-[state=unchecked]:bg-gray-200',
|
||||
'focus-visible:ring-ring focus-visible:ring-offset-background peer inline-flex h-[20px] w-[32px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-black data-[state=unchecked]:bg-gray-200 dark:data-[state=checked]:bg-green-500 dark:data-[state=unchecked]:bg-gray-500',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -17,7 +17,7 @@ const Switch = React.forwardRef<
|
|||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
'pointer-events-none block h-4 w-4 -translate-x-0.5 rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.45)] transition-transform data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0',
|
||||
'pointer-events-none block h-4 w-4 -translate-x-0.5 rounded-full bg-white transition-transform data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0',
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
|
|
|
|||
|
|
@ -626,9 +626,24 @@ export default {
|
|||
com_nav_engine: 'Engine',
|
||||
com_nav_browser: 'Browser',
|
||||
com_nav_external: 'External',
|
||||
com_nav_delete_cache_storage: 'Delete cache storage',
|
||||
com_nav_delete_cache_storage: 'Delete TTS cache storage',
|
||||
com_nav_enable_cache_tts: 'Enable cache TTS',
|
||||
com_nav_voice_select: 'Voice',
|
||||
com_nav_info_enter_to_send:
|
||||
'When enabled, pressing `ENTER` will send your message. When disabled, pressing Enter will add a new line, and you\'ll need to press `CTRL + ENTER` to send your message.',
|
||||
com_nav_info_save_draft:
|
||||
'When enabled, the text and attachments you enter in the chat form will be automatically saved locally as drafts. These drafts will be available even if you reload the page or switch to a different conversation. Drafts are stored locally on your device and are deleted once the message is sent.',
|
||||
com_nav_info_fork_change_default:
|
||||
'`Visible messages only` includes just the direct path to the selected message. `Include related branches` adds branches along the path. `Include all to/from here` includes all connected messages and branches.',
|
||||
com_nav_info_fork_split_target_setting: 'idk what this does 🤷♂️',
|
||||
com_nav_info_user_name_display:
|
||||
'When enabled, the username of the sender will be shown above each message you send. When disabled, you will only see "You" above your messages.',
|
||||
com_nav_info_latex_parsing:
|
||||
'When enabled, LaTeX code in messages will be rendered as mathematical equations. Disabling this may improve performance if you don\'t need LaTeX rendering.',
|
||||
com_nav_info_revoke:
|
||||
'This action will revoke and remove all the API keys that you have provided. You will need to re-enter these credentials to continue using those endpoints.',
|
||||
com_nav_info_delete_cache_storage:
|
||||
'This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.',
|
||||
com_nav_setting_general: 'General',
|
||||
com_nav_setting_beta: 'Beta features',
|
||||
com_nav_setting_data: 'Data controls',
|
||||
|
|
|
|||
|
|
@ -1057,7 +1057,8 @@ button {
|
|||
font-weight:500;
|
||||
line-height:1.25rem;
|
||||
padding:.5rem .75rem;
|
||||
pointer-events:auto
|
||||
pointer-events:auto;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
.custom-btn {
|
||||
align-items: center;
|
||||
|
|
@ -1068,6 +1069,7 @@ button {
|
|||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
.btn:focus {
|
||||
outline: 2px solid transparent;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue