mirror of
https://github.com/langgenius/dify.git
synced 2026-02-20 01:01:50 -05:00
feat: Unify sandbox detection and apply Agent icon override
This commit is contained in:
@@ -4,6 +4,7 @@ import type { DocPathWithoutLang } from '@/types/doc-paths'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node'
|
||||
import AnswerDefault from '@/app/components/workflow/nodes/answer/default'
|
||||
import EndDefault from '@/app/components/workflow/nodes/end/default'
|
||||
@@ -18,7 +19,9 @@ import { useIsChatMode } from './use-is-chat-mode'
|
||||
export const useAvailableNodesMetaData = () => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const isSandboxed = useAppStore(s => s.appDetail?.runtime_type === 'sandboxed')
|
||||
const isSandboxFeatureEnabled = useFeatures(s => s.features.sandbox?.enabled) ?? false
|
||||
const isSandboxRuntime = useAppStore(s => s.appDetail?.runtime_type === 'sandboxed')
|
||||
const isSandboxed = isSandboxFeatureEnabled || isSandboxRuntime
|
||||
const docLink = useDocLink()
|
||||
|
||||
const startNodeMetaData = useMemo(() => ({
|
||||
@@ -76,10 +79,14 @@ export const useAvailableNodesMetaData = () => {
|
||||
const title = isSandboxed && metaData.type === BlockEnum.LLM
|
||||
? t('blocks.agent', { ns: 'workflow' })
|
||||
: t(`blocks.${metaData.type}` as const, { ns: 'workflow' })
|
||||
const iconTypeOverride = isSandboxed && metaData.type === BlockEnum.LLM
|
||||
? BlockEnum.Agent
|
||||
: undefined
|
||||
const description = t(`blocksAbout.${metaData.type}`, { ns: 'workflow' })
|
||||
const helpLinkPath = `/use-dify/nodes/${metaData.helpLinkUri}` as DocPathWithoutLang
|
||||
return toNodeDefaultBase(typedNode, {
|
||||
...metaData,
|
||||
iconType: iconTypeOverride,
|
||||
title,
|
||||
description,
|
||||
helpLinkUri: docLink(helpLinkPath),
|
||||
@@ -87,6 +94,7 @@ export const useAvailableNodesMetaData = () => {
|
||||
...typedNode.defaultValue,
|
||||
type: metaData.type,
|
||||
title,
|
||||
_iconTypeOverride: iconTypeOverride,
|
||||
})
|
||||
})
|
||||
}, [mergedNodesMetaData, t, docLink, isSandboxed])
|
||||
|
||||
@@ -8,12 +8,20 @@ import {
|
||||
import answerDefault from '@/app/components/workflow/nodes/answer/default'
|
||||
import llmDefault from '@/app/components/workflow/nodes/llm/default'
|
||||
import startDefault from '@/app/components/workflow/nodes/start/default'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||
import { STORAGE_KEYS } from '@/config/storage-keys'
|
||||
import { storage } from '@/utils/storage'
|
||||
import { useIsChatMode } from './use-is-chat-mode'
|
||||
|
||||
export const useWorkflowTemplate = () => {
|
||||
const isChatMode = useIsChatMode()
|
||||
const isSandboxed = useAppStore(s => s.appDetail?.runtime_type === 'sandboxed')
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const isSandboxedByType = appDetail?.runtime_type === 'sandboxed'
|
||||
const isSandboxedBySelection = appDetail?.id
|
||||
? storage.getBoolean(`${STORAGE_KEYS.LOCAL.WORKFLOW.SANDBOX_RUNTIME_PREFIX}${appDetail.id}`) === true
|
||||
: false
|
||||
const isSandboxed = isSandboxedByType || isSandboxedBySelection
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { newNode: startNode } = generateNewNode({
|
||||
@@ -39,6 +47,7 @@ export const useWorkflowTemplate = () => {
|
||||
query_prompt_template: '{{#sys.query#}}\n\n{{#sys.files#}}',
|
||||
},
|
||||
selected: true,
|
||||
_iconTypeOverride: isSandboxed ? BlockEnum.Agent : undefined,
|
||||
type: llmDefault.metaData.type,
|
||||
title: llmTitle,
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ import OnlineUsers from '@/app/components/workflow/header/online-users'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status'
|
||||
import {
|
||||
BlockEnum,
|
||||
SupportUploadFileTypes,
|
||||
ViewType,
|
||||
} from '@/app/components/workflow/types'
|
||||
@@ -221,14 +222,32 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
}
|
||||
}, [workflowStore])
|
||||
|
||||
const isSandboxRuntime = appDetail?.runtime_type === 'sandboxed'
|
||||
const isSandboxFeatureEnabled = data?.features?.sandbox?.enabled === true
|
||||
const isSandboxed = isSandboxRuntime || isSandboxFeatureEnabled
|
||||
|
||||
const nodesData = useMemo(() => {
|
||||
if (data) {
|
||||
const processedNodes = initialNodes(data.graph.nodes, data.graph.edges)
|
||||
collaborationManager.setNodes([], processedNodes)
|
||||
return processedNodes
|
||||
const resolvedNodes = isSandboxed
|
||||
? processedNodes.map((node) => {
|
||||
if (node.data.type !== BlockEnum.LLM)
|
||||
return node
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
_iconTypeOverride: BlockEnum.Agent,
|
||||
},
|
||||
}
|
||||
})
|
||||
: processedNodes
|
||||
collaborationManager.setNodes([], resolvedNodes)
|
||||
return resolvedNodes
|
||||
}
|
||||
return []
|
||||
}, [data])
|
||||
}, [data, isSandboxed])
|
||||
|
||||
const edgesData = useMemo(() => {
|
||||
if (data) {
|
||||
@@ -304,7 +323,7 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
}, [replayRunId, workflowStore, getWorkflowRunAndTraceUrl])
|
||||
|
||||
const isDataReady = !(!data || isLoading || isLoadingCurrentWorkspace || !currentWorkspace.id)
|
||||
const sandboxEnabled = data?.features?.sandbox?.enabled === true
|
||||
const sandboxEnabled = isSandboxFeatureEnabled
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDataReady || !appId)
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useSyncExternalStore,
|
||||
} from 'react'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { Folder as FolderLine } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import {
|
||||
Agent,
|
||||
@@ -28,7 +35,9 @@ import {
|
||||
WebhookLine,
|
||||
WindowCursor,
|
||||
} from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { STORAGE_KEYS } from '@/config/storage-keys'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { storage } from '@/utils/storage'
|
||||
import { BlockEnum } from './types'
|
||||
|
||||
type BlockIconProps = {
|
||||
@@ -114,13 +123,45 @@ const ICON_CONTAINER_BG_COLOR_MAP: Record<string, string> = {
|
||||
[BlockEnum.TriggerWebhook]: 'bg-util-colors-blue-blue-500',
|
||||
[BlockEnum.TriggerPlugin]: 'bg-util-colors-blue-blue-500',
|
||||
}
|
||||
|
||||
const useDisplayBlockType = (type: BlockEnum) => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const featuresStore = useFeaturesStore()
|
||||
|
||||
const subscribe = useCallback((listener: () => void) => {
|
||||
if (!featuresStore)
|
||||
return () => {}
|
||||
return featuresStore.subscribe(listener)
|
||||
}, [featuresStore])
|
||||
|
||||
const getSnapshot = useCallback(() => {
|
||||
if (!featuresStore)
|
||||
return false
|
||||
return featuresStore.getState().features.sandbox?.enabled ?? false
|
||||
}, [featuresStore])
|
||||
|
||||
const isSandboxFeatureEnabled = useSyncExternalStore(subscribe, getSnapshot, () => false)
|
||||
const isSandboxRuntime = appDetail?.runtime_type === 'sandboxed'
|
||||
const isSandboxSelection = useMemo(() => {
|
||||
if (!appDetail?.id)
|
||||
return false
|
||||
return storage.getBoolean(`${STORAGE_KEYS.LOCAL.WORKFLOW.SANDBOX_RUNTIME_PREFIX}${appDetail.id}`) === true
|
||||
}, [appDetail?.id])
|
||||
|
||||
const isSandboxed = isSandboxRuntime || isSandboxFeatureEnabled || isSandboxSelection
|
||||
return isSandboxed && type === BlockEnum.LLM
|
||||
? BlockEnum.Agent
|
||||
: type
|
||||
}
|
||||
|
||||
const BlockIcon: FC<BlockIconProps> = ({
|
||||
type,
|
||||
size = 'sm',
|
||||
className,
|
||||
toolIcon,
|
||||
}) => {
|
||||
const isToolOrDataSourceOrTriggerPlugin = type === BlockEnum.Tool || type === BlockEnum.DataSource || type === BlockEnum.TriggerPlugin
|
||||
const displayType = useDisplayBlockType(type)
|
||||
const isToolOrDataSourceOrTriggerPlugin = displayType === BlockEnum.Tool || displayType === BlockEnum.DataSource || displayType === BlockEnum.TriggerPlugin
|
||||
const showDefaultIcon = !isToolOrDataSourceOrTriggerPlugin || !toolIcon
|
||||
|
||||
return (
|
||||
@@ -128,7 +169,7 @@ const BlockIcon: FC<BlockIconProps> = ({
|
||||
cn(
|
||||
'flex items-center justify-center border-[0.5px] border-white/2 text-white',
|
||||
ICON_CONTAINER_CLASSNAME_SIZE_MAP[size],
|
||||
showDefaultIcon && ICON_CONTAINER_BG_COLOR_MAP[type],
|
||||
showDefaultIcon && ICON_CONTAINER_BG_COLOR_MAP[displayType],
|
||||
toolIcon && '!shadow-none',
|
||||
className,
|
||||
)
|
||||
@@ -136,7 +177,7 @@ const BlockIcon: FC<BlockIconProps> = ({
|
||||
>
|
||||
{
|
||||
showDefaultIcon && (
|
||||
getIcon(type, (type === BlockEnum.TriggerSchedule || type === BlockEnum.TriggerWebhook)
|
||||
getIcon(displayType, (displayType === BlockEnum.TriggerSchedule || displayType === BlockEnum.TriggerWebhook)
|
||||
? (size === 'xs' ? 'w-4 h-4' : 'w-4.5 h-4.5')
|
||||
: (size === 'xs' ? 'w-3 h-3' : 'w-3.5 h-3.5'))
|
||||
)
|
||||
@@ -175,9 +216,11 @@ export const VarBlockIcon: FC<BlockIconProps> = ({
|
||||
type,
|
||||
className,
|
||||
}) => {
|
||||
const displayType = useDisplayBlockType(type)
|
||||
|
||||
return (
|
||||
<>
|
||||
{getIcon(type, `w-3 h-3 ${className}`)}
|
||||
{getIcon(displayType, `w-3 h-3 ${className}`)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ const Blocks = ({
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={block.metaData.type}
|
||||
type={block.metaData.iconType || block.metaData.type}
|
||||
/>
|
||||
<div className="system-md-medium mb-1 text-text-primary">{block.metaData.title}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{block.metaData.description}</div>
|
||||
@@ -117,7 +117,7 @@ const Blocks = ({
|
||||
>
|
||||
<BlockIcon
|
||||
className="mr-2 shrink-0"
|
||||
type={block.metaData.type}
|
||||
type={block.metaData.iconType || block.metaData.type}
|
||||
/>
|
||||
<div className="grow text-sm text-text-secondary">{block.metaData.title}</div>
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ const NextStep = ({
|
||||
<div className="flex py-1">
|
||||
<div className="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default shadow-xs">
|
||||
<BlockIcon
|
||||
type={selectedNode!.data.type}
|
||||
type={selectedNode!.data._iconTypeOverride ?? selectedNode!.data.type}
|
||||
toolIcon={toolIcon}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@ const Item = ({
|
||||
className="group relative flex h-9 cursor-pointer items-center rounded-lg border-[0.5px] border-divider-regular bg-background-default px-2 text-xs text-text-secondary shadow-xs last-of-type:mb-0 hover:bg-background-default-hover"
|
||||
>
|
||||
<BlockIcon
|
||||
type={data.type}
|
||||
type={data._iconTypeOverride ?? data.type}
|
||||
toolIcon={toolIcon}
|
||||
className="mr-1.5 shrink-0"
|
||||
/>
|
||||
|
||||
@@ -527,7 +527,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
<div className="flex items-center px-4 pb-1 pt-4">
|
||||
<BlockIcon
|
||||
className="mr-1 shrink-0"
|
||||
type={data.type}
|
||||
type={data._iconTypeOverride ?? data.type}
|
||||
toolIcon={toolIcon}
|
||||
size="md"
|
||||
/>
|
||||
|
||||
@@ -69,11 +69,8 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const nodeRef = useRef<HTMLDivElement>(null)
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { _subGraphEntry, _iconTypeOverride } = data as {
|
||||
_subGraphEntry?: boolean
|
||||
_iconTypeOverride?: BlockEnum
|
||||
}
|
||||
const iconType = _iconTypeOverride ?? data.type
|
||||
const { _subGraphEntry } = data
|
||||
const iconType = data._iconTypeOverride ?? data.type
|
||||
|
||||
const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
|
||||
const { handleNodeLoopChildSizeChange } = useNodeLoopInteractions()
|
||||
|
||||
@@ -91,6 +91,8 @@ export type CommonNodeType<T = {}> = {
|
||||
_retryIndex?: number
|
||||
_dataSourceStartToAdd?: boolean
|
||||
_isTempNode?: boolean
|
||||
_subGraphEntry?: boolean
|
||||
_iconTypeOverride?: BlockEnum
|
||||
isInIteration?: boolean
|
||||
iteration_id?: string
|
||||
selected?: boolean
|
||||
@@ -369,6 +371,7 @@ export type NodeDefaultBase = {
|
||||
classification: BlockClassificationEnum
|
||||
sort: number
|
||||
type: BlockEnum
|
||||
iconType?: BlockEnum
|
||||
title: string
|
||||
author: string
|
||||
description?: string
|
||||
|
||||
Reference in New Issue
Block a user