mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
Merge remote-tracking branch 'origin/main' into feat/trigger-saas
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import AppCard from '@/app/components/app/overview/app-card'
|
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 { useAppWorkflow } from '@/service/use-workflow'
|
||||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||||
import { isTriggerNode } from '@/app/components/workflow/types'
|
import { isTriggerNode } from '@/app/components/workflow/types'
|
||||||
|
import { useDocLink } from '@/context/i18n'
|
||||||
|
|
||||||
export type ICardViewProps = {
|
export type ICardViewProps = {
|
||||||
appId: string
|
appId: string
|
||||||
@@ -33,6 +34,7 @@ export type ICardViewProps = {
|
|||||||
|
|
||||||
const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const docLink = useDocLink()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const appDetail = useAppStore(state => state.appDetail)
|
const appDetail = useAppStore(state => state.appDetail)
|
||||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||||
@@ -53,6 +55,35 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
|||||||
})
|
})
|
||||||
}, [isWorkflowApp, currentWorkflow])
|
}, [isWorkflowApp, currentWorkflow])
|
||||||
const shouldRenderAppCards = !isWorkflowApp || hasTriggerNode === false
|
const shouldRenderAppCards = !isWorkflowApp || hasTriggerNode === false
|
||||||
|
const disableAppCards = !shouldRenderAppCards
|
||||||
|
|
||||||
|
const triggerDocUrl = docLink('/guides/workflow/node/start')
|
||||||
|
const buildTriggerModeMessage = useCallback((featureName: string) => (
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<div className='text-xs text-text-secondary'>
|
||||||
|
{t('appOverview.overview.disableTooltip.triggerMode', { feature: featureName })}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='cursor-pointer text-xs font-medium text-text-accent hover:underline'
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
window.open(triggerDocUrl, '_blank')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
), [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 () => {
|
const updateAppDetail = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -124,39 +155,48 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
|||||||
if (!appDetail)
|
if (!appDetail)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
|
|
||||||
return (
|
const appCards = (
|
||||||
<div className={className || 'mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'}>
|
<>
|
||||||
{
|
<AppCard
|
||||||
shouldRenderAppCards && (
|
appInfo={appDetail}
|
||||||
<>
|
cardType="webapp"
|
||||||
<AppCard
|
isInPanel={isInPanel}
|
||||||
appInfo={appDetail}
|
triggerModeDisabled={disableAppCards}
|
||||||
cardType="webapp"
|
triggerModeMessage={disableWebAppTooltip}
|
||||||
isInPanel={isInPanel}
|
onChangeStatus={onChangeSiteStatus}
|
||||||
onChangeStatus={onChangeSiteStatus}
|
onGenerateCode={onGenerateCode}
|
||||||
onGenerateCode={onGenerateCode}
|
onSaveSiteConfig={onSaveSiteConfig}
|
||||||
onSaveSiteConfig={onSaveSiteConfig}
|
/>
|
||||||
/>
|
<AppCard
|
||||||
<AppCard
|
cardType="api"
|
||||||
cardType="api"
|
appInfo={appDetail}
|
||||||
appInfo={appDetail}
|
isInPanel={isInPanel}
|
||||||
isInPanel={isInPanel}
|
triggerModeDisabled={disableAppCards}
|
||||||
onChangeStatus={onChangeApiStatus}
|
triggerModeMessage={disableApiTooltip}
|
||||||
/>
|
onChangeStatus={onChangeApiStatus}
|
||||||
{showMCPCard && (
|
/>
|
||||||
<MCPServiceCard
|
{showMCPCard && (
|
||||||
appInfo={appDetail}
|
<MCPServiceCard
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{showTriggerCard && (
|
|
||||||
<TriggerCard
|
|
||||||
appInfo={appDetail}
|
appInfo={appDetail}
|
||||||
onToggleResult={handleCallbackResult}
|
triggerModeDisabled={disableAppCards}
|
||||||
|
triggerModeMessage={disableMcpTooltip}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const triggerCardNode = showTriggerCard ? (
|
||||||
|
<TriggerCard
|
||||||
|
appInfo={appDetail}
|
||||||
|
onToggleResult={handleCallbackResult}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className || 'mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'}>
|
||||||
|
{disableAppCards && triggerCardNode}
|
||||||
|
{appCards}
|
||||||
|
{!disableAppCards && triggerCardNode}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export type IAppCardProps = {
|
|||||||
isInPanel?: boolean
|
isInPanel?: boolean
|
||||||
cardType?: 'api' | 'webapp'
|
cardType?: 'api' | 'webapp'
|
||||||
customBgColor?: string
|
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<void>
|
onChangeStatus: (val: boolean) => Promise<void>
|
||||||
onSaveSiteConfig?: (params: ConfigParams) => Promise<void>
|
onSaveSiteConfig?: (params: ConfigParams) => Promise<void>
|
||||||
onGenerateCode?: () => Promise<void>
|
onGenerateCode?: () => Promise<void>
|
||||||
@@ -61,6 +63,8 @@ function AppCard({
|
|||||||
isInPanel,
|
isInPanel,
|
||||||
cardType = 'webapp',
|
cardType = 'webapp',
|
||||||
customBgColor,
|
customBgColor,
|
||||||
|
triggerModeDisabled = false,
|
||||||
|
triggerModeMessage = '',
|
||||||
onChangeStatus,
|
onChangeStatus,
|
||||||
onSaveSiteConfig,
|
onSaveSiteConfig,
|
||||||
onGenerateCode,
|
onGenerateCode,
|
||||||
@@ -111,7 +115,7 @@ function AppCard({
|
|||||||
const hasStartNode = currentWorkflow?.graph?.nodes?.some(node => node.data.type === BlockEnum.Start)
|
const hasStartNode = currentWorkflow?.graph?.nodes?.some(node => node.data.type === BlockEnum.Start)
|
||||||
const missingStartNode = isWorkflowApp && !hasStartNode
|
const missingStartNode = isWorkflowApp && !hasStartNode
|
||||||
const hasInsufficientPermissions = isApp ? !isCurrentWorkspaceEditor : !isCurrentWorkspaceManager
|
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 runningStatus = (appUnpublished || missingStartNode) ? false : (isApp ? appInfo.enable_site : appInfo.enable_api)
|
||||||
const isMinimalState = appUnpublished || missingStartNode
|
const isMinimalState = appUnpublished || missingStartNode
|
||||||
const { app_base_url, access_token } = appInfo.site ?? {}
|
const { app_base_url, access_token } = appInfo.site ?? {}
|
||||||
@@ -189,7 +193,20 @@ function AppCard({
|
|||||||
className={
|
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' : ''}`}
|
`${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' : ''}`}
|
||||||
>
|
>
|
||||||
<div className={`${customBgColor ?? 'bg-background-default'} rounded-xl`}>
|
<div className={`${customBgColor ?? 'bg-background-default'} relative rounded-xl ${triggerModeDisabled ? 'opacity-60' : ''}`}>
|
||||||
|
{triggerModeDisabled && (
|
||||||
|
triggerModeMessage
|
||||||
|
? (
|
||||||
|
<Tooltip
|
||||||
|
popupContent={triggerModeMessage}
|
||||||
|
popupClassName="max-w-64 rounded-xl bg-components-panel-bg px-3 py-2 text-xs text-text-secondary shadow-lg"
|
||||||
|
position="right"
|
||||||
|
>
|
||||||
|
<div className='absolute inset-0 z-10 cursor-not-allowed rounded-xl' aria-hidden="true"></div>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
: <div className='absolute inset-0 z-10 cursor-not-allowed rounded-xl' aria-hidden="true"></div>
|
||||||
|
)}
|
||||||
<div className={`flex w-full flex-col items-start justify-center gap-3 self-stretch p-3 ${isMinimalState ? 'border-0' : 'border-b-[0.5px] border-divider-subtle'}`}>
|
<div className={`flex w-full flex-col items-start justify-center gap-3 self-stretch p-3 ${isMinimalState ? 'border-0' : 'border-b-[0.5px] border-divider-subtle'}`}>
|
||||||
<div className='flex w-full items-center gap-3 self-stretch'>
|
<div className='flex w-full items-center gap-3 self-stretch'>
|
||||||
<AppBasic
|
<AppBasic
|
||||||
@@ -214,18 +231,23 @@ function AppCard({
|
|||||||
</div>
|
</div>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
popupContent={
|
popupContent={
|
||||||
toggleDisabled && (appUnpublished || missingStartNode) ? (
|
toggleDisabled ? (
|
||||||
<>
|
triggerModeDisabled && triggerModeMessage
|
||||||
<div className="mb-1 text-xs font-normal text-text-secondary">
|
? triggerModeMessage
|
||||||
{t('appOverview.overview.appInfo.enableTooltip.description')}
|
: (appUnpublished || missingStartNode) ? (
|
||||||
</div>
|
<>
|
||||||
<div
|
<div className="mb-1 text-xs font-normal text-text-secondary">
|
||||||
className="cursor-pointer text-xs font-normal text-text-accent hover:underline"
|
{t('appOverview.overview.appInfo.enableTooltip.description')}
|
||||||
onClick={() => window.open(docLink('/guides/workflow/node/user-input'), '_blank')}
|
</div>
|
||||||
>
|
<div
|
||||||
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
|
className="cursor-pointer text-xs font-normal text-text-accent hover:underline"
|
||||||
</div>
|
onClick={() => window.open(docLink('/guides/workflow/node/user-input'), '_blank')}
|
||||||
</>
|
>
|
||||||
|
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: ''
|
||||||
) : ''
|
) : ''
|
||||||
}
|
}
|
||||||
position="right"
|
position="right"
|
||||||
@@ -329,9 +351,11 @@ function AppCard({
|
|||||||
{!isApp && <SecretKeyButton appId={appInfo.id} />}
|
{!isApp && <SecretKeyButton appId={appInfo.id} />}
|
||||||
{OPERATIONS_MAP[cardType].map((op) => {
|
{OPERATIONS_MAP[cardType].map((op) => {
|
||||||
const disabled
|
const disabled
|
||||||
= op.opName === t('appOverview.overview.appInfo.settings.entry')
|
= triggerModeDisabled
|
||||||
? false
|
? true
|
||||||
: !runningStatus
|
: op.opName === t('appOverview.overview.appInfo.settings.entry')
|
||||||
|
? false
|
||||||
|
: !runningStatus
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="mr-1 min-w-[88px]"
|
className="mr-1 min-w-[88px]"
|
||||||
|
|||||||
@@ -30,10 +30,14 @@ import { useDocLink } from '@/context/i18n'
|
|||||||
|
|
||||||
export type IAppCardProps = {
|
export type IAppCardProps = {
|
||||||
appInfo: AppDetailResponse & Partial<AppSSO>
|
appInfo: AppDetailResponse & Partial<AppSSO>
|
||||||
|
triggerModeDisabled?: boolean // align with Trigger Node vs User Input exclusivity
|
||||||
|
triggerModeMessage?: React.ReactNode // display-only message explaining the trigger restriction
|
||||||
}
|
}
|
||||||
|
|
||||||
function MCPServiceCard({
|
function MCPServiceCard({
|
||||||
appInfo,
|
appInfo,
|
||||||
|
triggerModeDisabled = false,
|
||||||
|
triggerModeMessage = '',
|
||||||
}: IAppCardProps) {
|
}: IAppCardProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const docLink = useDocLink()
|
const docLink = useDocLink()
|
||||||
@@ -79,7 +83,7 @@ function MCPServiceCard({
|
|||||||
const hasStartNode = currentWorkflow?.graph?.nodes?.some(node => node.data.type === BlockEnum.Start)
|
const hasStartNode = currentWorkflow?.graph?.nodes?.some(node => node.data.type === BlockEnum.Start)
|
||||||
const missingStartNode = isWorkflowApp && !hasStartNode
|
const missingStartNode = isWorkflowApp && !hasStartNode
|
||||||
const hasInsufficientPermissions = !isCurrentWorkspaceEditor
|
const hasInsufficientPermissions = !isCurrentWorkspaceEditor
|
||||||
const toggleDisabled = hasInsufficientPermissions || appUnpublished || missingStartNode
|
const toggleDisabled = hasInsufficientPermissions || appUnpublished || missingStartNode || triggerModeDisabled
|
||||||
const isMinimalState = appUnpublished || missingStartNode
|
const isMinimalState = appUnpublished || missingStartNode
|
||||||
|
|
||||||
const [activated, setActivated] = useState(serverActivated)
|
const [activated, setActivated] = useState(serverActivated)
|
||||||
@@ -144,7 +148,18 @@ function MCPServiceCard({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={cn('w-full max-w-full rounded-xl border-l-[0.5px] border-t border-effects-highlight', isMinimalState && 'h-12')}>
|
<div className={cn('w-full max-w-full rounded-xl border-l-[0.5px] border-t border-effects-highlight', isMinimalState && 'h-12')}>
|
||||||
<div className='rounded-xl bg-background-default'>
|
<div className={cn('relative rounded-xl bg-background-default', triggerModeDisabled && 'opacity-60')}>
|
||||||
|
{triggerModeDisabled && (
|
||||||
|
triggerModeMessage ? (
|
||||||
|
<Tooltip
|
||||||
|
popupContent={triggerModeMessage}
|
||||||
|
popupClassName="max-w-64 rounded-xl bg-components-panel-bg px-3 py-2 text-xs text-text-secondary shadow-lg"
|
||||||
|
position="right"
|
||||||
|
>
|
||||||
|
<div className='absolute inset-0 z-10 cursor-not-allowed rounded-xl' aria-hidden="true"></div>
|
||||||
|
</Tooltip>
|
||||||
|
) : <div className='absolute inset-0 z-10 cursor-not-allowed rounded-xl' aria-hidden="true"></div>
|
||||||
|
)}
|
||||||
<div className={cn('flex w-full flex-col items-start justify-center gap-3 self-stretch p-3', isMinimalState ? 'border-0' : 'border-b-[0.5px] border-divider-subtle')}>
|
<div className={cn('flex w-full flex-col items-start justify-center gap-3 self-stretch p-3', isMinimalState ? 'border-0' : 'border-b-[0.5px] border-divider-subtle')}>
|
||||||
<div className='flex w-full items-center gap-3 self-stretch'>
|
<div className='flex w-full items-center gap-3 self-stretch'>
|
||||||
<div className='flex grow items-center'>
|
<div className='flex grow items-center'>
|
||||||
@@ -182,7 +197,7 @@ function MCPServiceCard({
|
|||||||
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
|
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : ''
|
) : triggerModeMessage || ''
|
||||||
) : ''
|
) : ''
|
||||||
}
|
}
|
||||||
position="right"
|
position="right"
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ const translation = {
|
|||||||
running: 'In Service',
|
running: 'In Service',
|
||||||
disable: 'Disabled',
|
disable: 'Disabled',
|
||||||
},
|
},
|
||||||
|
disableTooltip: {
|
||||||
|
triggerMode: 'The {{feature}} feature is not supported in Trigger Node mode.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
analysis: {
|
analysis: {
|
||||||
title: 'Analysis',
|
title: 'Analysis',
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ const translation = {
|
|||||||
running: '稼働中',
|
running: '稼働中',
|
||||||
disable: '無効',
|
disable: '無効',
|
||||||
},
|
},
|
||||||
|
disableTooltip: {
|
||||||
|
triggerMode: 'トリガーノードモードでは{{feature}}機能を使用できません。',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
analysis: {
|
analysis: {
|
||||||
title: '分析',
|
title: '分析',
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ const translation = {
|
|||||||
running: '运行中',
|
running: '运行中',
|
||||||
disable: '已停用',
|
disable: '已停用',
|
||||||
},
|
},
|
||||||
|
disableTooltip: {
|
||||||
|
triggerMode: '触发节点模式下不支持{{feature}}功能。',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
analysis: {
|
analysis: {
|
||||||
title: '分析',
|
title: '分析',
|
||||||
|
|||||||
Reference in New Issue
Block a user