mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 05:48:58 +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'
|
||||
@ -39,7 +42,7 @@ export const SettingsIcon = ({ className }: SVGProps<SVGElement>) => {
|
||||
|
||||
export const SyncIcon = () => {
|
||||
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.69773 13.1783C7.29715 13.8879 9.20212 13.8494 10.8334 12.9075C13.5438 11.3427 14.4724 7.87704 12.9076 5.16672L12.7409 4.87804M3.09233 10.8335C1.52752 8.12314 2.45615 4.65746 5.16647 3.09265C6.7978 2.15081 8.70277 2.11227 10.3022 2.82185M1.66226 10.8892L3.48363 11.3773L3.97166 9.5559M12.0284 6.44393L12.5164 4.62256L14.3378 5.1106" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.69773 13.1783C7.29715 13.8879 9.20212 13.8494 10.8334 12.9075C13.5438 11.3427 14.4724 7.87704 12.9076 5.16672L12.7409 4.87804M3.09233 10.8335C1.52752 8.12314 2.45615 4.65746 5.16647 3.09265C6.7978 2.15081 8.70277 2.11227 10.3022 2.82185M1.66226 10.8892L3.48363 11.3773L3.97166 9.5559M12.0284 6.44393L12.5164 4.62256L14.3378 5.1106" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
}
|
||||
|
||||
@ -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}>
|
||||
{
|
||||
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>
|
||||
}
|
||||
{
|
||||
doc.data_source_type === DataSourceType.NOTION
|
||||
? <span>{doc.name}</span>
|
||||
: <span>{doc?.name?.replace(/\.[^/.]+$/, '')}<span className='text-gray-500'>.{suffix}</span></span>
|
||||
}
|
||||
<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 ?? fileType}Icon`], s.commonIcon, 'mr-1.5')}></div>
|
||||
}
|
||||
{
|
||||
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