소스 검색

Feat: use file size limit from api (#9711)

KVOJJJin 6 달 전
부모
커밋
e32116b9a3

+ 4 - 0
web/app/components/base/file-uploader/constants.ts

@@ -1,3 +1,7 @@
+// fallback for file size limit of dify_config
+export const IMG_SIZE_LIMIT = 10 * 1024 * 1024
 export const FILE_SIZE_LIMIT = 15 * 1024 * 1024
+export const AUDIO_SIZE_LIMIT = 50 * 1024 * 1024
+export const VIDEO_SIZE_LIMIT = 100 * 1024 * 1024
 
 export const FILE_URL_REGEX = /^(https?|ftp):\/\//

+ 107 - 9
web/app/components/base/file-uploader/hooks.ts

@@ -3,6 +3,7 @@ import {
   useCallback,
   useState,
 } from 'react'
+import useSWR from 'swr'
 import { useParams } from 'next/navigation'
 import produce from 'immer'
 import { v4 as uuid4 } from 'uuid'
@@ -14,19 +15,113 @@ import {
   getSupportFileType,
   isAllowedFileExtension,
 } from './utils'
-import { FILE_SIZE_LIMIT } from './constants'
+import {
+  AUDIO_SIZE_LIMIT,
+  FILE_SIZE_LIMIT,
+  IMG_SIZE_LIMIT,
+  VIDEO_SIZE_LIMIT,
+} from '@/app/components/base/file-uploader/constants'
 import { useToastContext } from '@/app/components/base/toast'
 import { TransferMethod } from '@/types/app'
 import { SupportUploadFileTypes } from '@/app/components/workflow/types'
 import type { FileUpload } from '@/app/components/base/features/types'
 import { formatFileSize } from '@/utils/format'
-import { fetchRemoteFileInfo } from '@/service/common'
+import { fetchFileUploadConfig, fetchRemoteFileInfo } from '@/service/common'
+
+export const useFileSizeLimit = () => {
+  const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
+  const imgSizeLimit = Number(fileUploadConfigResponse?.image_file_size_limit) * 1024 * 1024 || IMG_SIZE_LIMIT
+  const docSizeLimit = Number(fileUploadConfigResponse?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT
+  const audioSizeLimit = Number(fileUploadConfigResponse?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT
+  const videoSizeLimit = Number(fileUploadConfigResponse?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT
+
+  return {
+    imgSizeLimit,
+    docSizeLimit,
+    audioSizeLimit,
+    videoSizeLimit,
+  }
+}
 
 export const useFile = (fileConfig: FileUpload) => {
   const { t } = useTranslation()
   const { notify } = useToastContext()
   const fileStore = useFileStore()
   const params = useParams()
+  const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit()
+
+  const checkSizeLimit = (fileType: string, fileSize: number) => {
+    switch (fileType) {
+      case SupportUploadFileTypes.image: {
+        if (fileSize > imgSizeLimit) {
+          notify({
+            type: 'error',
+            message: t('common.fileUploader.uploadFromComputerLimit', {
+              type: SupportUploadFileTypes.image,
+              size: formatFileSize(imgSizeLimit),
+            }),
+          })
+          return false
+        }
+        return true
+      }
+      case SupportUploadFileTypes.document: {
+        if (fileSize > docSizeLimit) {
+          notify({
+            type: 'error',
+            message: t('common.fileUploader.uploadFromComputerLimit', {
+              type: SupportUploadFileTypes.document,
+              size: formatFileSize(docSizeLimit),
+            }),
+          })
+          return false
+        }
+        return true
+      }
+      case SupportUploadFileTypes.audio: {
+        if (fileSize > audioSizeLimit) {
+          notify({
+            type: 'error',
+            message: t('common.fileUploader.uploadFromComputerLimit', {
+              type: SupportUploadFileTypes.audio,
+              size: formatFileSize(audioSizeLimit),
+            }),
+          })
+          return false
+        }
+        return true
+      }
+      case SupportUploadFileTypes.video: {
+        if (fileSize > videoSizeLimit) {
+          notify({
+            type: 'error',
+            message: t('common.fileUploader.uploadFromComputerLimit', {
+              type: SupportUploadFileTypes.video,
+              size: formatFileSize(videoSizeLimit),
+            }),
+          })
+          return false
+        }
+        return true
+      }
+      case SupportUploadFileTypes.custom: {
+        if (fileSize > docSizeLimit) {
+          notify({
+            type: 'error',
+            message: t('common.fileUploader.uploadFromComputerLimit', {
+              type: SupportUploadFileTypes.document,
+              size: formatFileSize(docSizeLimit),
+            }),
+          })
+          return false
+        }
+        return true
+      }
+      default: {
+        return true
+      }
+    }
+  }
 
   const handleAddFile = useCallback((newFile: FileEntity) => {
     const {
@@ -117,12 +212,15 @@ export const useFile = (fileConfig: FileUpload) => {
         progress: 100,
         supportFileType: getSupportFileType(url, res.file_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
       }
-      handleUpdateFile(newFile)
+      if (!checkSizeLimit(newFile.supportFileType, newFile.size))
+        handleRemoveFile(uploadingFile.id)
+      else
+        handleUpdateFile(newFile)
     }).catch(() => {
       notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
       handleRemoveFile(uploadingFile.id)
     })
-  }, [handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types])
+  }, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types])
 
   const handleLoadFileFromLinkSuccess = useCallback(() => { }, [])
 
@@ -140,13 +238,13 @@ export const useFile = (fileConfig: FileUpload) => {
       notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
       return
     }
-    if (file.size > FILE_SIZE_LIMIT) {
-      notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerLimit', { size: formatFileSize(FILE_SIZE_LIMIT) }) })
+    const allowedFileTypes = fileConfig.allowed_file_types
+    const fileType = getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom))
+    if (!checkSizeLimit(fileType, file.size))
       return
-    }
+
     const reader = new FileReader()
     const isImage = file.type.startsWith('image')
-    const allowedFileTypes = fileConfig.allowed_file_types
 
     reader.addEventListener(
       'load',
@@ -187,7 +285,7 @@ export const useFile = (fileConfig: FileUpload) => {
       false,
     )
     reader.readAsDataURL(file)
-  }, [notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions])
+  }, [checkSizeLimit, notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions])
 
   const handleClipboardPasteFile = useCallback((e: ClipboardEvent<HTMLTextAreaElement>) => {
     const file = e.clipboardData?.files[0]

+ 8 - 2
web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx

@@ -10,7 +10,7 @@ import FileTypeItem from './file-type-item'
 import InputNumberWithSlider from './input-number-with-slider'
 import Field from '@/app/components/app/configuration/config-var/config-modal/field'
 import { TransferMethod } from '@/types/app'
-import { FILE_SIZE_LIMIT } from '@/app/components/base/file-uploader/constants'
+import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
 import { formatFileSize } from '@/utils/format'
 
 type Props = {
@@ -36,6 +36,7 @@ const FileUploadSetting: FC<Props> = ({
     allowed_file_types,
     allowed_file_extensions,
   } = payload
+  const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit()
 
   const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
     const newPayload = produce(payload, (draft) => {
@@ -142,7 +143,12 @@ const FileUploadSetting: FC<Props> = ({
           title={t('appDebug.variableConfig.maxNumberOfUploads')!}
         >
           <div>
-            <div className='mb-1.5 text-text-tertiary body-xs-regular'>{t('appDebug.variableConfig.maxNumberTip', { size: formatFileSize(FILE_SIZE_LIMIT) })}</div>
+            <div className='mb-1.5 text-text-tertiary body-xs-regular'>{t('appDebug.variableConfig.maxNumberTip', {
+              imgLimit: formatFileSize(imgSizeLimit),
+              docLimit: formatFileSize(docSizeLimit),
+              audioLimit: formatFileSize(audioSizeLimit),
+              videoLimit: formatFileSize(videoSizeLimit),
+            })}</div>
             <InputNumberWithSlider
               value={max_length}
               min={1}

+ 1 - 1
web/i18n/en-US/app-debug.ts

@@ -386,7 +386,7 @@ const translation = {
     'localUpload': 'Local Upload',
     'both': 'Both',
     'maxNumberOfUploads': 'Max number of uploads',
-    'maxNumberTip': 'Max {{size}} each',
+    'maxNumberTip': 'Document < {{docLimit}}, image < {{imgLimit}}, audio < {{audioLimit}}, video < {{videoLimit}}',
     'errorMsg': {
       labelNameRequired: 'Label name is required',
       varNameCanBeRepeat: 'Variable name can not be repeated',

+ 1 - 1
web/i18n/en-US/common.ts

@@ -572,7 +572,7 @@ const translation = {
     pasteFileLinkInputPlaceholder: 'Enter URL...',
     uploadFromComputerReadError: 'File reading failed, please try again.',
     uploadFromComputerUploadError: 'File upload failed, please upload again.',
-    uploadFromComputerLimit: 'Upload File cannot exceed {{size}}',
+    uploadFromComputerLimit: 'Upload {{type}} cannot exceed {{size}}',
     pasteFileLinkInvalid: 'Invalid file link',
     fileExtensionNotSupport: 'File extension not supported',
   },

+ 1 - 1
web/i18n/zh-Hans/app-debug.ts

@@ -379,7 +379,7 @@ const translation = {
     'localUpload': '本地上传',
     'both': '两者',
     'maxNumberOfUploads': '最大上传数',
-    'maxNumberTip': '最大上传文件大小为 {{size}}',
+    'maxNumberTip': '文档 < {{docLimit}}, 图片 < {{imgLimit}}, 音频 < {{audioLimit}}, 视频 < {{videoLimit}}',
     'content': '内容',
     'errorMsg': {
       labelNameRequired: '显示名称必填',

+ 1 - 1
web/i18n/zh-Hans/common.ts

@@ -572,7 +572,7 @@ const translation = {
     pasteFileLinkInputPlaceholder: '输入文件链接',
     uploadFromComputerReadError: '文件读取失败,请重新选择。',
     uploadFromComputerUploadError: '文件上传失败,请重新上传。',
-    uploadFromComputerLimit: '上传文件不能超过 {{size}}',
+    uploadFromComputerLimit: '上传 {{type}} 不能超过 {{size}}',
     pasteFileLinkInvalid: '文件链接无效',
     fileExtensionNotSupport: '文件类型不支持',
   },

+ 4 - 2
web/models/common.ts

@@ -211,9 +211,11 @@ export type PluginProvider = {
 }
 
 export type FileUploadConfigResponse = {
-  file_size_limit: number
   batch_count_limit: number
-  image_file_size_limit?: number | string
+  image_file_size_limit?: number | string // default is 10MB
+  file_size_limit: number // default is 15MB
+  audio_file_size_limit?: number // default is 50MB
+  video_file_size_limit?: number // default is 100MB
 }
 
 export type InvitationResult = {