This commit is contained in:
GuanMu 2025-12-29 16:49:36 +08:00 committed by GitHub
commit 47a6d4fbd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 928 additions and 7 deletions

View File

@ -1,6 +1,6 @@
import json import json
from collections.abc import Generator, Mapping, Sequence from collections.abc import Generator, Mapping, Sequence
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, Literal, cast
from packaging.version import Version from packaging.version import Version
from pydantic import ValidationError from pydantic import ValidationError
@ -42,6 +42,8 @@ from core.workflow.nodes.agent.entities import AgentNodeData, AgentOldVersionMod
from core.workflow.nodes.base.node import Node from core.workflow.nodes.base.node import Node
from core.workflow.nodes.base.variable_template_parser import VariableTemplateParser from core.workflow.nodes.base.variable_template_parser import VariableTemplateParser
from core.workflow.runtime import VariablePool from core.workflow.runtime import VariablePool
from core.workflow.utils.condition.entities import Condition
from core.workflow.utils.condition.processor import ConditionProcessor
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from factories.agent_factory import get_plugin_agent_strategy from factories.agent_factory import get_plugin_agent_strategy
@ -182,6 +184,7 @@ class AgentNode(Node[AgentNodeData]):
""" """
agent_parameters_dictionary = {parameter.name: parameter for parameter in agent_parameters} agent_parameters_dictionary = {parameter.name: parameter for parameter in agent_parameters}
condition_processor = ConditionProcessor()
result: dict[str, Any] = {} result: dict[str, Any] = {}
for parameter_name in node_data.agent_parameters: for parameter_name in node_data.agent_parameters:
@ -219,7 +222,32 @@ class AgentNode(Node[AgentNodeData]):
value = parameter_value value = parameter_value
if parameter.type == "array[tools]": if parameter.type == "array[tools]":
value = cast(list[dict[str, Any]], value) value = cast(list[dict[str, Any]], value)
value = [tool for tool in value if tool.get("enabled", False)] filtered_tools: list[dict[str, Any]] = []
for tool in value:
activation_condition = tool.get("activation_condition")
include_tool = True
if activation_condition and activation_condition.get("enabled"):
logical_operator = activation_condition.get("logical_operator", "and")
if logical_operator not in {"and", "or"}:
logical_operator = "and"
try:
conditions_raw = activation_condition.get("conditions", []) or []
conditions = [Condition.model_validate(condition) for condition in conditions_raw]
if conditions:
_, _, include_tool = condition_processor.process_conditions(
variable_pool=variable_pool,
conditions=conditions,
operator=cast(Literal["and", "or"], logical_operator),
)
else:
include_tool = False
except (ValidationError, ValueError):
include_tool = False
tool.pop("activation_condition", None)
if include_tool:
filtered_tools.append(tool)
value = [tool for tool in filtered_tools if tool.get("enabled", False)]
value = self._filter_mcp_type_tool(strategy, value) value = self._filter_mcp_type_tool(strategy, value)
for tool in value: for tool in value:
if "schemas" in tool: if "schemas" in tool:

View File

@ -30,6 +30,7 @@ import ToolTrigger from '@/app/components/plugins/plugin-detail-panel/tool-selec
import { CollectionType } from '@/app/components/tools/types' import { CollectionType } from '@/app/components/tools/types'
import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
import { AgentToolConditionEditor } from '@/app/components/workflow/nodes/agent/components/tool-condition'
import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form' import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form'
import { MARKETPLACE_API_PREFIX } from '@/config' import { MARKETPLACE_API_PREFIX } from '@/config'
import { useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
@ -131,6 +132,7 @@ const ToolSelector: FC<Props> = ({
description: tool.tool_description, description: tool.tool_description,
}, },
schemas: tool.paramSchemas, schemas: tool.paramSchemas,
activation_condition: value?.activation_condition,
} }
} }
const handleSelectTool = (tool: ToolDefaultValue) => { const handleSelectTool = (tool: ToolDefaultValue) => {
@ -153,6 +155,15 @@ const ToolSelector: FC<Props> = ({
} as any) } as any)
} }
const handleActivationConditionChange = (condition?: ToolValue['activation_condition']) => {
if (!value)
return
onSelect({
...value,
activation_condition: condition,
} as any)
}
// tool settings & params // tool settings & params
const currentToolSettings = useMemo(() => { const currentToolSettings = useMemo(() => {
if (!currentProvider) if (!currentProvider)
@ -272,7 +283,7 @@ const ToolSelector: FC<Props> = ({
)} )}
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-10"> <PortalToFollowElemContent className="z-10">
<div className={cn('relative max-h-[642px] min-h-20 w-[361px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm', 'overflow-y-auto pb-2')}> <div className={cn('relative max-h-[642px] min-h-20 w-[480px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm', 'overflow-y-auto pb-2')}>
<> <>
<div className="system-xl-semibold px-4 pb-1 pt-3.5 text-text-primary">{t(`detailPanel.toolSelector.${isEdit ? 'toolSetting' : 'title'}`, { ns: 'plugin' })}</div> <div className="system-xl-semibold px-4 pb-1 pt-3.5 text-text-primary">{t(`detailPanel.toolSelector.${isEdit ? 'toolSetting' : 'title'}`, { ns: 'plugin' })}</div>
{/* base form */} {/* base form */}
@ -401,6 +412,20 @@ const ToolSelector: FC<Props> = ({
)} )}
</> </>
)} )}
{value?.provider_name && nodeId && (
<>
<Divider className="my-1 w-full" />
<div className="px-4 py-2">
<AgentToolConditionEditor
value={value.activation_condition}
onChange={handleActivationConditionChange}
availableVars={nodeOutputVars}
availableNodes={availableNodes}
disabled={disabled}
/>
</div>
</>
)}
</> </>
</div> </div>
</PortalToFollowElemContent> </PortalToFollowElemContent>

View File

@ -1,6 +1,7 @@
import type { ParametersSchema, PluginMeta, PluginTriggerSubscriptionConstructor, SupportedCreationMethods, TriggerEvent } from '../../plugins/types' import type { ParametersSchema, PluginMeta, PluginTriggerSubscriptionConstructor, SupportedCreationMethods, TriggerEvent, PluginMeta as WorkflowPluginMeta } from '../../plugins/types'
import type { Collection, Event } from '../../tools/types' import type { Collection, Event } from '../../tools/types'
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { AgentToolActivationCondition } from '@/app/components/workflow/nodes/agent/types'
export enum TabsEnum { export enum TabsEnum {
Start = 'start', Start = 'start',
@ -56,7 +57,7 @@ export type ToolDefaultValue = PluginCommonDefaultValue & {
paramSchemas: Record<string, unknown>[] paramSchemas: Record<string, unknown>[]
output_schema?: Record<string, unknown> output_schema?: Record<string, unknown>
credential_id?: string credential_id?: string
meta?: PluginMeta meta?: WorkflowPluginMeta
plugin_id?: string plugin_id?: string
provider_icon?: Collection['icon'] provider_icon?: Collection['icon']
provider_icon_dark?: Collection['icon'] provider_icon_dark?: Collection['icon']
@ -87,6 +88,7 @@ export type ToolValue = {
enabled?: boolean enabled?: boolean
extra?: { description?: string } & Record<string, unknown> extra?: { description?: string } & Record<string, unknown>
credential_id?: string credential_id?: string
activation_condition?: AgentToolActivationCondition
} }
export type DataSourceItem = { export type DataSourceItem = {

View File

@ -0,0 +1,68 @@
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiAddLine } from '@remixicon/react'
import type {
NodeOutPutVar,
ValueSelector,
Var,
} from '@/app/components/workflow/types'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
type Props = {
variables: NodeOutPutVar[]
onSelect: (valueSelector: ValueSelector, varItem: Var) => void
disabled?: boolean
}
const ConditionAdd = ({
variables,
onSelect,
disabled,
}: Props) => {
const { t } = useTranslation('workflow')
const [open, setOpen] = useState(false)
const handleSelect = useCallback((valueSelector: ValueSelector, varItem: Var) => {
onSelect(valueSelector, varItem)
setOpen(false)
}, [onSelect])
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={{
mainAxis: 4,
crossAxis: 0,
}}
>
<PortalToFollowElemTrigger onClick={() => !disabled && setOpen(!open)}>
<Button
size='small'
disabled={disabled}
>
<RiAddLine className='mr-1 h-3.5 w-3.5' />
{t('nodes.agent.toolCondition.addCondition')}
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<VarReferenceVars
vars={variables}
isSupportFileVar
onChange={handleSelect}
/>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default ConditionAdd

View File

@ -0,0 +1,57 @@
import { useTranslation } from 'react-i18next'
import { useStore } from '@/app/components/workflow/store'
import PromptEditor from '@/app/components/base/prompt-editor'
import { BlockEnum } from '@/app/components/workflow/types'
import type {
Node,
} from '@/app/components/workflow/types'
type ConditionInputProps = {
disabled?: boolean
value: string
onChange: (value: string) => void
availableNodes: Node[]
}
const ConditionInput = ({
value,
onChange,
disabled,
availableNodes,
}: ConditionInputProps) => {
const { t } = useTranslation('workflow')
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
const pipelineId = useStore(s => s.pipelineId)
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
return (
<PromptEditor
key={controlPromptEditorRerenderKey}
compact
value={value}
placeholder={t('nodes.ifElse.enterValue') || ''}
workflowVariableBlock={{
show: true,
variables: [],
workflowNodesMap: availableNodes.reduce((acc, node) => {
acc[node.id] = {
title: node.data.title,
type: node.data.type,
}
if (node.data.type === BlockEnum.Start) {
acc.sys = {
title: t('blocks.start'),
type: BlockEnum.Start,
}
}
return acc
}, {} as any),
showManageInputField: !!pipelineId,
onManageInputField: () => setShowInputFieldPanel?.(true),
}}
onChange={onChange}
editable={!disabled}
/>
)
}
export default ConditionInput

View File

@ -0,0 +1,193 @@
import { memo, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiDeleteBinLine } from '@remixicon/react'
import { produce } from 'immer'
import ConditionVarSelector from './condition-var-selector'
import ConditionOperator from './condition-operator'
import {
getConditionOperators,
getDefaultValueByType,
operatorNeedsValue,
} from '../../utils'
import type {
AgentToolCondition,
} from '../../types'
import type {
Node,
NodeOutPutVar,
ValueSelector,
Var,
} from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
import Input from '@/app/components/base/input'
import ConditionInput from './condition-input'
import { cn } from '@/utils/classnames'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
type Props = {
className?: string
condition: AgentToolCondition
availableVars: NodeOutPutVar[]
availableNodes: Node[]
disabled?: boolean
onChange: (condition: AgentToolCondition) => void
onRemove: () => void
}
const ConditionItem = ({
className,
condition,
availableVars,
availableNodes,
disabled,
onChange,
onRemove,
}: Props) => {
const { t } = useTranslation('workflow')
const [open, setOpen] = useState(false)
const needsValue = operatorNeedsValue(condition.comparison_operator)
const handleSelectVar = useCallback((valueSelector: ValueSelector, varItem: Var) => {
const operators = getConditionOperators(varItem.type)
const defaultOperator = operators[0]
const nextCondition = produce(condition, (draft) => {
draft.variable_selector = valueSelector
draft.varType = varItem.type
draft.comparison_operator = defaultOperator
draft.value = operatorNeedsValue(defaultOperator) ? getDefaultValueByType(varItem.type) : undefined
})
onChange(nextCondition)
}, [condition, onChange])
const handleOperatorChange = useCallback((operator: string) => {
const nextCondition = produce(condition, (draft) => {
draft.comparison_operator = operator
if (operatorNeedsValue(operator))
draft.value = draft.varType ? getDefaultValueByType(draft.varType) : ''
else
draft.value = undefined
})
onChange(nextCondition)
}, [condition, onChange])
const handleValueChange = useCallback((value: string | boolean) => {
const nextCondition = produce(condition, (draft) => {
draft.value = value
})
onChange(nextCondition)
}, [condition, onChange])
const handleTextValueChange = useCallback((value: string) => {
handleValueChange(value)
}, [handleValueChange])
const handleBooleanValueChange = useCallback((value: boolean) => {
if (disabled)
return
handleValueChange(value)
}, [disabled, handleValueChange])
const renderValueInput = () => {
if (!needsValue)
return <div className='system-xs-regular text-text-tertiary'>{t('nodes.agent.toolCondition.noValueNeeded')}</div>
if (condition.varType === VarType.boolean) {
if (typeof condition.value === 'string' && condition.value !== 'true' && condition.value !== 'false') {
return (
<ConditionInput
value={condition.value}
onChange={handleTextValueChange}
disabled={disabled}
availableNodes={availableNodes}
/>
)
}
const booleanValue = (() => {
if (typeof condition.value === 'boolean')
return condition.value
if (condition.value === 'false')
return false
if (condition.value === 'true')
return true
return true
})()
return (
<div className='p-1'>
<BoolValue
value={booleanValue}
onChange={handleBooleanValueChange}
/>
</div>
)
}
const normalizedValue = typeof condition.value === 'string' || typeof condition.value === 'number'
? String(condition.value)
: ''
if (condition.varType === VarType.number) {
return (
<Input
value={normalizedValue}
onChange={event => handleTextValueChange(event.target.value)}
disabled={disabled}
/>
)
}
const textValue = typeof condition.value === 'string' ? condition.value : ''
return (
<ConditionInput
value={textValue}
onChange={handleTextValueChange}
disabled={disabled}
availableNodes={availableNodes}
/>
)
}
return (
<div className={cn('mb-1 flex w-full last-of-type:mb-0', className)}>
<div className='flex-1 rounded-lg bg-components-input-bg-normal'>
<div className='flex items-center p-1'>
<div className='w-0 grow'>
<ConditionVarSelector
open={open}
onOpenChange={setOpen}
valueSelector={condition.variable_selector}
varType={condition.varType}
availableVars={availableVars}
availableNodes={availableNodes}
onSelect={handleSelectVar}
disabled={disabled}
/>
</div>
<div className='mx-1 h-3 w-[1px] bg-divider-regular' />
<ConditionOperator
varType={condition.varType}
value={condition.comparison_operator}
onSelect={handleOperatorChange}
disabled={disabled || !condition.variable_selector}
/>
</div>
<div className='border-t border-divider-subtle px-3 py-2'>
{renderValueInput()}
</div>
</div>
<button
type='button'
className='ml-1 mt-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-lg text-text-tertiary transition-colors hover:bg-state-destructive-hover hover:text-text-destructive'
onClick={onRemove}
disabled={disabled}
>
<RiDeleteBinLine className='h-4 w-4' />
</button>
</div>
)
}
export default memo(ConditionItem)

View File

@ -0,0 +1,72 @@
import { RiLoopLeftLine } from '@remixicon/react'
import { memo, useMemo } from 'react'
import ConditionItem from './condition-item'
import type {
AgentToolCondition,
AgentToolConditionLogicalOperator,
} from '../../types'
import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
type Props = {
conditions: AgentToolCondition[]
logicalOperator: AgentToolConditionLogicalOperator
availableVars: NodeOutPutVar[]
availableNodes: Node[]
disabled?: boolean
onChange: (condition: AgentToolCondition) => void
onRemove: (conditionId: string) => void
onToggleLogicalOperator: () => void
}
const ConditionList = ({
conditions,
logicalOperator,
availableVars,
availableNodes,
disabled,
onChange,
onRemove,
onToggleLogicalOperator,
}: Props) => {
const hasMultiple = conditions.length > 1
const containerClassName = useMemo(() => cn('relative', hasMultiple && 'pl-[60px]'), [hasMultiple])
return (
<div className={containerClassName}>
{hasMultiple && (
<div className='absolute bottom-0 left-0 top-0 w-[60px]'>
<div className='absolute bottom-4 left-[46px] top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep'></div>
<div className='absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg'></div>
<button
type='button'
className='absolute right-1 top-1/2 flex h-5 -translate-y-1/2 items-center gap-1 rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1.5 text-[10px] font-semibold uppercase text-text-accent-secondary shadow-xs disabled:cursor-not-allowed disabled:opacity-60'
onClick={onToggleLogicalOperator}
disabled={disabled}
>
{logicalOperator.toUpperCase()}
<RiLoopLeftLine className='h-3 w-3' />
</button>
</div>
)}
{conditions.map(condition => (
<ConditionItem
key={condition.id}
className=''
condition={condition}
availableVars={availableVars}
availableNodes={availableNodes}
disabled={disabled}
onChange={onChange}
onRemove={() => onRemove(condition.id)}
/>
))}
</div>
)
}
export default memo(ConditionList)

View File

@ -0,0 +1,93 @@
import {
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { RiArrowDownSLine } from '@remixicon/react'
import type { VarType } from '@/app/components/workflow/types'
import { getConditionOperators } from '../../utils'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { cn } from '@/utils/classnames'
type Props = {
varType?: VarType
value?: string
onSelect: (operator: string) => void
disabled?: boolean
}
const ConditionOperator = ({
varType,
value,
onSelect,
disabled,
}: Props) => {
const { t } = useTranslation('workflow')
const [open, setOpen] = useState(false)
const placeholder = t('nodes.agent.toolCondition.operatorPlaceholder') as string
const options = useMemo(() => {
return getConditionOperators(varType).map((option) => {
const key = `nodes.ifElse.comparisonOperator.${option}`
const translated = t(key as any) as string
return {
value: option,
label: translated === key ? option : translated,
}
})
}, [t, varType])
const selectedOption = options.find(option => option.value === value)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{
mainAxis: 4,
crossAxis: 0,
}}
>
<PortalToFollowElemTrigger
onClick={() => {
if (!disabled)
setOpen(v => !v)
}}
>
<Button
className={cn('h-7 shrink-0 px-2', !selectedOption && 'opacity-50')}
size='small'
variant='ghost'
disabled={disabled}
>
{selectedOption?.label ?? placeholder}
<RiArrowDownSLine className='ml-1 h-3.5 w-3.5' />
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<div className='rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
{options.map(option => (
<div
key={option.value}
className='flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover'
onClick={() => {
onSelect(option.value)
setOpen(false)
}}
>
{option.label}
</div>
))}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default ConditionOperator

View File

@ -0,0 +1,95 @@
import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag'
import type {
Node,
NodeOutPutVar,
ValueSelector,
Var,
} from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
type Props = {
open: boolean
onOpenChange: (open: boolean) => void
valueSelector?: ValueSelector
varType?: VarType
availableVars: NodeOutPutVar[]
availableNodes: Node[]
onSelect: (valueSelector: ValueSelector, varItem: Var) => void
disabled?: boolean
}
const ConditionVarSelector = ({
open,
onOpenChange,
valueSelector,
varType,
availableVars,
availableNodes,
onSelect,
disabled,
}: Props) => {
const { t } = useTranslation('workflow')
const handleTriggerClick = useCallback(() => {
if (disabled)
return
onOpenChange(!open)
}, [disabled, onOpenChange, open])
const handleSelect = useCallback((selector: ValueSelector, varItem: Var) => {
if (disabled)
return
onSelect(selector, varItem)
onOpenChange(false)
}, [disabled, onOpenChange, onSelect])
return (
<PortalToFollowElem
open={open}
onOpenChange={(state) => {
if (!disabled)
onOpenChange(state)
}}
placement='bottom-start'
offset={{
mainAxis: 4,
crossAxis: 0,
}}
>
<PortalToFollowElemTrigger onClick={handleTriggerClick}>
<div className={cn('cursor-pointer', disabled && '!cursor-not-allowed opacity-60')}>
{valueSelector && valueSelector.length > 0 ? (
<VariableTag
valueSelector={valueSelector}
varType={varType ?? VarType.string}
isShort
availableNodes={availableNodes}
/>
) : (
<div className='system-xs-regular text-text-tertiary'>{t('nodes.agent.toolCondition.selectVariable')}</div>
)}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<VarReferenceVars
vars={availableVars}
isSupportFileVar
onChange={handleSelect}
/>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(ConditionVarSelector)

View File

@ -0,0 +1,158 @@
'use client'
import { memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { v4 as uuid4 } from 'uuid'
import { produce } from 'immer'
import Switch from '@/app/components/base/switch'
import ConditionAdd from './condition-add'
import ConditionList from './condition-list'
import type {
AgentToolActivationCondition,
AgentToolCondition,
} from '../../types'
import { AgentToolConditionLogicalOperator } from '../../types'
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
import {
getConditionOperators,
getDefaultValueByType,
operatorNeedsValue,
} from '../../utils'
import { cn } from '@/utils/classnames'
type Props = {
value?: AgentToolActivationCondition
onChange: (value: AgentToolActivationCondition | undefined) => void
availableVars: NodeOutPutVar[]
availableNodes: Node[]
disabled?: boolean
}
const AgentToolConditionEditor = ({
value,
onChange,
availableVars,
availableNodes,
disabled,
}: Props) => {
const { t } = useTranslation('workflow')
const currentValue = useMemo<AgentToolActivationCondition>(() => value ?? ({
enabled: false,
logical_operator: AgentToolConditionLogicalOperator.And,
conditions: [],
}), [value])
const handleToggle = useCallback((state: boolean) => {
const next = produce(currentValue, (draft) => {
draft.enabled = state
})
onChange(next)
}, [currentValue, onChange])
const handleAddCondition = useCallback((valueSelector: ValueSelector, varItem: Var) => {
const operators = getConditionOperators(varItem.type)
const defaultOperator = operators[0]
const newCondition: AgentToolCondition = {
id: uuid4(),
varType: varItem.type ?? VarType.string,
variable_selector: valueSelector,
comparison_operator: defaultOperator,
value: operatorNeedsValue(defaultOperator) ? getDefaultValueByType(varItem.type ?? VarType.string) : undefined,
}
const next = produce(currentValue, (draft) => {
draft.enabled = true
draft.conditions.push(newCondition)
})
onChange(next)
}, [currentValue, onChange])
const handleConditionChange = useCallback((updated: AgentToolCondition) => {
const next = produce(currentValue, (draft) => {
const targetIndex = draft.conditions.findIndex(item => item.id === updated.id)
if (targetIndex !== -1)
draft.conditions[targetIndex] = updated
})
onChange(next)
}, [currentValue, onChange])
const handleRemoveCondition = useCallback((conditionId: string) => {
const next = produce(currentValue, (draft) => {
draft.conditions = draft.conditions.filter(item => item.id !== conditionId)
})
onChange(next)
}, [currentValue, onChange])
const handleToggleLogicalOperator = useCallback(() => {
const next = produce(currentValue, (draft) => {
draft.logical_operator = draft.logical_operator === AgentToolConditionLogicalOperator.And
? AgentToolConditionLogicalOperator.Or
: AgentToolConditionLogicalOperator.And
})
onChange(next)
}, [currentValue, onChange])
const isEnabled = currentValue.enabled
const hasConditions = currentValue.conditions.length > 0
return (
<div className=''>
<div className='flex items-start justify-between gap-3'>
<div>
<div className='system-sm-semibold text-text-primary'>{t('nodes.agent.toolCondition.title')}</div>
<div className='system-xs-regular text-text-tertiary'>{t('nodes.agent.toolCondition.description')}</div>
</div>
<Switch
defaultValue={isEnabled}
onChange={handleToggle}
disabled={disabled}
/>
</div>
{isEnabled && (
<div className='space-y-3'>
<div className='rounded-[10px] bg-components-panel-bg px-3 py-2'>
{hasConditions && (
<div className='mb-2'>
<ConditionList
conditions={currentValue.conditions}
logicalOperator={currentValue.logical_operator}
availableVars={availableVars}
availableNodes={availableNodes}
disabled={disabled}
onChange={handleConditionChange}
onRemove={handleRemoveCondition}
onToggleLogicalOperator={handleToggleLogicalOperator}
/>
</div>
)}
<div className={cn(
'flex items-center justify-between pr-[30px]',
hasConditions && currentValue.conditions.length > 1 && 'ml-[60px]',
!hasConditions && 'mt-1',
)}>
<ConditionAdd
variables={availableVars}
onSelect={handleAddCondition}
disabled={disabled}
/>
</div>
</div>
{!hasConditions && (
<div className='system-xs-regular text-text-tertiary'>
{t('nodes.agent.toolCondition.addFirstCondition')}
</div>
)}
{hasConditions && currentValue.conditions.length <= 1 && (
<div className='system-xs-regular text-text-tertiary'>
{t('nodes.agent.toolCondition.singleConditionTip')}
</div>
)}
</div>
)}
</div>
)
}
export default memo(AgentToolConditionEditor)

View File

@ -0,0 +1 @@
export { default as AgentToolConditionEditor } from './editor'

View File

@ -1,6 +1,6 @@
import type { ToolVarInputs } from '../tool/types' import type { ToolVarInputs } from '../tool/types'
import type { PluginMeta } from '@/app/components/plugins/types' import type { PluginMeta } from '@/app/components/plugins/types'
import type { CommonNodeType, Memory } from '@/app/components/workflow/types' import type { CommonNodeType, Memory, ValueSelector, VarType } from '@/app/components/workflow/types'
export type AgentNodeType = CommonNodeType & { export type AgentNodeType = CommonNodeType & {
agent_strategy_provider_name?: string agent_strategy_provider_name?: string
@ -18,3 +18,22 @@ export type AgentNodeType = CommonNodeType & {
export enum AgentFeature { export enum AgentFeature {
HISTORY_MESSAGES = 'history-messages', HISTORY_MESSAGES = 'history-messages',
} }
export enum AgentToolConditionLogicalOperator {
And = 'and',
Or = 'or',
}
export type AgentToolCondition = {
id: string
varType: VarType
variable_selector?: ValueSelector
comparison_operator?: string
value?: string | string[] | boolean
}
export type AgentToolActivationCondition = {
enabled: boolean
logical_operator: AgentToolConditionLogicalOperator
conditions: AgentToolCondition[]
}

View File

@ -163,9 +163,11 @@ const useConfig = (id: string, payload: AgentNodeType) => {
VarKindType.array, VarKindType.array,
VarKindType.number, VarKindType.number,
VarKindType.string, VarKindType.string,
VarKindType.boolean,
VarKindType.secret, VarKindType.secret,
VarKindType.arrayString, VarKindType.arrayString,
VarKindType.arrayNumber, VarKindType.arrayNumber,
VarKindType.arrayBoolean,
VarKindType.file, VarKindType.file,
VarKindType.arrayFile, VarKindType.arrayFile,
].includes(varPayload.type) ].includes(varPayload.type)

View File

@ -0,0 +1,50 @@
import { VarType } from '@/app/components/workflow/types'
const COMPARISON_OPERATOR_WITHOUT_VALUE = new Set([
'empty',
'not empty',
'null',
'not null',
'exists',
'not exists',
])
export const getConditionOperators = (varType?: VarType): string[] => {
switch (varType) {
case VarType.number:
case VarType.integer:
return ['=', '≠', '>', '<', '≥', '≤']
case VarType.boolean:
return ['is', 'is not']
case VarType.arrayString:
case VarType.arrayNumber:
case VarType.arrayBoolean:
case VarType.array:
case VarType.arrayAny:
return ['contains', 'not contains', 'empty', 'not empty']
case VarType.arrayFile:
return ['contains', 'not contains', 'empty', 'not empty']
case VarType.file:
return ['exists', 'not exists']
case VarType.object:
return ['empty', 'not empty']
case VarType.any:
return ['is', 'is not', 'empty', 'not empty']
default:
return ['contains', 'not contains', 'start with', 'end with', 'is', 'is not', 'empty', 'not empty']
}
}
export const operatorNeedsValue = (operator?: string): boolean => {
if (!operator)
return false
return !COMPARISON_OPERATOR_WITHOUT_VALUE.has(operator)
}
export const getDefaultValueByType = (varType: VarType): string | boolean => {
if (varType === VarType.boolean)
return true
return ''
}

View File

@ -1,5 +1,10 @@
import { CronExpressionParser } from 'cron-parser' import { CronExpressionParser } from 'cron-parser'
// Minimal representation of the CronDate object returned by cron-parser.
type CronDateLike = {
toDate: () => Date
}
// Convert a UTC date from cron-parser to user timezone representation // Convert a UTC date from cron-parser to user timezone representation
// This ensures consistency with other execution time calculations // This ensures consistency with other execution time calculations
const convertToUserTimezoneRepresentation = (utcDate: Date, timezone: string): Date => { const convertToUserTimezoneRepresentation = (utcDate: Date, timezone: string): Date => {
@ -42,7 +47,7 @@ export const parseCronExpression = (cronExpression: string, timezone: string = '
}) })
// Get the next 5 execution times using the take() method // Get the next 5 execution times using the take() method
const nextCronDates = interval.take(5) const nextCronDates: CronDateLike[] = interval.take(5)
// Convert CronDate objects to Date objects and ensure they represent // Convert CronDate objects to Date objects and ensure they represent
// the time in user timezone (consistent with execution-time-calculator.ts) // the time in user timezone (consistent with execution-time-calculator.ts)

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "ميزات مجانية:", "plans.community.includesTitle": "ميزات مجانية:",
"plans.community.name": "مجتمع", "plans.community.name": "مجتمع",
"plans.community.price": "مجاني", "plans.community.price": "مجاني",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "اتصل بالمبيعات", "plans.enterprise.btnText": "اتصل بالمبيعات",
"plans.enterprise.description": "للمؤسسات التي تتطلب أمانًا وامتثالًا وقابلية للتوسع وتحكمًا وحلولًا مخصصة على مستوى المؤسسة", "plans.enterprise.description": "للمؤسسات التي تتطلب أمانًا وامتثالًا وقابلية للتوسع وتحكمًا وحلولًا مخصصة على مستوى المؤسسة",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Kostenlose Funktionen:", "plans.community.includesTitle": "Kostenlose Funktionen:",
"plans.community.name": "Gemeinschaft", "plans.community.name": "Gemeinschaft",
"plans.community.price": "Kostenlos", "plans.community.price": "Kostenlos",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Vertrieb kontaktieren", "plans.enterprise.btnText": "Vertrieb kontaktieren",
"plans.enterprise.description": "Erhalten Sie volle Fähigkeiten und Unterstützung für großangelegte, missionskritische Systeme.", "plans.enterprise.description": "Erhalten Sie volle Fähigkeiten und Unterstützung für großangelegte, missionskritische Systeme.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Norwegisch", "metadata.languageMap.no": "Norwegisch",
"metadata.languageMap.pl": "Polnisch", "metadata.languageMap.pl": "Polnisch",
"metadata.languageMap.pt": "Portugiesisch", "metadata.languageMap.pt": "Portugiesisch",
"metadata.languageMap.ro": "Rumänisch",
"metadata.languageMap.ru": "Russisch", "metadata.languageMap.ru": "Russisch",
"metadata.languageMap.sv": "Schwedisch", "metadata.languageMap.sv": "Schwedisch",
"metadata.languageMap.th": "Thai", "metadata.languageMap.th": "Thai",

View File

@ -363,6 +363,15 @@
"nodes.agent.strategyNotFoundDescAndSwitchVersion": "The installed plugin version does not provide this strategy. Click to switch version.", "nodes.agent.strategyNotFoundDescAndSwitchVersion": "The installed plugin version does not provide this strategy. Click to switch version.",
"nodes.agent.strategyNotInstallTooltip": "{{strategy}} is not installed", "nodes.agent.strategyNotInstallTooltip": "{{strategy}} is not installed",
"nodes.agent.strategyNotSet": "Agentic strategy Not Set", "nodes.agent.strategyNotSet": "Agentic strategy Not Set",
"nodes.agent.toolCondition.addCondition": "Add Condition",
"nodes.agent.toolCondition.addFirstCondition": "No conditions yet. Add your first condition.",
"nodes.agent.toolCondition.description": "When enabled, load this tool only when the conditions are met.",
"nodes.agent.toolCondition.logicalOperator": "Logical operator: {{value}}",
"nodes.agent.toolCondition.noValueNeeded": "No value is required for this operator.",
"nodes.agent.toolCondition.operatorPlaceholder": "Select operator",
"nodes.agent.toolCondition.selectVariable": "Select variable...",
"nodes.agent.toolCondition.singleConditionTip": "Add at least one condition to start.",
"nodes.agent.toolCondition.title": "Activation Condition",
"nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Not Authorized", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} Not Authorized",
"nodes.agent.toolNotInstallTooltip": "{{tool}} is not installed", "nodes.agent.toolNotInstallTooltip": "{{tool}} is not installed",
"nodes.agent.toolbox": "toolbox", "nodes.agent.toolbox": "toolbox",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Características gratuitas:", "plans.community.includesTitle": "Características gratuitas:",
"plans.community.name": "Comunidad", "plans.community.name": "Comunidad",
"plans.community.price": "Gratis", "plans.community.price": "Gratis",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Contactar ventas", "plans.enterprise.btnText": "Contactar ventas",
"plans.enterprise.description": "Obtén capacidades completas y soporte para sistemas críticos a gran escala.", "plans.enterprise.description": "Obtén capacidades completas y soporte para sistemas críticos a gran escala.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Noruego", "metadata.languageMap.no": "Noruego",
"metadata.languageMap.pl": "Polaco", "metadata.languageMap.pl": "Polaco",
"metadata.languageMap.pt": "Portugués", "metadata.languageMap.pt": "Portugués",
"metadata.languageMap.ro": "Rumano",
"metadata.languageMap.ru": "Ruso", "metadata.languageMap.ru": "Ruso",
"metadata.languageMap.sv": "Sueco", "metadata.languageMap.sv": "Sueco",
"metadata.languageMap.th": "Tailandés", "metadata.languageMap.th": "Tailandés",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "ویژگی‌های رایگان:", "plans.community.includesTitle": "ویژگی‌های رایگان:",
"plans.community.name": "جامعه", "plans.community.name": "جامعه",
"plans.community.price": "رایگان", "plans.community.price": "رایگان",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "تماس با فروش", "plans.enterprise.btnText": "تماس با فروش",
"plans.enterprise.description": "دریافت کامل‌ترین قابلیت‌ها و پشتیبانی برای سیستم‌های بزرگ و بحرانی.", "plans.enterprise.description": "دریافت کامل‌ترین قابلیت‌ها و پشتیبانی برای سیستم‌های بزرگ و بحرانی.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Fonctionnalités gratuites :", "plans.community.includesTitle": "Fonctionnalités gratuites :",
"plans.community.name": "Communauté", "plans.community.name": "Communauté",
"plans.community.price": "Gratuit", "plans.community.price": "Gratuit",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Contacter les ventes", "plans.enterprise.btnText": "Contacter les ventes",
"plans.enterprise.description": "Obtenez toutes les capacités et le support pour les systèmes à grande échelle et critiques pour la mission.", "plans.enterprise.description": "Obtenez toutes les capacités et le support pour les systèmes à grande échelle et critiques pour la mission.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Norvégien", "metadata.languageMap.no": "Norvégien",
"metadata.languageMap.pl": "Polonais", "metadata.languageMap.pl": "Polonais",
"metadata.languageMap.pt": "Portugais", "metadata.languageMap.pt": "Portugais",
"metadata.languageMap.ro": "Roumain",
"metadata.languageMap.ru": "Russe", "metadata.languageMap.ru": "Russe",
"metadata.languageMap.sv": "Suédois", "metadata.languageMap.sv": "Suédois",
"metadata.languageMap.th": "Thaï", "metadata.languageMap.th": "Thaï",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "निःशुल्क सुविधाएँ:", "plans.community.includesTitle": "निःशुल्क सुविधाएँ:",
"plans.community.name": "समुदाय", "plans.community.name": "समुदाय",
"plans.community.price": "मुक्त", "plans.community.price": "मुक्त",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "बिक्री से संपर्क करें", "plans.enterprise.btnText": "बिक्री से संपर्क करें",
"plans.enterprise.description": "बड़े पैमाने पर मिशन-क्रिटिकल सिस्टम के लिए पूर्ण क्षमताएं और समर्थन प्राप्त करें।", "plans.enterprise.description": "बड़े पैमाने पर मिशन-क्रिटिकल सिस्टम के लिए पूर्ण क्षमताएं और समर्थन प्राप्त करें।",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Fitur Gratis:", "plans.community.includesTitle": "Fitur Gratis:",
"plans.community.name": "Masyarakat", "plans.community.name": "Masyarakat",
"plans.community.price": "Bebas", "plans.community.price": "Bebas",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Hubungi Sales", "plans.enterprise.btnText": "Hubungi Sales",
"plans.enterprise.description": "Untuk perusahaan, memerlukan keamanan, kepatuhan, skalabilitas, kontrol, dan fitur yang lebih canggih di seluruh organisasi", "plans.enterprise.description": "Untuk perusahaan, memerlukan keamanan, kepatuhan, skalabilitas, kontrol, dan fitur yang lebih canggih di seluruh organisasi",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Caratteristiche Gratuite:", "plans.community.includesTitle": "Caratteristiche Gratuite:",
"plans.community.name": "Comunità", "plans.community.name": "Comunità",
"plans.community.price": "Gratuito", "plans.community.price": "Gratuito",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Contatta le vendite", "plans.enterprise.btnText": "Contatta le vendite",
"plans.enterprise.description": "Ottieni tutte le capacità e il supporto per sistemi mission-critical su larga scala.", "plans.enterprise.description": "Ottieni tutte le capacità e il supporto per sistemi mission-critical su larga scala.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Norvegese", "metadata.languageMap.no": "Norvegese",
"metadata.languageMap.pl": "Polacco", "metadata.languageMap.pl": "Polacco",
"metadata.languageMap.pt": "Portoghese", "metadata.languageMap.pt": "Portoghese",
"metadata.languageMap.ro": "Rumeno",
"metadata.languageMap.ru": "Russo", "metadata.languageMap.ru": "Russo",
"metadata.languageMap.sv": "Svedese", "metadata.languageMap.sv": "Svedese",
"metadata.languageMap.th": "Thailandese", "metadata.languageMap.th": "Thailandese",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "無料機能:", "plans.community.includesTitle": "無料機能:",
"plans.community.name": "コミュニティ", "plans.community.name": "コミュニティ",
"plans.community.price": "無料", "plans.community.price": "無料",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "営業に相談", "plans.enterprise.btnText": "営業に相談",
"plans.enterprise.description": "企業レベルのセキュリティとカスタマイズを実現", "plans.enterprise.description": "企業レベルのセキュリティとカスタマイズを実現",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "ノルウェー語", "metadata.languageMap.no": "ノルウェー語",
"metadata.languageMap.pl": "ポーランド語", "metadata.languageMap.pl": "ポーランド語",
"metadata.languageMap.pt": "ポルトガル語", "metadata.languageMap.pt": "ポルトガル語",
"metadata.languageMap.ro": "ルーマニア語",
"metadata.languageMap.ru": "ロシア語", "metadata.languageMap.ru": "ロシア語",
"metadata.languageMap.sv": "スウェーデン語", "metadata.languageMap.sv": "スウェーデン語",
"metadata.languageMap.th": "タイ語", "metadata.languageMap.th": "タイ語",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "무료 기능:", "plans.community.includesTitle": "무료 기능:",
"plans.community.name": "커뮤니티", "plans.community.name": "커뮤니티",
"plans.community.price": "무료", "plans.community.price": "무료",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "판매 문의하기", "plans.enterprise.btnText": "판매 문의하기",
"plans.enterprise.description": "대규모 미션 크리티컬 시스템을 위한 완전한 기능과 지원을 제공합니다.", "plans.enterprise.description": "대규모 미션 크리티컬 시스템을 위한 완전한 기능과 지원을 제공합니다.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "노르웨이어", "metadata.languageMap.no": "노르웨이어",
"metadata.languageMap.pl": "폴란드어", "metadata.languageMap.pl": "폴란드어",
"metadata.languageMap.pt": "포르투갈어", "metadata.languageMap.pt": "포르투갈어",
"metadata.languageMap.ro": "루마니아어",
"metadata.languageMap.ru": "러시아어", "metadata.languageMap.ru": "러시아어",
"metadata.languageMap.sv": "스웨덴어", "metadata.languageMap.sv": "스웨덴어",
"metadata.languageMap.th": "태국어", "metadata.languageMap.th": "태국어",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Darmowe funkcje:", "plans.community.includesTitle": "Darmowe funkcje:",
"plans.community.name": "Społeczność", "plans.community.name": "Społeczność",
"plans.community.price": "Darmowy", "plans.community.price": "Darmowy",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Skontaktuj się z działem sprzedaży", "plans.enterprise.btnText": "Skontaktuj się z działem sprzedaży",
"plans.enterprise.description": "Uzyskaj pełne możliwości i wsparcie dla systemów o kluczowym znaczeniu dla misji.", "plans.enterprise.description": "Uzyskaj pełne możliwości i wsparcie dla systemów o kluczowym znaczeniu dla misji.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Recursos Gratuitos:", "plans.community.includesTitle": "Recursos Gratuitos:",
"plans.community.name": "Comunidade", "plans.community.name": "Comunidade",
"plans.community.price": "Grátis", "plans.community.price": "Grátis",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Contate Vendas", "plans.enterprise.btnText": "Contate Vendas",
"plans.enterprise.description": "Obtenha capacidades completas e suporte para sistemas críticos em larga escala.", "plans.enterprise.description": "Obtenha capacidades completas e suporte para sistemas críticos em larga escala.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Norueguês", "metadata.languageMap.no": "Norueguês",
"metadata.languageMap.pl": "Polonês", "metadata.languageMap.pl": "Polonês",
"metadata.languageMap.pt": "Português", "metadata.languageMap.pt": "Português",
"metadata.languageMap.ro": "Romeno",
"metadata.languageMap.ru": "Russo", "metadata.languageMap.ru": "Russo",
"metadata.languageMap.sv": "Sueco", "metadata.languageMap.sv": "Sueco",
"metadata.languageMap.th": "Tailandês", "metadata.languageMap.th": "Tailandês",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Funcții gratuite:", "plans.community.includesTitle": "Funcții gratuite:",
"plans.community.name": "Comunitate", "plans.community.name": "Comunitate",
"plans.community.price": "Gratuit", "plans.community.price": "Gratuit",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Contactați Vânzări", "plans.enterprise.btnText": "Contactați Vânzări",
"plans.enterprise.description": "Obțineți capacități și asistență complete pentru sisteme critice la scară largă.", "plans.enterprise.description": "Obțineți capacități și asistență complete pentru sisteme critice la scară largă.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Norvegiană", "metadata.languageMap.no": "Norvegiană",
"metadata.languageMap.pl": "Poloneză", "metadata.languageMap.pl": "Poloneză",
"metadata.languageMap.pt": "Portugheză", "metadata.languageMap.pt": "Portugheză",
"metadata.languageMap.ro": "Română",
"metadata.languageMap.ru": "Rusă", "metadata.languageMap.ru": "Rusă",
"metadata.languageMap.sv": "Suedeză", "metadata.languageMap.sv": "Suedeză",
"metadata.languageMap.th": "Tailandeză", "metadata.languageMap.th": "Tailandeză",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Бесплатные функции:", "plans.community.includesTitle": "Бесплатные функции:",
"plans.community.name": "Сообщество", "plans.community.name": "Сообщество",
"plans.community.price": "Свободно", "plans.community.price": "Свободно",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Связаться с отделом продаж", "plans.enterprise.btnText": "Связаться с отделом продаж",
"plans.enterprise.description": "Получите полный набор возможностей и поддержку для крупномасштабных критически важных систем.", "plans.enterprise.description": "Получите полный набор возможностей и поддержку для крупномасштабных критически важных систем.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Норвежский", "metadata.languageMap.no": "Норвежский",
"metadata.languageMap.pl": "Польский", "metadata.languageMap.pl": "Польский",
"metadata.languageMap.pt": "Португальский", "metadata.languageMap.pt": "Португальский",
"metadata.languageMap.ro": "румынский",
"metadata.languageMap.ru": "Русский", "metadata.languageMap.ru": "Русский",
"metadata.languageMap.sv": "Шведский", "metadata.languageMap.sv": "Шведский",
"metadata.languageMap.th": "Тайский", "metadata.languageMap.th": "Тайский",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Brezplačne funkcije:", "plans.community.includesTitle": "Brezplačne funkcije:",
"plans.community.name": "Skupnost", "plans.community.name": "Skupnost",
"plans.community.price": "Brezplačno", "plans.community.price": "Brezplačno",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Kontaktirajte prodajo", "plans.enterprise.btnText": "Kontaktirajte prodajo",
"plans.enterprise.description": "Pridobite vse zmogljivosti in podporo za velike sisteme kritične za misijo.", "plans.enterprise.description": "Pridobite vse zmogljivosti in podporo za velike sisteme kritične za misijo.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "คุณสมบัติเสรี:", "plans.community.includesTitle": "คุณสมบัติเสรี:",
"plans.community.name": "ชุมชน", "plans.community.name": "ชุมชน",
"plans.community.price": "ฟรี", "plans.community.price": "ฟรี",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "ติดต่อฝ่ายขาย", "plans.enterprise.btnText": "ติดต่อฝ่ายขาย",
"plans.enterprise.description": "รับความสามารถและการสนับสนุนเต็มรูปแบบสําหรับระบบที่สําคัญต่อภารกิจขนาดใหญ่", "plans.enterprise.description": "รับความสามารถและการสนับสนุนเต็มรูปแบบสําหรับระบบที่สําคัญต่อภารกิจขนาดใหญ่",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "นอร์เวย์", "metadata.languageMap.no": "นอร์เวย์",
"metadata.languageMap.pl": "โปแลนด์", "metadata.languageMap.pl": "โปแลนด์",
"metadata.languageMap.pt": "โปรตุเกส", "metadata.languageMap.pt": "โปรตุเกส",
"metadata.languageMap.ro": "โรมาเนีย",
"metadata.languageMap.ru": "รัสเซีย", "metadata.languageMap.ru": "รัสเซีย",
"metadata.languageMap.sv": "สวีเดน", "metadata.languageMap.sv": "สวีเดน",
"metadata.languageMap.th": "ไทย", "metadata.languageMap.th": "ไทย",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Ücretsiz Özellikler:", "plans.community.includesTitle": "Ücretsiz Özellikler:",
"plans.community.name": "Topluluk", "plans.community.name": "Topluluk",
"plans.community.price": "Ücretsiz", "plans.community.price": "Ücretsiz",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Satış ile İletişime Geç", "plans.enterprise.btnText": "Satış ile İletişime Geç",
"plans.enterprise.description": "Büyük ölçekli kritik sistemler için tam yetenekler ve destek.", "plans.enterprise.description": "Büyük ölçekli kritik sistemler için tam yetenekler ve destek.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Безкоштовні можливості:", "plans.community.includesTitle": "Безкоштовні можливості:",
"plans.community.name": "Спільнота", "plans.community.name": "Спільнота",
"plans.community.price": "Безкоштовно", "plans.community.price": "Безкоштовно",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Зв'язатися з відділом продажу", "plans.enterprise.btnText": "Зв'язатися з відділом продажу",
"plans.enterprise.description": "Отримайте повні можливості та підтримку для масштабних критично важливих систем.", "plans.enterprise.description": "Отримайте повні можливості та підтримку для масштабних критично важливих систем.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Норвезька", "metadata.languageMap.no": "Норвезька",
"metadata.languageMap.pl": "Польська", "metadata.languageMap.pl": "Польська",
"metadata.languageMap.pt": "Португальська", "metadata.languageMap.pt": "Португальська",
"metadata.languageMap.ro": "Румунська",
"metadata.languageMap.ru": "Російська", "metadata.languageMap.ru": "Російська",
"metadata.languageMap.sv": "Шведська", "metadata.languageMap.sv": "Шведська",
"metadata.languageMap.th": "Тайська", "metadata.languageMap.th": "Тайська",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "Tính năng miễn phí:", "plans.community.includesTitle": "Tính năng miễn phí:",
"plans.community.name": "Cộng đồng", "plans.community.name": "Cộng đồng",
"plans.community.price": "Miễn phí", "plans.community.price": "Miễn phí",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "Liên hệ với Bộ phận Bán hàng", "plans.enterprise.btnText": "Liên hệ với Bộ phận Bán hàng",
"plans.enterprise.description": "Nhận toàn bộ khả năng và hỗ trợ cho các hệ thống quan trọng cho nhiệm vụ quy mô lớn.", "plans.enterprise.description": "Nhận toàn bộ khả năng và hỗ trợ cho các hệ thống quan trọng cho nhiệm vụ quy mô lớn.",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "Tiếng Na Uy", "metadata.languageMap.no": "Tiếng Na Uy",
"metadata.languageMap.pl": "Tiếng Ba Lan", "metadata.languageMap.pl": "Tiếng Ba Lan",
"metadata.languageMap.pt": "Tiếng Bồ Đào Nha", "metadata.languageMap.pt": "Tiếng Bồ Đào Nha",
"metadata.languageMap.ro": "Tiếng Romania",
"metadata.languageMap.ru": "Tiếng Nga", "metadata.languageMap.ru": "Tiếng Nga",
"metadata.languageMap.sv": "Tiếng Thụy Điển", "metadata.languageMap.sv": "Tiếng Thụy Điển",
"metadata.languageMap.th": "Tiếng Thái", "metadata.languageMap.th": "Tiếng Thái",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "免费功能:", "plans.community.includesTitle": "免费功能:",
"plans.community.name": "Community", "plans.community.name": "Community",
"plans.community.price": "免费", "plans.community.price": "免费",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "联系销售", "plans.enterprise.btnText": "联系销售",
"plans.enterprise.description": "适合需要组织级安全性、合规性、可扩展性、控制和定制解决方案的企业", "plans.enterprise.description": "适合需要组织级安全性、合规性、可扩展性、控制和定制解决方案的企业",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "挪威语", "metadata.languageMap.no": "挪威语",
"metadata.languageMap.pl": "波兰语", "metadata.languageMap.pl": "波兰语",
"metadata.languageMap.pt": "葡萄牙语", "metadata.languageMap.pt": "葡萄牙语",
"metadata.languageMap.ro": "罗马尼亚语",
"metadata.languageMap.ru": "俄语", "metadata.languageMap.ru": "俄语",
"metadata.languageMap.sv": "瑞典语", "metadata.languageMap.sv": "瑞典语",
"metadata.languageMap.th": "泰语", "metadata.languageMap.th": "泰语",

View File

@ -357,6 +357,15 @@
"nodes.agent.strategyNotFoundDescAndSwitchVersion": "安装的插件版本不提供此策略。点击切换版本。", "nodes.agent.strategyNotFoundDescAndSwitchVersion": "安装的插件版本不提供此策略。点击切换版本。",
"nodes.agent.strategyNotInstallTooltip": "{{strategy}} 未安装", "nodes.agent.strategyNotInstallTooltip": "{{strategy}} 未安装",
"nodes.agent.strategyNotSet": "代理策略未设置", "nodes.agent.strategyNotSet": "代理策略未设置",
"nodes.agent.toolCondition.addCondition": "新增条件",
"nodes.agent.toolCondition.addFirstCondition": "暂未添加条件,请先新增一个条件。",
"nodes.agent.toolCondition.description": "开启后,根据条件加载该工具。",
"nodes.agent.toolCondition.logicalOperator": "当前逻辑:{{value}}",
"nodes.agent.toolCondition.noValueNeeded": "该运算符无需填写值。",
"nodes.agent.toolCondition.operatorPlaceholder": "选择运算符",
"nodes.agent.toolCondition.selectVariable": "选择变量...",
"nodes.agent.toolCondition.singleConditionTip": "请添加至少一个条件。",
"nodes.agent.toolCondition.title": "启用条件",
"nodes.agent.toolNotAuthorizedTooltip": "{{tool}} 未授权", "nodes.agent.toolNotAuthorizedTooltip": "{{tool}} 未授权",
"nodes.agent.toolNotInstallTooltip": "{{tool}} 未安装", "nodes.agent.toolNotInstallTooltip": "{{tool}} 未安装",
"nodes.agent.toolbox": "工具箱", "nodes.agent.toolbox": "工具箱",

View File

@ -20,6 +20,7 @@
"plans.community.includesTitle": "免費功能:", "plans.community.includesTitle": "免費功能:",
"plans.community.name": "社區", "plans.community.name": "社區",
"plans.community.price": "免費", "plans.community.price": "免費",
"plans.community.priceTip": "",
"plans.enterprise.btnText": "聯繫銷售", "plans.enterprise.btnText": "聯繫銷售",
"plans.enterprise.description": "獲得大規模關鍵任務系統的完整功能和支援。", "plans.enterprise.description": "獲得大規模關鍵任務系統的完整功能和支援。",
"plans.enterprise.features": [ "plans.enterprise.features": [

View File

@ -247,6 +247,7 @@
"metadata.languageMap.no": "挪威語", "metadata.languageMap.no": "挪威語",
"metadata.languageMap.pl": "波蘭語", "metadata.languageMap.pl": "波蘭語",
"metadata.languageMap.pt": "葡萄牙語", "metadata.languageMap.pt": "葡萄牙語",
"metadata.languageMap.ro": "羅馬尼亞語",
"metadata.languageMap.ru": "俄語", "metadata.languageMap.ru": "俄語",
"metadata.languageMap.sv": "瑞典語", "metadata.languageMap.sv": "瑞典語",
"metadata.languageMap.th": "泰語", "metadata.languageMap.th": "泰語",