mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-20 01:29:04 +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 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>
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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]
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
@ -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',
|
||||||
|
@ -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: '全部标签',
|
||||||
|
@ -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}`)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user