diff --git a/web/app/components/plugins/card/base/title.tsx b/web/app/components/plugins/card/base/title.tsx index bfdcd7fc2b..383e7b31c1 100644 --- a/web/app/components/plugins/card/base/title.tsx +++ b/web/app/components/plugins/card/base/title.tsx @@ -4,7 +4,7 @@ const Title = ({ title: string }) => { return ( -
+
{title}
) diff --git a/web/app/components/plugins/card/index.tsx b/web/app/components/plugins/card/index.tsx index 235f4d4953..ff86a3c39c 100644 --- a/web/app/components/plugins/card/index.tsx +++ b/web/app/components/plugins/card/index.tsx @@ -12,6 +12,7 @@ import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' import { getLanguage } from '@/i18n/language' import { useCategories } from '../hooks' +import { renderI18nObject } from '@/hooks/use-i18n' export type Props = { className?: string @@ -47,7 +48,7 @@ const Card = ({ const isBundle = !['plugin', 'model', 'tool', 'extension', 'agent_strategy'].includes(type) const cornerMark = isBundle ? categoriesMap.bundle?.label : categoriesMap[category]?.label const getLocalizedText = (obj: Record | undefined) => - obj?.[locale] || obj?.['en-US'] || obj?.en_US || '' + obj ? renderI18nObject(obj, locale) : '' const wrapClassName = cn('relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className) if (isLoading) { diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 430ceae7de..d997299844 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -94,7 +94,7 @@ const PluginItem: FC = ({
{verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />} - <Badge className='ml-1' text={source === PluginSource.github ? plugin.meta!.version : plugin.version} /> + <Badge className='shrink-0 ml-1' text={source === PluginSource.github ? plugin.meta!.version : plugin.version} /> </div> <div className='flex items-center justify-between'> <Description text={description[locale]} descriptionLineRows={1}></Description> diff --git a/web/app/components/workflow/block-selector/index-bar.tsx b/web/app/components/workflow/block-selector/index-bar.tsx index 3c5bf8d6e2..8d4b3de10e 100644 --- a/web/app/components/workflow/block-selector/index-bar.tsx +++ b/web/app/components/workflow/block-selector/index-bar.tsx @@ -6,6 +6,7 @@ import classNames from '@/utils/classnames' export const CUSTOM_GROUP_NAME = '@@@custom@@@' export const WORKFLOW_GROUP_NAME = '@@@workflow@@@' +export const AGENT_GROUP_NAME = '@@@agent@@@' /* { A: { @@ -46,8 +47,10 @@ export const groupItems = (items: ToolWithProvider[], getFirstChar: (item: ToolW groupName = item.author else if (item.type === CollectionType.custom) groupName = CUSTOM_GROUP_NAME - else + else if (item.type === CollectionType.workflow) groupName = WORKFLOW_GROUP_NAME + else + groupName = AGENT_GROUP_NAME if (!acc[letter][groupName]) acc[letter][groupName] = [] diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx index 8bf5095833..a8fd34b98a 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx @@ -6,7 +6,7 @@ import type { BlockEnum } from '../../../types' import type { ToolDefaultValue } from '../../types' import Item from './item' import { useTranslation } from 'react-i18next' -import { CUSTOM_GROUP_NAME, WORKFLOW_GROUP_NAME } from '../../index-bar' +import { AGENT_GROUP_NAME, CUSTOM_GROUP_NAME, WORKFLOW_GROUP_NAME } from '../../index-bar' type Props = { payload: Record<string, ToolWithProvider[]> @@ -27,6 +27,9 @@ const ToolListTreeView: FC<Props> = ({ if (name === WORKFLOW_GROUP_NAME) return t('workflow.tabs.workflowTool') + if (name === AGENT_GROUP_NAME) + return t('workflow.tabs.agent') + return name }, [t]) diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index 060f6dfa2c..5b5d1da20b 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -60,7 +60,6 @@ const Blocks = ({ Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => { if (!result[groupName]) result[groupName] = [] - result[groupName].push(...withLetterAndGroupViewToolsData[letter][groupName]) }) }) diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index 36201ddfef..9646b0da87 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -24,6 +24,7 @@ import { useNodesExtraData } from './use-nodes-data' import { useToastContext } from '@/app/components/base/toast' import { CollectionType } from '@/app/components/tools/types' import { useGetLanguage } from '@/context/i18n' +import type { AgentNodeType } from '../nodes/agent/types' export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { t } = useTranslation() @@ -33,6 +34,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const buildInTools = useStore(s => s.buildInTools) const customTools = useStore(s => s.customTools) const workflowTools = useStore(s => s.workflowTools) + const agentStrategies = useStore(s => s.agentStrategies) const needWarningNodes = useMemo(() => { const list = [] @@ -57,6 +59,18 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { toolIcon = workflowTools.find(tool => tool.id === node.data.provider_id)?.icon } + if (node.data.type === BlockEnum.Agent) { + const data = node.data as AgentNodeType + const provider = agentStrategies.find(s => s.plugin_unique_identifier === data.plugin_unique_identifier) + const strategy = provider?.declaration.strategies.find(s => s.identity.name === data.agent_strategy_name) + // debugger + moreDataForCheckValid = { + provider, + strategy, + language, + } + } + if (node.type === CUSTOM_NODE) { const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t, moreDataForCheckValid) @@ -92,7 +106,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { } return list - }, [t, nodes, edges, nodesExtraData, buildInTools, customTools, workflowTools, language, isChatMode]) + }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, agentStrategies]) return needWarningNodes } diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 0f6ae59b6e..8ce31b8acf 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -58,6 +58,7 @@ import I18n from '@/context/i18n' import { CollectionType } from '@/app/components/tools/types' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' import { useWorkflowConfig } from '@/service/use-workflow' +import { fetchStrategyList } from '@/service/strategy' export const useIsChatMode = () => { const appDetail = useAppStore(s => s.appDetail) @@ -459,6 +460,21 @@ export const useFetchToolsData = () => { } } +export const useFetchAgentStrategy = () => { + const workflowStore = useWorkflowStore() + const handleFetchAllAgentStrategies = useCallback(async () => { + const agentStrategies = await fetchStrategyList() + + workflowStore.setState({ + agentStrategies: agentStrategies || [], + }) + }, [workflowStore]) + + return { + handleFetchAllAgentStrategies, + } +} + export const useWorkflowInit = () => { const workflowStore = useWorkflowStore() const { @@ -466,6 +482,7 @@ export const useWorkflowInit = () => { edges: edgesTemplate, } = useWorkflowTemplate() const { handleFetchAllTools } = useFetchToolsData() + const { handleFetchAllAgentStrategies } = useFetchAgentStrategy() const appDetail = useAppStore(state => state.appDetail)! const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash) const [data, setData] = useState<FetchWorkflowDraftResponse>() @@ -545,7 +562,8 @@ export const useWorkflowInit = () => { handleFetchAllTools('builtin') handleFetchAllTools('custom') handleFetchAllTools('workflow') - }, [handleFetchPreloadData, handleFetchAllTools]) + handleFetchAllAgentStrategies() + }, [handleFetchPreloadData, handleFetchAllTools, handleFetchAllAgentStrategies]) useEffect(() => { if (data) { diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index 9a4944080b..1cf9fc23ef 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -1,5 +1,5 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' -import { useMemo, useState } from 'react' +import { memo, useMemo, useState } from 'react' import type { Strategy } from './agent-strategy' import classNames from '@/utils/classnames' import { RiArrowDownSLine, RiArrowRightUpLine, RiErrorWarningFill } from '@remixicon/react' @@ -38,7 +38,7 @@ const ExternalNotInstallWarn = () => { function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => string): ToolWithProvider[] { return input.map((item) => { const res: ToolWithProvider = { - id: item.provider, + id: item.plugin_unique_identifier, author: item.declaration.identity.author, name: item.declaration.identity.name, description: item.declaration.identity.description as any, @@ -69,7 +69,7 @@ export type AgentStrategySelectorProps = { onChange: (value?: Strategy) => void, } -export const AgentStrategySelector = (props: AgentStrategySelectorProps) => { +export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => { const { value, onChange } = props const [open, setOpen] = useState(false) const [viewType, setViewType] = useState<ViewType>(ViewType.flat) @@ -126,6 +126,7 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => { agent_strategy_provider_name: tool!.provider_name, agent_strategy_label: tool!.tool_label, agent_output_schema: tool!.output_schema, + plugin_unique_identifier: tool!.provider_id, }) setOpen(false) }} @@ -147,4 +148,6 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => { </div> */} </PortalToFollowElemContent> </PortalToFollowElem> -} +}) + +AgentStrategySelector.displayName = 'AgentStrategySelector' diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index 454c84833b..4ec46a6d61 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -12,16 +12,20 @@ import Slider from '@/app/components/base/slider' import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' import Field from './field' -import type { ComponentProps } from 'react' -import { useDefaultModel, useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { type ComponentProps, memo } from 'react' +import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import Editor from './prompt/editor' import { useWorkflowStore } from '../../../store' +import { useRenderI18nObject } from '@/hooks/use-i18n' +import type { NodeOutPutVar } from '../../../types' +import type { Node } from 'reactflow' export type Strategy = { agent_strategy_provider_name: string agent_strategy_name: string agent_strategy_label: string agent_output_schema: Record<string, any> + plugin_unique_identifier: string } export type AgentStrategyProps = { @@ -30,6 +34,8 @@ export type AgentStrategyProps = { formSchema: CredentialFormSchema[] formValue: ToolVarInputs onFormValueChange: (value: ToolVarInputs) => void + nodeOutputVars?: NodeOutPutVar[], + availableNodes?: Node[], } type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field @@ -47,11 +53,11 @@ type StringSchema = CustomSchema<'string', { type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema | StringSchema -export const AgentStrategy = (props: AgentStrategyProps) => { - const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props +export const AgentStrategy = memo((props: AgentStrategyProps) => { + const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes } = props const { t } = useTranslation() - const language = useLanguage() const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration) + const renderI18nObject = useRenderI18nObject() const workflowStore = useWorkflowStore() const { setControlPromptEditorRerenderKey, @@ -70,7 +76,7 @@ export const AgentStrategy = (props: AgentStrategyProps) => { const onChange = (value: number) => { props.onChange({ ...props.value, [schema.variable]: value }) } - return <Field title={def.label[language]} tooltip={def.tooltip?.[language]} inline> + return <Field title={renderI18nObject(def.label)} tooltip={def.tooltip && renderI18nObject(def.tooltip)} inline> <div className='flex w-[200px] items-center gap-3'> <Slider value={value} @@ -103,7 +109,7 @@ export const AgentStrategy = (props: AgentStrategyProps) => { props.onChange({ ...props.value, [schema.variable]: value }) } return ( - <Field title={schema.label[language]} tooltip={schema.tooltip?.[language]}> + <Field title={renderI18nObject(schema.label)} tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}> <ToolSelector scope={schema.scope} value={value} @@ -122,8 +128,8 @@ export const AgentStrategy = (props: AgentStrategyProps) => { <MultipleToolSelector scope={schema.scope} value={value || []} - label={schema.label[language]} - tooltip={schema.tooltip?.[language]} + label={renderI18nObject(schema.label)} + tooltip={schema.tooltip && renderI18nObject(schema.tooltip)} onChange={onChange} supportCollapse /> @@ -142,13 +148,15 @@ export const AgentStrategy = (props: AgentStrategyProps) => { value={value} onChange={onChange} onGenerated={handleGenerated} - title={schema.label[language]} + title={renderI18nObject(schema.label)} headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase' containerClassName='bg-transparent' gradientBorder={false} isSupportPromptGenerator={!!schema.auto_generate?.type} - titleTooltip={schema.tooltip?.[language]} + titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)} editorContainerClassName='px-0' + availableNodes={availableNodes} + nodesOutputVars={nodeOutputVars} isSupportJinja={schema.template?.enabled} varList={[]} modelConfig={ @@ -197,4 +205,6 @@ export const AgentStrategy = (props: AgentStrategyProps) => { /> } </div> -} +}) + +AgentStrategy.displayName = 'AgentStrategy' diff --git a/web/app/components/workflow/nodes/_base/components/setting-item.tsx b/web/app/components/workflow/nodes/_base/components/setting-item.tsx index fdaadc476f..ca074ffbb7 100644 --- a/web/app/components/workflow/nodes/_base/components/setting-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/setting-item.tsx @@ -1,7 +1,7 @@ import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' import classNames from '@/utils/classnames' -import type { ComponentProps, PropsWithChildren, ReactNode } from 'react' +import { type ComponentProps, type PropsWithChildren, type ReactNode, memo } from 'react' export type SettingItemProps = PropsWithChildren<{ label: string @@ -9,7 +9,7 @@ export type SettingItemProps = PropsWithChildren<{ tooltip?: ReactNode }> -export const SettingItem = ({ label, children, status, tooltip }: SettingItemProps) => { +export const SettingItem = memo(({ label, children, status, tooltip }: SettingItemProps) => { const indicator: ComponentProps<typeof Indicator>['color'] = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined const needTooltip = ['error', 'warning'].includes(status as any) return <div className='flex items-center justify-between bg-workflow-block-parma-bg rounded-md py-1 px-1.5 space-x-1 text-xs font-normal relative'> @@ -23,4 +23,6 @@ export const SettingItem = ({ label, children, status, tooltip }: SettingItemPro </Tooltip> {indicator && <Indicator color={indicator} className='absolute -right-0.5 -top-0.5' />} </div> -} +}) + +SettingItem.displayName = 'SettingItem' diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index 893472af5e..4228c5694d 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -1,7 +1,7 @@ import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' import classNames from '@/utils/classnames' -import { useMemo, useRef } from 'react' +import { memo, useMemo, useRef } from 'react' import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools' export type ToolIconProps = { @@ -10,7 +10,7 @@ export type ToolIconProps = { providerName: string } -export const ToolIcon = ({ status, tooltip, providerName }: ToolIconProps) => { +export const ToolIcon = memo(({ status, tooltip, providerName }: ToolIconProps) => { const indicator = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined const containerRef = useRef<HTMLDivElement>(null) const notSuccess = (['error', 'warning'] as Array<ToolIconProps['status']>).includes(status) @@ -41,4 +41,6 @@ export const ToolIcon = ({ status, tooltip, providerName }: ToolIconProps) => { {indicator && <Indicator color={indicator} className="absolute right-[-1px] top-[-1px]" />} </div> </Tooltip> -} +}) + +ToolIcon.displayName = 'ToolIcon' diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index c748379cef..da1cba4adc 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -1,6 +1,8 @@ +import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '../../constants' import type { NodeDefault } from '../../types' import type { AgentNodeType } from './types' +import { renderI18nObject } from '@/hooks/use-i18n' const nodeDefault: NodeDefault<AgentNodeType> = { defaultValue: { @@ -15,16 +17,29 @@ const nodeDefault: NodeDefault<AgentNodeType> = { ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS }, - checkValid(payload, t, moreDataForCheckValid) { - let isValid = true - let errorMessages = '' - if (payload.type) { - isValid = true - errorMessages = '' + checkValid(payload, t, moreDataForCheckValid: { + strategyProvider: StrategyPluginDetail | undefined, + strategy: StrategyDetail | undefined + language: string + }) { + const { strategy, language } = moreDataForCheckValid + if (!strategy) { + return { + isValid: false, + errorMessage: t('workflow.checkList.strategyNotSelected'), + } + } + for (const param of strategy.parameters) { + if (param.required && !payload.agent_parameters?.[param.name]?.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.fieldRequired', { field: renderI18nObject(param.label, language) }), + } + } } return { - isValid, - errorMessage: errorMessages, + isValid: true, + errorMessage: '', } }, } diff --git a/web/app/components/workflow/nodes/agent/node.tsx b/web/app/components/workflow/nodes/agent/node.tsx index cef0a4330b..2cf9c67233 100644 --- a/web/app/components/workflow/nodes/agent/node.tsx +++ b/web/app/components/workflow/nodes/agent/node.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react' +import { type FC, memo, useMemo } from 'react' import type { NodeProps } from '../../types' import type { AgentNodeType } from './types' import { SettingItem } from '../_base/components/setting-item' @@ -10,6 +10,7 @@ import useConfig from './use-config' import { useTranslation } from 'react-i18next' import { FormTypeEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useRenderI18nObject } from '@/hooks/use-i18n' const useAllModel = () => { const { data: textGeneration } = useModelList(ModelTypeEnum.textGeneration) @@ -32,7 +33,8 @@ const useAllModel = () => { } const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { - const { inputs, currentStrategy } = useConfig(props.id, props.data) + const { inputs, currentStrategy, currentStrategyStatus, pluginDetail } = useConfig(props.id, props.data) + const renderI18nObject = useRenderI18nObject() const { t } = useTranslation() const modelList = useAllModel() const models = useMemo(() => { @@ -87,9 +89,16 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { {inputs.agent_strategy_name ? <SettingItem label={t('workflow.nodes.agent.strategy.shortLabel')} - status='error' - tooltip={t('workflow.nodes.agent.strategyNotInstallTooltip', { + status={ + ['plugin-not-found', 'strategy-not-found'].includes(currentStrategyStatus) + ? 'error' + : undefined + } + tooltip={t(`workflow.nodes.agent.${currentStrategyStatus === 'plugin-not-found' ? 'strategyNotInstallTooltip' : 'strategyNotFoundInPlugin'}`, { strategy: inputs.agent_strategy_label, + plugin: pluginDetail?.declaration.label + ? renderI18nObject(pluginDetail?.declaration.label) + : undefined, })} > {inputs.agent_strategy_label} @@ -126,4 +135,6 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { </div> } -export default AgentNode +AgentNode.displayName = 'AgentNode' + +export default memo(AgentNode) diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index af4ce6c863..ab8ff1c4b9 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import { useMemo } from 'react' +import { memo, useMemo } from 'react' import type { NodePanelProps } from '../../types' import type { AgentNodeType } from './types' import Field from '../_base/components/field' @@ -33,6 +33,9 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { formData, onFormChange, + availableNodesWithParent, + availableVars, + isShowSingleRun, hideSingleRun, runningStatus, @@ -75,6 +78,7 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { agent_strategy_name: inputs.agent_strategy_name!, agent_strategy_label: inputs.agent_strategy_label!, agent_output_schema: inputs.output_schema, + plugin_unique_identifier: inputs.plugin_unique_identifier!, } : undefined} onStrategyChange={(strategy) => { setInputs({ @@ -83,11 +87,14 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { agent_strategy_name: strategy?.agent_strategy_name, agent_strategy_label: strategy?.agent_strategy_label, output_schema: strategy!.agent_output_schema, + plugin_unique_identifier: strategy!.plugin_unique_identifier, }) }} formSchema={currentStrategy?.parameters?.map(strategyParamToCredientialForm) || []} formValue={formData} onFormValueChange={onFormChange} + nodeOutputVars={availableVars} + availableNodes={availableNodesWithParent} /> </Field> <div> @@ -135,4 +142,6 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { </div> } -export default AgentPanel +AgentPanel.displayName = 'AgentPanel' + +export default memo(AgentPanel) diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index e75079ae8c..1b5c96364f 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -7,4 +7,5 @@ export type AgentNodeType = CommonNodeType & { agent_strategy_label?: string agent_parameters?: ToolVarInputs output_schema: Record<string, any> + plugin_unique_identifier?: string } diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index a1f96e33a2..45dd648989 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -6,8 +6,12 @@ import type { AgentNodeType } from './types' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { type ToolVarInputs, VarType } from '../tool/types' +import { useCheckInstalled } from '@/service/use-plugins' +import type { Var } from '../../types' +import { VarType as VarKindType } from '../../types' +import useAvailableVarList from '../_base/hooks/use-available-var-list' const useConfig = (id: string, payload: AgentNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() @@ -20,16 +24,20 @@ const useConfig = (id: string, payload: AgentNodeType) => { const strategyProvider = useStrategyProviderDetail( inputs.agent_strategy_provider_name || '', ) - const currentStrategy = strategyProvider.data?.declaration.strategies.find( str => str.identity.name === inputs.agent_strategy_name, ) - const currentStrategyStatus = useMemo(() => { + const currentStrategyStatus: 'loading' | 'plugin-not-found' | 'strategy-not-found' | 'success' = useMemo(() => { if (strategyProvider.isLoading) return 'loading' if (strategyProvider.isError) return 'plugin-not-found' if (!currentStrategy) return 'strategy-not-found' return 'success' }, [currentStrategy, strategyProvider]) + const pluginId = inputs.agent_strategy_provider_name?.split('/').splice(0, 2).join('/') + const pluginDetail = useCheckInstalled({ + pluginIds: [pluginId || ''], + enabled: Boolean(pluginId), + }) const formData = useMemo(() => { return Object.fromEntries( Object.entries(inputs.agent_parameters || {}).map(([key, value]) => { @@ -51,6 +59,30 @@ const useConfig = (id: string, payload: AgentNodeType) => { }) } + // vars + + const filterMemoryPromptVar = useCallback((varPayload: Var) => { + return [ + VarKindType.arrayObject, + VarKindType.array, + VarKindType.number, + VarKindType.string, + VarKindType.secret, + VarKindType.arrayString, + VarKindType.arrayNumber, + VarKindType.file, + VarKindType.arrayFile, + ].includes(varPayload.type) + }, []) + + const { + availableVars, + availableNodesWithParent, + } = useAvailableVarList(id, { + onlyLeafNodeVar: false, + filterVar: filterMemoryPromptVar, + }) + // single run const { isShowSingleRun, @@ -70,7 +102,9 @@ const useConfig = (id: string, payload: AgentNodeType) => { defaultRunInputData: {}, }) const allVarStrArr = (() => { - const arr = [''] + const arr = currentStrategy?.parameters.filter(item => item.type === 'string').map((item) => { + return formData[item.name] + }) || [] return arr })() @@ -91,6 +125,9 @@ const useConfig = (id: string, payload: AgentNodeType) => { onFormChange, currentStrategyStatus, strategyProvider: strategyProvider.data, + pluginDetail: pluginDetail.data?.plugins.at(0), + availableVars, + availableNodesWithParent, isShowSingleRun, showSingleRun, diff --git a/web/app/components/workflow/nodes/agent/utils.ts b/web/app/components/workflow/nodes/agent/utils.ts deleted file mode 100644 index 25beff3eb2..0000000000 --- a/web/app/components/workflow/nodes/agent/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { AgentNodeType } from './types' - -export const checkNodeValid = (payload: AgentNodeType) => { - return true -} diff --git a/web/app/components/workflow/run/utils/format-log/agent/data.ts b/web/app/components/workflow/run/utils/format-log/agent/data.ts index d64670a015..a1e06bf63b 100644 --- a/web/app/components/workflow/run/utils/format-log/agent/data.ts +++ b/web/app/components/workflow/run/utils/format-log/agent/data.ts @@ -1,5 +1,4 @@ import { BlockEnum } from '@/app/components/workflow/types' -import { has } from 'immer/dist/internal' export const agentNodeData = (() => { const node = { @@ -120,7 +119,6 @@ export const oneStepCircle = (() => { ], }], } - })() export const multiStepsCircle = (() => { @@ -138,13 +136,13 @@ export const multiStepsCircle = (() => { { id: '1', parent_id: '4', label: 'Node 1' }, { id: '2', parent_id: '1', label: 'Node 2' }, { id: '4', parent_id: '2', label: 'Node 4' }, - // { id: '1', parent_id: '4', label: 'Node 1' }, - // { id: '2', parent_id: '1', label: 'Node 2' }, - // { id: '4', parent_id: '2', label: 'Node 4' }, + { id: '1', parent_id: '4', label: 'Node 1' }, + { id: '2', parent_id: '1', label: 'Node 2' }, + { id: '4', parent_id: '2', label: 'Node 4' }, ], }, } - + // 1 -> [2(4(1(2(4...)))), 3] return { in: [node], expect: [{ @@ -165,7 +163,7 @@ export const multiStepsCircle = (() => { label: 'Node 4', children: [], hasCircle: true, - } + }, ], }, { diff --git a/web/app/components/workflow/run/utils/format-log/agent/index.ts b/web/app/components/workflow/run/utils/format-log/agent/index.ts index 65c7f6d36e..c1f3afc20a 100644 --- a/web/app/components/workflow/run/utils/format-log/agent/index.ts +++ b/web/app/components/workflow/run/utils/format-log/agent/index.ts @@ -5,24 +5,26 @@ import { cloneDeep } from 'lodash-es' const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool] const remove = (node: AgentLogItemWithChildren, removeId: string) => { - const { children } = node - if (!children || children.length === 0) { + let { children } = node + if (!children || children.length === 0) return + + const hasCircle = !!children.find(c => c.id === removeId) + if (hasCircle) { + node.hasCircle = true + node.children = node.children.filter(c => c.id !== removeId) + children = node.children } - children.forEach((child, index) => { - if (child.id === removeId) { - node.hasCircle = true - children.splice(index, 1) - return - } + + children.forEach((child) => { remove(child, removeId) }) } const removeRepeatedSiblings = (list: AgentLogItemWithChildren[]) => { - if (!list || list.length === 0) { + if (!list || list.length === 0) return [] - } + const result: AgentLogItemWithChildren[] = [] const addedItemIds: string[] = [] list.forEach((item) => { @@ -35,19 +37,18 @@ const removeRepeatedSiblings = (list: AgentLogItemWithChildren[]) => { } const removeCircleLogItem = (log: AgentLogItemWithChildren) => { - let newLog = cloneDeep(log) + const newLog = cloneDeep(log) newLog.children = removeRepeatedSiblings(newLog.children) let { id, children } = newLog - if (!children || children.length === 0) { + if (!children || children.length === 0) return log - } + // check one step circle const hasOneStepCircle = !!children.find(c => c.id === id) if (hasOneStepCircle) { newLog.hasCircle = true newLog.children = newLog.children.filter(c => c.id !== id) children = newLog.children - } children.forEach((child, index) => { @@ -85,6 +86,7 @@ const format = (list: NodeTracing[]): NodeTracing[] => { let removedCircleTree: AgentLogItemWithChildren[] = [] if (supportedAgentLogNodes.includes(item.node_type) && item.execution_metadata?.agent_log && item.execution_metadata?.agent_log.length > 0) treeList = listToTree(item.execution_metadata.agent_log) + // console.log(JSON.stringify(treeList)) removedCircleTree = treeList.length > 0 ? treeList.map(t => removeCircleLogItem(t)) : [] item.agentLog = removedCircleTree diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 6bd47eaa01..b05c6676c0 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -22,6 +22,9 @@ import type { } from './types' import { WorkflowContext } from './context' import type { NodeTracing, VersionHistory } from '@/types/workflow' +import type { + StrategyPluginDetail, +} from '@/app/components/plugins/types' // #TODO chatVar# // const MOCK_DATA = [ @@ -98,6 +101,7 @@ type Shape = { setCustomTools: (tools: ToolWithProvider[]) => void workflowTools: ToolWithProvider[] setWorkflowTools: (tools: ToolWithProvider[]) => void + agentStrategies: StrategyPluginDetail[], clipboardElements: Node[] setClipboardElements: (clipboardElements: Node[]) => void showDebugAndPreviewPanel: boolean @@ -230,6 +234,7 @@ export const createWorkflowStore = () => { setCustomTools: customTools => set(() => ({ customTools })), workflowTools: [], setWorkflowTools: workflowTools => set(() => ({ workflowTools })), + agentStrategies: [], clipboardElements: [], setClipboardElements: clipboardElements => set(() => ({ clipboardElements })), showDebugAndPreviewPanel: false, diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index 46ac4ce1c6..bdf096491e 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -395,6 +395,7 @@ export const canRunBySingle = (nodeType: BlockEnum) => { || nodeType === BlockEnum.Tool || nodeType === BlockEnum.ParameterExtractor || nodeType === BlockEnum.Iteration + || nodeType === BlockEnum.Agent } type ConnectedSourceOrTargetNodesChange = { diff --git a/web/hooks/use-i18n.ts b/web/hooks/use-i18n.ts new file mode 100644 index 0000000000..261293c86d --- /dev/null +++ b/web/hooks/use-i18n.ts @@ -0,0 +1,14 @@ +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' + +export const renderI18nObject = (obj: Record<string, string>, language: string) => { + if (obj?.[language]) return obj[language] + if (obj?.en_US) return obj.en_US + return Object.values(obj)[0] +} + +export const useRenderI18nObject = () => { + const language = useLanguage() + return (obj: Record<string, string>) => { + return renderI18nObject(obj, language) + } +} diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 5b5df3843e..c2f9685036 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -218,6 +218,7 @@ const translation = { 'transform': 'Transform', 'utilities': 'Utilities', 'noResult': 'No match found', + 'agent': 'Agent Strategy', }, blocks: { 'start': 'Start', @@ -732,6 +733,7 @@ const translation = { toolNotInstallTooltip: '{{tool}} is not installed', toolNotAuthorizedTooltip: '{{tool}} Not Authorized', strategyNotInstallTooltip: '{{strategy}} is not installed', + strategyNotFoundInPlugin: '{{strategy}} is not found in {{plugin}}', modelSelectorTooltips: { deprecated: 'This model is deprecated', }, @@ -747,6 +749,9 @@ const translation = { json: 'agent generated json', }, }, + checkList: { + strategyNotSelected: 'Strategy not selected', + }, }, tracing: { stopBy: 'Stop by {{user}}', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index a86c746b3e..c72e97b588 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -218,6 +218,7 @@ const translation = { 'transform': '转换', 'utilities': '工具', 'noResult': '未找到匹配项', + 'agent': 'Agent 策略', }, blocks: { 'start': '开始', @@ -732,6 +733,7 @@ const translation = { toolNotInstallTooltip: '{{tool}} 未安装', toolNotAuthorizedTooltip: '{{tool}} 未授权', strategyNotInstallTooltip: '{{strategy}} 未安装', + strategyNotFoundInPlugin: '在 {{plugin}} 中未找到 {{strategy}}', modelSelectorTooltips: { deprecated: '此模型已弃用', }, @@ -746,6 +748,9 @@ const translation = { }, json: 'agent 生成的json', }, + checkList: { + strategyNotSelected: '未选择策略', + }, }, }, tracing: { diff --git a/web/service/strategy.ts b/web/service/strategy.ts new file mode 100644 index 0000000000..bb032ba286 --- /dev/null +++ b/web/service/strategy.ts @@ -0,0 +1,10 @@ +import type { StrategyPluginDetail } from '@/app/components/plugins/types' +import { get } from './base' + +export const fetchStrategyList = () => { + return get<StrategyPluginDetail[]>('/workspaces/current/agent-providers') +} + +export const fetchStrategyDetail = (agentProvider: string) => { + return get<StrategyPluginDetail>(`/workspaces/current/agent-provider/${agentProvider}`) +} diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 5ad7d831d9..785e158261 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -300,7 +300,7 @@ export const useMutationPluginsFromMarketplace = () => { exclude, type, page = 1, - pageSize = 20, + pageSize = 40, } = pluginsSearchParams return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', { body: { diff --git a/web/service/use-strategy.ts b/web/service/use-strategy.ts index cbc09509b3..49f852ebf5 100644 --- a/web/service/use-strategy.ts +++ b/web/service/use-strategy.ts @@ -1,4 +1,3 @@ -import { get } from './base' import type { StrategyPluginDetail, } from '@/app/components/plugins/types' @@ -6,6 +5,7 @@ import { useInvalid } from './use-base' import { useQuery, } from '@tanstack/react-query' +import { fetchStrategyDetail, fetchStrategyList } from './strategy' const NAME_SPACE = 'agent_strategy' @@ -13,7 +13,7 @@ const useStrategyListKey = [NAME_SPACE, 'strategyList'] export const useStrategyProviders = () => { return useQuery<StrategyPluginDetail[]>({ queryKey: useStrategyListKey, - queryFn: () => get<StrategyPluginDetail[]>('/workspaces/current/agent-providers'), + queryFn: fetchStrategyList, }) } @@ -24,7 +24,7 @@ export const useInvalidateStrategyProviders = () => { export const useStrategyProviderDetail = (agentProvider: string) => { return useQuery<StrategyPluginDetail>({ queryKey: [NAME_SPACE, 'detail', agentProvider], - queryFn: () => get<StrategyPluginDetail>(`/workspaces/current/agent-provider/${agentProvider}`), + queryFn: () => fetchStrategyDetail(agentProvider), enabled: !!agentProvider, }) }