From 788550affac962ae77df10ed493b7f249579f7c3 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Mon, 18 Mar 2024 14:49:17 +0800 Subject: [PATCH] chat upload file --- .../feature-panel/file-upload/index.tsx | 59 +++++++++ .../file-upload/param-config-content.tsx | 116 ++++++++++++++++++ .../file-upload/param-config.tsx | 47 +++++++ .../file-upload/radio-group/index.tsx | 40 ++++++ .../file-upload/radio-group/style.module.css | 24 ++++ .../base/features/feature-panel/index.tsx | 2 + web/app/components/base/features/store.ts | 7 ++ web/app/components/base/features/types.ts | 9 ++ web/app/components/workflow/features.tsx | 30 +++-- web/app/components/workflow/header/index.tsx | 32 ++--- .../workflow/hooks/use-nodes-sync-draft.ts | 1 + web/app/components/workflow/index.tsx | 7 ++ .../panel/debug-and-preview/chat-wrapper.tsx | 1 + 13 files changed, 344 insertions(+), 31 deletions(-) create mode 100644 web/app/components/base/features/feature-panel/file-upload/index.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/param-config.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css diff --git a/web/app/components/base/features/feature-panel/file-upload/index.tsx b/web/app/components/base/features/feature-panel/file-upload/index.tsx new file mode 100644 index 0000000000..d002f0005d --- /dev/null +++ b/web/app/components/base/features/feature-panel/file-upload/index.tsx @@ -0,0 +1,59 @@ +'use client' +import produce from 'immer' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import type { OnFeaturesChange } from '../../types' +import { + useFeatures, + useFeaturesStore, +} from '../../hooks' +import ParamConfig from './param-config' +import Switch from '@/app/components/base/switch' +import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files' + +type FileUploadProps = { + onChange?: OnFeaturesChange +} +const FileUpload = ({ + onChange, +}: FileUploadProps) => { + const { t } = useTranslation() + const featuresStore = useFeaturesStore() + const file = useFeatures(s => s.features.file) + + const handleSwitch = useCallback((value: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + const newFeatures = produce(features, (draft) => { + draft.file.image.enabled = value + }) + setFeatures(newFeatures) + + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( +
+
+ +
+
+ {t('common.imageUploader.imageUpload')} +
+
+
+ +
+ +
+
+ ) +} +export default React.memo(FileUpload) diff --git a/web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx b/web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx new file mode 100644 index 0000000000..1a2184c6b4 --- /dev/null +++ b/web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx @@ -0,0 +1,116 @@ +'use client' + +import produce from 'immer' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import type { OnFeaturesChange } from '../../types' +import { + useFeatures, + useFeaturesStore, +} from '../../hooks' +import RadioGroup from './radio-group' +import { TransferMethod } from '@/types/app' +import ParamItem from '@/app/components/base/param-item' + +const MIN = 1 +const MAX = 6 +type ParamConfigContentProps = { + onChange?: OnFeaturesChange +} +const ParamConfigContent = ({ + onChange, +}: ParamConfigContentProps) => { + const { t } = useTranslation() + const featuresStore = useFeaturesStore() + const file = useFeatures(s => s.features.file) + + const transferMethod = useMemo(() => { + if (!file.image.transfer_methods || file.image.transfer_methods.length === 2) + return TransferMethod.all + + return file.image.transfer_methods[0] + }, [file.image.transfer_methods]) + + const handleTransferMethodsChange = useCallback((value: TransferMethod) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + const newFeatures = produce(features, (draft) => { + if (TransferMethod.all) + draft.file.image.transfer_methods = [TransferMethod.remote_url, TransferMethod.local_file] + else + draft.file.image.transfer_methods = [value] + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + const handleLimitsChange = useCallback((_key: string, value: number) => { + if (!value) + return + + const { + features, + setFeatures, + } = featuresStore!.getState() + const newFeatures = produce(features, (draft) => { + draft.file.image.number_limits = value + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( +
+
+
{t('appDebug.vision.visionSettings.title')}
+
+
+
{t('appDebug.vision.visionSettings.uploadMethod')}
+ +
+
+ +
+
+
+
+ ) +} + +export default React.memo(ParamConfigContent) diff --git a/web/app/components/base/features/feature-panel/file-upload/param-config.tsx b/web/app/components/base/features/feature-panel/file-upload/param-config.tsx new file mode 100644 index 0000000000..ed3a1b434c --- /dev/null +++ b/web/app/components/base/features/feature-panel/file-upload/param-config.tsx @@ -0,0 +1,47 @@ +'use client' + +import { memo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import type { OnFeaturesChange } from '../../types' +import ParamConfigContent from './param-config-content' +import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +type ParamsConfigProps = { + onChange?: OnFeaturesChange +} +const ParamsConfig = ({ + onChange, +}: ParamsConfigProps) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + return ( + + setOpen(v => !v)}> +
+ +
{t('appDebug.voice.settings')}
+
+
+ +
+ +
+
+
+ ) +} +export default memo(ParamsConfig) diff --git a/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx b/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx new file mode 100644 index 0000000000..77e4d02184 --- /dev/null +++ b/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx @@ -0,0 +1,40 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from 'classnames' +import s from './style.module.css' + +type OPTION = { + label: string + value: any +} + +type Props = { + className?: string + options: OPTION[] + value: any + onChange: (value: any) => void +} + +const RadioGroup: FC = ({ + className = '', + options, + value, + onChange, +}) => { + return ( +
+ {options.map(item => ( +
onChange(item.value)} + > +
+
{item.label}
+
+ ))} +
+ ) +} +export default React.memo(RadioGroup) diff --git a/web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css b/web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css new file mode 100644 index 0000000000..22c29c6a42 --- /dev/null +++ b/web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css @@ -0,0 +1,24 @@ +.item { + @apply grow flex items-center h-8 px-2.5 rounded-lg bg-gray-25 border border-gray-100 cursor-pointer space-x-2; +} + +.item:hover { + background-color: #ffffff; + border-color: #B2CCFF; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); +} + +.item.checked { + background-color: #ffffff; + border-color: #528BFF; + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.10); +} + +.radio { + @apply w-4 h-4 border-[2px] border-gray-200 rounded-full; +} + +.item.checked .radio { + border-width: 5px; + border-color: #155eef; +} \ No newline at end of file diff --git a/web/app/components/base/features/feature-panel/index.tsx b/web/app/components/base/features/feature-panel/index.tsx index 6d21f76a56..cefeaac29c 100644 --- a/web/app/components/base/features/feature-panel/index.tsx +++ b/web/app/components/base/features/feature-panel/index.tsx @@ -5,6 +5,7 @@ import { import { useTranslation } from 'react-i18next' import type { OnFeaturesChange } from '../types' import { useFeatures } from '../hooks' +import FileUpload from './file-upload' import OpeningStatement from './opening-statement' import type { OpeningStatementProps } from './opening-statement' import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer' @@ -34,6 +35,7 @@ const FeaturePanel = ({ return (
+ { showAdvanceFeature && (
diff --git a/web/app/components/base/features/store.ts b/web/app/components/base/features/store.ts index 2e8517b6c0..bdb2f70db6 100644 --- a/web/app/components/base/features/store.ts +++ b/web/app/components/base/features/store.ts @@ -39,6 +39,13 @@ export const createFeaturesStore = (initProps?: Partial) => { moderation: { enabled: false, }, + file: { + image: { + enabled: false, + number_limits: 3, + transfer_methods: ['local_file', 'remote_url'], + }, + }, }, } return createStore()(set => ({ diff --git a/web/app/components/base/features/types.ts b/web/app/components/base/features/types.ts index 60f273fbb2..1912eb6609 100644 --- a/web/app/components/base/features/types.ts +++ b/web/app/components/base/features/types.ts @@ -23,6 +23,13 @@ export type SensitiveWordAvoidance = EnabledOrDisabled & { config?: any } +export type FileUpload = { + image: EnabledOrDisabled & { + number_limits: number + transfer_methods: string[] + } +} + export enum FeatureEnum { opening = 'opening', suggested = 'suggested', @@ -30,6 +37,7 @@ export enum FeatureEnum { speech2text = 'speech2text', citation = 'citation', moderation = 'moderation', + file = 'file', } export type Features = { @@ -39,6 +47,7 @@ export type Features = { [FeatureEnum.speech2text]: SpeechToText [FeatureEnum.citation]: RetrieverResource [FeatureEnum.moderation]: SensitiveWordAvoidance + [FeatureEnum.file]: FileUpload } export type OnFeaturesChange = (features: Features) => void diff --git a/web/app/components/workflow/features.tsx b/web/app/components/workflow/features.tsx index 3da33f8c14..1cd9b5a48b 100644 --- a/web/app/components/workflow/features.tsx +++ b/web/app/components/workflow/features.tsx @@ -4,7 +4,10 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { useStore } from './store' -import { useNodesSyncDraft } from './hooks' +import { + useIsChatMode, + useNodesSyncDraft, +} from './hooks' import { XClose } from '@/app/components/base/icons/src/vender/line/general' import { FeaturesChoose, @@ -13,6 +16,7 @@ import { const Features = () => { const { t } = useTranslation() + const isChatMode = useIsChatMode() const setShowFeaturesPanel = useStore(state => state.setShowFeaturesPanel) const { handleSyncWorkflowDraft } = useNodesSyncDraft() @@ -24,16 +28,20 @@ const Features = () => {
{t('workflow.common.features')} -
- -
-
setShowFeaturesPanel(false)} - > - -
-
+ { + isChatMode && ( +
+ +
+
setShowFeaturesPanel(false)} + > + +
+
+ ) + }
{ const workflowStore = useWorkflowStore() const appDetail = useAppStore(s => s.appDetail) const appSidebarExpand = useAppStore(s => s.appSidebarExpand) - const isChatMode = useIsChatMode() const runningStatus = useStore(s => s.runningStatus) const { handleRunSetting } = useWorkflowRun() @@ -78,21 +74,17 @@ const Header: FC = () => { }
- { - isChatMode && ( - - ) - } +
diff --git a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts index 717942a029..dd7319532f 100644 --- a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts @@ -58,6 +58,7 @@ export const useNodesSyncDraft = () => { speech_to_text: features.speech2text, retriever_resource: features.citation, sensitive_word_avoidance: features.moderation, + file_upload: features.file, }, }, }).then((res) => { diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 56d4b272e8..280a410d4f 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -183,6 +183,13 @@ const WorkflowWrap = memo(() => { const features = data.features || {} const initialFeatures: FeaturesData = { + file: { + image: { + enabled: !!features.file_upload.image.enabled, + number_limits: features.file_upload.image.number_limits || 3, + transfer_methods: features.file_upload.image.transfer_methods || ['local_file', 'remote_url'], + }, + }, opening: { enabled: !!features.opening_statement, opening_statement: features.opening_statement, diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index 12113995d9..de87839001 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -35,6 +35,7 @@ const ChatWrapper = forwardRef((_, ref) => { speech_to_text: features.speech2text, retriever_resource: features.citation, sensitive_word_avoidance: features.moderation, + file_upload: features.file, } }, [features])