mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 02:42:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			130 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			130 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|   memo,
 | |
|   useState,
 | |
| } from 'react'
 | |
| import { useTranslation } from 'react-i18next'
 | |
| import { RiUploadCloud2Line } from '@remixicon/react'
 | |
| import FileInput from '../file-input'
 | |
| import { useFile } from '../hooks'
 | |
| import { useStore } from '../store'
 | |
| import { FILE_URL_REGEX } from '../constants'
 | |
| import {
 | |
|   PortalToFollowElem,
 | |
|   PortalToFollowElemContent,
 | |
|   PortalToFollowElemTrigger,
 | |
| } from '@/app/components/base/portal-to-follow-elem'
 | |
| import Button from '@/app/components/base/button'
 | |
| import type { FileUpload } from '@/app/components/base/features/types'
 | |
| import cn from '@/utils/classnames'
 | |
| 
 | |
| type FileFromLinkOrLocalProps = {
 | |
|   showFromLink?: boolean
 | |
|   showFromLocal?: boolean
 | |
|   trigger: (open: boolean) => React.ReactNode
 | |
|   fileConfig: FileUpload
 | |
| }
 | |
| const FileFromLinkOrLocal = ({
 | |
|   showFromLink = true,
 | |
|   showFromLocal = true,
 | |
|   trigger,
 | |
|   fileConfig,
 | |
| }: FileFromLinkOrLocalProps) => {
 | |
|   const { t } = useTranslation()
 | |
|   const files = useStore(s => s.files)
 | |
|   const [open, setOpen] = useState(false)
 | |
|   const [url, setUrl] = useState('')
 | |
|   const [showError, setShowError] = useState(false)
 | |
|   const { handleLoadFileFromLink } = useFile(fileConfig)
 | |
|   const disabled = !!fileConfig.number_limits && files.length >= fileConfig.number_limits
 | |
| 
 | |
|   const handleSaveUrl = () => {
 | |
|     if (!url)
 | |
|       return
 | |
| 
 | |
|     if (!FILE_URL_REGEX.test(url)) {
 | |
|       setShowError(true)
 | |
|       return
 | |
|     }
 | |
|     handleLoadFileFromLink(url)
 | |
|     setUrl('')
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <PortalToFollowElem
 | |
|       placement='top'
 | |
|       offset={4}
 | |
|       open={open}
 | |
|       onOpenChange={setOpen}
 | |
|     >
 | |
|       <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} asChild>
 | |
|         {trigger(open)}
 | |
|       </PortalToFollowElemTrigger>
 | |
|       <PortalToFollowElemContent className='z-10'>
 | |
|         <div className='p-3 w-[280px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl shadow-lg'>
 | |
|           {
 | |
|             showFromLink && (
 | |
|               <>
 | |
|                 <div className={cn(
 | |
|                   'flex items-center p-1 h-8 bg-components-input-bg-active border border-components-input-border-active rounded-lg shadow-xs',
 | |
|                   showError && 'border-components-input-border-destructive',
 | |
|                 )}>
 | |
|                   <input
 | |
|                     className='grow block mr-0.5 px-1 bg-transparent system-sm-regular outline-none appearance-none'
 | |
|                     placeholder={t('common.fileUploader.pasteFileLinkInputPlaceholder') || ''}
 | |
|                     value={url}
 | |
|                     onChange={(e) => {
 | |
|                       setShowError(false)
 | |
|                       setUrl(e.target.value)
 | |
|                     }}
 | |
|                     disabled={disabled}
 | |
|                   />
 | |
|                   <Button
 | |
|                     className='shrink-0'
 | |
|                     size='small'
 | |
|                     variant='primary'
 | |
|                     disabled={!url || disabled}
 | |
|                     onClick={handleSaveUrl}
 | |
|                   >
 | |
|                     {t('common.operation.ok')}
 | |
|                   </Button>
 | |
|                 </div>
 | |
|                 {
 | |
|                   showError && (
 | |
|                     <div className='mt-0.5 body-xs-regular text-text-destructive'>
 | |
|                       {t('common.fileUploader.pasteFileLinkInvalid')}
 | |
|                     </div>
 | |
|                   )
 | |
|                 }
 | |
|               </>
 | |
|             )
 | |
|           }
 | |
|           {
 | |
|             showFromLink && showFromLocal && (
 | |
|               <div className='flex items-center p-2 h-7 system-2xs-medium-uppercase text-text-quaternary'>
 | |
|                 <div className='mr-2 w-[93px] h-[1px] bg-gradient-to-l from-[rgba(16,24,40,0.08)]' />
 | |
|                 OR
 | |
|                 <div className='ml-2 w-[93px] h-[1px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]' />
 | |
|               </div>
 | |
|             )
 | |
|           }
 | |
|           {
 | |
|             showFromLocal && (
 | |
|               <Button
 | |
|                 className='relative w-full'
 | |
|                 variant='secondary-accent'
 | |
|                 disabled={disabled}
 | |
|               >
 | |
|                 <RiUploadCloud2Line className='mr-1 w-4 h-4' />
 | |
|                 {t('common.fileUploader.uploadFromComputer')}
 | |
|                 <FileInput fileConfig={fileConfig} />
 | |
|               </Button>
 | |
|             )
 | |
|           }
 | |
|         </div>
 | |
|       </PortalToFollowElemContent>
 | |
|     </PortalToFollowElem>
 | |
|   )
 | |
| }
 | |
| 
 | |
| export default memo(FileFromLinkOrLocal)
 | 
