From 9006a744b9c33d914f563ca8b7adc8460ba9c840 Mon Sep 17 00:00:00 2001 From: twwu Date: Tue, 17 Dec 2024 10:13:53 +0800 Subject: [PATCH] feat: enhance segment management by adding new segment mutation and improving UI layout --- .../detail/completed/InfiniteVirtualList.tsx | 98 ------------------- .../detail/completed/common/chunk-content.tsx | 1 + .../documents/detail/completed/index.tsx | 51 +++++----- .../datasets/documents/detail/new-segment.tsx | 37 +++---- web/service/datasets.ts | 14 --- web/service/knowledge/use-segment.ts | 22 ++++- 6 files changed, 69 insertions(+), 154 deletions(-) delete mode 100644 web/app/components/datasets/documents/detail/completed/InfiniteVirtualList.tsx diff --git a/web/app/components/datasets/documents/detail/completed/InfiniteVirtualList.tsx b/web/app/components/datasets/documents/detail/completed/InfiniteVirtualList.tsx deleted file mode 100644 index 7b510bcf21..0000000000 --- a/web/app/components/datasets/documents/detail/completed/InfiniteVirtualList.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import type { CSSProperties, FC } from 'react' -import React from 'react' -import { FixedSizeList as List } from 'react-window' -import InfiniteLoader from 'react-window-infinite-loader' -import SegmentCard from './SegmentCard' -import s from './style.module.css' -import type { SegmentDetailModel } from '@/models/datasets' - -type IInfiniteVirtualListProps = { - hasNextPage?: boolean // Are there more items to load? (This information comes from the most recent API request.) - isNextPageLoading: boolean // Are we currently loading a page of items? (This may be an in-flight flag in your Redux store for example.) - items: Array // Array of items loaded so far. - loadNextPage: () => Promise // Callback function responsible for loading the next page of items. - onClick: (detail: SegmentDetailModel) => void - onChangeSwitch: (segId: string, enabled: boolean) => Promise - onDelete: (segId: string) => Promise - archived?: boolean - embeddingAvailable: boolean -} - -const InfiniteVirtualList: FC = ({ - hasNextPage, - isNextPageLoading, - items, - loadNextPage, - onClick: onClickCard, - onChangeSwitch, - onDelete, - archived, - embeddingAvailable, -}) => { - // If there are more items to be loaded then add an extra row to hold a loading indicator. - const itemCount = hasNextPage ? items.length + 1 : items.length - - // Only load 1 page of items at a time. - // Pass an empty callback to InfiniteLoader in case it asks us to load more than once. - const loadMoreItems = isNextPageLoading ? () => { } : loadNextPage - - // Every row is loaded except for our loading indicator row. - const isItemLoaded = (index: number) => !hasNextPage || index < items.length - - // Render an item or a loading indicator. - const Item = ({ index, style }: { index: number; style: CSSProperties }) => { - let content - if (!isItemLoaded(index)) { - content = ( - <> - {[1, 2, 3].map(v => ( - - ))} - - ) - } - else { - content = items[index].map(segItem => ( - onClickCard(segItem)} - onChangeSwitch={onChangeSwitch} - onDelete={onDelete} - loading={false} - archived={archived} - embeddingAvailable={embeddingAvailable} - /> - )) - } - - return ( -
- {content} -
- ) - } - - return ( - - {({ onItemsRendered, ref }) => ( - - {Item} - - )} - - ) -} -export default InfiniteVirtualList diff --git a/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx b/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx index f250cb3ded..47bd3ab4a1 100644 --- a/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx @@ -50,6 +50,7 @@ const ChunkContent: FC = ({ return ( = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [datasetId, documentId, selectedSegmentIds]) + const { mutateAsync: updateSegment } = useUpdateSegment() + const handleUpdateSegment = useCallback(async ( segmentId: string, question: string, @@ -274,30 +276,31 @@ const Completed: FC = ({ if (needRegenerate) params.regenerate_child_chunks = needRegenerate - try { - eventEmitter?.emit('update-segment') - const res = await updateSegment({ datasetId, documentId, segmentId, body: params }) - notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - if (!needRegenerate) - onCloseSegmentDetail() - for (const seg of segments) { - if (seg.id === segmentId) { - seg.answer = res.data.answer - seg.content = res.data.content - seg.keywords = res.data.keywords - seg.word_count = res.data.word_count - seg.hit_count = res.data.hit_count - seg.enabled = res.data.enabled - seg.updated_at = res.data.updated_at - seg.child_chunks = res.data.child_chunks + eventEmitter?.emit('update-segment') + await updateSegment({ datasetId, documentId, segmentId, body: params }, { + onSuccess(data) { + notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + if (!needRegenerate) + onCloseSegmentDetail() + for (const seg of segments) { + if (seg.id === segmentId) { + seg.answer = data.data.answer + seg.content = data.data.content + seg.keywords = data.data.keywords + seg.word_count = data.data.word_count + seg.hit_count = data.data.hit_count + seg.enabled = data.data.enabled + seg.updated_at = data.data.updated_at + seg.child_chunks = data.data.child_chunks + } } - } - setSegments([...segments]) - eventEmitter?.emit('update-segment-success') - } - finally { - eventEmitter?.emit('update-segment-done') - } + setSegments([...segments]) + eventEmitter?.emit('update-segment-success') + }, + onSettled() { + eventEmitter?.emit('update-segment-done') + }, + }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [segments, datasetId, documentId]) diff --git a/web/app/components/datasets/documents/detail/new-segment.tsx b/web/app/components/datasets/documents/detail/new-segment.tsx index b2ffcf5f2b..666a1caf85 100644 --- a/web/app/components/datasets/documents/detail/new-segment.tsx +++ b/web/app/components/datasets/documents/detail/new-segment.tsx @@ -16,10 +16,10 @@ import { useDocumentContext } from './index' import { useStore as useAppStore } from '@/app/components/app/store' import { ToastContext } from '@/app/components/base/toast' import { ChuckingMode, type SegmentUpdater } from '@/models/datasets' -import { addSegment } from '@/service/datasets' import classNames from '@/utils/classnames' import { formatNumber } from '@/utils/format' import Divider from '@/app/components/base/divider' +import { useAddSegment } from '@/service/knowledge/use-segment' type NewSegmentModalProps = { onCancel: () => void @@ -71,6 +71,8 @@ const NewSegmentModal: FC = ({ setKeywords([]) } + const { mutateAsync: addSegment } = useAddSegment() + const handleSave = async () => { const params: SegmentUpdater = { content: '' } if (isQAModel) { @@ -105,23 +107,24 @@ const NewSegmentModal: FC = ({ params.keywords = keywords setLoading(true) - try { - await addSegment({ datasetId, documentId, body: params }) - notify({ - type: 'success', - message: t('datasetDocuments.segment.chunkAdded'), - className: `!w-[296px] !bottom-0 ${appSidebarExpand === 'expand' ? '!left-[216px]' : '!left-14'} + await addSegment({ datasetId, documentId, body: params }, { + onSuccess() { + notify({ + type: 'success', + message: t('datasetDocuments.segment.chunkAdded'), + className: `!w-[296px] !bottom-0 ${appSidebarExpand === 'expand' ? '!left-[216px]' : '!left-14'} !top-auto !right-auto !mb-[52px] !ml-11`, - customComponent: CustomButton, - }) - handleCancel('add') - refreshTimer.current = setTimeout(() => { - onSave() - }, 3000) - } - finally { - setLoading(false) - } + customComponent: CustomButton, + }) + handleCancel('add') + refreshTimer.current = setTimeout(() => { + onSave() + }, 3000) + }, + onSettled() { + setLoading(false) + }, + }) } const wordCountText = useMemo(() => { diff --git a/web/service/datasets.ts b/web/service/datasets.ts index 158b716764..2a30cc685a 100644 --- a/web/service/datasets.ts +++ b/web/service/datasets.ts @@ -23,8 +23,6 @@ import type { IndexingStatusResponse, ProcessRuleResponse, RelatedAppResponse, - SegmentDetailModel, - SegmentUpdater, createDocumentResponse, } from '@/models/datasets' import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations' @@ -178,18 +176,6 @@ export const modifyDocMetadata: Fetcher = ({ datasetId, documentId, segmentId, body }) => { - return patch<{ data: SegmentDetailModel; doc_form: string }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) -} - -export const addSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string }, { datasetId: string; documentId: string; body: SegmentUpdater }> = ({ datasetId, documentId, body }) => { - return post<{ data: SegmentDetailModel; doc_form: string }>(`/datasets/${datasetId}/documents/${documentId}/segment`, { body }) -} - -export const deleteSegment: Fetcher = ({ datasetId, documentId, segmentId }) => { - return del(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`) -} - export const segmentBatchImport: Fetcher<{ job_id: string; job_status: string }, { url: string; body: FormData }> = ({ url, body }) => { return post<{ job_id: string; job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true }) } diff --git a/web/service/knowledge/use-segment.ts b/web/service/knowledge/use-segment.ts index 8bb51c6d85..72813b40a1 100644 --- a/web/service/knowledge/use-segment.ts +++ b/web/service/knowledge/use-segment.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { del, get, patch, post } from '../base' import type { CommonResponse } from '@/models/common' -import type { ChildChunkDetail, ChildSegmentsResponse, SegmentsResponse } from '@/models/datasets' +import type { ChildChunkDetail, ChildSegmentsResponse, ChuckingMode, SegmentDetailModel, SegmentUpdater, SegmentsResponse } from '@/models/datasets' const NAME_SPACE = 'segment' @@ -31,6 +31,26 @@ export const useSegmentList = ( }) } +export const useUpdateSegment = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'update'], + mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: SegmentUpdater }) => { + const { datasetId, documentId, segmentId, body } = payload + return patch<{ data: SegmentDetailModel; doc_form: ChuckingMode }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) + }, + }) +} + +export const useAddSegment = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'add'], + mutationFn: (payload: { datasetId: string; documentId: string; body: SegmentUpdater }) => { + const { datasetId, documentId, body } = payload + return post<{ data: SegmentDetailModel; doc_form: ChuckingMode }>(`/datasets/${datasetId}/documents/${documentId}/segment`, { body }) + }, + }) +} + export const useEnableSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'enable'],