feat: enhance segment management by adding new segment mutation and improving UI layout
This commit is contained in:
parent
493ec06e95
commit
9006a744b9
@ -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<SegmentDetailModel[]> // Array of items loaded so far.
|
|
||||||
loadNextPage: () => Promise<void> // Callback function responsible for loading the next page of items.
|
|
||||||
onClick: (detail: SegmentDetailModel) => void
|
|
||||||
onChangeSwitch: (segId: string, enabled: boolean) => Promise<void>
|
|
||||||
onDelete: (segId: string) => Promise<void>
|
|
||||||
archived?: boolean
|
|
||||||
embeddingAvailable: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const InfiniteVirtualList: FC<IInfiniteVirtualListProps> = ({
|
|
||||||
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 => (
|
|
||||||
<SegmentCard key={v} loading={true} detail={{ position: v } as any} />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
content = items[index].map(segItem => (
|
|
||||||
<SegmentCard
|
|
||||||
key={segItem.id}
|
|
||||||
detail={segItem}
|
|
||||||
onClick={() => onClickCard(segItem)}
|
|
||||||
onChangeSwitch={onChangeSwitch}
|
|
||||||
onDelete={onDelete}
|
|
||||||
loading={false}
|
|
||||||
archived={archived}
|
|
||||||
embeddingAvailable={embeddingAvailable}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={style} className={s.cardWrapper}>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InfiniteLoader
|
|
||||||
itemCount={itemCount}
|
|
||||||
isItemLoaded={isItemLoaded}
|
|
||||||
loadMoreItems={loadMoreItems}
|
|
||||||
>
|
|
||||||
{({ onItemsRendered, ref }) => (
|
|
||||||
<List
|
|
||||||
ref={ref}
|
|
||||||
className="List"
|
|
||||||
height={800}
|
|
||||||
width={'100%'}
|
|
||||||
itemSize={200}
|
|
||||||
itemCount={itemCount}
|
|
||||||
onItemsRendered={onItemsRendered}
|
|
||||||
>
|
|
||||||
{Item}
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
</InfiniteLoader>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default InfiniteVirtualList
|
|
@ -50,6 +50,7 @@ const ChunkContent: FC<IChunkContentProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoHeightTextarea
|
<AutoHeightTextarea
|
||||||
|
outerClassName='mb-6'
|
||||||
className='body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
|
className='body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
|
||||||
value={question}
|
value={question}
|
||||||
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
|
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
|
||||||
|
@ -24,7 +24,6 @@ import Input from '@/app/components/base/input'
|
|||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import type { Item } from '@/app/components/base/select'
|
import type { Item } from '@/app/components/base/select'
|
||||||
import { SimpleSelect } from '@/app/components/base/select'
|
import { SimpleSelect } from '@/app/components/base/select'
|
||||||
import { updateSegment } from '@/service/datasets'
|
|
||||||
import { type ChildChunkDetail, ChuckingMode, type SegmentDetailModel, type SegmentUpdater } from '@/models/datasets'
|
import { type ChildChunkDetail, ChuckingMode, type SegmentDetailModel, type SegmentUpdater } from '@/models/datasets'
|
||||||
import NewSegment from '@/app/components/datasets/documents/detail/new-segment'
|
import NewSegment from '@/app/components/datasets/documents/detail/new-segment'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
@ -39,6 +38,7 @@ import {
|
|||||||
useSegmentList,
|
useSegmentList,
|
||||||
useSegmentListKey,
|
useSegmentListKey,
|
||||||
useUpdateChildSegment,
|
useUpdateChildSegment,
|
||||||
|
useUpdateSegment,
|
||||||
} from '@/service/knowledge/use-segment'
|
} from '@/service/knowledge/use-segment'
|
||||||
import { useInvalid } from '@/service/use-base'
|
import { useInvalid } from '@/service/use-base'
|
||||||
|
|
||||||
@ -244,6 +244,8 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [datasetId, documentId, selectedSegmentIds])
|
}, [datasetId, documentId, selectedSegmentIds])
|
||||||
|
|
||||||
|
const { mutateAsync: updateSegment } = useUpdateSegment()
|
||||||
|
|
||||||
const handleUpdateSegment = useCallback(async (
|
const handleUpdateSegment = useCallback(async (
|
||||||
segmentId: string,
|
segmentId: string,
|
||||||
question: string,
|
question: string,
|
||||||
@ -274,30 +276,31 @@ const Completed: FC<ICompletedProps> = ({
|
|||||||
if (needRegenerate)
|
if (needRegenerate)
|
||||||
params.regenerate_child_chunks = needRegenerate
|
params.regenerate_child_chunks = needRegenerate
|
||||||
|
|
||||||
try {
|
eventEmitter?.emit('update-segment')
|
||||||
eventEmitter?.emit('update-segment')
|
await updateSegment({ datasetId, documentId, segmentId, body: params }, {
|
||||||
const res = await updateSegment({ datasetId, documentId, segmentId, body: params })
|
onSuccess(data) {
|
||||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||||
if (!needRegenerate)
|
if (!needRegenerate)
|
||||||
onCloseSegmentDetail()
|
onCloseSegmentDetail()
|
||||||
for (const seg of segments) {
|
for (const seg of segments) {
|
||||||
if (seg.id === segmentId) {
|
if (seg.id === segmentId) {
|
||||||
seg.answer = res.data.answer
|
seg.answer = data.data.answer
|
||||||
seg.content = res.data.content
|
seg.content = data.data.content
|
||||||
seg.keywords = res.data.keywords
|
seg.keywords = data.data.keywords
|
||||||
seg.word_count = res.data.word_count
|
seg.word_count = data.data.word_count
|
||||||
seg.hit_count = res.data.hit_count
|
seg.hit_count = data.data.hit_count
|
||||||
seg.enabled = res.data.enabled
|
seg.enabled = data.data.enabled
|
||||||
seg.updated_at = res.data.updated_at
|
seg.updated_at = data.data.updated_at
|
||||||
seg.child_chunks = res.data.child_chunks
|
seg.child_chunks = data.data.child_chunks
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
setSegments([...segments])
|
||||||
setSegments([...segments])
|
eventEmitter?.emit('update-segment-success')
|
||||||
eventEmitter?.emit('update-segment-success')
|
},
|
||||||
}
|
onSettled() {
|
||||||
finally {
|
eventEmitter?.emit('update-segment-done')
|
||||||
eventEmitter?.emit('update-segment-done')
|
},
|
||||||
}
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [segments, datasetId, documentId])
|
}, [segments, datasetId, documentId])
|
||||||
|
|
||||||
|
@ -16,10 +16,10 @@ import { useDocumentContext } from './index'
|
|||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import { ChuckingMode, type SegmentUpdater } from '@/models/datasets'
|
import { ChuckingMode, type SegmentUpdater } from '@/models/datasets'
|
||||||
import { addSegment } from '@/service/datasets'
|
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
import { formatNumber } from '@/utils/format'
|
import { formatNumber } from '@/utils/format'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
|
import { useAddSegment } from '@/service/knowledge/use-segment'
|
||||||
|
|
||||||
type NewSegmentModalProps = {
|
type NewSegmentModalProps = {
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
@ -71,6 +71,8 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
|
|||||||
setKeywords([])
|
setKeywords([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { mutateAsync: addSegment } = useAddSegment()
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const params: SegmentUpdater = { content: '' }
|
const params: SegmentUpdater = { content: '' }
|
||||||
if (isQAModel) {
|
if (isQAModel) {
|
||||||
@ -105,23 +107,24 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
|
|||||||
params.keywords = keywords
|
params.keywords = keywords
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
await addSegment({ datasetId, documentId, body: params }, {
|
||||||
await addSegment({ datasetId, documentId, body: params })
|
onSuccess() {
|
||||||
notify({
|
notify({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: t('datasetDocuments.segment.chunkAdded'),
|
message: t('datasetDocuments.segment.chunkAdded'),
|
||||||
className: `!w-[296px] !bottom-0 ${appSidebarExpand === 'expand' ? '!left-[216px]' : '!left-14'}
|
className: `!w-[296px] !bottom-0 ${appSidebarExpand === 'expand' ? '!left-[216px]' : '!left-14'}
|
||||||
!top-auto !right-auto !mb-[52px] !ml-11`,
|
!top-auto !right-auto !mb-[52px] !ml-11`,
|
||||||
customComponent: CustomButton,
|
customComponent: CustomButton,
|
||||||
})
|
})
|
||||||
handleCancel('add')
|
handleCancel('add')
|
||||||
refreshTimer.current = setTimeout(() => {
|
refreshTimer.current = setTimeout(() => {
|
||||||
onSave()
|
onSave()
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
},
|
||||||
finally {
|
onSettled() {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const wordCountText = useMemo(() => {
|
const wordCountText = useMemo(() => {
|
||||||
|
@ -23,8 +23,6 @@ import type {
|
|||||||
IndexingStatusResponse,
|
IndexingStatusResponse,
|
||||||
ProcessRuleResponse,
|
ProcessRuleResponse,
|
||||||
RelatedAppResponse,
|
RelatedAppResponse,
|
||||||
SegmentDetailModel,
|
|
||||||
SegmentUpdater,
|
|
||||||
createDocumentResponse,
|
createDocumentResponse,
|
||||||
} from '@/models/datasets'
|
} from '@/models/datasets'
|
||||||
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
|
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
|
||||||
@ -178,18 +176,6 @@ export const modifyDocMetadata: Fetcher<CommonResponse, CommonDocReq & { body: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apis for segments in a document
|
// apis for segments in a document
|
||||||
export const updateSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string }, { datasetId: string; documentId: string; segmentId: string; body: SegmentUpdater }> = ({ 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<CommonResponse, { datasetId: string; documentId: string; segmentId: string }> = ({ datasetId, documentId, segmentId }) => {
|
|
||||||
return del<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const segmentBatchImport: Fetcher<{ job_id: string; job_status: string }, { url: string; body: FormData }> = ({ url, body }) => {
|
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 })
|
return post<{ job_id: string; job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true })
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { del, get, patch, post } from '../base'
|
import { del, get, patch, post } from '../base'
|
||||||
import type { CommonResponse } from '@/models/common'
|
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'
|
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 = () => {
|
export const useEnableSegment = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: [NAME_SPACE, 'enable'],
|
mutationKey: [NAME_SPACE, 'enable'],
|
||||||
|
Loading…
Reference in New Issue
Block a user