From 1c5464dbef8813737b59ac31acfc885163e774e9 Mon Sep 17 00:00:00 2001 From: twwu Date: Mon, 9 Dec 2024 11:46:18 +0800 Subject: [PATCH] feat: add pagination for chunk list --- .../detail/completed/full-screen-drawer.tsx | 32 ++ .../documents/detail/completed/index.tsx | 58 +-- .../documents/detail/completed/mock-data.ts | 369 ------------------ .../detail/completed/segment-card.tsx | 53 ++- .../detail/completed/segment-detail.tsx | 6 +- .../datasets/documents/detail/index.tsx | 2 +- ...{new-segment-modal.tsx => new-segment.tsx} | 88 ++--- .../components/datasets/documents/list.tsx | 1 - web/models/datasets.ts | 1 + web/utils/time.ts | 5 + 10 files changed, 146 insertions(+), 469 deletions(-) create mode 100644 web/app/components/datasets/documents/detail/completed/full-screen-drawer.tsx delete mode 100644 web/app/components/datasets/documents/detail/completed/mock-data.ts rename web/app/components/datasets/documents/detail/{new-segment-modal.tsx => new-segment.tsx} (70%) create mode 100644 web/utils/time.ts diff --git a/web/app/components/datasets/documents/detail/completed/full-screen-drawer.tsx b/web/app/components/datasets/documents/detail/completed/full-screen-drawer.tsx new file mode 100644 index 0000000000..057d073c56 --- /dev/null +++ b/web/app/components/datasets/documents/detail/completed/full-screen-drawer.tsx @@ -0,0 +1,32 @@ +import React, { type FC } from 'react' +import Drawer from '@/app/components/base/drawer' + +type IFullScreenDrawerProps = { + isOpen: boolean + onClose?: () => void + fullScreen: boolean + children: React.ReactNode +} + +const FullScreenDrawer: FC = ({ + isOpen, + onClose = () => {}, + fullScreen, + children, +}) => { + return ( + + {children} + ) +} + +export default FullScreenDrawer diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 713b421398..1628126756 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -11,12 +11,12 @@ import SegmentList from './segment-list' import DisplayToggle from './display-toggle' import BatchAction from './batch-action' import SegmentDetail from './segment-detail' -import { mockChildSegments } from './mock-data' import SegmentCard from './segment-card' import ChildSegmentList from './child-segment-list' +import FullScreenDrawer from './full-screen-drawer' +import Pagination from '@/app/components/base/pagination' import cn from '@/utils/classnames' import { formatNumber } from '@/utils/format' -import Drawer from '@/app/components/base/drawer' import Divider from '@/app/components/base/divider' import Input from '@/app/components/base/input' import { ToastContext } from '@/app/components/base/toast' @@ -24,12 +24,14 @@ import type { Item } from '@/app/components/base/select' import { SimpleSelect } from '@/app/components/base/select' import { updateSegment } from '@/service/datasets' import type { ChildChunkDetail, SegmentDetailModel, SegmentUpdater } from '@/models/datasets' -import NewSegmentModal from '@/app/components/datasets/documents/detail/new-segment-modal' +import NewSegment from '@/app/components/datasets/documents/detail/new-segment' import { useEventEmitterContextContext } from '@/context/event-emitter' import Checkbox from '@/app/components/base/checkbox' import { useChildSegmentList, useDeleteSegment, useDisableSegment, useEnableSegment, useSegmentList } from '@/service/knowledge/use-segment' import { Chunk } from '@/app/components/base/icons/src/public/knowledge' +const DEFAULT_LIMIT = 10 + type SegmentListContextValue = { isCollapsed: boolean toggleCollapsed: () => void @@ -99,9 +101,8 @@ const Completed: FC = ({ const [selectedSegmentIds, setSelectedSegmentIds] = useState([]) const { eventEmitter } = useEventEmitterContextContext() const [isCollapsed, setIsCollapsed] = useState(true) - // todo: pagination - const [currentPage, setCurrentPage] = useState(1) - const [limit, setLimit] = useState(10) + const [currentPage, setCurrentPage] = useState(1) // start from 1 + const [limit, setLimit] = useState(DEFAULT_LIMIT) const [fullScreen, setFullScreen] = useState(false) const { run: handleSearch } = useDebounceFn(() => { @@ -135,8 +136,6 @@ const Completed: FC = ({ ) useEffect(() => { - // setSegments(mockSegments.data) - // todo: remove mock data if (segmentListData) setSegments(segmentListData.data || []) }, [segmentListData]) @@ -156,15 +155,14 @@ const Completed: FC = ({ ) useEffect(() => { - setChildSegments(mockChildSegments.data) - // todo: remove mock data - // if (childChunkListData) - // setChildSegments(childChunkListData.data || []) + if (childChunkListData) + setChildSegments(childChunkListData.data || []) }, [childChunkListData]) const resetList = useCallback(() => { setSegments([]) refreshSegmentList() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const onClickCard = (detail: SegmentDetailModel, isEditMode = false) => { @@ -362,16 +360,18 @@ const Completed: FC = ({ archived={archived} /> } + {/* Pagination */} + setCurrentPage(cur + 1)} + total={segmentListData?.total || 0} + limit={limit} + onLimitChange={limit => setLimit(limit)} + /> {/* Edit or view segment detail */} - {}} - panelClassname={`!p-0 ${fullScreen - ? '!max-w-full !w-full' - : 'mt-16 mr-2 mb-2 !max-w-[560px] !w-[560px] border-[0.5px] border-components-panel-border rounded-xl'}`} - mask={false} - unmount - footer={null} + fullScreen={fullScreen} > = ({ onUpdate={handleUpdateSegment} onCancel={onCloseDrawer} /> - + {/* Create New Segment */} - onNewSegmentModalChange(false)} - onSave={resetList} - /> + + onNewSegmentModalChange(false)} + onSave={resetList} + /> + {/* Batch Action Buttons */} {selectedSegmentIds.length > 0 && = 2 Core\n\nRAM >= 4 GiB\n\n快速启动\n\n启动 Dify 服务器的最简单方法是运行我们的 docker-compose.yml 文件。在运行安装命令之前,请确保您的机器上安装了 Docker 和 Docker Compose:\n\nbash cd docker cp .env.example .env docker compose up -d\n\n运行后,可以在浏览器上访问 http://localhost/install 进入 Dify 控制台并开始初始化安装操作。\n\n自定义配置', - answer: '', - word_count: 650, - tokens: 427, - keywords: [ - 'Docker', - 'Dify', - 'env', - 'AWS', - 'docker', - '自定义', - '使用', - '确保您', - '安装', - 'compose', - ], - index_node_id: '2af623f5-dea6-4b6b-a147-17f9e76ac1dd', - index_node_hash: '7570a716c175c92b47658536e3c0df7dce8bac30b09cd33fb4333299874ebb0d', - hit_count: 0, - enabled: true, - disabled_at: 1732081062, - disabled_by: '', - status: 'completed', - created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5', - created_at: 1732081062, - indexing_at: 1732081061, - completed_at: 1732081064, - error: null, - stopped_at: 1732081062, - }, - { - id: 'c2cbfe0b-304c-40c2-9980-7d39d65e5b18', - position: 5, - document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c', - content: '运行后,可以在浏览器上访问 http://localhost/install 进入 Dify 控制台并开始初始化安装操作。\n\n自定义配置\n\n如果您需要自定义配置,请参考 .env.example 文件中的注释,并更新 .env 文件中对应的值。此外,您可能需要根据您的具体部署环境和需求对 docker-compose.yaml 文件本身进行调整,例如更改镜像版本、端口映射或卷挂载。完成任何更改后,请重新运行 docker-compose up -d。您可以在此处找到可用环境变量的完整列表。\n\n使用 Helm Chart 部署\n\n使用 Helm Chart 版本或者 YAML 文件,可以在 Kubernetes 上部署 Dify。\n\nHelm Chart by @LeoQuote\n\nHelm Chart by @BorisPolonsky\n\nYAML 文件 by @Winson-030\n\n使用 Terraform 部署\n\n使用 terraform 一键将 Dify 部署到云平台\n\nAzure Global\n\nAzure Terraform by @nikawang\n\nGoogle Cloud\n\nGoogle Cloud Terraform by @sotazum\n\nStar History\n\nContributing\n\n对于那些想要贡献代码的人,请参阅我们的贡献指南。 同时,请考虑通过社交媒体、活动和会议来支持 Dify 的分享。\n\n我们正在寻找贡献者来帮助将Dify翻译成除了中文和英文之外的其他语言。如果您有兴趣帮助,请参阅我们的i18n README获取更多信息,并在我们的Discord社区服务器的global-users频道中留言。\n\nContributors\n\n社区与支持', - answer: '', - word_count: 751, - tokens: 424, - keywords: [ - 'Terraform', - 'Dify', - 'env', - 'Chart', - '自定义', - 'docker', - 'Helm', - '部署', - '请参阅', - '文件', - ], - index_node_id: 'e8b230c2-1ab6-4e70-b317-c50479b284d1', - index_node_hash: '1efe0128dc40d87f3cd57855e872e4b67f20cc71a6c52732bfd67cd5bdcff65e', - hit_count: 0, - enabled: true, - disabled_at: 1732081062, - disabled_by: '', - status: 'completed', - created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5', - created_at: 1732081062, - indexing_at: 1732081061, - completed_at: 1732081064, - error: null, - stopped_at: 1732081062, - }, - { - id: '0dcea77f-657d-4765-bc4a-a71806bede29', - position: 6, - document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c', - content: 'Contributors\n\n社区与支持\n\n我们欢迎您为 Dify 做出贡献,以帮助改善 Dify。包括:提交代码、问题、新想法,或分享您基于 Dify 创建的有趣且有用的 AI 应用程序。同时,我们也欢迎您在不同的活动、会议和社交媒体上分享 Dify。\n\nGithub Discussion. 👉:分享您的应用程序并与社区交流。\n\nGitHub Issues。👉:使用 Dify.AI 时遇到的错误和问题,请参阅贡献指南。\n\n电子邮件支持。👉:关于使用 Dify.AI 的问题。\n\nDiscord。👉:分享您的应用程序并与社区交流。\n\nX(Twitter)。👉:分享您的应用程序并与社区交流。\n\n商业许可。👉:有关商业用途许可 Dify.AI 的商业咨询。\n\n微信 👉:扫描下方二维码,添加微信好友,备注 Dify,我们将邀请您加入 Dify 社区。\n\n安全问题\n\n为了保护您的隐私,请避免在 GitHub 上发布安全问题。发送问题至 security@dify.ai,我们将为您做更细致的解答。\n\nLicense\n\n本仓库遵循 Dify Open Source License 开源协议,该许可证本质上是 Apache 2.0,但有一些额外的限制。', - answer: '', - word_count: 525, - tokens: 388, - keywords: [ - '问题', - 'Dify', - '分享', - 'AI', - 'GitHub', - '微信', - '应用程序', - '社区', - '欢迎您', - 'License', - ], - index_node_id: '3d17802d-9316-4e0d-9e9e-179f12e9830c', - index_node_hash: 'd7d3093eb73803bdbfabe811e33ff60c8b75c15340f9046cac53b2e02fa07203', - hit_count: 0, - enabled: true, - disabled_at: 1732081062, - disabled_by: '', - status: 'completed', - created_by: '573cfc4a-4ff1-43d2-b3e9-46ff1def08c5', - created_at: 1732081062, - indexing_at: 1732081061, - completed_at: 1732081064, - error: null, - stopped_at: 1732081062, - }, - ], - doc_form: 'text_model', - has_more: false, - limit: 10, - total: 6, -} - -export const mockChildSegments = { - data: [ - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6d', - position: 1, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\n', - word_count: 45, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - { - id: 'f3c7e7b6-5e7e-4c8d-9a0b-8f7e1c1f7a6c', - position: 2, - segment_id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea', - content: 'Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。', - word_count: 79, - created_at: 1732081062, - type: 'automatic' as ChildChunkType, - }, - ], - total: 2, - total_pages: 10, - page: 1, - limit: 10, -} diff --git a/web/app/components/datasets/documents/detail/completed/segment-card.tsx b/web/app/components/datasets/documents/detail/completed/segment-card.tsx index 4a2e9a95f1..4a862e6633 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card.tsx @@ -14,6 +14,8 @@ import { formatNumber } from '@/utils/format' import Confirm from '@/app/components/base/confirm' import cn from '@/utils/classnames' import Badge from '@/app/components/base/badge' +import { isAfter } from '@/utils/time' +import Tooltip from '@/app/components/base/tooltip' const Dot = React.memo(() => { return ( @@ -115,6 +117,8 @@ const SegmentCard: FC = ({ answer, keywords, child_chunks = [], + created_at, + updated_at, } = detail as Required['detail'] const [showModal, setShowModal] = useState(false) const isCollapsed = useSegmentListContext(s => s.isCollapsed) @@ -132,10 +136,11 @@ const SegmentCard: FC = ({ return mode === 'hierarchical' && parentMode === 'full-doc' }, [mode, parentMode]) - // todo: change to real logic const chunkEdited = useMemo(() => { - return mode !== 'hierarchical' || parentMode !== 'full-doc' - }, [mode, parentMode]) + if (mode === 'hierarchical' && parentMode === 'full-doc') + return false + return isAfter(updated_at * 1000, created_at * 1000) + }, [mode, parentMode, updated_at, created_at]) const textOpacity = useMemo(() => { return enabled ? '' : 'opacity-50 group-hover/card:opacity-100' @@ -203,22 +208,32 @@ const SegmentCard: FC = ({ rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-md backdrop-blur-[5px]"> {!archived && ( <> -
{ - e.stopPropagation() - onClickEdit?.() - }}> - -
-
{ - e.stopPropagation() - setShowModal(true) - } - }> - -
+ +
{ + e.stopPropagation() + onClickEdit?.() + }}> + +
+
+ +
{ + e.stopPropagation() + setShowModal(true) + } + }> + +
+
)} diff --git a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx index 03d0b499d5..0f86822d17 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx @@ -63,6 +63,8 @@ const SegmentDetail: FC = ({ }) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => { + if (loading) + return e.preventDefault() handleSave() } @@ -164,7 +166,7 @@ const SegmentDetail: FC = ({
· - {formatNumber(segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')} + {formatNumber(isEditMode ? question.length : segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}
@@ -197,4 +199,4 @@ const SegmentDetail: FC = ({ ) } -export default SegmentDetail +export default React.memo(SegmentDetail) diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index 9a933c2a7e..7d2ee9fa13 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -234,7 +234,7 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => {
{isDetailLoading ? - :
+ :
{embedding ? : void docForm: string onSave: () => void } const NewSegmentModal: FC = ({ - isShow, onCancel, docForm, onSave, @@ -86,6 +83,8 @@ const NewSegmentModal: FC = ({ }) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => { + if (loading) + return e.preventDefault() handleSave() } @@ -167,58 +166,47 @@ const NewSegmentModal: FC = ({ } return ( - {}} - panelClassname={`!p-0 ${fullScreen - ? '!max-w-full !w-full' - : 'mt-16 mr-2 mb-2 !max-w-[560px] !w-[560px] border-[0.5px] border-components-panel-border rounded-xl'}`} - mask={false} - unmount - footer={null} - > -
-
-
-
{ - docForm === 'qa_model' - ? t('datasetDocuments.segment.newQaSegment') - : t('datasetDocuments.segment.addChunk') - }
-
- - · - {formatNumber(question.length)} {t('datasetDocuments.segment.characters')} -
-
-
- {fullScreen && ( - <> - {renderActionButtons()} - - - )} -
- -
-
- -
+
+
+
+
{ + docForm === 'qa_model' + ? t('datasetDocuments.segment.newQaSegment') + : t('datasetDocuments.segment.addChunk') + }
+
+ + · + {formatNumber(question.length)} {t('datasetDocuments.segment.characters')}
-
-
- {renderContent()} +
+ {fullScreen && ( + <> + {renderActionButtons()} + + + )} +
+ +
+
+
- {renderKeywords()}
- {!fullScreen && ( -
- {renderActionButtons()} -
- )}
- +
+
+ {renderContent()} +
+ {renderKeywords()} +
+ {!fullScreen && ( +
+ {renderActionButtons()} +
+ )} +
) } diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index c342048d22..18ae342ed6 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -281,7 +281,6 @@ export const OperationAction: FC<{