feat: add pagination for chunk list

This commit is contained in:
twwu 2024-12-09 11:46:18 +08:00
parent c541d4aaa8
commit 1c5464dbef
10 changed files with 146 additions and 469 deletions

View File

@ -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<IFullScreenDrawerProps> = ({
isOpen,
onClose = () => {},
fullScreen,
children,
}) => {
return (
<Drawer
isOpen={isOpen}
onClose={onClose}
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}
>
{children}
</Drawer>)
}
export default FullScreenDrawer

View File

@ -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<ICompletedProps> = ({
const [selectedSegmentIds, setSelectedSegmentIds] = useState<string[]>([])
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<ICompletedProps> = ({
)
useEffect(() => {
// setSegments(mockSegments.data)
// todo: remove mock data
if (segmentListData)
setSegments(segmentListData.data || [])
}, [segmentListData])
@ -156,15 +155,14 @@ const Completed: FC<ICompletedProps> = ({
)
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<ICompletedProps> = ({
archived={archived}
/>
}
{/* Pagination */}
<Pagination
current={currentPage - 1}
onChange={cur => setCurrentPage(cur + 1)}
total={segmentListData?.total || 0}
limit={limit}
onLimitChange={limit => setLimit(limit)}
/>
{/* Edit or view segment detail */}
<Drawer
<FullScreenDrawer
isOpen={currSegment.showModal}
onClose={() => {}}
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}
>
<SegmentDetail
segInfo={currSegment.segInfo ?? { id: '' }}
@ -379,14 +379,18 @@ const Completed: FC<ICompletedProps> = ({
onUpdate={handleUpdateSegment}
onCancel={onCloseDrawer}
/>
</Drawer>
</FullScreenDrawer>
{/* Create New Segment */}
<NewSegmentModal
isShow={showNewSegmentModal}
docForm={docForm}
onCancel={() => onNewSegmentModalChange(false)}
onSave={resetList}
/>
<FullScreenDrawer
isOpen={showNewSegmentModal}
fullScreen={fullScreen}
>
<NewSegment
docForm={docForm}
onCancel={() => onNewSegmentModalChange(false)}
onSave={resetList}
/>
</FullScreenDrawer>
{/* Batch Action Buttons */}
{selectedSegmentIds.length > 0
&& <BatchAction

View File

@ -1,369 +0,0 @@
import type { ChildChunkType } from '@/models/datasets'
export const mockSegments = {
data: [
{
id: '12aa196a-cf47-4962-a64a-7d927ed9b0ea',
position: 1,
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
content: 'Dify 云服务 · 自托管 · 文档 · (需用英文)常见问题解答 / 联系团队\n\nDify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等让您可以快速从原型到生产。以下是其核心功能列表\n\n1. 工作流: 在画布上构建和测试功能强大的 AI 工作流程,利用以下所有功能以及更多功能。\n\nhttps://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa\n\n2. 全面的模型支持: 与数百种专有/开源 LLMs 以及数十种推理提供商和自托管解决方案无缝集成,涵盖 GPT、Mistral、Llama3 以及任何与 OpenAI API 兼容的模型。完整的支持模型提供商列表可在此处找到。\n\n3. Prompt IDE: 用于制作提示、比较模型性能以及向基于聊天的应用程序添加其他功能(如文本转语音)的直观界面。\n\n4. RAG Pipeline: 广泛的 RAG 功能,涵盖从文档摄入到检索的所有内容,支持从 PDF、PPT 和其他常见文档格式中提取文本的开箱即用的支持。\n\n5. Agent 智能体: 您可以基于 LLM 函数调用或 ReAct 定义 Agent并为 Agent 添加预构建或自定义工具。Dify 为 AI Agent 提供了50多种内置工具如谷歌搜索、DALL·E、Stable Diffusion 和 WolframAlpha 等。',
answer: '',
word_count: 672,
tokens: 481,
keywords: [
'功能',
'AI',
'LLM',
'模型',
'文档',
'Agent',
'开源',
'Dify',
'支持',
'RAG',
],
index_node_id: 'b67972c2-4a95-4e46-bf8e-f32535bfc483',
index_node_hash: '40ead185f2ec6a451da09e99f4f5a7438df4542590090660b7f2f40099220cf0',
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,
child_chunks: [
// {
// 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: '4c701023-90a6-4df9-bc26-49cfb701badc',
position: 2,
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
content: '6. LLMOps: 随时间监视和分析应用程序日志和性能。您可以根据生产数据和标注持续改进提示、数据集和模型。\n\n7. 后端即服务: 所有 Dify 的功能都带有相应的 API因此您可以轻松地将 Dify 集成到自己的业务逻辑中。\n\n功能比较',
answer: '',
word_count: 122,
tokens: 104,
keywords: [
'标注',
'API',
'Dify',
'集成',
'LLMOps',
'后端',
'应用程序',
'数据',
'日志',
'功能',
],
index_node_id: 'fd5a3ea6-c726-41cb-bf0f-00da11f7cab9',
index_node_hash: '4e3f5f693e9e43734c12613bbb9971eae154be7765fd0b91fb7263b1755319b8',
hit_count: 0,
enabled: false,
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: '070f9780-1819-43fc-b976-780db8e19ed9',
position: 3,
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
content: '功能 Dify.AI LangChain Flowise OpenAI Assistant API 编程方法 API + 应用程序导向 Python 代码 应用程序导向 API 导向 支持的 LLMs 丰富多样 丰富多样 丰富多样 仅限 OpenAI RAG引擎 ✅ ✅ ✅ ✅ Agent ✅ ✅ ❌ ✅ 工作流 ✅ ❌ ✅ ❌ 可观测性 ✅ ✅ ❌ ❌ 企业功能SSO/访问控制) ✅ ❌ ❌ ❌ 本地部署 ✅ ✅ ✅ ❌',
answer: '',
word_count: 214,
tokens: 158,
keywords: [
'导向',
'API',
'Dify',
'OpenAI',
'AI',
'多样',
'LangChain',
'应用程序',
'Flowise',
'丰富',
],
index_node_id: 'a3c7a2bd-003a-4667-a4a8-2da6c27cd887',
index_node_hash: 'e824b23aa039ebc6a6b34a366251235bd81ad72535c2ea66fab949b1f78a65dc',
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: 'c817f359-d927-4987-b940-e040251b10e1',
position: 4,
document_id: '887985f1-ca0c-4805-8e9f-34cbc4738a3c',
content: '使用 Dify\n\n云 我们提供 Dify 云服务,任何人都可以零设置尝试。它提供了自部署版本的所有功能,并在沙盒计划中包含 200 次免费的 GPT-4 调用。\n\n自托管 Dify 社区版 使用这个入门指南快速在您的环境中运行 Dify。 使用我们的文档进行进一步的参考和更深入的说明。\n\n面向企业/组织的 Dify 我们提供额外的面向企业的功能。给我们发送电子邮件讨论企业需求。\n\n对于使用 AWS 的初创公司和中小型企业,请查看 AWS Marketplace 上的 Dify 高级版,并使用一键部署到您自己的 AWS VPC。它是一个价格实惠的 AMI 产品,提供了使用自定义徽标和品牌创建应用程序的选项。\n\n保持领先\n\n在 GitHub 上给 Dify Star并立即收到新版本的通知。\n\n安装社区版\n\n系统要求\n\n在安装 Dify 之前,请确保您的机器满足以下最低系统要求:\n\nCPU >= 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,
}

View File

@ -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<ISegmentCardProps> = ({
answer,
keywords,
child_chunks = [],
created_at,
updated_at,
} = detail as Required<ISegmentCardProps>['detail']
const [showModal, setShowModal] = useState(false)
const isCollapsed = useSegmentListContext(s => s.isCollapsed)
@ -132,10 +136,11 @@ const SegmentCard: FC<ISegmentCardProps> = ({
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<ISegmentCardProps> = ({
rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-md backdrop-blur-[5px]">
{!archived && (
<>
<div
className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer'
onClick={(e) => {
e.stopPropagation()
onClickEdit?.()
}}>
<RiEditLine className='w-4 h-4 text-text-tertiary' />
</div>
<div className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-destructive-hover cursor-pointer group/delete'
onClick={(e) => {
e.stopPropagation()
setShowModal(true)
}
}>
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' />
</div>
<Tooltip
popupContent='Edit'
popupClassName='text-text-secondary system-xs-medium'
>
<div
className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer'
onClick={(e) => {
e.stopPropagation()
onClickEdit?.()
}}>
<RiEditLine className='w-4 h-4 text-text-tertiary' />
</div>
</Tooltip>
<Tooltip
popupContent='Delete'
popupClassName='text-text-secondary system-xs-medium'
>
<div className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-destructive-hover cursor-pointer group/delete'
onClick={(e) => {
e.stopPropagation()
setShowModal(true)
}
}>
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' />
</div>
</Tooltip>
<Divider type="vertical" className="h-3.5 bg-divider-regular" />
</>
)}

View File

@ -63,6 +63,8 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
})
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => {
if (loading)
return
e.preventDefault()
handleSave()
}
@ -164,7 +166,7 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
<div className='flex items-center gap-x-2'>
<SegmentIndexTag positionId={segInfo?.position || ''} />
<span className='text-text-quaternary system-xs-medium'>·</span>
<span className='text-text-tertiary system-xs-medium'>{formatNumber(segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}</span>
<span className='text-text-tertiary system-xs-medium'>{formatNumber(isEditMode ? question.length : segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}</span>
</div>
</div>
<div className='flex items-center'>
@ -197,4 +199,4 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
)
}
export default SegmentDetail
export default React.memo(SegmentDetail)

View File

@ -234,7 +234,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
<div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
{isDetailLoading
? <Loading type='app' />
: <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : `relative pb-[30px] pt-3 ${isFullDocMode ? 'pl-11' : 'pl-5'} pr-11`}`}>
: <div className={`h-full w-full flex flex-col ${embedding ? 'px-6 py-3 sm:py-12 sm:px-16' : `relative pt-3 ${isFullDocMode ? 'pl-11' : 'pl-5'} pr-11`}`}>
{embedding
? <Embedding detail={documentDetail} detailUpdate={detailMutate} />
: <Completed

View File

@ -6,7 +6,6 @@ import { useParams } from 'next/navigation'
import { RiCloseLine, RiExpandDiagonalLine } from '@remixicon/react'
import { useKeyPress } from 'ahooks'
import { SegmentIndexTag, useSegmentListContext } from './completed'
import Drawer from '@/app/components/base/drawer'
import Button from '@/app/components/base/button'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
import { ToastContext } from '@/app/components/base/toast'
@ -19,14 +18,12 @@ import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/co
import Divider from '@/app/components/base/divider'
type NewSegmentModalProps = {
isShow: boolean
onCancel: () => void
docForm: string
onSave: () => void
}
const NewSegmentModal: FC<NewSegmentModalProps> = ({
isShow,
onCancel,
docForm,
onSave,
@ -86,6 +83,8 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
})
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => {
if (loading)
return
e.preventDefault()
handleSave()
}
@ -167,58 +166,47 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
}
return (
<Drawer
isOpen={isShow}
onClose={() => {}}
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}
>
<div className={'flex flex-col h-full'}>
<div className={classNames('flex items-center justify-between', fullScreen ? 'py-3 pr-4 pl-6 border border-divider-subtle' : 'pt-3 pr-3 pl-4')}>
<div className='flex flex-col'>
<div className='text-text-primary system-xl-semibold'>{
docForm === 'qa_model'
? t('datasetDocuments.segment.newQaSegment')
: t('datasetDocuments.segment.addChunk')
}</div>
<div className='flex items-center gap-x-2'>
<SegmentIndexTag label={'New Chunk'} />
<span className='text-text-quaternary system-xs-medium'>·</span>
<span className='text-text-tertiary system-xs-medium'>{formatNumber(question.length)} {t('datasetDocuments.segment.characters')}</span>
</div>
</div>
<div className='flex items-center'>
{fullScreen && (
<>
{renderActionButtons()}
<Divider type='vertical' className='h-3.5 bg-divider-regular ml-4 mr-2' />
</>
)}
<div className='w-8 h-8 flex justify-center items-center p-1.5 cursor-pointer mr-1' onClick={toggleFullScreen}>
<RiExpandDiagonalLine className='w-4 h-4 text-text-tertiary' />
</div>
<div className='w-8 h-8 flex justify-center items-center p-1.5 cursor-pointer' onClick={handleCancel}>
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
</div>
<div className={'flex flex-col h-full'}>
<div className={classNames('flex items-center justify-between', fullScreen ? 'py-3 pr-4 pl-6 border border-divider-subtle' : 'pt-3 pr-3 pl-4')}>
<div className='flex flex-col'>
<div className='text-text-primary system-xl-semibold'>{
docForm === 'qa_model'
? t('datasetDocuments.segment.newQaSegment')
: t('datasetDocuments.segment.addChunk')
}</div>
<div className='flex items-center gap-x-2'>
<SegmentIndexTag label={'New Chunk'} />
<span className='text-text-quaternary system-xs-medium'>·</span>
<span className='text-text-tertiary system-xs-medium'>{formatNumber(question.length)} {t('datasetDocuments.segment.characters')}</span>
</div>
</div>
<div className={classNames('flex grow overflow-hidden', fullScreen ? 'w-full flex-row justify-center px-6 pt-6 gap-x-8' : 'flex-col gap-y-1 py-3 px-4')}>
<div className={classNames('break-all overflow-y-auto whitespace-pre-line', fullScreen ? 'w-1/2' : 'grow')}>
{renderContent()}
<div className='flex items-center'>
{fullScreen && (
<>
{renderActionButtons()}
<Divider type='vertical' className='h-3.5 bg-divider-regular ml-4 mr-2' />
</>
)}
<div className='w-8 h-8 flex justify-center items-center p-1.5 cursor-pointer mr-1' onClick={toggleFullScreen}>
<RiExpandDiagonalLine className='w-4 h-4 text-text-tertiary' />
</div>
<div className='w-8 h-8 flex justify-center items-center p-1.5 cursor-pointer' onClick={handleCancel}>
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
</div>
{renderKeywords()}
</div>
{!fullScreen && (
<div className='flex items-center justify-end p-4 pt-3 border-t-[1px] border-t-divider-subtle'>
{renderActionButtons()}
</div>
)}
</div>
</Drawer>
<div className={classNames('flex grow overflow-hidden', fullScreen ? 'w-full flex-row justify-center px-6 pt-6 gap-x-8' : 'flex-col gap-y-1 py-3 px-4')}>
<div className={classNames('break-all overflow-y-auto whitespace-pre-line', fullScreen ? 'w-1/2' : 'grow')}>
{renderContent()}
</div>
{renderKeywords()}
</div>
{!fullScreen && (
<div className='flex items-center justify-end p-4 pt-3 border-t-[1px] border-t-divider-subtle'>
{renderActionButtons()}
</div>
)}
</div>
)
}

View File

@ -281,7 +281,6 @@ export const OperationAction: FC<{
<Tooltip
popupContent={t('datasetDocuments.list.action.settings')}
popupClassName='text-text-secondary system-xs-medium'
needsDelay
>
<button
className={cn('rounded-lg mr-2 cursor-pointer',

View File

@ -451,6 +451,7 @@ export type SegmentDetailModel = {
stopped_at: number
answer?: string
child_chunks?: ChildChunkDetail[]
updated_at: number
}
export type SegmentsResponse = {

5
web/utils/time.ts Normal file
View File

@ -0,0 +1,5 @@
import dayjs, { type ConfigType } from 'dayjs'
export const isAfter = (date: ConfigType, compare: ConfigType) => {
return dayjs(date).isAfter(dayjs(compare))
}