From 1a156e6569ba94bd3b156b6da18fdfb40daf08c4 Mon Sep 17 00:00:00 2001
From: balibabu <cike8899@users.noreply.github.com>
Date: Thu, 8 Feb 2024 18:12:31 +0800
Subject: [PATCH] feat: test document chunks (#62)

---
 web/src/interfaces/database/knowledge.ts      |  27 +++++
 .../components/knowledge-testing/index.tsx    |  39 ++++++-
 .../components/knowledge-testing/model.ts     |  72 ++++++++++++
 .../testing-control/index.less                |   3 +
 .../testing-control/index.tsx                 | 104 +++++++++++++-----
 .../testing-result/index.less                 |  20 ++++
 .../testing-result/index.tsx                  |  82 ++++++++++++--
 .../testing-result/select-files.tsx           |  83 +++++++-------
 web/src/utils/api.ts                          |   2 +-
 9 files changed, 341 insertions(+), 91 deletions(-)
 create mode 100644 web/src/pages/add-knowledge/components/knowledge-testing/model.ts

diff --git a/web/src/interfaces/database/knowledge.ts b/web/src/interfaces/database/knowledge.ts
index a98a04b..68b88da 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 5c4d22d..e3dd862 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 0000000..ac75807
--- /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 42842a2..2e9f01a 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 97ef9d5..376feff 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 2a1d8c0..46ef353 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 14a04ac..dbe7b22 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 0aee3dc..ab5d06c 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 d7634ef..d0afb18 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 };
 
-- 
GitLab