From 61c7fdc6147ca1f7f3cb8677ff7a579dad9bae70 Mon Sep 17 00:00:00 2001 From: twwu Date: Thu, 15 Jan 2026 16:13:01 +0800 Subject: [PATCH] feat: add humanInputEmailDeliveryEnabled to provider context and update related components for email delivery handling --- web/__mocks__/provider-context.ts | 1 + .../apikey-info-panel.test-utils.tsx | 1 + web/app/components/billing/type.ts | 1 + .../rag-pipeline-header/publisher/popup.tsx | 16 ++------ .../delivery-method/method-item.tsx | 4 +- .../delivery-method/method-selector.tsx | 40 ++++++++++++++----- web/context/provider-context.tsx | 6 +++ 7 files changed, 46 insertions(+), 23 deletions(-) diff --git a/web/__mocks__/provider-context.ts b/web/__mocks__/provider-context.ts index 373c2f86d3..d3296bacd0 100644 --- a/web/__mocks__/provider-context.ts +++ b/web/__mocks__/provider-context.ts @@ -35,6 +35,7 @@ export const baseProviderContextValue: ProviderContextState = { refreshLicenseLimit: noop, isAllowTransferWorkspace: false, isAllowPublishAsCustomKnowledgePipelineTemplate: false, + humanInputEmailDeliveryEnabled: false, } export const createMockProviderContextValue = (overrides: Partial = {}): ProviderContextState => { diff --git a/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx b/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx index 17857ec702..54763907df 100644 --- a/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx +++ b/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx @@ -53,6 +53,7 @@ const defaultProviderContext = { refreshLicenseLimit: noop, isAllowTransferWorkspace: false, isAllowPublishAsCustomKnowledgePipelineTemplate: false, + humanInputEmailDeliveryEnabled: false, } const defaultModalContext: ModalContextState = { diff --git a/web/app/components/billing/type.ts b/web/app/components/billing/type.ts index 1522de63b2..e3eb8b6799 100644 --- a/web/app/components/billing/type.ts +++ b/web/app/components/billing/type.ts @@ -118,6 +118,7 @@ export type CurrentPlanInfoBackend = { knowledge_pipeline: { publish_enabled: boolean } + human_input_email_delivery_enabled: boolean } export type SubscriptionItem = { diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx index b006c9acfb..081efce20b 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx @@ -35,7 +35,7 @@ import { import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useModalContextSelector } from '@/context/modal-context' -import { useProviderContext } from '@/context/provider-context' +import { useProviderContextSelector } from '@/context/provider-context' import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { useInvalidDatasetList } from '@/service/knowledge/use-dataset' @@ -65,7 +65,7 @@ const Popup = () => { const { mutateAsync: publishWorkflow } = usePublishWorkflow() const { notify } = useToastContext() const workflowStore = useWorkflowStore() - const { isAllowPublishAsCustomKnowledgePipelineTemplate } = useProviderContext() + const isAllowPublishAsCustomKnowledgePipelineTemplate = useProviderContextSelector(s => s.isAllowPublishAsCustomKnowledgePipelineTemplate) const setShowPricingModal = useModalContextSelector(s => s.setShowPricingModal) const apiReferenceUrl = useDatasetApiAccessUrl() @@ -149,7 +149,7 @@ const Popup = () => { if (confirmVisible) hideConfirm() } - }, [handleCheckBeforePublish, publishWorkflow, pipelineId, notify, t, workflowStore, mutateDatasetRes, invalidPublishedPipelineInfo, showConfirm, publishedAt, confirmVisible, hidePublishing, showPublishing, hideConfirm, publishing]) + }, [publishing, handleCheckBeforePublish, publishedAt, confirmVisible, showPublishing, publishWorkflow, pipelineId, datasetId, showConfirm, notify, t, workflowStore, mutateDatasetRes, invalidPublishedPipelineInfo, invalidDatasetList, hidePublishing, hideConfirm]) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => { e.preventDefault() @@ -204,15 +204,7 @@ const Popup = () => { hidePublishingAsCustomizedPipeline() hidePublishAsKnowledgePipelineModal() } - }, [ - pipelineId, - publishAsCustomizedPipeline, - showPublishingAsCustomizedPipeline, - hidePublishingAsCustomizedPipeline, - hidePublishAsKnowledgePipelineModal, - notify, - t, - ]) + }, [showPublishingAsCustomizedPipeline, publishAsCustomizedPipeline, pipelineId, notify, t, invalidCustomizedTemplateList, hidePublishingAsCustomizedPipeline, hidePublishAsKnowledgePipelineModal]) const handleClickPublishAsKnowledgePipeline = useCallback(() => { if (!isAllowPublishAsCustomKnowledgePipelineTemplate) diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx index 5e20a6ddfd..1416fe4619 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx @@ -98,7 +98,9 @@ const DeliveryMethodItem: FC = ({ )}
{method.type}
- {method.type === DeliveryMethodType.Email && (method.config as EmailConfig)?.debug_mode && DEBUG} + {method.type === DeliveryMethodType.Email + && (method.config as EmailConfig)?.debug_mode + && DEBUG}
{!readonly && ( diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx index 3f4935648c..2511ebdb49 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx @@ -8,8 +8,7 @@ import { RiMailSendFill, RiRobot2Fill, } from '@remixicon/react' -import * as React from 'react' -import { useCallback, useRef, useState } from 'react' +import { memo, useCallback, useMemo, useRef, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { v4 as uuid4 } from 'uuid' import ActionButton from '@/app/components/base/action-button' @@ -21,23 +20,26 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import { IS_CE_EDITION } from '@/config' +import { useProviderContextSelector } from '@/context/provider-context' import { cn } from '@/utils/classnames' import { DeliveryMethodType } from '../../types' const i18nPrefix = 'nodes.humanInput' -type Props = { +type MethodSelectorProps = { data: DeliveryMethod[] onAdd: (method: DeliveryMethod) => void } -const MethodSelector: FC = ({ +const MethodSelector: FC = ({ data, onAdd, }) => { const { t } = useTranslation() const [open, doSetOpen] = useState(false) + const humanInputEmailDeliveryEnabled = useProviderContextSelector(s => s.humanInputEmailDeliveryEnabled) const openRef = useRef(open) + const setOpen = useCallback((v: boolean) => { doSetOpen(v) openRef.current = v @@ -47,6 +49,13 @@ const MethodSelector: FC = ({ setOpen(!openRef.current) }, [setOpen]) + const emailDeliveryInfo = useMemo(() => { + return { + noPermission: !humanInputEmailDeliveryEnabled, + added: data.some(method => method.type === DeliveryMethodType.Email), + } + }, [data, humanInputEmailDeliveryEnabled]) + return ( = ({ )}
method.type === DeliveryMethodType.Email) && 'cursor-not-allowed bg-transparent hover:bg-transparent')} + className={cn( + 'relative flex cursor-pointer items-center gap-1 rounded-lg p-1 pl-3 hover:bg-state-base-hover', + (emailDeliveryInfo.noPermission || emailDeliveryInfo.added) && 'cursor-not-allowed bg-transparent hover:bg-transparent', + )} onClick={() => { - if (data.some(method => method.type === DeliveryMethodType.Email)) + if (emailDeliveryInfo.noPermission || emailDeliveryInfo.added) return onAdd({ id: uuid4(), @@ -102,16 +114,24 @@ const MethodSelector: FC = ({ }) }} > -
method.type === DeliveryMethodType.Email) && 'opacity-50')}> +
-
method.type === DeliveryMethodType.Email) && 'opacity-50')}> +
{t(`${i18nPrefix}.deliveryMethod.types.email.title`, { ns: 'workflow' })}
{t(`${i18nPrefix}.deliveryMethod.types.email.description`, { ns: 'workflow' })}
- {data.some(method => method.type === DeliveryMethodType.Email) && ( + {emailDeliveryInfo.added && (
{t(`${i18nPrefix}.deliveryMethod.added`, { ns: 'workflow' })}
)} + {emailDeliveryInfo.noPermission && ( +
Upgrade
+ )}
{/* Slack */}
= ({ ) } -export default React.memo(MethodSelector) +export default memo(MethodSelector) diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index d350d08b4a..2a71d9cf93 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -64,6 +64,7 @@ export type ProviderContextState = { refreshLicenseLimit: () => void isAllowTransferWorkspace: boolean isAllowPublishAsCustomKnowledgePipelineTemplate: boolean + humanInputEmailDeliveryEnabled: boolean } export const baseProviderContextValue: ProviderContextState = { @@ -96,6 +97,7 @@ export const baseProviderContextValue: ProviderContextState = { refreshLicenseLimit: noop, isAllowTransferWorkspace: false, isAllowPublishAsCustomKnowledgePipelineTemplate: false, + humanInputEmailDeliveryEnabled: false, } const ProviderContext = createContext(baseProviderContextValue) @@ -137,6 +139,7 @@ export const ProviderContextProvider = ({ const { data: educationAccountInfo, isLoading: isLoadingEducationAccountInfo, isFetching: isFetchingEducationAccountInfo, isFetchedAfterMount: isEducationDataFetchedAfterMount } = useEducationStatus(!enableEducationPlan) const [isAllowTransferWorkspace, setIsAllowTransferWorkspace] = useState(false) const [isAllowPublishAsCustomKnowledgePipelineTemplate, setIsAllowPublishAsCustomKnowledgePipelineTemplate] = useState(false) + const [humanInputEmailDeliveryEnabled, setHumanInputEmailDeliveryEnabled] = useState(false) const refreshModelProviders = () => { queryClient.invalidateQueries({ queryKey: ['common', 'model-providers'] }) @@ -173,6 +176,8 @@ export const ProviderContextProvider = ({ setIsAllowTransferWorkspace(data.is_allow_transfer_workspace) if (data.knowledge_pipeline?.publish_enabled) setIsAllowPublishAsCustomKnowledgePipelineTemplate(data.knowledge_pipeline?.publish_enabled) + if (data.human_input_email_delivery_enabled) + setHumanInputEmailDeliveryEnabled(data.human_input_email_delivery_enabled) } catch (error) { console.error('Failed to fetch plan info:', error) @@ -250,6 +255,7 @@ export const ProviderContextProvider = ({ refreshLicenseLimit: fetchPlan, isAllowTransferWorkspace, isAllowPublishAsCustomKnowledgePipelineTemplate, + humanInputEmailDeliveryEnabled, }} > {children}