balibabu commited on
Commit
b6c8684
·
1 Parent(s): f6ddf72

Feat: Scrolling knowledge base list and set the number of entries per page to 30 #3695 (#3712)

Browse files

### What problem does this PR solve?

Feat: Scrolling knowledge base list and set the number of entries per
page to 30 #3695

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

api/apps/kb_app.py CHANGED
@@ -125,15 +125,16 @@ def detail():
125
  @manager.route('/list', methods=['GET'])
126
  @login_required
127
  def list_kbs():
 
128
  page_number = int(request.args.get("page", 1))
129
  items_per_page = int(request.args.get("page_size", 150))
130
  orderby = request.args.get("orderby", "create_time")
131
  desc = request.args.get("desc", True)
132
  try:
133
  tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
134
- kbs = KnowledgebaseService.get_by_tenant_ids(
135
- [m["tenant_id"] for m in tenants], current_user.id, page_number, items_per_page, orderby, desc)
136
- return get_json_result(data=kbs)
137
  except Exception as e:
138
  return server_error_response(e)
139
 
 
125
  @manager.route('/list', methods=['GET'])
126
  @login_required
127
  def list_kbs():
128
+ keywords = request.args.get("keywords", "")
129
  page_number = int(request.args.get("page", 1))
130
  items_per_page = int(request.args.get("page_size", 150))
131
  orderby = request.args.get("orderby", "create_time")
132
  desc = request.args.get("desc", True)
133
  try:
134
  tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
135
+ kbs, total = KnowledgebaseService.get_by_tenant_ids(
136
+ [m["tenant_id"] for m in tenants], current_user.id, page_number, items_per_page, orderby, desc, keywords)
137
+ return get_json_result(data={"kbs": kbs, "total": total})
138
  except Exception as e:
139
  return server_error_response(e)
140
 
api/db/services/knowledgebase_service.py CHANGED
@@ -16,6 +16,7 @@
16
  from api.db import StatusEnum, TenantPermission
17
  from api.db.db_models import Knowledgebase, DB, Tenant, User, UserTenant,Document
18
  from api.db.services.common_service import CommonService
 
19
 
20
 
21
  class KnowledgebaseService(CommonService):
@@ -34,7 +35,7 @@ class KnowledgebaseService(CommonService):
34
  @classmethod
35
  @DB.connection_context()
36
  def get_by_tenant_ids(cls, joined_tenant_ids, user_id,
37
- page_number, items_per_page, orderby, desc):
38
  fields = [
39
  cls.model.id,
40
  cls.model.avatar,
@@ -51,20 +52,31 @@ class KnowledgebaseService(CommonService):
51
  User.avatar.alias('tenant_avatar'),
52
  cls.model.update_time
53
  ]
54
- kbs = cls.model.select(*fields).join(User, on=(cls.model.tenant_id == User.id)).where(
55
- ((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
56
- TenantPermission.TEAM.value)) | (
57
- cls.model.tenant_id == user_id))
58
- & (cls.model.status == StatusEnum.VALID.value)
59
- )
 
 
 
 
 
 
 
 
 
60
  if desc:
61
  kbs = kbs.order_by(cls.model.getter_by(orderby).desc())
62
  else:
63
  kbs = kbs.order_by(cls.model.getter_by(orderby).asc())
64
 
 
 
65
  kbs = kbs.paginate(page_number, items_per_page)
66
 
67
- return list(kbs.dicts())
68
 
69
  @classmethod
70
  @DB.connection_context()
 
16
  from api.db import StatusEnum, TenantPermission
17
  from api.db.db_models import Knowledgebase, DB, Tenant, User, UserTenant,Document
18
  from api.db.services.common_service import CommonService
19
+ from peewee import fn
20
 
21
 
22
  class KnowledgebaseService(CommonService):
 
35
  @classmethod
36
  @DB.connection_context()
37
  def get_by_tenant_ids(cls, joined_tenant_ids, user_id,
38
+ page_number, items_per_page, orderby, desc, keywords):
39
  fields = [
40
  cls.model.id,
41
  cls.model.avatar,
 
52
  User.avatar.alias('tenant_avatar'),
53
  cls.model.update_time
54
  ]
55
+ if keywords:
56
+ kbs = cls.model.select(*fields).join(User, on=(cls.model.tenant_id == User.id)).where(
57
+ ((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
58
+ TenantPermission.TEAM.value)) | (
59
+ cls.model.tenant_id == user_id))
60
+ & (cls.model.status == StatusEnum.VALID.value),
61
+ (fn.LOWER(cls.model.name).contains(keywords.lower()))
62
+ )
63
+ else:
64
+ kbs = cls.model.select(*fields).join(User, on=(cls.model.tenant_id == User.id)).where(
65
+ ((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
66
+ TenantPermission.TEAM.value)) | (
67
+ cls.model.tenant_id == user_id))
68
+ & (cls.model.status == StatusEnum.VALID.value)
69
+ )
70
  if desc:
71
  kbs = kbs.order_by(cls.model.getter_by(orderby).desc())
72
  else:
73
  kbs = kbs.order_by(cls.model.getter_by(orderby).asc())
74
 
75
+ count = kbs.count()
76
+
77
  kbs = kbs.paginate(page_number, items_per_page)
78
 
79
+ return list(kbs.dicts()), count
80
 
81
  @classmethod
82
  @DB.connection_context()
sdk/python/test/test_frontend_api/test_dataset.py CHANGED
@@ -13,7 +13,7 @@ def test_dataset(get_auth):
13
  while True:
14
  res = list_dataset(get_auth, page_number)
15
  data = res.get("data")
16
- for item in data:
17
  dataset_id = item.get("id")
18
  dataset_list.append(dataset_id)
19
  if len(dataset_list) < page_number * 150:
@@ -42,7 +42,7 @@ def test_dataset_1k_dataset(get_auth):
42
  while True:
43
  res = list_dataset(get_auth, page_number)
44
  data = res.get("data")
45
- for item in data:
46
  dataset_id = item.get("id")
47
  dataset_list.append(dataset_id)
48
  if len(dataset_list) < page_number * 150:
 
13
  while True:
14
  res = list_dataset(get_auth, page_number)
15
  data = res.get("data")
16
+ for item in data.get("kbs"):
17
  dataset_id = item.get("id")
18
  dataset_list.append(dataset_id)
19
  if len(dataset_list) < page_number * 150:
 
42
  while True:
43
  res = list_dataset(get_auth, page_number)
44
  data = res.get("data")
45
+ for item in data.get("kbs"):
46
  dataset_id = item.get("id")
47
  dataset_list.append(dataset_id)
48
  if len(dataset_list) < page_number * 150:
web/.umirc.ts CHANGED
@@ -34,7 +34,7 @@ export default defineConfig({
34
  proxy: [
35
  {
36
  context: ['/api', '/v1'],
37
- target: 'http://127.0.0.1:9456/',
38
  changeOrigin: true,
39
  ws: true,
40
  logger: console,
 
34
  proxy: [
35
  {
36
  context: ['/api', '/v1'],
37
+ target: 'http://127.0.0.1:9380/',
38
  changeOrigin: true,
39
  ws: true,
40
  logger: console,
web/package-lock.json CHANGED
@@ -57,6 +57,7 @@
57
  "react-force-graph": "^1.44.4",
58
  "react-hook-form": "^7.53.1",
59
  "react-i18next": "^14.0.0",
 
60
  "react-markdown": "^9.0.1",
61
  "react-pdf-highlighter": "^6.1.0",
62
  "react-string-replace": "^1.1.1",
@@ -24705,6 +24706,25 @@
24705
  }
24706
  }
24707
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24708
  "node_modules/react-is": {
24709
  "version": "18.2.0",
24710
  "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz",
 
57
  "react-force-graph": "^1.44.4",
58
  "react-hook-form": "^7.53.1",
59
  "react-i18next": "^14.0.0",
60
+ "react-infinite-scroll-component": "^6.1.0",
61
  "react-markdown": "^9.0.1",
62
  "react-pdf-highlighter": "^6.1.0",
63
  "react-string-replace": "^1.1.1",
 
24706
  }
24707
  }
24708
  },
24709
+ "node_modules/react-infinite-scroll-component": {
24710
+ "version": "6.1.0",
24711
+ "resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
24712
+ "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
24713
+ "dependencies": {
24714
+ "throttle-debounce": "^2.1.0"
24715
+ },
24716
+ "peerDependencies": {
24717
+ "react": ">=16.0.0"
24718
+ }
24719
+ },
24720
+ "node_modules/react-infinite-scroll-component/node_modules/throttle-debounce": {
24721
+ "version": "2.3.0",
24722
+ "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
24723
+ "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==",
24724
+ "engines": {
24725
+ "node": ">=8"
24726
+ }
24727
+ },
24728
  "node_modules/react-is": {
24729
  "version": "18.2.0",
24730
  "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz",
web/package.json CHANGED
@@ -68,6 +68,7 @@
68
  "react-force-graph": "^1.44.4",
69
  "react-hook-form": "^7.53.1",
70
  "react-i18next": "^14.0.0",
 
71
  "react-markdown": "^9.0.1",
72
  "react-pdf-highlighter": "^6.1.0",
73
  "react-string-replace": "^1.1.1",
 
68
  "react-force-graph": "^1.44.4",
69
  "react-hook-form": "^7.53.1",
70
  "react-i18next": "^14.0.0",
71
+ "react-infinite-scroll-component": "^6.1.0",
72
  "react-markdown": "^9.0.1",
73
  "react-pdf-highlighter": "^6.1.0",
74
  "react-string-replace": "^1.1.1",
web/src/hooks/knowledge-hooks.ts CHANGED
@@ -3,14 +3,17 @@ import { IKnowledge, ITestingResult } from '@/interfaces/database/knowledge';
3
  import i18n from '@/locales/config';
4
  import kbService from '@/services/knowledge-service';
5
  import {
 
6
  useIsMutating,
7
  useMutation,
8
  useMutationState,
9
  useQuery,
10
  useQueryClient,
11
  } from '@tanstack/react-query';
 
12
  import { message } from 'antd';
13
  import { useSearchParams } from 'umi';
 
14
  import { useSetPaginationParams } from './route-hook';
15
 
16
  export const useKnowledgeBaseId = (): string => {
@@ -50,7 +53,7 @@ export const useNextFetchKnowledgeList = (
50
  gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
51
  queryFn: async () => {
52
  const { data } = await kbService.getList();
53
- const list = data?.data ?? [];
54
  return shouldFilterListWithoutDocument
55
  ? list.filter((x: IKnowledge) => x.chunk_num > 0)
56
  : list;
@@ -60,6 +63,52 @@ export const useNextFetchKnowledgeList = (
60
  return { list: data, loading };
61
  };
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  export const useCreateKnowledge = () => {
64
  const queryClient = useQueryClient();
65
  const {
@@ -95,7 +144,9 @@ export const useDeleteKnowledge = () => {
95
  const { data } = await kbService.rmKb({ kb_id: id });
96
  if (data.code === 0) {
97
  message.success(i18n.t(`message.deleted`));
98
- queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeList'] });
 
 
99
  }
100
  return data?.data ?? [];
101
  },
 
3
  import i18n from '@/locales/config';
4
  import kbService from '@/services/knowledge-service';
5
  import {
6
+ useInfiniteQuery,
7
  useIsMutating,
8
  useMutation,
9
  useMutationState,
10
  useQuery,
11
  useQueryClient,
12
  } from '@tanstack/react-query';
13
+ import { useDebounce } from 'ahooks';
14
  import { message } from 'antd';
15
  import { useSearchParams } from 'umi';
16
+ import { useHandleSearchChange } from './logic-hooks';
17
  import { useSetPaginationParams } from './route-hook';
18
 
19
  export const useKnowledgeBaseId = (): string => {
 
53
  gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
54
  queryFn: async () => {
55
  const { data } = await kbService.getList();
56
+ const list = data?.data?.kbs ?? [];
57
  return shouldFilterListWithoutDocument
58
  ? list.filter((x: IKnowledge) => x.chunk_num > 0)
59
  : list;
 
63
  return { list: data, loading };
64
  };
65
 
66
+ export const useInfiniteFetchKnowledgeList = () => {
67
+ const { searchString, handleInputChange } = useHandleSearchChange();
68
+ const debouncedSearchString = useDebounce(searchString, { wait: 500 });
69
+
70
+ const PageSize = 30;
71
+ const {
72
+ data,
73
+ error,
74
+ fetchNextPage,
75
+ hasNextPage,
76
+ isFetching,
77
+ isFetchingNextPage,
78
+ status,
79
+ } = useInfiniteQuery({
80
+ queryKey: ['infiniteFetchKnowledgeList', debouncedSearchString],
81
+ queryFn: async ({ pageParam }) => {
82
+ const { data } = await kbService.getList({
83
+ page: pageParam,
84
+ page_size: PageSize,
85
+ keywords: debouncedSearchString,
86
+ });
87
+ const list = data?.data ?? [];
88
+ return list;
89
+ },
90
+ initialPageParam: 1,
91
+ getNextPageParam: (lastPage, pages, lastPageParam) => {
92
+ if (lastPageParam * PageSize <= lastPage.total) {
93
+ return lastPageParam + 1;
94
+ }
95
+ return undefined;
96
+ },
97
+ });
98
+ return {
99
+ data,
100
+ loading: isFetching,
101
+ error,
102
+ fetchNextPage,
103
+ hasNextPage,
104
+ isFetching,
105
+ isFetchingNextPage,
106
+ status,
107
+ handleInputChange,
108
+ searchString,
109
+ };
110
+ };
111
+
112
  export const useCreateKnowledge = () => {
113
  const queryClient = useQueryClient();
114
  const {
 
144
  const { data } = await kbService.rmKb({ kb_id: id });
145
  if (data.code === 0) {
146
  message.success(i18n.t(`message.deleted`));
147
+ queryClient.invalidateQueries({
148
+ queryKey: ['infiniteFetchKnowledgeList'],
149
+ });
150
  }
151
  return data?.data ?? [];
152
  },
web/src/locales/en.ts CHANGED
@@ -75,6 +75,7 @@ export default {
75
  namePlaceholder: 'Please input name!',
76
  doc: 'Docs',
77
  searchKnowledgePlaceholder: 'Search',
 
78
  },
79
  knowledgeDetails: {
80
  dataset: 'Dataset',
 
75
  namePlaceholder: 'Please input name!',
76
  doc: 'Docs',
77
  searchKnowledgePlaceholder: 'Search',
78
+ noMoreData: 'It is all, nothing more',
79
  },
80
  knowledgeDetails: {
81
  dataset: 'Dataset',
web/src/locales/zh-traditional.ts CHANGED
@@ -75,6 +75,7 @@ export default {
75
  namePlaceholder: '請輸入名稱',
76
  doc: '文件',
77
  searchKnowledgePlaceholder: '搜索',
 
78
  },
79
  knowledgeDetails: {
80
  dataset: '數據集',
 
75
  namePlaceholder: '請輸入名稱',
76
  doc: '文件',
77
  searchKnowledgePlaceholder: '搜索',
78
+ noMoreData: 'It is all, nothing more',
79
  },
80
  knowledgeDetails: {
81
  dataset: '數據集',
web/src/locales/zh.ts CHANGED
@@ -75,6 +75,7 @@ export default {
75
  namePlaceholder: '请输入名称',
76
  doc: '文档',
77
  searchKnowledgePlaceholder: '搜索',
 
78
  },
79
  knowledgeDetails: {
80
  dataset: '数据集',
 
75
  namePlaceholder: '请输入名称',
76
  doc: '文档',
77
  searchKnowledgePlaceholder: '搜索',
78
+ noMoreData: '沒有更多的數據了',
79
  },
80
  knowledgeDetails: {
81
  dataset: '数据集',
web/src/pages/knowledge/index.less CHANGED
@@ -2,6 +2,7 @@
2
 
3
  .knowledge {
4
  padding: 48px 0;
 
5
  }
6
 
7
  .topWrapper {
 
2
 
3
  .knowledge {
4
  padding: 48px 0;
5
+ overflow: auto;
6
  }
7
 
8
  .topWrapper {
web/src/pages/knowledge/index.tsx CHANGED
@@ -1,18 +1,26 @@
1
- import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
2
  import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
3
  import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
4
- import { Button, Empty, Flex, Input, Space, Spin } from 'antd';
 
 
 
 
 
 
 
 
 
 
 
 
5
  import KnowledgeCard from './knowledge-card';
6
  import KnowledgeCreatingModal from './knowledge-creating-modal';
7
 
8
- import { useTranslation } from 'react-i18next';
9
- import { useSaveKnowledge, useSearchKnowledge } from './hooks';
10
  import styles from './index.less';
11
 
12
  const KnowledgeList = () => {
13
- const { searchString, handleInputChange } = useSearchKnowledge();
14
- const { loading, list: data } = useNextFetchKnowledgeList();
15
- const list = data.filter((x) => x.name.includes(searchString));
16
  const { data: userInfo } = useFetchUserInfo();
17
  const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' });
18
  const {
@@ -22,9 +30,23 @@ const KnowledgeList = () => {
22
  onCreateOk,
23
  loading: creatingLoading,
24
  } = useSaveKnowledge();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  return (
27
- <Flex className={styles.knowledge} vertical flex={1}>
28
  <div className={styles.topWrapper}>
29
  <div>
30
  <span className={styles.title}>
@@ -53,21 +75,30 @@ const KnowledgeList = () => {
53
  </Space>
54
  </div>
55
  <Spin spinning={loading}>
56
- <Flex
57
- gap={'large'}
58
- wrap="wrap"
59
- className={styles.knowledgeCardContainer}
 
 
 
60
  >
61
- {list.length > 0 ? (
62
- list.map((item: any) => {
63
- return (
64
- <KnowledgeCard item={item} key={item.name}></KnowledgeCard>
65
- );
66
- })
67
- ) : (
68
- <Empty className={styles.knowledgeEmpty}></Empty>
69
- )}
70
- </Flex>
 
 
 
 
 
 
71
  </Spin>
72
  <KnowledgeCreatingModal
73
  loading={creatingLoading}
 
1
+ import { useInfiniteFetchKnowledgeList } from '@/hooks/knowledge-hooks';
2
  import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
3
  import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
4
+ import {
5
+ Button,
6
+ Divider,
7
+ Empty,
8
+ Flex,
9
+ Input,
10
+ Skeleton,
11
+ Space,
12
+ Spin,
13
+ } from 'antd';
14
+ import { useTranslation } from 'react-i18next';
15
+ import InfiniteScroll from 'react-infinite-scroll-component';
16
+ import { useSaveKnowledge } from './hooks';
17
  import KnowledgeCard from './knowledge-card';
18
  import KnowledgeCreatingModal from './knowledge-creating-modal';
19
 
20
+ import { useMemo } from 'react';
 
21
  import styles from './index.less';
22
 
23
  const KnowledgeList = () => {
 
 
 
24
  const { data: userInfo } = useFetchUserInfo();
25
  const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' });
26
  const {
 
30
  onCreateOk,
31
  loading: creatingLoading,
32
  } = useSaveKnowledge();
33
+ const {
34
+ fetchNextPage,
35
+ data,
36
+ hasNextPage,
37
+ searchString,
38
+ handleInputChange,
39
+ loading,
40
+ } = useInfiniteFetchKnowledgeList();
41
+ console.log('🚀 ~ KnowledgeList ~ data:', data);
42
+ const nextList = data?.pages?.flatMap((x) => x.kbs) ?? [];
43
+
44
+ const total = useMemo(() => {
45
+ return data?.pages.at(-1).total ?? 0;
46
+ }, [data?.pages]);
47
 
48
  return (
49
+ <Flex className={styles.knowledge} vertical flex={1} id="scrollableDiv">
50
  <div className={styles.topWrapper}>
51
  <div>
52
  <span className={styles.title}>
 
75
  </Space>
76
  </div>
77
  <Spin spinning={loading}>
78
+ <InfiniteScroll
79
+ dataLength={nextList?.length ?? 0}
80
+ next={fetchNextPage}
81
+ hasMore={hasNextPage}
82
+ loader={<Skeleton avatar paragraph={{ rows: 1 }} active />}
83
+ endMessage={total && <Divider plain>{t('noMoreData')} 🤐</Divider>}
84
+ scrollableTarget="scrollableDiv"
85
  >
86
+ <Flex
87
+ gap={'large'}
88
+ wrap="wrap"
89
+ className={styles.knowledgeCardContainer}
90
+ >
91
+ {nextList?.length > 0 ? (
92
+ nextList.map((item: any) => {
93
+ return (
94
+ <KnowledgeCard item={item} key={item.name}></KnowledgeCard>
95
+ );
96
+ })
97
+ ) : (
98
+ <Empty className={styles.knowledgeEmpty}></Empty>
99
+ )}
100
+ </Flex>
101
+ </InfiniteScroll>
102
  </Spin>
103
  <KnowledgeCreatingModal
104
  loading={creatingLoading}