dify / web /app /components /base /image-uploader /text-generation-image-uploader.tsx
Severian's picture
initial commit
a8b3f00
raw
history blame
4.06 kB
import type { FC } from 'react'
import {
Fragment,
useEffect,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import Uploader from './uploader'
import ImageLinkInput from './image-link-input'
import ImageList from './image-list'
import { useImageFiles } from './hooks'
import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images'
import { Link03 } from '@/app/components/base/icons/src/vender/line/general'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type { ImageFile, VisionSettings } from '@/types/app'
import { TransferMethod } from '@/types/app'
type PasteImageLinkButtonProps = {
onUpload: (imageFile: ImageFile) => void
disabled?: boolean
}
const PasteImageLinkButton: FC<PasteImageLinkButtonProps> = ({
onUpload,
disabled,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const handleUpload = (imageFile: ImageFile) => {
setOpen(false)
onUpload(imageFile)
}
const handleToggle = () => {
if (disabled)
return
setOpen(v => !v)
}
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top-start'
>
<PortalToFollowElemTrigger onClick={handleToggle}>
<div className={`
relative flex items-center justify-center px-3 h-8 bg-gray-100 hover:bg-gray-200 text-xs text-gray-500 rounded-lg
${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}
`}>
<Link03 className='mr-2 w-4 h-4' />
{t('common.imageUploader.pasteImageLink')}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<div className='p-2 w-[320px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg'>
<ImageLinkInput onUpload={handleUpload} />
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
type TextGenerationImageUploaderProps = {
settings: VisionSettings
onFilesChange: (files: ImageFile[]) => void
}
const TextGenerationImageUploader: FC<TextGenerationImageUploaderProps> = ({
settings,
onFilesChange,
}) => {
const { t } = useTranslation()
const {
files,
onUpload,
onRemove,
onImageLinkLoadError,
onImageLinkLoadSuccess,
onReUpload,
} = useImageFiles()
useEffect(() => {
onFilesChange(files)
}, [files])
const localUpload = (
<Uploader
onUpload={onUpload}
disabled={files.length >= settings.number_limits}
limit={+settings.image_file_size_limit!}
>
{
hovering => (
<div className={`
flex items-center justify-center px-3 h-8 bg-gray-100
text-xs text-gray-500 rounded-lg cursor-pointer
${hovering && 'bg-gray-200'}
`}>
<ImagePlus className='mr-2 w-4 h-4' />
{t('common.imageUploader.uploadFromComputer')}
</div>
)
}
</Uploader>
)
const urlUpload = (
<PasteImageLinkButton
onUpload={onUpload}
disabled={files.length >= settings.number_limits}
/>
)
return (
<div>
<div className='mb-1'>
<ImageList
list={files}
onRemove={onRemove}
onReUpload={onReUpload}
onImageLinkLoadError={onImageLinkLoadError}
onImageLinkLoadSuccess={onImageLinkLoadSuccess}
/>
</div>
<div className={`grid gap-1 ${settings.transfer_methods.length === 2 ? 'grid-cols-2' : 'grid-cols-1'}`}>
{
settings.transfer_methods.map((method) => {
if (method === TransferMethod.local_file)
return <Fragment key={TransferMethod.local_file}>{localUpload}</Fragment>
if (method === TransferMethod.remote_url)
return <Fragment key={TransferMethod.remote_url}>{urlUpload}</Fragment>
return null
})
}
</div>
</div>
)
}
export default TextGenerationImageUploader