diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx index 57f3ef6881..fb431c5ac8 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useMemo } from 'react' +import React, { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AppCard from '@/app/components/app/overview/app-card' @@ -24,6 +24,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' import { useAppWorkflow } from '@/service/use-workflow' import type { BlockEnum } from '@/app/components/workflow/types' import { isTriggerNode } from '@/app/components/workflow/types' +import { useDocLink } from '@/context/i18n' export type ICardViewProps = { appId: string @@ -33,6 +34,7 @@ export type ICardViewProps = { const CardView: FC = ({ appId, isInPanel, className }) => { const { t } = useTranslation() + const docLink = useDocLink() const { notify } = useContext(ToastContext) const appDetail = useAppStore(state => state.appDetail) const setAppDetail = useAppStore(state => state.setAppDetail) @@ -53,6 +55,35 @@ const CardView: FC = ({ appId, isInPanel, className }) => { }) }, [isWorkflowApp, currentWorkflow]) const shouldRenderAppCards = !isWorkflowApp || hasTriggerNode === false + const disableAppCards = !shouldRenderAppCards + + const triggerDocUrl = docLink('/guides/workflow/node/start') + const buildTriggerModeMessage = useCallback((featureName: string) => ( +
+
+ {t('appOverview.overview.disableTooltip.triggerMode', { feature: featureName })} +
+
{ + event.stopPropagation() + window.open(triggerDocUrl, '_blank') + }} + > + {t('appOverview.overview.appInfo.enableTooltip.learnMore')} +
+
+ ), [t, triggerDocUrl]) + + const disableWebAppTooltip = disableAppCards + ? buildTriggerModeMessage(t('appOverview.overview.appInfo.title')) + : null + const disableApiTooltip = disableAppCards + ? buildTriggerModeMessage(t('appOverview.overview.apiInfo.title')) + : null + const disableMcpTooltip = disableAppCards + ? buildTriggerModeMessage(t('tools.mcp.server.title')) + : null const updateAppDetail = async () => { try { @@ -124,39 +155,48 @@ const CardView: FC = ({ appId, isInPanel, className }) => { if (!appDetail) return - return ( -
- { - shouldRenderAppCards && ( - <> - - - {showMCPCard && ( - - )} - - ) - } - {showTriggerCard && ( - + + + {showMCPCard && ( + )} + + ) + + const triggerCardNode = showTriggerCard ? ( + + ) : null + + return ( +
+ {disableAppCards && triggerCardNode} + {appCards} + {!disableAppCards && triggerCardNode}
) } diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx index dcb6ae6b4d..a0f5780b71 100644 --- a/web/app/components/app/overview/app-card.tsx +++ b/web/app/components/app/overview/app-card.tsx @@ -51,6 +51,8 @@ export type IAppCardProps = { isInPanel?: boolean cardType?: 'api' | 'webapp' customBgColor?: string + triggerModeDisabled?: boolean // true when Trigger Node mode needs UI locked to avoid conflicting actions + triggerModeMessage?: React.ReactNode // contextual copy explaining why the card is disabled in trigger mode onChangeStatus: (val: boolean) => Promise onSaveSiteConfig?: (params: ConfigParams) => Promise onGenerateCode?: () => Promise @@ -61,6 +63,8 @@ function AppCard({ isInPanel, cardType = 'webapp', customBgColor, + triggerModeDisabled = false, + triggerModeMessage = '', onChangeStatus, onSaveSiteConfig, onGenerateCode, @@ -111,7 +115,7 @@ function AppCard({ const hasStartNode = currentWorkflow?.graph?.nodes?.some(node => node.data.type === BlockEnum.Start) const missingStartNode = isWorkflowApp && !hasStartNode const hasInsufficientPermissions = isApp ? !isCurrentWorkspaceEditor : !isCurrentWorkspaceManager - const toggleDisabled = hasInsufficientPermissions || appUnpublished || missingStartNode + const toggleDisabled = hasInsufficientPermissions || appUnpublished || missingStartNode || triggerModeDisabled const runningStatus = (appUnpublished || missingStartNode) ? false : (isApp ? appInfo.enable_site : appInfo.enable_api) const isMinimalState = appUnpublished || missingStartNode const { app_base_url, access_token } = appInfo.site ?? {} @@ -189,7 +193,20 @@ function AppCard({ className={ `${isInPanel ? 'border-l-[0.5px] border-t' : 'border-[0.5px] shadow-xs'} w-full max-w-full rounded-xl border-effects-highlight ${className ?? ''} ${isMinimalState ? 'h-12' : ''}`} > -
+
+ {triggerModeDisabled && ( + triggerModeMessage + ? ( + + + + ) + : + )}
-
- {t('appOverview.overview.appInfo.enableTooltip.description')} -
-
window.open(docLink('/guides/workflow/node/user-input'), '_blank')} - > - {t('appOverview.overview.appInfo.enableTooltip.learnMore')} -
- + toggleDisabled ? ( + triggerModeDisabled && triggerModeMessage + ? triggerModeMessage + : (appUnpublished || missingStartNode) ? ( + <> +
+ {t('appOverview.overview.appInfo.enableTooltip.description')} +
+
window.open(docLink('/guides/workflow/node/user-input'), '_blank')} + > + {t('appOverview.overview.appInfo.enableTooltip.learnMore')} +
+ + ) + : '' ) : '' } position="right" @@ -329,9 +351,11 @@ function AppCard({ {!isApp && } {OPERATIONS_MAP[cardType].map((op) => { const disabled - = op.opName === t('appOverview.overview.appInfo.settings.entry') - ? false - : !runningStatus + = triggerModeDisabled + ? true + : op.opName === t('appOverview.overview.appInfo.settings.entry') + ? false + : !runningStatus return (