feat: add document pause and resume functionality (#21894)

This commit is contained in:
GuanMu
2025-07-04 14:06:47 +08:00
committed by GitHub
parent c9c49200e0
commit a66ed7157e
22 changed files with 88 additions and 4 deletions

View File

@@ -11,6 +11,8 @@ import {
RiEqualizer2Line, RiEqualizer2Line,
RiLoopLeftLine, RiLoopLeftLine,
RiMoreFill, RiMoreFill,
RiPauseCircleLine,
RiPlayCircleLine,
} from '@remixicon/react' } from '@remixicon/react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
@@ -42,7 +44,7 @@ import { useDatasetDetailContextWithSelector as useDatasetDetailContext } from '
import type { Props as PaginationProps } from '@/app/components/base/pagination' import type { Props as PaginationProps } from '@/app/components/base/pagination'
import Pagination from '@/app/components/base/pagination' import Pagination from '@/app/components/base/pagination'
import Checkbox from '@/app/components/base/checkbox' import Checkbox from '@/app/components/base/checkbox'
import { useDocumentArchive, useDocumentDelete, useDocumentDisable, useDocumentEnable, useDocumentUnArchive, useSyncDocument, useSyncWebsite } from '@/service/knowledge/use-document' import { useDocumentArchive, useDocumentDelete, useDocumentDisable, useDocumentEnable, useDocumentPause, useDocumentResume, useDocumentUnArchive, useSyncDocument, useSyncWebsite } from '@/service/knowledge/use-document'
import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type' import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type'
import useBatchEditDocumentMetadata from '../metadata/hooks/use-batch-edit-document-metadata' import useBatchEditDocumentMetadata from '../metadata/hooks/use-batch-edit-document-metadata'
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal' import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
@@ -168,7 +170,7 @@ export const StatusItem: FC<{
</div> </div>
} }
type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' | 'pause' | 'resume'
// operation action for list and detail // operation action for list and detail
export const OperationAction: FC<{ export const OperationAction: FC<{
@@ -180,13 +182,14 @@ export const OperationAction: FC<{
id: string id: string
data_source_type: string data_source_type: string
doc_form: string doc_form: string
display_status?: string
} }
datasetId: string datasetId: string
onUpdate: (operationName?: string) => void onUpdate: (operationName?: string) => void
scene?: 'list' | 'detail' scene?: 'list' | 'detail'
className?: string className?: string
}> = ({ embeddingAvailable, datasetId, detail, onUpdate, scene = 'list', className = '' }) => { }> = ({ embeddingAvailable, datasetId, detail, onUpdate, scene = 'list', className = '' }) => {
const { id, enabled = false, archived = false, data_source_type } = detail || {} const { id, enabled = false, archived = false, data_source_type, display_status } = detail || {}
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [deleting, setDeleting] = useState(false) const [deleting, setDeleting] = useState(false)
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
@@ -199,6 +202,8 @@ export const OperationAction: FC<{
const { mutateAsync: deleteDocument } = useDocumentDelete() const { mutateAsync: deleteDocument } = useDocumentDelete()
const { mutateAsync: syncDocument } = useSyncDocument() const { mutateAsync: syncDocument } = useSyncDocument()
const { mutateAsync: syncWebsite } = useSyncWebsite() const { mutateAsync: syncWebsite } = useSyncWebsite()
const { mutateAsync: pauseDocument } = useDocumentPause()
const { mutateAsync: resumeDocument } = useDocumentResume()
const isListScene = scene === 'list' const isListScene = scene === 'list'
const onOperate = async (operationName: OperationName) => { const onOperate = async (operationName: OperationName) => {
@@ -222,6 +227,12 @@ export const OperationAction: FC<{
else else
opApi = syncWebsite opApi = syncWebsite
break break
case 'pause':
opApi = pauseDocument
break
case 'resume':
opApi = resumeDocument
break
default: default:
opApi = deleteDocument opApi = deleteDocument
setDeleting(true) setDeleting(true)
@@ -323,6 +334,18 @@ export const OperationAction: FC<{
<Divider className='my-1' /> <Divider className='my-1' />
</> </>
)} )}
{!archived && display_status?.toLowerCase() === 'indexing' && (
<div className={s.actionItem} onClick={() => onOperate('pause')}>
<RiPauseCircleLine className='h-4 w-4 text-text-tertiary' />
<span className={s.actionName}>{t('datasetDocuments.list.action.pause')}</span>
</div>
)}
{!archived && display_status?.toLowerCase() === 'paused' && (
<div className={s.actionItem} onClick={() => onOperate('resume')}>
<RiPlayCircleLine className='h-4 w-4 text-text-tertiary' />
<span className={s.actionName}>{t('datasetDocuments.list.action.resume')}</span>
</div>
)}
{!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}> {!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
<RiArchive2Line className='h-4 w-4 text-text-tertiary' /> <RiArchive2Line className='h-4 w-4 text-text-tertiary' />
<span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span> <span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
@@ -626,7 +649,7 @@ const DocumentList: FC<IDocumentListProps> = ({
<OperationAction <OperationAction
embeddingAvailable={embeddingAvailable} embeddingAvailable={embeddingAvailable}
datasetId={datasetId} datasetId={datasetId}
detail={pick(doc, ['name', 'enabled', 'archived', 'id', 'data_source_type', 'doc_form'])} detail={pick(doc, ['name', 'enabled', 'archived', 'id', 'data_source_type', 'doc_form', 'display_status'])}
onUpdate={onUpdate} onUpdate={onUpdate}
/> />
</td> </td>

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Löschen', delete: 'Löschen',
enableWarning: 'Archivierte Datei kann nicht aktiviert werden', enableWarning: 'Archivierte Datei kann nicht aktiviert werden',
sync: 'Synchronisieren', sync: 'Synchronisieren',
resume: 'Fortsetzen',
pause: 'Pause',
}, },
index: { index: {
enable: 'Aktivieren', enable: 'Aktivieren',

View File

@@ -30,6 +30,8 @@ const translation = {
delete: 'Delete', delete: 'Delete',
enableWarning: 'Archived file cannot be enabled', enableWarning: 'Archived file cannot be enabled',
sync: 'Sync', sync: 'Sync',
pause: 'Pause',
resume: 'Resume',
}, },
index: { index: {
enable: 'Enable', enable: 'Enable',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'Eliminar', delete: 'Eliminar',
enableWarning: 'El archivo archivado no puede habilitarse', enableWarning: 'El archivo archivado no puede habilitarse',
sync: 'Sincronizar', sync: 'Sincronizar',
resume: 'Reanudar',
pause: 'Pausa',
}, },
index: { index: {
enable: 'Habilitar', enable: 'Habilitar',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'حذف', delete: 'حذف',
enableWarning: 'فایل بایگانی شده نمی‌تواند فعال شود', enableWarning: 'فایل بایگانی شده نمی‌تواند فعال شود',
sync: 'همگام‌سازی', sync: 'همگام‌سازی',
resume: 'رزومه',
pause: 'مکث',
}, },
index: { index: {
enable: 'فعال کردن', enable: 'فعال کردن',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Supprimer', delete: 'Supprimer',
enableWarning: 'Le fichier archivé ne peut pas être activé', enableWarning: 'Le fichier archivé ne peut pas être activé',
sync: 'Synchroniser', sync: 'Synchroniser',
pause: 'Pause',
resume: 'Reprendre',
}, },
index: { index: {
enable: 'Activer', enable: 'Activer',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'हटाएँ', delete: 'हटाएँ',
enableWarning: 'संग्रहित फाइल को सक्रिय नहीं किया जा सकता', enableWarning: 'संग्रहित फाइल को सक्रिय नहीं किया जा सकता',
sync: 'सिंक्रोनाइज़ करें', sync: 'सिंक्रोनाइज़ करें',
resume: 'रिज़्यूमे',
pause: 'रोकें',
}, },
index: { index: {
enable: 'सक्रिय करें', enable: 'सक्रिय करें',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'Elimina', delete: 'Elimina',
enableWarning: 'Il file archiviato non può essere abilitato', enableWarning: 'Il file archiviato non può essere abilitato',
sync: 'Sincronizza', sync: 'Sincronizza',
resume: 'Riassumere',
pause: 'Pausa',
}, },
index: { index: {
enable: 'Abilita', enable: 'Abilita',

View File

@@ -30,6 +30,8 @@ const translation = {
delete: '削除', delete: '削除',
enableWarning: 'アーカイブされたファイルは有効にできません', enableWarning: 'アーカイブされたファイルは有効にできません',
sync: '同期', sync: '同期',
pause: '一時停止',
resume: '履歴書',
}, },
index: { index: {
enable: '有効にする', enable: '有効にする',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: '삭제', delete: '삭제',
enableWarning: '아카이브된 파일은 활성화할 수 없습니다.', enableWarning: '아카이브된 파일은 활성화할 수 없습니다.',
sync: '동기화', sync: '동기화',
resume: '이력서',
pause: '일시 중지',
}, },
index: { index: {
enable: '활성화', enable: '활성화',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Usuń', delete: 'Usuń',
enableWarning: 'Zarchiwizowany plik nie może zostać włączony', enableWarning: 'Zarchiwizowany plik nie może zostać włączony',
sync: 'Synchronizuj', sync: 'Synchronizuj',
resume: 'Wznawiać',
pause: 'Pauza',
}, },
index: { index: {
enable: 'Włącz', enable: 'Włącz',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Excluir', delete: 'Excluir',
enableWarning: 'O arquivo arquivado não pode ser habilitado', enableWarning: 'O arquivo arquivado não pode ser habilitado',
sync: 'Sincronizar', sync: 'Sincronizar',
resume: 'Retomar',
pause: 'Pausa',
}, },
index: { index: {
enable: 'Habilitar', enable: 'Habilitar',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Șterge', delete: 'Șterge',
enableWarning: 'Fișierul arhivat nu poate fi activat', enableWarning: 'Fișierul arhivat nu poate fi activat',
sync: 'Sincronizează', sync: 'Sincronizează',
pause: 'Pauză',
resume: 'Relua',
}, },
index: { index: {
enable: 'Activează', enable: 'Activează',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'Удалить', delete: 'Удалить',
enableWarning: 'Архивный файл не может быть включен', enableWarning: 'Архивный файл не может быть включен',
sync: 'Синхронизировать', sync: 'Синхронизировать',
resume: 'Резюме',
pause: 'Пауза',
}, },
index: { index: {
enable: 'Включить', enable: 'Включить',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'Izbriši', delete: 'Izbriši',
enableWarning: 'Arhivirane datoteke ni mogoče omogočiti', enableWarning: 'Arhivirane datoteke ni mogoče omogočiti',
sync: 'Sinhroniziraj', sync: 'Sinhroniziraj',
pause: 'Pavza',
resume: 'Življenjepis',
}, },
index: { index: {
enable: 'Omogoči', enable: 'Omogoči',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'ลบ', delete: 'ลบ',
enableWarning: 'ไม่สามารถเปิดใช้งานไฟล์ที่เก็บถาวรได้', enableWarning: 'ไม่สามารถเปิดใช้งานไฟล์ที่เก็บถาวรได้',
sync: 'ซิงค์', sync: 'ซิงค์',
pause: 'หยุด',
resume: 'ดำเนิน',
}, },
index: { index: {
enable: 'เปิด', enable: 'เปิด',

View File

@@ -29,6 +29,8 @@ const translation = {
delete: 'Sil', delete: 'Sil',
enableWarning: 'Arşivlenmiş dosya etkinleştirilemez', enableWarning: 'Arşivlenmiş dosya etkinleştirilemez',
sync: 'Senkronize et', sync: 'Senkronize et',
pause: 'Duraklat',
resume: 'Özgeçmiş',
}, },
index: { index: {
enable: 'Etkinleştir', enable: 'Etkinleştir',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Видалити', delete: 'Видалити',
enableWarning: 'Архівований файл неможливо активувати', enableWarning: 'Архівований файл неможливо активувати',
sync: 'Синхронізувати', sync: 'Синхронізувати',
pause: 'Пауза',
resume: 'Резюме',
}, },
index: { index: {
enable: 'Активувати', enable: 'Активувати',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: 'Xóa', delete: 'Xóa',
enableWarning: 'Tệp đã lưu trữ không thể được kích hoạt', enableWarning: 'Tệp đã lưu trữ không thể được kích hoạt',
sync: 'Đồng bộ', sync: 'Đồng bộ',
pause: 'Tạm dừng',
resume: 'Tiếp tục',
}, },
index: { index: {
enable: 'Kích hoạt', enable: 'Kích hoạt',

View File

@@ -30,6 +30,8 @@ const translation = {
delete: '删除', delete: '删除',
enableWarning: '归档的文件无法启用', enableWarning: '归档的文件无法启用',
sync: '同步', sync: '同步',
pause: '暂停',
resume: '恢复',
}, },
index: { index: {
enable: '启用中', enable: '启用中',

View File

@@ -28,6 +28,8 @@ const translation = {
delete: '刪除', delete: '刪除',
enableWarning: '歸檔的檔案無法啟用', enableWarning: '歸檔的檔案無法啟用',
sync: '同步', sync: '同步',
resume: '恢復',
pause: '暫停',
}, },
index: { index: {
enable: '啟用中', enable: '啟用中',

View File

@@ -5,6 +5,7 @@ import {
import { del, get, patch } from '../base' import { del, get, patch } from '../base'
import { useInvalid } from '../use-base' import { useInvalid } from '../use-base'
import type { MetadataType, SortType } from '../datasets' import type { MetadataType, SortType } from '../datasets'
import { pauseDocIndexing, resumeDocIndexing } from '../datasets'
import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets' import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets'
import { DocumentActionType } from '@/models/datasets' import { DocumentActionType } from '@/models/datasets'
import type { CommonResponse } from '@/models/common' import type { CommonResponse } from '@/models/common'
@@ -130,3 +131,23 @@ export const useDocumentMetadata = (payload: {
export const useInvalidDocumentDetailKey = () => { export const useInvalidDocumentDetailKey = () => {
return useInvalid(useDocumentDetailKey) return useInvalid(useDocumentDetailKey)
} }
export const useDocumentPause = () => {
return useMutation({
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
if (!datasetId || !documentId)
throw new Error('datasetId and documentId are required')
return pauseDocIndexing({ datasetId, documentId }) as Promise<CommonResponse>
},
})
}
export const useDocumentResume = () => {
return useMutation({
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
if (!datasetId || !documentId)
throw new Error('datasetId and documentId are required')
return resumeDocIndexing({ datasetId, documentId }) as Promise<CommonResponse>
},
})
}