fix: uploader

This commit is contained in:
StyleZhang 2024-09-20 12:07:48 +08:00
parent d580fc1e9d
commit edf462c640
14 changed files with 105 additions and 106 deletions

View File

@ -63,7 +63,7 @@ const ChatInputArea = ({
const handleSend = () => {
if (onSend) {
if (files.find(item => item.type === TransferMethod.local_file && !item.fileStorageId)) {
if (files.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId)) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') })
return
}

View File

@ -61,7 +61,7 @@ const FileFromLinkOrLocal = ({
size='small'
variant='primary'
disabled={!url || disabled}
onClick={() => handleLoadFileFromLink()}
onClick={() => handleLoadFileFromLink(url)}
>
{t('common.operation.ok')}
</Button>

View File

@ -7,37 +7,33 @@ import FileTypeIcon from '../file-type-icon'
import {
getFileAppearanceType,
getFileExtension,
isImage,
} from '../utils'
import FileImageRender from '../file-image-render'
import type { FileEntity } from '../types'
import ActionButton from '@/app/components/base/action-button'
import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
import { formatFileSize } from '@/utils/format'
import cn from '@/utils/classnames'
import { ReplayLine } from '@/app/components/base/icons/src/vender/other'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
type FileInAttachmentItemProps = {
fileId: string
file: File
imageUrl?: string
progress?: number
file: FileEntity
showDeleteAction?: boolean
showDownloadAction?: boolean
onRemove?: (fileId: string) => void
onReUpload?: (fileId: string) => void
}
const FileInAttachmentItem = ({
fileId,
file,
imageUrl,
progress = 0,
showDeleteAction,
showDownloadAction = true,
onRemove,
onReUpload,
}: FileInAttachmentItemProps) => {
const isImageFile = isImage(file)
const ext = getFileExtension(file)
const { id, name, progress, supportFileType, base64Url, url } = file
const ext = getFileExtension(name)
const isImageFile = supportFileType === SupportUploadFileTypes.image
return (
<div className={cn(
@ -49,14 +45,14 @@ const FileInAttachmentItem = ({
isImageFile && (
<FileImageRender
className='w-8 h-8'
imageUrl={imageUrl || ''}
imageUrl={base64Url || url || ''}
/>
)
}
{
!isImageFile && (
<FileTypeIcon
type={getFileAppearanceType(file)}
type={getFileAppearanceType(file.type)}
size='lg'
/>
)
@ -92,7 +88,7 @@ const FileInAttachmentItem = ({
progress === -1 && (
<ActionButton
className='mr-1'
onClick={() => onReUpload?.(fileId)}
onClick={() => onReUpload?.(id)}
>
<ReplayLine className='w-4 h-4 text-text-tertiary' />
</ActionButton>
@ -100,7 +96,7 @@ const FileInAttachmentItem = ({
}
{
showDeleteAction && (
<ActionButton onClick={() => onRemove?.(fileId)}>
<ActionButton onClick={() => onRemove?.(id)}>
<RiDeleteBinLine className='w-4 h-4' />
</ActionButton>
)

View File

@ -96,15 +96,12 @@ const FileUploaderInAttachment = ({
{
files.map(file => (
<FileItem
key={file.fileId}
fileId={file.fileId}
file={file.file}
progress={file.progress}
imageUrl={file.base64Url}
key={file.id}
file={file}
showDeleteAction
showDownloadAction={false}
onRemove={() => handleRemoveFile(file.fileId)}
onReUpload={() => handleReUploadFile(file.fileId)}
onRemove={() => handleRemoveFile(file.id)}
onReUpload={() => handleReUploadFile(file.id)}
/>
))
}

View File

@ -1,32 +1,31 @@
import { RiCloseLine } from '@remixicon/react'
import FileImageRender from '../file-image-render'
import type { FileEntity } from '../types'
import Button from '@/app/components/base/button'
import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
import { ReplayLine } from '@/app/components/base/icons/src/vender/other'
type FileImageItemProps = {
fileId: string
imageUrl?: string
progress?: number
file: FileEntity
showDeleteAction?: boolean
onRemove?: (fileId: string) => void
onReUpload?: (fileId: string) => void
}
const FileImageItem = ({
fileId,
imageUrl,
progress = 0,
file,
showDeleteAction,
onRemove,
onReUpload,
}: FileImageItemProps) => {
const { id, progress, base64Url, url } = file
return (
<div className='group relative'>
{
showDeleteAction && (
<Button
className='hidden group-hover:flex absolute -right-1.5 -top-1.5 p-0 w-5 h-5 rounded-full z-10'
onClick={() => onRemove?.(fileId)}
onClick={() => onRemove?.(id)}
>
<RiCloseLine className='w-4 h-4 text-components-button-secondary-text' />
</Button>
@ -34,7 +33,7 @@ const FileImageItem = ({
}
<FileImageRender
className='w-[68px] h-[68px] shadow-md'
imageUrl={imageUrl || ''}
imageUrl={base64Url || url || ''}
/>
{
progress > 0 && progress < 100 && (
@ -54,7 +53,7 @@ const FileImageItem = ({
<div className='absolute inset-0 flex items-center justify-center border-[2px] border-state-destructive-border bg-background-overlay-destructive z-10'>
<ReplayLine
className='w-5 h-5'
onClick={() => onReUpload?.(fileId)}
onClick={() => onReUpload?.(id)}
/>
</div>
)

View File

@ -7,6 +7,7 @@ import {
getFileExtension,
} from '../utils'
import FileTypeIcon from '../file-type-icon'
import type { FileEntity } from '../types'
import cn from '@/utils/classnames'
import { formatFileSize } from '@/utils/format'
import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
@ -15,24 +16,21 @@ import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button'
type FileItemProps = {
fileId: string
file: File
progress?: number
file: FileEntity
showDeleteAction?: boolean
showDownloadAction?: boolean
onRemove?: (fileId: string) => void
onReUpload?: (fileId: string) => void
}
const FileItem = ({
fileId,
file,
progress = 0,
showDeleteAction,
showDownloadAction = true,
onRemove,
onReUpload,
}: FileItemProps) => {
const ext = getFileExtension(file)
const { id, name, progress } = file
const ext = getFileExtension(name)
const uploadError = progress === -1
return (
@ -48,20 +46,20 @@ const FileItem = ({
showDeleteAction && (
<Button
className='hidden group-hover:flex absolute -right-1.5 -top-1.5 p-0 w-5 h-5 rounded-full z-10'
onClick={() => onRemove?.(fileId)}
onClick={() => onRemove?.(id)}
>
<RiCloseLine className='w-4 h-4 text-components-button-secondary-text' />
</Button>
)
}
<div className='mb-1 h-8 line-clamp-2 system-xs-medium text-text-tertiary'>
{file.name}
{name}
</div>
<div className='flex items-center justify-between'>
<div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'>
<FileTypeIcon
size='sm'
type={getFileAppearanceType(file)}
type={getFileAppearanceType(name)}
className='mr-1'
/>
{
@ -95,7 +93,7 @@ const FileItem = ({
uploadError && (
<ReplayLine
className='w-4 h-4 text-text-tertiary'
onClick={() => onReUpload?.(fileId)}
onClick={() => onReUpload?.(id)}
/>
)
}

View File

@ -1,10 +1,10 @@
import { isImage } from '../utils'
import { useFile } from '../hooks'
import { useStore } from '../store'
import type { FileEntity } from '../types'
import FileImageItem from './file-image-item'
import FileItem from './file-item'
import type { FileUpload } from '@/app/components/base/features/types'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
type FileListProps = {
files: FileEntity[]
@ -24,13 +24,11 @@ export const FileList = ({
<div className='flex flex-wrap gap-2'>
{
files.map((file) => {
if (isImage(file.file)) {
if (file.supportFileType === SupportUploadFileTypes.image) {
return (
<FileImageItem
key={file.fileId}
fileId={file.fileId}
imageUrl={file.base64Url}
progress={file.progress}
key={file.id}
file={file}
showDeleteAction={showDeleteAction}
onRemove={onRemove}
onReUpload={onReUpload}
@ -40,10 +38,8 @@ export const FileList = ({
return (
<FileItem
key={file.fileId}
fileId={file.fileId}
file={file.file}
progress={file.progress}
key={file.id}
file={file}
showDeleteAction={showDeleteAction}
showDownloadAction={showDownloadAction}
onRemove={onRemove}

View File

@ -11,7 +11,7 @@ import type { FileEntity } from './types'
import { useFileStore } from './store'
import {
fileUpload,
getFileType,
getSupportFileType,
} from './utils'
import { FILE_SIZE_LIMIT } from './constants'
import { useToastContext } from '@/app/components/base/toast'
@ -45,7 +45,7 @@ export const useFile = (fileConfig: FileUpload) => {
} = fileStore.getState()
const newFiles = produce(files, (draft) => {
const index = draft.findIndex(file => file.fileId === newFile.fileId)
const index = draft.findIndex(file => file.id === newFile.id)
if (index > -1)
draft[index] = newFile
@ -59,7 +59,7 @@ export const useFile = (fileConfig: FileUpload) => {
setFiles,
} = fileStore.getState()
const newFiles = files.filter(file => file.fileId !== fileId)
const newFiles = files.filter(file => file.id !== fileId)
setFiles(newFiles)
}, [fileStore])
@ -68,7 +68,7 @@ export const useFile = (fileConfig: FileUpload) => {
files,
setFiles,
} = fileStore.getState()
const index = files.findIndex(file => file.fileId === fileId)
const index = files.findIndex(file => file.id === fileId)
if (index > -1) {
const uploadingFile = files[index]
@ -77,12 +77,12 @@ export const useFile = (fileConfig: FileUpload) => {
})
setFiles(newFiles)
fileUpload({
file: uploadingFile.file!,
file: uploadingFile.originalFile!,
onProgressCallback: (progress) => {
handleUpdateFile({ ...uploadingFile, progress })
},
onSuccessCallback: (res) => {
handleUpdateFile({ ...uploadingFile, fileId: res.id, progress: 100 })
handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 })
},
onErrorCallback: () => {
notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerUploadError') })
@ -92,7 +92,20 @@ export const useFile = (fileConfig: FileUpload) => {
}
}, [fileStore, notify, t, handleUpdateFile, params])
const handleLoadFileFromLink = useCallback(() => {}, [])
const handleLoadFileFromLink = useCallback((url: string) => {
const uploadingFile = {
id: uuid4(),
name: 'remote file (todo)',
type: '',
size: 0,
progress: 0,
transferMethod: TransferMethod.remote_url,
supportFileType: '',
url,
base64Url: '',
}
handleAddFile(uploadingFile)
}, [handleAddFile])
const handleLoadFileFromLinkSuccess = useCallback(() => { }, [])
@ -113,28 +126,29 @@ export const useFile = (fileConfig: FileUpload) => {
const reader = new FileReader()
const isImage = file.type.startsWith('image')
const allowedFileTypes = fileConfig.allowed_file_types
const isCustomFileType = allowedFileTypes?.includes(SupportUploadFileTypes.custom)
reader.addEventListener(
'load',
() => {
const uploadingFile = {
fileId: uuid4(),
file,
url: '',
id: uuid4(),
name: file.name,
type: file.type,
size: file.size,
progress: 0,
transferMethod: TransferMethod.local_file,
supportFileType: getSupportFileType(file.name, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
originalFile: file,
base64Url: isImage ? reader.result as string : '',
fileType: isCustomFileType ? SupportUploadFileTypes.custom : getFileType(file),
type: TransferMethod.local_file,
}
handleAddFile(uploadingFile)
fileUpload({
file: uploadingFile.file,
file: uploadingFile.originalFile,
onProgressCallback: (progress) => {
handleUpdateFile({ ...uploadingFile, progress })
},
onSuccessCallback: (res) => {
handleUpdateFile({ ...uploadingFile, fileStorageId: res.id, progress: 100 })
handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 })
},
onErrorCallback: () => {
notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerUploadError') })

View File

@ -18,12 +18,15 @@ export enum FileAppearanceTypeEnum {
export type FileAppearanceType = keyof typeof FileAppearanceTypeEnum
export type FileEntity = {
fileId: string
file: File
fileStorageId?: string
id: string
name: string
size: number
type: string
progress: number
url?: string
transferMethod: TransferMethod
supportFileType: string
originalFile?: File
uploadedId?: string
base64Url?: string
type: TransferMethod
fileType: string
url?: string
}

View File

@ -2,6 +2,7 @@ import { FileAppearanceTypeEnum } from './types'
import type { FileEntity } from './types'
import { upload } from '@/service/base'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
type FileUploadParams = {
file: File
@ -38,35 +39,30 @@ export const fileUpload: FileUpload = ({
})
}
export const getFileAppearanceType = (file?: File) => {
if (!file)
export const getFileAppearanceType = (fileType: string) => {
if (!fileType)
return FileAppearanceTypeEnum.custom
const mimeType = file.type
if (mimeType.includes('image'))
if (fileType.includes('image'))
return FileAppearanceTypeEnum.image
if (mimeType.includes('video'))
if (fileType.includes('video'))
return FileAppearanceTypeEnum.video
if (mimeType.includes('audio'))
if (fileType.includes('audio'))
return FileAppearanceTypeEnum.audio
if (mimeType.includes('pdf'))
if (fileType.includes('pdf'))
return FileAppearanceTypeEnum.pdf
return FileAppearanceTypeEnum.custom
}
export const isImage = (file?: File) => {
return file?.type.startsWith('image')
}
export const getFileExtension = (file?: File) => {
if (!file)
export const getFileExtension = (fileName: string) => {
if (!fileName)
return ''
const fileNamePair = file.name.split('.')
const fileNamePair = fileName.split('.')
const fileNamePairLength = fileNamePair.length
if (fileNamePairLength > 1)
@ -75,8 +71,11 @@ export const getFileExtension = (file?: File) => {
return ''
}
export const getFileType = (file?: File) => {
const extension = getFileExtension(file)
export const getSupportFileType = (fileName: string, isCustom?: boolean) => {
if (isCustom)
return SupportUploadFileTypes.custom
const extension = getFileExtension(fileName)
for (const key in FILE_EXTS) {
if ((FILE_EXTS[key]).includes(extension.toUpperCase()))
return key
@ -87,9 +86,9 @@ export const getFileType = (file?: File) => {
export const getProcessedFiles = (files: FileEntity[]) => {
return files.filter(file => file.progress !== -1).map(fileItem => ({
type: fileItem.fileType,
transfer_method: fileItem.type,
type: fileItem.supportFileType,
transfer_method: fileItem.transferMethod,
url: fileItem.url || '',
upload_file_id: fileItem.fileStorageId || '',
upload_file_id: fileItem.uploadedId || '',
}))
}

View File

@ -13,6 +13,7 @@ import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import type { VisionFile, VisionSettings } from '@/types/app'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
export type IRunOnceProps = {
siteInfo: SiteInfo
@ -113,12 +114,9 @@ const RunOnce: FC<IRunOnceProps> = ({
</div>
)
}
<FileUploaderInAttachmentWrapper onChange={files => onVisionFilesChange(files.filter(file => file.progress !== -1).map(fileItem => ({
type: fileItem.fileType,
transfer_method: fileItem.type,
url: fileItem.url || '',
upload_file_id: fileItem.fileId,
})))} />
<FileUploaderInAttachmentWrapper
onChange={files => onVisionFilesChange(getProcessedFiles(files))}
/>
{promptConfig.prompt_variables.length > 0 && (
<div className='mt-4 h-[1px] bg-gray-100'></div>
)}

View File

@ -23,6 +23,7 @@ import { Line3 } from '@/app/components/base/icons/src/public/common'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import cn from '@/utils/classnames'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
type Props = {
payload: InputVar
@ -158,12 +159,7 @@ const FormItem: FC<Props> = ({
}
{(type === InputVarType.singleFile || type === InputVarType.multiFiles) && (
<FileUploaderInAttachmentWrapper
onChange={files => onChange(files.filter(file => file.progress !== -1).map(fileItem => ({
type: fileItem.fileType,
transfer_method: fileItem.type,
url: fileItem.url,
upload_file_id: fileItem.fileId,
})))}
onChange={files => onChange(getProcessedFiles(files))}
fileConfig={{
allowed_file_types: payload.allowed_file_types,
allowed_file_extensions: payload.allowed_file_extensions,

View File

@ -189,7 +189,6 @@ export const useChat = (
const { files, ...restParams } = params
const bodyParams = {
conversation_id: conversationId.current,
files: getProcessedFiles(files || []),
...restParams,
}
@ -208,7 +207,7 @@ export const useChat = (
let hasSetResponseId = false
handleRun(
params,
bodyParams,
{
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
responseItem.content = responseItem.content + message

View File

@ -308,3 +308,7 @@ export const verifyForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boo
export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
post<CommonResponse>(url, { body })
export const fetchRemoteFileInfo: Fetcher<{ file_type: string; file_length: number }, string> = (url) => {
return get<{ file_type: string; file_length: number }>(url)
}