dify/web/app/components/base/file-uploader/utils.ts
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

252 lines
7.5 KiB
TypeScript

import type { TFunction } from 'i18next'
import type { FileEntity } from './types'
import type { FileResponse } from '@/types/workflow'
import mime from 'mime'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { upload } from '@/service/base'
import { TransferMethod } from '@/types/app'
import { FileAppearanceTypeEnum } from './types'
/**
* Get appropriate error message for file upload errors
* @param error - The error object from upload failure
* @param defaultMessage - Default error message to use if no specific error is matched
* @param t - Translation function
* @returns Localized error message
*/
export const getFileUploadErrorMessage = (
error: any,
defaultMessage: string,
t: TFunction,
): string => {
const errorCode = error?.response?.code
if (errorCode === 'forbidden') return error?.response?.message
if (errorCode === 'file_extension_blocked')
return t(($) => $['fileUploader.fileExtensionBlocked'], { ns: 'common' })
return defaultMessage
}
type FileUploadResponse = {
created_at: number
created_by: string
extension: string
id: string
mime_type: string
name: string
preview_url: string | null
size: number
source_url: string
}
type FileUploadParams = {
file: File
onProgressCallback: (progress: number) => void
onSuccessCallback: (res: FileUploadResponse) => void
onErrorCallback: (error?: any) => void
}
type FileUpload = (v: FileUploadParams, isPublic?: boolean, url?: string) => void
export const fileUpload: FileUpload = (
{ file, onProgressCallback, onSuccessCallback, onErrorCallback },
isPublic,
url,
) => {
const formData = new FormData()
formData.append('file', file)
const onProgress = (e: ProgressEvent) => {
if (e.lengthComputable) {
const percent = Math.floor((e.loaded / e.total) * 100)
onProgressCallback(percent)
}
}
upload(
{
xhr: new XMLHttpRequest(),
data: formData,
onprogress: onProgress,
},
isPublic,
url,
)
.then((res) => {
onSuccessCallback(res as FileUploadResponse)
})
.catch((error) => {
onErrorCallback(error)
})
}
const additionalExtensionMap = new Map<string, string[]>([['text/x-markdown', ['md']]])
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
let extension = ''
let extensions = new Set<string>()
if (fileMimetype) {
const extensionsFromMimeType = mime.getAllExtensions(fileMimetype) || new Set<string>()
const additionalExtensions = additionalExtensionMap.get(fileMimetype) || []
extensions = new Set<string>([...extensionsFromMimeType, ...additionalExtensions])
}
let extensionInFileName = ''
if (fileName) {
const fileNamePair = fileName.split('.')
const fileNamePairLength = fileNamePair.length
if (fileNamePairLength > 1) {
extensionInFileName = fileNamePair[fileNamePairLength - 1]!.toLowerCase()
if (extensions.has(extensionInFileName)) extension = extensionInFileName
}
}
if (!extension) {
if (extensions.size > 0) {
const firstExtension = extensions.values().next().value
extension = firstExtension ? firstExtension.toLowerCase() : ''
} else {
extension = extensionInFileName
}
}
if (isRemote) extension = ''
return extension
}
export const getFileAppearanceType = (fileName: string, fileMimetype: string) => {
const extension = getFileExtension(fileName, fileMimetype)
if (extension === 'gif') return FileAppearanceTypeEnum.gif
if (FILE_EXTS.image!.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.image
if (FILE_EXTS.video!.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.video
if (FILE_EXTS.audio!.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.audio
if (extension === 'html') return FileAppearanceTypeEnum.code
if (extension === 'pdf') return FileAppearanceTypeEnum.pdf
if (extension === 'md' || extension === 'markdown' || extension === 'mdx')
return FileAppearanceTypeEnum.markdown
if (extension === 'xlsx' || extension === 'xls') return FileAppearanceTypeEnum.excel
if (extension === 'docx' || extension === 'doc') return FileAppearanceTypeEnum.word
if (extension === 'pptx' || extension === 'ppt') return FileAppearanceTypeEnum.ppt
if (FILE_EXTS.document!.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.document
return FileAppearanceTypeEnum.custom
}
export const getSupportFileType = (fileName: string, fileMimetype: string, isCustom?: boolean) => {
if (isCustom) return SupportUploadFileTypes.custom
const extension = getFileExtension(fileName, fileMimetype)
for (const key in FILE_EXTS) {
if (FILE_EXTS[key]!.includes(extension.toUpperCase())) return key
}
return ''
}
export const getProcessedFiles = (files: FileEntity[]) => {
return files
.filter((file) => file.progress !== -1)
.map((fileItem) => ({
type: fileItem.supportFileType,
transfer_method: fileItem.transferMethod,
url: fileItem.url || '',
upload_file_id: fileItem.uploadedId || '',
}))
}
export const getProcessedFilesFromResponse = (files: FileResponse[]) => {
return files.map((fileItem) => {
let supportFileType = fileItem.type
if (fileItem.filename && fileItem.mime_type) {
const detectedTypeFromFileName = getSupportFileType(fileItem.filename, '')
const detectedTypeFromMime = getSupportFileType('', fileItem.mime_type)
if (
detectedTypeFromFileName &&
detectedTypeFromMime &&
detectedTypeFromFileName === detectedTypeFromMime &&
detectedTypeFromFileName !== fileItem.type
) {
supportFileType = detectedTypeFromFileName
}
}
return {
id: fileItem.related_id,
name: fileItem.filename,
size: fileItem.size || 0,
type: fileItem.mime_type,
progress: 100,
transferMethod: fileItem.transfer_method,
supportFileType,
uploadedId: fileItem.upload_file_id || fileItem.related_id,
url: fileItem.url || fileItem.remote_url,
}
})
}
export const getSupportFileExtensionList = (
allowFileTypes: string[],
allowFileExtensions: string[],
) => {
if (allowFileTypes.includes(SupportUploadFileTypes.custom))
return allowFileExtensions.map((item) => item.slice(1).toUpperCase())
return allowFileTypes.map((type) => FILE_EXTS[type]).flat()
}
export const isAllowedFileExtension = (
fileName: string,
fileMimetype: string,
allowFileTypes: string[],
allowFileExtensions: string[],
) => {
return getSupportFileExtensionList(allowFileTypes, allowFileExtensions).includes(
getFileExtension(fileName, fileMimetype).toUpperCase(),
)
}
export const getFilesInLogs = (rawData: any) => {
const result = Object.keys(rawData || {})
.map((key) => {
if (
typeof rawData[key] === 'object' &&
rawData[key]?.dify_model_identity === '__dify__file__'
) {
return {
varName: key,
list: getProcessedFilesFromResponse([rawData[key]]),
}
}
if (
Array.isArray(rawData[key]) &&
rawData[key].some((item) => item?.dify_model_identity === '__dify__file__')
) {
return {
varName: key,
list: getProcessedFilesFromResponse(rawData[key]),
}
}
return undefined
})
.filter(Boolean)
return result
}
export const fileIsUploaded = (file: FileEntity) => {
if (file.uploadedId) return true
if (file.transferMethod === TransferMethod.remote_url && file.progress === 100) return true
}