From e3fcee124aae5b283bbcda5b634fe99320363ac1 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 11 Jun 2025 22:47:31 +0800 Subject: [PATCH] agent node --- .../tool-selector/index.tsx | 31 +- .../tool-selector/reasoning-config-form.tsx | 280 ++++++++++-------- .../components/tools/utils/to-form-schema.ts | 77 +++-- .../_base/components/form-input-item.tsx | 2 +- .../workflow/nodes/agent/default.ts | 18 +- .../components/workflow/nodes/agent/types.ts | 1 + .../workflow/utils/workflow-init.ts | 8 + 7 files changed, 232 insertions(+), 185 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index ec35db953a..fb047e195c 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next' import Link from 'next/link' import { RiArrowLeftLine, - RiArrowRightUpLine, } from '@remixicon/react' import { PortalToFollowElem, @@ -15,6 +14,7 @@ import { import ToolTrigger from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger' import ToolItem from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-item' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' +import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form' import Button from '@/app/components/base/button' import Indicator from '@/app/components/header/indicator' import ToolCredentialForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form' @@ -23,8 +23,7 @@ import Textarea from '@/app/components/base/textarea' import Divider from '@/app/components/base/divider' import TabSlider from '@/app/components/base/tab-slider-plain' import ReasoningConfigForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form' -import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' -import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' +import { generateFormValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useAppContext } from '@/context/app-context' import { @@ -173,11 +172,9 @@ const ToolSelector: FC = ({ const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams]) const handleSettingsFormChange = (v: Record) => { - const newValue = getStructureValue(v) - const toolValue = { ...value, - settings: newValue, + settings: v, } onSelect(toolValue as any) } @@ -400,24 +397,12 @@ const ToolSelector: FC = ({ {/* user settings form */} {(currType === 'settings' || userSettingsOnly) && (
-
item.url - ? ( - {t('tools.howToGet')} - - ) - : null} />
)} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx index 017e0eb7aa..0dae8f64e1 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -7,17 +7,22 @@ import { } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' import Switch from '@/app/components/base/switch' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import Input from '@/app/components/base/input' +import FormInputTypeSwitch from '@/app/components/workflow/nodes/_base/components/form-input-type-switch' +import FormInputBoolean from '@/app/components/workflow/nodes/_base/components/form-input-boolean' +import { SimpleSelect } from '@/app/components/base/select' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Node } from 'reactflow' import type { NodeOutPutVar, ValueSelector, - Var, } from '@/app/components/workflow/types' import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' @@ -46,14 +51,13 @@ const ReasoningConfigForm: React.FC = ({ }) => { const { t } = useTranslation() const language = useLanguage() - const handleAutomatic = (key: string, val: any) => { - onChange({ - ...value, - [key]: { - value: val ? null : value[key]?.value, - auto: val ? 1 : 0, - }, - }) + const getVarKindType = (type: FormTypeEnum) => { + if (type === FormTypeEnum.file || type === FormTypeEnum.files) + return VarKindType.variable + if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber) + return VarKindType.constant + if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput) + return VarKindType.mixed } const [inputsIsFocus, setInputsIsFocus] = useState>({}) @@ -67,52 +71,38 @@ const ReasoningConfigForm: React.FC = ({ }) } }, []) - const handleNotMixedTypeChange = useCallback((variable: string) => { - return (varValue: ValueSelector | string, varKindType: VarKindType) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - const target = draft[variable].value - if (target) { - target.type = varKindType - target.value = varValue - } - else { - draft[variable].value = { - type: varKindType, - value: varValue, - } - } - }) - onChange(newValue) - } - }, [value, onChange]) - const handleMixedTypeChange = useCallback((variable: string) => { - return (itemValue: string) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - const target = draft[variable].value - if (target) { - target.value = itemValue - } - else { - draft[variable].value = { - type: VarKindType.mixed, - value: itemValue, - } - } - }) - onChange(newValue) - } - }, [value, onChange]) - const handleFileChange = useCallback((variable: string) => { - return (varValue: ValueSelector | string) => { - const newValue = produce(value, (draft: ToolVarInputs) => { + + const handleAutomatic = (key: string, val: any, type: FormTypeEnum) => { + onChange({ + ...value, + [key]: { + value: val ? null : { type: getVarKindType(type), value: null }, + auto: val ? 1 : 0, + }, + }) + } + const handleTypeChange = useCallback((variable: string, defaultValue: any) => { + return (newType: VarKindType) => { + const res = produce(value, (draft: ToolVarInputs) => { draft[variable].value = { - type: VarKindType.variable, - value: varValue, + type: newType, + value: newType === VarKindType.variable ? '' : defaultValue, } }) - onChange(newValue) + onChange(res) } - }, [value, onChange]) + }, [onChange, value]) + const handleValueChange = useCallback((variable: string, varType: FormTypeEnum) => { + return (newValue: any) => { + const res = produce(value, (draft: ToolVarInputs) => { + draft[variable].value = { + type: getVarKindType(varType), + value: newValue, + } + }) + onChange(res) + } + }, [onChange, value]) const handleAppChange = useCallback((variable: string) => { return (app: { app_id: string @@ -136,6 +126,17 @@ const ReasoningConfigForm: React.FC = ({ onChange(newValue) } }, [onChange, value]) + const handleVariableSelectorChange = useCallback((variable: string) => { + return (newValue: ValueSelector | string) => { + const res = produce(value, (draft: ToolVarInputs) => { + draft[variable].value = { + type: VarKindType.variable, + value: newValue, + } + }) + onChange(res) + } + }, [onChange, value]) const [isShowSchema, { setTrue: showSchema, @@ -147,6 +148,7 @@ const ReasoningConfigForm: React.FC = ({ const renderField = (schema: any, showSchema: (schema: SchemaRoot, rootName: string) => void) => { const { + default: defaultValue, variable, label, required, @@ -155,6 +157,8 @@ const ReasoningConfigForm: React.FC = ({ scope, url, input_schema, + placeholder, + options, } = schema const auto = value[variable]?.auto const tooltipContent = (tooltip && ( @@ -166,28 +170,55 @@ const ReasoningConfigForm: React.FC = ({ asChild={false} /> )) const varInput = value[variable].value + const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput const isNumber = type === FormTypeEnum.textNumber - const isSelect = type === FormTypeEnum.select - const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const isObject = type === FormTypeEnum.object const isArray = type === FormTypeEnum.array - const isShowSchemaTooltip = isObject || isArray + const isShowJSONEditor = isObject || isArray + const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files + const isBoolean = type === FormTypeEnum.boolean + const isSelect = type === FormTypeEnum.select const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector - // const isToolSelector = type === FormTypeEnum.toolSelector - const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector && !isObject && !isArray - const valueType = (() => { - if (isNumber) return VarType.number - if (isSelect) return VarType.string - if (isFile) return VarType.file - if (isObject) return VarType.object - if (isArray) return VarType.array - - return VarType.string - })() + const showTypeSwitch = isNumber || isObject || isArray + const isConstant = varInput?.type === VarKindType.constant || !varInput?.type + const showVariableSelector = isFile || varInput?.type === VarKindType.variable + const targetVarType = () => { + if (isString) + return VarType.string + else if (isNumber) + return VarType.number + else if (type === FormTypeEnum.files) + return VarType.arrayFile + else if (type === FormTypeEnum.file) + return VarType.file + else if (isBoolean) + return VarType.boolean + else if (isObject) + return VarType.object + else if (isArray) + return VarType.arrayObject + else + return VarType.string + } + const getFilterVar = () => { + if (isNumber) + return (varPayload: any) => varPayload.type === VarType.number + else if (isString) + return (varPayload: any) => [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + else if (isFile) + return (varPayload: any) => [VarType.file, VarType.arrayFile].includes(varPayload.type) + else if (isBoolean) + return (varPayload: any) => varPayload.type === VarType.boolean + else if (isObject) + return (varPayload: any) => varPayload.type === VarType.object + else if (isArray) + return (varPayload: any) => [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(varPayload.type) + return undefined + } return ( -
+
{label[language] || label.en_US} @@ -196,8 +227,8 @@ const ReasoningConfigForm: React.FC = ({ )} {tooltipContent} ยท - {valueType} - {isShowSchemaTooltip && ( + {targetVarType()} + {isShowJSONEditor && ( {t('workflow.nodes.agent.clickToViewParameterSchema')} @@ -213,22 +244,25 @@ const ReasoningConfigForm: React.FC = ({ )}
-
handleAutomatic(variable, !auto)}> +
handleAutomatic(variable, !auto, type)}> {t('plugin.detailPanel.toolSelector.auto')} handleAutomatic(variable, val)} + onChange={val => handleAutomatic(variable, val, type)} />
{auto === 0 && ( - <> +
+ {showTypeSwitch && ( + + )} {isString && ( - = ({ placeholderClassName='!leading-[21px]' /> )} - {/* {isString && ( - varPayload.type === VarType.number || varPayload.type === VarType.secret || varPayload.type === VarType.string} - /> - )} */} - {(isNumber || isSelect) && ( - varPayload.type === schema._type : undefined} - availableVars={isSelect ? nodeOutputVars : undefined} - schema={schema} + onChange={handleValueChange(variable, type)} + placeholder={placeholder?.[language] || placeholder?.en_US} /> )} - {(isFile || isObject || isArray) && ( - { - if(isFile) - return varPayload.type === VarType.file || varPayload.type === VarType.arrayFile - if(isObject) - return varPayload.type === VarType.object - if(isArray) - return [VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type) - return true - }} + {isBoolean && ( + )} + {isSelect && ( + { + if (option.show_on.length) + return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + + return true + }).map((option: { value: any; label: { [x: string]: any; en_US: any } }) => ({ value: option.value, name: option.label[language] || option.label.en_US }))} + onSelect={item => handleValueChange(variable, type)(item.value as string)} + placeholder={placeholder?.[language] || placeholder?.en_US} + /> + )} + {isShowJSONEditor && isConstant && ( +
+ {placeholder?.[language] || placeholder?.en_US}
} + /> +
+ )} {isAppSelector && ( = ({ scope={scope} /> )} - + {showVariableSelector && ( + + )} +
)} {url && ( , formSchemas: { varia return newValues } -export const generateFormValue = (value: Record, formSchemas: { variable: string; default?: any }[], isReasoning = false) => { +const correctInitialData = (type: string, target: any, defaultValue: any) => { + if (type === 'text-input' || type === 'secret-input') + target.type = 'mixed' + + if (type === 'boolean') { + if (typeof defaultValue === 'string') + target.value = defaultValue === 'true' || defaultValue === '1' + + if (typeof defaultValue === 'boolean') + target.value = defaultValue + + if (typeof defaultValue === 'number') + target.value = defaultValue === 1 + } + + if (type === 'number-input') { + if (typeof defaultValue === 'string' && defaultValue !== '') + target.value = Number.parseFloat(defaultValue) + } + + if (type === 'app-selector' || type === 'model-selector') + target.value = defaultValue + + return target +} + +export const generateFormValue = (value: Record, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { const newValues = {} as any formSchemas.forEach((formSchema) => { const itemValue = value[formSchema.variable] if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) { + const value = formSchema.default newValues[formSchema.variable] = { - ...(isReasoning ? { value: null, auto: 1 } : { value: formSchema.default }), + value: { + type: 'constant', + value: formSchema.default, + }, + ...(isReasoning ? { auto: 1, value: null } : {}), } + if (!isReasoning) + newValues[formSchema.variable].value = correctInitialData(formSchema.type, newValues[formSchema.variable].value, value) } }) return newValues } -export const getPlainValue = (value: Record) => { - const plainValue = { ...value } - Object.keys(plainValue).forEach((key) => { - plainValue[key] = value[key].value - }) - return plainValue -} - -export const getStructureValue = (value: Record) => { - const newValue = { ...value } as any - Object.keys(newValue).forEach((key) => { - newValue[key] = { - value: value[key], - } - }) - return newValue -} - export const getConfiguredValue = (value: Record, formSchemas: { variable: string; type: string; default?: any }[]) => { const newValues = { ...value } formSchemas.forEach((formSchema) => { @@ -105,27 +120,7 @@ export const getConfiguredValue = (value: Record, formSchemas: { va type: 'constant', value: formSchema.default, } - if (formSchema.type === 'text-input' || formSchema.type === 'secret-input') - newValues[formSchema.variable].type = 'mixed' - - if (formSchema.type === 'boolean') { - if (typeof value === 'string') - newValues[formSchema.variable].value = value === 'true' || value === '1' - - if (typeof value === 'boolean') - newValues[formSchema.variable].value = value - - if (typeof value === 'number') - newValues[formSchema.variable].value = value === 1 - } - - if (formSchema.type === 'number-input') { - if (typeof value === 'string' && value !== '') - newValues[formSchema.variable].value = Number.parseFloat(value) - } - - if (formSchema.type === 'app-selector' || formSchema.type === 'model-selector') - newValues[formSchema.variable] = value + newValues[formSchema.variable] = correctInitialData(formSchema.type, newValues[formSchema.variable], value) } }) return newValues diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 5e934b439b..c0468a5ecc 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -117,7 +117,7 @@ const FormInputItem: FC = ({ const getVarKindType = () => { if (isFile) return VarKindType.variable - if (isSelect || isAppSelector || isModelSelector || isBoolean) + if (isSelect || isBoolean || isNumber) return VarKindType.constant if (isString) return VarKindType.mixed diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 4e7b447de8..55cb7de377 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -7,6 +7,7 @@ import { renderI18nObject } from '@/i18n' const nodeDefault: NodeDefault = { defaultValue: { + version: '2', }, getAvailablePrevNodes(isChatMode) { return isChatMode @@ -60,15 +61,28 @@ const nodeDefault: NodeDefault = { const schemas = toolValue.schemas || [] const userSettings = toolValue.settings const reasoningConfig = toolValue.parameters + const version = payload.version schemas.forEach((schema: any) => { if (schema?.required) { - if (schema.form === 'form' && !userSettings[schema.name]?.value) { + if (schema.form === 'form' && !version && !userSettings[schema.name]?.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), } } - if (schema.form === 'llm' && reasoningConfig[schema.name].auto === 0 && !userSettings[schema.name]?.value) { + if (schema.form === 'form' && version && !userSettings[schema.name]?.value.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + if (schema.form === 'llm' && !version && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + if (schema.form === 'llm' && version && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index e50586bd27..5a13a4a4f3 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -11,6 +11,7 @@ export type AgentNodeType = CommonNodeType & { output_schema: Record plugin_unique_identifier?: string memory?: Memory + version?: string } export enum AgentFeature { diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index 8610bbe9d1..cd4ab1a095 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -27,6 +27,7 @@ import type { QuestionClassifierNodeType } from '../nodes/question-classifier/ty import type { IfElseNodeType } from '../nodes/if-else/types' import { branchNameCorrect } from '../nodes/if-else/utils' import type { IterationNodeType } from '../nodes/iteration/types' +import type { AgentNodeType } from '../nodes/agent/types' import type { LoopNodeType } from '../nodes/loop/types' import type { ToolNodeType } from '../nodes/tool/types' import { @@ -304,6 +305,13 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { } } + if (node.data.type === BlockEnum.Agent && !(node as Node).data.version) { + // TODO: formatting legacy agent node data + // (node as Node).data.version = '2' + // const toolData = (node as Node).data.agent_parameters?.tool + // const multipleTools = (node as Node).data.agent_parameters?.multiple_tools + } + return node }) }