file uploader

This commit is contained in:
StyleZhang 2024-09-10 14:17:29 +08:00
parent 97056dad30
commit 32b6c7063a
7 changed files with 69 additions and 33 deletions

View File

@ -38,7 +38,7 @@ const Operation = forwardRef<HTMLDivElement, OperationProps>(({
ref={ref}
>
<div className='flex items-center space-x-1'>
{visionConfig?.enabled && <FileUploaderInChatInput />}
<FileUploaderInChatInput />
{
speechToTextConfig?.enabled && (
<ActionButton

View File

@ -1,9 +1,11 @@
import type { ChangeEvent } from 'react'
import {
memo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { RiUploadCloud2Line } from '@remixicon/react'
import { useFile } from '../hooks'
import {
PortalToFollowElem,
PortalToFollowElemContent,
@ -26,6 +28,16 @@ const FileFromLinkOrLocal = ({
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [url, setUrl] = useState('')
const { handleLocalFileUpload } = useFile()
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file)
return
handleLocalFileUpload(file)
}
return (
<PortalToFollowElem
@ -81,7 +93,7 @@ const FileFromLinkOrLocal = ({
className='absolute block inset-0 opacity-0 text-[0] w-full disabled:cursor-not-allowed cursor-pointer'
onClick={e => ((e.target as HTMLInputElement).value = '')}
type='file'
onChange={() => {}}
onChange={handleChange}
/>
</Button>
)

View File

@ -4,12 +4,14 @@ import {
} from 'react'
import { RiCloseLine } from '@remixicon/react'
import { useStore } from '../store'
import { useFile } from '../hooks'
import FileListItem from './file-list-flex-item'
import Button from '@/app/components/base/button'
import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
const FileListFlexOperation = forwardRef<HTMLDivElement>((_, ref) => {
const files = useStore(s => s.files)
const { handleRemoveFile } = useFile()
return (
<div
@ -22,21 +24,28 @@ const FileListFlexOperation = forwardRef<HTMLDivElement>((_, ref) => {
key={file._id}
className='relative'
>
<Button className='absolute -right-1.5 -top-1.5 p-0 w-5 h-5 rounded-full z-10'>
<Button
className='absolute -right-1.5 -top-1.5 p-0 w-5 h-5 rounded-full z-10'
onClick={() => handleRemoveFile(file._id)}
>
<RiCloseLine className='w-4 h-4 text-components-button-secondary-text' />
</Button>
<div
className='absolute inset-0 border-[2px] border-effects-image-frame shadow-md bg-black'
>
<ProgressCircle
className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'
percentage={30}
size={16}
circleStrokeColor='stroke-components-progress-white-border'
circleFillColor='fill-transparent'
sectorFillColor='fill-components-progress-white-progress'
/>
</div>
{
file._progress !== 100 && (
<div
className='absolute inset-0 border-[2px] border-effects-image-frame shadow-md bg-black'
>
<ProgressCircle
className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'
percentage={file._progress}
size={16}
circleStrokeColor='stroke-components-progress-white-border'
circleFillColor='fill-transparent'
sectorFillColor='fill-components-progress-white-progress'
/>
</div>
)
}
<FileListItem />
</div>
))

View File

@ -6,7 +6,7 @@ import {
import produce from 'immer'
import { v4 as uuid4 } from 'uuid'
import { useTranslation } from 'react-i18next'
import type { TFile } from './types'
import type { FileEntity } from './types'
import { useFileStore } from './store'
import { fileUpload } from './utils'
import { useToastContext } from '@/app/components/base/toast'
@ -15,15 +15,12 @@ type UseFileParams = {
isPublicAPI?: boolean
url?: string
}
export const useFile = ({
isPublicAPI,
url,
}: UseFileParams) => {
export const useFile = () => {
const { t } = useTranslation()
const { notify } = useToastContext()
const fileStore = useFileStore()
const handleAddOrUpdateFiles = useCallback((newFile: TFile) => {
const handleAddOrUpdateFiles = useCallback((newFile: FileEntity) => {
const {
files,
setFiles,
@ -71,29 +68,37 @@ export const useFile = ({
setFiles([])
}, [fileStore])
const handleLocalFileUpload = useCallback((file: File) => {
const handleLocalFileUpload = useCallback((
file: File,
{
isPublicAPI,
url,
}: UseFileParams = { isPublicAPI: false },
) => {
const reader = new FileReader()
const isImage = file.type.startsWith('image')
reader.addEventListener(
'load',
() => {
const imageFile = {
const uploadingFile = {
_id: uuid4(),
file,
_url: reader.result as string,
_progress: 0,
_base64Url: isImage ? reader.result as string : '',
}
handleAddOrUpdateFiles(imageFile)
handleAddOrUpdateFiles(uploadingFile)
fileUpload({
file: imageFile.file,
file: uploadingFile.file,
onProgressCallback: (progress) => {
handleAddOrUpdateFiles({ ...imageFile, _progress: progress })
handleAddOrUpdateFiles({ ...uploadingFile, _progress: progress })
},
onSuccessCallback: (res) => {
handleAddOrUpdateFiles({ ...imageFile, _fileId: res.id, _progress: 100 })
handleAddOrUpdateFiles({ ...uploadingFile, _fileId: res.id, _progress: 100 })
},
onErrorCallback: () => {
notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerUploadError') })
handleAddOrUpdateFiles({ ...imageFile, _progress: -1 })
handleAddOrUpdateFiles({ ...uploadingFile, _progress: -1 })
},
}, isPublicAPI, url)
},
@ -107,7 +112,7 @@ export const useFile = ({
false,
)
reader.readAsDataURL(file)
}, [notify, t, handleAddOrUpdateFiles, isPublicAPI, url])
}, [notify, t, handleAddOrUpdateFiles])
const handleClipboardPasteFile = useCallback((e: ClipboardEvent<HTMLTextAreaElement>) => {
const file = e.clipboardData?.files[0]

View File

@ -7,11 +7,11 @@ import {
useStore as useZustandStore,
} from 'zustand'
import { createStore } from 'zustand/vanilla'
import type { TFile } from './types'
import type { FileEntity } from './types'
type Shape = {
files: TFile[]
setFiles: (files: TFile[]) => void
files: FileEntity[]
setFiles: (files: FileEntity[]) => void
}
export const createFileStore = () => {

View File

@ -1,3 +1,5 @@
import type { TransferMethod } from '@/types/app'
export enum FileTypeEnum {
IMAGE = 'IMAGE',
VIDEO = 'VIDEO',
@ -13,10 +15,12 @@ export enum FileTypeEnum {
OTHER = 'OTHER',
}
export type TFile = {
export type FileEntity = {
file: File
_id: string
_fileId?: string
_progress?: number
_url?: string
_base64Url?: string
_method?: TransferMethod
}

View File

@ -34,3 +34,9 @@ export const fileUpload: FileUpload = ({
onErrorCallback()
})
}
export const isFileType = (type: string) => {
return (file: File) => {
return file.type === type
}
}