🔄 feat: chat direction (LTR-RTL) (#3260)

* feat: chat direction

* fix: FileRow

* feat: smooth trigger transition
This commit is contained in:
Marco Beretta 2024-07-17 16:08:13 +02:00 committed by GitHub
parent d5782ac66c
commit 237a0de8b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 145 additions and 111 deletions

View file

@ -4,16 +4,19 @@ import { ListeningIcon, Spinner } from '~/components/svg';
import { useLocalize, useSpeechToText } from '~/hooks'; import { useLocalize, useSpeechToText } from '~/hooks';
import { useChatFormContext } from '~/Providers'; import { useChatFormContext } from '~/Providers';
import { globalAudioId } from '~/common'; import { globalAudioId } from '~/common';
import { cn } from '~/utils';
export default function AudioRecorder({ export default function AudioRecorder({
textAreaRef, textAreaRef,
methods, methods,
ask, ask,
isRTL,
disabled, disabled,
}: { }: {
textAreaRef: React.RefObject<HTMLTextAreaElement>; textAreaRef: React.RefObject<HTMLTextAreaElement>;
methods: ReturnType<typeof useChatFormContext>; methods: ReturnType<typeof useChatFormContext>;
ask: (data: { text: string }) => void; ask: (data: { text: string }) => void;
isRTL: boolean;
disabled: boolean; disabled: boolean;
}) { }) {
const localize = useLocalize(); const localize = useLocalize();
@ -77,7 +80,12 @@ export default function AudioRecorder({
<button <button
onClick={isListening ? handleStopRecording : handleStartRecording} onClick={isListening ? handleStopRecording : handleStartRecording}
disabled={disabled} disabled={disabled}
className="absolute bottom-1.5 right-12 flex h-[30px] w-[30px] items-center justify-center rounded-lg p-0.5 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 md:bottom-3 md:right-12" className={cn(
'absolute flex h-[30px] w-[30px] items-center justify-center rounded-lg p-0.5 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700',
isRTL
? 'bottom-1.5 left-4 md:bottom-3 md:left-12'
: 'bottom-1.5 right-12 md:bottom-3 md:right-12',
)}
type="button" type="button"
> >
{renderIcon()} {renderIcon()}

View file

@ -48,6 +48,9 @@ const ChatForm = ({ index = 0 }) => {
store.showMentionPopoverFamily(index), store.showMentionPopoverFamily(index),
); );
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
const isRTL = chatDirection === 'rtl';
const { requiresKey } = useRequiresKey(); const { requiresKey } = useRequiresKey();
const handleKeyUp = useHandleKeyUp({ const handleKeyUp = useHandleKeyUp({
index, index,
@ -149,6 +152,7 @@ const ChatForm = ({ index = 0 }) => {
files={files} files={files}
setFiles={setFiles} setFiles={setFiles}
setFilesLoading={setFilesLoading} setFilesLoading={setFilesLoading}
isRTL={isRTL}
Wrapper={({ children }) => ( Wrapper={({ children }) => (
<div className="mx-2 mt-2 flex flex-wrap gap-2 px-2.5 md:pl-0 md:pr-4"> <div className="mx-2 mt-2 flex flex-wrap gap-2 px-2.5 md:pl-0 md:pr-4">
{children} {children}
@ -179,7 +183,7 @@ const ChatForm = ({ index = 0 }) => {
? ' pl-10 md:pl-[55px]' ? ' pl-10 md:pl-[55px]'
: 'pl-3 md:pl-4', : 'pl-3 md:pl-4',
'm-0 w-full resize-none border-0 bg-transparent py-[10px] placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ', 'm-0 w-full resize-none border-0 bg-transparent py-[10px] placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ',
SpeechToText ? 'pr-20 md:pr-[85px]' : 'pr-10 md:pr-12', SpeechToText && !isRTL ? 'pr-20 md:pr-[85px]' : 'pr-10 md:pr-12',
'max-h-[65vh] md:max-h-[75vh]', 'max-h-[65vh] md:max-h-[75vh]',
removeFocusRings, removeFocusRings,
)} )}
@ -188,15 +192,21 @@ const ChatForm = ({ index = 0 }) => {
<AttachFile <AttachFile
endpoint={_endpoint ?? ''} endpoint={_endpoint ?? ''}
endpointType={endpointType} endpointType={endpointType}
isRTL={isRTL}
disabled={disableInputs} disabled={disableInputs}
/> />
{(isSubmitting || isSubmittingAdded) && (showStopButton || showStopAdded) ? ( {(isSubmitting || isSubmittingAdded) && (showStopButton || showStopAdded) ? (
<StopButton stop={handleStopGenerating} setShowStopButton={setShowStopButton} /> <StopButton
stop={handleStopGenerating}
setShowStopButton={setShowStopButton}
isRTL={isRTL}
/>
) : ( ) : (
endpoint && ( endpoint && (
<SendButton <SendButton
ref={submitButtonRef} ref={submitButtonRef}
control={methods.control} control={methods.control}
isRTL={isRTL}
disabled={!!(filesLoading || isSubmitting || disableInputs)} disabled={!!(filesLoading || isSubmitting || disableInputs)}
/> />
) )
@ -206,6 +216,7 @@ const ChatForm = ({ index = 0 }) => {
disabled={!!disableInputs} disabled={!!disableInputs}
textAreaRef={textAreaRef} textAreaRef={textAreaRef}
ask={submitMessage} ask={submitMessage}
isRTL={isRTL}
methods={methods} methods={methods}
/> />
)} )}

View file

@ -9,14 +9,17 @@ import { useGetFileConfig } from '~/data-provider';
import { AttachmentIcon } from '~/components/svg'; import { AttachmentIcon } from '~/components/svg';
import { FileUpload } from '~/components/ui'; import { FileUpload } from '~/components/ui';
import { useFileHandling } from '~/hooks'; import { useFileHandling } from '~/hooks';
import { cn } from '~/utils';
const AttachFile = ({ const AttachFile = ({
endpoint, endpoint,
endpointType, endpointType,
isRTL,
disabled = false, disabled = false,
}: { }: {
endpoint: EModelEndpoint | ''; endpoint: EModelEndpoint | '';
endpointType?: EModelEndpoint; endpointType?: EModelEndpoint;
isRTL: boolean;
disabled?: boolean | null; disabled?: boolean | null;
}) => { }) => {
const { handleFileChange } = useFileHandling(); const { handleFileChange } = useFileHandling();
@ -30,7 +33,14 @@ const AttachFile = ({
} }
return ( return (
<div className="absolute bottom-2 left-2 md:bottom-3 md:left-4"> <div
className={cn(
'absolute',
isRTL
? 'bottom-2 right-14 md:bottom-3.5 md:right-3'
: 'bottom-2 left-2 md:bottom-3.5 md:left-4',
)}
>
<FileUpload handleFileChange={handleFileChange} className="flex"> <FileUpload handleFileChange={handleFileChange} className="flex">
<button <button
disabled={!!disabled} disabled={!!disabled}

View file

@ -13,6 +13,7 @@ export default function FileRow({
assistant_id, assistant_id,
tool_resource, tool_resource,
fileFilter, fileFilter,
isRTL,
Wrapper, Wrapper,
}: { }: {
files: Map<string, ExtendedFile>; files: Map<string, ExtendedFile>;
@ -21,6 +22,7 @@ export default function FileRow({
fileFilter?: (file: ExtendedFile) => boolean; fileFilter?: (file: ExtendedFile) => boolean;
assistant_id?: string; assistant_id?: string;
tool_resource?: EToolResources; tool_resource?: EToolResources;
isRTL?: boolean;
Wrapper?: React.FC<{ children: React.ReactNode }>; Wrapper?: React.FC<{ children: React.ReactNode }>;
}) { }) {
const files = Array.from(_files.values()).filter((file) => const files = Array.from(_files.values()).filter((file) =>
@ -64,8 +66,11 @@ export default function FileRow({
} }
const renderFiles = () => { const renderFiles = () => {
// Inline style for RTL
const rowStyle = isRTL ? { display: 'flex', flexDirection: 'row-reverse' } : {};
return ( return (
<> <div style={rowStyle as React.CSSProperties}>
{files {files
.reduce( .reduce(
(acc, current) => { (acc, current) => {
@ -90,10 +95,9 @@ export default function FileRow({
/> />
); );
} }
return <FileContainer key={index} file={file} onDelete={handleDelete} />; return <FileContainer key={index} file={file} onDelete={handleDelete} />;
})} })}
</> </div>
); );
}; };

View file

@ -9,42 +9,48 @@ import { cn } from '~/utils';
type SendButtonProps = { type SendButtonProps = {
disabled: boolean; disabled: boolean;
control: Control<{ text: string }>; control: Control<{ text: string }>;
isRTL: boolean;
}; };
const SubmitButton = React.memo( const SubmitButton = React.memo(
forwardRef((props: { disabled: boolean }, ref: React.ForwardedRef<HTMLButtonElement>) => { forwardRef(
const localize = useLocalize(); (props: { disabled: boolean; isRTL: boolean }, ref: React.ForwardedRef<HTMLButtonElement>) => {
return ( const localize = useLocalize();
<TooltipProvider delayDuration={250}> return (
<Tooltip> <TooltipProvider delayDuration={250}>
<TooltipTrigger asChild> <Tooltip>
<button <TooltipTrigger asChild>
ref={ref} <button
disabled={props.disabled} ref={ref}
className={cn( disabled={props.disabled}
'absolute bottom-1.5 right-2 rounded-lg border border-black p-0.5 text-white transition-colors enabled:bg-black disabled:bg-black disabled:text-gray-400 disabled:opacity-10 dark:border-white dark:bg-white dark:disabled:bg-white md:bottom-3 md:right-3', className={cn(
)} 'absolute rounded-lg border border-black p-0.5 text-white transition-colors enabled:bg-black disabled:bg-black disabled:text-gray-400 disabled:opacity-10 dark:border-white dark:bg-white dark:disabled:bg-white',
data-testid="send-button" props.isRTL
type="submit" ? 'bottom-1.5 left-2 md:bottom-3 md:left-3'
> : 'bottom-1.5 right-2 md:bottom-3 md:right-3',
<span className="" data-state="closed"> )}
<SendIcon size={24} /> data-testid="send-button"
</span> type="submit"
</button> >
</TooltipTrigger> <span className="" data-state="closed">
<TooltipContent side="top" sideOffset={10}> <SendIcon size={24} />
{localize('com_nav_send_message')} </span>
</TooltipContent> </button>
</Tooltip> </TooltipTrigger>
</TooltipProvider> <TooltipContent side="top" sideOffset={10}>
); {localize('com_nav_send_message')}
}), </TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
),
); );
const SendButton = React.memo( const SendButton = React.memo(
forwardRef((props: SendButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => { forwardRef((props: SendButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
const data = useWatch({ control: props.control }); const data = useWatch({ control: props.control });
return <SubmitButton ref={ref} disabled={props.disabled || !data?.text} />; return <SubmitButton ref={ref} disabled={props.disabled || !data?.text} isRTL={props.isRTL} />;
}), }),
); );

View file

@ -1,6 +1,13 @@
export default function StopButton({ stop, setShowStopButton }) { import { cn } from '~/utils';
export default function StopButton({ stop, setShowStopButton, isRTL }) {
return ( return (
<div className="absolute bottom-3 right-2 md:bottom-4 md:right-4"> <div
className={cn(
'absolute',
isRTL ? 'bottom-3 left-2 md:bottom-4 md:left-4' : 'bottom-3 right-2 md:bottom-4 md:right-4',
)}
>
<button <button
type="button" type="button"
className="border-gizmo-gray-900 rounded-full border-2 p-1 dark:border-gray-200" className="border-gizmo-gray-900 rounded-full border-2 p-1 dark:border-gray-200"

View file

@ -11,7 +11,7 @@ import {
TransitionChild, TransitionChild,
} from '@headlessui/react'; } from '@headlessui/react';
import { GearIcon, DataIcon, SpeechIcon, UserIcon, ExperimentIcon } from '~/components/svg'; import { GearIcon, DataIcon, SpeechIcon, UserIcon, ExperimentIcon } from '~/components/svg';
import { General, Messages, Speech, Beta, Data, Account } from './SettingsTabs'; import { General, Chat, Speech, Beta, Data, Account } from './SettingsTabs';
import { useMediaQuery, useLocalize } from '~/hooks'; import { useMediaQuery, useLocalize } from '~/hooks';
import { cn } from '~/utils'; import { cn } from '~/utils';
@ -100,7 +100,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
> >
<Tabs.Trigger <Tabs.Trigger
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-white radix-state-active:bg-gray-200',
@ -114,21 +114,21 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700', isSmallScreen ? '' : 'dark:bg-gray-700',
)} )}
value={SettingsTabValues.MESSAGES} value={SettingsTabValues.CHAT}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
> >
<MessageSquare className="icon-sm" /> <MessageSquare className="icon-sm" />
{localize('com_endpoint_messages')} {localize('com_nav_setting_chat')}
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-white radix-state-active:bg-gray-200',
@ -142,7 +142,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-white radix-state-active:bg-gray-200',
@ -156,7 +156,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-white radix-state-active:bg-gray-200',
@ -170,7 +170,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-white radix-state-active:bg-gray-200',
@ -185,7 +185,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.List> </Tabs.List>
<div className="max-h-[373px] overflow-auto sm:w-full sm:max-w-none md:pr-0.5 md:pt-0.5"> <div className="max-h-[373px] overflow-auto sm:w-full sm:max-w-none md:pr-0.5 md:pt-0.5">
<General /> <General />
<Messages /> <Chat />
<Beta /> <Beta />
<Speech /> <Speech />
<Data /> <Data />

View file

@ -4,15 +4,19 @@ import { SettingsTabValues } from 'librechat-data-provider';
import SendMessageKeyEnter from './EnterToSend'; import SendMessageKeyEnter from './EnterToSend';
import ShowCodeSwitch from './ShowCodeSwitch'; import ShowCodeSwitch from './ShowCodeSwitch';
import { ForkSettings } from './ForkSettings'; import { ForkSettings } from './ForkSettings';
import ChatDirection from './ChatDirection';
import SaveDraft from './SaveDraft'; import SaveDraft from './SaveDraft';
function Messages() { function Chat() {
return ( return (
<Tabs.Content value={SettingsTabValues.MESSAGES} role="tabpanel" className="md: w-full"> <Tabs.Content value={SettingsTabValues.CHAT} role="tabpanel" className="md: w-full">
<div className="flex flex-col gap-3 text-sm text-black 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"> <div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<SendMessageKeyEnter /> <SendMessageKeyEnter />
</div> </div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ChatDirection />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600"> <div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ShowCodeSwitch /> <ShowCodeSwitch />
</div> </div>
@ -25,4 +29,4 @@ function Messages() {
); );
} }
export default memo(Messages); export default memo(Chat);

View file

@ -0,0 +1,31 @@
import React from 'react';
import { useRecoilState } from 'recoil';
import { useLocalize } from '~/hooks';
import store from '~/store';
const ChatDirection = () => {
const [direction, setDirection] = useRecoilState(store.chatDirection);
const localize = useLocalize();
const toggleChatDirection = () => {
setDirection((prev) => (prev === 'LTR' ? 'RTL' : 'LTR'));
};
return (
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span>{localize('com_nav_chat_direction')}</span>
</div>
<label
onClick={toggleChatDirection}
data-testid="chatDirection"
className="btn btn-neutral relative"
style={{ userSelect: 'none' }}
>
{direction.toLowerCase()}
</label>
</div>
);
};
export default ChatDirection;

View file

@ -1,5 +1,5 @@
export { default as General } from './General/General'; export { default as General } from './General/General';
export { default as Messages } from './Messages/Messages'; export { default as Chat } from './Chat/Chat';
export { ClearChatsButton } from './General/General'; export { ClearChatsButton } from './General/General';
export { default as Data } from './Data/Data'; export { default as Data } from './Data/Data';
export { default as Beta } from './Beta/Beta'; export { default as Beta } from './Beta/Beta';

View file

@ -1,10 +1,14 @@
import { useRecoilValue } from 'recoil';
import { forwardRef, useLayoutEffect, useState } from 'react'; import { forwardRef, useLayoutEffect, useState } from 'react';
import ReactTextareaAutosize from 'react-textarea-autosize'; import ReactTextareaAutosize from 'react-textarea-autosize';
import type { TextareaAutosizeProps } from 'react-textarea-autosize'; import type { TextareaAutosizeProps } from 'react-textarea-autosize';
import store from '~/store';
export const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>( export const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
(props, ref) => { (props, ref) => {
const [, setIsRerendered] = useState(false); const [, setIsRerendered] = useState(false);
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
useLayoutEffect(() => setIsRerendered(true), []); useLayoutEffect(() => setIsRerendered(true), []);
return <ReactTextareaAutosize dir="auto" {...props} ref={ref} />; return <ReactTextareaAutosize dir={chatDirection} {...props} ref={ref} />;
}, },
); );

View file

@ -112,7 +112,7 @@ export default function useTextarea({
? getAssistantName({ name: assistantName, localize }) ? getAssistantName({ name: assistantName, localize })
: getSender(conversation as TEndpointOption); : getSender(conversation as TEndpointOption);
return `${localize('com_endpoint_message')} ${sender ? sender : 'ChatGPT'}`; return `${localize('com_endpoint_message')} ${sender ? sender : 'AI'}`;
}; };
const placeholder = getPlaceholderText(); const placeholder = getPlaceholderText();

View file

@ -469,7 +469,6 @@ export default {
com_ui_max_tags: 'الحد الأقصى المسموح به هو {0}، باستخدام أحدث القيم.', com_ui_max_tags: 'الحد الأقصى المسموح به هو {0}، باستخدام أحدث القيم.',
com_auth_back_to_login: 'العودة إلى تسجيل الدخول', com_auth_back_to_login: 'العودة إلى تسجيل الدخول',
com_endpoint_message: 'رسالة', com_endpoint_message: 'رسالة',
com_endpoint_messages: 'رسائل',
com_endpoint_message_not_appendable: 'عدّل رسالتك أو أعد إنشاءها.', com_endpoint_message_not_appendable: 'عدّل رسالتك أو أعد إنشاءها.',
com_endpoint_context_tokens: 'الحد الأقصى لرموز السياق', com_endpoint_context_tokens: 'الحد الأقصى لرموز السياق',
com_endpoint_context_info: com_endpoint_context_info:
@ -2294,10 +2293,6 @@ export const comparisons = {
english: 'Message', english: 'Message',
translated: 'رسالة', translated: 'رسالة',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'رسائل',
},
com_endpoint_message_not_appendable: { com_endpoint_message_not_appendable: {
english: 'Edit your message or Regenerate.', english: 'Edit your message or Regenerate.',
translated: 'عدّل رسالتك أو أعد إنشاءها.', translated: 'عدّل رسالتك أو أعد إنشاءها.',

View file

@ -565,7 +565,6 @@ export default {
com_ui_min_tags: com_ui_min_tags:
'Es können keine weiteren Werte entfernt werden, mindestens {0} sind erforderlich.', 'Es können keine weiteren Werte entfernt werden, mindestens {0} sind erforderlich.',
com_ui_max_tags: 'Die maximal erlaubte Anzahl ist {0}, die neuesten Werte werden verwendet.', com_ui_max_tags: 'Die maximal erlaubte Anzahl ist {0}, die neuesten Werte werden verwendet.',
com_endpoint_messages: 'Nachrichten',
com_endpoint_context_tokens: 'Max. Kontexttoken', com_endpoint_context_tokens: 'Max. Kontexttoken',
com_endpoint_context_info: com_endpoint_context_info:
'Die maximale Anzahl an Token, die für den Kontext verwendet werden kann. Verwenden Sie dies, um zu steuern, wie viele Token pro Anfrage gesendet werden. Wenn nicht angegeben, werden systemseitige Standardwerte basierend auf der bekannten Kontextgröße der Modelle verwendet. Höhere Werte können zu Fehlern und/oder höheren Tokenkosten führen.', 'Die maximale Anzahl an Token, die für den Kontext verwendet werden kann. Verwenden Sie dies, um zu steuern, wie viele Token pro Anfrage gesendet werden. Wenn nicht angegeben, werden systemseitige Standardwerte basierend auf der bekannten Kontextgröße der Modelle verwendet. Höhere Werte können zu Fehlern und/oder höheren Tokenkosten führen.',
@ -2603,10 +2602,6 @@ export const comparisons = {
english: 'Maximum number allowed is {0}, using latest values.', english: 'Maximum number allowed is {0}, using latest values.',
translated: 'Die maximal erlaubte Anzahl ist {0}, die neuesten Werte werden verwendet.', translated: 'Die maximal erlaubte Anzahl ist {0}, die neuesten Werte werden verwendet.',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'Nachrichten',
},
com_endpoint_context_tokens: { com_endpoint_context_tokens: {
english: 'Max Context Tokens', english: 'Max Context Tokens',
translated: 'Max. Kontexttoken', translated: 'Max. Kontexttoken',

View file

@ -371,7 +371,6 @@ export default {
'WARNING: Misuse of this feature can get you BANNED from using Bing! Click on \'System Message\' for full instructions and the default message if omitted, which is the \'Sydney\' preset that is considered safe.', 'WARNING: Misuse of this feature can get you BANNED from using Bing! Click on \'System Message\' for full instructions and the default message if omitted, which is the \'Sydney\' preset that is considered safe.',
com_endpoint_system_message: 'System Message', com_endpoint_system_message: 'System Message',
com_endpoint_message: 'Message', com_endpoint_message: 'Message',
com_endpoint_messages: 'Messages',
com_endpoint_message_not_appendable: 'Edit your message or Regenerate.', com_endpoint_message_not_appendable: 'Edit your message or Regenerate.',
com_endpoint_default_blank: 'default: blank', com_endpoint_default_blank: 'default: blank',
com_endpoint_default_false: 'default: false', com_endpoint_default_false: 'default: false',
@ -597,6 +596,7 @@ export default {
com_nav_enter_to_send: 'Press Enter to send messages', com_nav_enter_to_send: 'Press Enter to send messages',
com_nav_user_name_display: 'Display username in messages', com_nav_user_name_display: 'Display username in messages',
com_nav_save_drafts: 'Save drafts locally', com_nav_save_drafts: 'Save drafts locally',
com_nav_chat_direction: 'Chat direction',
com_nav_show_code: 'Always show code when using code interpreter', com_nav_show_code: 'Always show code when using code interpreter',
com_nav_auto_send_prompts: 'Auto-send Prompts', com_nav_auto_send_prompts: 'Auto-send Prompts',
com_nav_always_make_prod: 'Always make new versions production', com_nav_always_make_prod: 'Always make new versions production',
@ -660,6 +660,7 @@ export default {
com_nav_info_delete_cache_storage: 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.', '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_general: 'General',
com_nav_setting_chat: 'Chat',
com_nav_setting_beta: 'Beta features', com_nav_setting_beta: 'Beta features',
com_nav_setting_data: 'Data controls', com_nav_setting_data: 'Data controls',
com_nav_setting_account: 'Account', com_nav_setting_account: 'Account',

View file

@ -539,7 +539,6 @@ export default {
com_ui_import_conversation_file_type_error: com_ui_import_conversation_file_type_error:
'com_ui_import_conversation_file_type_error: Tipo de archivo no compatible para importar', 'com_ui_import_conversation_file_type_error: Tipo de archivo no compatible para importar',
com_ui_min_tags: 'No se pueden eliminar más valores, se requiere un mínimo de {0}.', com_ui_min_tags: 'No se pueden eliminar más valores, se requiere un mínimo de {0}.',
com_endpoint_messages: 'Mensajes',
com_endpoint_context_tokens: 'Máximo de tokens de contexto', com_endpoint_context_tokens: 'Máximo de tokens de contexto',
com_endpoint_stop_placeholder: 'Separe los valores presionando `Intro`', com_endpoint_stop_placeholder: 'Separe los valores presionando `Intro`',
com_error_no_base_url: com_error_no_base_url:
@ -2513,10 +2512,6 @@ export const comparisons = {
english: 'Cannot remove more values, a minimum of {0} are required.', english: 'Cannot remove more values, a minimum of {0} are required.',
translated: 'No se pueden eliminar más valores, se requiere un mínimo de {0}.', translated: 'No se pueden eliminar más valores, se requiere un mínimo de {0}.',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'Mensajes',
},
com_endpoint_context_tokens: { com_endpoint_context_tokens: {
english: 'Max Context Tokens', english: 'Max Context Tokens',
translated: 'Máximo de tokens de contexto', translated: 'Máximo de tokens de contexto',

View file

@ -571,7 +571,6 @@ export default {
com_ui_min_tags: 'Impossible de supprimer plus de valeurs, un minimum de {0} est requis.', com_ui_min_tags: 'Impossible de supprimer plus de valeurs, un minimum de {0} est requis.',
com_ui_max_tags: 'Le nombre maximum autorisé est {0}, en utilisant les dernières valeurs.', com_ui_max_tags: 'Le nombre maximum autorisé est {0}, en utilisant les dernières valeurs.',
com_auth_back_to_login: 'Retour à la connexion', com_auth_back_to_login: 'Retour à la connexion',
com_endpoint_messages: 'Messages',
com_endpoint_context_tokens: 'Jetons de contexte maximum', com_endpoint_context_tokens: 'Jetons de contexte maximum',
com_endpoint_context_info: com_endpoint_context_info:
'Le nombre maximum de jetons qui peuvent être utilisés pour le contexte. Utilisez ceci pour contrôler le nombre de jetons envoyés par requête. Si non spécifié, les valeurs par défaut du système seront utilisées en fonction de la taille de contexte connue des modèles. Définir des valeurs plus élevées peut entraîner des erreurs et/ou un coût en jetons plus élevé.', 'Le nombre maximum de jetons qui peuvent être utilisés pour le contexte. Utilisez ceci pour contrôler le nombre de jetons envoyés par requête. Si non spécifié, les valeurs par défaut du système seront utilisées en fonction de la taille de contexte connue des modèles. Définir des valeurs plus élevées peut entraîner des erreurs et/ou un coût en jetons plus élevé.',
@ -2733,10 +2732,6 @@ export const comparisons = {
english: 'Back to Login', english: 'Back to Login',
translated: 'Retour à la connexion', translated: 'Retour à la connexion',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'Messages',
},
com_endpoint_context_tokens: { com_endpoint_context_tokens: {
english: 'Max Context Tokens', english: 'Max Context Tokens',
translated: 'Jetons de contexte maximum', translated: 'Jetons de contexte maximum',

View file

@ -293,7 +293,6 @@ export default {
'ATTENZIONE: L\'uso improprio di questa funzione può farti BANNARE dall\'utilizzo di Bing! Clicca su "Messaggio di sistema" per le istruzioni complete e il messaggio predefinito se omesso, che è il preset "Sydney" considerato sicuro.', 'ATTENZIONE: L\'uso improprio di questa funzione può farti BANNARE dall\'utilizzo di Bing! Clicca su "Messaggio di sistema" per le istruzioni complete e il messaggio predefinito se omesso, che è il preset "Sydney" considerato sicuro.',
com_endpoint_system_message: 'Messaggio di sistema', com_endpoint_system_message: 'Messaggio di sistema',
com_endpoint_message: 'Messaggio', com_endpoint_message: 'Messaggio',
com_endpoint_messages: 'Messaggi',
com_endpoint_message_not_appendable: 'Modifica il tuo messaggio o Rigenera.', com_endpoint_message_not_appendable: 'Modifica il tuo messaggio o Rigenera.',
com_endpoint_default_blank: 'predefinito: vuoto', com_endpoint_default_blank: 'predefinito: vuoto',
com_endpoint_default_false: 'predefinito: falso', com_endpoint_default_false: 'predefinito: falso',
@ -1649,10 +1648,6 @@ export const comparisons = {
english: 'Message', english: 'Message',
translated: 'Messaggio', translated: 'Messaggio',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'Messaggi',
},
com_endpoint_message_not_appendable: { com_endpoint_message_not_appendable: {
english: 'Edit your message or Regenerate.', english: 'Edit your message or Regenerate.',
translated: 'Modifica il tuo messaggio o Rigenera.', translated: 'Modifica il tuo messaggio o Rigenera.',

View file

@ -544,7 +544,6 @@ export default {
com_ui_mention: com_ui_mention:
'エンドポイント、アシスタント、またはプリセットを素早く切り替えるには、それらを言及してください。', 'エンドポイント、アシスタント、またはプリセットを素早く切り替えるには、それらを言及してください。',
com_ui_import_conversation_file_type_error: 'サポートされていないインポート形式です', com_ui_import_conversation_file_type_error: 'サポートされていないインポート形式です',
com_endpoint_messages: 'メッセージ',
com_endpoint_context_tokens: 'コンテキストトークン数の最大値', com_endpoint_context_tokens: 'コンテキストトークン数の最大値',
com_endpoint_context_info: com_endpoint_context_info:
'コンテキストに使用できるトークンの最大数です。リクエストごとに送信されるトークン数を制御するために使用します。指定しない場合は、既知のモデルのコンテキストサイズに基づいてシステムのデフォルト値が使用されます。高い値を設定すると、エラーが発生したり、トークンコストが高くなる可能性があります。', 'コンテキストに使用できるトークンの最大数です。リクエストごとに送信されるトークン数を制御するために使用します。指定しない場合は、既知のモデルのコンテキストサイズに基づいてシステムのデフォルト値が使用されます。高い値を設定すると、エラーが発生したり、トークンコストが高くなる可能性があります。',
@ -2568,10 +2567,6 @@ export const comparisons = {
english: 'Unsupported import type', english: 'Unsupported import type',
translated: 'サポートされていないインポート形式です', translated: 'サポートされていないインポート形式です',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'メッセージ',
},
com_endpoint_context_tokens: { com_endpoint_context_tokens: {
english: 'Max Context Tokens', english: 'Max Context Tokens',
translated: 'コンテキストトークン数の最大値', translated: 'コンテキストトークン数の最大値',

View file

@ -455,7 +455,6 @@ export default {
com_auth_error_login_server: '내부 서버 오류가 발생했습니다. 잠시 기다렸다가 다시 시도해 주세요.', com_auth_error_login_server: '내부 서버 오류가 발생했습니다. 잠시 기다렸다가 다시 시도해 주세요.',
com_auth_back_to_login: '로그인 화면으로 돌아가기', com_auth_back_to_login: '로그인 화면으로 돌아가기',
com_endpoint_message: '메시지', com_endpoint_message: '메시지',
com_endpoint_messages: '메시지',
com_endpoint_message_not_appendable: '메시지를 수정하거나 다시 생성하세요.', com_endpoint_message_not_appendable: '메시지를 수정하거나 다시 생성하세요.',
com_endpoint_context_tokens: '최대 컨텍스트 토큰 수', com_endpoint_context_tokens: '최대 컨텍스트 토큰 수',
com_endpoint_context_info: com_endpoint_context_info:
@ -2257,10 +2256,6 @@ export const comparisons = {
english: 'Message', english: 'Message',
translated: '메시지', translated: '메시지',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: '메시지',
},
com_endpoint_message_not_appendable: { com_endpoint_message_not_appendable: {
english: 'Edit your message or Regenerate.', english: 'Edit your message or Regenerate.',
translated: '메시지를 수정하거나 다시 생성하세요.', translated: '메시지를 수정하거나 다시 생성하세요.',

View file

@ -518,7 +518,6 @@ export default {
com_ui_terms_of_service: 'Условия использования', com_ui_terms_of_service: 'Условия использования',
com_ui_min_tags: 'Нельзя удалить больше значений, требуется минимум {0}.', com_ui_min_tags: 'Нельзя удалить больше значений, требуется минимум {0}.',
com_ui_max_tags: 'Максимально допустимое количество - {0}, используются последние значения.', com_ui_max_tags: 'Максимально допустимое количество - {0}, используются последние значения.',
com_endpoint_messages: 'Сообщения',
com_endpoint_context_tokens: 'Максимальное количество контекстных токенов', com_endpoint_context_tokens: 'Максимальное количество контекстных токенов',
com_endpoint_context_info: com_endpoint_context_info:
'Максимальное количество токенов, которое может быть использовано для контекста. Используется для контроля количества токенов, отправляемых за один запрос. Если не указано, будут использованы системные значения по умолчанию, основанные на известном размере контекста моделей. Установка более высоких значений может привести к ошибкам и/или более высокой стоимости токенов.', 'Максимальное количество токенов, которое может быть использовано для контекста. Используется для контроля количества токенов, отправляемых за один запрос. Если не указано, будут использованы системные значения по умолчанию, основанные на известном размере контекста моделей. Установка более высоких значений может привести к ошибкам и/или более высокой стоимости токенов.',
@ -2466,10 +2465,6 @@ export const comparisons = {
english: 'Maximum number allowed is {0}, using latest values.', english: 'Maximum number allowed is {0}, using latest values.',
translated: 'Максимально допустимое количество - {0}, используются последние значения.', translated: 'Максимально допустимое количество - {0}, используются последние значения.',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: 'Сообщения',
},
com_endpoint_context_tokens: { com_endpoint_context_tokens: {
english: 'Max Context Tokens', english: 'Max Context Tokens',
translated: 'Максимальное количество контекстных токенов', translated: 'Максимальное количество контекстных токенов',

View file

@ -507,7 +507,6 @@ export default {
com_ui_import_conversation_file_type_error: '不支持的导入类型', com_ui_import_conversation_file_type_error: '不支持的导入类型',
com_ui_min_tags: '无法再移除更多值,至少需要保留{0}个。', com_ui_min_tags: '无法再移除更多值,至少需要保留{0}个。',
com_ui_max_tags: '最多允许{0}个,使用最新值。', com_ui_max_tags: '最多允许{0}个,使用最新值。',
com_endpoint_messages: '消息',
com_endpoint_context_tokens: '最大上下文词元数', com_endpoint_context_tokens: '最大上下文词元数',
com_endpoint_context_info: com_endpoint_context_info:
'可用于上下文的最大词元数。用于控制每个请求发送的词元数量。如果未指定,将根据已知模型的上下文大小使用系统默认值。设置较高的值可能会导致错误和/或更高的词元成本。', '可用于上下文的最大词元数。用于控制每个请求发送的词元数量。如果未指定,将根据已知模型的上下文大小使用系统默认值。设置较高的值可能会导致错误和/或更高的词元成本。',
@ -2501,10 +2500,6 @@ export const comparisons = {
english: 'Maximum number allowed is {0}, using latest values.', english: 'Maximum number allowed is {0}, using latest values.',
translated: '最多允许{0}个,使用最新值。', translated: '最多允许{0}个,使用最新值。',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: '消息',
},
com_endpoint_context_tokens: { com_endpoint_context_tokens: {
english: 'Max Context Tokens', english: 'Max Context Tokens',
translated: '最大上下文词元数', translated: '最大上下文词元数',

View file

@ -446,7 +446,6 @@ export default {
com_ui_max_tags: '允許的最大數量為 {0},已使用最新值。', com_ui_max_tags: '允許的最大數量為 {0},已使用最新值。',
com_auth_back_to_login: '返回登入', com_auth_back_to_login: '返回登入',
com_endpoint_message: '訊息', com_endpoint_message: '訊息',
com_endpoint_messages: '訊息',
com_endpoint_message_not_appendable: '無法附加訊息或重新生成。', com_endpoint_message_not_appendable: '無法附加訊息或重新生成。',
com_endpoint_context_tokens: '最大前後文 token 數', com_endpoint_context_tokens: '最大前後文 token 數',
com_endpoint_context_info: com_endpoint_context_info:
@ -2264,10 +2263,6 @@ export const comparisons = {
english: 'Message', english: 'Message',
translated: '訊息', translated: '訊息',
}, },
com_endpoint_messages: {
english: 'Messages',
translated: '訊息',
},
com_endpoint_message_not_appendable: { com_endpoint_message_not_appendable: {
english: 'Edit your message or Regenerate.', english: 'Edit your message or Regenerate.',
translated: '無法附加訊息或重新生成。', translated: '無法附加訊息或重新生成。',

View file

@ -951,10 +951,6 @@ Write a prompt that is mindful of the nuances in the language with respect to it
- **english**: Message - **english**: Message
- **translated**: Messaggio - **translated**: Messaggio
- **com_endpoint_messages**:
- **english**: Messages
- **translated**: Messaggi
- **com_endpoint_message_not_appendable**: - **com_endpoint_message_not_appendable**:
- **english**: Edit your message or Regenerate. - **english**: Edit your message or Regenerate.
- **translated**: Modifica il tuo messaggio o Rigenera. - **translated**: Modifica il tuo messaggio o Rigenera.

View file

@ -25,6 +25,7 @@ const localStorageAtoms = {
// Messages settings // Messages settings
enterToSend: atomWithLocalStorage('enterToSend', true), enterToSend: atomWithLocalStorage('enterToSend', true),
chatDirection: atomWithLocalStorage('chatDirection', 'LTR'),
showCode: atomWithLocalStorage('showCode', false), showCode: atomWithLocalStorage('showCode', false),
saveDrafts: atomWithLocalStorage('saveDrafts', false), saveDrafts: atomWithLocalStorage('saveDrafts', false),
forkSetting: atomWithLocalStorage('forkSetting', ''), forkSetting: atomWithLocalStorage('forkSetting', ''),

View file

@ -1058,6 +1058,7 @@ button {
border-color:transparent; border-color:transparent;
border-radius:.5rem; border-radius:.5rem;
border-width:1px; border-width:1px;
cursor: pointer;
display:inline-flex; display:inline-flex;
font-size:.875rem; font-size:.875rem;
font-weight:500; font-weight:500;
@ -2182,4 +2183,4 @@ ol ol):not(:where([class~=not-prose] *)) {
.move-up { .move-up {
animation: moveUp 4s ease-in-out infinite; animation: moveUp 4s ease-in-out infinite;
} }

View file

@ -842,9 +842,9 @@ export enum SettingsTabValues {
*/ */
GENERAL = 'general', GENERAL = 'general',
/** /**
* Tab for Messages Settings * Tab for Chat Settings
*/ */
MESSAGES = 'messages', CHAT = 'chat',
/** /**
* Tab for Speech Settings * Tab for Speech Settings
*/ */