mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 05:36:02 +08:00
feat: document support rename in in dataset (#4732)
This commit is contained in:
parent
9cf9720efa
commit
96460d5ea3
@ -150,6 +150,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
|
||||
scene='detail'
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
detail={{
|
||||
name: documentDetail?.name || '',
|
||||
enabled: documentDetail?.enabled || false,
|
||||
archived: documentDetail?.archived || false,
|
||||
id: documentId,
|
||||
|
@ -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'
|
||||
@ -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 <div className='flex items-center' onClick={e => e.stopPropagation()}>
|
||||
{isListScene && !embeddingAvailable && (
|
||||
<Switch defaultValue={false} onChange={() => { }} disabled={true} size='md' />
|
||||
@ -213,6 +236,15 @@ export const OperationAction: FC<{
|
||||
</>}
|
||||
{!archived && (
|
||||
<>
|
||||
<div className={s.actionItem} onClick={() => {
|
||||
handleShowRenameModal({
|
||||
id: detail.id,
|
||||
name: detail.name,
|
||||
})
|
||||
}}>
|
||||
<Edit03 className='w-4 h-4 text-gray-500' />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.table.rename')}</span>
|
||||
</div>
|
||||
<div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
|
||||
<SettingsIcon />
|
||||
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
|
||||
@ -272,6 +304,16 @@ export const OperationAction: FC<{
|
||||
</div>
|
||||
</div>
|
||||
</Modal>}
|
||||
|
||||
{isShowRenameModal && currDocument && (
|
||||
<RenameModal
|
||||
datasetId={datasetId}
|
||||
documentId={currDocument.id}
|
||||
name={currDocument.name}
|
||||
onClose={setShowRenameModalFalse}
|
||||
onSaved={handleRenamed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -326,13 +368,30 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
}
|
||||
}
|
||||
|
||||
const [currDocument, setCurrDocument] = useState<LocalDoc | null>(null)
|
||||
const [isShowRenameModal, {
|
||||
setTrue: setShowRenameModalTrue,
|
||||
setFalse: setShowRenameModalFalse,
|
||||
}] = useBoolean(false)
|
||||
const handleShowRenameModal = useCallback((doc: LocalDoc) => {
|
||||
setCurrDocument(doc)
|
||||
setShowRenameModalTrue()
|
||||
}, [setShowRenameModalTrue])
|
||||
const handleRenamed = useCallback(() => {
|
||||
onUpdate()
|
||||
}, [onUpdate])
|
||||
|
||||
return (
|
||||
<div className='w-full h-full overflow-x-auto'>
|
||||
<table className={`min-w-[700px] max-w-full w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}>
|
||||
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-medium text-xs uppercase">
|
||||
<tr>
|
||||
<td className='w-12'>#</td>
|
||||
<td>{t('datasetDocuments.list.table.header.fileName')}</td>
|
||||
<td>
|
||||
<div className='flex'>
|
||||
{t('datasetDocuments.list.table.header.fileName')}
|
||||
</div>
|
||||
</td>
|
||||
<td className='w-24'>{t('datasetDocuments.list.table.header.words')}</td>
|
||||
<td className='w-44'>{t('datasetDocuments.list.table.header.hitCount')}</td>
|
||||
<td className='w-44'>
|
||||
@ -347,7 +406,8 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
</thead>
|
||||
<tbody className="text-gray-700">
|
||||
{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 <tr
|
||||
key={doc.id}
|
||||
className={'border-b border-gray-200 h-8 hover:bg-gray-50 cursor-pointer'}
|
||||
@ -355,17 +415,33 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
router.push(`/datasets/${datasetId}/documents/${doc.id}`)
|
||||
}}>
|
||||
<td className='text-left align-middle text-gray-500 text-xs'>{doc.position}</td>
|
||||
<td className={s.tdValue}>
|
||||
<td>
|
||||
<div className='group flex items-center justify-between'>
|
||||
<span className={s.tdValue}>
|
||||
{
|
||||
doc?.data_source_type === DataSourceType.NOTION
|
||||
? <NotionIcon className='inline-flex -mt-[3px] mr-1.5 align-middle' type='page' src={doc.data_source_info.notion_page_icon} />
|
||||
: <div className={cn(s[`${doc?.data_source_info?.upload_file?.extension ?? suffix}Icon`], s.commonIcon, 'mr-1.5')}></div>
|
||||
: <div className={cn(s[`${doc?.data_source_info?.upload_file?.extension ?? fileType}Icon`], s.commonIcon, 'mr-1.5')}></div>
|
||||
}
|
||||
{
|
||||
doc.data_source_type === DataSourceType.NOTION
|
||||
? <span>{doc.name}</span>
|
||||
: <span>{doc?.name?.replace(/\.[^/.]+$/, '')}<span className='text-gray-500'>.{suffix}</span></span>
|
||||
doc.name
|
||||
}
|
||||
</span>
|
||||
<div className='group-hover:flex hidden'>
|
||||
<TooltipPlus popupContent={t('datasetDocuments.list.table.rename')}>
|
||||
<div
|
||||
className='p-1 rounded-md cursor-pointer hover:bg-black/5'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleShowRenameModal(doc)
|
||||
}}
|
||||
>
|
||||
<Edit03 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td>{renderCount(doc.word_count)}</td>
|
||||
<td>{renderCount(doc.hit_count)}</td>
|
||||
@ -383,7 +459,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
<OperationAction
|
||||
embeddingAvailable={embeddingAvailable}
|
||||
datasetId={datasetId}
|
||||
detail={pick(doc, ['enabled', 'archived', 'id', 'data_source_type', 'doc_form'])}
|
||||
detail={pick(doc, ['name', 'enabled', 'archived', 'id', 'data_source_type', 'doc_form'])}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</td>
|
||||
@ -391,6 +467,16 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{isShowRenameModal && currDocument && (
|
||||
<RenameModal
|
||||
datasetId={datasetId}
|
||||
documentId={currDocument.id}
|
||||
name={currDocument.name}
|
||||
onClose={setShowRenameModalFalse}
|
||||
onSaved={handleRenamed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
75
web/app/components/datasets/documents/rename-modal.tsx
Normal file
75
web/app/components/datasets/documents/rename-modal.tsx
Normal file
@ -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<Props> = ({
|
||||
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 (
|
||||
<Modal
|
||||
title={t('datasetDocuments.list.table.rename')}
|
||||
isShow
|
||||
onClose={onClose}
|
||||
wrapperClassName='!z-50'
|
||||
>
|
||||
<div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('datasetDocuments.list.table.name')}</div>
|
||||
<input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'}
|
||||
value={newName}
|
||||
onChange={e => setNewName(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<Button className='mr-2 flex-shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button type='primary' className='flex-shrink-0' onClick={handleSave} loading={saveLoading}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default React.memo(RenameModal)
|
@ -13,6 +13,8 @@ const translation = {
|
||||
status: 'STATUS',
|
||||
action: 'ACTION',
|
||||
},
|
||||
rename: 'Rename',
|
||||
name: 'Name',
|
||||
},
|
||||
action: {
|
||||
uploadFile: 'Upload new file',
|
||||
|
@ -13,6 +13,8 @@ const translation = {
|
||||
status: '状态',
|
||||
action: '操作',
|
||||
},
|
||||
rename: '重命名',
|
||||
name: '名称',
|
||||
},
|
||||
action: {
|
||||
uploadFile: '上传新文件',
|
||||
|
@ -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 = {
|
||||
|
@ -114,6 +114,12 @@ export const fetchDocumentDetail: Fetcher<DocumentDetailResponse, CommonDocReq &
|
||||
return get<DocumentDetailResponse>(`/datasets/${datasetId}/documents/${documentId}`, { params })
|
||||
}
|
||||
|
||||
export const renameDocumentName: Fetcher<CommonResponse, CommonDocReq & { name: string }> = ({ datasetId, documentId, name }) => {
|
||||
return post<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/rename`, {
|
||||
body: { name },
|
||||
})
|
||||
}
|
||||
|
||||
export const pauseDocIndexing: Fetcher<CommonResponse, CommonDocReq> = ({ datasetId, documentId }) => {
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/processing/pause`)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user