浏览代码

模板管理

CzRger 4 月之前
父节点
当前提交
53672792a0

+ 122 - 99
web/app/components/datasets/documents/index.tsx

@@ -30,6 +30,7 @@ import useEditDocumentMetadata from '../metadata/hooks/use-edit-dataset-metadata
 import DatasetMetadataDrawer from '../metadata/metadata-dataset/dataset-metadata-drawer'
 import StatusWithAction from '../common/document-status-with-action/status-with-action'
 import { SimpleSelect } from '@/app/components/base/select'
+import DetailModal from './mould/index'
 
 const FolderPlusIcon = ({ className }: React.SVGProps<SVGElement>) => {
   return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
@@ -259,113 +260,135 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
     onUpdateDocList: invalidDocumentList,
   })
 
+  const [mouldModalVisible, setMouldModalVisible] = useState(false)
+  const [transfer, setTransfer] = useState<any>({
+    mode: 'add',
+  })
+
   return (
-    <div className='flex h-full flex-col overflow-y-auto'>
-      <div className='flex flex-col justify-center gap-1 px-6 pt-4'>
-        <h1 className='text-base font-semibold text-text-primary'>{t('datasetDocuments.list.title')}</h1>
-        <div className='flex items-center space-x-0.5 text-sm font-normal text-text-tertiary'>
-          <span>{t('datasetDocuments.list.desc')}</span>
-          <a
-            className='flex items-center text-text-accent'
-            target='_blank'
-            href='https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'>
-            <span>{t('datasetDocuments.list.learnMore')}</span>
-            <RiExternalLinkLine className='h-3 w-3' />
-          </a>
+    <>
+      <div className='flex h-full flex-col overflow-y-auto'>
+        <div className='flex flex-col justify-center gap-1 px-6 pt-4'>
+          <h1 className='text-base font-semibold text-text-primary'>{t('datasetDocuments.list.title')}</h1>
+          <div className='flex items-center space-x-0.5 text-sm font-normal text-text-tertiary'>
+            <span>{t('datasetDocuments.list.desc')}</span>
+            <a
+              className='flex items-center text-text-accent'
+              target='_blank'
+              href='https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'>
+              <span>{t('datasetDocuments.list.learnMore')}</span>
+              <RiExternalLinkLine className='h-3 w-3' />
+            </a>
+          </div>
         </div>
-      </div>
-      <div className='flex flex-1 flex-col px-6 py-4'>
-        <div className='flex flex-wrap items-center justify-between'>
-          <Input
-            showLeftIcon
-            showClearIcon
-            wrapperClassName='!w-[200px]'
-            value={inputValue}
-            onChange={e => handleInputChange(e.target.value)}
-            onClear={() => handleInputChange('')}
-          />
-          <div className="ml-2 mr-auto h-[32px]">
-            <SimpleSelect
-              className="h-[32px] w-[200px]"
-              defaultValue={status}
-              onSelect={(i) => {
-                setStatus(i.value)
-              }}
-              items={optionsStatus}
-              allowSearch={false}
-              placeholder="请选择审核状态"
+        <div className='flex flex-1 flex-col px-6 py-4'>
+          <div className='flex flex-wrap items-center justify-between'>
+            <Input
+              showLeftIcon
+              showClearIcon
+              wrapperClassName='!w-[200px]'
+              value={inputValue}
+              onChange={e => handleInputChange(e.target.value)}
+              onClear={() => handleInputChange('')}
             />
-          </div>
-          <div className='flex !h-8 items-center justify-center gap-2'>
-            <Button variant='primary' onClick={routeToDocCreate} className='shrink-0'>
-              <BookOpenIcon className={cn('mr-2 h-4 w-4 stroke-current')} />
-              模板管理
-            </Button>
-            <Button variant='primary' onClick={routeToDocCreate} className='shrink-0'>
-              <ArrowDownTrayIcon className={cn('mr-2 h-4 w-4 stroke-current')} />
-              模板下载
-            </Button>
-
-            {!isFreePlan && <AutoDisabledDocument datasetId={datasetId} />}
-            <IndexFailed datasetId={datasetId} />
-            {!embeddingAvailable && <StatusWithAction type='warning' description={t('dataset.embeddingModelNotAvailable')} />}
-            {embeddingAvailable && (
-              <Button variant='secondary' className='shrink-0' onClick={showEditMetadataModal}>
-                <RiDraftLine className='mr-1 size-4' />
-                {t('dataset.metadata.metadata')}
-              </Button>
-            )}
-            {isShowEditMetadataModal && (
-              <DatasetMetadataDrawer
-                userMetadata={datasetMetaData || []}
-                onClose={hideEditMetadataModal}
-                onAdd={handleAddMetaData}
-                onRename={handleRename}
-                onRemove={handleDeleteMetaData}
-                builtInMetadata={builtInMetaData || []}
-                isBuiltInEnabled={!!builtInEnabled}
-                onIsBuiltInEnabledChange={setBuiltInEnabled}
+            <div className="ml-2 mr-auto h-[32px]">
+              <SimpleSelect
+                className="h-[32px] w-[200px]"
+                defaultValue={status}
+                onSelect={(i) => {
+                  setStatus(i.value)
+                }}
+                items={optionsStatus}
+                allowSearch={false}
+                placeholder="请选择审核状态"
               />
-            )}
-            {embeddingAvailable && (
-              <Button variant='primary' onClick={routeToDocCreate} className='shrink-0'>
-                <PlusIcon className={cn('mr-2 h-4 w-4 stroke-current')} />
-                {isDataSourceNotion && t('datasetDocuments.list.addPages')}
-                {isDataSourceWeb && t('datasetDocuments.list.addUrl')}
-                {(!dataset?.data_source_type || isDataSourceFile) && t('datasetDocuments.list.addFile')}
+            </div>
+            <div className='flex !h-8 items-center justify-center gap-2'>
+              <Button variant='primary' onClick={() => {
+                setTransfer({ mode: 'manage' })
+                setMouldModalVisible(true)
+              }} className='shrink-0'>
+                <BookOpenIcon className={cn('mr-2 h-4 w-4 stroke-current')} />
+                模板管理
               </Button>
-            )}
+              <Button variant='primary' onClick={() => {
+                setTransfer({ mode: 'download' })
+                setMouldModalVisible(true)
+              }} className='shrink-0'>
+                <ArrowDownTrayIcon className={cn('mr-2 h-4 w-4 stroke-current')} />
+                模板下载
+              </Button>
+
+              {!isFreePlan && <AutoDisabledDocument datasetId={datasetId} />}
+              <IndexFailed datasetId={datasetId} />
+              {!embeddingAvailable && <StatusWithAction type='warning' description={t('dataset.embeddingModelNotAvailable')} />}
+              {embeddingAvailable && (
+                <Button variant='secondary' className='shrink-0' onClick={showEditMetadataModal}>
+                  <RiDraftLine className='mr-1 size-4' />
+                  {t('dataset.metadata.metadata')}
+                </Button>
+              )}
+              {isShowEditMetadataModal && (
+                <DatasetMetadataDrawer
+                  userMetadata={datasetMetaData || []}
+                  onClose={hideEditMetadataModal}
+                  onAdd={handleAddMetaData}
+                  onRename={handleRename}
+                  onRemove={handleDeleteMetaData}
+                  builtInMetadata={builtInMetaData || []}
+                  isBuiltInEnabled={!!builtInEnabled}
+                  onIsBuiltInEnabledChange={setBuiltInEnabled}
+                />
+              )}
+              {embeddingAvailable && (
+                <Button variant='primary' onClick={routeToDocCreate} className='shrink-0'>
+                  <PlusIcon className={cn('mr-2 h-4 w-4 stroke-current')} />
+                  {isDataSourceNotion && t('datasetDocuments.list.addPages')}
+                  {isDataSourceWeb && t('datasetDocuments.list.addUrl')}
+                  {(!dataset?.data_source_type || isDataSourceFile) && t('datasetDocuments.list.addFile')}
+                </Button>
+              )}
+            </div>
           </div>
+          {isListLoading
+            ? <Loading type='app' />
+            : total > 0
+              ? <List
+                embeddingAvailable={embeddingAvailable}
+                documents={documentsList || []}
+                datasetId={datasetId}
+                onUpdate={handleUpdate}
+                selectedIds={selectedIds}
+                onSelectedIdChange={setSelectedIds}
+                pagination={{
+                  total,
+                  limit,
+                  onLimitChange: setLimit,
+                  current: currPage,
+                  onChange: setCurrPage,
+                }}
+                onManageMetadata={showEditMetadataModal}
+              />
+              : <EmptyElement canAdd={embeddingAvailable} onClick={routeToDocCreate} type={isDataSourceNotion ? 'sync' : 'upload'} />
+          }
+          <NotionPageSelectorModal
+            isShow={notionPageSelectorModalVisible}
+            onClose={() => setNotionPageSelectorModalVisible(false)}
+            onSave={handleSaveNotionPageSelected}
+            datasetId={dataset?.id || ''}
+          />
         </div>
-        {isListLoading
-          ? <Loading type='app' />
-          : total > 0
-            ? <List
-              embeddingAvailable={embeddingAvailable}
-              documents={documentsList || []}
-              datasetId={datasetId}
-              onUpdate={handleUpdate}
-              selectedIds={selectedIds}
-              onSelectedIdChange={setSelectedIds}
-              pagination={{
-                total,
-                limit,
-                onLimitChange: setLimit,
-                current: currPage,
-                onChange: setCurrPage,
-              }}
-              onManageMetadata={showEditMetadataModal}
-            />
-            : <EmptyElement canAdd={embeddingAvailable} onClick={routeToDocCreate} type={isDataSourceNotion ? 'sync' : 'upload'} />
-        }
-        <NotionPageSelectorModal
-          isShow={notionPageSelectorModalVisible}
-          onClose={() => setNotionPageSelectorModalVisible(false)}
-          onSave={handleSaveNotionPageSelected}
-          datasetId={dataset?.id || ''}
-        />
       </div>
-    </div>
+      {
+        mouldModalVisible && (
+          <DetailModal
+            transfer={transfer}
+            datasetId={datasetId}
+            onCancel={() => setMouldModalVisible(false)}
+          />
+        )
+      }
+    </>
   )
 }
 

+ 4 - 0
web/app/components/datasets/documents/mould/index.module.css

@@ -0,0 +1,4 @@
+.modal {
+  padding: 24px 32px !important;
+  width: 400px !important;
+}

+ 198 - 0
web/app/components/datasets/documents/mould/index.tsx

@@ -0,0 +1,198 @@
+'use client'
+import { useCallback, useState } from 'react'
+import { RiCloseLine, RiUploadCloud2Line } from '@remixicon/react'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import Modal from '@/app/components/base/modal'
+import Button from '@/app/components/base/button'
+import 'react-multi-email/dist/style.css'
+import FileInput from '@/app/components/base/file-uploader/file-input'
+import { FileContextProvider, useStore } from '@/app/components/base/file-uploader/store'
+import { useFile } from '@/app/components/base/file-uploader/hooks'
+import useSWR from 'swr'
+import { addMouldFile, delMouldFile, fetchMoulds } from '@/service/common'
+import { useDebounceFn } from 'ahooks'
+import Confirm from '@/app/components/base/confirm'
+import { downloadFile } from '@/app/components/base/file-uploader/utils'
+
+const DetailModel = ({
+  datasetId,
+  transfer,
+  onCancel,
+}: any) => {
+  const [showConfirmDelete, setShowConfirmDelete] = useState(false)
+  const [delId, setDelId] = useState('')
+  const [delFileName, setDelFileName] = useState('')
+
+  const fileConfig: any = {
+    image: {
+      detail: 'high',
+      enabled: true,
+      number_limits: 3,
+      transfer_methods: [
+        'local_file',
+        'remote_url',
+      ],
+    },
+    enabled: true,
+    allowed_file_types: [
+      'custom',
+    ],
+    allowed_file_extensions: [
+      '.doc',
+      '.docx',
+      '.xls',
+      '.xlsx',
+      '.ppt',
+      '.pptx',
+      '.pdf',
+      '.csv',
+    ],
+    allowed_file_upload_methods: [
+      'local_file',
+    ],
+    number_limits: 6,
+    fileUploadConfig: {
+      file_size_limit: 15,
+      batch_count_limit: 5,
+      image_file_size_limit: 10,
+      video_file_size_limit: 100,
+      audio_file_size_limit: 50,
+      workflow_file_upload_limit: 10,
+    },
+  }
+  const files = useStore(s => s.files)
+  const {
+    handleRemoveFile,
+    handleReUploadFile,
+  } = useFile(fileConfig)
+  const { data, mutate }: any = useSWR(
+    {
+      url: '/workspaces/123123',
+      params: {},
+    },
+    fetchMoulds,
+  )
+  const fileList = data?.data || []
+  const handleAdd = useCallback(async () => {
+    try {
+      const { result }: any = await addMouldFile({
+        url: '/workspaces/123123',
+        body: { id: files[0].id, fileName: files[0].name },
+      })
+      handleRemoveFile(files[0].id)
+      if (result === 'success')
+        mutate()
+    }
+    catch (e) { }
+  }, [files, handleRemoveFile])
+  const { run: getFiles } = useDebounceFn(() => {
+    if (files.length > 0) {
+      console.log(5555)
+      console.log(files)
+      handleAdd()
+    }
+  }, { wait: 500 })
+  getFiles()
+  const handleDel = useCallback(async () => {
+    try {
+      const { result }: any = await delMouldFile({
+        url: '/workspaces/123123',
+        body: { id: delId },
+      })
+      if (result === 'success')
+        mutate()
+    }
+    catch (e) { }
+  }, [delId])
+  return (
+    <>
+      <div className={cn(s.wrap)}>
+        <Modal overflowVisible isShow onClose={() => { }} className={cn(s.modal)}>
+          <div className='mb-2 flex justify-between'>
+            <div className='text-xl font-semibold text-text-primary'>模板{transfer.mode === 'manage' ? '管理' : '下载'}</div>
+            <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
+          </div>
+          <div>
+            {
+              transfer.mode === 'manage' && (
+                <div className="flex w-full">
+                  <Button
+                    className='relative w-[80px]'
+                    variant='secondary-accent'
+                  >
+                    <RiUploadCloud2Line className='mr-1 h-4 w-4' />
+                    新增
+                    <FileInput fileConfig={fileConfig} />
+                  </Button>
+                  <div className="flex grow items-center justify-center text-sm text-[#666666]">共{fileList.length}个模板</div>
+                </div>
+              )
+            }
+          </div>
+          <div className="mt-2.5 flex max-h-[400px] w-full flex-col gap-2.5 overflow-y-auto">
+            {
+              fileList.map((file: any) => (
+                <div key={file.id} className='flex w-full items-center rounded border border-[#E0E3E8] bg-[#F3F4F8] px-4 py-3'>
+                  <div>
+                    {
+                      (file.fileName.includes('.xls') || file.fileName.includes('.xlsx')) && (
+                        <img src="/imgs/excel.png"/>
+                      )
+                    }
+                    {
+                      (file.fileName.includes('.doc') || file.fileName.includes('.docx')) && (
+                        <img src="/imgs/word.png"/>
+                      )
+                    }
+                    {
+                      (file.fileName.includes('.ppt') || file.fileName.includes('.pptx')) && (
+                        <img src="/imgs/ppt.png"/>
+                      )
+                    }
+                  </div>
+                  <div className="ml-2">{file.fileName}</div>
+                  <div className="ml-auto" onClick={(e) => {
+                    e.stopPropagation()
+                    downloadFile(`/files/${file.id}/file-preview?timestamp=${new Date().getTime()}` || '', file.fileName)
+                  }}>
+                    <img src="/imgs/download.png"/>
+                  </div>
+                  {
+                    transfer.mode === 'manage' && (
+                      <div className="ml-5" onClick={() => {
+                        setDelId(file.id)
+                        setShowConfirmDelete(true)
+                      }}>
+                        <img src="/imgs/del.png"/>
+                      </div>
+                    )
+                  }
+                </div>
+              ))
+            }
+          </div>
+        </Modal>
+      </div>
+      {showConfirmDelete && (
+        <Confirm
+          title="删除确认"
+          content={`请确认是否删除${delFileName}?`}
+          isShow={showConfirmDelete}
+          onConfirm={handleDel}
+          onCancel={() => setShowConfirmDelete(false)}
+        />
+      )}
+    </>
+  )
+}
+
+const DetailModelWrapper = (props: any) => {
+  return (
+    <FileContextProvider>
+      <DetailModel {...props} />
+    </FileContextProvider>
+  )
+}
+
+export default DetailModelWrapper

+ 1 - 1
web/app/components/header/account-setting/knowledges-page/detail-modal/index.tsx

@@ -47,7 +47,7 @@ const InviteModal = ({
       }
     }
     catch (e) { }
-  }, [name, onCancel, onSend, transfer])
+  }, [serviceType, serviceName, url, method, onCancel, onSend, transfer])
 
   return (
     <div className={cn(s.wrap)}>

二进制
web/public/imgs/del.png


二进制
web/public/imgs/download.png


二进制
web/public/imgs/excel.png


二进制
web/public/imgs/ppt.png


二进制
web/public/imgs/word.png


+ 44 - 0
web/service/common.ts

@@ -144,6 +144,26 @@ export const fetchKnowledges = ({ url, params }) => {
   })
 }
 
+export const fetchMoulds = ({ url, params }) => {
+  // return get<{ accounts: Member[] | null }>(url, { params })
+  console.log('查询知识库模板')
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      const arr: any = []
+      for (let i = 1; i < 2; i++) {
+        // /files/7be8a2ca-a633-473c-8bcf-b73ee9fd7738/file-preview?timestamp=1744947616&nonce=a4107f3e163bbb0da255914e2ccabaf3&sign=H82Wk2VbWjXXEyzRUhGyNQlR-PG8NEGy5ijCEF0IKgo=
+        arr.push({
+          id: '7be8a2ca-a633-473c-8bcf-b73ee9fd7738',
+          fileName: `文件${i}.doc`,
+        })
+      }
+      resolve({
+        data: arr,
+      })
+    }, 1000)
+  })
+}
+
 export const fetchProviders: Fetcher<Provider[] | null, { url: string; params: Record<string, any> }> = ({ url, params }) => {
   return get<Provider[] | null>(url, { params })
 }
@@ -219,6 +239,30 @@ export const editKnowledge = ({ url, body }) => {
   })
 }
 
+export const addMouldFile = ({ url, body }) => {
+  // return post<InvitationResponse>(url, { body })
+  console.log('新增模板文件', body)
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        result: 'success',
+      })
+    }, 1000)
+  })
+}
+
+export const delMouldFile = ({ url, body }) => {
+  // return post<InvitationResponse>(url, { body })
+  console.log('删除模板文件', body)
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        result: 'success',
+      })
+    }, 1000)
+  })
+}
+
 export const fetchFilePreview: Fetcher<{ content: string }, { fileID: string }> = ({ fileID }) => {
   return get<{ content: string }>(`/files/${fileID}/preview`)
 }