fix: #209 after saving the knowledge base configuration, jump to the dataset page (#212)

### What problem does this PR solve?
fix: #209 after saving the knowledge base configuration, jump to the
dataset page
feat: translate ConfigurationForm
feat: translate KnowledgeTesting
feat: translate document list page
feat: translate knowledge list page

Issue link: #209

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
balibabu 2024-04-03 11:21:54 +08:00 committed by GitHub
parent 392e515c3f
commit 9b9b6d5408
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 299 additions and 196 deletions

View File

@ -22,6 +22,7 @@ import omit from 'lodash/omit';
import React, { useEffect, useMemo } from 'react';
import { useFetchParserListOnMount } from './hooks';
import { useTranslate } from '@/hooks/commonHooks';
import styles from './index.less';
const { CheckableTag } = Tag;
@ -56,6 +57,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
documentExtension,
);
const [form] = Form.useForm();
const { t } = useTranslate('knowledgeDetails');
const handleOk = async () => {
const values = await form.validateFields();
@ -91,7 +93,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
return (
<Modal
title="Chunk Method"
title={t('chunkMethod')}
open={visible}
onOk={handleOk}
onCancel={hideModal}

View File

@ -1,3 +1,4 @@
import { useTranslate } from '@/hooks/commonHooks';
import { Form, Slider } from 'antd';
type FieldType = {
@ -10,27 +11,23 @@ interface IProps {
}
const SimilaritySlider = ({ isTooltipShown = false }: IProps) => {
const { t } = useTranslate('knowledgeDetails');
return (
<>
<Form.Item<FieldType>
label="Similarity threshold"
label={t('similarityThreshold')}
name={'similarity_threshold'}
tooltip={isTooltipShown && `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.`
}
tooltip={isTooltipShown && t('similarityThresholdTip')}
initialValue={0.2}
>
<Slider max={1} step={0.01} />
</Form.Item>
<Form.Item<FieldType>
label="Vector similarity weight"
label={t('vectorSimilarityWeight')}
name={'vector_similarity_weight'}
initialValue={0.3}
tooltip={isTooltipShown && `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.
`}
tooltip={isTooltipShown && t('vectorSimilarityWeightTip')}
>
<Slider max={1} step={0.01} />
</Form.Item>

View File

@ -92,9 +92,9 @@ export const useShowDeleteConfirm = () => {
title: t('common.deleteModalTitle'),
icon: <ExclamationCircleFilled />,
// content: 'Some descriptions',
okText: 'Yes',
okText: t('common.ok'),
okType: 'danger',
cancelText: 'No',
cancelText: t('common.cancel'),
async onOk() {
try {
const ret = await onOk?.();
@ -115,3 +115,7 @@ export const useShowDeleteConfirm = () => {
return showDeleteConfirm;
};
export const useTranslate = (keyPrefix: string) => {
return useTranslation('translation', { keyPrefix });
};

View File

@ -1,4 +1,7 @@
import { KnowledgeSearchParams } from '@/constants/knowledge';
import {
KnowledgeRouteKey,
KnowledgeSearchParams,
} from '@/constants/knowledge';
import { useCallback } from 'react';
import { useLocation, useNavigate, useSearchParams } from 'umi';
@ -42,3 +45,12 @@ export const useNavigateWithFromState = () => {
[navigate],
);
};
export const useNavigateToDataset = () => {
const navigate = useNavigate();
const { knowledgeId } = useGetKnowledgeSearchParams();
return useCallback(() => {
navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeId}`);
}, [knowledgeId, navigate]);
};

View File

@ -5,12 +5,8 @@ import translation_en from './en.json';
import translation_zh from './zh.json';
const resources = {
en: {
translation: translation_en,
},
zh: {
translation: translation_zh,
},
en: translation_en,
zh: translation_zh,
};
i18n.use(initReactI18next).init({

View File

@ -1,50 +1,122 @@
{
"common": {
"delete": "Delete",
"deleteModalTitle": "Are you sure delete this item?"
},
"login": {
"login": "Sign in",
"signUp": "Sign up",
"loginDescription": "Were 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": "Dont 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!"
},
"footer": {
"detail": "All rights reserved @ React"
},
"layout": {
"file": "file",
"knowledge": "knowledge",
"chat": "chat"
},
"setting": {
"btn": "en"
"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": "Were 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": "Dont 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"
}
}
}

View File

@ -1,21 +1,23 @@
{
"login": { "login": "登录" },
"header": {
"register": "注册",
"signin": "登陆",
"home": "首页",
"setting": "user setting",
"logout": "logout"
},
"footer": {
"detail": "版权所有 @ React"
},
"layout": {
"file": "文件",
"knowledge": "知识库",
"chat": "聊天"
},
"setting": {
"btn": "中文"
"translation": {
"login": { "login": "登录" },
"header": {
"register": "注册",
"signin": "登陆",
"home": "首页",
"setting": "user setting",
"logout": "logout"
},
"footer": {
"detail": "版权所有 @ React"
},
"layout": {
"file": "文件",
"knowledge": "知识库",
"chat": "聊天"
},
"setting": {
"btn": "中文"
}
}
}

View File

@ -42,6 +42,7 @@ import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal';
import { useSetSelectedRecord } from '@/hooks/logicHooks';
import { useTranslation } from 'react-i18next';
import styles from './index.less';
const KnowledgeFile = () => {
@ -76,6 +77,9 @@ const KnowledgeFile = () => {
hideChangeParserModal,
showChangeParserModal,
} = useChangeDocumentParser(currentRecord.id);
const { t } = useTranslation('translation', {
keyPrefix: 'knowledgeDetails',
});
const actionItems: MenuProps['items'] = useMemo(() => {
return [
@ -87,7 +91,7 @@ const KnowledgeFile = () => {
<Button type="link">
<Space>
<FileTextOutlined />
Local files
{t('localFiles')}
</Space>
</Button>
</div>
@ -101,18 +105,18 @@ const KnowledgeFile = () => {
<div>
<Button type="link">
<FileOutlined />
Create empty file
{t('emptyFiles')}
</Button>
</div>
),
// disabled: true,
},
];
}, [linkToUploadPage, showCreateModal]);
}, [linkToUploadPage, showCreateModal, t]);
const columns: ColumnsType<IKnowledgeFile> = [
{
title: 'Name',
title: t('name'),
dataIndex: 'name',
key: 'name',
fixed: 'left',
@ -133,17 +137,17 @@ const KnowledgeFile = () => {
),
},
{
title: 'Chunk Number',
title: t('chunkNumber'),
dataIndex: 'chunk_num',
key: 'chunk_num',
},
{
title: 'Upload Date',
title: t('uploadDate'),
dataIndex: 'create_date',
key: 'create_date',
},
{
title: 'Chunk Method',
title: t('chunkMethod'),
dataIndex: 'parser_id',
key: 'parser_id',
render: (text) => {
@ -151,7 +155,7 @@ const KnowledgeFile = () => {
},
},
{
title: 'Enabled',
title: t('enabled'),
key: 'status',
dataIndex: 'status',
render: (_, { status, id }) => (
@ -166,7 +170,7 @@ const KnowledgeFile = () => {
),
},
{
title: 'Parsing Status',
title: t('parsingStatus'),
dataIndex: 'run',
key: 'run',
render: (text, record) => {
@ -174,7 +178,7 @@ const KnowledgeFile = () => {
},
},
{
title: 'Action',
title: t('action'),
key: 'action',
render: (_, record) => (
<ParsingActionCell
@ -194,17 +198,17 @@ const KnowledgeFile = () => {
return (
<div className={styles.datasetWrapper}>
<h3>Dataset</h3>
<p>Hey, don't forget to adjust the chunk after adding the dataset! 😉</p>
<h3>{t('dataset')}</h3>
<p>{t('datasetDescription')}</p>
<Divider></Divider>
<div className={styles.filter}>
<Space>
<h3>Total</h3>
<h3>{t('total', { keyPrefix: 'common' })}</h3>
<Tag color="purple">{total} files</Tag>
</Space>
<Space>
<Input
placeholder="Seach your files"
placeholder={t('searchFiles')}
value={searchString}
style={{ width: 220 }}
allowClear
@ -214,7 +218,7 @@ const KnowledgeFile = () => {
<Dropdown menu={{ items: actionItems }} trigger={['click']}>
<Button type="primary" icon={<PlusOutlined />}>
Add file
{t('addFile')}
</Button>
</Dropdown>
</Space>

View File

@ -1,4 +1,4 @@
import { useShowDeleteConfirm } from '@/hooks/commonHooks';
import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks';
import { useRemoveDocument } from '@/hooks/documentHooks';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { api_host } from '@/utils/api';
@ -29,7 +29,7 @@ const ParsingActionCell = ({
}: IProps) => {
const documentId = record.id;
const isRunning = isParserRunning(record.run);
const { t } = useTranslate('knowledgeDetails');
const removeDocument = useRemoveDocument(documentId);
const showDeleteConfirm = useShowDeleteConfirm();
@ -65,7 +65,7 @@ const ParsingActionCell = ({
label: (
<div>
<Button type="link" onClick={onShowChangeParserModal}>
Chunk Method
{t('chunkMethod')}
</Button>
</div>
),
@ -83,7 +83,7 @@ const ParsingActionCell = ({
<ToolOutlined size={20} />
</Button>
</Dropdown>
<Tooltip title="Rename">
<Tooltip title={t('rename', { keyPrefix: 'common' })}>
<Button
type="text"
disabled={isRunning}

View File

@ -1,5 +1,6 @@
import { ReactComponent as RefreshIcon } from '@/assets/svg/refresh.svg';
import { ReactComponent as RunIcon } from '@/assets/svg/run.svg';
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';
@ -22,6 +23,8 @@ interface IProps {
}
const PopoverContent = ({ record }: IProps) => {
const { t } = useTranslate('knowledgeDetails');
const replaceText = (text: string) => {
// Remove duplicate \n
const nextText = text.replace(/(\n)\1+/g, '$1');
@ -44,17 +47,17 @@ const PopoverContent = ({ record }: IProps) => {
const items: DescriptionsProps['items'] = [
{
key: 'process_begin_at',
label: 'Process Begin At',
label: t('processBeginAt'),
children: record.process_begin_at,
},
{
key: 'process_duation',
label: 'Process Duration',
label: t('processDuration'),
children: record.process_duation,
},
{
key: 'progress_msg',
label: 'Progress Msg',
label: t('progressMsg'),
children: replaceText(record.progress_msg.trim()),
},
];

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';
@ -17,7 +18,7 @@ const RenameModal = ({
hideModal,
}: IProps) => {
const [form] = Form.useForm();
const { t } = useTranslate('common');
type FieldType = {
name?: string;
};
@ -43,7 +44,7 @@ const RenameModal = ({
return (
<Modal
title="Rename"
title={t('rename')}
open={visible}
onOk={handleOk}
onCancel={hideModal}
@ -60,9 +61,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>

View File

@ -7,40 +7,27 @@ import {
} from './hooks';
import MaxTokenNumber from '@/components/max-token-number';
import { useTranslate } from '@/hooks/commonHooks';
import { FormInstance } from 'antd/lib';
import styles from './index.less';
const { Option } = Select;
const ConfigurationForm = ({ form }: { form: FormInstance }) => {
const { submitKnowledgeConfiguration, submitLoading } =
useSubmitKnowledgeConfiguration();
const { submitKnowledgeConfiguration, submitLoading, navigateToDataset } =
useSubmitKnowledgeConfiguration(form);
const { parserList, embeddingModelOptions, disabled } =
useFetchKnowledgeConfigurationOnMount(form);
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const { t } = useTranslate('knowledgeConfiguration');
return (
<Form
form={form}
name="validateOnly"
layout="vertical"
autoComplete="off"
onFinish={submitKnowledgeConfiguration}
onFinishFailed={onFinishFailed}
>
<Form.Item
name="name"
label="Knowledge base name"
rules={[{ required: true }]}
>
<Form form={form} name="validateOnly" layout="vertical" autoComplete="off">
<Form.Item name="name" label={t('name')} rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item
name="avatar"
label="Knowledge base photo"
label={t('photo')}
valuePropName="fileList"
getValueFromEvent={normFile}
>
@ -52,43 +39,43 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => {
>
<button style={{ border: 0, background: 'none' }} type="button">
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
<div style={{ marginTop: 8 }}>{t('upload')}</div>
</button>
</Upload>
</Form.Item>
<Form.Item name="description" label="Description">
<Form.Item name="description" label={t('description')}>
<Input />
</Form.Item>
<Form.Item
label="Language"
label={t('language')}
name="language"
initialValue={'English'}
rules={[{ required: true, message: 'Please input your language!' }]}
rules={[{ required: true, message: t('languageMessage') }]}
>
<Select placeholder="select your language">
<Option value="English">English</Option>
<Option value="Chinese">Chinese</Option>
<Select placeholder={t('languagePlaceholder')}>
<Option value="English">{t('english')}</Option>
<Option value="Chinese">{t('chinese')}</Option>
</Select>
</Form.Item>
<Form.Item
name="permission"
label="Permissions"
tooltip="If the permission is 'Team', all the team member can manipulate the knowledgebase."
tooltip={t('permissionsTip')}
rules={[{ required: true }]}
>
<Radio.Group>
<Radio value="me">Only me</Radio>
<Radio value="team">Team</Radio>
<Radio value="me">{t('me')}</Radio>
<Radio value="team">{t('team')}</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="embd_id"
label="Embedding model"
rules={[{ required: true }]}
tooltip="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."
tooltip={t('embeddingModelTip')}
>
<Select
placeholder="Please select a embedding model"
placeholder={t('embeddingModelPlaceholder')}
options={embeddingModelOptions}
disabled={disabled}
></Select>
@ -96,10 +83,10 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => {
<Form.Item
name="parser_id"
label="Chunk method"
tooltip="The instruction is at right."
tooltip={t('chunkMethodTip')}
rules={[{ required: true }]}
>
<Select placeholder="Please select a chunk method" disabled={disabled}>
<Select placeholder={t('chunkMethodPlaceholder')} disabled={disabled}>
{parserList.map((x) => (
<Option value={x.value} key={x.value}>
{x.label}
@ -120,16 +107,16 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => {
<Form.Item>
<div className={styles.buttonWrapper}>
<Space>
<Button htmlType="reset" size={'middle'}>
Cancel
<Button size={'middle'} onClick={navigateToDataset}>
{t('cancel')}
</Button>
<Button
htmlType="submit"
type="primary"
size={'middle'}
loading={submitLoading}
onClick={submitKnowledgeConfiguration}
>
Save
{t('save')}
</Button>
</Space>
</div>

View File

@ -5,6 +5,7 @@ import {
useUpdateKnowledge,
} from '@/hooks/knowledgeHook';
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
import { useNavigateToDataset } from '@/hooks/routeHook';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import {
useFetchTenantInfo,
@ -20,24 +21,24 @@ import pick from 'lodash/pick';
import { useCallback, useEffect } from 'react';
import { LlmModelType } from '../../constant';
export const useSubmitKnowledgeConfiguration = () => {
export const useSubmitKnowledgeConfiguration = (form: FormInstance) => {
const save = useUpdateKnowledge();
const knowledgeBaseId = useKnowledgeBaseId();
const submitLoading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']);
const navigateToDataset = useNavigateToDataset();
const submitKnowledgeConfiguration = useCallback(
async (values: any) => {
const avatar = await getBase64FromUploadFileList(values.avatar);
save({
...values,
avatar,
kb_id: knowledgeBaseId,
});
},
[save, knowledgeBaseId],
);
const submitKnowledgeConfiguration = useCallback(async () => {
const values = await form.validateFields();
const avatar = await getBase64FromUploadFileList(values.avatar);
save({
...values,
avatar,
kb_id: knowledgeBaseId,
});
navigateToDataset();
}, [save, knowledgeBaseId, form, navigateToDataset]);
return { submitKnowledgeConfiguration, submitLoading };
return { submitKnowledgeConfiguration, submitLoading, navigateToDataset };
};
export const useFetchKnowledgeConfigurationOnMount = (form: FormInstance) => {

View File

@ -6,6 +6,7 @@ import {
useSelectKnowledgeDetailsLoading,
} from './hooks';
import { useTranslate } from '@/hooks/commonHooks';
import styles from './index.less';
const { Title } = Typography;
@ -13,11 +14,14 @@ const { Title } = Typography;
const Configuration = () => {
const loading = useSelectKnowledgeDetailsLoading();
const { form, chunkMethod } = useHandleChunkMethodChange();
const { t } = useTranslate('knowledgeConfiguration');
return (
<div className={styles.configurationWrapper}>
<Title level={5}>Configuration</Title>
<p>Update your knowledge base details especially parsing method here.</p>
<Title level={5}>
{t('configuration', { keyPrefix: 'knowledgeDetails' })}
</Title>
<p>{t('titleDescription')}</p>
<Divider></Divider>
<Spin spinning={loading}>
<Row gutter={32}>

View File

@ -8,8 +8,9 @@ import { getWidth } from '@/utils';
import { Avatar, Menu, MenuProps, Space } from 'antd';
import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useSelector } from 'umi';
import { KnowledgeRouteKey, routeMap } from '../../constant';
import { KnowledgeRouteKey } from '../../constant';
import styles from './index.less';
const KnowledgeSidebar = () => {
@ -23,6 +24,7 @@ const KnowledgeSidebar = () => {
const [windowWidth, setWindowWidth] = useState(getWidth());
const [collapsed, setCollapsed] = useState(false);
const { t } = useTranslation();
const handleSelect: MenuProps['onSelect'] = (e) => {
navigate(`/knowledge/${e.key}?id=${id}`);
@ -32,7 +34,7 @@ const KnowledgeSidebar = () => {
const getItem = useCallback(
(
label: React.ReactNode,
label: string,
key: React.Key,
icon?: React.ReactNode,
disabled?: boolean,
@ -43,28 +45,28 @@ const KnowledgeSidebar = () => {
key,
icon,
children,
label,
label: t(`knowledgeDetails.${label}`),
type,
disabled,
} as MenuItem;
},
[],
[t],
);
const items: MenuItem[] = useMemo(() => {
return [
getItem(
routeMap[KnowledgeRouteKey.Dataset], // TODO: Change icon color when selected
KnowledgeRouteKey.Dataset, // TODO: Change icon color when selected
KnowledgeRouteKey.Dataset,
<DatasetIcon />,
),
getItem(
routeMap[KnowledgeRouteKey.Testing],
KnowledgeRouteKey.Testing,
KnowledgeRouteKey.Testing,
<TestingIcon />,
),
getItem(
routeMap[KnowledgeRouteKey.Configuration],
KnowledgeRouteKey.Configuration,
KnowledgeRouteKey.Configuration,
<ConfigurationIcon />,
),

View File

@ -1,7 +1,8 @@
import SimilaritySlider from '@/components/similarity-slider';
import { Button, Card, Divider, Flex, Form, Input, Slider, Tag } from 'antd';
import { Button, Card, Divider, Flex, Form, Input, Slider } from 'antd';
import { FormInstance } from 'antd/lib';
import { useTranslate } from '@/hooks/commonHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import styles from './index.less';
@ -22,6 +23,7 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
const loading = useOneNamespaceEffectsLoading('testingModel', [
'testDocumentChunk',
]);
const { t } = useTranslate('knowledgeDetails');
const buttonDisabled =
!question || (typeof question === 'string' && question.trim() === '');
@ -29,9 +31,9 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
return (
<section className={styles.testingControlWrapper}>
<div>
<b>Retrieval testing</b>
<b>{t('testing')}</b>
</div>
<p>Final step! After success, leave the rest to Infiniflow AI.</p>
<p>{t('testingDescription')}</p>
<Divider></Divider>
<section>
<Form
@ -46,22 +48,18 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
<Form.Item<FieldType>
label="Top K"
name={'top_k'}
tooltip="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."
tooltip={t('topKTip')}
>
<Slider marks={{ 0: 0, 2048: 2048 }} max={2048} />
</Form.Item>
<Card size="small" title="Test text">
<Card size="small" title={t('testText')}>
<Form.Item<FieldType>
name={'question'}
rules={[
{ required: true, message: 'Please input your question!' },
]}
rules={[{ required: true, message: t('testTextPlaceholder') }]}
>
<Input.TextArea autoSize={{ minRows: 8 }}></Input.TextArea>
</Form.Item>
<Flex justify={'space-between'}>
<Tag>10/200</Tag>
<Flex justify={'end'}>
<Button
type="primary"
size="small"
@ -69,7 +67,7 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
disabled={buttonDisabled}
loading={loading}
>
Testing
{t('testingLabel')}
</Button>
</Flex>
</Card>

View File

@ -1,5 +1,6 @@
import { ReactComponent as SelectedFilesCollapseIcon } from '@/assets/svg/selected-files-collapse.svg';
import Image from '@/components/image';
import { useTranslate } from '@/hooks/commonHooks';
import { ITestingChunk } from '@/interfaces/database/knowledge';
import {
Card,
@ -10,11 +11,13 @@ import {
Popover,
Space,
} from 'antd';
import camelCase from 'lodash/camelCase';
import { useDispatch, useSelector } from 'umi';
import { TestingModelState } from '../model';
import styles from './index.less';
import SelectFiles from './select-files';
import styles from './index.less';
const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [
{ field: 'similarity', label: 'Hybrid Similarity' },
{ field: 'term_similarity', label: 'Term Similarity' },
@ -22,6 +25,7 @@ const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [
];
const ChunkTitle = ({ item }: { item: ITestingChunk }) => {
const { t } = useTranslate('knowledgeDetails');
return (
<Flex gap={10}>
{similarityList.map((x) => (
@ -29,7 +33,7 @@ const ChunkTitle = ({ item }: { item: ITestingChunk }) => {
<span className={styles.similarityCircle}>
{((item[x.field] as number) * 100).toFixed(2)}
</span>
<span className={styles.similarityText}>{x.label}</span>
<span className={styles.similarityText}>{t(camelCase(x.field))}</span>
</Space>
))}
</Flex>
@ -49,6 +53,7 @@ const TestingResult = ({ handleTesting }: IProps) => {
selectedDocumentIds,
}: TestingModelState = useSelector((state: any) => state.testingModel);
const dispatch = useDispatch();
const { t } = useTranslate('knowledgeDetails');
const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
console.log('Page: ', pageNumber, pageSize);
@ -75,13 +80,15 @@ const TestingResult = ({ handleTesting }: IProps) => {
align="center"
className={styles.selectFilesTitle}
>
<span>
{selectedDocumentIds?.length ?? 0}/{documents.length} Files
Selected
</span>
<Space>
<span>
{selectedDocumentIds?.length ?? 0}/{documents.length}
</span>
{t('filesSelected')}
</Space>
<Space size={52}>
<b>Hits</b>
<b>View</b>
<b>{t('hits')}</b>
<b>{t('view')}</b>
</Space>
</Flex>
),

View File

@ -7,6 +7,7 @@ import {
import { Breadcrumb } from 'antd';
import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
import { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, Outlet, useDispatch, useLocation } from 'umi';
import Siderbar from './components/knowledge-sidebar';
import {
@ -21,6 +22,7 @@ const KnowledgeAdding = () => {
const dispatch = useDispatch();
const knowledgeBaseId = useKnowledgeBaseId();
const { t } = useTranslation();
const location = useLocation();
const activeKey: KnowledgeRouteKey =
(useSecondPathName() as KnowledgeRouteKey) || KnowledgeRouteKey.Dataset;
@ -33,14 +35,18 @@ const KnowledgeAdding = () => {
const breadcrumbItems: ItemType[] = useMemo(() => {
const items: ItemType[] = [
{
title: <a onClick={() => gotoList('/knowledge')}>Knowledge Base</a>,
title: (
<a onClick={() => gotoList('/knowledge')}>
{t('header.knowledgeBase')}
</a>
),
},
{
title: datasetActiveKey ? (
<Link
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
>
{routeMap[activeKey]}
{t(`knowledgeDetails.${activeKey}`)}
</Link>
) : (
routeMap[activeKey]
@ -55,7 +61,7 @@ const KnowledgeAdding = () => {
}
return items;
}, [activeKey, datasetActiveKey, gotoList, knowledgeBaseId]);
}, [activeKey, datasetActiveKey, gotoList, knowledgeBaseId, t]);
useEffect(() => {
const search: string = location.search.slice(1);

View File

@ -10,6 +10,7 @@ import {
UserOutlined,
} from '@ant-design/icons';
import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd';
import { useTranslation } from 'react-i18next';
import { useDispatch, useNavigate } from 'umi';
import styles from './index.less';
@ -22,6 +23,7 @@ const KnowledgeCard = ({ item }: IProps) => {
const navigate = useNavigate();
const dispatch = useDispatch();
const showDeleteConfirm = useShowDeleteConfirm();
const { t } = useTranslation();
const removeKnowledge = () => {
return dispatch({
@ -41,7 +43,7 @@ const KnowledgeCard = ({ item }: IProps) => {
key: '1',
label: (
<Space>
Delete
{t('common.delete')}
<DeleteOutlined />
</Space>
),
@ -87,7 +89,10 @@ const KnowledgeCard = ({ item }: IProps) => {
<div className={styles.bottomLeft}>
<FileTextOutlined className={styles.leftIcon} />
<span className={styles.rightText}>
<Space>{item.doc_num}Docs</Space>
<Space>
{item.doc_num}
{t('knowledgeList.doc')}
</Space>
</span>
</div>
</div>