diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index cda6e98a26..8e2bac6926 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -150,6 +150,7 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => { scene='detail' embeddingAvailable={embeddingAvailable} detail={{ + name: documentDetail?.name || '', enabled: documentDetail?.enabled || false, archived: documentDetail?.archived || false, id: documentId, diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index 01618b6e6b..4566287fb8 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -1,8 +1,8 @@ /* eslint-disable no-mixed-operators */ 'use client' import type { FC, SVGProps } from 'react' -import React, { useEffect, useState } from 'react' -import { useDebounceFn } from 'ahooks' +import React, { useCallback, useEffect, useState } from 'react' +import { useBoolean, useDebounceFn } from 'ahooks' import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline' import { ExclamationCircleIcon } from '@heroicons/react/24/solid' import { pick } from 'lodash-es' @@ -11,7 +11,10 @@ import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import cn from 'classnames' import dayjs from 'dayjs' +import { Edit03 } from '../../base/icons/src/vender/solid/general' +import TooltipPlus from '../../base/tooltip-plus' import s from './style.module.css' +import RenameModal from './rename-modal' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' import Popover from '@/app/components/base/popover' @@ -39,7 +42,7 @@ export const SettingsIcon = ({ className }: SVGProps) => { export const SyncIcon = () => { return - + } @@ -107,6 +110,7 @@ type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_ export const OperationAction: FC<{ embeddingAvailable: boolean detail: { + name: string enabled: boolean archived: boolean id: string @@ -164,6 +168,25 @@ export const OperationAction: FC<{ onOperate(operationName) }, { wait: 500 }) + const [currDocument, setCurrDocument] = useState<{ + id: string + name: string + } | null>(null) + const [isShowRenameModal, { + setTrue: setShowRenameModalTrue, + setFalse: setShowRenameModalFalse, + }] = useBoolean(false) + const handleShowRenameModal = useCallback((doc: { + id: string + name: string + }) => { + setCurrDocument(doc) + setShowRenameModalTrue() + }, [setShowRenameModalTrue]) + const handleRenamed = useCallback(() => { + onUpdate() + }, [onUpdate]) + return
e.stopPropagation()}> {isListScene && !embeddingAvailable && ( { }} disabled={true} size='md' /> @@ -213,6 +236,15 @@ export const OperationAction: FC<{ } {!archived && ( <> +
{ + handleShowRenameModal({ + id: detail.id, + name: detail.name, + }) + }}> + + {t('datasetDocuments.list.table.rename')} +
router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}> {t('datasetDocuments.list.action.settings')} @@ -272,6 +304,16 @@ export const OperationAction: FC<{
} + + {isShowRenameModal && currDocument && ( + + )} } @@ -326,13 +368,30 @@ const DocumentList: FC = ({ embeddingAvailable, documents = } } + const [currDocument, setCurrDocument] = useState(null) + const [isShowRenameModal, { + setTrue: setShowRenameModalTrue, + setFalse: setShowRenameModalFalse, + }] = useBoolean(false) + const handleShowRenameModal = useCallback((doc: LocalDoc) => { + setCurrDocument(doc) + setShowRenameModalTrue() + }, [setShowRenameModalTrue]) + const handleRenamed = useCallback(() => { + onUpdate() + }, [onUpdate]) + return (
- + {localDocs.map((doc) => { - const suffix = doc.name.split('.').pop() || 'txt' + const isFile = doc.data_source_type === DataSourceType.FILE + const fileType = isFile ? doc.data_source_detail_dict?.upload_file.extension : '' return = ({ embeddingAvailable, documents = router.push(`/datasets/${datasetId}/documents/${doc.id}`) }}> - @@ -383,7 +459,7 @@ const DocumentList: FC = ({ embeddingAvailable, documents = @@ -391,6 +467,16 @@ const DocumentList: FC = ({ embeddingAvailable, documents = })}
#{t('datasetDocuments.list.table.header.fileName')} +
+ {t('datasetDocuments.list.table.header.fileName')} +
+
{t('datasetDocuments.list.table.header.words')} {t('datasetDocuments.list.table.header.hitCount')} @@ -347,7 +406,8 @@ const DocumentList: FC = ({ embeddingAvailable, documents =
{doc.position} - { - doc?.data_source_type === DataSourceType.NOTION - ? - :
- } - { - doc.data_source_type === DataSourceType.NOTION - ? {doc.name} - : {doc?.name?.replace(/\.[^/.]+$/, '')}.{suffix} - } +
+
+ + { + doc?.data_source_type === DataSourceType.NOTION + ? + :
+ } + { + doc.name + } +
+
+ +
{ + e.stopPropagation() + handleShowRenameModal(doc) + }} + > + +
+
+
+
+
{renderCount(doc.word_count)} {renderCount(doc.hit_count)}
+ + {isShowRenameModal && currDocument && ( + + )}
) } diff --git a/web/app/components/datasets/documents/rename-modal.tsx b/web/app/components/datasets/documents/rename-modal.tsx new file mode 100644 index 0000000000..a47d47e4b3 --- /dev/null +++ b/web/app/components/datasets/documents/rename-modal.tsx @@ -0,0 +1,75 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' +import Toast from '../../base/toast' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' +import { renameDocumentName } from '@/service/datasets' + +type Props = { + datasetId: string + documentId: string + name: string + onClose: () => void + onSaved: () => void +} + +const RenameModal: FC = ({ + documentId, + datasetId, + name, + onClose, + onSaved, +}) => { + const { t } = useTranslation() + + const [newName, setNewName] = useState(name) + const [saveLoading, { + setTrue: setSaveLoadingTrue, + setFalse: setSaveLoadingFalse, + }] = useBoolean(false) + + const handleSave = async () => { + setSaveLoadingTrue() + try { + await renameDocumentName({ + datasetId, + documentId, + name: newName, + }) + Toast.notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + onSaved() + onClose() + } + catch (error) { + if (error) + Toast.notify({ type: 'error', message: error.toString() }) + } + finally { + setSaveLoadingFalse() + } + } + + return ( + +
{t('datasetDocuments.list.table.name')}
+ setNewName(e.target.value)} + /> + +
+ + +
+
+ ) +} +export default React.memo(RenameModal) diff --git a/web/i18n/en-US/dataset-documents.ts b/web/i18n/en-US/dataset-documents.ts index 6a8cf6c5ca..b431965323 100644 --- a/web/i18n/en-US/dataset-documents.ts +++ b/web/i18n/en-US/dataset-documents.ts @@ -13,6 +13,8 @@ const translation = { status: 'STATUS', action: 'ACTION', }, + rename: 'Rename', + name: 'Name', }, action: { uploadFile: 'Upload new file', diff --git a/web/i18n/zh-Hans/dataset-documents.ts b/web/i18n/zh-Hans/dataset-documents.ts index 903f4b916e..9ea5e7aa0f 100644 --- a/web/i18n/zh-Hans/dataset-documents.ts +++ b/web/i18n/zh-Hans/dataset-documents.ts @@ -13,6 +13,8 @@ const translation = { status: '状态', action: '操作', }, + rename: '重命名', + name: '名称', }, action: { uploadFile: '上传新文件', diff --git a/web/models/datasets.ts b/web/models/datasets.ts index 585c0473d4..1f02a43184 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -178,6 +178,12 @@ export type SimpleDocumentDetail = InitialDocumentDetail & { updated_at: number hit_count: number dataset_process_rule_id?: string + data_source_detail_dict?: { + upload_file: { + name: string + extension: string + } + } } export type DocumentListResponse = { diff --git a/web/service/datasets.ts b/web/service/datasets.ts index 628aa4df6a..302b16f6f5 100644 --- a/web/service/datasets.ts +++ b/web/service/datasets.ts @@ -114,6 +114,12 @@ export const fetchDocumentDetail: Fetcher(`/datasets/${datasetId}/documents/${documentId}`, { params }) } +export const renameDocumentName: Fetcher = ({ datasetId, documentId, name }) => { + return post(`/datasets/${datasetId}/documents/${documentId}/rename`, { + body: { name }, + }) +} + export const pauseDocIndexing: Fetcher = ({ datasetId, documentId }) => { return patch(`/datasets/${datasetId}/documents/${documentId}/processing/pause`) }