From b3529d3cccd706da3136c1287800d1786284994e Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 29 Aug 2024 20:18:51 +0800 Subject: [PATCH] file upload --- .../new-feature-panel/file-upload/index.tsx | 102 ++++++++++++++++++ .../file-upload/setting-content.tsx | 86 +++++++++++++++ .../file-upload/setting-modal.tsx | 50 +++++++++ .../base/features/new-feature-panel/index.tsx | 19 +--- web/app/components/base/features/types.ts | 6 +- web/app/components/workflow/index.tsx | 7 ++ .../_base/components/file-upload-setting.tsx | 76 +++++++++---- web/i18n/en-US/app-debug.ts | 7 ++ web/i18n/zh-Hans/app-debug.ts | 7 ++ 9 files changed, 320 insertions(+), 40 deletions(-) create mode 100644 web/app/components/base/features/new-feature-panel/file-upload/index.tsx create mode 100644 web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx create mode 100644 web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx diff --git a/web/app/components/base/features/new-feature-panel/file-upload/index.tsx b/web/app/components/base/features/new-feature-panel/file-upload/index.tsx new file mode 100644 index 0000000000..369f6b2c61 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/file-upload/index.tsx @@ -0,0 +1,102 @@ +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 = { + onChange?: OnFeaturesChange +} + +const FileUpload = ({ + 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(newFeatures) + }, [featuresStore, onChange]) + + return ( + + + + } + title={t('appDebug.feature.fileUpload.title')} + value={file?.enabled} + onChange={state => handleChange(FeatureEnum.file, state)} + onMouseEnter={() => setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > + <> + {!file?.enabled && ( +
{t('appDebug.feature.fileUpload.description')}
+ )} + {file?.enabled && ( + <> + {!isHovering && !modalOpen && ( +
+
+
{t('appDebug.feature.fileUpload.supportedTypes')}
+
{supportedTypes}
+
+
+
+
{t('appDebug.feature.fileUpload.numberLimit')}
+
{file?.number_limits}
+
+
+ )} + {(isHovering || modalOpen) && ( + { + setModalOpen(v) + setIsHovering(v) + }} + onChange={onChange} + > + + + )} + + )} + +
+ ) +} + +export default FileUpload diff --git a/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx b/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx new file mode 100644 index 0000000000..44d1da938f --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx @@ -0,0 +1,86 @@ +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 = { + onClose: () => void + onChange?: OnFeaturesChange +} +const SettingContent = ({ + 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(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(newFeatures) + }, [featuresStore, onChange, tempPayload]) + + return ( + <> +
+
{t('appDebug.feature.fileUpload.modalTitle')}
+
+
+ setTempPayload(p)} + /> +
+ + +
+ + ) +} + +export default React.memo(SettingContent) diff --git a/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx b/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx new file mode 100644 index 0000000000..f632cd0ae7 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx @@ -0,0 +1,50 @@ +'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 +} +const FileUploadSettings = ({ + open, + onOpen, + onChange, + disabled, + children, +}: FileUploadSettingsProps) => { + return ( + + !disabled && onOpen((open: boolean) => !open)}> + {children} + + +
+ onOpen(false)} + onChange={(v) => { + onChange?.(v) + onOpen(false) + }} /> +
+
+
+ ) +} +export default memo(FileUploadSettings) diff --git a/web/app/components/base/features/new-feature-panel/index.tsx b/web/app/components/base/features/new-feature-panel/index.tsx index 8b666ef363..e6d766250a 100644 --- a/web/app/components/base/features/new-feature-panel/index.tsx +++ b/web/app/components/base/features/new-feature-panel/index.tsx @@ -7,10 +7,7 @@ import { RiEqualizer2Line, RiExternalLinkLine, } from '@remixicon/react' -import { - FolderUpload, - MessageFast, -} from '@/app/components/base/icons/src/vender/features' +import { MessageFast } from '@/app/components/base/icons/src/vender/features' import DialogWrapper from '@/app/components/base/features/new-feature-panel/dialog-wrapper' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -25,6 +22,7 @@ import ConversationOpener from '@/app/components/base/features/new-feature-panel import Moderation from '@/app/components/base/features/new-feature-panel/moderation' import SpeechToText from '@/app/components/base/features/new-feature-panel/speech-to-text' import TextToSpeech from '@/app/components/base/features/new-feature-panel/text-to-speech' +import FileUpload from '@/app/components/base/features/new-feature-panel/file-upload' import FollowUp from '@/app/components/base/features/new-feature-panel/follow-up' import Citation from '@/app/components/base/features/new-feature-panel/citation' @@ -100,18 +98,7 @@ const NewFeaturePanel = ({ {text2speechDefaultModel && ( )} - {/* file upload ##TODO## */} -
-
-
- -
-
- File upload -
- handleChange(FeatureEnum.text2speech, value)} defaultValue={!!features.text2speech?.enabled} /> -
-
+ {isChatMode && ( )} diff --git a/web/app/components/base/features/types.ts b/web/app/components/base/features/types.ts index 57b91aa3e1..128264a69f 100644 --- a/web/app/components/base/features/types.ts +++ b/web/app/components/base/features/types.ts @@ -33,7 +33,11 @@ export type FileUpload = { number_limits?: number transfer_methods?: TransferMethod[] } -} + allowed_file_types?: string[] + allowed_file_extensions?: string[] + allowed_file_upload_methods?: string[] + number_limits?: number +} & EnabledOrDisabled export enum FeatureEnum { moreLikeThis = 'moreLikeThis', diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index d96faa8677..6192c8247a 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -35,6 +35,7 @@ import type { } from './types' import { ControlMode, + SupportUploadFileTypes, } from './types' import { WorkflowContextProvider } from './context' import { @@ -88,6 +89,7 @@ import type { Features as FeaturesData } from '@/app/components/base/features/ty import { useFeaturesStore } from '@/app/components/base/features/hooks' import { useEventEmitterContextContext } from '@/context/event-emitter' import Confirm from '@/app/components/base/confirm' +import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' const nodeTypes = { [CUSTOM_NODE]: CustomNode, @@ -404,6 +406,11 @@ const WorkflowWrap = memo(() => { number_limits: features.file_upload?.image.number_limits || 3, transfer_methods: features.file_upload?.image.transfer_methods || ['local_file', 'remote_url'], }, + enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled), + allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image], + allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), + allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image.transfer_methods || ['local_file', 'remote_url'], + number_limits: features.file_upload?.number_limits || features.file_upload?.image.number_limits || 3, }, opening: { enabled: !!features.opening_statement, diff --git a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx index 1622db496a..e791afbb66 100644 --- a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx @@ -14,12 +14,14 @@ import { TransferMethod } from '@/types/app' type Props = { payload: UploadFileSetting isMultiple: boolean + inFeaturePanel?: boolean onChange: (payload: UploadFileSetting) => void } const FileUploadSetting: FC = ({ payload, isMultiple, + inFeaturePanel = false, onChange, }) => { const { t } = useTranslation() @@ -83,29 +85,31 @@ const FileUploadSetting: FC = ({ return (
- -
- { - [SupportUploadFileTypes.image, SupportUploadFileTypes.document, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( - - )) - } - -
-
+ {!inFeaturePanel && ( + +
+ { + [SupportUploadFileTypes.image, SupportUploadFileTypes.document, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( + + )) + } + +
+
+ )} = ({
)} + {inFeaturePanel && ( + +
+ { + [SupportUploadFileTypes.image, SupportUploadFileTypes.document, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( + + )) + } + +
+
+ )} ) diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index 91e14c4f80..1c4ce2a97d 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -199,6 +199,13 @@ const translation = { }, }, }, + fileUpload: { + title: 'File Upload', + description: 'The chat input box allows uploading of images, documents, and other files.', + supportedTypes: 'Support File Types', + numberLimit: 'Max uploads', + modalTitle: 'File Upload Setting', + }, }, generate: { title: 'Prompt Generator', diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index 89032ff84c..003c2c9318 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -199,6 +199,13 @@ const translation = { }, }, }, + fileUpload: { + title: '文件上传', + description: '聊天输入框支持上传文件。类型包括图片、文档以及其它类型', + supportedTypes: '支持的文件类型', + numberLimit: '最大上传数', + modalTitle: '文件上传设置', + }, }, generate: { title: '提示词生成器',