mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-17 11:45:55 +08:00
fix: uploader
This commit is contained in:
parent
9fd2f798ff
commit
4ed46e3fed
@ -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>
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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]
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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: '全部标签',
|
||||
|
@ -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}`)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user