text-generation-image-uploader.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import type { FC } from 'react'
  2. import {
  3. Fragment,
  4. useEffect,
  5. useState,
  6. } from 'react'
  7. import { useTranslation } from 'react-i18next'
  8. import Uploader from './uploader'
  9. import ImageLinkInput from './image-link-input'
  10. import ImageList from './image-list'
  11. import { useImageFiles } from './hooks'
  12. import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images'
  13. import { Link03 } from '@/app/components/base/icons/src/vender/line/general'
  14. import {
  15. PortalToFollowElem,
  16. PortalToFollowElemContent,
  17. PortalToFollowElemTrigger,
  18. } from '@/app/components/base/portal-to-follow-elem'
  19. import type { ImageFile, VisionSettings } from '@/types/app'
  20. import { TransferMethod } from '@/types/app'
  21. type PasteImageLinkButtonProps = {
  22. onUpload: (imageFile: ImageFile) => void
  23. disabled?: boolean
  24. }
  25. const PasteImageLinkButton: FC<PasteImageLinkButtonProps> = ({
  26. onUpload,
  27. disabled,
  28. }) => {
  29. const { t } = useTranslation()
  30. const [open, setOpen] = useState(false)
  31. const handleUpload = (imageFile: ImageFile) => {
  32. setOpen(false)
  33. onUpload(imageFile)
  34. }
  35. const handleToggle = () => {
  36. if (disabled)
  37. return
  38. setOpen(v => !v)
  39. }
  40. return (
  41. <PortalToFollowElem
  42. open={open}
  43. onOpenChange={setOpen}
  44. placement='top-start'
  45. >
  46. <PortalToFollowElemTrigger onClick={handleToggle}>
  47. <div className={`
  48. relative flex items-center justify-center px-3 h-8 bg-gray-100 hover:bg-gray-200 text-xs text-gray-500 rounded-lg
  49. ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}
  50. `}>
  51. <Link03 className='mr-2 w-4 h-4' />
  52. {t('common.imageUploader.pasteImageLink')}
  53. </div>
  54. </PortalToFollowElemTrigger>
  55. <PortalToFollowElemContent className='z-10'>
  56. <div className='p-2 w-[320px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg'>
  57. <ImageLinkInput onUpload={handleUpload} />
  58. </div>
  59. </PortalToFollowElemContent>
  60. </PortalToFollowElem>
  61. )
  62. }
  63. type TextGenerationImageUploaderProps = {
  64. settings: VisionSettings
  65. onFilesChange: (files: ImageFile[]) => void
  66. }
  67. const TextGenerationImageUploader: FC<TextGenerationImageUploaderProps> = ({
  68. settings,
  69. onFilesChange,
  70. }) => {
  71. const { t } = useTranslation()
  72. const {
  73. files,
  74. onUpload,
  75. onRemove,
  76. onImageLinkLoadError,
  77. onImageLinkLoadSuccess,
  78. onReUpload,
  79. } = useImageFiles()
  80. useEffect(() => {
  81. onFilesChange(files)
  82. }, [files])
  83. const localUpload = (
  84. <Uploader
  85. onUpload={onUpload}
  86. disabled={files.length >= settings.number_limits}
  87. limit={+settings.image_file_size_limit!}
  88. >
  89. {
  90. hovering => (
  91. <div className={`
  92. flex items-center justify-center px-3 h-8 bg-gray-100
  93. text-xs text-gray-500 rounded-lg cursor-pointer
  94. ${hovering && 'bg-gray-200'}
  95. `}>
  96. <ImagePlus className='mr-2 w-4 h-4' />
  97. {t('common.imageUploader.uploadFromComputer')}
  98. </div>
  99. )
  100. }
  101. </Uploader>
  102. )
  103. const urlUpload = (
  104. <PasteImageLinkButton
  105. onUpload={onUpload}
  106. disabled={files.length >= settings.number_limits}
  107. />
  108. )
  109. return (
  110. <div>
  111. <div className='mb-1'>
  112. <ImageList
  113. list={files}
  114. onRemove={onRemove}
  115. onReUpload={onReUpload}
  116. onImageLinkLoadError={onImageLinkLoadError}
  117. onImageLinkLoadSuccess={onImageLinkLoadSuccess}
  118. />
  119. </div>
  120. <div className={`grid gap-1 ${settings.transfer_methods.length === 2 ? 'grid-cols-2' : 'grid-cols-1'}`}>
  121. {
  122. settings.transfer_methods.map((method) => {
  123. if (method === TransferMethod.local_file)
  124. return <Fragment key={TransferMethod.local_file}>{localUpload}</Fragment>
  125. if (method === TransferMethod.remote_url)
  126. return <Fragment key={TransferMethod.remote_url}>{urlUpload}</Fragment>
  127. return null
  128. })
  129. }
  130. </div>
  131. </div>
  132. )
  133. }
  134. export default TextGenerationImageUploader