| img_id: string; | img_id: string; | ||||
| important_kwd: any[]; | 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; | |||||
| } | 
| import { Flex } from 'antd'; | |||||
| import { Flex, Form } from 'antd'; | |||||
| import TestingControl from './testing-control'; | import TestingControl from './testing-control'; | ||||
| import TestingResult from './testing-result'; | import TestingResult from './testing-result'; | ||||
| import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; | |||||
| import { useEffect } from 'react'; | |||||
| import { useDispatch } from 'umi'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const KnowledgeTesting = () => { | 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 ( | return ( | ||||
| <Flex className={styles.testingWrapper} gap={16}> | <Flex className={styles.testingWrapper} gap={16}> | ||||
| <TestingControl></TestingControl> | |||||
| <TestingResult></TestingResult> | |||||
| <TestingControl | |||||
| form={form} | |||||
| handleTesting={handleTesting} | |||||
| ></TestingControl> | |||||
| <TestingResult handleTesting={handleTesting}></TestingResult> | |||||
| </Flex> | </Flex> | ||||
| ); | ); | ||||
| }; | }; | 
| 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; | 
| width: 350px; | width: 350px; | ||||
| background-color: white; | background-color: white; | ||||
| padding: 30px 20px; | padding: 30px 20px; | ||||
| overflow: auto; | |||||
| height: calc(100vh - 160px); | |||||
| .historyTitle { | .historyTitle { | ||||
| padding: 30px 0 20px; | padding: 30px 0 20px; | ||||
| } | } | 
| Card, | Card, | ||||
| Divider, | Divider, | ||||
| Flex, | Flex, | ||||
| Form, | |||||
| Input, | Input, | ||||
| Slider, | Slider, | ||||
| SliderSingleProps, | SliderSingleProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { DeleteOutlined, HistoryOutlined } from '@ant-design/icons'; | import { DeleteOutlined, HistoryOutlined } from '@ant-design/icons'; | ||||
| import { FormInstance } from 'antd/lib'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const list = [1, 2, 3]; | const list = [1, 2, 3]; | ||||
| const marks: SliderSingleProps['marks'] = { | 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 ( | return ( | ||||
| <section className={styles.testingControlWrapper}> | <section className={styles.testingControlWrapper}> | ||||
| <p> | <p> | ||||
| <b>Retrieval testing</b> | <b>Retrieval testing</b> | ||||
| </p> | </p> | ||||
| <p>xxxx</p> | |||||
| <p>Final step! After success, leave the rest to Infiniflow AI.</p> | |||||
| <Divider></Divider> | <Divider></Divider> | ||||
| <section> | <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> | ||||
| <section> | <section> | ||||
| <p className={styles.historyTitle}> | <p className={styles.historyTitle}> | 
| flex: 1; | flex: 1; | ||||
| background-color: white; | background-color: white; | ||||
| padding: 30px 20px; | padding: 30px 20px; | ||||
| overflow: auto; | |||||
| height: calc(100vh - 160px); | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| .selectFilesCollapse { | .selectFilesCollapse { | ||||
| :global(.ant-collapse-header) { | :global(.ant-collapse-header) { | ||||
| padding-left: 22px; | padding-left: 22px; | ||||
| } | } | ||||
| margin-bottom: 32px; | margin-bottom: 32px; | ||||
| overflow-y: auto; | |||||
| } | } | ||||
| .selectFilesTitle { | .selectFilesTitle { | ||||
| padding-right: 10px; | 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; | |||||
| } | |||||
| } | } | 
| import { ReactComponent as SelectedFilesCollapseIcon } from '@/assets/svg/selected-files-collapse.svg'; | 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 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 ( | return ( | ||||
| <section className={styles.testingResultWrapper}> | <section className={styles.testingResultWrapper}> | ||||
| <Collapse | <Collapse | ||||
| align="center" | align="center" | ||||
| className={styles.selectFilesTitle} | className={styles.selectFilesTitle} | ||||
| > | > | ||||
| <span>4/25 Files Selected</span> | |||||
| <span> | |||||
| {selectedDocumentIds?.length ?? 0}/{documents.length} Files | |||||
| Selected | |||||
| </span> | |||||
| <Space size={52}> | <Space size={52}> | ||||
| <b>Hits</b> | <b>Hits</b> | ||||
| <b>View</b> | <b>View</b> | ||||
| ), | ), | ||||
| children: ( | children: ( | ||||
| <div> | <div> | ||||
| <SelectFiles></SelectFiles> | |||||
| <SelectFiles handleTesting={handleTesting}></SelectFiles> | |||||
| </div> | </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> | </Card> | ||||
| ))} | ))} | ||||
| </Flex> | </Flex> | ||||
| <Pagination | |||||
| size={'small'} | |||||
| showQuickJumper | |||||
| current={pagination.current} | |||||
| pageSize={pagination.pageSize} | |||||
| total={total} | |||||
| showSizeChanger | |||||
| onChange={onChange} | |||||
| /> | |||||
| </section> | </section> | ||||
| ); | ); | ||||
| }; | }; | 
| import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg'; | 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 { 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', | title: 'Name', | ||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| dataIndex: 'doc_name', | |||||
| key: 'doc_name', | |||||
| render: (text) => <p>{text}</p>, | render: (text) => <p>{text}</p>, | ||||
| }, | }, | ||||
| { | { | ||||
| title: 'Hits', | title: 'Hits', | ||||
| dataIndex: 'hits', | |||||
| key: 'hits', | |||||
| dataIndex: 'count', | |||||
| key: 'count', | |||||
| width: 80, | width: 80, | ||||
| }, | }, | ||||
| { | { | ||||
| title: 'View', | title: 'View', | ||||
| key: 'view', | key: 'view', | ||||
| width: 50, | width: 50, | ||||
| render: () => <NavigationPointerIcon />, | |||||
| render: (_, { doc_id }) => ( | |||||
| <a | |||||
| href={`${api_host}/document/get/${doc_id}`} | |||||
| target="_blank" | |||||
| rel="noreferrer" | |||||
| > | |||||
| <NavigationPointerIcon /> | |||||
| </a> | |||||
| ), | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| const rowSelection = { | 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 ( | return ( | ||||
| <Table | <Table | ||||
| columns={columns} | columns={columns} | ||||
| dataSource={data} | |||||
| dataSource={documents} | |||||
| showHeader={false} | showHeader={false} | ||||
| rowSelection={rowSelection} | rowSelection={rowSelection} | ||||
| rowKey={'doc_id'} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | 
| let api_host = `http://223.111.148.200:9380/v1`; | |||||
| let api_host = `http://123.60.95.134:9380/v1`; | |||||
| export { api_host }; | export { api_host }; | ||||