mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 03:10:15 +01:00
feat: Add More Translation Text & Minor UI Fixes (#861)
* config token translation * more translation and fix * fix conflict * fix(DialogTemplate) bug with the spec.tsx, localize hooks need to be in a recoil root * small clean up * fix(NewTopic) in endpoint * fix(RecoilRoot) * test(DialogTemplate.spec) used data-testid * fix(DialogTemplate) * some cleanup --------- Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
This commit is contained in:
parent
28230d9305
commit
ac8b898495
28 changed files with 333 additions and 206 deletions
|
|
@ -59,7 +59,9 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }: TEditP
|
|||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTemplate
|
||||
title={`${title || localize('com_endpoint_edit_preset')} - ${preset?.title}`}
|
||||
title={`${title || localize('com_ui_edit') + ' ' + localize('com_endpoint_preset')} - ${
|
||||
preset?.title
|
||||
}`}
|
||||
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden lg:w-[950px] xl:h-[720px]"
|
||||
main={
|
||||
<div className="flex w-full flex-col items-center gap-2 md:h-[530px]">
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
|||
selection={{
|
||||
selectHandler: submitPreset,
|
||||
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
|
||||
selectText: 'Save',
|
||||
selectText: localize('com_ui_save'),
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ import {
|
|||
} from '~/components/ui';
|
||||
import OptionHover from './OptionHover';
|
||||
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
|
||||
const localize = useLocalize();
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -43,14 +45,15 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
|
||||
Custom Name <small className="opacity-40">(default: blank)</small>
|
||||
{localize('com_endpoint_custom_name')}{' '}
|
||||
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
|
||||
</Label>
|
||||
<Input
|
||||
id="modelLabel"
|
||||
disabled={readonly}
|
||||
value={modelLabel || ''}
|
||||
onChange={(e) => setModelLabel(e.target.value ?? null)}
|
||||
placeholder="Set a custom name for Claude"
|
||||
placeholder={localize('com_endpoint_anthropic_custom_name_placeholder')}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
|
||||
|
|
@ -60,14 +63,15 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
|
||||
Prompt Prefix <small className="opacity-40">(default: blank)</small>
|
||||
{localize('com_endpoint_prompt_prefix')}{' '}
|
||||
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
|
||||
</Label>
|
||||
<TextareaAutosize
|
||||
id="promptPrefix"
|
||||
disabled={readonly}
|
||||
value={promptPrefix || ''}
|
||||
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
|
||||
placeholder="Set custom instructions or context. Ignored if empty."
|
||||
placeholder={localize('com_endpoint_prompt_prefix_placeholder')}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
|
||||
|
|
@ -80,7 +84,8 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
|
||||
Temperature <small className="opacity-40">(default: 1)</small>
|
||||
{localize('com_endpoint_temperature')}{' '}
|
||||
<small className="opacity-40">({localize('com_endpoint_default')}: 0.2)</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="temp-int"
|
||||
|
|
@ -116,9 +121,10 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
|
||||
Top P <small className="opacity-40">(default: 0.7)</small>
|
||||
</Label>
|
||||
{localize('com_endpoint_top_p')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', '0.7')})
|
||||
</small>
|
||||
<InputNumber
|
||||
id="top-p-int"
|
||||
disabled={readonly}
|
||||
|
|
@ -155,7 +161,10 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
|
||||
Top K <small className="opacity-40">(default: 5)</small>
|
||||
{localize('com_endpoint_top_k')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', '5')})
|
||||
</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="top-k-int"
|
||||
|
|
@ -191,9 +200,10 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
|
||||
Max Output Tokens <small className="opacity-40">(default: 1024)</small>
|
||||
</Label>
|
||||
{localize('com_endpoint_max_output_tokens')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', '1024')})
|
||||
</small>
|
||||
<InputNumber
|
||||
id="max-tokens-int"
|
||||
disabled={readonly}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
disabled={readonly}
|
||||
value={promptPrefix || ''}
|
||||
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
|
||||
placeholder={localize('com_endpoint_google_prompt_prefix_placeholder')}
|
||||
placeholder={localize('com_endpoint_prompt_prefix_placeholder')}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ',
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export default function NewConversationMenu() {
|
|||
}
|
||||
>
|
||||
{icon}
|
||||
<span className="max-w-0 overflow-hidden whitespace-nowrap px-0 text-slate-600 transition-all group-hover:max-w-[80px] group-hover:px-2 group-data-[state=open]:max-w-[80px] group-data-[state=open]:px-2 dark:text-slate-300">
|
||||
<span className="max-w-0 overflow-hidden whitespace-nowrap px-0 text-slate-600 transition-all group-data-[state=open]:max-w-[80px] group-data-[state=open]:px-2 dark:text-slate-300">
|
||||
{localize('com_endpoint_new_topic')}
|
||||
</span>
|
||||
</Button>
|
||||
|
|
@ -209,7 +209,7 @@ export default function NewConversationMenu() {
|
|||
onClick={() => setShowPresets((prev) => !prev)}
|
||||
>
|
||||
{showPresets ? localize('com_endpoint_hide') : localize('com_endpoint_show')}{' '}
|
||||
{localize('com_endpoint_examples')}
|
||||
{localize('com_endpoint_presets')}
|
||||
</span>
|
||||
<FileUpload onFileSelected={onFileSelected} />
|
||||
<Dialog>
|
||||
|
|
@ -223,18 +223,19 @@ export default function NewConversationMenu() {
|
|||
className="h-auto bg-transparent px-2 py-1 text-xs font-medium font-normal text-red-700 hover:bg-slate-200 hover:text-red-700 dark:bg-transparent dark:text-red-400 dark:hover:bg-gray-800 dark:hover:text-red-400"
|
||||
> */}
|
||||
<Trash2 className="mr-1 flex w-[22px] items-center stroke-1" />
|
||||
{localize('com_endpoint_clear_all')}
|
||||
{localize('com_ui_clear')} {localize('com_ui_all')}
|
||||
{/* </Button> */}
|
||||
</label>
|
||||
</DialogTrigger>
|
||||
<DialogTemplate
|
||||
title="Clear presets"
|
||||
description="Are you sure you want to clear all presets? This is irreversible."
|
||||
title={`${localize('com_ui_clear')} ${localize('com_endpoint_presets')}`}
|
||||
description={localize('com_endpoint_presets_clear_warning')}
|
||||
selection={{
|
||||
selectHandler: clearAllPresets,
|
||||
selectClasses: 'bg-red-600 hover:bg-red-700 dark:hover:bg-red-800 text-white',
|
||||
selectText: 'Clear',
|
||||
selectText: localize('com_ui_clear'),
|
||||
}}
|
||||
className="max-w-[500px]"
|
||||
/>
|
||||
</Dialog>
|
||||
</DropdownMenuLabel>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FileUp } from 'lucide-react';
|
||||
import { cn } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type FileUploadProps = {
|
||||
onFileSelected: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
|
|
@ -23,6 +24,7 @@ const FileUpload: React.FC<FileUploadProps> = ({
|
|||
}) => {
|
||||
const [statusColor, setStatusColor] = useState<string>('text-gray-600');
|
||||
const [status, setStatus] = useState<null | string>(null);
|
||||
const localize = useLocalize();
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const file = event.target.files?.[0];
|
||||
|
|
@ -59,7 +61,11 @@ const FileUpload: React.FC<FileUploadProps> = ({
|
|||
>
|
||||
<FileUp className="mr-1 flex w-[22px] items-center stroke-1" />
|
||||
<span className="flex text-xs ">
|
||||
{!status ? text || 'Import' : status === 'success' ? successText : invalidText}
|
||||
{!status
|
||||
? text || localize('com_endpoint_import')
|
||||
: status === localize('com_ui_succes')
|
||||
? successText
|
||||
: invalidText}
|
||||
</span>
|
||||
<input
|
||||
id={`file-upload-${id}`}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import FileUpload from '../EndpointMenu/FileUpload';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const GoogleConfig = ({ setToken }: { setToken: React.Dispatch<React.SetStateAction<string>> }) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<FileUpload
|
||||
id="googleKey"
|
||||
className="w-full"
|
||||
text="Import Service Account JSON Key"
|
||||
successText="Successfully Imported Service Account JSON Key"
|
||||
invalidText="Invalid Service Account JSON Key, Did you import the correct file?"
|
||||
text={localize('com_endpoint_config_token_import_json_key')}
|
||||
successText={localize('com_endpoint_config_token_import_json_key_succesful')}
|
||||
invalidText={localize('com_endpoint_config_token_import_json_key_invalid')}
|
||||
validator={(credentials) => {
|
||||
if (!credentials) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
function HelpText({ endpoint }: { endpoint: string }) {
|
||||
const localize = useLocalize();
|
||||
const textMap = {
|
||||
bingAI: (
|
||||
<small className="break-all text-gray-600">
|
||||
{'To get your Access token for Bing, login to '}
|
||||
{localize('com_endpoint_config_token_get_edge_key')}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://www.bing.com"
|
||||
|
|
@ -13,22 +15,22 @@ function HelpText({ endpoint }: { endpoint: string }) {
|
|||
>
|
||||
https://www.bing.com
|
||||
</a>
|
||||
{`. Use dev tools or an extension while logged into the site to copy the content of the _U cookie.
|
||||
If this fails, follow these `}
|
||||
{'. '}
|
||||
{localize('com_endpoint_config_token_get_edge_key_dev_tool')}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/waylaidwanderer/node-chatgpt-api/issues/378#issuecomment-1559868368"
|
||||
rel="noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
instructions
|
||||
</a>
|
||||
{' to provide the full cookie strings.'}
|
||||
{localize('com_endpoint_config_token_edge_instructions')}
|
||||
</a>{' '}
|
||||
{localize('com_endpoint_config_token_edge_full_token_string')}
|
||||
</small>
|
||||
),
|
||||
chatGPTBrowser: (
|
||||
<small className="break-all text-gray-600">
|
||||
{'To get your Access token For ChatGPT \'Free Version\', login to '}
|
||||
{localize('com_endpoint_config_token_chatgpt')}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://chat.openai.com"
|
||||
|
|
@ -37,7 +39,8 @@ function HelpText({ endpoint }: { endpoint: string }) {
|
|||
>
|
||||
https://chat.openai.com
|
||||
</a>
|
||||
, then visit{' '}
|
||||
{', '}
|
||||
{localize('com_endpoint_config_token_chatgpt_then_visit')}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://chat.openai.com/api/auth/session"
|
||||
|
|
@ -46,31 +49,32 @@ function HelpText({ endpoint }: { endpoint: string }) {
|
|||
>
|
||||
https://chat.openai.com/api/auth/session
|
||||
</a>
|
||||
. Copy access token.
|
||||
{'. '}
|
||||
{localize('com_endpoint_config_token_chatgpt_copy_token')}
|
||||
</small>
|
||||
),
|
||||
google: (
|
||||
<small className="break-all text-gray-600">
|
||||
You need to{' '}
|
||||
{localize('com_endpoint_config_token_google_need_to')}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://console.cloud.google.com/vertex-ai"
|
||||
rel="noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
Enable Vertex AI
|
||||
{localize('com_endpoint_config_token_google_vertex_ai')}
|
||||
</a>{' '}
|
||||
API on Google Cloud, then{' '}
|
||||
{localize('com_endpoint_config_token_google_vertex_api')}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1"
|
||||
rel="noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
Create a Service Account
|
||||
{localize('com_endpoint_config_token_google_service_account')}
|
||||
</a>
|
||||
{`. Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
|
||||
Lastly, create a JSON key to import here.`}
|
||||
{'. '}
|
||||
{localize('com_endpoint_config_token_google_vertex_api_role')}
|
||||
</small>
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { ChangeEvent, FC } from 'react';
|
||||
import { Input, Label } from '~/components';
|
||||
import { cn, defaultTextPropsLabel, removeFocusOutlines } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface InputWithLabelProps {
|
||||
value: string;
|
||||
|
|
@ -10,6 +11,7 @@ interface InputWithLabelProps {
|
|||
}
|
||||
|
||||
const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id }) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<>
|
||||
<Label htmlFor={id} className="text-left text-sm font-medium">
|
||||
|
|
@ -21,7 +23,7 @@ const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id })
|
|||
id={id}
|
||||
value={value || ''}
|
||||
onChange={onChange}
|
||||
placeholder={`Enter ${label}`}
|
||||
placeholder={`${localize('com_ui_enter')} ${label}`}
|
||||
className={cn(
|
||||
defaultTextPropsLabel,
|
||||
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import InputWithLabel from './InputWithLabel';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type ConfigProps = {
|
||||
token: string;
|
||||
|
|
@ -7,12 +8,13 @@ type ConfigProps = {
|
|||
};
|
||||
|
||||
const OtherConfig = ({ token, setToken }: ConfigProps) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<InputWithLabel
|
||||
id={'chatGPTLabel'}
|
||||
value={token || ''}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setToken(e.target.value || '')}
|
||||
label={'Token Name'}
|
||||
label={localize('com_endpoint_config_token_name')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import { Dialog } from '~/components/ui';
|
|||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { alternateName } from '~/utils';
|
||||
import store from '~/store';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
||||
const localize = useLocalize();
|
||||
const [token, setToken] = useState('');
|
||||
const { saveToken } = store.useToken(endpoint);
|
||||
|
||||
|
|
@ -30,21 +32,21 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
|||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTemplate
|
||||
title={`Set Token for ${alternateName[endpoint] ?? endpoint}`}
|
||||
title={`${localize('com_endpoint_config_token_for')} ${
|
||||
alternateName[endpoint] ?? endpoint
|
||||
}`}
|
||||
className="w-full max-w-[650px] sm:w-3/4 md:w-3/4 lg:w-3/4"
|
||||
main={
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<EndpointComponent token={token} setToken={setToken} endpoint={endpoint} />
|
||||
<small className="text-red-600">
|
||||
Your token will be sent to the server, but not saved.
|
||||
</small>
|
||||
<small className="text-red-600">{localize('com_endpoint_config_token_server')}</small>
|
||||
<HelpText endpoint={endpoint} />
|
||||
</div>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: submit,
|
||||
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
|
||||
selectText: 'Submit',
|
||||
selectText: localize('com_ui_submit'),
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { StopGeneratingIcon } from '~/components';
|
|||
import { Settings } from 'lucide-react';
|
||||
import { SetTokenDialog } from './SetTokenDialog';
|
||||
import store from '~/store';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function SubmitButton({
|
||||
endpoint,
|
||||
|
|
@ -17,6 +18,7 @@ export default function SubmitButton({
|
|||
|
||||
const isTokenProvided = endpointsConfig?.[endpoint]?.userProvide ? !!getToken() : true;
|
||||
const endpointsToHideSetTokens = new Set(['openAI', 'azureOpenAI', 'bingAI']);
|
||||
const localize = useLocalize();
|
||||
|
||||
const clickHandler = (e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -50,7 +52,7 @@ export default function SubmitButton({
|
|||
<div className="flex items-center justify-center rounded-md text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||
<div className="m-0 mr-0 flex items-center justify-center rounded-md p-2 sm:p-2">
|
||||
<Settings className="mr-1 inline-block h-auto w-[18px]" />
|
||||
Set Token First
|
||||
{localize('com_endpoint_config_token_name_placeholder')}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useUpdateMessageMutation } from 'librechat-data-provider';
|
|||
import type { TEditProps } from '~/common';
|
||||
import store from '~/store';
|
||||
import Container from './Container';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const EditMessage = ({
|
||||
text,
|
||||
|
|
@ -18,6 +19,7 @@ const EditMessage = ({
|
|||
const textEditor = useRef<HTMLDivElement | null>(null);
|
||||
const { conversationId, parentMessageId, messageId } = message;
|
||||
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
|
||||
const localize = useLocalize();
|
||||
|
||||
const resubmitMessage = () => {
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
|
|
@ -91,17 +93,17 @@ const EditMessage = ({
|
|||
disabled={isSubmitting}
|
||||
onClick={resubmitMessage}
|
||||
>
|
||||
Save & Submit
|
||||
{localize('com_ui_save')} {'&'} {localize('com_ui_submit')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={updateMessage}
|
||||
>
|
||||
Save
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
<button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
|
||||
Cancel
|
||||
{localize('com_ui_cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from 'react';
|
||||
import type { TConversation, TMessage } from 'librechat-data-provider';
|
||||
import { Clipboard, CheckMark, EditIcon, RegenerateIcon, ContinueIcon } from '~/components/svg';
|
||||
import { useGenerations } from '~/hooks';
|
||||
import { useGenerations, useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
type THoverButtons = {
|
||||
|
|
@ -25,6 +25,7 @@ export default function HoverButtons({
|
|||
regenerate,
|
||||
handleContinue,
|
||||
}: THoverButtons) {
|
||||
const localize = useLocalize();
|
||||
const { endpoint } = conversation ?? {};
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const { hideEditButton, regenerateEnabled, continueSupported } = useGenerations({
|
||||
|
|
@ -57,7 +58,7 @@ export default function HoverButtons({
|
|||
)}
|
||||
onClick={onEdit}
|
||||
type="button"
|
||||
title="edit"
|
||||
title={localize('com_ui_edit')}
|
||||
disabled={hideEditButton}
|
||||
>
|
||||
<EditIcon />
|
||||
|
|
@ -69,7 +70,9 @@ export default function HoverButtons({
|
|||
)}
|
||||
onClick={() => copyToClipboard(setIsCopied)}
|
||||
type="button"
|
||||
title={isCopied ? 'Copied to clipboard' : 'Copy to clipboard'}
|
||||
title={
|
||||
isCopied ? localize('com_ui_copied_to_clipboard') : localize('com_ui_copy_to_clipboard')
|
||||
}
|
||||
>
|
||||
{isCopied ? <CheckMark /> : <Clipboard />}
|
||||
</button>
|
||||
|
|
@ -78,7 +81,7 @@ export default function HoverButtons({
|
|||
className="hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
|
||||
onClick={regenerate}
|
||||
type="button"
|
||||
title="regenerate"
|
||||
title={localize('com_ui_regenerate')}
|
||||
>
|
||||
<RegenerateIcon className="hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400" />
|
||||
</button>
|
||||
|
|
@ -88,7 +91,7 @@ export default function HoverButtons({
|
|||
className="hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible "
|
||||
onClick={handleContinue}
|
||||
type="button"
|
||||
title="continue"
|
||||
title={localize('com_ui_continue')}
|
||||
>
|
||||
<ContinueIcon className="h-4 w-4 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { TPreset } from 'librechat-data-provider';
|
|||
import { Plugin } from '~/components/svg';
|
||||
import EndpointOptionsDialog from '../Endpoints/EndpointOptionsDialog';
|
||||
import { cn, alternateName } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ const MessageHeader = ({ isSearchView = false }) => {
|
|||
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
||||
const conversation = useRecoilValue(store.conversation);
|
||||
const searchQuery = useRecoilValue(store.searchQuery);
|
||||
const localize = useLocalize();
|
||||
|
||||
if (!conversation) {
|
||||
return null;
|
||||
|
|
@ -31,7 +33,7 @@ const MessageHeader = ({ isSearchView = false }) => {
|
|||
beta
|
||||
</span>
|
||||
<span className="px-1">•</span> */}
|
||||
Model: {model}
|
||||
{localize('com_ui_model')}: {model}
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import { CheckIcon } from 'lucide-react';
|
|||
import { DialogButton } from '~/components/ui';
|
||||
import React, { useState, useContext, useEffect, useCallback } from 'react';
|
||||
import { useClearConversationsMutation } from 'librechat-data-provider';
|
||||
import { useRecoilValue, useRecoilState } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import store from '~/store';
|
||||
import { ThemeContext } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import { localize } from '~/localization/Translation';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export const ThemeSelector = ({
|
||||
theme,
|
||||
|
|
@ -16,19 +16,19 @@ export const ThemeSelector = ({
|
|||
theme: string;
|
||||
onChange: (value: string) => void;
|
||||
}) => {
|
||||
const lang = useRecoilValue(store.lang);
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize(lang, 'com_nav_theme')}</div>
|
||||
<div>{localize('com_nav_theme')}</div>
|
||||
<select
|
||||
className="w-24 rounded border border-black/10 bg-transparent text-sm dark:border-white/20 dark:bg-gray-900"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={theme}
|
||||
>
|
||||
<option value="system">{localize(lang, 'com_nav_theme_system')}</option>
|
||||
<option value="dark">{localize(lang, 'com_nav_theme_dark')}</option>
|
||||
<option value="light">{localize(lang, 'com_nav_theme_light')}</option>
|
||||
<option value="system">{localize('com_nav_theme_system')}</option>
|
||||
<option value="dark">{localize('com_nav_theme_dark')}</option>
|
||||
<option value="light">{localize('com_nav_theme_light')}</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -45,11 +45,11 @@ export const ClearChatsButton = ({
|
|||
showText: boolean;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const lang = useRecoilValue(store.lang);
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
{showText && <div>{localize(lang, 'com_nav_clear_all_chats')}</div>}
|
||||
{showText && <div>{localize('com_nav_clear_all_chats')}</div>}
|
||||
<DialogButton
|
||||
id="clearConvosBtn"
|
||||
onClick={onClick}
|
||||
|
|
@ -70,7 +70,7 @@ export const ClearChatsButton = ({
|
|||
id="clearConvosTxt"
|
||||
data-testid="clear-convos-confirm"
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" /> {localize(lang, 'com_nav_confirm_clear')}
|
||||
<CheckIcon className="h-5 w-5" /> {localize('com_nav_confirm_clear')}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
|
|
@ -78,7 +78,7 @@ export const ClearChatsButton = ({
|
|||
id="clearConvosTxt"
|
||||
data-testid="clear-convos-initial"
|
||||
>
|
||||
{localize(lang, 'com_nav_clear')}
|
||||
{localize('com_ui_clear')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -95,25 +95,25 @@ export const LangSelector = ({
|
|||
langcode: string;
|
||||
onChange: (value: string) => void;
|
||||
}) => {
|
||||
const lang = useRecoilValue(store.lang);
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize(lang, 'com_nav_language')}</div>
|
||||
<div>{localize('com_nav_language')}</div>
|
||||
<select
|
||||
className="w-24 rounded border border-black/10 bg-transparent text-sm dark:border-white/20 dark:bg-gray-900"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={langcode}
|
||||
>
|
||||
<option value="en">{localize(lang, 'com_nav_lang_english')}</option>
|
||||
<option value="cn">{localize(lang, 'com_nav_lang_chinese')}</option>
|
||||
<option value="de">{localize(lang, 'com_nav_lang_german')}</option>
|
||||
<option value="es">{localize(lang, 'com_nav_lang_spanish')}</option>
|
||||
<option value="fr">{localize(lang, 'com_nav_lang_french')}</option>
|
||||
<option value="it">{localize(lang, 'com_nav_lang_italian')}</option>
|
||||
<option value="pl">{localize(lang, 'com_nav_lang_polish')}</option>
|
||||
<option value="br">{localize(lang, 'com_nav_lang_brazilian_portuguese')}</option>
|
||||
<option value="ru">{localize(lang, 'com_nav_lang_russian')}</option>
|
||||
<option value="en">{localize('com_nav_lang_english')}</option>
|
||||
<option value="cn">{localize('com_nav_lang_chinese')}</option>
|
||||
<option value="de">{localize('com_nav_lang_german')}</option>
|
||||
<option value="es">{localize('com_nav_lang_spanish')}</option>
|
||||
<option value="fr">{localize('com_nav_lang_french')}</option>
|
||||
<option value="it">{localize('com_nav_lang_italian')}</option>
|
||||
<option value="pl">{localize('com_nav_lang_polish')}</option>
|
||||
<option value="br">{localize('com_nav_lang_brazilian_portuguese')}</option>
|
||||
<option value="ru">{localize('com_nav_lang_russian')}</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { render, fireEvent } from '@testing-library/react';
|
|||
import '@testing-library/jest-dom/extend-expect';
|
||||
import DialogTemplate from './DialogTemplate';
|
||||
import { Dialog } from '@radix-ui/react-dialog';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
describe('DialogTemplate', () => {
|
||||
let mockSelectHandler;
|
||||
|
|
@ -14,21 +15,24 @@ describe('DialogTemplate', () => {
|
|||
|
||||
it('renders correctly with all props', () => {
|
||||
const { getByText } = render(
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={() => {
|
||||
return;
|
||||
}}
|
||||
>
|
||||
<DialogTemplate
|
||||
title="Test Dialog"
|
||||
description="Test Description"
|
||||
main={<div>Main Content</div>}
|
||||
buttons={<button>Button</button>}
|
||||
leftButtons={<button>Left Button</button>}
|
||||
selection={{ selectHandler: mockSelectHandler, selectText: 'Select' }}
|
||||
/>
|
||||
</Dialog>,
|
||||
<RecoilRoot>
|
||||
<Dialog
|
||||
open
|
||||
data-testid="test-dialog"
|
||||
onOpenChange={() => {
|
||||
return;
|
||||
}}
|
||||
>
|
||||
<DialogTemplate
|
||||
title="Test Dialog"
|
||||
description="Test Description"
|
||||
main={<div>Main Content</div>}
|
||||
buttons={<button>Button</button>}
|
||||
leftButtons={<button>Left Button</button>}
|
||||
selection={{ selectHandler: mockSelectHandler, selectText: 'Select' }}
|
||||
/>
|
||||
</Dialog>
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByText('Test Dialog')).toBeInTheDocument();
|
||||
|
|
@ -41,39 +45,41 @@ describe('DialogTemplate', () => {
|
|||
});
|
||||
|
||||
it('renders correctly without optional props', () => {
|
||||
const { getByText, queryByText } = render(
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={() => {
|
||||
return;
|
||||
}}
|
||||
>
|
||||
<DialogTemplate title="Test Dialog" />
|
||||
</Dialog>,
|
||||
const { queryByText } = render(
|
||||
<RecoilRoot>
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={() => {
|
||||
return;
|
||||
}}
|
||||
></Dialog>
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByText('Test Dialog')).toBeInTheDocument();
|
||||
expect(queryByText('Test Dialog')).toBeNull();
|
||||
expect(queryByText('Test Description')).not.toBeInTheDocument();
|
||||
expect(queryByText('Main Content')).not.toBeInTheDocument();
|
||||
expect(queryByText('Button')).not.toBeInTheDocument();
|
||||
expect(queryByText('Left Button')).not.toBeInTheDocument();
|
||||
expect(getByText('Cancel')).toBeInTheDocument();
|
||||
expect(queryByText('Cancel')).not.toBeInTheDocument();
|
||||
expect(queryByText('Select')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls selectHandler when the select button is clicked', () => {
|
||||
const { getByText } = render(
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={() => {
|
||||
return;
|
||||
}}
|
||||
>
|
||||
<DialogTemplate
|
||||
title="Test Dialog"
|
||||
selection={{ selectHandler: mockSelectHandler, selectText: 'Select' }}
|
||||
/>
|
||||
</Dialog>,
|
||||
<RecoilRoot>
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={() => {
|
||||
return;
|
||||
}}
|
||||
>
|
||||
<DialogTemplate
|
||||
title="Test Dialog"
|
||||
selection={{ selectHandler: mockSelectHandler, selectText: 'Select' }}
|
||||
/>
|
||||
</Dialog>
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
fireEvent.click(getByText('Select'));
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
DialogTitle,
|
||||
} from './';
|
||||
import { cn } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type SelectionProps = {
|
||||
selectHandler?: () => void;
|
||||
|
|
@ -27,9 +28,11 @@ type DialogTemplateProps = {
|
|||
};
|
||||
|
||||
const DialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivElement>) => {
|
||||
const localize = useLocalize();
|
||||
const { title, description, main, buttons, leftButtons, selection, className, headerClassName } =
|
||||
props;
|
||||
const { selectHandler, selectClasses, selectText } = selection || {};
|
||||
const Cancel = localize('com_ui_cancel');
|
||||
|
||||
const defaultSelect =
|
||||
'bg-gray-900 text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900';
|
||||
|
|
@ -49,7 +52,7 @@ const DialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivE
|
|||
<DialogFooter>
|
||||
<div>{leftButtons ? leftButtons : null}</div>
|
||||
<div className="flex h-auto gap-2">
|
||||
<DialogClose className="dark:hover:gray-400 border-gray-700">Cancel</DialogClose>
|
||||
<DialogClose className="dark:hover:gray-400 border-gray-700">{Cancel}</DialogClose>
|
||||
{buttons ? buttons : null}
|
||||
{selection ? (
|
||||
<DialogClose
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import CheckMark from '../svg/CheckMark';
|
||||
import { Listbox, Transition } from '@headlessui/react';
|
||||
import { cn } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type SelectDropDownProps = {
|
||||
id?: string;
|
||||
|
|
@ -18,7 +19,7 @@ type SelectDropDownProps = {
|
|||
};
|
||||
|
||||
function SelectDropDown({
|
||||
title = 'Model',
|
||||
title,
|
||||
value,
|
||||
disabled,
|
||||
setValue,
|
||||
|
|
@ -29,10 +30,16 @@ function SelectDropDown({
|
|||
subContainerClassName,
|
||||
className,
|
||||
}: SelectDropDownProps) {
|
||||
const localize = useLocalize();
|
||||
const transitionProps = { className: 'top-full mt-3' };
|
||||
if (showAbove) {
|
||||
transitionProps.className = 'bottom-full mb-3';
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
title = localize('com_ui_model');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center justify-center gap-2', containerClassName ?? '')}>
|
||||
<div className={cn('relative w-full', subContainerClassName ?? '')}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue