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 (
|
||||
<AutoHeightTextarea
|
||||
outerClassName='mb-6'
|
||||
className='body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
|
||||
value={question}
|
||||
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
|
||||
|
@ -24,7 +24,6 @@ import Input from '@/app/components/base/input'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import type { Item } 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 NewSegment from '@/app/components/datasets/documents/detail/new-segment'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
@ -39,6 +38,7 @@ import {
|
||||
useSegmentList,
|
||||
useSegmentListKey,
|
||||
useUpdateChildSegment,
|
||||
useUpdateSegment,
|
||||
} from '@/service/knowledge/use-segment'
|
||||
import { useInvalid } from '@/service/use-base'
|
||||
|
||||
@ -244,6 +244,8 @@ const Completed: FC<ICompletedProps> = ({
|
||||
// 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<ICompletedProps> = ({
|
||||
if (needRegenerate)
|
||||
params.regenerate_child_chunks = needRegenerate
|
||||
|
||||
try {
|
||||
eventEmitter?.emit('update-segment')
|
||||
const res = await updateSegment({ datasetId, documentId, segmentId, body: params })
|
||||
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 = 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
|
||||
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 {
|
||||
},
|
||||
onSettled() {
|
||||
eventEmitter?.emit('update-segment-done')
|
||||
}
|
||||
},
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [segments, datasetId, documentId])
|
||||
|
||||
|
@ -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<NewSegmentModalProps> = ({
|
||||
setKeywords([])
|
||||
}
|
||||
|
||||
const { mutateAsync: addSegment } = useAddSegment()
|
||||
|
||||
const handleSave = async () => {
|
||||
const params: SegmentUpdater = { content: '' }
|
||||
if (isQAModel) {
|
||||
@ -105,8 +107,8 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
|
||||
params.keywords = keywords
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await addSegment({ datasetId, documentId, body: params })
|
||||
await addSegment({ datasetId, documentId, body: params }, {
|
||||
onSuccess() {
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('datasetDocuments.segment.chunkAdded'),
|
||||
@ -118,10 +120,11 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
|
||||
refreshTimer.current = setTimeout(() => {
|
||||
onSave()
|
||||
}, 3000)
|
||||
}
|
||||
finally {
|
||||
},
|
||||
onSettled() {
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const wordCountText = useMemo(() => {
|
||||
|
@ -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<CommonResponse, CommonDocReq & { body: {
|
||||
}
|
||||
|
||||
// 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 }) => {
|
||||
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 { 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'],
|
||||
|
Loading…
Reference in New Issue
Block a user