diff --git a/web/app/components/base/drawer/index.tsx b/web/app/components/base/drawer/index.tsx index c2285b5c53..c6d6a7aa34 100644 --- a/web/app/components/base/drawer/index.tsx +++ b/web/app/components/base/drawer/index.tsx @@ -19,6 +19,7 @@ export type IDrawerProps = { onClose: () => void onCancel?: () => void onOk?: () => void + unmount?: boolean } export default function Drawer({ @@ -35,11 +36,12 @@ export default function Drawer({ onClose, onCancel, onOk, + unmount = false, }: IDrawerProps) { const { t } = useTranslation() return ( !clickOutsideNotOpen && onClose()} className="fixed z-30 inset-0 overflow-y-auto" @@ -49,7 +51,7 @@ export default function Drawer({ -
+
<> {title && = ({ (items || []).map((item, index) => (
+ className={cn('flex items-center mr-1 mt-1 pl-1.5 pr-1 py-1 system-xs-regular text-text-secondary border border-divider-deep bg-components-badge-white-to-dark rounded-md')} + > {item} { !disableRemove && ( - handleRemove(index)} - /> +
handleRemove(index)}> + +
) }
@@ -90,24 +90,27 @@ const TagInput: FC = ({ } { !disableAdd && ( - setFocused(true)} - onBlur={handleBlur} - value={value} - onChange={(e: ChangeEvent) => { - setValue(e.target.value) - }} - onKeyDown={handleKeyDown} - placeholder={t(placeholder || (isSpecialMode ? 'common.model.params.stop_sequencesPlaceholder' : 'datasetDocuments.segment.addKeyWord'))} - /> +
+ {!isSpecialMode && !focused && } + setFocused(true)} + onBlur={handleBlur} + value={value} + onChange={(e: ChangeEvent) => { + setValue(e.target.value) + }} + onKeyDown={handleKeyDown} + placeholder={t(placeholder || (isSpecialMode ? 'common.model.params.stop_sequencesPlaceholder' : 'datasetDocuments.segment.addKeyWord'))} + /> +
) }
diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 46f8470a4e..713b421398 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -11,40 +11,44 @@ import SegmentList from './segment-list' import DisplayToggle from './display-toggle' import BatchAction from './batch-action' import SegmentDetail from './segment-detail' -import { mockChildSegments, mockSegments } from './mock-data' +import { mockChildSegments } from './mock-data' import SegmentCard from './segment-card' import ChildSegmentList from './child-segment-list' import cn from '@/utils/classnames' import { formatNumber } from '@/utils/format' -import Modal from '@/app/components/base/modal' +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' import type { Item } from '@/app/components/base/select' import { SimpleSelect } from '@/app/components/base/select' import { updateSegment } from '@/service/datasets' -import type { SegmentDetailModel, SegmentUpdater } from '@/models/datasets' +import type { ChildChunkDetail, SegmentDetailModel, SegmentUpdater } from '@/models/datasets' import NewSegmentModal from '@/app/components/datasets/documents/detail/new-segment-modal' import { useEventEmitterContextContext } from '@/context/event-emitter' import Checkbox from '@/app/components/base/checkbox' -import { useDeleteSegment, useDisableSegment, useEnableSegment, useSegmentList } from '@/service/knowledge/use-segment' +import { useChildSegmentList, useDeleteSegment, useDisableSegment, useEnableSegment, useSegmentList } from '@/service/knowledge/use-segment' import { Chunk } from '@/app/components/base/icons/src/public/knowledge' type SegmentListContextValue = { isCollapsed: boolean toggleCollapsed: () => void + fullScreen: boolean + toggleFullScreen: () => void } const SegmentListContext = createContext({ isCollapsed: true, toggleCollapsed: () => {}, + fullScreen: false, + toggleFullScreen: () => {}, }) export const useSegmentListContext = (selector: (value: SegmentListContextValue) => any) => { return useContextSelector(SegmentListContext, selector) } -export const SegmentIndexTag: FC<{ positionId: string | number; className?: string }> = ({ positionId, className }) => { +export const SegmentIndexTag: FC<{ positionId?: string | number; label?: string; className?: string }> = ({ positionId, label, className }) => { const localPositionId = useMemo(() => { const positionIdStr = String(positionId) if (positionIdStr.length >= 3) @@ -55,7 +59,7 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri
- {localPositionId} + {label || localPositionId}
) @@ -84,19 +88,21 @@ const Completed: FC = ({ const { notify } = useContext(ToastContext) const [datasetId = '', documentId = '', docForm, mode, parentMode] = useDocumentContext(s => [s.datasetId, s.documentId, s.docForm, s.mode, s.parentMode]) // the current segment id and whether to show the modal - const [currSegment, setCurrSegment] = useState<{ segInfo?: SegmentDetailModel; showModal: boolean; isEditing?: boolean }>({ showModal: false }) + const [currSegment, setCurrSegment] = useState<{ segInfo?: SegmentDetailModel; showModal: boolean; isEditMode?: boolean }>({ showModal: false }) const [inputValue, setInputValue] = useState('') // the input value const [searchValue, setSearchValue] = useState('') // the search value const [selectedStatus, setSelectedStatus] = useState('all') // the selected status, enabled/disabled/undefined const [segments, setSegments] = useState([]) // all segments data + const [childSegments, setChildSegments] = useState([]) // all child segments data 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 [fullScreen, setFullScreen] = useState(false) const { run: handleSearch } = useDebounceFn(() => { setSearchValue(inputValue) @@ -111,37 +117,63 @@ const Completed: FC = ({ setSelectedStatus(value === 'all' ? 'all' : !!value) } - const { isLoading: isLoadingSegmentList, data: segmentList, refetch: refreshSegmentList } = useSegmentList( + const isFullDocMode = useMemo(() => { + return mode === 'hierarchical' && parentMode === 'full-doc' + }, [mode, parentMode]) + + const { isLoading: isLoadingSegmentList, data: segmentListData, refetch: refreshSegmentList } = useSegmentList( { datasetId, documentId, params: { page: currentPage, limit, - keyword: searchValue, + keyword: isFullDocMode ? '' : searchValue, enabled: selectedStatus === 'all' ? 'all' : !!selectedStatus, }, }, - mode === 'hierarchical' && parentMode === 'full-doc', ) useEffect(() => { - setSegments(mockSegments.data) - // if (segmentList) - // setSegments(segmentList.data || []) - }, [segmentList]) + // setSegments(mockSegments.data) + // todo: remove mock data + if (segmentListData) + setSegments(segmentListData.data || []) + }, [segmentListData]) + + const { data: childChunkListData, refetch: refreshChildSegmentList } = useChildSegmentList( + { + datasetId, + documentId, + segmentId: segments[0]?.id || '', + params: { + page: currentPage, + limit, + keyword: searchValue, + }, + }, + !isFullDocMode || segments.length === 0, + ) + + useEffect(() => { + setChildSegments(mockChildSegments.data) + // todo: remove mock data + // if (childChunkListData) + // setChildSegments(childChunkListData.data || []) + }, [childChunkListData]) const resetList = useCallback(() => { setSegments([]) refreshSegmentList() }, []) - const onClickCard = (detail: SegmentDetailModel, isEditing = false) => { - setCurrSegment({ segInfo: detail, showModal: true, isEditing }) + const onClickCard = (detail: SegmentDetailModel, isEditMode = false) => { + setCurrSegment({ segInfo: detail, showModal: true, isEditMode }) } - const onCloseModal = () => { + const onCloseDrawer = () => { setCurrSegment({ ...currSegment, showModal: false }) + setFullScreen(false) } const { mutateAsync: enableSegment } = useEnableSegment() @@ -218,7 +250,7 @@ const Completed: FC = ({ eventEmitter?.emit('update-segment') const res = await updateSegment({ datasetId, documentId, segmentId, body: params }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - onCloseModal() + onCloseDrawer() for (const seg of segments) { if (seg.id === segmentId) { seg.answer = res.data.answer @@ -243,7 +275,7 @@ const Completed: FC = ({ }, [importStatus, resetList]) const isAllSelected = useMemo(() => { - return segments.every(seg => selectedSegmentIds.includes(seg.id)) + return segments.length > 0 && segments.every(seg => selectedSegmentIds.includes(seg.id)) }, [segments, selectedSegmentIds]) const isSomeSelected = useMemo(() => { @@ -259,18 +291,21 @@ const Completed: FC = ({ }, [segments, isAllSelected, selectedSegmentIds]) const totalText = useMemo(() => { - return segmentList?.total ? formatNumber(segmentList.total) : '--' - }, [segmentList?.total]) + return segmentListData?.total ? formatNumber(segmentListData.total) : '--' + }, [segmentListData?.total]) - const isFullDocMode = useMemo(() => { - return mode === 'hierarchical' && parentMode === 'full-doc' - }, [mode, parentMode]) + const toggleFullScreen = useCallback(() => { + setFullScreen(!fullScreen) + }, [fullScreen]) return ( setIsCollapsed(!isCollapsed), + fullScreen, + toggleFullScreen, }}> + {/* Menu Bar */} {!isFullDocMode &&
= ({
} + {/* Segment list */} { isFullDocMode ?
@@ -309,7 +345,7 @@ const Completed: FC = ({ loading={false} /> {}} enabled={!archived} /> @@ -326,23 +362,32 @@ const Completed: FC = ({ archived={archived} /> } - {}} className='!max-w-[640px] !overflow-visible'> + {/* 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} + > - + + {/* Create New Segment */} onNewSegmentModalChange(false)} onSave={resetList} /> + {/* Batch Action Buttons */} {selectedSegmentIds.length > 0 && = ({ return (
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 64589b910e..03d0b499d5 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx @@ -2,50 +2,43 @@ import React, { type FC, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiCloseLine, - RiEditLine, + RiExpandDiagonalLine, } from '@remixicon/react' -import { StatusItem } from '../../list' -import s from './style.module.css' -import { SegmentIndexTag } from '.' +import { useKeyPress } from 'ahooks' +import { SegmentIndexTag, useSegmentListContext } from './index' import type { SegmentDetailModel } from '@/models/datasets' import { useEventEmitterContextContext } from '@/context/event-emitter' import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common' -import Switch from '@/app/components/base/switch' import Button from '@/app/components/base/button' import TagInput from '@/app/components/base/tag-input' -import cn from '@/utils/classnames' import { formatNumber } from '@/utils/format' +import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' +import classNames from '@/utils/classnames' import Divider from '@/app/components/base/divider' type ISegmentDetailProps = { - embeddingAvailable: boolean segInfo?: Partial & { id: string } - onChangeSwitch?: (enabled: boolean, segId?: string) => Promise onUpdate: (segmentId: string, q: string, a: string, k: string[]) => void onCancel: () => void - archived?: boolean - isEditing?: boolean + isEditMode?: boolean } /** * Show all the contents of the segment */ const SegmentDetail: FC = ({ - embeddingAvailable, segInfo, - archived, - onChangeSwitch, onUpdate, onCancel, - isEditing: initialIsEditing, + isEditMode, }) => { const { t } = useTranslation() - const [isEditing, setIsEditing] = useState(initialIsEditing) const [question, setQuestion] = useState(segInfo?.content || '') const [answer, setAnswer] = useState(segInfo?.answer || '') const [keywords, setKeywords] = useState(segInfo?.keywords || []) const { eventEmitter } = useEventEmitterContextContext() const [loading, setLoading] = useState(false) + const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen]) eventEmitter?.useSubscription((v) => { if (v === 'update-segment') @@ -55,7 +48,7 @@ const SegmentDetail: FC = ({ }) const handleCancel = () => { - setIsEditing(false) + onCancel() setQuestion(segInfo?.content || '') setAnswer(segInfo?.answer || '') setKeywords(segInfo?.keywords || []) @@ -64,6 +57,17 @@ const SegmentDetail: FC = ({ onUpdate(segInfo?.id || '', question, answer, keywords) } + useKeyPress(['esc'], (e) => { + e.preventDefault() + handleCancel() + }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => { + e.preventDefault() + handleSave() + } + , { exactMatch: true, useCapture: true }) + const renderContent = () => { if (segInfo?.answer) { return ( @@ -75,7 +79,7 @@ const SegmentDetail: FC = ({ value={question} placeholder={t('datasetDocuments.segment.questionPlaceholder') || ''} onChange={e => setQuestion(e.target.value)} - disabled={!isEditing} + disabled={!isEditMode} />
ANSWER
= ({ value={answer} placeholder={t('datasetDocuments.segment.answerPlaceholder') || ''} onChange={e => setAnswer(e.target.value)} - disabled={!isEditing} + disabled={!isEditMode} autoFocus /> @@ -93,91 +97,104 @@ const SegmentDetail: FC = ({ return ( setQuestion(e.target.value)} - disabled={!isEditing} + disabled={!isEditMode} autoFocus /> ) } - return ( -
-
- {isEditing && ( - <> - - - - )} - {!isEditing && !archived && embeddingAvailable && ( - <> -
-
{t('common.operation.edit')}
- setIsEditing(true)} /> + const renderActionButtons = () => { + return ( +
+ + +
+ ) + } + + const renderKeywords = () => { + return ( +
+
{t('datasetDocuments.segment.keywords')}
+
+ {!segInfo?.keywords?.length + ? '-' + : ( + setKeywords(newKeywords)} + disableAdd={!isEditMode} + disableRemove={!isEditMode || (keywords.length === 1)} + /> + ) + }
- -
{renderContent()}
-
{t('datasetDocuments.segment.keywords')}
-
- {!segInfo?.keywords?.length - ? '-' - : ( - setKeywords(newKeywords)} - disableAdd={!isEditing} - disableRemove={!isEditing || (keywords.length === 1)} - /> - ) - } -
-
-
-
{formatNumber(segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')} -
{formatNumber(segInfo?.hit_count as number)} {t('datasetDocuments.segment.hitCount')} -
{t('datasetDocuments.segment.vectorHash')}{segInfo?.index_node_hash} + ) + } + + return ( +
+
+
+
{isEditMode ? 'Edit Chunk' : 'Chunk Detail'}
+
+ + · + {formatNumber(segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')} +
- - {embeddingAvailable && ( + {isEditMode && fullScreen && ( <> - - { - await onChangeSwitch?.(val, segInfo?.id || '') - }} - disabled={archived} - /> + {renderActionButtons()} + )} +
+ +
+
+ +
+
+
+ {renderContent()} +
+ {renderKeywords()} +
+ {isEditMode && !fullScreen && ( +
+ {renderActionButtons()} +
+ )}
) } -SegmentDetail.displayName = 'SegmentDetail' - -export default React.memo(SegmentDetail) +export default SegmentDetail diff --git a/web/app/components/datasets/documents/detail/completed/segment-list.tsx b/web/app/components/datasets/documents/detail/completed/segment-list.tsx index a0e8ba37e7..485f70ea8e 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-list.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-list.tsx @@ -12,7 +12,7 @@ type ISegmentListProps = { items: SegmentDetailModel[] selectedSegmentIds: string[] onSelected: (segId: string) => void - onClick: (detail: SegmentDetailModel, isEditing?: boolean) => void + onClick: (detail: SegmentDetailModel, isEditMode?: boolean) => void onChangeSwitch: (enabled: boolean, segId?: string,) => Promise onDelete: (segId: string) => Promise archived?: boolean @@ -45,11 +45,11 @@ const SegmentList: FC = ({ checked={selectedSegmentIds.includes(segItem.id)} onCheck={() => onSelected(segItem.id)} /> -
+
onClickCard(segItem)} + onClick={() => onClickCard(segItem, true)} onChangeSwitch={onChangeSwitch} onClickEdit={() => onClickCard(segItem, true)} onDelete={onDelete} diff --git a/web/app/components/datasets/documents/detail/new-segment-modal.tsx b/web/app/components/datasets/documents/detail/new-segment-modal.tsx index dae9cf19fb..12b58b8658 100644 --- a/web/app/components/datasets/documents/detail/new-segment-modal.tsx +++ b/web/app/components/datasets/documents/detail/new-segment-modal.tsx @@ -3,15 +3,20 @@ import type { FC } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useParams } from 'next/navigation' -import { RiCloseLine } from '@remixicon/react' -import Modal from '@/app/components/base/modal' +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 { Hash02 } from '@/app/components/base/icons/src/vender/line/general' import { ToastContext } from '@/app/components/base/toast' import type { SegmentUpdater } from '@/models/datasets' import { addSegment } from '@/service/datasets' import TagInput from '@/app/components/base/tag-input' +import classNames from '@/utils/classnames' +import { formatNumber } from '@/utils/format' +import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' +import Divider from '@/app/components/base/divider' type NewSegmentModalProps = { isShow: boolean @@ -30,14 +35,15 @@ const NewSegmentModal: FC = ({ const { notify } = useContext(ToastContext) const [question, setQuestion] = useState('') const [answer, setAnswer] = useState('') - const { datasetId, documentId } = useParams() + const { datasetId, documentId } = useParams<{ datasetId: string; documentId: string }>() const [keywords, setKeywords] = useState([]) const [loading, setLoading] = useState(false) + const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen]) const handleCancel = () => { + onCancel() setQuestion('') setAnswer('') - onCancel() setKeywords([]) } @@ -74,6 +80,17 @@ const NewSegmentModal: FC = ({ } } + useKeyPress(['esc'], (e) => { + e.preventDefault() + handleCancel() + }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => { + e.preventDefault() + handleSave() + } + , { exactMatch: true, useCapture: true }) + const renderContent = () => { if (docForm === 'qa_model') { return ( @@ -101,7 +118,7 @@ const NewSegmentModal: FC = ({ return ( setQuestion(e.target.value)} @@ -110,46 +127,98 @@ const NewSegmentModal: FC = ({ ) } - return ( - { }} className='pt-8 px-8 pb-6 !max-w-[640px] !rounded-xl'> -
-
-
- + const renderActionButtons = () => { + return ( +
+
-
- - - - { - docForm === 'qa_model' - ? t('datasetDocuments.segment.newQaSegment') - : t('datasetDocuments.segment.newTextSegment') - } - - -
-
{renderContent()}
-
{t('datasetDocuments.segment.keywords')}
-
+ + +
+ ) + } + + const renderKeywords = () => { + return ( +
+
{t('datasetDocuments.segment.keywords')}
+
setKeywords(newKeywords)} />
-
- - -
- + ) + } + + 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()} + + + )} +
+ +
+
+ +
+
+
+
+
+ {renderContent()} +
+ {renderKeywords()} +
+ {!fullScreen && ( +
+ {renderActionButtons()} +
+ )} +
+
) } diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index ce35e78a8a..c342048d22 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -338,7 +338,7 @@ export const OperationAction: FC<{
} - btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!bg-gray-100 !shadow-none' : '!bg-transparent')} + btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!hover:bg-state-base-hover !shadow-none' : '!bg-transparent')} popupClassName='!w-full' className={`flex justify-end !w-[200px] h-fit !z-20 ${className}`} /> @@ -371,7 +371,7 @@ export const OperationAction: FC<{ export const renderTdValue = (value: string | number | null, isEmptyStyle = false) => { return ( -
+
{value ?? '-'}
) @@ -418,7 +418,7 @@ const DocumentList: FC = ({ const isGeneralMode = chunkingMode !== ChuckingMode.parentChild const isQAMode = chunkingMode === ChuckingMode.qa const [localDocs, setLocalDocs] = useState(documents) - const [enableSort, setEnableSort] = useState(false) + const [enableSort, setEnableSort] = useState(true) useEffect(() => { setLocalDocs(documents) @@ -426,7 +426,7 @@ const DocumentList: FC = ({ const onClickSort = () => { setEnableSort(!enableSort) - if (!enableSort) { + if (enableSort) { const sortedDocs = [...localDocs].sort((a, b) => dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1) setLocalDocs(sortedDocs) } @@ -497,7 +497,7 @@ const DocumentList: FC = ({ return (
- + - + {localDocs.map((doc, index) => { const isFile = doc.data_source_type === DataSourceType.FILE const fileType = isFile ? doc.data_source_detail_dict?.upload_file?.extension : '' return { router.push(`/datasets/${datasetId}/documents/${doc.id}`) }}> @@ -573,13 +573,13 @@ const DocumentList: FC = ({ popupContent={t('datasetDocuments.list.table.rename')} >
{ e.stopPropagation() handleShowRenameModal(doc) }} > - +
diff --git a/web/i18n/en-US/dataset-documents.ts b/web/i18n/en-US/dataset-documents.ts index 266c7fb2e7..a0bcb5bba0 100644 --- a/web/i18n/en-US/dataset-documents.ts +++ b/web/i18n/en-US/dataset-documents.ts @@ -334,20 +334,21 @@ const translation = { segment: { paragraphs: 'Paragraphs', chunks: 'CHUNKS', - keywords: 'Key Words', - addKeyWord: 'Add key word', + keywords: 'KEYWORDS', + addKeyWord: 'Add keyword', keywordError: 'The maximum length of keyword is 20', characters: 'characters', hitCount: 'Retrieval count', vectorHash: 'Vector hash: ', - questionPlaceholder: 'add question here', + questionPlaceholder: 'Add question here', questionEmpty: 'Question can not be empty', - answerPlaceholder: 'add answer here', + answerPlaceholder: 'Add answer here', answerEmpty: 'Answer can not be empty', - contentPlaceholder: 'add content here', + contentPlaceholder: 'Add content here', contentEmpty: 'Content can not be empty', newTextSegment: 'New Text Segment', newQaSegment: 'New Q&A Segment', + addChunk: 'Add Chunk', delete: 'Delete this chunk ?', }, } diff --git a/web/i18n/zh-Hans/dataset-documents.ts b/web/i18n/zh-Hans/dataset-documents.ts index 35288c04d6..355c4bee7f 100644 --- a/web/i18n/zh-Hans/dataset-documents.ts +++ b/web/i18n/zh-Hans/dataset-documents.ts @@ -346,6 +346,7 @@ const translation = { contentEmpty: '内容不能为空', newTextSegment: '新文本分段', newQaSegment: '新问答分段', + addChunk: '新增分段', delete: '删除这个分段?', }, } diff --git a/web/models/datasets.ts b/web/models/datasets.ts index a57b6ed13b..1c9999008e 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -459,6 +459,7 @@ export type SegmentsResponse = { limit: number total: number total_pages: number + page: number } export type HitTestingRecord = { @@ -615,6 +616,14 @@ export type ChildChunkDetail = { type: ChildChunkType } +export type ChildSegmentResponse = { + data: ChildChunkDetail[] + total: number + total_pages: number + page: number + limit: number +} + export type UpdateDocumentParams = { datasetId: string documentId: string diff --git a/web/service/knowledge/use-document.ts b/web/service/knowledge/use-document.ts index 5b200d899d..5efc936f41 100644 --- a/web/service/knowledge/use-document.ts +++ b/web/service/knowledge/use-document.ts @@ -35,7 +35,7 @@ const toBatchDocumentsIdParams = (documentIds: string[] | string) => { export const useDocumentBatchAction = (action: DocumentActionType) => { return useMutation({ mutationFn: ({ datasetId, documentIds, documentId }: UpdateDocumentBatchParams) => { - return patch(`/datasets/${datasetId}/documents/status/${action}?${toBatchDocumentsIdParams(documentId || documentIds!)}`) + return patch(`/datasets/${datasetId}/documents/status/${action}/batch?${toBatchDocumentsIdParams(documentId || documentIds!)}`) }, }) } @@ -59,7 +59,7 @@ export const useDocumentUnArchive = () => { export const useDocumentDelete = () => { return useMutation({ mutationFn: ({ datasetId, documentIds, documentId }: UpdateDocumentBatchParams) => { - return del(`/datasets/${datasetId}/documents?${toBatchDocumentsIdParams(documentId || documentIds!)}`) + return del(`/datasets/${datasetId}/documents/batch?${toBatchDocumentsIdParams(documentId || documentIds!)}`) }, }) } diff --git a/web/service/knowledge/use-segment.ts b/web/service/knowledge/use-segment.ts index 25679599e8..9f17cf76cd 100644 --- a/web/service/knowledge/use-segment.ts +++ b/web/service/knowledge/use-segment.ts @@ -1,11 +1,11 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { del, get, patch } from '../base' import type { CommonResponse } from '@/models/common' -import type { SegmentsResponse } from '@/models/datasets' +import type { ChildSegmentResponse, SegmentsResponse } from '@/models/datasets' const NAME_SPACE = 'segment' -const useSegmentListKey = [NAME_SPACE, 'list'] +const useSegmentListKey = [NAME_SPACE, 'chunkList'] export const useSegmentList = ( payload: { @@ -28,7 +28,7 @@ export const useSegmentList = ( return get(`/datasets/${datasetId}/documents/${documentId}/segments`, { params }) }, enabled: !disable, - initialData: disable ? { data: [], has_more: false, total: 0, total_pages: 0, limit: 10 } : undefined, + initialData: disable ? { data: [], has_more: false, page: 1, total: 0, total_pages: 0, limit: 10 } : undefined, }) } @@ -64,3 +64,30 @@ export const useDeleteSegment = () => { }, }) } + +const useChildSegmentListKey = [NAME_SPACE, 'childChunkList'] + +export const useChildSegmentList = ( + payload: { + datasetId: string + documentId: string + segmentId: string + params: { + page: number + limit: number + keyword: string + } + }, + disable?: boolean, +) => { + const { datasetId, documentId, segmentId, params } = payload + const { page, limit, keyword } = params + return useQuery({ + queryKey: [...useChildSegmentListKey, datasetId, documentId, segmentId, page, limit, keyword], + queryFn: () => { + return get(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { params }) + }, + enabled: !disable, + initialData: disable ? { data: [], total: 0, page: 1, total_pages: 0, limit: 10 } : undefined, + }) +}
e.stopPropagation()}> @@ -519,22 +519,22 @@ const DocumentList: FC = ({
{t('datasetDocuments.list.table.header.words')} {t('datasetDocuments.list.table.header.hitCount')} -
+
{t('datasetDocuments.list.table.header.uploadTime')} - +
{t('datasetDocuments.list.table.header.status')} {t('datasetDocuments.list.table.header.action')}