This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import { RiEqualizer2Line } from '@remixicon/react'
import { FolderUpload } from '@/app/components/base/icons/src/vender/features'
import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card'
import SettingModal from '@/app/components/base/features/new-feature-panel/file-upload/setting-modal'
import Button from '@/app/components/base/button'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types'
type Props = {
disabled: boolean
onChange?: OnFeaturesChange
}
const FileUpload = ({
disabled,
onChange,
}: Props) => {
const { t } = useTranslation()
const file = useFeatures(s => s.features.file)
const featuresStore = useFeaturesStore()
const [modalOpen, setModalOpen] = useState(false)
const [isHovering, setIsHovering] = useState(false)
const supportedTypes = useMemo(() => {
return file?.allowed_file_types?.join(',') || '-'
}, [file?.allowed_file_types])
const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => {
const {
features,
setFeatures,
} = featuresStore!.getState()
const newFeatures = produce(features, (draft) => {
draft[type] = {
...draft[type],
enabled,
image: { enabled },
}
})
setFeatures(newFeatures)
if (onChange)
onChange()
}, [featuresStore, onChange])
return (
<FeatureCard
icon={
<div className='shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-blue-600 p-1 shadow-xs'>
<FolderUpload className='h-4 w-4 text-text-primary-on-surface' />
</div>
}
title={t('appDebug.feature.fileUpload.title')}
value={file?.enabled}
onChange={state => handleChange(FeatureEnum.file, state)}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
disabled={disabled}
>
<>
{!file?.enabled && (
<div className='system-xs-regular line-clamp-2 min-h-8 text-text-tertiary'>{t('appDebug.feature.fileUpload.description')}</div>
)}
{file?.enabled && (
<>
{!isHovering && !modalOpen && (
<div className='flex items-center gap-4 pt-0.5'>
<div className=''>
<div className='system-2xs-medium-uppercase mb-0.5 text-text-tertiary'>{t('appDebug.feature.fileUpload.supportedTypes')}</div>
<div className='system-xs-regular text-text-secondary'>{supportedTypes}</div>
</div>
<div className='h-[27px] w-px rotate-12 bg-divider-subtle'></div>
<div className=''>
<div className='system-2xs-medium-uppercase mb-0.5 text-text-tertiary'>{t('appDebug.feature.fileUpload.numberLimit')}</div>
<div className='system-xs-regular text-text-secondary'>{file?.number_limits}</div>
</div>
</div>
)}
{(isHovering || modalOpen) && (
<SettingModal
open={modalOpen && !disabled}
onOpen={(v) => {
setModalOpen(v)
setIsHovering(v)
}}
onChange={onChange}
>
<Button className='w-full' disabled={disabled}>
<RiEqualizer2Line className='mr-1 h-4 w-4' />
{t('common.operation.settings')}
</Button>
</SettingModal>
)}
</>
)}
</>
</FeatureCard>
)
}
export default FileUpload

View File

@@ -0,0 +1,89 @@
import React, { useCallback, useMemo, useState } from 'react'
import { produce } from 'immer'
import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react'
import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/file-upload-setting'
import Button from '@/app/components/base/button'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
import type { UploadFileSetting } from '@/app/components/workflow/types'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
type SettingContentProps = {
imageUpload?: boolean
onClose: () => void
onChange?: OnFeaturesChange
}
const SettingContent = ({
imageUpload,
onClose,
onChange,
}: SettingContentProps) => {
const { t } = useTranslation()
const featuresStore = useFeaturesStore()
const file = useFeatures(state => state.features.file)
const fileSettingPayload = useMemo(() => {
return {
allowed_file_upload_methods: file?.allowed_file_upload_methods || ['local_file', 'remote_url'],
allowed_file_types: file?.allowed_file_types || [SupportUploadFileTypes.image],
allowed_file_extensions: file?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image],
max_length: file?.number_limits || 3,
} as UploadFileSetting
}, [file])
const [tempPayload, setTempPayload] = useState<UploadFileSetting>(fileSettingPayload)
const handleChange = useCallback(() => {
const {
features,
setFeatures,
} = featuresStore!.getState()
const newFeatures = produce(features, (draft) => {
draft.file = {
...draft.file,
allowed_file_upload_methods: tempPayload.allowed_file_upload_methods,
number_limits: tempPayload.max_length,
allowed_file_types: tempPayload.allowed_file_types,
allowed_file_extensions: tempPayload.allowed_file_extensions,
}
})
setFeatures(newFeatures)
if (onChange)
onChange()
}, [featuresStore, onChange, tempPayload])
return (
<>
<div className='mb-4 flex items-center justify-between'>
<div className='system-xl-semibold text-text-primary'>{!imageUpload ? t('appDebug.feature.fileUpload.modalTitle') : t('appDebug.feature.imageUpload.modalTitle')}</div>
<div className='cursor-pointer p-1' onClick={onClose}><RiCloseLine className='h-4 w-4 text-text-tertiary'/></div>
</div>
<FileUploadSetting
isMultiple
inFeaturePanel
hideSupportFileType={imageUpload}
payload={tempPayload}
onChange={(p: UploadFileSetting) => setTempPayload(p)}
/>
<div className='mt-4 flex items-center justify-end'>
<Button
onClick={onClose}
className='mr-2'
>
{t('common.operation.cancel')}
</Button>
<Button
variant='primary'
onClick={handleChange}
disabled={tempPayload.allowed_file_types.length === 0}
>
{t('common.operation.save')}
</Button>
</div>
</>
)
}
export default React.memo(SettingContent)

View File

@@ -0,0 +1,53 @@
'use client'
import { memo } from 'react'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import SettingContent from '@/app/components/base/features/new-feature-panel/file-upload/setting-content'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
type FileUploadSettingsProps = {
open: boolean
onOpen: (state: any) => void
onChange?: OnFeaturesChange
disabled?: boolean
children?: React.ReactNode
imageUpload?: boolean
}
const FileUploadSettings = ({
open,
onOpen,
onChange,
disabled,
children,
imageUpload,
}: FileUploadSettingsProps) => {
return (
<PortalToFollowElem
open={open}
onOpenChange={onOpen}
placement='left'
offset={{
mainAxis: 32,
}}
>
<PortalToFollowElemTrigger className='flex' onClick={() => !disabled && onOpen((open: boolean) => !open)}>
{children}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 50 }}>
<div className='max-h-[calc(100vh-20px)] w-[360px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-4 shadow-2xl'>
<SettingContent
imageUpload={imageUpload}
onClose={() => onOpen(false)}
onChange={(v) => {
onChange?.(v)
onOpen(false)
}} />
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(FileUploadSettings)