diff --git a/web/src/app.tsx b/web/src/app.tsx index 746b64995555e0401c5d62b4fb8e0e952fd3dd57..e33201a9408dd94b22bd6d68cb433ef5bfa7d583 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,7 +1,26 @@ -import { App, ConfigProvider } from 'antd'; -import { ReactNode } from 'react'; +import i18next from '@/locales/config'; +import { App, ConfigProvider, ConfigProviderProps } from 'antd'; +import enUS from 'antd/locale/en_US'; +import zhCN from 'antd/locale/zh_CN'; +import React, { ReactNode, useEffect, useState } from 'react'; +import storage from './utils/authorizationUtil'; + +type Locale = ConfigProviderProps['locale']; + +const RootProvider = ({ children }: React.PropsWithChildren) => { + const getLocale = (lng: string) => (lng === 'zh' ? zhCN : enUS); + + const [locale, setLocal] = useState<Locale>(getLocale(storage.getLanguage())); + + i18next.on('languageChanged', function (lng: string) { + storage.setLanguage(lng); + setLocal(getLocale(lng)); + }); + + useEffect(() => { + i18next.changeLanguage(storage.getLanguage()); + }, [locale]); -export function rootContainer(container: ReactNode) { return ( <ConfigProvider theme={{ @@ -9,8 +28,13 @@ export function rootContainer(container: ReactNode) { fontFamily: 'Inter', }, }} + locale={locale} > - <App> {container}</App> + <App> {children}</App> </ConfigProvider> ); +}; + +export function rootContainer(container: ReactNode) { + return <RootProvider>{container}</RootProvider>; } diff --git a/web/src/components/chunk-method-modal/index.tsx b/web/src/components/chunk-method-modal/index.tsx index f4ecbd958d5b2b75fec3ee4f293dfd13e72bda96..7f592b5462e5ad14cefa196a9bcb99d7b96eab0b 100644 --- a/web/src/components/chunk-method-modal/index.tsx +++ b/web/src/components/chunk-method-modal/index.tsx @@ -134,12 +134,8 @@ const ChunkMethodModal: React.FC<IProps> = ({ {showPages && ( <> <Space> - <p>Page Ranges:</p> - <Tooltip - title={ - 'page ranges: Define the page ranges that need to be parsed. The pages that not included in these ranges will be ignored.' - } - > + <p>{t('pageRanges')}:</p> + <Tooltip title={t('pageRangesTip')}> <QuestionCircleOutlined className={styles.questionIcon} ></QuestionCircleOutlined> @@ -163,7 +159,7 @@ const ChunkMethodModal: React.FC<IProps> = ({ rules={[ { required: true, - message: 'Missing start page number', + message: t('fromMessage'), }, ({ getFieldValue }) => ({ validator(_, value) { @@ -175,16 +171,14 @@ const ChunkMethodModal: React.FC<IProps> = ({ return Promise.resolve(); } return Promise.reject( - new Error( - 'The current value must be greater than the previous to!', - ), + new Error(t('greaterThanPrevious')), ); }, }), ]} > <InputNumber - placeholder="from" + placeholder={t('fromPlaceholder')} min={0} precision={0} className={styles.pageInputNumber} @@ -197,7 +191,7 @@ const ChunkMethodModal: React.FC<IProps> = ({ rules={[ { required: true, - message: 'Missing end page number(excluded)', + message: t('toMessage'), }, ({ getFieldValue }) => ({ validator(_, value) { @@ -208,16 +202,14 @@ const ChunkMethodModal: React.FC<IProps> = ({ return Promise.resolve(); } return Promise.reject( - new Error( - 'The current value must be greater than to!', - ), + new Error(t('greaterThan')), ); }, }), ]} > <InputNumber - placeholder="to" + placeholder={t('toPlaceholder')} min={0} precision={0} className={styles.pageInputNumber} @@ -235,7 +227,7 @@ const ChunkMethodModal: React.FC<IProps> = ({ block icon={<PlusOutlined />} > - Add page + {t('addPage')} </Button> </Form.Item> </> @@ -246,12 +238,10 @@ const ChunkMethodModal: React.FC<IProps> = ({ {showOne && ( <Form.Item name={['parser_config', 'layout_recognize']} - label="Layout recognize" + label={t('layoutRecognize')} initialValue={true} valuePropName="checked" - tooltip={ - 'Use visual models for layout analysis to better identify document structure, find where the titles, text blocks, images, and tables are. Without this feature, only the plain text of the PDF can be obtained.' - } + tooltip={t('layoutRecognizeTip')} > <Switch /> </Form.Item> @@ -265,14 +255,13 @@ const ChunkMethodModal: React.FC<IProps> = ({ getFieldValue(['parser_config', 'layout_recognize']) && ( <Form.Item name={['parser_config', 'task_page_size']} - label="Task page size" - tooltip={`If using layout recognize, the PDF file will be split into groups of successive. Layout analysis will be performed parallelly between groups to increase the processing speed. - The 'Task page size' determines the size of groups. The larger the page size is, the lower the chance of splitting continuous text between pages into different chunks.`} + label={t('taskPageSize')} + tooltip={t('taskPageSizeTip')} initialValue={12} rules={[ { required: true, - message: 'Please input your task page size!', + message: t('taskPageSizeMessage'), }, ]} > diff --git a/web/src/components/max-token-number.tsx b/web/src/components/max-token-number.tsx index 43618be4fcfa8d6aa5bb6182aaaf3609890e3fcf..9913b035bc0c02af02b9c495c02df45b82041d25 100644 --- a/web/src/components/max-token-number.tsx +++ b/web/src/components/max-token-number.tsx @@ -1,18 +1,18 @@ +import { useTranslate } from '@/hooks/commonHooks'; import { Flex, Form, InputNumber, Slider } from 'antd'; const MaxTokenNumber = () => { + const { t } = useTranslate('knowledgeConfiguration'); + return ( - <Form.Item - label="Chunk token number" - tooltip="It determine the token number of a chunk approximately." - > + <Form.Item label={t('chunkTokenNumber')} tooltip={t('chunkTokenNumberTip')}> <Flex gap={20} align="center"> <Flex flex={1}> <Form.Item name={['parser_config', 'chunk_token_num']} noStyle initialValue={128} - rules={[{ required: true, message: 'Province is required' }]} + rules={[{ required: true, message: t('chunkTokenNumberMessage') }]} > <Slider max={2048} style={{ width: '100%' }} /> </Form.Item> @@ -20,7 +20,7 @@ const MaxTokenNumber = () => { <Form.Item name={['parser_config', 'chunk_token_num']} noStyle - rules={[{ required: true, message: 'Street is required' }]} + rules={[{ required: true, message: t('chunkTokenNumberMessage') }]} > <InputNumber max={2048} min={0} /> </Form.Item> diff --git a/web/src/components/rename-modal/index.tsx b/web/src/components/rename-modal/index.tsx index 9802ff88f9cba5372c779dd2162de3fe33c3be36..afe5b7f67dee8da5c00b82cbccc6bba66818cfc0 100644 --- a/web/src/components/rename-modal/index.tsx +++ b/web/src/components/rename-modal/index.tsx @@ -1,3 +1,4 @@ +import { useTranslate } from '@/hooks/commonHooks'; import { Form, Input, Modal } from 'antd'; import { useEffect } from 'react'; import { IModalManagerChildrenProps } from '../modal-manager'; @@ -17,6 +18,7 @@ const RenameModal = ({ onOk, }: IProps) => { const [form] = Form.useForm(); + const { t } = useTranslate('common'); type FieldType = { name?: string; @@ -48,7 +50,7 @@ const RenameModal = ({ return ( <Modal - title="Rename" + title={t('rename')} open={visible} onOk={handleOk} onCancel={handleCancel} @@ -66,9 +68,9 @@ const RenameModal = ({ form={form} > <Form.Item<FieldType> - label="Name" + label={t('name')} name="name" - rules={[{ required: true, message: 'Please input name!' }]} + rules={[{ required: true, message: t('namePlaceholder') }]} > <Input /> </Form.Item> diff --git a/web/src/global.ts b/web/src/global.ts deleted file mode 100644 index d025e4a3373ad39de74782143886e41efab0405d..0000000000000000000000000000000000000000 --- a/web/src/global.ts +++ /dev/null @@ -1 +0,0 @@ -import '@/locales/config'; diff --git a/web/src/hooks/commonHooks.tsx b/web/src/hooks/commonHooks.tsx index 4125cd7cdb8e0691ca86fed0e8e647844727002f..03a16509b24ed89f1bbf8671fa9d19026ce0a70f 100644 --- a/web/src/hooks/commonHooks.tsx +++ b/web/src/hooks/commonHooks.tsx @@ -119,3 +119,7 @@ export const useShowDeleteConfirm = () => { export const useTranslate = (keyPrefix: string) => { return useTranslation('translation', { keyPrefix }); }; + +export const useCommonTranslation = () => { + return useTranslation('translation', { keyPrefix: 'common' }); +}; diff --git a/web/src/hooks/logicHooks.ts b/web/src/hooks/logicHooks.ts index 42dea0ffd2b19ab42123259012cabf23d92149b5..23833db99845b67709d704c27bff3fbe720e64ff 100644 --- a/web/src/hooks/logicHooks.ts +++ b/web/src/hooks/logicHooks.ts @@ -1,9 +1,11 @@ import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; import { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useSetModalState } from './commonHooks'; import { useSetDocumentParser } from './documentHooks'; import { useOneNamespaceEffectsLoading } from './storeHooks'; +import { useSaveSetting } from './userSettingHook'; export const useChangeDocumentParser = (documentId: string) => { const setDocumentParser = useSetDocumentParser(); @@ -45,3 +47,15 @@ export const useSetSelectedRecord = <T = IKnowledgeFile>() => { return { currentRecord, setRecord }; }; + +export const useChangeLanguage = () => { + const { i18n } = useTranslation(); + const saveSetting = useSaveSetting(); + + const changeLanguage = (lng: string) => { + i18n.changeLanguage(lng === 'Chinese' ? 'zh' : 'en'); + saveSetting({ language: lng }); + }; + + return changeLanguage; +}; diff --git a/web/src/hooks/userSettingHook.ts b/web/src/hooks/userSettingHook.ts index c6d5065a7592b3c37bf7b0a23be8b65fa2890346..79f138121d03f8943af22de0517a4f77190911f3 100644 --- a/web/src/hooks/userSettingHook.ts +++ b/web/src/hooks/userSettingHook.ts @@ -84,7 +84,7 @@ export const useSaveSetting = () => { const dispatch = useDispatch(); const saveSetting = useCallback( - (userInfo: { new_password: string } | IUserInfo): number => { + (userInfo: { new_password: string } | Partial<IUserInfo>): number => { return dispatch<any>({ type: 'settingModel/setting', payload: userInfo }); }, [dispatch], diff --git a/web/src/layouts/components/right-toolbar/index.tsx b/web/src/layouts/components/right-toolbar/index.tsx index e5df898f43f6a1c53967a5aa5f5d853e9239d653..faca5c903f3e2db628513af8ee8b6cb661eab9c7 100644 --- a/web/src/layouts/components/right-toolbar/index.tsx +++ b/web/src/layouts/components/right-toolbar/index.tsx @@ -1,11 +1,19 @@ +import { ReactComponent as TranslationIcon } from '@/assets/svg/translation.svg'; +import { useTranslate } from '@/hooks/commonHooks'; import { GithubOutlined } from '@ant-design/icons'; -import { Space } from 'antd'; +import { Dropdown, MenuProps, Space } from 'antd'; import React from 'react'; import User from '../user'; + +import { useChangeLanguage } from '@/hooks/logicHooks'; import styled from './index.less'; -const Circle = ({ children }: React.PropsWithChildren) => { - return <div className={styled.circle}>{children}</div>; +const Circle = ({ children, ...restProps }: React.PropsWithChildren) => { + return ( + <div {...restProps} className={styled.circle}> + {children} + </div> + ); }; const handleGithubCLick = () => { @@ -13,17 +21,38 @@ const handleGithubCLick = () => { }; const RightToolBar = () => { + const { t } = useTranslate('common'); + const changeLanguage = useChangeLanguage(); + + const handleItemClick: MenuProps['onClick'] = ({ key }) => { + changeLanguage(key); + }; + + const items: MenuProps['items'] = [ + { + key: 'English', + label: <span>{t('english')}</span>, + }, + { type: 'divider' }, + { + key: 'Chinese', + label: <span>{t('chinese')}</span>, + }, + ]; + return ( <div className={styled.toolbarWrapper}> <Space wrap size={16}> <Circle> <GithubOutlined onClick={handleGithubCLick} /> </Circle> + <Dropdown menu={{ items, onClick: handleItemClick }} placement="bottom"> + <Circle> + <TranslationIcon /> + </Circle> + </Dropdown> {/* <Circle> - <TranslationIcon /> - </Circle> - <Circle> - <MoonIcon /> + <MonIcon /> </Circle> */} <User></User> </Space> diff --git a/web/src/locales/config.ts b/web/src/locales/config.ts index 311f5d200f049bd144e5cceed229acd0da3f2777..17b7a7f2b20cdbcbf113f44e26d393fea28b8010 100644 --- a/web/src/locales/config.ts +++ b/web/src/locales/config.ts @@ -1,8 +1,8 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -import translation_en from './en.json'; -import translation_zh from './zh.json'; +import translation_en from './en'; +import translation_zh from './zh'; const resources = { en: translation_en, diff --git a/web/src/locales/en.json b/web/src/locales/en.json deleted file mode 100644 index 027d7b7ec53137f064fce4bb952840aec8ae88cb..0000000000000000000000000000000000000000 --- a/web/src/locales/en.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "translation": { - "common": { - "delete": "Delete", - "deleteModalTitle": "Are you sure delete this item?", - "ok": "Yes", - "cancel": "No", - "total": "Total", - "rename": "Rename", - "name": "Name", - "namePlaceholder": "Please input name" - }, - "login": { - "login": "Sign in", - "signUp": "Sign up", - "loginDescription": "We’re so excited to see you again!", - "registerDescription": "Glad to have you on board!", - "emailLabel": "Email", - "emailPlaceholder": "Please input email", - "passwordLabel": "Password", - "passwordPlaceholder": "Please input password", - "rememberMe": "Remember me", - "signInTip": "Don’t have an account?", - "signUpTip": "Already have an account?", - "nicknameLabel": "Nickname", - "nicknamePlaceholder": "Please input nickname", - "register": "Create an account", - "continue": "Continue" - }, - "header": { - "knowledgeBase": "Knowledge Base", - "chat": "Chat", - "register": "Register", - "signin": "Sign in", - "home": "Home", - "setting": "用ć·č®ľç˝®", - "logout": "登出" - }, - "knowledgeList": { - "welcome": "Welcome back", - "description": "Which database are we going to use today?", - "createKnowledgeBase": "Create knowledge base", - "name": "Name", - "namePlaceholder": "Please input name!", - "doc": "Docs" - }, - "knowledgeDetails": { - "dataset": "Dataset", - "testing": "Retrieval testing", - "configuration": "Configuration", - "name": "Name", - "namePlaceholder": "Please input name!", - "doc": "Docs", - "datasetDescription": "Hey, don't forget to adjust the chunk after adding the dataset! đź‰", - "addFile": "Add file", - "searchFiles": "Search your files", - "localFiles": "Local files", - "emptyFiles": "Create empty file", - "chunkNumber": "Chunk Number", - "uploadDate": "Upload Date", - "chunkMethod": "Chunk Method", - "enabled": "Enabled", - "action": "Action", - "parsingStatus": "Parsing Status", - "processBeginAt": "Process Begin At", - "processDuration": "Process Duration", - "progressMsg": "Progress Msg", - "testingDescription": "Final step! After success, leave the rest to Infiniflow AI.", - "topK": "Top K", - "topKTip": "For the computaion cost, not all the retrieved chunk will be computed vector cosine similarity with query. The bigger the 'Top K' is, the higher the recall rate is, the slower the retrieval speed is.", - "similarityThreshold": "Similarity threshold", - "similarityThresholdTip": "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. If the similarity between query and chunk is less than this threshold, the chunk will be filtered out.", - "vectorSimilarityWeight": "Vector similarity weight", - "vectorSimilarityWeightTip": "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. The sum of both weights is 1.0.", - "testText": "Test text", - "testTextPlaceholder": "Please input your question!", - "testingLabel": "Testing", - "similarity": "Hybrid Similarity", - "termSimilarity": "Term Similarity", - "vectorSimilarity": "Vector Similarity", - "hits": "Hits", - "view": "View", - "filesSelected": "Files Selected" - }, - "knowledgeConfiguration": { - "titleDescription": "Update your knowledge base details especially parsing method here.", - "name": "Knowledge base name", - "photo": "Knowledge base photo", - "description": "Description", - "language": "Language", - "languageMessage": "Please input your language!", - "languagePlaceholder": "Please input your language!", - "permissions": "Permissions", - "embeddingModel": "Embedding model", - "chunkTokenNumber": "Chunk token number", - "embeddingModelTip": "The embedding model used to embedding chunks. It's unchangable once the knowledgebase has chunks. You need to delete all the chunks if you want to change it.", - "permissionsTip": "If the permission is 'Team', all the team member can manipulate the knowledgebase.", - "chunkTokenNumberTip": "It determine the token number of a chunk approximately.", - "chunkMethodTip": "The instruction is at right.", - "upload": "Upload", - "english": "English", - "chinese": "Chinese", - "embeddingModelPlaceholder": "Please select a embedding model", - "chunkMethodPlaceholder": "Please select a chunk method", - "save": "Save", - "me": "Only me", - "team": "Team", - "cancel": "Cancel" - }, - "footer": { - "detail": "All rights reserved @ React" - }, - "layout": { - "file": "file", - "knowledge": "knowledge", - "chat": "chat" - }, - "setting": { - "btn": "en" - } - } -} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts new file mode 100644 index 0000000000000000000000000000000000000000..487b5c313b06ff1744c8d9e6424421c7c66c0632 --- /dev/null +++ b/web/src/locales/en.ts @@ -0,0 +1,433 @@ +export default { + translation: { + common: { + delete: 'Delete', + deleteModalTitle: 'Are you sure delete this item?', + ok: 'Yes', + cancel: 'No', + total: 'Total', + rename: 'Rename', + name: 'Name', + save: 'Save', + namePlaceholder: 'Please input name', + next: 'Next', + create: 'Create', + edit: 'Edit', + upload: 'Upload', + english: 'English', + chinese: 'Chinese', + language: 'Language', + languageMessage: 'Please input your language!', + languagePlaceholder: 'select your language', + }, + login: { + login: 'Sign in', + signUp: 'Sign up', + loginDescription: 'We’re so excited to see you again!', + registerDescription: 'Glad to have you on board!', + emailLabel: 'Email', + emailPlaceholder: 'Please input email', + passwordLabel: 'Password', + passwordPlaceholder: 'Please input password', + rememberMe: 'Remember me', + signInTip: 'Don’t have an account?', + signUpTip: 'Already have an account?', + nicknameLabel: 'Nickname', + nicknamePlaceholder: 'Please input nickname', + register: 'Create an account', + continue: 'Continue', + title: 'Start building your smart assistants.', + description: + 'Sign up for free to explore top RAG technology. Create knowledge bases and AIs to empower your business.', + review: 'from 500+ reviews', + }, + header: { + knowledgeBase: 'Knowledge Base', + chat: 'Chat', + register: 'Register', + signin: 'Sign in', + home: 'Home', + setting: '用ć·č®ľç˝®', + logout: '登出', + }, + knowledgeList: { + welcome: 'Welcome back', + description: 'Which knowledge base are we going to use today?', + createKnowledgeBase: 'Create knowledge base', + name: 'Name', + namePlaceholder: 'Please input name!', + doc: 'Docs', + }, + knowledgeDetails: { + dataset: 'Dataset', + testing: 'Retrieval testing', + files: 'files', + configuration: 'Configuration', + name: 'Name', + namePlaceholder: 'Please input name!', + doc: 'Docs', + datasetDescription: + "Hey, don't forget to adjust the chunk after adding the dataset! đź‰", + addFile: 'Add file', + searchFiles: 'Search your files', + localFiles: 'Local files', + emptyFiles: 'Create empty file', + chunkNumber: 'Chunk Number', + uploadDate: 'Upload Date', + chunkMethod: 'Chunk Method', + enabled: 'Enabled', + action: 'Action', + parsingStatus: 'Parsing Status', + processBeginAt: 'Process Begin At', + processDuration: 'Process Duration', + progressMsg: 'Progress Msg', + testingDescription: + 'Final step! After success, leave the rest to Infiniflow AI.', + topK: 'Top K', + topKTip: + "For the computaion cost, not all the retrieved chunk will be computed vector cosine similarity with query. The bigger the 'Top K' is, the higher the recall rate is, the slower the retrieval speed is.", + similarityThreshold: 'Similarity threshold', + similarityThresholdTip: + "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. If the similarity between query and chunk is less than this threshold, the chunk will be filtered out.", + vectorSimilarityWeight: 'Vector similarity weight', + vectorSimilarityWeightTip: + "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. The sum of both weights is 1.0.", + testText: 'Test text', + testTextPlaceholder: 'Please input your question!', + testingLabel: 'Testing', + similarity: 'Hybrid Similarity', + termSimilarity: 'Term Similarity', + vectorSimilarity: 'Vector Similarity', + hits: 'Hits', + view: 'View', + filesSelected: 'Files Selected', + upload: 'Upload', + runningStatus0: 'UNSTART', + runningStatus1: 'Parsing', + runningStatus2: 'CANCEL', + runningStatus3: 'SUCCESS', + runningStatus4: 'FAIL', + pageRanges: 'Page Ranges', + pageRangesTip: + 'page ranges: Define the page ranges that need to be parsed. The pages that not included in these ranges will be ignored.', + fromPlaceholder: 'from', + fromMessage: 'Missing start page number', + toPlaceholder: 'to', + toMessage: 'Missing end page number(excluded)', + layoutRecognize: 'Layout recognize', + layoutRecognizeTip: + 'Use visual models for layout analysis to better identify document structure, find where the titles, text blocks, images, and tables are. Without this feature, only the plain text of the PDF can be obtained.', + taskPageSize: 'Task page size', + taskPageSizeMessage: 'Please input your task page size!', + taskPageSizeTip: `If using layout recognize, the PDF file will be split into groups of successive. Layout analysis will be performed parallelly between groups to increase the processing speed. The 'Task page size' determines the size of groups. The larger the page size is, the lower the chance of splitting continuous text between pages into different chunks.`, + addPage: 'Add page', + greaterThan: 'The current value must be greater than to!', + greaterThanPrevious: + 'The current value must be greater than the previous to!', + selectFiles: 'Select files', + changeSpecificCategory: 'Change specific category', + uploadTitle: 'Click or drag file to this area to upload', + uploadDescription: + 'Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.', + }, + knowledgeConfiguration: { + titleDescription: + 'Update your knowledge base details especially parsing method here.', + name: 'Knowledge base name', + photo: 'Knowledge base photo', + description: 'Description', + language: 'Language', + languageMessage: 'Please input your language!', + languagePlaceholder: 'Please input your language!', + permissions: 'Permissions', + embeddingModel: 'Embedding model', + chunkTokenNumber: 'Chunk token number', + chunkTokenNumberMessage: 'Chunk token number is required', + embeddingModelTip: + "The embedding model used to embedding chunks. It's unchangable once the knowledgebase has chunks. You need to delete all the chunks if you want to change it.", + permissionsTip: + "If the permission is 'Team', all the team member can manipulate the knowledgebase.", + chunkTokenNumberTip: + 'It determine the token number of a chunk approximately.', + chunkMethod: 'Chunk method', + chunkMethodTip: 'The instruction is at right.', + upload: 'Upload', + english: 'English', + chinese: 'Chinese', + embeddingModelPlaceholder: 'Please select a embedding model', + chunkMethodPlaceholder: 'Please select a chunk method', + save: 'Save', + me: 'Only me', + team: 'Team', + cancel: 'Cancel', + methodTitle: 'Chunking Method Description', + methodExamples: 'Examples', + methodExamplesDescription: + 'This visual guides is in order to make understanding easier for you.', + dialogueExamplesTitle: 'Dialogue Examples', + methodEmpty: + 'This will display a visual explanation of the knowledge base categories', + book: `<p>Supported file formats are <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p> + Since a book is long and not all the parts are useful, if it's a PDF, + please setup the <i>page ranges</i> for every book in order eliminate negative effects and save computing time for analyzing.</p>`, + laws: `<p>Supported file formats are <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p> + Legal documents have a very rigorous writing format. We use text feature to detect split point. + </p><p> + The chunk granularity is consistent with 'ARTICLE', and all the upper level text will be included in the chunk. + </p>`, + manual: `<p>Only <b>PDF</b> is supported.</p><p> + We assume manual has hierarchical section structure. We use the lowest section titles as pivots to slice documents. + So, the figures and tables in the same section will not be sliced apart, and chunk size might be large. + </p>`, + naive: `<p>Supported file formats are <b>DOCX, EXCEL, PPT, IMAGE, PDF, TXT</b>.</p> + <p>This method apply the naive ways to chunk files: </p> + <p> + <li>Successive text will be sliced into pieces using vision detection model.</li> + <li>Next, these successive pieces are merge into chunks whose token number is no more than 'Token number'.</li></p>`, + paper: `<p>Only <b>PDF</b> file is supported.</p><p> + If our model works well, the paper will be sliced by it's sections, like <i>abstract, 1.1, 1.2</i>, etc. </p><p> + The benefit of doing this is that LLM can better summarize the content of relevant sections in the paper, + resulting in more comprehensive answers that help readers better understand the paper. + The downside is that it increases the context of the LLM conversation and adds computational cost, + so during the conversation, you can consider reducing the â€<b>topN</b>’ setting.</p>`, + presentation: `<p>The supported file formats are <b>PDF</b>, <b>PPTX</b>.</p><p> + Every page will be treated as a chunk. And the thumbnail of every page will be stored.</p><p> + <i>All the PPT files you uploaded will be chunked by using this method automatically, setting-up for every PPT file is not necessary.</i></p>`, + qa: `<p><b>EXCEL</b> and <b>CSV/TXT</b> files are supported.</p><p> + If the file is in excel format, there should be 2 columns question and answer without header. + And question column is ahead of answer column. + And it's O.K if it has multiple sheets as long as the columns are rightly composed.</p><p> + + If it's in csv format, it should be UTF-8 encoded. Use TAB as delimiter to separate question and answer.</p><p> + + <i>All the deformed lines will be ignored. + Every pair of Q&A will be treated as a chunk.</i></p>`, + resume: `<p>The supported file formats are <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>. + </p><p> + The rĂ©sumĂ© comes in a variety of formats, just like a person’s personality, but we often have to organize them into structured data that makes it easy to search. + </p><p> + Instead of chunking the rĂ©sumĂ©, we parse the rĂ©sumĂ© into structured data. As a HR, you can dump all the rĂ©sumĂ© you have, + the you can list all the candidates that match the qualifications just by talk with <i>'RAGFlow'</i>. + </p> + `, + table: `<p><b>EXCEL</b> and <b>CSV/TXT</b> format files are supported.</p><p> + Here're some tips: + <ul> + <li>For csv or txt file, the delimiter between columns is <em><b>TAB</b></em>.</li> + <li>The first line must be column headers.</li> + <li>Column headers must be meaningful terms in order to make our LLM understanding. + It's good to enumerate some synonyms using slash <i>'/'</i> to separate, and even better to + enumerate values using brackets like <i>'gender/sex(male, female)'</i>.<p> + Here are some examples for headers:<ol> + <li>supplier/vendor<b>'TAB'</b>color(yellow, red, brown)<b>'TAB'</b>gender/sex(male, female)<b>'TAB'</b>size(M,L,XL,XXL)</li> + <li>姓ĺŤ/ĺŤĺ—<b>'TAB'</b>电话/手机/微信<b>'TAB'</b>最é«ĺ¦ĺŽ†ďĽé«ä¸ďĽŚčŚé«ďĽŚçˇ•ĺŁ«ďĽŚćś¬ç§‘,博士,ĺťä¸ďĽŚä¸ćŠ€ďĽŚä¸ä¸“,专科,专升本,MPA,MBA,EMBA)</li> + </ol> + </p> + </li> + <li>Every row in table will be treated as a chunk.</li> + </ul>`, + picture: ` + <p>Image files are supported. Video is coming soon.</p><p> + If the picture has text in it, OCR is applied to extract the text as its text description. + </p><p> + If the text extracted by OCR is not enough, visual LLM is used to get the descriptions. + </p>`, + one: ` + <p>Supported file formats are <b>DOCX, EXCEL, PDF, TXT</b>. + </p><p> + For a document, it will be treated as an entire chunk, no split at all. + </p><p> + If you want to summarize something that needs all the context of an article and the selected LLM's context length covers the document length, you can try this method. + </p>`, + }, + chunk: { + chunk: 'Chunk', + bulk: 'Bulk', + selectAll: 'Select All', + enabledSelected: 'Enable Selected', + disabledSelected: 'Disable Selected', + deleteSelected: 'Delete Selected', + search: 'Search', + all: 'All', + enabled: 'Enabled', + disabled: 'Disabled', + keyword: 'Keyword', + function: 'Function', + chunkMessage: 'Please input value!', + }, + chat: { + assistantSetting: 'Assistant Setting', + promptEngine: 'Prompt Engine', + modelSetting: 'Model Setting', + chat: 'Chat', + newChat: 'New chat', + send: 'Send', + sendPlaceholder: 'Message Resume Assistant...', + chatConfiguration: 'Chat Configuration', + chatConfigurationDescription: + ' Here, dress up a dedicated assistant for your special knowledge bases! đź’•', + assistantName: 'Assistant name', + namePlaceholder: 'e.g. Resume Jarvis', + assistantAvatar: 'Assistant avatar', + language: 'Language', + emptyResponse: 'Empty response', + emptyResponseTip: `If nothing is retrieved with user's question in the knowledgebase, it will use this as an answer. If you want LLM comes up with its own opinion when nothing is retrieved, leave this blank.`, + setAnOpener: 'Set an opener', + setAnOpenerInitial: `Hi! I'm your assistant, what can I do for you?`, + setAnOpenerTip: 'How do you want to welcome your clients?', + knowledgeBases: 'Knowledgebases', + knowledgeBasesMessage: 'Please select', + knowledgeBasesTip: 'Select knowledgebases associated.', + system: 'System', + systemInitialValue: `You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history. + Here is the knowledge base: + {knowledge} + The above is the knowledge base.`, + systemMessage: 'Please input!', + systemTip: + 'Instructions you need LLM to follow when LLM answers questions, like charactor design, answer length and answer language etc.', + topN: 'Top N', + topNTip: `Not all the chunks whose similarity score is above the 'simialrity threashold' will be feed to LLMs. LLM can only see these 'Top N' chunks.`, + variable: 'Variable', + variableTip: `If you use dialog APIs, the varialbes might help you chat with your clients with different strategies. + The variables are used to fill-in the 'System' part in prompt in order to give LLM a hint. + The 'knowledge' is a very special variable which will be filled-in with the retrieved chunks. + All the variables in 'System' should be curly bracketed.`, + add: 'Add', + key: 'key', + optional: 'Optional', + operation: 'operation', + model: 'Model', + modelTip: 'Large language chat model', + modelMessage: 'Please select!', + freedom: 'Freedom', + improvise: 'Improvise', + precise: 'Precise', + balance: 'Balance', + freedomTip: `'Precise' means the LLM will be conservative and answer your question cautiously. 'Improvise' means the you want LLM talk much and freely. 'Balance' is between cautiously and freely.`, + temperature: 'Temperature', + temperatureMessage: 'Temperature is required', + temperatureTip: + 'This parameter controls the randomness of predictions by the model. A lower temperature makes the model more confident in its responses, while a higher temperature makes it more creative and diverse.', + topP: 'Top P', + topPMessage: 'Top P is required', + topPTip: + 'Also known as “nucleus sampling,” this parameter sets a threshold to select a smaller set of words to sample from. It focuses on the most likely words, cutting off the less probable ones.', + presencePenalty: 'Presence Penalty', + presencePenaltyMessage: 'Presence Penalty is required', + presencePenaltyTip: + 'This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation.', + frequencyPenalty: 'Frequency Penalty', + frequencyPenaltyMessage: 'Frequency Penalty is required', + frequencyPenaltyTip: + 'Similar to the presence penalty, this reduces the model’s tendency to repeat the same words frequently.', + maxTokens: 'Max Tokens', + maxTokensMessage: 'Max Tokens is required', + maxTokensTip: + 'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).', + }, + setting: { + profile: 'Profile', + profileDescription: 'Update your photo and personal details here.', + password: 'Password', + passwordDescription: + 'Please enter your current password to change your password.', + model: 'Model Providers', + modelDescription: 'Manage your account settings and preferences here.', + team: 'Team', + logout: 'Log out', + username: 'Username', + usernameMessage: 'Please input your username!', + photo: 'Your photo', + photoDescription: 'This will be displayed on your profile.', + colorSchema: 'Color schema', + colorSchemaMessage: 'Please select your color schema!', + colorSchemaPlaceholder: 'select your color schema', + bright: 'Bright', + dark: 'Dark', + timezone: 'Timezone', + timezoneMessage: 'Please input your timezone!', + timezonePlaceholder: 'select your timezone', + email: 'Email address', + emailDescription: 'Once registered, E-mail cannot be changed.', + currentPassword: 'Current password', + currentPasswordMessage: 'Please input your password!', + newPassword: 'New password', + newPasswordMessage: 'Please input your password!', + newPasswordDescription: + 'Your new password must be more than 8 characters.', + confirmPassword: 'Confirm new password', + confirmPasswordMessage: 'Please confirm your password!', + confirmPasswordNonMatchMessage: + 'The new password that you entered do not match!', + cancel: 'Cancel', + addedModels: 'Added models', + modelsToBeAdded: 'Models to be added', + addTheModel: 'Add the model', + apiKey: 'API-Key', + apiKeyMessage: 'Please input api key!', + apiKeyTip: + 'The API key can be obtained by registering the corresponding LLM supplier.', + showMoreModels: 'Show more models', + baseUrl: 'Base-Url', + baseUrlTip: + 'If your API key is from OpenAI, just ignore it. Any other intermediate providers will give this base url with the API key.', + modify: 'Modify', + systemModelSettings: 'System Model Settings', + chatModel: 'Chat model', + chatModelTip: + 'The default chat LLM all the newly created knowledgebase will use.', + embeddingModel: 'Embedding model', + embeddingModelTip: + 'The default embedding model all the newly created knowledgebase will use.', + img2txtModel: 'Img2txt model', + img2txtModelTip: + 'The default multi-module model all the newly created knowledgebase will use. It can describe a picture or video.', + sequence2txtModel: 'Img2txt model', + sequence2txtModelTip: + 'The default ASR model all the newly created knowledgebase will use. Use this model to translate voices to corresponding text.', + workspace: 'Workspace', + upgrade: 'Upgrade', + }, + message: { + registered: 'Registered!', + logout: 'logout', + logged: 'logged!', + pleaseSelectChunk: 'Please select chunk!', + modified: 'Modified', + created: 'Created', + deleted: 'Deleted', + renamed: 'Renamed', + operated: 'Operated', + updated: 'Updated', + 200: 'The server successfully returns the requested data.', + 201: 'Create or modify data successfully.', + 202: 'A request has been queued in the background (asynchronous task).', + 204: 'Data deleted successfully.', + 400: 'There was an error in the request issued, and the server did not create or modify data.', + 401: 'The user does not have permissions (wrong token, username, password).', + 403: 'The user is authorized, but access is prohibited.', + 404: 'The request was made for a record that does not exist, and the server did not perform the operation.', + 406: 'The requested format is not available.', + 410: 'The requested resource has been permanently deleted and will not be available again.', + 422: 'When creating an object, a validation error occurred.', + 500: 'A server error occurred, please check the server.', + 502: 'Gateway error.', + 503: 'The service is unavailable and the server is temporarily overloaded or undergoing maintenance.', + 504: 'Gateway timeout.', + requestError: 'Request error', + networkAnomalyDescription: + 'There is an abnormality in your network and you cannot connect to the server.', + networkAnomaly: 'network anomaly', + hint: 'hint', + }, + footer: { + profile: 'All rights reserved @ React', + }, + layout: { + file: 'file', + knowledge: 'knowledge', + chat: 'chat', + }, + }, +}; diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json deleted file mode 100644 index a6fd83502aa23be3062461224673eb774aa0c502..0000000000000000000000000000000000000000 --- a/web/src/locales/zh.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "translation": { - "login": { "login": "登录" }, - "header": { - "register": "注册", - "signin": "登陆", - "home": "首页", - "setting": "user setting", - "logout": "logout" - }, - "footer": { - "detail": "ç‰ćťć‰€ćś‰ @ React" - }, - "layout": { - "file": "文件", - "knowledge": "知识库", - "chat": "čŠĺ¤©" - }, - "setting": { - "btn": "ä¸ć–‡" - } - } -} diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa79895f697bd10e623c3ae9f255c09f78fb2bf3 --- /dev/null +++ b/web/src/locales/zh.ts @@ -0,0 +1,417 @@ +export default { + translation: { + common: { + delete: 'ĺ 除', + deleteModalTitle: '确定ĺ 除ĺ—?', + ok: 'ćŻ', + cancel: 'ĺ¦', + total: '总共', + rename: '重命ĺŤ', + name: 'ĺŤç§°', + save: 'äżťĺ', + namePlaceholder: '请输入ĺŤç§°', + next: '下一ćĄ', + create: 'ĺ›ĺ»ş', + edit: '编辑', + upload: 'ä¸ŠäĽ ', + english: '英文', + chinese: 'ä¸ć–‡', + language: 'čŻč¨€', + languageMessage: '请输入čŻč¨€', + languagePlaceholder: '请选择čŻč¨€', + }, + login: { + login: '登录', + signUp: '注册', + loginDescription: 'ĺľé«ĺ…´ĺ†Ťć¬ˇč§ĺ°ć‚¨ďĽ', + registerDescription: 'ĺľé«ĺ…´ć‚¨ĺŠ ĺ…ĄďĽ', + emailLabel: '邮箱', + emailPlaceholder: '请输入邮箱地址', + passwordLabel: '密ç ', + passwordPlaceholder: '请输入密ç ', + rememberMe: '记住ć‘', + signInTip: '没有ĺ¸ć·ďĽź', + signUpTip: '已经有ĺ¸ć·ďĽź', + nicknameLabel: 'ĺŤç§°', + nicknamePlaceholder: '请输入ĺŤç§°', + register: 'ĺ›ĺ»şč´¦ć·', + continue: '继ç»', + title: '开始构建您的智č˝ĺŠ©ć‰‹', + description: + '免费注册以探索顶级 RAG 技术。 ĺ›ĺ»şçźĄčŻ†ĺş“和人工智č˝ćťĄĺ˘žĺĽşć‚¨çš„业务', + review: '来自 500 多条评论', + }, + header: { + knowledgeBase: '知识库', + chat: 'čŠĺ¤©', + register: '注册', + signin: '登录', + home: '首页', + setting: '用ć·č®ľç˝®', + logout: '登出', + }, + knowledgeList: { + welcome: '欢迎回来', + description: '今天ć‘们č¦ä˝żç”¨ĺ“Şä¸ŞçźĄčŻ†ĺş“?', + createKnowledgeBase: 'ĺ›ĺ»şçźĄčŻ†ĺş“', + name: 'ĺŤç§°', + namePlaceholder: '请输入ĺŤç§°', + doc: '文档', + }, + knowledgeDetails: { + dataset: '数据集', + testing: '检索测试', + configuration: 'é…Ťç˝®', + files: '文件', + name: 'ĺŤç§°', + namePlaceholder: '请输入ĺŤç§°', + doc: '文档', + datasetDescription: 'ĺżďĽŚć·»ĺŠ 数据集ĺŽĺ«ĺżäş†č°ć•´č§Łćžĺť—ďĽ đź‰', + addFile: '新增文件', + searchFiles: 'ćśç´˘ć–‡ä»¶', + localFiles: '本地文件', + emptyFiles: '新建空文件', + chunkNumber: '模块数', + uploadDate: 'ä¸ŠäĽ ć—Ąćśź', + chunkMethod: '解ćžć–ąćł•', + enabled: 'ĺŻç”¨', + action: '动作', + parsingStatus: '解ćžçŠ¶ć€', + processBeginAt: 'ćµç¨‹ĺĽ€ĺ§‹äşŽ', + processDuration: '过程ćŚç»ć—¶é—´', + progressMsg: '进度ć¶ćŻ', + testingDescription: '最ĺŽä¸€ćĄďĽ ć功ĺŽďĽŚĺ‰©ä¸‹çš„就交给Infiniflow AIĺ§ă€‚', + topK: 'Top K', + topKTip: + '对于计算ć本,并非所有检索ĺ°çš„ĺť—é˝äĽšč®ˇç®—与查询的ĺ‘量余弦相似度。 Top K越大,召回率越é«ďĽŚćŁ€ç´˘é€źĺş¦č¶Šć…˘ă€‚', + similarityThreshold: '相似度é值', + similarityThresholdTip: + 'ć‘们使用混ĺ相似度得ĺ†ćťĄčŻ„估两行文本之间的距离。 ĺ®ćŻĺŠ ćťĺ…łé”®čŻŤç›¸äĽĽĺş¦ĺ’Śĺ‘量余弦相似度。 如果查询和块之间的相似度小于ć¤é值,ĺ™čŻĄĺť—将被过滤掉。', + vectorSimilarityWeight: 'ĺ‘量相似度ćťé‡Ť', + vectorSimilarityWeightTip: + 'ć‘们使用混ĺ相似度得ĺ†ćťĄčŻ„估两行文本之间的距离。 ĺ®ćŻĺŠ ćťĺ…łé”®čŻŤç›¸äĽĽĺş¦ĺ’Śĺ‘量余弦相似度。 两个ćťé‡Ťäą‹ĺ’Śä¸ş 1.0。', + testText: '测试文本', + testTextPlaceholder: '请输入您的问é˘ďĽ', + testingLabel: '测试', + similarity: 'ć··ĺ相似度', + termSimilarity: '术čŻç›¸äĽĽĺş¦', + vectorSimilarity: 'ĺ‘量相似度', + hits: 'ĺ‘˝ä¸ć•°', + view: 'çś‹ćł•', + filesSelected: '选定的文件', + upload: 'ä¸ŠäĽ ', + runningStatus0: '未ĺŻĺŠ¨', + runningStatus1: '解ćžä¸', + runningStatus2: '取ć¶', + runningStatus3: 'ć功', + runningStatus4: '失败', + pageRanges: '页ç čŚĺ›´', + pageRangesTip: + '页ç čŚĺ›´ďĽšĺ®šäą‰éś€č¦č§Łćžçš„页面čŚĺ›´ă€‚ 不包ĺ«ĺś¨čż™äş›čŚĺ›´ĺ†…的页面将被忽略。', + fromPlaceholder: '从', + fromMessage: '缺少起始页ç ', + toPlaceholder: 'ĺ°', + toMessage: '缺少结束页ç ďĽä¸ŤĺŚ…ĺ«ďĽ‰', + layoutRecognize: 'ĺ¸ĺ±€čŻ†ĺ«', + layoutRecognizeTip: + '使用视觉模型进行ĺ¸ĺ±€ĺ†ćžďĽŚä»Ąć›´ĺĄ˝ĺś°čŻ†ĺ«ć–‡ćˇŁç»“构,找ĺ°ć ‡é˘ă€ć–‡ćś¬ĺť—ă€ĺ›ľĺŹĺ’Śčˇ¨ć Ľçš„位置。 如果没有ć¤ĺŠźč˝ďĽŚĺ™ĺŹŞč˝čŽ·ĺŹ– PDF 的纯文本。', + taskPageSize: '任务页面大小', + taskPageSizeMessage: '请输入您的任务页面大小ďĽ', + taskPageSizeTip: `如果使用ĺ¸ĺ±€čŻ†ĺ«ďĽŚPDF 文件将被ĺ†ćčżžç»çš„组。 ĺ¸ĺ±€ĺ†ćžĺ°†ĺś¨ç»„之间并行执行,以ćŹé«ĺ¤„ç†é€źĺş¦ă€‚ “任务页面大小”决定组的大小。 页面大小越大,将页面之间的连ç»ć–‡ćś¬ĺ†ĺ‰˛ć不ĺŚĺť—的机会就越低。`, + addPage: '新增页面', + greaterThan: '当前值必须大于起始值ďĽ', + greaterThanPrevious: '当前值必须大于之前的值ďĽ', + selectFiles: '选择文件', + changeSpecificCategory: '更改特定类ĺ«', + uploadTitle: '点击ć–拖拽文件至ć¤ĺŚşĺźźĺŤłĺŹŻä¸ŠäĽ ', + uploadDescription: + '支ćŚĺŤ•ć¬ˇć–ć‰ąé‡Źä¸ŠäĽ ă€‚ 严ç¦ä¸ŠäĽ 公司数据ć–其他违ç¦ć–‡ä»¶ă€‚', + }, + knowledgeConfiguration: { + titleDescription: '在这里更新您的知识库详细信ćŻďĽŚĺ°¤ĺ…¶ćŻč§Łćžć–ąćł•ă€‚', + name: '知识库ĺŤç§°', + photo: '知识库图片', + description: '描述', + language: 'čŻč¨€', + languageMessage: '请输入čŻč¨€', + languagePlaceholder: '请输入čŻč¨€', + permissions: 'ćťé™', + embeddingModel: '嵌入模型', + chunkTokenNumber: '块令牌数', + chunkTokenNumberMessage: '块令牌数ćŻĺż…填项', + embeddingModelTip: + '用于嵌入块的嵌入模型。 一旦知识库有了块,ĺ®ĺ°±ć— 法更改。 ĺ¦‚ćžśä˝ ćłć”ąĺŹĺ®ďĽŚä˝ 需č¦ĺ 除所有的块。', + permissionsTip: '如果ćťé™ćŻâ€śĺ›˘éźâ€ťďĽŚĺ™ć‰€ćś‰ĺ›˘éźćĺ‘é˝ĺŹŻä»Ąć“Ťä˝śçźĄčŻ†ĺş“。', + chunkTokenNumberTip: 'ĺ®ĺ¤§č‡´çˇ®ĺ®šäş†ä¸€ä¸Şĺť—的令牌数量。', + chunkMethod: '解ćžć–ąćł•', + chunkMethodTip: '说ćŽä˝ŤäşŽĺŹłäľ§ă€‚', + upload: 'ä¸ŠäĽ ', + english: '英文', + chinese: 'ä¸ć–‡', + embeddingModelPlaceholder: '请选择嵌入模型', + chunkMethodPlaceholder: '请选择ĺ†ĺť—ć–ąćł•', + save: 'äżťĺ', + me: '只有ć‘', + team: '团éź', + cancel: '取ć¶', + methodTitle: 'ĺ†ĺť—方法说ćŽ', + methodExamples: '示例', + methodExamplesDescription: '这个视觉指南ćŻä¸şäş†č®©ć‚¨ć›´ĺ®ąć“ç†č§Łă€‚', + dialogueExamplesTitle: '对话示例', + methodEmpty: '这将ćľç¤şçźĄčŻ†ĺş“ç±»ĺ«çš„可视化解释', + book: `<p>支ćŚçš„ć–‡ä»¶ć ĽĺĽŹä¸ş<b>DOCX</b>ă€<b>PDF</b>ă€<b>TXT</b>。</p><p> + 由于一本书ĺľé•żďĽŚĺą¶ä¸ŤćŻć‰€ćś‰é¨ĺ†é˝ćś‰ç”¨ďĽŚĺ¦‚ćžśćŻ PDF, + 请为每本书设置<i>页面čŚĺ›´</i>,以ć¶é™¤č´źéť˘ĺ˝±ĺ“Ťĺą¶čŠ‚çśĺ†ćžč®ˇç®—时间。</p>`, + laws: `<p>支ćŚçš„ć–‡ä»¶ć ĽĺĽŹä¸ş<b>DOCX</b>ă€<b>PDF</b>ă€<b>TXT</b>。</p><p> + ćł•ĺľ‹ć–‡ä»¶ćś‰éťžĺ¸¸ä¸Ąć Ľçš„äą¦ĺ†™ć ĽĺĽŹă€‚ ć‘们使用文本特ĺľćťĄćŁ€ćµ‹ĺ†ĺ‰˛ç‚ąă€‚ + </p><p> + chunk的粒度与'ARTICLE'一致,所有上层文本é˝äĽšĺŚ…ĺ«ĺś¨chunkä¸ă€‚ + </p>`, + manual: `<p>仅支ćŚ<b>PDF</b>。</p><p> + ć‘们ĺ‡č®ľć‰‹ĺ†Śĺ…·ćś‰ĺ†ĺ±‚é¨ĺ†ç»“构。 ć‘们使用最低的é¨ĺ†ć ‡é˘ä˝śä¸şĺŻąć–‡ćˇŁčż›čˇŚĺ‡ç‰‡çš„枢轴。 + ĺ› ć¤ďĽŚĺŚä¸€é¨ĺ†ä¸çš„图和表不会被ĺ†ĺ‰˛ďĽŚĺą¶ä¸”块大小可č˝äĽšĺľĺ¤§ă€‚ + </p>`, + naive: `<p>支ćŚçš„ć–‡ä»¶ć ĽĺĽŹä¸ş<b>DOCXă€EXCELă€PPTă€IMAGEă€PDFă€TXT</b>。</p> + <p>ć¤ć–ąćł•ĺ°†ç®€ĺŤ•çš„方法应用于块文件:</p> + <p> + <li>系统将使用视觉检测模型将连ç»ć–‡ćś¬ĺ†ĺ‰˛ć多个片段。</li> + <li>接下来,这些连ç»çš„片段被ĺ并ć令牌数不超过“令牌数”的块。</li></p>`, + paper: `<p>仅支ćŚ<b>PDF</b>文件。</p><p> + 如果ć‘们的模型čżčˇŚč‰ŻĺĄ˝ďĽŚč®şć–‡ĺ°†ćŚ‰ĺ…¶é¨ĺ†čż›čˇŚĺ‡ç‰‡ďĽŚäľ‹ĺ¦‚<i>ć‘č¦ă€1.1ă€1.2</i>ç‰ă€‚</p><p> + čż™ć ·ĺšçš„好处ćŻLLM可以更好的概括论文ä¸ç›¸ĺ…łç« 节的内容, + 产生更全面的ç”ćˇďĽŚĺ¸®ĺŠ©čŻ»č€…更好地ç†č§Łč®şć–‡ă€‚ + 缺点ćŻĺ®ĺ˘žĺŠ 了 LLM 对话的čŚć™Żĺą¶ĺ˘žĺŠ 了计算ć本, + 所以在对话过程ä¸ďĽŚä˝ 可以č€č™‘减少â€<b>topN</b>’的设置。</p>`, + presentation: `<p>支ćŚçš„ć–‡ä»¶ć ĽĺĽŹä¸ş<b>PDF</b>ă€<b>PPTX</b>。</p><p> + 每个页面é˝ĺ°†č˘«č§†ä¸şä¸€ä¸Şĺť—。 并且每个页面的缩略图é˝äĽšč˘«ĺ储。</p><p> + <i>ć‚¨ä¸ŠäĽ çš„ć‰€ćś‰PPT文件é˝äĽšä˝żç”¨ć¤ć–ąćł•č‡ŞĺŠ¨ĺ†ĺť—ďĽŚć— éś€ä¸şćŻŹä¸ŞPPT文件进行设置。</i></p>`, + qa: `支ćŚ<p><b>EXCEL</b>ĺ’Ś<b>CSV/TXT</b>文件。</p><p> + 如果文件ćŻExcelć ĽĺĽŹďĽŚĺş”čŻĄćś‰2ĺ—é—®é˘ĺ’Śç”ćˇďĽŚć˛ˇćś‰ć ‡é˘ă€‚ + é—®é˘ć Źä˝ŤäşŽç”ćˇć Źäą‹ĺ‰Ťă€‚ + 如果有多个工作表也没关系,只č¦ĺ—的组ĺćŁçˇ®ĺŤłĺŹŻă€‚</p><p> + + ĺ¦‚ćžśćŻ csv ć ĽĺĽŹďĽŚĺ™ĺş”采用 UTF-8 编ç 。 使用 TAB 作为ĺ†éš”符来ĺ†éš”é—®é˘ĺ’Śç”ćˇă€‚</p><p> + + <i>所有ĺŹĺ˝˘çš„çşżé˝ĺ°†č˘«ĺż˝ç•Ąă€‚ + 每对问ç”é˝ĺ°†č˘«č§†ä¸şä¸€ä¸Şĺť—。</i></p>`, + resume: `<p>支ćŚçš„ć–‡ä»¶ć ĽĺĽŹä¸ş<b>DOCX</b>ă€<b>PDF</b>ă€<b>TXT</b>。 + </p><p> + ç®€ĺŽ†ćś‰ĺ¤šç§Ťć ĽĺĽŹďĽŚĺ°±ĺŹä¸€ä¸Şäşşçš„ä¸Şć€§ä¸€ć ·ďĽŚä˝†ć‘们经常必须将ĺ®ä»¬ç»„织ć结构化数据,以便于ćśç´˘ă€‚ + </p><p> + ć‘们不ćŻĺ°†ç®€ĺŽ†ĺ†ĺť—,而ćŻĺ°†ç®€ĺŽ†č§Łćžä¸şç»“构化数据。 作为HRďĽŚä˝ ĺŹŻä»Ąć‰”ćŽ‰ć‰€ćś‰çš„ç®€ĺŽ†ďĽŚ + 您只需与<i>'RAGFlow'</i>交č°ĺŤłĺŹŻĺ—出所有符ĺčµ„ć Ľçš„ĺ€™é€‰äşşă€‚ + </p> + `, + table: `支ćŚ<p><b>EXCEL</b>ĺ’Ś<b>CSV/TXT</b>ć ĽĺĽŹć–‡ä»¶ă€‚</p><p> + 以下ćŻä¸€äş›ćŹç¤şďĽš + <ul> + <li>对于 csv ć– txt 文件,ĺ—äą‹é—´çš„ĺ†éš”符为 <em><b>TAB</b></em>。</li> + <li>第一行必须ćŻĺ—ć ‡é˘ă€‚</li> + <li>ĺ—ć ‡é˘ĺż…须ćŻćś‰ć„Źäą‰çš„术čŻďĽŚä»Ąäľżć‘们的法ĺ¦çˇ•ĺŁ«č˝ĺ¤źç†č§Łă€‚ + ĺ—举一些ĺŚäą‰čŻŤć—¶ćś€ĺĄ˝ä˝żç”¨ć–śćť <i>'/'</i>来ĺ†éš”,甚至更好 + 使用方括号枚举值,例如 <i>'gender/sex(male,female)'</i>.<p> + 以下ćŻć ‡é˘çš„一些示例:<ol> + <li>供应商/供货商<b>'TAB'</b>颜色ďĽé»„色ă€çş˘č‰˛ă€ćŁ•č‰˛ďĽ‰<b>'TAB'</b>性ĺ«ďĽç”·ă€ĺĄłďĽ‰<b>'TAB'</ b>ĺ°şç ďĽMă€Lă€XLă€XXL)</li> + <li>姓ĺŤ/ĺŤĺ—<b>'TAB'</b>电话/手机/微信<b>'TAB'</b>最é«ĺ¦ĺŽ†ďĽé«ä¸ďĽŚčŚé«ďĽŚçˇ•ĺŁ«ďĽŚćś¬ç§‘,博士,ĺťä¸ďĽŚä¸ćŠ€ďĽŚä¸ 专,专科,专升本,MPA,MBA,EMBA)</li> + </ol> + </p> + </li> + <li>表ä¸çš„每一行é˝ĺ°†č˘«č§†ä¸şä¸€ä¸Şĺť—。</li> + </ul>`, + picture: ` + <p>支ćŚĺ›ľĺŹć–‡ä»¶ă€‚ 视频即将推出。</p><p> + 如果图片ä¸ćś‰ć–‡ĺ—,ĺ™ĺş”用 OCR ćŹĺŹ–ć–‡ĺ—作为其文ĺ—描述。 + </p><p> + 如果OCRćŹĺŹ–的文本不够,可以使用视觉LLM来获取描述。 + </p>`, + one: ` + <p>支ćŚçš„ć–‡ä»¶ć ĽĺĽŹä¸ş<b>DOCXă€EXCELă€PDFă€TXT</b>。 + </p><p> + 对于一个文档,ĺ®ĺ°†č˘«č§†ä¸şä¸€ä¸Şĺ®Ść•´çš„ĺť—ďĽŚć ąćś¬ä¸ŤäĽšč˘«ĺ†ĺ‰˛ă€‚ + </p><p> + ĺ¦‚ćžśä˝ č¦ć€»ç»“的东西需č¦ä¸€çŻ‡ć–‡ç« çš„ĺ…¨é¨ä¸Šä¸‹ć–‡ďĽŚĺą¶ä¸”所选LLMçš„ä¸Šä¸‹ć–‡é•żĺş¦č¦†ç›–äş†ć–‡ćˇŁé•żĺş¦ďĽŚä˝ ĺŹŻä»Ąĺ°ťčŻ•čż™ç§Ťć–ąćł•ă€‚ + </p>`, + }, + chunk: { + chunk: '解ćžĺť—', + bulk: '批量', + selectAll: '选择所有', + enabledSelected: 'ĺŻç”¨é€‰ĺ®šçš„', + disabledSelected: 'ç¦ç”¨é€‰ĺ®šçš„', + deleteSelected: 'ĺ 除选定的', + search: 'ćśç´˘', + all: '所有', + enabled: 'ĺŻç”¨', + disabled: 'ç¦ç”¨çš„', + keyword: '关键词', + function: '函数', + chunkMessage: '请输入值ďĽ', + }, + chat: { + assistantSetting: '助ç†č®ľç˝®', + promptEngine: 'ćŹç¤şĺĽ•ć“Ž', + modelSetting: '模型设置', + chat: 'čŠĺ¤©', + newChat: '新建čŠĺ¤©', + send: '发é€', + sendPlaceholder: 'ć¶ćŻć¦‚č¦ĺŠ©ć‰‹...', + chatConfiguration: 'čŠĺ¤©é…Ťç˝®', + chatConfigurationDescription: 'ĺś¨čż™é‡ŚďĽŚä¸şä˝ çš„ä¸“ä¸šçźĄčŻ†ĺş“čŁ…ć‰®ä¸“ĺ±žĺŠ©ć‰‹ďĽ đź’•', + assistantName: '助ç†ĺ§“ĺŤ', + namePlaceholder: '例如 贾维斯简历', + assistantAvatar: '助ç†ĺ¤´ĺŹ', + language: 'čŻč¨€', + emptyResponse: '空回复', + emptyResponseTip: `如果在知识库ä¸ć˛ˇćś‰ćŁ€ç´˘ĺ°ç”¨ć·çš„é—®é˘ďĽŚĺ®ĺ°†ä˝żç”¨ĺ®ä˝śä¸şç”ćˇă€‚ 如果您希望 LLM 在未检索ĺ°ä»»ä˝•ĺ†…容时ćŹĺ‡şč‡Şĺ·±çš„ć„Źč§ďĽŚčŻ·ĺ°†ć¤ç•™ç©şă€‚`, + setAnOpener: '设置开场白', + setAnOpenerInitial: `ä˝ ĺĄ˝ďĽ ć‘ćŻä˝ 的助ç†ďĽŚćś‰ä»€äąĺŹŻä»Ąĺ¸®ĺ°ä˝ çš„ĺ—?`, + setAnOpenerTip: '您ćłĺ¦‚何欢迎您的客ć·ďĽź', + knowledgeBases: '知识库', + knowledgeBasesMessage: '请选择', + knowledgeBasesTip: '选择关č”的知识库。', + system: '系统', + systemInitialValue: `ä˝ ćŻä¸€ä¸Şć™şč˝ĺŠ©ć‰‹ďĽŚčŻ·ć€»ç»“知识库的内容来回ç”é—®é˘ďĽŚčŻ·ĺ—举知识库ä¸çš„数据详细回ç”。当所有知识库内容é˝ä¸Žé—®é˘ć— ĺ…łć—¶ďĽŚä˝ çš„ĺ›žç”必须包括“知识库ä¸ćśŞć‰ľĺ°ć‚¨č¦çš„ç”ćˇďĽâ€ťčż™ĺŹĄčŻťă€‚回ç”需č¦č€č™‘čŠĺ¤©ĺŽ†ĺŹ˛ă€‚ + 以下ćŻçźĄčŻ†ĺş“: + {knowledge} + 以上ćŻçźĄčŻ†ĺş“。`, + systemMessage: '请输入', + systemTip: + '当LLM回ç”é—®é˘ć—¶ďĽŚä˝ 需č¦LLMéµĺľŞçš„说ćŽďĽŚćŻ”如角色设计ă€ç”ćˇé•żĺş¦ĺ’Śç”ćˇčŻč¨€ç‰ă€‚', + topN: 'Top N', + topNTip: `并非所有相似度得ĺ†é«äşŽâ€śç›¸äĽĽĺş¦é值”的块é˝äĽšč˘«ćŹäľ›ç»™ćł•ĺ¦çˇ•ĺŁ«ă€‚ LLM 只č˝çś‹ĺ°čż™äş›â€śTop N”块。`, + variable: 'ĺŹé‡Ź', + variableTip: `如果您使用对话 API,ĺŹé‡ŹĺŹŻč˝äĽšĺ¸®ĺŠ©ć‚¨ä˝żç”¨ä¸ŤĺŚçš„ç–略与客ć·čŠĺ¤©ă€‚ + 这些ĺŹé‡Źç”¨äşŽĺˇ«ĺ†™ćŹç¤şä¸çš„“系统”é¨ĺ†ďĽŚä»Ąäľżç»™LLM一个ćŹç¤şă€‚ + “知识”ćŻä¸€ä¸Şéťžĺ¸¸ç‰ąć®Šçš„ĺŹé‡ŹďĽŚĺ®ĺ°†ç”¨ćŁ€ç´˘ĺ°çš„块填充。 + “System”ä¸çš„所有ĺŹé‡Źé˝ĺş”该用大括号括起来。`, + add: '新增', + key: 'ĺ…łé”®ĺ—', + optional: '可选的', + operation: '操作', + model: '模型', + modelTip: '大čŻč¨€čŠĺ¤©ć¨ˇĺž‹', + modelMessage: '请选择', + freedom: '自由', + improvise: '即兴ĺ›ä˝ś', + precise: '精确', + balance: '平衡', + freedomTip: `“精确”意味着法ĺ¦çˇ•ĺŁ«äĽšäżťĺ®ĺą¶č°¨ć…Žĺś°ĺ›žç”ä˝ çš„é—®é˘ă€‚ â€śĺŤłĺ…´ĺŹ‘ćŚĄâ€ťć„Źĺ‘łçť€ä˝ ĺ¸Śćś›ćł•ĺ¦çˇ•ĺŁ«č˝ĺ¤źč‡Şç”±ĺś°ç•…所欲言。 “平衡”ćŻč°¨ć…Žä¸Žč‡Şç”±äą‹é—´çš„平衡。`, + temperature: '温度', + temperatureMessage: '温度ćŻĺż…填项', + temperatureTip: + '该参数控ĺ¶ć¨ˇĺž‹é˘„测的随机性。 čľä˝Žçš„温度使模型对其响应更有信ĺżďĽŚč€Śčľé«çš„温度ĺ™ä˝żĺ…¶ć›´ĺ…·ĺ›é€ ć€§ĺ’Śĺ¤šć ·ć€§ă€‚', + topP: 'Top P', + topPMessage: 'Top P ćŻĺż…填项', + topPTip: + 'čŻĄĺŹ‚ć•°äąźç§°ä¸şâ€ść ¸ĺżé‡‡ć ·â€ťďĽŚĺ®č®ľç˝®ä¸€ä¸Şé值来选择čľĺ°Źçš„ĺŤ•čŻŤé›†čż›čˇŚé‡‡ć ·ă€‚ ĺ®ä¸“注于最可č˝çš„单词,剔除不太可č˝çš„单词。', + presencePenalty: '出ĺ¸ĺ¤„罚', + presencePenaltyMessage: '出ĺ¸ĺ¤„罚ćŻĺż…填项', + presencePenaltyTip: + '这会通过ć©ç˝šĺŻąčŻťä¸ĺ·˛ç»Źĺ‡şçŽ°çš„单词来é»ć˘ć¨ˇĺž‹é‡Ťĺ¤Ťç›¸ĺŚçš„信ćŻă€‚', + frequencyPenalty: '频率ć©ç˝š', + frequencyPenaltyMessage: '频率ć©ç˝šćŻĺż…填项', + frequencyPenaltyTip: + '与ĺ在ć©ç˝šç±»äĽĽďĽŚčż™ĺ‡Źĺ°‘了模型频çąé‡Ťĺ¤Ťç›¸ĺŚĺŤ•čŻŤçš„倾ĺ‘。', + maxTokens: '最大tokenć•°', + maxTokensMessage: '最大tokenć•°ćŻĺż…填项', + maxTokensTip: + 'čż™č®ľç˝®äş†ć¨ˇĺž‹čľ“ĺ‡şçš„ćś€ĺ¤§é•żĺş¦ďĽŚä»Ąć ‡č®°ďĽĺŤ•čŻŤć–单词片段)的数量来衡量。', + }, + setting: { + profile: '概č¦', + profileDescription: '在ć¤ć›´ć–°ć‚¨çš„照片和个人详细信ćŻă€‚', + password: '密ç ', + passwordDescription: '请输入您当前的密ç 以更改您的密ç 。', + model: '模型ćŹäľ›ĺ•†', + modelDescription: '在ć¤ç®ˇç†ć‚¨çš„ĺ¸ć·č®ľç˝®ĺ’Śé¦–选项。', + team: '团éź', + logout: '登出', + username: '用ć·ĺŤ', + usernameMessage: '请输入用ć·ĺŤ', + photo: '头ĺŹ', + photoDescription: '这将ćľç¤şĺś¨ć‚¨çš„个人资料上。', + colorSchema: '主é˘', + colorSchemaMessage: '请选择您的主é˘ďĽ', + colorSchemaPlaceholder: '请选择您的主é˘ďĽ', + bright: 'ćŽäş®', + dark: '暗色', + timezone: '时区', + timezoneMessage: '请选择时区', + timezonePlaceholder: '请选择时区', + email: '邮箱地址', + emailDescription: '一旦注册,电ĺé‚®ä»¶ĺ°†ć— ćł•ć›´ć”ąă€‚', + currentPassword: '当前密ç ', + currentPasswordMessage: '请输入当前密ç ', + newPassword: '新密ç ', + newPasswordMessage: '请输入新密ç ', + newPasswordDescription: '您的新密ç 必须超过 8 个ĺ—符。', + confirmPassword: '确认新密ç ', + confirmPasswordMessage: '请确认新密ç ', + confirmPasswordNonMatchMessage: '您输入的新密ç 不匹配ďĽ', + cancel: '取ć¶', + addedModels: 'ć·»ĺŠ äş†çš„ć¨ˇĺž‹', + modelsToBeAdded: 'ĺľ…ć·»ĺŠ çš„ć¨ˇĺž‹', + addTheModel: 'ć·»ĺŠ ć¨ˇĺž‹', + apiKey: 'API-Key', + apiKeyMessage: '请输入 api key!', + apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。', + showMoreModels: '展示更多模型', + baseUrl: 'Base-Url', + baseUrlTip: + '如果您的 API 密钥来自 OpenAI,请忽略ĺ®ă€‚ 任何其他ä¸é—´ćŹäľ›ĺ•†é˝äĽšćŹäľ›ĺ¸¦ćś‰ API 密钥的基本 URL。', + modify: '修改', + systemModelSettings: '系统模型设置', + chatModel: 'čŠĺ¤©ć¨ˇĺž‹', + chatModelTip: '所有新ĺ›ĺ»şçš„知识库é˝äĽšä˝żç”¨é»č®¤çš„čŠĺ¤©LLM。', + embeddingModel: '嵌入模型', + embeddingModelTip: '所有新ĺ›ĺ»şçš„知识库é˝ĺ°†ä˝żç”¨çš„é»č®¤ĺµŚĺ…Ąć¨ˇĺž‹ă€‚', + img2txtModel: 'Img2txt模型', + img2txtModelTip: + '所有新ĺ›ĺ»şçš„知识库é˝ĺ°†ä˝żç”¨é»č®¤çš„多模块模型。 ĺ®ĺŹŻä»ĄćŹŹčż°ĺ›ľç‰‡ć–视频。', + sequence2txtModel: 'Img2txt模型', + sequence2txtModelTip: + '所有新ĺ›ĺ»şçš„知识库é˝ĺ°†ä˝żç”¨é»č®¤çš„ ASR 模型。 使用ć¤ć¨ˇĺž‹ĺ°†čŻéźłçż»čŻ‘为相应的文本。', + workspace: '工作空间', + upgrade: '升级', + }, + message: { + registered: '注册ć功', + logout: '登出ć功', + logged: '登录ć功', + pleaseSelectChunk: '请选择解ćžĺť—', + modified: 'ć›´ć–°ć功', + created: 'ĺ›ĺ»şć功', + deleted: 'ĺ 除ć功', + renamed: '重命ĺŤć功', + operated: '操作ć功', + updated: 'ć›´ć–°ć功', + 200: '服务器ć功返回请求的数据。', + 201: '新建ć–修改数据ć功。', + 202: '一个请求已经进入ĺŽĺŹ°ćŽ’éźďĽĺĽ‚ćĄä»»ĺŠˇďĽ‰ă€‚', + 204: 'ĺ 除数据ć功。', + 400: '发出的请求有错误,服务器没有进行新建ć–修改数据的操作。', + 401: '用ć·ć˛ˇćś‰ćťé™ďĽä»¤ç‰Śă€ç”¨ć·ĺŤă€ĺŻ†ç 错误)。', + 403: '用ć·ĺľ—ĺ°ćŽćťďĽŚä˝†ćŻč®żé—®ćŻč˘«ç¦ć˘çš„。', + 404: '发出的请求é’对的ćŻä¸Ťĺ在的记录,服务器没有进行操作。', + 406: 'čŻ·ć±‚çš„ć ĽĺĽŹä¸ŤĺŹŻĺľ—ă€‚', + 410: '请求的资ćşč˘«ć°¸äą…ĺ 除,且不会再得ĺ°çš„。', + 422: '当ĺ›ĺ»şä¸€ä¸ŞĺŻąč±ˇć—¶ďĽŚĺŹ‘生一个验čŻé”™čŻŻă€‚', + 500: '服务器发生错误,请检查服务器。', + 502: '网关错误。', + 503: '服务不可用,服务器暂时过载ć–维护。', + 504: '网关超时。', + requestError: '请求错误', + networkAnomalyDescription: 'ć‚¨çš„ç˝‘ç»śĺŹ‘ç”źĺĽ‚ĺ¸¸ďĽŚć— ćł•čżžćŽĄćśŤĺŠˇĺ™¨', + networkAnomaly: '网络异常', + hint: 'ćŹç¤ş', + }, + footer: { + profile: 'All rights reserved @ React', + }, + layout: { + file: 'file', + knowledge: 'knowledge', + chat: 'chat', + }, + }, +}; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx index 15f84ac2dd4a0a58a265881150b32ba890df775b..93f2382ac7d925bf3dfc0e4495e025ff2ecd23c6 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx @@ -3,6 +3,7 @@ import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; import { DeleteOutlined } from '@ant-design/icons'; import { Checkbox, Form, Input, Modal, Space } from 'antd'; import React, { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'umi'; import EditTag from '../edit-tag'; @@ -24,6 +25,7 @@ const ChunkCreatingModal: React.FC<kFProps> = ({ doc_id, chunkId }) => { const [keywords, setKeywords] = useState<string[]>([]); const loading = useOneNamespaceEffectsLoading('chunkModel', ['create_chunk']); const { removeChunk } = useDeleteChunkByIds(); + const { t } = useTranslation(); const handleCancel = () => { dispatch({ @@ -87,7 +89,7 @@ const ChunkCreatingModal: React.FC<kFProps> = ({ doc_id, chunkId }) => { return ( <Modal - title={`${chunkId ? 'Edit' : 'Create'} Chunk`} + title={`${chunkId ? t('common.edit') : t('common.create')} ${t('chunk.chunk')}`} open={isShowCreateModal} onOk={handleOk} onCancel={handleCancel} @@ -100,27 +102,27 @@ const ChunkCreatingModal: React.FC<kFProps> = ({ doc_id, chunkId }) => { layout={'vertical'} > <Form.Item<FieldType> - label="Chunk" + label={t('chunk.chunk')} name="content" - rules={[{ required: true, message: 'Please input value!' }]} + rules={[{ required: true, message: t('chunk.chunkMessage') }]} > <Input.TextArea autoSize={{ minRows: 4, maxRows: 10 }} /> </Form.Item> </Form> <section> - <p>Keyword*</p> + <p>{t('chunk.keyword')} *</p> <EditTag tags={keywords} setTags={setKeywords} /> </section> {chunkId && ( <section> - <p>Function*</p> + <p>{t('chunk.function')} *</p> <Space size={'large'}> <Checkbox onChange={handleCheck} checked={checked}> - Enabled + {t('chunk.enabled')} </Checkbox> <span onClick={handleRemove}> - <DeleteOutlined /> Delete + <DeleteOutlined /> {t('common.delete')} </span> </Space> </section> diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx index fd02a1b1046f3108b4d8f8aa3f45d144fa482c4f..ead23c48c8bb5d7c88814bff3af1cd3e8e627688 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx @@ -1,5 +1,6 @@ import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; import { KnowledgeRouteKey } from '@/constants/knowledge'; +import { useTranslate } from '@/hooks/commonHooks'; import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; import { ArrowLeftOutlined, @@ -49,6 +50,7 @@ const ChunkToolBar = ({ const dispatch = useDispatch(); const knowledgeBaseId = useKnowledgeBaseId(); const [isShowSearchBox, setIsShowSearchBox] = useState(false); + const { t } = useTranslate('chunk'); const handleSelectAllCheck = useCallback( (e: any) => { @@ -95,7 +97,7 @@ const ChunkToolBar = ({ label: ( <> <Checkbox onChange={handleSelectAllCheck} checked={checked}> - <b>Select All</b> + <b>{t('selectAll')}</b> </Checkbox> </> ), @@ -106,7 +108,7 @@ const ChunkToolBar = ({ label: ( <Space onClick={handleEnabledClick}> <CheckCircleOutlined /> - <b>Enabled Selected</b> + <b>{t('enabledSelected')}</b> </Space> ), }, @@ -115,7 +117,7 @@ const ChunkToolBar = ({ label: ( <Space onClick={handleDisabledClick}> <CloseCircleOutlined /> - <b>Disabled Selected</b> + <b>{t('disabledSelected')}</b> </Space> ), }, @@ -125,7 +127,7 @@ const ChunkToolBar = ({ label: ( <Space onClick={handleDelete}> <DeleteOutlined /> - <b>Delete Selected</b> + <b>{t('deleteSelected')}</b> </Space> ), }, @@ -136,6 +138,7 @@ const ChunkToolBar = ({ handleDelete, handleEnabledClick, handleDisabledClick, + t, ]); const content = ( @@ -151,9 +154,9 @@ const ChunkToolBar = ({ const filterContent = ( <Radio.Group onChange={handleFilterChange} value={available}> <Space direction="vertical"> - <Radio value={undefined}>All</Radio> - <Radio value={1}>Enabled</Radio> - <Radio value={0}>Disabled</Radio> + <Radio value={undefined}>{t('all')}</Radio> + <Radio value={1}>{t('enabled')}</Radio> + <Radio value={0}>{t('disabled')}</Radio> </Space> </Radio.Group> ); @@ -172,14 +175,14 @@ const ChunkToolBar = ({ <Space> <Popover content={content} placement="bottom" arrow={false}> <Button> - Bulk + {t('bulk')} <DownOutlined /> </Button> </Popover> {isShowSearchBox ? ( <Input size="middle" - placeholder="Search" + placeholder={t('search')} prefix={<SearchOutlined />} allowClear onChange={handleSearchChange} diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx index d63e70e39b62cf7bc0d9651e058fb86fbcbc59ed..dc6029199a0e3aff2d673bc49c866aa55e8a6b06 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx @@ -16,6 +16,7 @@ import { } from './hooks'; import { ChunkModelState } from './model'; +import { useTranslation } from 'react-i18next'; import styles from './index.less'; const Chunk = () => { @@ -33,6 +34,7 @@ const Chunk = () => { const documentInfo = useSelectDocumentInfo(); const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); const isPdf = documentInfo.type === 'pdf'; + const { t } = useTranslation(); const getChunkList = useFetchChunkList(); @@ -86,7 +88,7 @@ const Chunk = () => { [], ); const showSelectedChunkWarning = () => { - message.warning('Please select chunk!'); + message.warning(t('message.pleaseSelectChunk')); }; const handleRemoveChunk = useCallback(async () => { diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts index 5d9089a983dca5bfaa8857f8865a8cc8eb4f00fa..93187b15f7b9dd8bb73ce3c8bb72eca5b906ec9c 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts @@ -4,6 +4,7 @@ import kbService from '@/services/kbService'; import { message } from 'antd'; import { pick } from 'lodash'; // import { delay } from '@/utils/storeUtil'; +import i18n from '@/locales/config'; import { DvaModel } from 'umi'; export interface ChunkModelState extends BaseState { @@ -102,7 +103,7 @@ const model: DvaModel<ChunkModelState> = { const { data } = yield call(kbService.switch_chunk, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified successfully ďĽ'); + message.success(i18n.t('message.modified')); } return retcode; }, diff --git a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx index aaff97ccd221dabc91d84554e212520828b9c5b5..263457a757515cd53371b9e866aa72a1ba8539fd 100644 --- a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx @@ -40,6 +40,7 @@ import classNames from 'classnames'; import { ReactElement, useCallback, useMemo, useRef, useState } from 'react'; import { Link, useNavigate } from 'umi'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const { Dragger } = Upload; @@ -135,6 +136,7 @@ const KnowledgeUploadFile = () => { const documentList = useSelectDocumentList(); const runDocumentByIds = useRunDocument(); const uploadDocument = useUploadDocument(); + const { t } = useTranslate('knowledgeDetails'); const enabled = useMemo(() => { if (isUpload) { @@ -257,10 +259,10 @@ const KnowledgeUploadFile = () => { </Flex> <Flex justify="space-around"> <p className={styles.selectFilesText}> - <b>Select files</b> + <b>{t('selectFiles')}</b> </p> <p className={styles.changeSpecificCategoryText}> - <b>Change specific category</b> + <b>{t('changeSpecificCategory')}</b> </p> </Flex> </div> @@ -275,13 +277,8 @@ const KnowledgeUploadFile = () => { <Button className={styles.uploaderButton}> <CloudUploadOutlined className={styles.uploaderIcon} /> </Button> - <p className="ant-upload-text"> - Click or drag file to this area to upload - </p> - <p className="ant-upload-hint"> - Support for a single or bulk upload. Strictly prohibited from - uploading company data or other banned files. - </p> + <p className="ant-upload-text">{t('uploadTitle')}</p> + <p className="ant-upload-hint">{t('uploadDescription')}</p> </Dragger> </section> <section className={styles.footer}> @@ -292,7 +289,7 @@ const KnowledgeUploadFile = () => { disabled={!enabled} size="large" > - Next + {t('next', { keyPrefix: 'common' })} </Button> </section> </div> diff --git a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx index a520a90db91831e9a83e793eeffa4df5dbdfe012..096b22de4ee0eeaaf44439124787c1c9759810b2 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx @@ -204,7 +204,9 @@ const KnowledgeFile = () => { <div className={styles.filter}> <Space> <h3>{t('total', { keyPrefix: 'common' })}</h3> - <Tag color="purple">{total} files</Tag> + <Tag color="purple"> + {total} {t('files')} + </Tag> </Space> <Space> <Input diff --git a/web/src/pages/add-knowledge/components/knowledge-file/model.ts b/web/src/pages/add-knowledge/components/knowledge-file/model.ts index f946c9d78893c4c2b61b4218cde76f8bb8e49ceb..490fa8533d00372f9690a726ee3efdd9f9aa8b36 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-file/model.ts @@ -1,5 +1,6 @@ import { BaseState } from '@/interfaces/common'; import { IKnowledgeFile } from '@/interfaces/database/knowledge'; +import i18n from '@/locales/config'; import kbService, { getDocumentFile } from '@/services/kbService'; import { message } from 'antd'; import omit from 'lodash/omit'; @@ -54,14 +55,14 @@ const model: DvaModel<KFModelState> = { const { data } = yield call(kbService.createKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Created!'); + message.success(i18n.t('message.created')); } }, *updateKf({ payload = {} }, { call }) { const { data } = yield call(kbService.updateKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); } }, *getKfDetail({ payload = {} }, { call }) { @@ -109,7 +110,7 @@ const model: DvaModel<KFModelState> = { ); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); yield put({ type: 'getKfList', payload: { kb_id: payload.kb_id }, @@ -122,7 +123,7 @@ const model: DvaModel<KFModelState> = { }); const { retcode } = data; if (retcode === 0) { - message.success('Deleted!'); + message.success(i18n.t('message.deleted')); yield put({ type: 'getKfList', payload: { kb_id: payload.kb_id }, @@ -137,7 +138,7 @@ const model: DvaModel<KFModelState> = { ); const { retcode } = data; if (retcode === 0) { - message.success('rename successďĽ'); + message.success(i18n.t('message.renamed')); yield put({ type: 'getKfList', @@ -156,7 +157,7 @@ const model: DvaModel<KFModelState> = { payload: { kb_id: payload.kb_id }, }); - message.success('Created!'); + message.success(i18n.t('message.created')); } return retcode; }, @@ -173,7 +174,7 @@ const model: DvaModel<KFModelState> = { payload: { kb_id: payload.knowledgeBaseId }, }); } - message.success('Operation successfully ďĽ'); + message.success(i18n.t('message.operated')); } return retcode; }, @@ -189,7 +190,7 @@ const model: DvaModel<KFModelState> = { payload: { kb_id: payload.kb_id }, }); - message.success('Modified!'); + message.success(i18n.t('message.modified')); } return retcode; }, diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx index aa6c58659a7beb3051c1e20f540615e20717d823..d5ca83e888d6f3a6438136379265095144df6323 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx @@ -4,6 +4,7 @@ import { useTranslate } from '@/hooks/commonHooks'; import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { CloseCircleOutlined } from '@ant-design/icons'; import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd'; +import { useTranslation } from 'react-i18next'; import reactStringReplace from 'react-string-replace'; import { useDispatch } from 'umi'; import { RunningStatus, RunningStatusMap } from '../constant'; @@ -80,11 +81,14 @@ export const ParsingStatusCell = ({ record }: IProps) => { const dispatch = useDispatch(); const text = record.run; const runningStatus = RunningStatusMap[text]; + const { t } = useTranslation(); const isRunning = isParserRunning(text); const OperationIcon = iconMap[text]; + const label = t(`knowledgeDetails.runningStatus${text}`); + const handleOperationIconClick = () => { dispatch({ type: 'kFModel/document_run', @@ -103,11 +107,11 @@ export const ParsingStatusCell = ({ record }: IProps) => { {isRunning ? ( <Space> <Badge color={runningStatus.color} /> - {runningStatus.label} + {label} <span>{(record.progress * 100).toFixed(2)}%</span> </Space> ) : ( - runningStatus.label + label )} </Tag> </Popover> diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx index 8b6168e814ee1a5426ee86c18fc7e6422e1918c6..d7f6a6c5163e15fc0ea13d7628b156cadc284f91 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx @@ -1,25 +1,27 @@ import SvgIcon from '@/components/svg-icon'; +import { useTranslate } from '@/hooks/commonHooks'; import { useSelectParserList } from '@/hooks/userSettingHook'; import { Col, Divider, Empty, Row, Typography } from 'antd'; import { useMemo } from 'react'; import styles from './index.less'; -import { ImageMap, TextMap } from './utils'; +import { ImageMap } from './utils'; const { Title, Text } = Typography; const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { const parserList = useSelectParserList(); + const { t } = useTranslate('knowledgeConfiguration'); const item = useMemo(() => { const item = parserList.find((x) => x.value === chunkMethod); if (item) { return { title: item.label, - description: TextMap[item.value as keyof typeof TextMap]?.description, + description: t(item.value), }; } return { title: '', description: '' }; - }, [parserList, chunkMethod]); + }, [parserList, chunkMethod, t]); const imageList = useMemo(() => { if (chunkMethod in ImageMap) { @@ -33,18 +35,17 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { {imageList.length > 0 ? ( <> <Title level={5} className={styles.topTitle}> - "{item.title}" Chunking Method Description + "{item.title}" {t('methodTitle')} </Title> <p dangerouslySetInnerHTML={{ __html: item.description, }} ></p> - <Title level={5}>"{item.title}" Examples</Title> - <Text> - This visual guides is in order to make understanding easier - for you. - </Text> + <Title level={5}> + "{item.title}" {t('methodExamples')} + </Title> + <Text>{t('methodExamplesDescription')}</Text> <Row gutter={[10, 10]} className={styles.imageRow}> {imageList.map((x) => ( <Col span={12} key={x}> @@ -56,15 +57,14 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { </Col> ))} </Row> - <Title level={5}>{item.title} Dialogue Examples</Title> + <Title level={5}> + {item.title} {t('dialogueExamplesTitle')} + </Title> <Divider></Divider> </> ) : ( <Empty description={''} image={null}> - <p> - This will display a visual explanation of the knowledge base - categories - </p> + <p>{t('methodEmpty')}</p> <SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon> </Empty> )} diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx index b55164c176f367a2b7ff8ab0023f0c62c3573b0d..e50156ebf1c0ad52a1196387754dc55a3a4f6afa 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -59,7 +59,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { </Form.Item> <Form.Item name="permission" - label="Permissions" + label={t('permissions')} tooltip={t('permissionsTip')} rules={[{ required: true }]} > @@ -70,7 +70,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { </Form.Item> <Form.Item name="embd_id" - label="Embedding model" + label={t('embeddingModel')} rules={[{ required: true }]} tooltip={t('embeddingModelTip')} > @@ -82,7 +82,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { </Form.Item> <Form.Item name="parser_id" - label="Chunk method" + label={t('chunkMethod')} tooltip={t('chunkMethodTip')} rules={[{ required: true }]} > diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts index a24aea8f44a2c4683256aede1277a9bd51fb4cb4..a2eb9e11840dcb6f3adac41e1dda6baa1c2e4aba 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts @@ -1,4 +1,5 @@ import { IKnowledge } from '@/interfaces/database/knowledge'; +import i18n from '@/locales/config'; import kbService from '@/services/kbService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -32,7 +33,7 @@ const model: DvaModel<KSModelState> = { const { data } = yield call(kbService.createKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Created!'); + message.success(i18n.t('message.created')); } return data; }, @@ -41,7 +42,7 @@ const model: DvaModel<KSModelState> = { const { retcode } = data; if (retcode === 0) { yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } }); - message.success('Updated!'); + message.success(i18n.t('message.updated')); } }, *getKbDetail({ payload = {} }, { call, put }) { diff --git a/web/src/pages/add-knowledge/index.tsx b/web/src/pages/add-knowledge/index.tsx index abe0e2e2bef55cdf0d2026b4061260b25ada9f31..68df6039955883bb6187698a69d9b8c137764bc8 100644 --- a/web/src/pages/add-knowledge/index.tsx +++ b/web/src/pages/add-knowledge/index.tsx @@ -14,7 +14,6 @@ import { KnowledgeDatasetRouteKey, KnowledgeRouteKey, datasetRouteMap, - routeMap, } from './constant'; import styles from './index.less'; @@ -49,7 +48,7 @@ const KnowledgeAdding = () => { {t(`knowledgeDetails.${activeKey}`)} </Link> ) : ( - routeMap[activeKey] + t(`knowledgeDetails.${activeKey}`) ), }, ]; diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx index bca69b661d7d1520386fa955e2fe3c17c003865f..4dfdfe6cb5a7caeb9b5036d6d06ad6121bf84275 100644 --- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx @@ -4,6 +4,7 @@ import { Form, Input, Select, Upload } from 'antd'; import classNames from 'classnames'; import { ISegmentedContentProps } from '../interface'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const AssistantSetting = ({ show }: ISegmentedContentProps) => { @@ -12,6 +13,7 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { label: x.name, value: x.id, })); + const { t } = useTranslate('chat'); const normFile = (e: any) => { if (Array.isArray(e)) { @@ -28,14 +30,14 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { > <Form.Item name={'name'} - label="Assistant name" + label={t('assistantName')} rules={[{ required: true }]} > <Input placeholder="e.g. Resume Jarvis" /> </Form.Item> <Form.Item name="icon" - label="Assistant avatar" + label={t('assistantAvatar')} valuePropName="fileList" getValueFromEvent={normFile} > @@ -46,44 +48,45 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { > <button style={{ border: 0, background: 'none' }} type="button"> <PlusOutlined /> - <div style={{ marginTop: 8 }}>Upload</div> + <div style={{ marginTop: 8 }}> + {t('upload', { keyPrefix: 'common' })} + </div> </button> </Upload> </Form.Item> <Form.Item name={'language'} - label="Language" + label={t('language')} initialValue={'Chinese'} tooltip="coming soon" - style={{display:'none'}} + style={{ display: 'none' }} > <Select options={[ - { value: 'Chinese', label: 'Chinese' }, - { value: 'English', label: 'English' }, + { value: 'Chinese', label: t('chinese', { keyPrefix: 'common' }) }, + { value: 'English', label: t('english', { keyPrefix: 'common' }) }, ]} /> </Form.Item> <Form.Item name={['prompt_config', 'empty_response']} - label="Empty response" - tooltip="If nothing is retrieved with user's question in the knowledgebase, it will use this as an answer. - If you want LLM comes up with its own opinion when nothing is retrieved, leave this blank." + label={t('emptyResponse')} + tooltip={t('emptyResponseTip')} > <Input placeholder="" /> </Form.Item> <Form.Item name={['prompt_config', 'prologue']} - label="Set an opener" - tooltip="How do you want to welcome your clients?" - initialValue={"Hi! I'm your assistant, what can I do for you?"} + label={t('setAnOpener')} + tooltip={t('setAnOpenerTip')} + initialValue={t('setAnOpenerInitial')} > <Input.TextArea autoSize={{ minRows: 5 }} /> </Form.Item> <Form.Item - label="Knowledgebases" + label={t('knowledgeBases')} name="kb_ids" - tooltip="Select knowledgebases associated." + tooltip={t('knowledgeBasesTip')} rules={[ { required: true, @@ -95,7 +98,7 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { <Select mode="multiple" options={knowledgeOptions} - placeholder="Please select" + placeholder={t('knowledgeBasesMessage')} ></Select> </Form.Item> </section> diff --git a/web/src/pages/chat/chat-configuration-modal/index.tsx b/web/src/pages/chat/chat-configuration-modal/index.tsx index 91281e56ce22ec48a1ae985a22ed6e61da9bcf4d..8ddec396f78a58b8af5d97376b9b852d301acbed 100644 --- a/web/src/pages/chat/chat-configuration-modal/index.tsx +++ b/web/src/pages/chat/chat-configuration-modal/index.tsx @@ -7,6 +7,7 @@ import { import { IDialog } from '@/interfaces/database/chat'; import { Divider, Flex, Form, Modal, Segmented, UploadFile } from 'antd'; import { SegmentedValue } from 'antd/es/segmented'; +import camelCase from 'lodash/camelCase'; import omit from 'lodash/omit'; import { useEffect, useRef, useState } from 'react'; import { variableEnabledFieldMap } from '../constants'; @@ -17,20 +18,9 @@ import { useFetchModelId } from './hooks'; import ModelSetting from './model-setting'; import PromptEngine from './prompt-engine'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; -enum ConfigurationSegmented { - AssistantSetting = 'Assistant Setting', - PromptEngine = 'Prompt Engine', - ModelSetting = 'Model Setting', -} - -const segmentedMap = { - [ConfigurationSegmented.AssistantSetting]: AssistantSetting, - [ConfigurationSegmented.ModelSetting]: ModelSetting, - [ConfigurationSegmented.PromptEngine]: PromptEngine, -}; - const layout = { labelCol: { span: 7 }, wrapperCol: { span: 17 }, @@ -47,6 +37,18 @@ const validateMessages = { }, }; +enum ConfigurationSegmented { + AssistantSetting = 'Assistant Setting', + PromptEngine = 'Prompt Engine', + ModelSetting = 'Model Setting', +} + +const segmentedMap = { + [ConfigurationSegmented.AssistantSetting]: AssistantSetting, + [ConfigurationSegmented.ModelSetting]: ModelSetting, + [ConfigurationSegmented.PromptEngine]: PromptEngine, +}; + interface IProps extends IModalManagerChildrenProps { initialDialog: IDialog; loading: boolean; @@ -63,11 +65,13 @@ const ChatConfigurationModal = ({ clearDialog, }: IProps) => { const [form] = Form.useForm(); + const [value, setValue] = useState<ConfigurationSegmented>( ConfigurationSegmented.AssistantSetting, ); const promptEngineRef = useRef<Array<IPromptConfigParameters>>([]); const modelId = useFetchModelId(visible); + const { t } = useTranslate('chat'); const handleOk = async () => { const values = await form.validateFields(); @@ -115,10 +119,9 @@ const ChatConfigurationModal = ({ <Flex gap={16}> <ChatConfigurationAtom></ChatConfigurationAtom> <div> - <b>Chat Configuration</b> + <b>{t('chatConfiguration')}</b> <div className={styles.chatConfigurationDescription}> - Here, dress up a dedicated assistant for your special knowledge bases! - đź’• + {t('chatConfigurationDescription')} </div> </div> </Flex> @@ -158,7 +161,10 @@ const ChatConfigurationModal = ({ size={'large'} value={value} onChange={handleSegmentedChange} - options={Object.values(ConfigurationSegmented)} + options={Object.values(ConfigurationSegmented).map((x) => ({ + label: t(camelCase(x)), + value: x, + }))} block /> <Divider></Divider> diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx index 87390ab2b625ab463ee180a1b8ebf4cfa861d188..6f2ec7745d02da68eef54707f2d61b006d621241 100644 --- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx @@ -5,16 +5,19 @@ import { } from '@/constants/knowledge'; import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd'; import classNames from 'classnames'; +import camelCase from 'lodash/camelCase'; import { useEffect } from 'react'; import { ISegmentedContentProps } from '../interface'; +import { useTranslate } from '@/hooks/commonHooks'; import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks'; import { variableEnabledFieldMap } from '../constants'; import styles from './index.less'; const ModelSetting = ({ show, form }: ISegmentedContentProps) => { + const { t } = useTranslate('chat'); const parameterOptions = Object.values(ModelVariableType).map((x) => ({ - label: x, + label: t(camelCase(x)), value: x, })); @@ -44,18 +47,18 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { })} > <Form.Item - label="Model" + label={t('model')} name="llm_id" - tooltip="Large language chat model" - rules={[{ required: true, message: 'Please select!' }]} + tooltip={t('modelTip')} + rules={[{ required: true, message: t('modelMessage') }]} > <Select options={modelOptions} showSearch /> </Form.Item> <Divider></Divider> <Form.Item - label="Freedom" + label={t('freedom')} name="parameters" - tooltip="'Precise' means the LLM will be conservative and answer your question cautiously. 'Improvise' means the you want LLM talk much and freely. 'Balance' is between cautiously and freely." + tooltip={t('freedomTip')} initialValue={ModelVariableType.Precise} // rules={[{ required: true, message: 'Please input!' }]} > @@ -64,7 +67,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { onChange={handleParametersChange} /> </Form.Item> - <Form.Item label="Temperature" tooltip={'This parameter controls the randomness of predictions by the model. A lower temperature makes the model more confident in its responses, while a higher temperature makes it more creative and diverse.'}> + <Form.Item label={t('temperature')} tooltip={t('temperatureTip')}> <Flex gap={20} align="center"> <Form.Item name={'temperatureEnabled'} @@ -77,7 +80,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'temperature']} noStyle - rules={[{ required: true, message: 'Temperature is required' }]} + rules={[{ required: true, message: t('temperatureMessage') }]} > <Slider className={styles.variableSlider} max={1} step={0.01} /> </Form.Item> @@ -85,7 +88,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'temperature']} noStyle - rules={[{ required: true, message: 'Temperature is required' }]} + rules={[{ required: true, message: t('temperatureMessage') }]} > <InputNumber className={styles.sliderInputNumber} @@ -96,7 +99,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { </Form.Item> </Flex> </Form.Item> - <Form.Item label="Top P" tooltip={'Also known as “nucleus sampling,” this parameter sets a threshold to select a smaller set of words to sample from. It focuses on the most likely words, cutting off the less probable ones.'}> + <Form.Item label={t('topP')} tooltip={t('topPTip')}> <Flex gap={20} align="center"> <Form.Item name={'topPEnabled'} valuePropName="checked" noStyle> <Switch size="small" /> @@ -105,7 +108,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'top_p']} noStyle - rules={[{ required: true, message: 'Top_p is required' }]} + rules={[{ required: true, message: t('topPMessage') }]} > <Slider className={styles.variableSlider} max={1} step={0.01} /> </Form.Item> @@ -113,7 +116,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'top_p']} noStyle - rules={[{ required: true, message: 'Top_p is required' }]} + rules={[{ required: true, message: t('topPMessage') }]} > <InputNumber className={styles.sliderInputNumber} @@ -124,7 +127,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { </Form.Item> </Flex> </Form.Item> - <Form.Item label="Presence Penalty" tooltip={'This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation.'}> + <Form.Item label={t('presencePenalty')} tooltip={t('presencePenaltyTip')}> <Flex gap={20} align="center"> <Form.Item name={'presencePenaltyEnabled'} @@ -137,9 +140,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'presence_penalty']} noStyle - rules={[ - { required: true, message: 'Presence Penalty is required' }, - ]} + rules={[{ required: true, message: t('presencePenaltyMessage') }]} > <Slider className={styles.variableSlider} max={1} step={0.01} /> </Form.Item> @@ -147,9 +148,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'presence_penalty']} noStyle - rules={[ - { required: true, message: 'Presence Penalty is required' }, - ]} + rules={[{ required: true, message: t('presencePenaltyMessage') }]} > <InputNumber className={styles.sliderInputNumber} @@ -160,7 +159,10 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { </Form.Item> </Flex> </Form.Item> - <Form.Item label="Frequency Penalty" tooltip={'Similar to the presence penalty, this reduces the model’s tendency to repeat the same words frequently.'}> + <Form.Item + label={t('frequencyPenalty')} + tooltip={t('frequencyPenaltyTip')} + > <Flex gap={20} align="center"> <Form.Item name={'frequencyPenaltyEnabled'} @@ -174,7 +176,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { name={['llm_setting', 'frequency_penalty']} noStyle rules={[ - { required: true, message: 'Frequency Penalty is required' }, + { required: true, message: t('frequencyPenaltyMessage') }, ]} > <Slider className={styles.variableSlider} max={1} step={0.01} /> @@ -183,9 +185,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'frequency_penalty']} noStyle - rules={[ - { required: true, message: 'Frequency Penalty is required' }, - ]} + rules={[{ required: true, message: t('frequencyPenaltyMessage') }]} > <InputNumber className={styles.sliderInputNumber} @@ -196,7 +196,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { </Form.Item> </Flex> </Form.Item> - <Form.Item label="Max Tokens" tooltip={'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).'}> + <Form.Item label={t('maxTokens')} tooltip={t('maxTokensTip')}> <Flex gap={20} align="center"> <Form.Item name={'maxTokensEnabled'} valuePropName="checked" noStyle> <Switch size="small" /> @@ -205,7 +205,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'max_tokens']} noStyle - rules={[{ required: true, message: 'Max Tokens is required' }]} + rules={[{ required: true, message: t('maxTokensMessage') }]} > <Slider className={styles.variableSlider} max={2048} /> </Form.Item> @@ -213,7 +213,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { <Form.Item name={['llm_setting', 'max_tokens']} noStyle - rules={[{ required: true, message: 'Max Tokens is required' }]} + rules={[{ required: true, message: t('maxTokensMessage') }]} > <InputNumber className={styles.sliderInputNumber} diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx index 1fc6e1006facbad525a587c72cd92d2e2e182c80..1e49bc948572a454d40a10259ba3bebda94ecfa7 100644 --- a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx +++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx @@ -29,6 +29,7 @@ import { } from '../interface'; import { EditableCell, EditableRow } from './editable-cell'; +import { useTranslate } from '@/hooks/commonHooks'; import { useSelectPromptConfigParameters } from '../hooks'; import styles from './index.less'; @@ -44,6 +45,7 @@ const PromptEngine = ( ) => { const [dataSource, setDataSource] = useState<DataType[]>([]); const parameters = useSelectPromptConfigParameters(); + const { t } = useTranslate('chat'); const components = { body: { @@ -102,7 +104,7 @@ const PromptEngine = ( const columns: TableProps<DataType>['columns'] = [ { - title: 'key', + title: t('key'), dataIndex: 'variable', key: 'variable', onCell: (record: DataType) => ({ @@ -114,7 +116,7 @@ const PromptEngine = ( }), }, { - title: 'optional', + title: t('optional'), dataIndex: 'optional', key: 'optional', width: 40, @@ -130,7 +132,7 @@ const PromptEngine = ( }, }, { - title: 'operation', + title: t('operation'), dataIndex: 'operation', width: 30, key: 'operation', @@ -152,24 +154,21 @@ const PromptEngine = ( })} > <Form.Item - label="System" - rules={[{ required: true, message: 'Please input!' }]} - tooltip="Instructions you need LLM to follow when LLM answers questions, like charactor design, answer length and answer language etc." + label={t('system')} + rules={[{ required: true, message: t('systemMessage') }]} + tooltip={t('systemTip')} name={['prompt_config', 'system']} - initialValue={`ä˝ ćŻä¸€ä¸Şć™şč˝ĺŠ©ć‰‹ďĽŚčŻ·ć€»ç»“知识库的内容来回ç”é—®é˘ďĽŚčŻ·ĺ—举知识库ä¸çš„数据详细回ç”。当所有知识库内容é˝ä¸Žé—®é˘ć— ĺ…łć—¶ďĽŚä˝ çš„ĺ›žç”必须包括“知识库ä¸ćśŞć‰ľĺ°ć‚¨č¦çš„ç”ćˇďĽâ€ťčż™ĺŹĄčŻťă€‚回ç”需č¦č€č™‘čŠĺ¤©ĺŽ†ĺŹ˛ă€‚ - 以下ćŻçźĄčŻ†ĺş“: - {knowledge} - 以上ćŻçźĄčŻ†ĺş“。`} + initialValue={t('systemInitialValue')} > <Input.TextArea autoSize={{ maxRows: 8, minRows: 5 }} /> </Form.Item> <Divider></Divider> <SimilaritySlider isTooltipShown></SimilaritySlider> <Form.Item<FieldType> - label="Top N" + label={t('topN')} name={'top_n'} initialValue={8} - tooltip={`Not all the chunks whose similarity score is above the 'simialrity threashold' will be feed to LLMs. LLM can only see these 'Top N' chunks.`} + tooltip={t('topNTip')} > <Slider max={30} /> </Form.Item> @@ -177,18 +176,15 @@ const PromptEngine = ( <Row align={'middle'} justify="end"> <Col span={7} className={styles.variableAlign}> <label className={styles.variableLabel}> - Variables - <Tooltip title="If you use dialog APIs, the varialbes might help you chat with your clients with different strategies. - The variables are used to fill-in the 'System' part in prompt in order to give LLM a hint. - The 'knowledge' is a very special variable which will be filled-in with the retrieved chunks. - All the variables in 'System' should be curly bracketed."> + {t('variable')} + <Tooltip title={t('variableTip')}> <QuestionCircleOutlined className={styles.variableIcon} /> </Tooltip> </label> </Col> <Col span={17} className={styles.variableAlign}> <Button size="small" onClick={handleAdd}> - Add + {t('add')} </Button> </Col> </Row> diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx index 887e92f5876b87aab07ccda1dd8cba4e72da49eb..128bbe69a8edcf322dcd833dd6f9d86e72ea2851 100644 --- a/web/src/pages/chat/chat-container/index.tsx +++ b/web/src/pages/chat/chat-container/index.tsx @@ -37,6 +37,7 @@ import { } from '../hooks'; import SvgIcon from '@/components/svg-icon'; +import { useTranslate } from '@/hooks/commonHooks'; import { getExtension, isPdf } from '@/utils/documentUtils'; import styles from './index.less'; @@ -298,6 +299,7 @@ const ChatContainer = () => { const disabled = useGetSendButtonDisabled(); useGetFileIcon(); const loading = useSelectConversationLoading(); + const { t } = useTranslate('chat'); return ( <> @@ -328,7 +330,7 @@ const ChatContainer = () => { </Flex> <Input size="large" - placeholder="Message Resume Assistant..." + placeholder={t('sendPlaceholder')} value={value} disabled={disabled} suffix={ @@ -338,7 +340,7 @@ const ChatContainer = () => { loading={sendLoading} disabled={disabled} > - Send + {t('send')} </Button> } onPressEnter={handlePressEnter} diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx index 183914089b4aa79ace387e96018f610b87ee4261..ba18215e78d9d68d63af65eba74ab51c84d726e8 100644 --- a/web/src/pages/chat/index.tsx +++ b/web/src/pages/chat/index.tsx @@ -35,6 +35,7 @@ import { useSelectFirstDialogOnMount, } from './hooks'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const Chat = () => { @@ -71,6 +72,7 @@ const Chat = () => { } = useEditDialog(); const dialogLoading = useSelectDialogListLoading(); const conversationLoading = useSelectConversationListLoading(); + const { t } = useTranslate('chat'); useFetchDialogOnMount(dialogId, true); @@ -132,7 +134,8 @@ const Chat = () => { onClick: handleCreateTemporaryConversation, label: ( <Space> - <EditOutlined /> New chat + <EditOutlined /> + {t('newChat')} </Space> ), }, @@ -146,7 +149,7 @@ const Chat = () => { label: ( <Space> <EditOutlined /> - Edit + {t('edit', { keyPrefix: 'common' })} </Space> ), }, @@ -157,7 +160,7 @@ const Chat = () => { label: ( <Space> <DeleteOutlined /> - Delete chat + {t('delete', { keyPrefix: 'common' })} </Space> ), }, @@ -250,7 +253,7 @@ const Chat = () => { className={styles.chatTitle} > <Space> - <b>Chat</b> + <b>{t('chat')}</b> <Tag>{conversationList.length}</Tag> </Space> <Dropdown menu={{ items }}> diff --git a/web/src/pages/chat/model.ts b/web/src/pages/chat/model.ts index 22bf7b48e8c874b86c4fd27b558699db7001f130..1e94be0917100f723b474d23e5b1b1ab41826c8b 100644 --- a/web/src/pages/chat/model.ts +++ b/web/src/pages/chat/model.ts @@ -1,4 +1,5 @@ import { IConversation, IDialog, Message } from '@/interfaces/database/chat'; +import i18n from '@/locales/config'; import chatService from '@/services/chatService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -77,7 +78,9 @@ const model: DvaModel<ChatModelState> = { const { data } = yield call(chatService.setDialog, payload); if (data.retcode === 0) { yield put({ type: 'listDialog' }); - message.success(payload.dialog_id ? 'Modified!' : 'Created!'); + message.success( + i18n.t(`message.${payload.dialog_id ? 'modified' : 'created'}`), + ); } return data.retcode; }, @@ -85,7 +88,7 @@ const model: DvaModel<ChatModelState> = { const { data } = yield call(chatService.removeDialog, payload); if (data.retcode === 0) { yield put({ type: 'listDialog' }); - message.success('Deleted successfully !'); + message.success(i18n.t('message.deleted')); } return data.retcode; }, @@ -152,7 +155,7 @@ const model: DvaModel<ChatModelState> = { type: 'listConversation', payload: { dialog_id: payload.dialog_id }, }); - message.success('Deleted successfully !'); + message.success(i18n.t('message.deleted')); } return data.retcode; }, diff --git a/web/src/pages/login/index.tsx b/web/src/pages/login/index.tsx index 1de7e933bbed40d0124f8d08fb39b91f9bea1bef..98700dc9afc6d633aefcb023640aeaa3c7239340 100644 --- a/web/src/pages/login/index.tsx +++ b/web/src/pages/login/index.tsx @@ -75,7 +75,7 @@ const Login = () => { <div className={styles.loginLeft}> <div className={styles.leftContainer}> <div className={styles.loginTitle}> - <div>{title === 'login' ? t('login') : 'Create an account'}</div> + <div>{title === 'login' ? t('login') : t('register')}</div> <span> {title === 'login' ? t('loginDescription') diff --git a/web/src/pages/login/model.ts b/web/src/pages/login/model.ts index eaa92fb265031baf79e7dfb04bcb8add803bbafb..38865b190059d5f9c31c3d0d878c6753e8533136 100644 --- a/web/src/pages/login/model.ts +++ b/web/src/pages/login/model.ts @@ -1,4 +1,5 @@ import { Authorization } from '@/constants/authorization'; +import i18n from '@/locales/config'; import userService from '@/services/userService'; import authorizationUtil from '@/utils/authorizationUtil'; import { message } from 'antd'; @@ -31,7 +32,7 @@ const model: DvaModel<LoginModelState> = { const { retcode, data: res } = data; const authorization = response.headers.get(Authorization); if (retcode === 0) { - message.success('logged!'); + message.success(i18n.t('message.logged')); const token = res.access_token; const userInfo = { avatar: res.avatar, @@ -51,7 +52,7 @@ const model: DvaModel<LoginModelState> = { console.log(); const { retcode } = data; if (retcode === 0) { - message.success('Registered!'); + message.success(i18n.t('message.registered')); } return retcode; }, @@ -59,7 +60,7 @@ const model: DvaModel<LoginModelState> = { const { data } = yield call(userService.logout, payload); const { retcode } = data; if (retcode === 0) { - message.success('logout'); + message.success(i18n.t('message.logout')); } return retcode; }, diff --git a/web/src/pages/login/right-panel.tsx b/web/src/pages/login/right-panel.tsx index f4072567039fba4e307c739cae21b78c59d0ab58..964351325cadb13944627eba2528b6b9efeeaf30 100644 --- a/web/src/pages/login/right-panel.tsx +++ b/web/src/pages/login/right-panel.tsx @@ -3,11 +3,13 @@ import SvgIcon from '@/components/svg-icon'; import { Flex, Rate, Space, Typography } from 'antd'; import classNames from 'classnames'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const { Title, Text } = Typography; const LoginRightPanel = () => { + const { t } = useTranslate('login'); return ( <section className={styles.rightPanel}> <SvgIcon name="login-star" width={80}></SvgIcon> @@ -16,11 +18,10 @@ const LoginRightPanel = () => { level={1} className={classNames(styles.white, styles.loginTitle)} > - Start building your smart assisstants. + {t('title')} </Title> <Text className={classNames(styles.pink, styles.loginDescription)}> - Sign up for free to explore top RAG technology. Create knowledge bases - and AIs to empower your business. + {t('description')} </Text> <Flex align="center" gap={16}> <Avatars></Avatars> @@ -34,7 +35,7 @@ const LoginRightPanel = () => { </span> </Space> <span className={classNames(styles.pink, styles.loginRateReviews)}> - from 500+ reviews + {t('review')} </span> </Flex> </Flex> diff --git a/web/src/pages/user-setting/components/setting-title/index.tsx b/web/src/pages/user-setting/components/setting-title/index.tsx index 8ad153f3ad9e12f1c75ab7d77f0b157f42a29960..f5f8a7954c7c1c5759312d0fb673ac713b18c783 100644 --- a/web/src/pages/user-setting/components/setting-title/index.tsx +++ b/web/src/pages/user-setting/components/setting-title/index.tsx @@ -1,3 +1,4 @@ +import { useTranslate } from '@/hooks/commonHooks'; import { SettingOutlined } from '@ant-design/icons'; import { Button, Flex, Typography } from 'antd'; @@ -16,6 +17,8 @@ const SettingTitle = ({ clickButton, showRightButton = false, }: IProps) => { + const { t } = useTranslate('setting'); + return ( <Flex align="center" justify={'space-between'}> <div> @@ -24,7 +27,7 @@ const SettingTitle = ({ </div> {showRightButton && ( <Button type={'primary'} onClick={clickButton}> - <SettingOutlined></SettingOutlined> System Model Settings + <SettingOutlined></SettingOutlined> {t('systemModelSettings')} </Button> )} </Flex> diff --git a/web/src/pages/user-setting/model.ts b/web/src/pages/user-setting/model.ts index 0caa12f33e2b9365514f53bd1a3656115f5f112b..5fffd8f4cf7057e7da6517fe3ef58f66ed7fbb3c 100644 --- a/web/src/pages/user-setting/model.ts +++ b/web/src/pages/user-setting/model.ts @@ -5,6 +5,7 @@ import { IThirdOAIModelCollection as IThirdAiModelCollection, } from '@/interfaces/database/llm'; import { IUserInfo } from '@/interfaces/database/userSetting'; +import i18n from '@/locales/config'; import userService from '@/services/userService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -47,7 +48,8 @@ const model: DvaModel<SettingModelState> = { const { data } = yield call(userService.setting, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); + yield put({ type: 'getUserInfo', }); @@ -89,7 +91,8 @@ const model: DvaModel<SettingModelState> = { const { data } = yield call(userService.set_tenant_info, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); + yield put({ type: 'getTenantInfo', }); @@ -137,7 +140,8 @@ const model: DvaModel<SettingModelState> = { const { data } = yield call(userService.set_api_key, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); + yield put({ type: 'my_llm' }); yield put({ type: 'factories_list' }); yield put({ diff --git a/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx b/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx index e4270e423d9c4fe35e56d916a49e927204ab94d9..7a4850797168d7a668b60f4414be141124c9e14d 100644 --- a/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx @@ -1,4 +1,5 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager'; +import { useTranslate } from '@/hooks/commonHooks'; import { Form, Input, Modal } from 'antd'; import { useEffect } from 'react'; @@ -24,6 +25,7 @@ const ApiKeyModal = ({ onOk, }: IProps) => { const [form] = Form.useForm(); + const { t } = useTranslate('setting'); const handleOk = async () => { const ret = await form.validateFields(); @@ -49,7 +51,7 @@ const ApiKeyModal = ({ return ( <Modal - title="Modify" + title={t('modify')} open={visible} onOk={handleOk} onCancel={handleCancel} @@ -67,18 +69,18 @@ const ApiKeyModal = ({ form={form} > <Form.Item<FieldType> - label="Api-Key" + label={t('apiKey')} name="api_key" - tooltip="The API key can be obtained by registering the corresponding LLM supplier." - rules={[{ required: true, message: 'Please input api key!' }]} + tooltip={t('apiKeyTip')} + rules={[{ required: true, message: t('apiKeyMessage') }]} > <Input /> </Form.Item> {llmFactory === 'OpenAI' && ( <Form.Item<FieldType> - label="Base-Url" + label={t('baseUrl')} name="base_url" - tooltip="If your API key is from OpenAI, just ignore it. Any other intermediate providers will give this base url with the API key." + tooltip={t('baseUrlTip')} > <Input placeholder="https://api.openai.com/v1" /> </Form.Item> diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 4c8d2e8fe60005595cde799d0803b96e503b9bf7..03dd050d7461444edf57ec414beaa45db5c38335 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -1,5 +1,5 @@ import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg'; -import { useSetModalState } from '@/hooks/commonHooks'; +import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; import { LlmItem, useFetchLlmFactoryListOnMount, @@ -64,6 +64,7 @@ interface IModelCardProps { const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { const { visible, switchVisible } = useSetModalState(); + const { t } = useTranslate('setting'); const handleApiKeyClick = () => { clickApiKey(item.name); @@ -94,7 +95,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { </Button> <Button onClick={handleShowMoreClick}> <Flex gap={'small'}> - Show more models + {t('showMoreModels')} <MoreModelIcon /> </Flex> </Button> @@ -140,6 +141,7 @@ const UserSettingModel = () => { hideSystemSettingModal, showSystemSettingModal, } = useSubmitSystemModelSetting(); + const { t } = useTranslate('setting'); const handleApiKeyClick = useCallback( (llmFactory: string) => { @@ -155,7 +157,7 @@ const UserSettingModel = () => { const items: CollapseProps['items'] = [ { key: '1', - label: 'Added models', + label: t('addedModels'), children: ( <List grid={{ gutter: 16, column: 1 }} @@ -168,7 +170,7 @@ const UserSettingModel = () => { }, { key: '2', - label: 'Models to be added', + label: t('modelsToBeAdded'), children: ( <List grid={{ @@ -193,7 +195,7 @@ const UserSettingModel = () => { </Flex> <Divider></Divider> <Button type="link" onClick={handleAddModel(item.name)}> - Add the model + {t('addTheModel')} </Button> </Card> </List.Item> @@ -208,8 +210,8 @@ const UserSettingModel = () => { <Spin spinning={loading}> <section className={styles.modelWrapper}> <SettingTitle - title="Model Setting" - description="Manage your account settings and preferences here." + title={t('model')} + description={t('profileDescription')} showRightButton clickButton={showSystemSettingModal} ></SettingTitle> diff --git a/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx b/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx index 1deb482e64f5fbefb56f80521c68c746028d8a3b..53ee20631e02d63037b13b5b8db26e4bd50f0ace 100644 --- a/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx @@ -1,5 +1,6 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager'; import { LlmModelType } from '@/constants/knowledge'; +import { useTranslate } from '@/hooks/commonHooks'; import { ISystemModelSettingSavingParams } from '@/hooks/llmHooks'; import { Form, Modal, Select } from 'antd'; import { useEffect } from 'react'; @@ -21,6 +22,7 @@ const SystemModelSettingModal = ({ const [form] = Form.useForm(); const { systemSetting: initialValues, allOptions } = useFetchSystemModelSettingOnMount(visible); + const { t } = useTranslate('setting'); const handleOk = async () => { const values = await form.validateFields(); @@ -35,7 +37,7 @@ const SystemModelSettingModal = ({ return ( <Modal - title="System Model Settings" + title={t('systemModelSettings')} open={visible} onOk={handleOk} onCancel={hideModal} @@ -43,25 +45,32 @@ const SystemModelSettingModal = ({ confirmLoading={loading} > <Form form={form} onValuesChange={onFormLayoutChange} layout={'vertical'}> - - <Form.Item label="Chat model" name="llm_id" tooltip="The default chat LLM all the newly created knowledgebase will use."> + <Form.Item + label={t('chatModel')} + name="llm_id" + tooltip={t('chatModelTip')} + > <Select options={allOptions[LlmModelType.Chat]} /> </Form.Item> - <Form.Item label="Embedding model" name="embd_id" tooltip="The default embedding model all the newly created knowledgebase will use."> + <Form.Item + label={t('embeddingModel')} + name="embd_id" + tooltip={t('embeddingModelTip')} + > <Select options={allOptions[LlmModelType.Embedding]} /> </Form.Item> <Form.Item - label="Img2txt model" + label={t('img2txtModel')} name="img2txt_id" - tooltip="The default multi-module model all the newly created knowledgebase will use. It can describe a picture or video." + tooltip={t('img2txtModelTip')} > <Select options={allOptions[LlmModelType.Image2text]} /> </Form.Item> - + <Form.Item - label="Sequence2txt model" + label={t('sequence2txtModel')} name="asr_id" - tooltip="The default ASR model all the newly created knowledgebase will use. Use this model to translate voices to corresponding text." + tooltip={t('sequence2txtModelTip')} > <Select options={allOptions[LlmModelType.Speech2text]} /> </Form.Item> diff --git a/web/src/pages/user-setting/setting-password/index.tsx b/web/src/pages/user-setting/setting-password/index.tsx index 919af726bd068b407a8aad48949fd1888dc75406..66e744ab1a09fa05bd3fa3c267de79a06d404272 100644 --- a/web/src/pages/user-setting/setting-password/index.tsx +++ b/web/src/pages/user-setting/setting-password/index.tsx @@ -5,6 +5,7 @@ import { Button, Divider, Form, Input, Space } from 'antd'; import SettingTitle from '../components/setting-title'; import { useValidateSubmittable } from '../hooks'; +import { useTranslate } from '@/hooks/commonHooks'; import parentStyles from '../index.less'; import styles from './index.less'; @@ -22,6 +23,7 @@ const UserSettingPassword = () => { const loading = useOneNamespaceEffectsLoading('settingModel', ['setting']); const { form, submittable } = useValidateSubmittable(); const saveSetting = useSaveSetting(); + const { t } = useTranslate('setting'); const onFinish = (values: any) => { const password = rsaPsw(values.password) as string; @@ -37,8 +39,8 @@ const UserSettingPassword = () => { return ( <section className={styles.passwordWrapper}> <SettingTitle - title="Password" - description="Please enter your current password to change your password." + title={t('password')} + description={t('passwordDescription')} ></SettingTitle> <Divider /> <Form @@ -56,12 +58,12 @@ const UserSettingPassword = () => { // requiredMark={'optional'} > <Form.Item<FieldType> - label="Current password" + label={t('currentPassword')} name="password" rules={[ { required: true, - message: 'Please input your password!', + message: t('currentPasswordMessage'), whitespace: true, }, ]} @@ -69,14 +71,14 @@ const UserSettingPassword = () => { <Input.Password /> </Form.Item> <Divider /> - <Form.Item label="New password" required> + <Form.Item label={t('newPassword')} required> <Form.Item<FieldType> noStyle name="new_password" rules={[ { required: true, - message: 'Please input your password!', + message: t('newPasswordMessage'), whitespace: true, }, ]} @@ -84,18 +86,18 @@ const UserSettingPassword = () => { <Input.Password /> </Form.Item> <p className={parentStyles.itemDescription}> - Your new password must be more than 8 characters. + {t('newPasswordDescription')} </p> </Form.Item> <Divider /> <Form.Item<FieldType> - label="Confirm new password" + label={t('confirmPassword')} name="confirm_password" dependencies={['new_password']} rules={[ { required: true, - message: 'Please confirm your password!', + message: t('confirmPasswordMessage'), whitespace: true, }, ({ getFieldValue }) => ({ @@ -104,7 +106,7 @@ const UserSettingPassword = () => { return Promise.resolve(); } return Promise.reject( - new Error('The new password that you entered do not match!'), + new Error(t('confirmPasswordNonMatchMessage')), ); }, }), @@ -120,14 +122,14 @@ const UserSettingPassword = () => { } > <Space> - <Button htmlType="button">Cancel</Button> + <Button htmlType="button">{t('cancel')}</Button> <Button type="primary" htmlType="submit" disabled={!submittable} loading={loading} > - Save + {t('save', { keyPrefix: 'common' })} </Button> </Space> </Form.Item> diff --git a/web/src/pages/user-setting/setting-profile/index.tsx b/web/src/pages/user-setting/setting-profile/index.tsx index 952dc7b16c864fbf20edb7518799de3cb46c5350..627af9379db3949e511a9ba8ede70f1ac2c0c177 100644 --- a/web/src/pages/user-setting/setting-profile/index.tsx +++ b/web/src/pages/user-setting/setting-profile/index.tsx @@ -29,6 +29,8 @@ import { useValidateSubmittable, } from '../hooks'; +import { useTranslate } from '@/hooks/commonHooks'; +import { useChangeLanguage } from '@/hooks/logicHooks'; import parentStyles from '../index.less'; import styles from './index.less'; @@ -54,6 +56,8 @@ const UserSettingProfile = () => { const { form, submittable } = useValidateSubmittable(); const loading = useSelectUserInfoLoading(); useFetchUserInfo(); + const { t } = useTranslate('setting'); + const changeLanguage = useChangeLanguage(); const onFinish = async (values: any) => { const avatar = await getBase64FromUploadFileList(values.avatar); @@ -72,8 +76,8 @@ const UserSettingProfile = () => { return ( <section className={styles.profileWrapper}> <SettingTitle - title="Profile" - description="Update your photo and personal details here." + title={t('profile')} + description={t('profileDescription')} ></SettingTitle> <Divider /> <Spin spinning={loading}> @@ -91,12 +95,12 @@ const UserSettingProfile = () => { autoComplete="off" > <Form.Item<FieldType> - label="Username" + label={t('username')} name="nickname" rules={[ { required: true, - message: 'Please input your username!', + message: t('usernameMessage'), whitespace: true, }, ]} @@ -107,8 +111,8 @@ const UserSettingProfile = () => { <Form.Item<FieldType> label={ <div> - <Space>Your photo</Space> - <div>This will be displayed on your profile.</div> + <Space>{t('photo')}</Space> + <div>{t('photoDescription')}</div> </div> } name="avatar" @@ -126,41 +130,53 @@ const UserSettingProfile = () => { > <button style={{ border: 0, background: 'none' }} type="button"> <PlusOutlined /> - <div style={{ marginTop: 8 }}>Upload</div> + <div style={{ marginTop: 8 }}> + {t('upload', { keyPrefix: 'common' })} + </div> </button> </Upload> </Form.Item> <Divider /> <Form.Item<FieldType> - label="Color schema" + label={t('colorSchema')} name="color_schema" - rules={[ - { required: true, message: 'Please select your color schema!' }, - ]} + rules={[{ required: true, message: t('colorSchemaMessage') }]} > - <Select placeholder="select your color schema"> - <Option value="Bright">Bright</Option> - <Option value="Dark">Dark</Option> + <Select placeholder={t('colorSchemaPlaceholder')}> + <Option value="Bright">{t('bright')}</Option> + <Option value="Dark">{t('dark')}</Option> </Select> </Form.Item> <Divider /> <Form.Item<FieldType> - label="Language" + label={t('language', { keyPrefix: 'common' })} name="language" - rules={[{ required: true, message: 'Please input your language!' }]} + rules={[ + { + required: true, + message: t('languageMessage', { keyPrefix: 'common' }), + }, + ]} > - <Select placeholder="select your language"> - <Option value="English">English</Option> - <Option value="Chinese">Chinese</Option> + <Select + placeholder={t('languagePlaceholder', { keyPrefix: 'common' })} + onChange={changeLanguage} + > + <Option value="English"> + {t('english', { keyPrefix: 'common' })} + </Option> + <Option value="Chinese"> + {t('chinese', { keyPrefix: 'common' })} + </Option> </Select> </Form.Item> <Divider /> <Form.Item<FieldType> - label="Timezone" + label={t('timezone')} name="timezone" - rules={[{ required: true, message: 'Please input your timezone!' }]} + rules={[{ required: true, message: t('timezoneMessage') }]} > - <Select placeholder="select your timezone" showSearch> + <Select placeholder={t('timezonePlaceholder')} showSearch> {TimezoneList.map((x) => ( <Option value={x} key={x}> {x} @@ -169,12 +185,12 @@ const UserSettingProfile = () => { </Select> </Form.Item> <Divider /> - <Form.Item label="Email address"> + <Form.Item label={t('email')}> <Form.Item<FieldType> name="email" noStyle> <Input disabled /> </Form.Item> <p className={parentStyles.itemDescription}> - Once registered, E-mail cannot be changed. + {t('emailDescription')} </p> </Form.Item> <Form.Item @@ -184,14 +200,14 @@ const UserSettingProfile = () => { } > <Space> - <Button htmlType="button">Cancel</Button> + <Button htmlType="button">{t('cancel')}</Button> <Button type="primary" htmlType="submit" disabled={!submittable} loading={submitLoading} > - Save + {t('save', { keyPrefix: 'common' })} </Button> </Space> </Form.Item> diff --git a/web/src/pages/user-setting/setting-team/index.tsx b/web/src/pages/user-setting/setting-team/index.tsx index 48dab39e991eccb179fb785469b646dbb2b18161..c2445f038353a76f58632332e11a8588df779620 100644 --- a/web/src/pages/user-setting/setting-team/index.tsx +++ b/web/src/pages/user-setting/setting-team/index.tsx @@ -1,17 +1,21 @@ import { Button, Card, Flex } from 'antd'; +import { useTranslate } from '@/hooks/commonHooks'; import { useSelectUserInfo } from '@/hooks/userSettingHook'; import styles from './index.less'; const UserSettingTeam = () => { const userInfo = useSelectUserInfo(); + const { t } = useTranslate('setting'); return ( <div className={styles.teamWrapper}> <Card className={styles.teamCard}> <Flex align="center" justify={'space-between'}> - <span>{userInfo.nickname} Workspace</span> - <Button type="primary">Upgrade</Button> + <span> + {userInfo.nickname} {t('workspace')} + </span> + <Button type="primary">{t('upgrade')}</Button> </Flex> </Card> </div> diff --git a/web/src/pages/user-setting/sidebar/index.tsx b/web/src/pages/user-setting/sidebar/index.tsx index 097ab5f6465b590de52165074deeb0be69e8a68e..0945e31e11634f607b794b790322ce7f86d04bc1 100644 --- a/web/src/pages/user-setting/sidebar/index.tsx +++ b/web/src/pages/user-setting/sidebar/index.tsx @@ -7,38 +7,39 @@ import { UserSettingBaseKey, UserSettingIconMap, UserSettingRouteKey, - UserSettingRouteMap, } from '../constants'; +import { useTranslate } from '@/hooks/commonHooks'; import { useLogout } from '@/hooks/userSettingHook'; import styles from './index.less'; type MenuItem = Required<MenuProps>['items'][number]; -function getItem( - label: React.ReactNode, - key: React.Key, - icon?: React.ReactNode, - children?: MenuItem[], - type?: 'group', -): MenuItem { - return { - key, - icon, - children, - label, - type, - } as MenuItem; -} - -const items: MenuItem[] = Object.values(UserSettingRouteKey).map((value) => - getItem(UserSettingRouteMap[value], value, UserSettingIconMap[value]), -); - const SideBar = () => { const navigate = useNavigate(); const pathName = useSecondPathName(); const logout = useLogout(); + const { t } = useTranslate('setting'); + + function getItem( + label: string, + key: React.Key, + icon?: React.ReactNode, + children?: MenuItem[], + type?: 'group', + ): MenuItem { + return { + key, + icon, + children, + label: t(label), + type, + } as MenuItem; + } + + const items: MenuItem[] = Object.values(UserSettingRouteKey).map((value) => + getItem(value, value, UserSettingIconMap[value]), + ); const handleMenuClick: MenuProps['onClick'] = ({ key }) => { if (key === UserSettingRouteKey.Logout) { diff --git a/web/src/utils/authorizationUtil.ts b/web/src/utils/authorizationUtil.ts index a2b3d06908e6906cd6951d4e993f32503a807733..f4b8b5e7e1bad766e9658e714bca48ceba7180f5 100644 --- a/web/src/utils/authorizationUtil.ts +++ b/web/src/utils/authorizationUtil.ts @@ -38,6 +38,12 @@ const storage = { localStorage.removeItem(x); }); }, + setLanguage: (lng: string) => { + localStorage.setItem('lng', lng); + }, + getLanguage: (): string => { + return localStorage.getItem('lng') as string; + }, }; export default storage; diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index adc77c572c35b19d661b42bfc3f84cbe3edd2525..c5fd867d122aae703612b73dcea946c6ee738963 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -1,4 +1,5 @@ import { Authorization } from '@/constants/authorization'; +import i18n from '@/locales/config'; import authorizationUtil from '@/utils/authorizationUtil'; import { message, notification } from 'antd'; import { history } from 'umi'; @@ -7,21 +8,21 @@ import { RequestMethod, extend } from 'umi-request'; const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动ä¸ć–请求。errorHandler 抛出的error message const RetcodeMessage = { - 200: '服务器ć功返回请求的数据。', - 201: '新建ć–修改数据ć功。', - 202: '一个请求已经进入ĺŽĺŹ°ćŽ’éźďĽĺĽ‚ćĄä»»ĺŠˇďĽ‰ă€‚', - 204: 'ĺ 除数据ć功。', - 400: '发出的请求有错误,服务器没有进行新建ć–修改数据的操作。', - 401: '用ć·ć˛ˇćś‰ćťé™ďĽä»¤ç‰Śă€ç”¨ć·ĺŤă€ĺŻ†ç 错误)。', - 403: '用ć·ĺľ—ĺ°ćŽćťďĽŚä˝†ćŻč®żé—®ćŻč˘«ç¦ć˘çš„。', - 404: '发出的请求é’对的ćŻä¸Ťĺ在的记录,服务器没有进行操作。', - 406: 'čŻ·ć±‚çš„ć ĽĺĽŹä¸ŤĺŹŻĺľ—ă€‚', - 410: '请求的资ćşč˘«ć°¸äą…ĺ 除,且不会再得ĺ°çš„。', - 422: '当ĺ›ĺ»şä¸€ä¸ŞĺŻąč±ˇć—¶ďĽŚĺŹ‘生一个验čŻé”™čŻŻă€‚', - 500: '服务器发生错误,请检查服务器。', - 502: '网关错误。', - 503: '服务不可用,服务器暂时过载ć–维护。', - 504: '网关超时。', + 200: i18n.t('message.200'), + 201: i18n.t('message.201'), + 202: i18n.t('message.202'), + 204: i18n.t('message.204'), + 400: i18n.t('message.400'), + 401: i18n.t('message.401'), + 403: i18n.t('message.403'), + 404: i18n.t('message.404'), + 406: i18n.t('message.406'), + 410: i18n.t('message.410'), + 422: i18n.t('message.422'), + 500: i18n.t('message.500'), + 502: i18n.t('message.502'), + 503: i18n.t('message.503'), + 504: i18n.t('message.504'), }; type ResultCode = | 200 @@ -62,13 +63,13 @@ const errorHandler = (error: { RetcodeMessage[response.status as ResultCode] || response.statusText; const { status, url } = response; notification.error({ - message: `请求错误 ${status}: ${url}`, + message: `${i18n.t('message.requestError')} ${status}: ${url}`, description: errorText, }); } else if (!response) { notification.error({ - description: 'ć‚¨çš„ç˝‘ç»śĺŹ‘ç”źĺĽ‚ĺ¸¸ďĽŚć— ćł•čżžćŽĄćśŤĺŠˇĺ™¨', - message: '网络异常', + description: i18n.t('message.networkAnomalyDescription'), + message: i18n.t('message.networkAnomaly'), }); } } @@ -123,7 +124,7 @@ request.interceptors.response.use(async (response: any, options) => { message.error(data.retmsg); } else { notification.error({ - message: `ćŹç¤ş : ${data.retcode}`, + message: `${i18n.t('message.hint')} : ${data.retcode}`, description: data.retmsg, duration: 3, });