fix: uploader

This commit is contained in:
StyleZhang 2024-09-20 15:44:36 +08:00
parent 9fd2f798ff
commit 4ed46e3fed
8 changed files with 84 additions and 15 deletions

View File

@ -15,6 +15,7 @@ import { useTextAreaHeight } from './hooks'
import Operation from './operation'
import cn from '@/utils/classnames'
import { FileListInChatInput } from '@/app/components/base/file-uploader'
import { useFile } from '@/app/components/base/file-uploader/hooks'
import {
FileContextProvider,
useStore,
@ -60,6 +61,14 @@ const ChatInputArea = ({
const [showVoiceInput, setShowVoiceInput] = useState(false)
const files = useStore(s => s.files)
const setFiles = useStore(s => s.setFiles)
const {
handleDragFileEnter,
handleDragFileLeave,
handleDragFileOver,
handleDropFile,
handleClipboardPasteFile,
isDragActive,
} = useFile(visionConfig!)
const handleSend = () => {
if (onSend) {
@ -105,7 +114,7 @@ const ChatInputArea = ({
const operation = (
<Operation
ref={holdSpaceRef}
visionConfig={visionConfig}
fileConfig={visionConfig}
speechToTextConfig={speechToTextConfig}
onShowVoiceInput={handleShowVoiceInput}
onSend={handleSend}
@ -117,6 +126,7 @@ const ChatInputArea = ({
<div
className={cn(
'relative py-[9px] bg-components-panel-bg-blur border border-components-chat-input-border rounded-xl shadow-md z-10',
isDragActive && 'border border-dashed border-components-option-card-option-selected-border',
)}
>
<div className='relative px-[9px] max-h-[158px] overflow-x-hidden overflow-y-auto'>
@ -134,8 +144,10 @@ const ChatInputArea = ({
</div>
<Textarea
ref={textareaRef}
className='p-1 w-full leading-6 body-lg-regular text-text-tertiary outline-none'
placeholder='Enter message...'
className={cn(
'p-1 w-full leading-6 body-lg-regular text-text-tertiary outline-none',
)}
placeholder={t('common.chat.inputPlaceholder') || ''}
autoSize={{ minRows: 1 }}
onResize={handleTextareaResize}
value={query}
@ -145,6 +157,11 @@ const ChatInputArea = ({
}}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
onPaste={handleClipboardPasteFile}
onDragEnter={handleDragFileEnter}
onDragLeave={handleDragFileLeave}
onDragOver={handleDragFileOver}
onDrop={handleDropFile}
/>
</div>
{

View File

@ -16,13 +16,13 @@ import type { FileUpload } from '@/app/components/base/features/types'
import cn from '@/utils/classnames'
type OperationProps = {
visionConfig?: FileUpload
fileConfig?: FileUpload
speechToTextConfig?: EnableType
onShowVoiceInput?: () => void
onSend: () => void
}
const Operation = forwardRef<HTMLDivElement, OperationProps>(({
visionConfig,
fileConfig,
speechToTextConfig,
onShowVoiceInput,
onSend,
@ -38,7 +38,7 @@ const Operation = forwardRef<HTMLDivElement, OperationProps>(({
ref={ref}
>
<div className='flex items-center space-x-1'>
{visionConfig?.enabled && <FileUploaderInChatInput fileConfig={visionConfig} />}
{fileConfig?.enabled && <FileUploaderInChatInput fileConfig={fileConfig} />}
{
speechToTextConfig?.enabled && (
<ActionButton

View File

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

View File

@ -11,7 +11,9 @@ import type { FileEntity } from './types'
import { useFileStore } from './store'
import {
fileUpload,
getFileNameFromUrl,
getSupportFileType,
isAllowedFileExtension,
} from './utils'
import { FILE_SIZE_LIMIT } from './constants'
import { useToastContext } from '@/app/components/base/toast'
@ -19,6 +21,7 @@ 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'
export const useFile = (fileConfig: FileUpload) => {
const { t } = useTranslation()
@ -85,7 +88,7 @@ export const useFile = (fileConfig: FileUpload) => {
handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 })
},
onErrorCallback: () => {
notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerUploadError') })
notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerUploadError') })
handleUpdateFile({ ...uploadingFile, progress: -1 })
},
}, !!params.token)
@ -93,19 +96,39 @@ export const useFile = (fileConfig: FileUpload) => {
}, [fileStore, notify, t, handleUpdateFile, params])
const handleLoadFileFromLink = useCallback((url: string) => {
const allowedFileTypes = fileConfig.allowed_file_types
const fileName = getFileNameFromUrl(url)
if (!isAllowedFileExtension(fileName, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
return
}
const uploadingFile = {
id: uuid4(),
name: 'remote file (todo)',
name: fileName,
type: '',
size: 0,
progress: 0,
transferMethod: TransferMethod.remote_url,
supportFileType: '',
supportFileType: getSupportFileType(fileName, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
url,
base64Url: '',
}
handleAddFile(uploadingFile)
}, [handleAddFile])
fetchRemoteFileInfo(url).then((res) => {
const newFile = {
...uploadingFile,
type: res.file_type,
size: res.file_length,
progress: 100,
}
handleUpdateFile(newFile)
}).catch(() => {
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
handleRemoveFile(uploadingFile.id)
})
}, [handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions])
const handleLoadFileFromLinkSuccess = useCallback(() => { }, [])
@ -119,6 +142,10 @@ export const useFile = (fileConfig: FileUpload) => {
}, [fileStore])
const handleLocalFileUpload = useCallback((file: File) => {
if (!isAllowedFileExtension(file.name, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
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) }) })
return
@ -166,7 +193,7 @@ export const useFile = (fileConfig: FileUpload) => {
false,
)
reader.readAsDataURL(file)
}, [notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types])
}, [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]

View File

@ -92,3 +92,19 @@ export const getProcessedFiles = (files: FileEntity[]) => {
upload_file_id: fileItem.uploadedId || '',
}))
}
export const getFileNameFromUrl = (url: string) => {
const urlParts = url.split('/')
return urlParts[urlParts.length - 1] || ''
}
export const getSupportFileExtensionList = (allowFileTypes: string[], allowFileExtensions: string[]) => {
if (allowFileTypes.includes(SupportUploadFileTypes.custom))
return allowFileExtensions
return allowFileTypes.map(type => FILE_EXTS[type]).flat()
}
export const isAllowedFileExtension = (fileName: string, allowFileTypes: string[], allowFileExtensions: string[]) => {
return getSupportFileExtensionList(allowFileTypes, allowFileExtensions).includes(getFileExtension(fileName).toUpperCase())
}

View File

@ -500,6 +500,7 @@ const translation = {
vectorHash: 'Vector hash:',
hitScore: 'Retrieval Score:',
},
inputPlaceholder: 'Talk to Bot',
},
promptEditor: {
placeholder: 'Write your prompt word here, enter \'{\' to insert a variable, enter \'/\' to insert a prompt content block',
@ -565,6 +566,8 @@ const translation = {
uploadFromComputerReadError: 'File reading failed, please try again.',
uploadFromComputerUploadError: 'File upload failed, please upload again.',
uploadFromComputerLimit: 'Upload File cannot exceed {{size}}',
pasteFileLinkInvalid: 'Invalid file link',
fileExtensionNotSupport: 'File extension not supported',
},
tag: {
placeholder: 'All Tags',

View File

@ -500,6 +500,7 @@ const translation = {
vectorHash: '向量哈希:',
hitScore: '召回得分:',
},
inputPlaceholder: '和机器人聊天',
},
promptEditor: {
placeholder: '在这里写你的提示词,输入\'{\' 插入变量、输入\'/\' 插入提示内容块',
@ -565,6 +566,8 @@ const translation = {
uploadFromComputerReadError: '文件读取失败,请重新选择。',
uploadFromComputerUploadError: '文件上传失败,请重新上传。',
uploadFromComputerLimit: '上传文件不能超过 {{size}}',
pasteFileLinkInvalid: '文件链接无效',
fileExtensionNotSupport: '文件类型不支持',
},
tag: {
placeholder: '全部标签',

View File

@ -309,6 +309,6 @@ 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)
export const fetchRemoteFileInfo = (url: string) => {
return get<{ file_type: string; file_length: number }>(`/remote-files/${url}`)
}