change language #245 (#246)

### What problem does this PR solve?

change language

Issue link: #245



- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2024-04-07 17:41:29 +08:00
committed by GitHub
parent 591202721d
commit 373946ef3f
47 changed files with 1301 additions and 458 deletions

View File

@@ -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>

View File

@@ -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}

View File

@@ -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 () => {

View File

@@ -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;
},

View File

@@ -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>

View File

@@ -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

View File

@@ -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;
},

View File

@@ -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>

View File

@@ -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>
)}

View File

@@ -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 }]}
>

View File

@@ -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 }) {

View File

@@ -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}`)
),
},
];

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 models 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 models 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}

View File

@@ -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>

View File

@@ -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}

View File

@@ -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 }}>

View File

@@ -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;
},

View File

@@ -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')

View File

@@ -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;
},

View File

@@ -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>

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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: t('colorSchemaMessage') }]}
>
<Select placeholder={t('colorSchemaPlaceholder')}>
<Option value="Bright">{t('bright')}</Option>
<Option value="Dark">{t('dark')}</Option>
</Select>
</Form.Item>
<Divider />
<Form.Item<FieldType>
label={t('language', { keyPrefix: 'common' })}
name="language"
rules={[
{ required: true, message: 'Please select your color schema!' },
{
required: true,
message: t('languageMessage', { keyPrefix: 'common' }),
},
]}
>
<Select placeholder="select your color schema">
<Option value="Bright">Bright</Option>
<Option value="Dark">Dark</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="Language"
name="language"
rules={[{ required: true, message: 'Please input your language!' }]}
>
<Select placeholder="select your language">
<Option value="English">English</Option>
<Option value="Chinese">Chinese</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>

View File

@@ -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>

View File

@@ -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) {