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

View File

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

View File

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

View File

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

View File

@ -92,3 +92,19 @@ export const getProcessedFiles = (files: FileEntity[]) => {
upload_file_id: fileItem.uploadedId || '', 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:', vectorHash: 'Vector hash:',
hitScore: 'Retrieval Score:', hitScore: 'Retrieval Score:',
}, },
inputPlaceholder: 'Talk to Bot',
}, },
promptEditor: { promptEditor: {
placeholder: 'Write your prompt word here, enter \'{\' to insert a variable, enter \'/\' to insert a prompt content block', 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.', uploadFromComputerReadError: 'File reading failed, please try again.',
uploadFromComputerUploadError: 'File upload failed, please upload again.', uploadFromComputerUploadError: 'File upload failed, please upload again.',
uploadFromComputerLimit: 'Upload File cannot exceed {{size}}', uploadFromComputerLimit: 'Upload File cannot exceed {{size}}',
pasteFileLinkInvalid: 'Invalid file link',
fileExtensionNotSupport: 'File extension not supported',
}, },
tag: { tag: {
placeholder: 'All Tags', placeholder: 'All Tags',

View File

@ -500,6 +500,7 @@ const translation = {
vectorHash: '向量哈希:', vectorHash: '向量哈希:',
hitScore: '召回得分:', hitScore: '召回得分:',
}, },
inputPlaceholder: '和机器人聊天',
}, },
promptEditor: { promptEditor: {
placeholder: '在这里写你的提示词,输入\'{\' 插入变量、输入\'/\' 插入提示内容块', placeholder: '在这里写你的提示词,输入\'{\' 插入变量、输入\'/\' 插入提示内容块',
@ -565,6 +566,8 @@ const translation = {
uploadFromComputerReadError: '文件读取失败,请重新选择。', uploadFromComputerReadError: '文件读取失败,请重新选择。',
uploadFromComputerUploadError: '文件上传失败,请重新上传。', uploadFromComputerUploadError: '文件上传失败,请重新上传。',
uploadFromComputerLimit: '上传文件不能超过 {{size}}', uploadFromComputerLimit: '上传文件不能超过 {{size}}',
pasteFileLinkInvalid: '文件链接无效',
fileExtensionNotSupport: '文件类型不支持',
}, },
tag: { tag: {
placeholder: '全部标签', 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 }) => export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
post<CommonResponse>(url, { body }) post<CommonResponse>(url, { body })
export const fetchRemoteFileInfo: Fetcher<{ file_type: string; file_length: number }, string> = (url) => { export const fetchRemoteFileInfo = (url: string) => {
return get<{ file_type: string; file_length: number }>(url) return get<{ file_type: string; file_length: number }>(`/remote-files/${url}`)
} }