diff --git a/web/src/interfaces/database/knowledge.ts b/web/src/interfaces/database/knowledge.ts index a98a04bec90593f1ca35e9fe9155c53fdf87cbeb..68b88da6d9dd5a926776e9049f4fddb939d67e1a 100644 --- a/web/src/interfaces/database/knowledge.ts +++ b/web/src/interfaces/database/knowledge.ts @@ -75,3 +75,30 @@ export interface IChunk { img_id: string; important_kwd: any[]; } + +export interface ITestingChunk { + chunk_id: string; + content_ltks: string; + content_with_weight: string; + doc_id: string; + docnm_kwd: string; + img_id: string; + important_kwd: any[]; + kb_id: string; + similarity: number; + term_similarity: number; + vector: number[]; + vector_similarity: number; +} + +export interface ITestingDocument { + count: number; + doc_id: string; + doc_name: string; +} + +export interface ITestingResult { + chunks: ITestingChunk[]; + doc_aggs: Record<string, number>; + total: number; +} diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx index 5c4d22d98f09cc9b10c0a9065800f203a3617bbe..e3dd8625ffe95cd0a7cfb97723f0f869b94a32df 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx @@ -1,14 +1,47 @@ -import { Flex } from 'antd'; +import { Flex, Form } from 'antd'; import TestingControl from './testing-control'; import TestingResult from './testing-result'; +import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; +import { useEffect } from 'react'; +import { useDispatch } from 'umi'; import styles from './index.less'; const KnowledgeTesting = () => { + const [form] = Form.useForm(); + + const dispatch = useDispatch(); + const knowledgeBaseId = useKnowledgeBaseId(); + + const handleTesting = async () => { + const values = await form.validateFields(); + console.info(values); + const similarity_threshold = values.similarity_threshold / 100; + const vector_similarity_weight = values.vector_similarity_weight / 100; + dispatch({ + type: 'testingModel/testDocumentChunk', + payload: { + ...values, + similarity_threshold, + vector_similarity_weight, + kb_id: knowledgeBaseId, + }, + }); + }; + + useEffect(() => { + return () => { + dispatch({ type: 'testingModel/reset' }); + }; + }, [dispatch]); + return ( <Flex className={styles.testingWrapper} gap={16}> - <TestingControl></TestingControl> - <TestingResult></TestingResult> + <TestingControl + form={form} + handleTesting={handleTesting} + ></TestingControl> + <TestingResult handleTesting={handleTesting}></TestingResult> </Flex> ); }; diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/model.ts b/web/src/pages/add-knowledge/components/knowledge-testing/model.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac7580700ffd69b8e13ed7d84790e25a56bf16d9 --- /dev/null +++ b/web/src/pages/add-knowledge/components/knowledge-testing/model.ts @@ -0,0 +1,72 @@ +import { BaseState } from '@/interfaces/common'; +import { + ITestingChunk, + ITestingDocument, +} from '@/interfaces/database/knowledge'; +import kbService from '@/services/kbService'; +import { DvaModel } from 'umi'; + +export interface TestingModelState extends Pick<BaseState, 'pagination'> { + chunks: ITestingChunk[]; + documents: ITestingDocument[]; + total: number; + selectedDocumentIds: string[] | undefined; +} + +const initialState = { + chunks: [], + documents: [], + total: 0, + pagination: { + current: 1, + pageSize: 10, + }, + selectedDocumentIds: undefined, +}; + +const model: DvaModel<TestingModelState> = { + namespace: 'testingModel', + state: initialState, + reducers: { + setChunksAndDocuments(state, { payload }) { + return { + ...state, + ...payload, + }; + }, + setPagination(state, { payload }) { + return { ...state, pagination: { ...state.pagination, ...payload } }; + }, + setSelectedDocumentIds(state, { payload }) { + return { ...state, selectedDocumentIds: payload }; + }, + reset() { + return initialState; + }, + }, + effects: { + *testDocumentChunk({ payload = {} }, { call, put, select }) { + const { pagination, selectedDocumentIds }: TestingModelState = + yield select((state: any) => state.testingModel); + + const { data } = yield call(kbService.retrieval_test, { + ...payload, + doc_ids: selectedDocumentIds, + page: pagination.current, + size: pagination.pageSize, + }); + const { retcode, data: res } = data; + if (retcode === 0) { + yield put({ + type: 'setChunksAndDocuments', + payload: { + chunks: res.chunks, + documents: res.doc_aggs, + total: res.total, + }, + }); + } + }, + }, +}; +export default model; diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.less b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.less index 42842a27801cc41b48abff99da5bf6508a40f8af..2e9f01a12833443a7f7a9e6d74327e67c910db68 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.less +++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.less @@ -2,6 +2,9 @@ width: 350px; background-color: white; padding: 30px 20px; + overflow: auto; + height: calc(100vh - 160px); + .historyTitle { padding: 30px 0 20px; } diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx index 97ef9d5f24e4e5572c95d1d9f7634d8562b632ff..376feffb78b83a2b13f716fcc4c3d98be3bd8fe7 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx @@ -3,6 +3,7 @@ import { Card, Divider, Flex, + Form, Input, Slider, SliderSingleProps, @@ -11,50 +12,95 @@ import { } from 'antd'; import { DeleteOutlined, HistoryOutlined } from '@ant-design/icons'; +import { FormInstance } from 'antd/lib'; import styles from './index.less'; const list = [1, 2, 3]; const marks: SliderSingleProps['marks'] = { - 0: '0°C', - 26: '26°C', - 37: '37°C', - 100: { - style: { - color: '#f50', - }, - label: <strong>100°C</strong>, - }, + 0: '0', + 100: '1', }; -const TestingControl = () => { +type FieldType = { + similarity_threshold?: number; + vector_similarity_weight?: number; + top_k?: number; + question: string; +}; + +const formatter = (value: number | undefined) => { + return typeof value === 'number' ? value / 100 : 0; +}; + +const tooltip = { formatter }; + +interface IProps { + form: FormInstance; + handleTesting: () => Promise<any>; +} + +const TestingControl = ({ form, handleTesting }: IProps) => { + const question = Form.useWatch('question', { form, preserve: true }); + + const buttonDisabled = + !question || (typeof question === 'string' && question.trim() === ''); + return ( <section className={styles.testingControlWrapper}> <p> <b>Retrieval testing</b> </p> - <p>xxxx</p> + <p>Final step! After success, leave the rest to Infiniflow AI.</p> <Divider></Divider> <section> - <Slider range marks={marks} defaultValue={[26, 37]} /> - <Slider range marks={marks} defaultValue={[26, 37]} /> - <Card - size="small" - title="Test text" - extra={ - <Button type="primary" ghost> - Semantic Search - </Button> - } + <Form + name="testing" + layout="vertical" + form={form} + initialValues={{ + similarity_threshold: 20, + vector_similarity_weight: 30, + top_k: 1024, + }} > - <Input.TextArea autoSize={{ minRows: 8 }}></Input.TextArea> - <Flex justify={'space-between'}> - <Tag>10/200</Tag> - <Button type="primary" size="small"> - Testing - </Button> - </Flex> - </Card> + <Form.Item<FieldType> + label="Similarity threshold" + name={'similarity_threshold'} + > + <Slider marks={marks} defaultValue={0} tooltip={tooltip} /> + </Form.Item> + <Form.Item<FieldType> + label="Vector similarity weight" + name={'vector_similarity_weight'} + > + <Slider marks={marks} defaultValue={0} tooltip={tooltip} /> + </Form.Item> + <Form.Item<FieldType> label="Top k" name={'top_k'}> + <Slider marks={{ 0: 0, 2048: 2048 }} defaultValue={0} max={2048} /> + </Form.Item> + <Card size="small" title="Test text"> + <Form.Item<FieldType> + name={'question'} + rules={[ + { required: true, message: 'Please input your question!' }, + ]} + > + <Input.TextArea autoSize={{ minRows: 8 }}></Input.TextArea> + </Form.Item> + <Flex justify={'space-between'}> + <Tag>10/200</Tag> + <Button + type="primary" + size="small" + onClick={handleTesting} + disabled={buttonDisabled} + > + Testing + </Button> + </Flex> + </Card> + </Form> </section> <section> <p className={styles.historyTitle}> diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.less b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.less index 2a1d8c01fa3545e3c4066e49ec2f068b91ffd4dc..46ef353dc8828c47543703f79237ecb2ed8db118 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.less +++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.less @@ -2,15 +2,35 @@ flex: 1; background-color: white; padding: 30px 20px; + overflow: auto; + height: calc(100vh - 160px); + display: flex; + flex-direction: column; + justify-content: space-between; .selectFilesCollapse { :global(.ant-collapse-header) { padding-left: 22px; } margin-bottom: 32px; + overflow-y: auto; } .selectFilesTitle { padding-right: 10px; } + + .similarityCircle { + width: 24px; + height: 24px; + border-radius: 50%; + background-color: rgba(244, 235, 255, 1); + font-size: 10px; + font-weight: normal; + } + + .similarityText { + font-size: 12px; + font-weight: 500; + } } diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx index 14a04ac96b1347bf8f867c4d5a577f19e692d754..dbe7b22aa7fab25635e7ba56785dd3604eda6009 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx @@ -1,12 +1,55 @@ import { ReactComponent as SelectedFilesCollapseIcon } from '@/assets/svg/selected-files-collapse.svg'; -import { Card, Collapse, Flex, Space } from 'antd'; +import { ITestingChunk } from '@/interfaces/database/knowledge'; +import { Card, Collapse, Flex, Pagination, PaginationProps, Space } from 'antd'; +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' }, + { field: 'vector_similarity', label: 'Vector Similarity' }, +]; + +const ChunkTitle = ({ item }: { item: ITestingChunk }) => { + return ( + <Flex gap={10}> + {similarityList.map((x) => ( + <Space key={x.field}> + <span className={styles.similarityCircle}> + {((item[x.field] as number) * 100).toFixed(2)}% + </span> + <span className={styles.similarityText}>Hybrid Similarity</span> + </Space> + ))} + </Flex> + ); +}; + +interface IProps { + handleTesting: () => Promise<any>; +} + +const TestingResult = ({ handleTesting }: IProps) => { + const { + documents, + chunks, + total, + pagination, + selectedDocumentIds, + }: TestingModelState = useSelector((state: any) => state.testingModel); + const dispatch = useDispatch(); -const list = [1, 2, 3, 4]; + const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => { + console.log('Page: ', pageNumber, pageSize); + dispatch({ + type: 'testingModel/setPagination', + payload: { current: pageNumber, pageSize }, + }); + handleTesting(); + }; -const TestingResult = () => { return ( <section className={styles.testingResultWrapper}> <Collapse @@ -23,7 +66,10 @@ const TestingResult = () => { align="center" className={styles.selectFilesTitle} > - <span>4/25 Files Selected</span> + <span> + {selectedDocumentIds?.length ?? 0}/{documents.length} Files + Selected + </span> <Space size={52}> <b>Hits</b> <b>View</b> @@ -32,21 +78,33 @@ const TestingResult = () => { ), children: ( <div> - <SelectFiles></SelectFiles> + <SelectFiles handleTesting={handleTesting}></SelectFiles> </div> ), }, ]} /> - <Flex gap={'large'} vertical> - {list.map((x) => ( - <Card key={x} title="Default size card" extra={<a href="#">More</a>}> - <p>Card content</p> - <p>Card content</p> - <p>Card content</p> + <Flex + gap={'large'} + vertical + flex={1} + className={styles.selectFilesCollapse} + > + {chunks.map((x) => ( + <Card key={x.chunk_id} title={<ChunkTitle item={x}></ChunkTitle>}> + <div>{x.content_with_weight}</div> </Card> ))} </Flex> + <Pagination + size={'small'} + showQuickJumper + current={pagination.current} + pageSize={pagination.pageSize} + total={total} + showSizeChanger + onChange={onChange} + /> </section> ); }; diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx index 0aee3dc1330e958d513d1eba466c595a881fe1a0..ab5d06c9c2b9640d186108b49738861a372407a3 100644 --- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx @@ -1,80 +1,71 @@ import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg'; +import { ITestingDocument } from '@/interfaces/database/knowledge'; +import { api_host } from '@/utils/api'; import { Table, TableProps } from 'antd'; +import { useDispatch, useSelector } from 'umi'; -interface DataType { - key: string; - name: string; - hits: number; - address: string; - tags: string[]; +interface IProps { + handleTesting: () => Promise<any>; } -const SelectFiles = () => { - const columns: TableProps<DataType>['columns'] = [ +const SelectFiles = ({ handleTesting }: IProps) => { + const documents: ITestingDocument[] = useSelector( + (state: any) => state.testingModel.documents, + ); + + const dispatch = useDispatch(); + + const columns: TableProps<ITestingDocument>['columns'] = [ { title: 'Name', - dataIndex: 'name', - key: 'name', + dataIndex: 'doc_name', + key: 'doc_name', render: (text) => <p>{text}</p>, }, { title: 'Hits', - dataIndex: 'hits', - key: 'hits', + dataIndex: 'count', + key: 'count', width: 80, }, { title: 'View', key: 'view', width: 50, - render: () => <NavigationPointerIcon />, + render: (_, { doc_id }) => ( + <a + href={`${api_host}/document/get/${doc_id}`} + target="_blank" + rel="noreferrer" + > + <NavigationPointerIcon /> + </a> + ), }, ]; const rowSelection = { - onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => { - console.log( - `selectedRowKeys: ${selectedRowKeys}`, - 'selectedRows: ', - selectedRows, - ); + onChange: (selectedRowKeys: React.Key[]) => { + dispatch({ + type: 'testingModel/setSelectedDocumentIds', + payload: selectedRowKeys, + }); + handleTesting(); }, - getCheckboxProps: (record: DataType) => ({ - disabled: record.name === 'Disabled User', // Column configuration not to be checked - name: record.name, + getCheckboxProps: (record: ITestingDocument) => ({ + disabled: record.doc_name === 'Disabled User', // Column configuration not to be checked + name: record.doc_name, }), }; - const data: DataType[] = [ - { - key: '1', - name: 'John Brown', - hits: 32, - address: 'New York No. 1 Lake Park', - tags: ['nice', 'developer'], - }, - { - key: '2', - name: 'Jim Green', - hits: 42, - address: 'London No. 1 Lake Park', - tags: ['loser'], - }, - { - key: '3', - name: 'Joe Black', - hits: 32, - address: 'Sydney No. 1 Lake Park', - tags: ['cool', 'teacher'], - }, - ]; return ( <Table columns={columns} - dataSource={data} + dataSource={documents} showHeader={false} rowSelection={rowSelection} + rowKey={'doc_id'} /> ); }; diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index d7634efa04a1f65ca2d0b9c0dadceb7650c781d8..d0afb184765474d619ee3a6c40d6a02fe36baa6e 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -1,4 +1,4 @@ -let api_host = `http://223.111.148.200:9380/v1`; +let api_host = `http://123.60.95.134:9380/v1`; export { api_host };