mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-06-04 11:24:00 +08:00
### 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)
This commit is contained in:
parent
7ae8828e61
commit
ec560cc99d
@ -125,15 +125,16 @@ def detail():
|
|||||||
@manager.route('/list', methods=['GET'])
|
@manager.route('/list', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def list_kbs():
|
def list_kbs():
|
||||||
|
keywords = request.args.get("keywords", "")
|
||||||
page_number = int(request.args.get("page", 1))
|
page_number = int(request.args.get("page", 1))
|
||||||
items_per_page = int(request.args.get("page_size", 150))
|
items_per_page = int(request.args.get("page_size", 150))
|
||||||
orderby = request.args.get("orderby", "create_time")
|
orderby = request.args.get("orderby", "create_time")
|
||||||
desc = request.args.get("desc", True)
|
desc = request.args.get("desc", True)
|
||||||
try:
|
try:
|
||||||
tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
|
tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
|
||||||
kbs = KnowledgebaseService.get_by_tenant_ids(
|
kbs, total = KnowledgebaseService.get_by_tenant_ids(
|
||||||
[m["tenant_id"] for m in tenants], current_user.id, page_number, items_per_page, orderby, desc)
|
[m["tenant_id"] for m in tenants], current_user.id, page_number, items_per_page, orderby, desc, keywords)
|
||||||
return get_json_result(data=kbs)
|
return get_json_result(data={"kbs": kbs, "total": total})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return server_error_response(e)
|
return server_error_response(e)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
from api.db import StatusEnum, TenantPermission
|
from api.db import StatusEnum, TenantPermission
|
||||||
from api.db.db_models import Knowledgebase, DB, Tenant, User, UserTenant,Document
|
from api.db.db_models import Knowledgebase, DB, Tenant, User, UserTenant,Document
|
||||||
from api.db.services.common_service import CommonService
|
from api.db.services.common_service import CommonService
|
||||||
|
from peewee import fn
|
||||||
|
|
||||||
|
|
||||||
class KnowledgebaseService(CommonService):
|
class KnowledgebaseService(CommonService):
|
||||||
@ -34,7 +35,7 @@ class KnowledgebaseService(CommonService):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@DB.connection_context()
|
@DB.connection_context()
|
||||||
def get_by_tenant_ids(cls, joined_tenant_ids, user_id,
|
def get_by_tenant_ids(cls, joined_tenant_ids, user_id,
|
||||||
page_number, items_per_page, orderby, desc):
|
page_number, items_per_page, orderby, desc, keywords):
|
||||||
fields = [
|
fields = [
|
||||||
cls.model.id,
|
cls.model.id,
|
||||||
cls.model.avatar,
|
cls.model.avatar,
|
||||||
@ -51,20 +52,31 @@ class KnowledgebaseService(CommonService):
|
|||||||
User.avatar.alias('tenant_avatar'),
|
User.avatar.alias('tenant_avatar'),
|
||||||
cls.model.update_time
|
cls.model.update_time
|
||||||
]
|
]
|
||||||
kbs = cls.model.select(*fields).join(User, on=(cls.model.tenant_id == User.id)).where(
|
if keywords:
|
||||||
((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
|
kbs = cls.model.select(*fields).join(User, on=(cls.model.tenant_id == User.id)).where(
|
||||||
TenantPermission.TEAM.value)) | (
|
((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
|
||||||
cls.model.tenant_id == user_id))
|
TenantPermission.TEAM.value)) | (
|
||||||
& (cls.model.status == StatusEnum.VALID.value)
|
cls.model.tenant_id == user_id))
|
||||||
)
|
& (cls.model.status == StatusEnum.VALID.value),
|
||||||
|
(fn.LOWER(cls.model.name).contains(keywords.lower()))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
kbs = cls.model.select(*fields).join(User, on=(cls.model.tenant_id == User.id)).where(
|
||||||
|
((cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission ==
|
||||||
|
TenantPermission.TEAM.value)) | (
|
||||||
|
cls.model.tenant_id == user_id))
|
||||||
|
& (cls.model.status == StatusEnum.VALID.value)
|
||||||
|
)
|
||||||
if desc:
|
if desc:
|
||||||
kbs = kbs.order_by(cls.model.getter_by(orderby).desc())
|
kbs = kbs.order_by(cls.model.getter_by(orderby).desc())
|
||||||
else:
|
else:
|
||||||
kbs = kbs.order_by(cls.model.getter_by(orderby).asc())
|
kbs = kbs.order_by(cls.model.getter_by(orderby).asc())
|
||||||
|
|
||||||
|
count = kbs.count()
|
||||||
|
|
||||||
kbs = kbs.paginate(page_number, items_per_page)
|
kbs = kbs.paginate(page_number, items_per_page)
|
||||||
|
|
||||||
return list(kbs.dicts())
|
return list(kbs.dicts()), count
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@DB.connection_context()
|
@DB.connection_context()
|
||||||
|
@ -13,7 +13,7 @@ def test_dataset(get_auth):
|
|||||||
while True:
|
while True:
|
||||||
res = list_dataset(get_auth, page_number)
|
res = list_dataset(get_auth, page_number)
|
||||||
data = res.get("data")
|
data = res.get("data")
|
||||||
for item in data:
|
for item in data.get("kbs"):
|
||||||
dataset_id = item.get("id")
|
dataset_id = item.get("id")
|
||||||
dataset_list.append(dataset_id)
|
dataset_list.append(dataset_id)
|
||||||
if len(dataset_list) < page_number * 150:
|
if len(dataset_list) < page_number * 150:
|
||||||
@ -42,7 +42,7 @@ def test_dataset_1k_dataset(get_auth):
|
|||||||
while True:
|
while True:
|
||||||
res = list_dataset(get_auth, page_number)
|
res = list_dataset(get_auth, page_number)
|
||||||
data = res.get("data")
|
data = res.get("data")
|
||||||
for item in data:
|
for item in data.get("kbs"):
|
||||||
dataset_id = item.get("id")
|
dataset_id = item.get("id")
|
||||||
dataset_list.append(dataset_id)
|
dataset_list.append(dataset_id)
|
||||||
if len(dataset_list) < page_number * 150:
|
if len(dataset_list) < page_number * 150:
|
||||||
|
@ -34,7 +34,7 @@ export default defineConfig({
|
|||||||
proxy: [
|
proxy: [
|
||||||
{
|
{
|
||||||
context: ['/api', '/v1'],
|
context: ['/api', '/v1'],
|
||||||
target: 'http://127.0.0.1:9456/',
|
target: 'http://127.0.0.1:9380/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
logger: console,
|
logger: console,
|
||||||
|
20
web/package-lock.json
generated
20
web/package-lock.json
generated
@ -57,6 +57,7 @@
|
|||||||
"react-force-graph": "^1.44.4",
|
"react-force-graph": "^1.44.4",
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.1",
|
||||||
"react-i18next": "^14.0.0",
|
"react-i18next": "^14.0.0",
|
||||||
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-pdf-highlighter": "^6.1.0",
|
"react-pdf-highlighter": "^6.1.0",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
@ -24705,6 +24706,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-infinite-scroll-component": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"throttle-debounce": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-infinite-scroll-component/node_modules/throttle-debounce": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
"react-force-graph": "^1.44.4",
|
"react-force-graph": "^1.44.4",
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.1",
|
||||||
"react-i18next": "^14.0.0",
|
"react-i18next": "^14.0.0",
|
||||||
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-pdf-highlighter": "^6.1.0",
|
"react-pdf-highlighter": "^6.1.0",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
|
@ -3,14 +3,17 @@ import { IKnowledge, ITestingResult } from '@/interfaces/database/knowledge';
|
|||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import kbService from '@/services/knowledge-service';
|
import kbService from '@/services/knowledge-service';
|
||||||
import {
|
import {
|
||||||
|
useInfiniteQuery,
|
||||||
useIsMutating,
|
useIsMutating,
|
||||||
useMutation,
|
useMutation,
|
||||||
useMutationState,
|
useMutationState,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
|
import { useDebounce } from 'ahooks';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { useSearchParams } from 'umi';
|
import { useSearchParams } from 'umi';
|
||||||
|
import { useHandleSearchChange } from './logic-hooks';
|
||||||
import { useSetPaginationParams } from './route-hook';
|
import { useSetPaginationParams } from './route-hook';
|
||||||
|
|
||||||
export const useKnowledgeBaseId = (): string => {
|
export const useKnowledgeBaseId = (): string => {
|
||||||
@ -50,7 +53,7 @@ export const useNextFetchKnowledgeList = (
|
|||||||
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await kbService.getList();
|
const { data } = await kbService.getList();
|
||||||
const list = data?.data ?? [];
|
const list = data?.data?.kbs ?? [];
|
||||||
return shouldFilterListWithoutDocument
|
return shouldFilterListWithoutDocument
|
||||||
? list.filter((x: IKnowledge) => x.chunk_num > 0)
|
? list.filter((x: IKnowledge) => x.chunk_num > 0)
|
||||||
: list;
|
: list;
|
||||||
@ -60,6 +63,52 @@ export const useNextFetchKnowledgeList = (
|
|||||||
return { list: data, loading };
|
return { list: data, loading };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useInfiniteFetchKnowledgeList = () => {
|
||||||
|
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||||
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
|
|
||||||
|
const PageSize = 30;
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetching,
|
||||||
|
isFetchingNextPage,
|
||||||
|
status,
|
||||||
|
} = useInfiniteQuery({
|
||||||
|
queryKey: ['infiniteFetchKnowledgeList', debouncedSearchString],
|
||||||
|
queryFn: async ({ pageParam }) => {
|
||||||
|
const { data } = await kbService.getList({
|
||||||
|
page: pageParam,
|
||||||
|
page_size: PageSize,
|
||||||
|
keywords: debouncedSearchString,
|
||||||
|
});
|
||||||
|
const list = data?.data ?? [];
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
getNextPageParam: (lastPage, pages, lastPageParam) => {
|
||||||
|
if (lastPageParam * PageSize <= lastPage.total) {
|
||||||
|
return lastPageParam + 1;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading: isFetching,
|
||||||
|
error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetching,
|
||||||
|
isFetchingNextPage,
|
||||||
|
status,
|
||||||
|
handleInputChange,
|
||||||
|
searchString,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const useCreateKnowledge = () => {
|
export const useCreateKnowledge = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const {
|
const {
|
||||||
@ -95,7 +144,9 @@ export const useDeleteKnowledge = () => {
|
|||||||
const { data } = await kbService.rmKb({ kb_id: id });
|
const { data } = await kbService.rmKb({ kb_id: id });
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
message.success(i18n.t(`message.deleted`));
|
message.success(i18n.t(`message.deleted`));
|
||||||
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeList'] });
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['infiniteFetchKnowledgeList'],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return data?.data ?? [];
|
return data?.data ?? [];
|
||||||
},
|
},
|
||||||
|
@ -75,6 +75,7 @@ export default {
|
|||||||
namePlaceholder: 'Please input name!',
|
namePlaceholder: 'Please input name!',
|
||||||
doc: 'Docs',
|
doc: 'Docs',
|
||||||
searchKnowledgePlaceholder: 'Search',
|
searchKnowledgePlaceholder: 'Search',
|
||||||
|
noMoreData: 'It is all, nothing more',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
dataset: 'Dataset',
|
dataset: 'Dataset',
|
||||||
|
@ -75,6 +75,7 @@ export default {
|
|||||||
namePlaceholder: '請輸入名稱',
|
namePlaceholder: '請輸入名稱',
|
||||||
doc: '文件',
|
doc: '文件',
|
||||||
searchKnowledgePlaceholder: '搜索',
|
searchKnowledgePlaceholder: '搜索',
|
||||||
|
noMoreData: 'It is all, nothing more',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
dataset: '數據集',
|
dataset: '數據集',
|
||||||
|
@ -75,6 +75,7 @@ export default {
|
|||||||
namePlaceholder: '请输入名称',
|
namePlaceholder: '请输入名称',
|
||||||
doc: '文档',
|
doc: '文档',
|
||||||
searchKnowledgePlaceholder: '搜索',
|
searchKnowledgePlaceholder: '搜索',
|
||||||
|
noMoreData: '沒有更多的數據了',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
dataset: '数据集',
|
dataset: '数据集',
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
.knowledge {
|
.knowledge {
|
||||||
padding: 48px 0;
|
padding: 48px 0;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topWrapper {
|
.topWrapper {
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
import { useInfiniteFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { Button, Empty, Flex, Input, Space, Spin } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Empty,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
Skeleton,
|
||||||
|
Space,
|
||||||
|
Spin,
|
||||||
|
} from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
|
import { useSaveKnowledge } from './hooks';
|
||||||
import KnowledgeCard from './knowledge-card';
|
import KnowledgeCard from './knowledge-card';
|
||||||
import KnowledgeCreatingModal from './knowledge-creating-modal';
|
import KnowledgeCreatingModal from './knowledge-creating-modal';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useMemo } from 'react';
|
||||||
import { useSaveKnowledge, useSearchKnowledge } from './hooks';
|
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const KnowledgeList = () => {
|
const KnowledgeList = () => {
|
||||||
const { searchString, handleInputChange } = useSearchKnowledge();
|
|
||||||
const { loading, list: data } = useNextFetchKnowledgeList();
|
|
||||||
const list = data.filter((x) => x.name.includes(searchString));
|
|
||||||
const { data: userInfo } = useFetchUserInfo();
|
const { data: userInfo } = useFetchUserInfo();
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' });
|
const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' });
|
||||||
const {
|
const {
|
||||||
@ -22,9 +30,23 @@ const KnowledgeList = () => {
|
|||||||
onCreateOk,
|
onCreateOk,
|
||||||
loading: creatingLoading,
|
loading: creatingLoading,
|
||||||
} = useSaveKnowledge();
|
} = useSaveKnowledge();
|
||||||
|
const {
|
||||||
|
fetchNextPage,
|
||||||
|
data,
|
||||||
|
hasNextPage,
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
loading,
|
||||||
|
} = useInfiniteFetchKnowledgeList();
|
||||||
|
console.log('🚀 ~ KnowledgeList ~ data:', data);
|
||||||
|
const nextList = data?.pages?.flatMap((x) => x.kbs) ?? [];
|
||||||
|
|
||||||
|
const total = useMemo(() => {
|
||||||
|
return data?.pages.at(-1).total ?? 0;
|
||||||
|
}, [data?.pages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex className={styles.knowledge} vertical flex={1}>
|
<Flex className={styles.knowledge} vertical flex={1} id="scrollableDiv">
|
||||||
<div className={styles.topWrapper}>
|
<div className={styles.topWrapper}>
|
||||||
<div>
|
<div>
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
@ -53,21 +75,30 @@ const KnowledgeList = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<Flex
|
<InfiniteScroll
|
||||||
gap={'large'}
|
dataLength={nextList?.length ?? 0}
|
||||||
wrap="wrap"
|
next={fetchNextPage}
|
||||||
className={styles.knowledgeCardContainer}
|
hasMore={hasNextPage}
|
||||||
|
loader={<Skeleton avatar paragraph={{ rows: 1 }} active />}
|
||||||
|
endMessage={total && <Divider plain>{t('noMoreData')} 🤐</Divider>}
|
||||||
|
scrollableTarget="scrollableDiv"
|
||||||
>
|
>
|
||||||
{list.length > 0 ? (
|
<Flex
|
||||||
list.map((item: any) => {
|
gap={'large'}
|
||||||
return (
|
wrap="wrap"
|
||||||
<KnowledgeCard item={item} key={item.name}></KnowledgeCard>
|
className={styles.knowledgeCardContainer}
|
||||||
);
|
>
|
||||||
})
|
{nextList?.length > 0 ? (
|
||||||
) : (
|
nextList.map((item: any) => {
|
||||||
<Empty className={styles.knowledgeEmpty}></Empty>
|
return (
|
||||||
)}
|
<KnowledgeCard item={item} key={item.name}></KnowledgeCard>
|
||||||
</Flex>
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Empty className={styles.knowledgeEmpty}></Empty>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</InfiniteScroll>
|
||||||
</Spin>
|
</Spin>
|
||||||
<KnowledgeCreatingModal
|
<KnowledgeCreatingModal
|
||||||
loading={creatingLoading}
|
loading={creatingLoading}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user