mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 08:57:28 +08:00
Merge branch 'zhsama/llm-warning-ui' into feat/pull-a-variable
This commit is contained in:
commit
e85e31773a
@ -2,25 +2,36 @@ import type { FC } from 'react'
|
|||||||
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
|
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle'
|
||||||
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
||||||
|
import { cn } from '@/utils/classnames'
|
||||||
|
|
||||||
type AgentHeaderBarProps = {
|
type AgentHeaderBarProps = {
|
||||||
agentName: string
|
agentName: string
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
onViewInternals?: () => void
|
onViewInternals?: () => void
|
||||||
|
hasWarning?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AgentHeaderBar: FC<AgentHeaderBarProps> = ({
|
const AgentHeaderBar: FC<AgentHeaderBarProps> = ({
|
||||||
agentName,
|
agentName,
|
||||||
onRemove,
|
onRemove,
|
||||||
onViewInternals,
|
onViewInternals,
|
||||||
|
hasWarning,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between px-2 py-1">
|
<div className="flex items-center justify-between px-2 py-1">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="flex items-center gap-1 rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 py-0.5 shadow-xs">
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-1 rounded-md border-[0.5px] px-1.5 py-0.5 shadow-xs',
|
||||||
|
hasWarning
|
||||||
|
? 'border-text-warning-secondary bg-components-badge-status-light-warning-halo'
|
||||||
|
: 'border-components-panel-border-subtle bg-components-badge-white-to-dark',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex h-4 w-4 items-center justify-center rounded bg-util-colors-indigo-indigo-500">
|
<div className="flex h-4 w-4 items-center justify-center rounded bg-util-colors-indigo-indigo-500">
|
||||||
<Agent className="h-3 w-3 text-text-primary-on-surface" />
|
<Agent className="h-3 w-3 text-text-primary-on-surface" />
|
||||||
</div>
|
</div>
|
||||||
@ -39,11 +50,14 @@ const AgentHeaderBar: FC<AgentHeaderBarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center gap-0.5 text-text-tertiary hover:text-text-secondary"
|
className="flex items-center gap-1 text-text-tertiary hover:text-text-secondary"
|
||||||
onClick={onViewInternals}
|
onClick={onViewInternals}
|
||||||
>
|
>
|
||||||
<RiEqualizer2Line className="h-3.5 w-3.5" />
|
<RiEqualizer2Line className="h-3.5 w-3.5" />
|
||||||
<span className="system-xs-medium">{t('common.viewInternals', { ns: 'workflow' })}</span>
|
<span className="system-xs-medium">{t('common.viewInternals', { ns: 'workflow' })}</span>
|
||||||
|
{hasWarning && (
|
||||||
|
<AlertTriangle className="h-3.5 w-3.5 text-text-warning-secondary" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import type { AgentNode } from '@/app/components/base/prompt-editor/types'
|
import type { AgentNode, WorkflowVariableBlockType } from '@/app/components/base/prompt-editor/types'
|
||||||
|
import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types'
|
||||||
import type { MentionConfig, VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
import type { MentionConfig, VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||||
|
import type { AgentNodeType } from '@/app/components/workflow/nodes/agent/types'
|
||||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||||
import type {
|
import type {
|
||||||
Node,
|
CommonNodeType,
|
||||||
NodeOutPutVar,
|
NodeOutPutVar,
|
||||||
PromptItem,
|
PromptItem,
|
||||||
PromptTemplateItem,
|
PromptTemplateItem,
|
||||||
ValueSelector,
|
ValueSelector,
|
||||||
|
Node as WorkflowNode,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
@ -15,7 +18,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useNodes, useStoreApi } from 'reactflow'
|
||||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||||
import { useNodesMetaData, useNodesSyncDraft } from '@/app/components/workflow/hooks'
|
import { useNodesMetaData, useNodesSyncDraft } from '@/app/components/workflow/hooks'
|
||||||
import { VarKindType as VarKindTypeEnum } from '@/app/components/workflow/nodes/_base/types'
|
import { VarKindType as VarKindTypeEnum } from '@/app/components/workflow/nodes/_base/types'
|
||||||
@ -23,6 +26,8 @@ import { Type } from '@/app/components/workflow/nodes/llm/types'
|
|||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole } from '@/app/components/workflow/types'
|
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole } from '@/app/components/workflow/types'
|
||||||
import { generateNewNode, getNodeCustomTypeByNodeDataType } from '@/app/components/workflow/utils'
|
import { generateNewNode, getNodeCustomTypeByNodeDataType } from '@/app/components/workflow/utils'
|
||||||
|
import { useGetLanguage } from '@/context/i18n'
|
||||||
|
import { useStrategyProviders } from '@/service/use-strategy'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
import SubGraphModal from '../sub-graph-modal'
|
import SubGraphModal from '../sub-graph-modal'
|
||||||
import AgentHeaderBar from './agent-header-bar'
|
import AgentHeaderBar from './agent-header-bar'
|
||||||
@ -39,6 +44,14 @@ const DEFAULT_MENTION_CONFIG: MentionConfig = {
|
|||||||
null_strategy: 'use_default',
|
null_strategy: 'use_default',
|
||||||
default_value: '',
|
default_value: '',
|
||||||
}
|
}
|
||||||
|
type AgentCheckValidContext = {
|
||||||
|
provider?: StrategyPluginDetail
|
||||||
|
strategy?: StrategyDetail
|
||||||
|
language: string
|
||||||
|
isReadyForCheckValid: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowNodesMap = NonNullable<WorkflowVariableBlockType['workflowNodesMap']>
|
||||||
|
|
||||||
const resolvePromptText = (item?: PromptItem) => {
|
const resolvePromptText = (item?: PromptItem) => {
|
||||||
if (!item)
|
if (!item)
|
||||||
@ -113,7 +126,7 @@ const buildPromptTemplateWithText = (promptTemplate: PromptTemplateItem[] | Prom
|
|||||||
type MixedVariableTextInputProps = {
|
type MixedVariableTextInputProps = {
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
nodesOutputVars?: NodeOutPutVar[]
|
nodesOutputVars?: NodeOutPutVar[]
|
||||||
availableNodes?: Node[]
|
availableNodes?: WorkflowNode[]
|
||||||
value?: string
|
value?: string
|
||||||
onChange?: (text: string, type?: VarKindType, mentionConfig?: MentionConfig | null) => void
|
onChange?: (text: string, type?: VarKindType, mentionConfig?: MentionConfig | null) => void
|
||||||
showManageInputField?: boolean
|
showManageInputField?: boolean
|
||||||
@ -136,7 +149,10 @@ const MixedVariableTextInput = ({
|
|||||||
paramKey = '',
|
paramKey = '',
|
||||||
}: MixedVariableTextInputProps) => {
|
}: MixedVariableTextInputProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const language = useGetLanguage()
|
||||||
|
const { data: strategyProviders } = useStrategyProviders()
|
||||||
const reactFlowStore = useStoreApi()
|
const reactFlowStore = useStoreApi()
|
||||||
|
const nodes = useNodes<CommonNodeType>()
|
||||||
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
|
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
|
||||||
const setControlPromptEditorRerenderKey = useStore(s => s.setControlPromptEditorRerenderKey)
|
const setControlPromptEditorRerenderKey = useStore(s => s.setControlPromptEditorRerenderKey)
|
||||||
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
|
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
|
||||||
@ -147,7 +163,7 @@ const MixedVariableTextInput = ({
|
|||||||
return availableNodes.reduce((acc, node) => {
|
return availableNodes.reduce((acc, node) => {
|
||||||
acc[node.id] = node
|
acc[node.id] = node
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, Node>)
|
}, {} as Record<string, WorkflowNode>)
|
||||||
}, [availableNodes])
|
}, [availableNodes])
|
||||||
|
|
||||||
const contextNodeIds = useMemo(() => {
|
const contextNodeIds = useMemo(() => {
|
||||||
@ -159,6 +175,13 @@ const MixedVariableTextInput = ({
|
|||||||
return ids
|
return ids
|
||||||
}, [availableNodes])
|
}, [availableNodes])
|
||||||
|
|
||||||
|
const nodesById = useMemo(() => {
|
||||||
|
return nodes.reduce((acc, node) => {
|
||||||
|
acc[node.id] = node
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, WorkflowNode>)
|
||||||
|
}, [nodes])
|
||||||
|
|
||||||
type DetectedAgent = {
|
type DetectedAgent = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
name: string
|
name: string
|
||||||
@ -198,6 +221,63 @@ const MixedVariableTextInput = ({
|
|||||||
}))
|
}))
|
||||||
}, [availableNodes, contextNodeIds])
|
}, [availableNodes, contextNodeIds])
|
||||||
|
|
||||||
|
const workflowNodesMap = useMemo<WorkflowNodesMap>(() => {
|
||||||
|
const acc: WorkflowNodesMap = {}
|
||||||
|
availableNodes.forEach((node) => {
|
||||||
|
acc[node.id] = {
|
||||||
|
title: node.data.title,
|
||||||
|
type: node.data.type,
|
||||||
|
height: node.data.height,
|
||||||
|
width: node.data.width,
|
||||||
|
position: node.data.position,
|
||||||
|
}
|
||||||
|
if (node.data.type === BlockEnum.Start) {
|
||||||
|
acc.sys = {
|
||||||
|
title: t('blocks.start', { ns: 'workflow' }),
|
||||||
|
type: BlockEnum.Start,
|
||||||
|
height: node.data.height,
|
||||||
|
width: node.data.width,
|
||||||
|
position: node.data.position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, [availableNodes, t])
|
||||||
|
|
||||||
|
const getNodeWarning = useCallback((node?: WorkflowNode) => {
|
||||||
|
if (!node)
|
||||||
|
return true
|
||||||
|
const validator = nodesMetaDataMap?.[node.data.type as BlockEnum]?.checkValid
|
||||||
|
if (!validator)
|
||||||
|
return false
|
||||||
|
let moreDataForCheckValid: AgentCheckValidContext | undefined
|
||||||
|
if (node.data.type === BlockEnum.Agent) {
|
||||||
|
const agentData = node.data as AgentNodeType
|
||||||
|
const isReadyForCheckValid = !!strategyProviders
|
||||||
|
const provider = strategyProviders?.find(provider => provider.declaration.identity.name === agentData.agent_strategy_provider_name)
|
||||||
|
const strategy = provider?.declaration.strategies?.find(s => s.identity.name === agentData.agent_strategy_name)
|
||||||
|
moreDataForCheckValid = {
|
||||||
|
provider,
|
||||||
|
strategy,
|
||||||
|
language,
|
||||||
|
isReadyForCheckValid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { errorMessage } = validator(node.data, t, moreDataForCheckValid)
|
||||||
|
return Boolean(errorMessage)
|
||||||
|
}, [language, nodesMetaDataMap, strategyProviders, t])
|
||||||
|
|
||||||
|
const hasAgentWarning = useMemo(() => {
|
||||||
|
if (!detectedAgentFromValue)
|
||||||
|
return false
|
||||||
|
const agentWarning = getNodeWarning(nodesById[detectedAgentFromValue.nodeId])
|
||||||
|
if (!toolNodeId || !paramKey)
|
||||||
|
return agentWarning
|
||||||
|
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||||
|
const extractorWarning = getNodeWarning(nodesById[extractorNodeId])
|
||||||
|
return agentWarning || extractorWarning
|
||||||
|
}, [detectedAgentFromValue, getNodeWarning, nodesById, paramKey, toolNodeId])
|
||||||
|
|
||||||
const syncExtractorPromptFromText = useCallback((text: string) => {
|
const syncExtractorPromptFromText = useCallback((text: string) => {
|
||||||
if (!toolNodeId || !paramKey)
|
if (!toolNodeId || !paramKey)
|
||||||
return
|
return
|
||||||
@ -213,7 +293,7 @@ const MixedVariableTextInput = ({
|
|||||||
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||||
const { getNodes, setNodes } = reactFlowStore.getState()
|
const { getNodes, setNodes } = reactFlowStore.getState()
|
||||||
const nodes = getNodes()
|
const nodes = getNodes()
|
||||||
const extractorNode = nodes.find(node => node.id === extractorNodeId) as Node<LLMNodeType> | undefined
|
const extractorNode = nodes.find(node => node.id === extractorNodeId) as WorkflowNode<LLMNodeType> | undefined
|
||||||
if (!extractorNode?.data?.prompt_template)
|
if (!extractorNode?.data?.prompt_template)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -278,7 +358,7 @@ const MixedVariableTextInput = ({
|
|||||||
|
|
||||||
if (toolNodeId && paramKey) {
|
if (toolNodeId && paramKey) {
|
||||||
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||||
const defaultValue = nodesMetaDataMap?.[BlockEnum.LLM]?.defaultValue
|
const defaultValue = nodesMetaDataMap?.[BlockEnum.LLM]?.defaultValue as Partial<LLMNodeType> | undefined
|
||||||
const { getNodes, setNodes } = reactFlowStore.getState()
|
const { getNodes, setNodes } = reactFlowStore.getState()
|
||||||
const nodes = getNodes()
|
const nodes = getNodes()
|
||||||
const hasExtractorNode = nodes.some(node => node.id === extractorNodeId)
|
const hasExtractorNode = nodes.some(node => node.id === extractorNodeId)
|
||||||
@ -288,8 +368,9 @@ const MixedVariableTextInput = ({
|
|||||||
id: extractorNodeId,
|
id: extractorNodeId,
|
||||||
type: getNodeCustomTypeByNodeDataType(BlockEnum.LLM),
|
type: getNodeCustomTypeByNodeDataType(BlockEnum.LLM),
|
||||||
data: {
|
data: {
|
||||||
...(defaultValue as any),
|
...defaultValue,
|
||||||
title: defaultValue.title,
|
type: BlockEnum.LLM,
|
||||||
|
title: defaultValue?.title ?? '',
|
||||||
desc: defaultValue.desc || '',
|
desc: defaultValue.desc || '',
|
||||||
parent_node_id: toolNodeId,
|
parent_node_id: toolNodeId,
|
||||||
structured_output_enabled: true,
|
structured_output_enabled: true,
|
||||||
@ -351,6 +432,7 @@ const MixedVariableTextInput = ({
|
|||||||
agentName={detectedAgentFromValue.name}
|
agentName={detectedAgentFromValue.name}
|
||||||
onRemove={handleAgentRemove}
|
onRemove={handleAgentRemove}
|
||||||
onViewInternals={handleOpenSubGraphModal}
|
onViewInternals={handleOpenSubGraphModal}
|
||||||
|
hasWarning={hasAgentWarning}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PromptEditor
|
<PromptEditor
|
||||||
@ -362,19 +444,7 @@ const MixedVariableTextInput = ({
|
|||||||
workflowVariableBlock={{
|
workflowVariableBlock={{
|
||||||
show: !disableVariableInsertion,
|
show: !disableVariableInsertion,
|
||||||
variables: nodesOutputVars || [],
|
variables: nodesOutputVars || [],
|
||||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
workflowNodesMap,
|
||||||
acc[node.id] = {
|
|
||||||
title: node.data.title,
|
|
||||||
type: node.data.type,
|
|
||||||
}
|
|
||||||
if (node.data.type === BlockEnum.Start) {
|
|
||||||
acc.sys = {
|
|
||||||
title: t('blocks.start', { ns: 'workflow' }),
|
|
||||||
type: BlockEnum.Start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, {} as any),
|
|
||||||
showManageInputField,
|
showManageInputField,
|
||||||
onManageInputField,
|
onManageInputField,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -197,7 +197,12 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
|||||||
{referenceItems.map(item => (
|
{referenceItems.map(item => (
|
||||||
<div
|
<div
|
||||||
key={item.key}
|
key={item.key}
|
||||||
className="flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary"
|
className={cn(
|
||||||
|
'flex h-6 items-center justify-between space-x-1 rounded-md border px-1 text-xs font-normal text-text-secondary',
|
||||||
|
item.hasWarning
|
||||||
|
? 'border-text-warning-secondary bg-components-badge-status-light-warning-halo'
|
||||||
|
: 'border-transparent bg-workflow-block-parma-bg',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 items-center gap-1">
|
<div className="flex min-w-0 items-center gap-1">
|
||||||
<BlockIcon
|
<BlockIcon
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export type CommonNodeType<T = {}> = {
|
|||||||
_isCandidate?: boolean
|
_isCandidate?: boolean
|
||||||
_isBundled?: boolean
|
_isBundled?: boolean
|
||||||
_children?: { nodeId: string, nodeType: BlockEnum }[]
|
_children?: { nodeId: string, nodeType: BlockEnum }[]
|
||||||
|
parent_node_id?: string
|
||||||
_isEntering?: boolean
|
_isEntering?: boolean
|
||||||
_showAddVariablePopup?: boolean
|
_showAddVariablePopup?: boolean
|
||||||
_holdAddVariablePopup?: boolean
|
_holdAddVariablePopup?: boolean
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
BlockEnum,
|
BlockEnum,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
export function generateNewNode({ data, position, id, zIndex, type, ...rest }: Omit<Node, 'id'> & { id?: string }): {
|
export function generateNewNode<T = {}>({ data, position, id, zIndex, type, ...rest }: Omit<Node<T>, 'id'> & { id?: string }): {
|
||||||
newNode: Node
|
newNode: Node
|
||||||
newIterationStartNode?: Node
|
newIterationStartNode?: Node
|
||||||
newLoopStartNode?: Node
|
newLoopStartNode?: Node
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user