mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 16:29:01 +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'
|
scene='detail'
|
||||||
embeddingAvailable={embeddingAvailable}
|
embeddingAvailable={embeddingAvailable}
|
||||||
detail={{
|
detail={{
|
||||||
|
name: documentDetail?.name || '',
|
||||||
enabled: documentDetail?.enabled || false,
|
enabled: documentDetail?.enabled || false,
|
||||||
archived: documentDetail?.archived || false,
|
archived: documentDetail?.archived || false,
|
||||||
id: documentId,
|
id: documentId,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable no-mixed-operators */
|
/* eslint-disable no-mixed-operators */
|
||||||
'use client'
|
'use client'
|
||||||
import type { FC, SVGProps } from 'react'
|
import type { FC, SVGProps } from 'react'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useDebounceFn } from 'ahooks'
|
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||||
import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
|
import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
|
||||||
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
|
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
|
||||||
import { pick } from 'lodash-es'
|
import { pick } from 'lodash-es'
|
||||||
@ -11,7 +11,10 @@ import { useRouter } from 'next/navigation'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import dayjs from 'dayjs'
|
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 s from './style.module.css'
|
||||||
|
import RenameModal from './rename-modal'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import Popover from '@/app/components/base/popover'
|
import Popover from '@/app/components/base/popover'
|
||||||
@ -107,6 +110,7 @@ type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_
|
|||||||
export const OperationAction: FC<{
|
export const OperationAction: FC<{
|
||||||
embeddingAvailable: boolean
|
embeddingAvailable: boolean
|
||||||
detail: {
|
detail: {
|
||||||
|
name: string
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
archived: boolean
|
archived: boolean
|
||||||
id: string
|
id: string
|
||||||
@ -164,6 +168,25 @@ export const OperationAction: FC<{
|
|||||||
onOperate(operationName)
|
onOperate(operationName)
|
||||||
}, { wait: 500 })
|
}, { 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()}>
|
return <div className='flex items-center' onClick={e => e.stopPropagation()}>
|
||||||
{isListScene && !embeddingAvailable && (
|
{isListScene && !embeddingAvailable && (
|
||||||
<Switch defaultValue={false} onChange={() => { }} disabled={true} size='md' />
|
<Switch defaultValue={false} onChange={() => { }} disabled={true} size='md' />
|
||||||
@ -213,6 +236,15 @@ export const OperationAction: FC<{
|
|||||||
</>}
|
</>}
|
||||||
{!archived && (
|
{!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`)}>
|
<div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
|
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
|
||||||
@ -272,6 +304,16 @@ export const OperationAction: FC<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>}
|
</Modal>}
|
||||||
|
|
||||||
|
{isShowRenameModal && currDocument && (
|
||||||
|
<RenameModal
|
||||||
|
datasetId={datasetId}
|
||||||
|
documentId={currDocument.id}
|
||||||
|
name={currDocument.name}
|
||||||
|
onClose={setShowRenameModalFalse}
|
||||||
|
onSaved={handleRenamed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<div className='w-full h-full overflow-x-auto'>
|
<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}`}>
|
<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">
|
<thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-medium text-xs uppercase">
|
||||||
<tr>
|
<tr>
|
||||||
<td className='w-12'>#</td>
|
<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-24'>{t('datasetDocuments.list.table.header.words')}</td>
|
||||||
<td className='w-44'>{t('datasetDocuments.list.table.header.hitCount')}</td>
|
<td className='w-44'>{t('datasetDocuments.list.table.header.hitCount')}</td>
|
||||||
<td className='w-44'>
|
<td className='w-44'>
|
||||||
@ -347,7 +406,8 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-gray-700">
|
<tbody className="text-gray-700">
|
||||||
{localDocs.map((doc) => {
|
{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
|
return <tr
|
||||||
key={doc.id}
|
key={doc.id}
|
||||||
className={'border-b border-gray-200 h-8 hover:bg-gray-50 cursor-pointer'}
|
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}`)
|
router.push(`/datasets/${datasetId}/documents/${doc.id}`)
|
||||||
}}>
|
}}>
|
||||||
<td className='text-left align-middle text-gray-500 text-xs'>{doc.position}</td>
|
<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
|
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} />
|
? <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
|
doc.name
|
||||||
? <span>{doc.name}</span>
|
|
||||||
: <span>{doc?.name?.replace(/\.[^/.]+$/, '')}<span className='text-gray-500'>.{suffix}</span></span>
|
|
||||||
}
|
}
|
||||||
|
</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>
|
||||||
<td>{renderCount(doc.word_count)}</td>
|
<td>{renderCount(doc.word_count)}</td>
|
||||||
<td>{renderCount(doc.hit_count)}</td>
|
<td>{renderCount(doc.hit_count)}</td>
|
||||||
@ -383,7 +459,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
|||||||
<OperationAction
|
<OperationAction
|
||||||
embeddingAvailable={embeddingAvailable}
|
embeddingAvailable={embeddingAvailable}
|
||||||
datasetId={datasetId}
|
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}
|
onUpdate={onUpdate}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@ -391,6 +467,16 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{isShowRenameModal && currDocument && (
|
||||||
|
<RenameModal
|
||||||
|
datasetId={datasetId}
|
||||||
|
documentId={currDocument.id}
|
||||||
|
name={currDocument.name}
|
||||||
|
onClose={setShowRenameModalFalse}
|
||||||
|
onSaved={handleRenamed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</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',
|
status: 'STATUS',
|
||||||
action: 'ACTION',
|
action: 'ACTION',
|
||||||
},
|
},
|
||||||
|
rename: 'Rename',
|
||||||
|
name: 'Name',
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
uploadFile: 'Upload new file',
|
uploadFile: 'Upload new file',
|
||||||
|
@ -13,6 +13,8 @@ const translation = {
|
|||||||
status: '状态',
|
status: '状态',
|
||||||
action: '操作',
|
action: '操作',
|
||||||
},
|
},
|
||||||
|
rename: '重命名',
|
||||||
|
name: '名称',
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
uploadFile: '上传新文件',
|
uploadFile: '上传新文件',
|
||||||
|
@ -178,6 +178,12 @@ export type SimpleDocumentDetail = InitialDocumentDetail & {
|
|||||||
updated_at: number
|
updated_at: number
|
||||||
hit_count: number
|
hit_count: number
|
||||||
dataset_process_rule_id?: string
|
dataset_process_rule_id?: string
|
||||||
|
data_source_detail_dict?: {
|
||||||
|
upload_file: {
|
||||||
|
name: string
|
||||||
|
extension: string
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DocumentListResponse = {
|
export type DocumentListResponse = {
|
||||||
|
@ -114,6 +114,12 @@ export const fetchDocumentDetail: Fetcher<DocumentDetailResponse, CommonDocReq &
|
|||||||
return get<DocumentDetailResponse>(`/datasets/${datasetId}/documents/${documentId}`, { params })
|
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 }) => {
|
export const pauseDocIndexing: Fetcher<CommonResponse, CommonDocReq> = ({ datasetId, documentId }) => {
|
||||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/processing/pause`)
|
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/processing/pause`)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user