mirror of
https://github.com/langgenius/dify.git
synced 2026-07-22 19:48:36 +08:00
252 lines
7.5 KiB
TypeScript
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
|
|
}
|