feat: agent node checklist

This commit is contained in:
AkaraChen
2025-01-02 11:29:10 +08:00
parent 4663af8a60
commit 1b8ec6710a
14 changed files with 107 additions and 35 deletions

View File

@@ -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,17 @@ 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,
}
}
if (node.type === CUSTOM_NODE) {
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t, moreDataForCheckValid)
@@ -92,7 +105,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
}

View File

@@ -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) {

View File

@@ -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'

View File

@@ -12,7 +12,7 @@ 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 { type ComponentProps, memo } from 'react'
import { useDefaultModel, useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import Editor from './prompt/editor'
import { useWorkflowStore } from '../../../store'
@@ -22,6 +22,7 @@ export type Strategy = {
agent_strategy_name: string
agent_strategy_label: string
agent_output_schema: Record<string, any>
plugin_unique_identifier: string
}
export type AgentStrategyProps = {
@@ -47,7 +48,7 @@ type StringSchema = CustomSchema<'string', {
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema | StringSchema
export const AgentStrategy = (props: AgentStrategyProps) => {
export const AgentStrategy = memo((props: AgentStrategyProps) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props
const { t } = useTranslation()
const language = useLanguage()
@@ -197,4 +198,6 @@ export const AgentStrategy = (props: AgentStrategyProps) => {
/>
}
</div>
}
})
AgentStrategy.displayName = 'AgentStrategy'

View File

@@ -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'

View File

@@ -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'

View File

@@ -1,3 +1,4 @@
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'
@@ -15,16 +16,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
}) {
const { strategy } = moreDataForCheckValid
if (!strategy) {
return {
isValid: false,
errorMessage: 'Please select a strategy',
}
}
for (const param of strategy.parameters) {
if (param.required && !payload.agent_parameters?.[param.name]?.value) {
return {
isValid: false,
errorMessage: `Please select ${param.name}`,
}
}
}
// TODO: tool selector valid?
return {
isValid,
errorMessage: errorMessages,
isValid: true,
errorMessage: '',
}
},
}

View File

@@ -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'
@@ -126,4 +126,6 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
</div>
}
export default AgentNode
AgentNode.displayName = 'AgentNode'
export default memo(AgentNode)

View File

@@ -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'
@@ -75,6 +75,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,6 +84,7 @@ 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) || []}
@@ -135,4 +137,6 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
</div>
}
export default AgentPanel
AgentPanel.displayName = 'AgentPanel'
export default memo(AgentPanel)

View File

@@ -7,4 +7,5 @@ export type AgentNodeType = CommonNodeType & {
agent_strategy_label?: string
agent_parameters?: ToolVarInputs
output_schema: Record<string, any>
plugin_unique_identifier?: string
}

View File

@@ -1,5 +0,0 @@
import type { AgentNodeType } from './types'
export const checkNodeValid = (payload: AgentNodeType) => {
return true
}

View File

@@ -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,

10
web/service/strategy.ts Normal file
View File

@@ -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}`)
}

View File

@@ -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,
})
}