Merge branch 'feat/plugins' into dev/plugin-deploy

pull/12372/head
zxhlyh 1 year ago
commit 0631cf0c4f

@ -6,8 +6,8 @@ import {
BlockEnum, BlockEnum,
NodeRunningStatus, NodeRunningStatus,
} from '@/app/components/workflow/types' } from '@/app/components/workflow/types'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import { useWorkflowStore } from '@/app/components/workflow/store'
export const useWorkflowNodeFinished = () => { export const useWorkflowNodeFinished = () => {
const store = useStoreApi() const store = useStoreApi()
@ -18,8 +18,6 @@ export const useWorkflowNodeFinished = () => {
const { const {
workflowRunningData, workflowRunningData,
setWorkflowRunningData, setWorkflowRunningData,
iterParallelLogMap,
setIterParallelLogMap,
} = workflowStore.getState() } = workflowStore.getState()
const { const {
getNodes, getNodes,
@ -28,124 +26,45 @@ export const useWorkflowNodeFinished = () => {
setEdges, setEdges,
} = store.getState() } = store.getState()
const nodes = getNodes() const nodes = getNodes()
const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
if (nodeParentId) { const currentIndex = draft.tracing!.findIndex(item => item.id === data.id)
if (!data.execution_metadata.parallel_mode_run_id) { if (currentIndex > -1) {
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { draft.tracing![currentIndex] = {
const tracing = draft.tracing! ...draft.tracing![currentIndex],
const iterations = tracing.find(trace => trace.node_id === nodeParentId) // the iteration node ...data,
}
if (iterations && iterations.details) { }
const iterationIndex = data.execution_metadata?.iteration_index || 0 }))
if (!iterations.details[iterationIndex])
iterations.details[iterationIndex] = []
const currIteration = iterations.details[iterationIndex] const newNodes = produce(nodes, (draft) => {
const nodeIndex = currIteration.findIndex(node => const currentNode = draft.find(node => node.id === data.node_id)!
node.node_id === data.node_id && ( currentNode.data._runningStatus = data.status
node.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || node.parallel_id === data.execution_metadata?.parallel_id), if (data.status === NodeRunningStatus.Exception) {
) if (data.execution_metadata?.error_strategy === ErrorHandleTypeEnum.failBranch)
if (nodeIndex !== -1) { currentNode.data._runningBranchId = ErrorHandleTypeEnum.failBranch
currIteration[nodeIndex] = {
...currIteration[nodeIndex],
...(currIteration[nodeIndex].retryDetail
? { retryDetail: currIteration[nodeIndex].retryDetail }
: {}),
...data,
} as any
}
else {
currIteration.push({
...data,
} as any)
}
}
}))
} }
else { else {
// open parallel mode if (data.node_type === BlockEnum.IfElse)
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { currentNode.data._runningBranchId = data?.outputs?.selected_case_id
const tracing = draft.tracing!
const iterations = tracing.find(trace => trace.node_id === nodeParentId) // the iteration node
if (iterations && iterations.details) {
const iterRunID = data.execution_metadata?.parallel_mode_run_id
const currIteration = iterParallelLogMap.get(iterations.node_id)?.get(iterRunID) if (data.node_type === BlockEnum.QuestionClassifier)
const nodeIndex = currIteration?.findIndex(node => currentNode.data._runningBranchId = data?.outputs?.class_id
node.node_id === data.node_id && (
node?.parallel_run_id === data.execution_metadata?.parallel_mode_run_id),
)
if (currIteration) {
if (nodeIndex !== undefined && nodeIndex !== -1) {
currIteration[nodeIndex] = {
...currIteration[nodeIndex],
...data,
} as any
}
else {
currIteration.push({
...data,
} as any)
}
}
setIterParallelLogMap(iterParallelLogMap)
const iterLogMap = iterParallelLogMap.get(iterations.node_id)
if (iterLogMap)
iterations.details = Array.from(iterLogMap.values())
}
}))
} }
} })
else { setNodes(newNodes)
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { const newEdges = produce(edges, (draft) => {
const currentIndex = draft.tracing!.findIndex((trace) => { const incomeEdges = draft.filter((edge) => {
if (!trace.execution_metadata?.parallel_id) return edge.target === data.node_id
return trace.node_id === data.node_id
return trace.node_id === data.node_id && trace.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id
})
if (currentIndex > -1 && draft.tracing) {
draft.tracing[currentIndex] = {
...data,
...(draft.tracing[currentIndex].extras
? { extras: draft.tracing[currentIndex].extras }
: {}),
...(draft.tracing[currentIndex].retryDetail
? { retryDetail: draft.tracing[currentIndex].retryDetail }
: {}),
} as any
}
}))
const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._runningStatus = data.status as any
if (data.status === NodeRunningStatus.Exception) {
if (data.execution_metadata.error_strategy === ErrorHandleTypeEnum.failBranch)
currentNode.data._runningBranchId = ErrorHandleTypeEnum.failBranch
}
else {
if (data.node_type === BlockEnum.IfElse)
currentNode.data._runningBranchId = data?.outputs?.selected_case_id
if (data.node_type === BlockEnum.QuestionClassifier)
currentNode.data._runningBranchId = data?.outputs?.class_id
}
}) })
setNodes(newNodes) incomeEdges.forEach((edge) => {
const newEdges = produce(edges, (draft) => { edge.data = {
const incomeEdges = draft.filter((edge) => { ...edge.data,
return edge.target === data.node_id _targetRunningStatus: data.status as any,
}) }
incomeEdges.forEach((edge) => {
edge.data = {
...edge.data,
_targetRunningStatus: data.status as any,
}
})
}) })
setEdges(newEdges) })
} setEdges(newEdges)
}, [workflowStore, store]) }, [store, workflowStore])
return { return {
handleWorkflowNodeFinished, handleWorkflowNodeFinished,

@ -3,7 +3,6 @@ import { useStoreApi } from 'reactflow'
import produce from 'immer' import produce from 'immer'
import type { IterationFinishedResponse } from '@/types/workflow' import type { IterationFinishedResponse } from '@/types/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store' import { useWorkflowStore } from '@/app/components/workflow/store'
import { NodeRunningStatus } from '@/app/components/workflow/types'
import { DEFAULT_ITER_TIMES } from '@/app/components/workflow/constants' import { DEFAULT_ITER_TIMES } from '@/app/components/workflow/constants'
export const useWorkflowNodeIterationFinished = () => { export const useWorkflowNodeIterationFinished = () => {
@ -22,15 +21,14 @@ export const useWorkflowNodeIterationFinished = () => {
setNodes, setNodes,
} = store.getState() } = store.getState()
const nodes = getNodes() const nodes = getNodes()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const tracing = draft.tracing! const currentIndex = draft.tracing!.findIndex(item => item.id === data.id)
const currIterationNode = tracing.find(trace => trace.node_id === data.node_id)
if (currIterationNode) { if (currentIndex > -1) {
Object.assign(currIterationNode, { draft.tracing![currentIndex] = {
...draft.tracing![currentIndex],
...data, ...data,
status: NodeRunningStatus.Succeeded, }
})
} }
})) }))
setIterTimes(DEFAULT_ITER_TIMES) setIterTimes(DEFAULT_ITER_TIMES)

@ -10,8 +10,6 @@ export const useWorkflowNodeIterationNext = () => {
const handleWorkflowNodeIterationNext = useCallback((params: IterationNextResponse) => { const handleWorkflowNodeIterationNext = useCallback((params: IterationNextResponse) => {
const { const {
workflowRunningData,
setWorkflowRunningData,
iterTimes, iterTimes,
setIterTimes, setIterTimes,
} = workflowStore.getState() } = workflowStore.getState()
@ -22,17 +20,6 @@ export const useWorkflowNodeIterationNext = () => {
setNodes, setNodes,
} = store.getState() } = store.getState()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const iteration = draft.tracing!.find(trace => trace.node_id === data.node_id)
if (iteration) {
if (iteration.iterDurationMap && data.duration)
iteration.iterDurationMap[data.parallel_mode_run_id ?? `${data.index - 1}`] = data.duration
if (iteration.details!.length >= iteration.metadata.iterator_length!)
return
}
if (!data.parallel_mode_run_id)
iteration?.details!.push([])
}))
const nodes = getNodes() const nodes = getNodes()
const newNodes = produce(nodes, (draft) => { const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)! const currentNode = draft.find(node => node.id === data.node_id)!

@ -35,15 +35,13 @@ export const useWorkflowNodeIterationStarted = () => {
transform, transform,
} = store.getState() } = store.getState()
const nodes = getNodes() const nodes = getNodes()
setIterTimes(DEFAULT_ITER_TIMES)
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
draft.tracing!.push({ draft.tracing!.push({
...data, ...data,
status: NodeRunningStatus.Running, status: NodeRunningStatus.Running,
details: [], })
iterDurationMap: {},
} as any)
})) }))
setIterTimes(DEFAULT_ITER_TIMES)
const { const {
setViewport, setViewport,

@ -3,7 +3,6 @@ import { useStoreApi } from 'reactflow'
import produce from 'immer' import produce from 'immer'
import type { import type {
NodeFinishedResponse, NodeFinishedResponse,
NodeTracing,
} from '@/types/workflow' } from '@/types/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store' import { useWorkflowStore } from '@/app/components/workflow/store'
@ -16,8 +15,6 @@ export const useWorkflowNodeRetry = () => {
const { const {
workflowRunningData, workflowRunningData,
setWorkflowRunningData, setWorkflowRunningData,
iterParallelLogMap,
setIterParallelLogMap,
} = workflowStore.getState() } = workflowStore.getState()
const { const {
getNodes, getNodes,
@ -25,65 +22,9 @@ export const useWorkflowNodeRetry = () => {
} = store.getState() } = store.getState()
const nodes = getNodes() const nodes = getNodes()
const currentNode = nodes.find(node => node.id === data.node_id)! setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const nodeParent = nodes.find(node => node.id === currentNode.parentId) draft.tracing!.push(data)
if (nodeParent) { }))
if (!data.execution_metadata.parallel_mode_run_id) {
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const tracing = draft.tracing!
const iteration = tracing.find(trace => trace.node_id === nodeParent.id)
if (iteration && iteration.details?.length) {
const currentNodeRetry = iteration.details[nodeParent.data._iterationIndex - 1]?.find(item => item.node_id === data.node_id)
if (currentNodeRetry) {
if (currentNodeRetry?.retryDetail)
currentNodeRetry?.retryDetail.push(data as NodeTracing)
else
currentNodeRetry.retryDetail = [data as NodeTracing]
}
}
}))
}
else {
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const tracing = draft.tracing!
const iteration = tracing.find(trace => trace.node_id === nodeParent.id)
if (iteration && iteration.details?.length) {
const iterRunID = data.execution_metadata?.parallel_mode_run_id
const currIteration = iterParallelLogMap.get(iteration.node_id)?.get(iterRunID)
const currentNodeRetry = currIteration?.find(item => item.node_id === data.node_id)
if (currentNodeRetry) {
if (currentNodeRetry?.retryDetail)
currentNodeRetry?.retryDetail.push(data as NodeTracing)
else
currentNodeRetry.retryDetail = [data as NodeTracing]
}
setIterParallelLogMap(iterParallelLogMap)
const iterLogMap = iterParallelLogMap.get(iteration.node_id)
if (iterLogMap)
iteration.details = Array.from(iterLogMap.values())
}
}))
}
}
else {
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const tracing = draft.tracing!
const currentRetryNodeIndex = tracing.findIndex(trace => trace.node_id === data.node_id)
if (currentRetryNodeIndex > -1) {
const currentRetryNode = tracing[currentRetryNodeIndex]
if (currentRetryNode.retryDetail)
draft.tracing![currentRetryNodeIndex].retryDetail!.push(data as NodeTracing)
else
draft.tracing![currentRetryNodeIndex].retryDetail = [data as NodeTracing]
}
}))
}
const newNodes = produce(nodes, (draft) => { const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)! const currentNode = draft.find(node => node.id === data.node_id)!

@ -24,8 +24,6 @@ export const useWorkflowNodeStarted = () => {
const { const {
workflowRunningData, workflowRunningData,
setWorkflowRunningData, setWorkflowRunningData,
iterParallelLogMap,
setIterParallelLogMap,
} = workflowStore.getState() } = workflowStore.getState()
const { const {
getNodes, getNodes,
@ -35,84 +33,54 @@ export const useWorkflowNodeStarted = () => {
transform, transform,
} = store.getState() } = store.getState()
const nodes = getNodes() const nodes = getNodes()
const node = nodes.find(node => node.id === data.node_id) setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
if (node?.parentId) { draft.tracing!.push({
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { ...data,
const tracing = draft.tracing! status: NodeRunningStatus.Running,
const iterations = tracing.find(trace => trace.node_id === node?.parentId) })
const currIteration = iterations?.details![node.data.iteration_index] || iterations?.details![iterations.details!.length - 1] }))
if (!data.parallel_run_id) {
currIteration?.push({
...data,
status: NodeRunningStatus.Running,
} as any)
}
else {
const nodeId = iterations?.node_id as string
if (!iterParallelLogMap.has(nodeId as string))
iterParallelLogMap.set(iterations?.node_id as string, new Map())
const currentIterLogMap = iterParallelLogMap.get(nodeId)!
if (!currentIterLogMap.has(data.parallel_run_id))
currentIterLogMap.set(data.parallel_run_id, [{ ...data, status: NodeRunningStatus.Running } as any])
else
currentIterLogMap.get(data.parallel_run_id)!.push({ ...data, status: NodeRunningStatus.Running } as any)
setIterParallelLogMap(iterParallelLogMap)
if (iterations)
iterations.details = Array.from(currentIterLogMap.values())
}
}))
}
else {
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
draft.tracing!.push({
...data,
status: NodeRunningStatus.Running,
} as any)
}))
const { const {
setViewport, setViewport,
} = reactflow } = reactflow
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id) const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
const currentNode = nodes[currentNodeIndex] const currentNode = nodes[currentNodeIndex]
const position = currentNode.position const position = currentNode.position
const zoom = transform[2] const zoom = transform[2]
if (!currentNode.parentId) { if (!currentNode.parentId) {
setViewport({ setViewport({
x: (containerParams.clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom, x: (containerParams.clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
y: (containerParams.clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom, y: (containerParams.clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
zoom: transform[2], zoom: transform[2],
}) })
} }
const newNodes = produce(nodes, (draft) => { const newNodes = produce(nodes, (draft) => {
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
draft[currentNodeIndex].data._waitingRun = false draft[currentNodeIndex].data._waitingRun = false
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
const incomeEdges = draft.filter((edge) => {
return edge.target === data.node_id
}) })
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
const incomeEdges = draft.filter((edge) => {
return edge.target === data.node_id
})
incomeEdges.forEach((edge) => { incomeEdges.forEach((edge) => {
const incomeNode = nodes.find(node => node.id === edge.source)! const incomeNode = nodes.find(node => node.id === edge.source)!
if ( if (
(!incomeNode.data._runningBranchId && edge.sourceHandle === 'source') (!incomeNode.data._runningBranchId && edge.sourceHandle === 'source')
|| (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId) || (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId)
) { ) {
edge.data = { edge.data = {
...edge.data, ...edge.data,
_sourceRunningStatus: incomeNode.data._runningStatus, _sourceRunningStatus: incomeNode.data._runningStatus,
_targetRunningStatus: NodeRunningStatus.Running, _targetRunningStatus: NodeRunningStatus.Running,
_waitingRun: false, _waitingRun: false,
}
} }
}) }
}) })
setEdges(newEdges) })
} setEdges(newEdges)
}, [workflowStore, store, reactflow]) }, [workflowStore, store, reactflow])
return { return {

@ -127,6 +127,7 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => {
agent_strategy_provider_name: tool!.provider_name, agent_strategy_provider_name: tool!.provider_name,
agent_parameters: tool!.params, agent_parameters: tool!.params,
agent_strategy_label: tool!.tool_label, agent_strategy_label: tool!.tool_label,
agent_output_schema: tool!.output_schema,
}) })
setOpen(false) setOpen(false)
}} }}

@ -19,6 +19,7 @@ export type Strategy = {
agent_strategy_name: string agent_strategy_name: string
agent_strategy_label: string agent_strategy_label: string
agent_parameters?: ToolVarInputs agent_parameters?: ToolVarInputs
agent_output_schema: Record<string, any>
} }
export type AgentStrategyProps = { export type AgentStrategyProps = {
@ -37,44 +38,6 @@ type MultipleToolSelectorSchema = CustomSchema<'array[tools]'>
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
const devMockForm = [{ const devMockForm = [{
name: 'model',
label: {
en_US: 'Model',
zh_Hans: '模型',
pt_BR: 'Model',
ja_JP: 'Model',
},
placeholder: null,
scope: 'tool-call&llm',
auto_generate: null,
template: null,
required: true,
default: null,
min: null,
max: null,
options: [],
type: 'model-selector',
},
{
name: 'tools',
label: {
en_US: 'Tools list',
zh_Hans: '工具列表',
pt_BR: 'Tools list',
ja_JP: 'Tools list',
},
placeholder: null,
scope: null,
auto_generate: null,
template: null,
required: true,
default: null,
min: null,
max: null,
options: [],
type: 'array[tools]',
},
{
name: 'instruction', name: 'instruction',
label: { label: {
en_US: 'Instruction', en_US: 'Instruction',
@ -139,7 +102,12 @@ const devMockForm = [{
pt_BR: 'The maximum number of iterations to run', pt_BR: 'The maximum number of iterations to run',
ja_JP: 'The maximum number of iterations to run', ja_JP: 'The maximum number of iterations to run',
}, },
}] }].map((item) => {
return {
...item,
variable: item.name,
}
})
export const AgentStrategy = (props: AgentStrategyProps) => { export const AgentStrategy = (props: AgentStrategyProps) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props

@ -1,8 +1,9 @@
import type { ReactNode } from 'react'
import Collapse from '.' import Collapse from '.'
type FieldCollapseProps = { type FieldCollapseProps = {
title: string title: string
children: JSX.Element children: ReactNode
} }
const FieldCollapse = ({ const FieldCollapse = ({
title, title,

@ -1,5 +1,5 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC, ReactNode } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
@ -7,7 +7,7 @@ import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/
type Props = { type Props = {
className?: string className?: string
title?: string title?: string
children: JSX.Element children: ReactNode
} }
const OutputVars: FC<Props> = ({ const OutputVars: FC<Props> = ({

@ -318,17 +318,23 @@ const formatItem = (
case BlockEnum.Agent: { case BlockEnum.Agent: {
const payload = data as AgentNodeType const payload = data as AgentNodeType
if (!payload.agent_parameters) { const outputs: Var[] = []
res.vars = [] Object.keys(payload.output_schema.properties).forEach((outputKey) => {
break const output = payload.output_schema.properties[outputKey]
} outputs.push({
res.vars = Object.keys(payload.agent_parameters).map((key) => { variable: outputKey,
return { type: output.type === 'array'
variable: key, ? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]` as VarType
// TODO: is this correct? : `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}` as VarType,
type: payload.agent_parameters![key].type as unknown as VarType, // TODO: is this required?
} // @ts-expect-error todo added
description: output.description,
})
}) })
res.vars = [
...outputs,
...TOOL_OUTPUT_STRUCT,
]
break break
} }

@ -5,11 +5,24 @@ import Field from '../_base/components/field'
import { AgentStrategy } from '../_base/components/agent-strategy' import { AgentStrategy } from '../_base/components/agent-strategy'
import useConfig from './use-config' import useConfig from './use-config'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import OutputVars, { VarItem } from '../_base/components/output-vars'
import type { StrategyParamItem } from '@/app/components/plugins/types'
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
const i18nPrefix = 'workflow.nodes.agent'
function strategyParamToCredientialForm(param: StrategyParamItem): CredentialFormSchema {
return {
...param as any,
variable: param.name,
show_on: [],
}
}
const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
const { inputs, setInputs, currentStrategy } = useConfig(props.id, props.data) const { inputs, setInputs, currentStrategy } = useConfig(props.id, props.data)
const { t } = useTranslation() const { t } = useTranslation()
return <div className='space-y-2 my-2'> return <div className='my-2'>
<Field title={t('workflow.nodes.agent.strategy.label')} className='px-4' > <Field title={t('workflow.nodes.agent.strategy.label')} className='px-4' >
<AgentStrategy <AgentStrategy
strategy={inputs.agent_strategy_name ? { strategy={inputs.agent_strategy_name ? {
@ -17,6 +30,7 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
agent_strategy_name: inputs.agent_strategy_name!, agent_strategy_name: inputs.agent_strategy_name!,
agent_parameters: inputs.agent_parameters, agent_parameters: inputs.agent_parameters,
agent_strategy_label: inputs.agent_strategy_label!, agent_strategy_label: inputs.agent_strategy_label!,
agent_output_schema: inputs.output_schema,
} : undefined} } : undefined}
onStrategyChange={(strategy) => { onStrategyChange={(strategy) => {
setInputs({ setInputs({
@ -25,9 +39,10 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
agent_strategy_name: strategy?.agent_strategy_name, agent_strategy_name: strategy?.agent_strategy_name,
agent_parameters: strategy?.agent_parameters, agent_parameters: strategy?.agent_parameters,
agent_strategy_label: strategy?.agent_strategy_label, agent_strategy_label: strategy?.agent_strategy_label,
output_schema: strategy!.agent_output_schema,
}) })
}} }}
formSchema={currentStrategy?.parameters as any || []} formSchema={currentStrategy?.parameters?.map(strategyParamToCredientialForm) || []}
formValue={inputs.agent_parameters || {}} formValue={inputs.agent_parameters || {}}
onFormValueChange={value => setInputs({ onFormValueChange={value => setInputs({
...inputs, ...inputs,
@ -35,6 +50,33 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
})} })}
/> />
</Field> </Field>
<div>
<OutputVars>
<VarItem
name='text'
type='String'
description={t(`${i18nPrefix}.outputVars.text`)}
/>
<VarItem
name='files'
type='Array[File]'
description={t(`${i18nPrefix}.outputVars.files.title`)}
/>
<VarItem
name='json'
type='Array[Object]'
description={t(`${i18nPrefix}.outputVars.json`)}
/>
{inputs.output_schema && Object.entries(inputs.output_schema).map(([name, schema]) => (
<VarItem
key={name}
name={name}
type={schema.type}
description={schema.description}
/>
))}
</OutputVars>
</div>
</div> </div>
} }

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

@ -24,6 +24,7 @@ import Toast from '../../base/toast'
import InputsPanel from './inputs-panel' import InputsPanel from './inputs-panel'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import formatNodeList from '@/app/components/workflow/run/utils/format-log'
const WorkflowPreview = () => { const WorkflowPreview = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -160,7 +161,7 @@ const WorkflowPreview = () => {
{currentTab === 'TRACING' && ( {currentTab === 'TRACING' && (
<TracingPanel <TracingPanel
className='bg-background-section-burn' className='bg-background-section-burn'
list={workflowRunningData?.tracing || []} list={formatNodeList(workflowRunningData?.tracing || [], t)}
/> />
)} )}
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && ( {currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (

@ -0,0 +1,54 @@
import { useState } from 'react'
import { RiMoreLine } from '@remixicon/react'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Button from '@/app/components/base/button'
type AgentLogNavMoreProps = {
options: { id: string; label: string }[]
}
const AgentLogNavMore = ({
options,
}: AgentLogNavMoreProps) => {
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
placement='bottom-start'
offset={{
mainAxis: 2,
crossAxis: -54,
}}
open={open}
onOpenChange={setOpen}
>
<PortalToFollowElemTrigger>
<Button
className='w-6 h-6'
variant='ghost-accent'
>
<RiMoreLine className='w-4 h-4' />
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='p-1 w-[136px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl shadow-lg'>
{
options.map(option => (
<div
key={option.id}
className='flex items-center px-2 h-8 rounded-lg system-md-regular text-text-secondary hover:bg-state-base-hover cursor-pointer'
>
{option.label}
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default AgentLogNavMore

@ -0,0 +1,39 @@
import { RiArrowLeftLine } from '@remixicon/react'
import AgentLogNavMore from './agent-log-nav-more'
import Button from '@/app/components/base/button'
const AgentLogNav = () => {
return (
<div className='flex items-center p-1 pr-3 h-8'>
<Button
className='shrink-0 px-[5px]'
size='small'
variant='ghost-accent'
onClick={() => {}}
>
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Agent
</Button>
<div className='shrink-0 mx-0.5 system-xs-regular text-divider-deep'>/</div>
<Button
className='shrink-0 px-[5px]'
size='small'
variant='ghost-accent'
onClick={() => {}}
>
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Agent strategy
</Button>
<div className='shrink-0 mx-0.5 system-xs-regular text-divider-deep'>/</div>
<AgentLogNavMore
options={[]}
/>
<div className='shrink-0 mx-0.5 system-xs-regular text-divider-deep'>/</div>
<div className='flex items-center px-[5px] system-xs-medium-uppercase text-text-tertiary'>
Run Actions
</div>
</div>
)
}
export default AgentLogNav

@ -1,6 +1,5 @@
import Button from '@/app/components/base/button'
import { RiArrowLeftLine } from '@remixicon/react'
import AgentLogItem from './agent-log-item' import AgentLogItem from './agent-log-item'
import AgentLogNav from './agent-log-nav'
import type { AgentLogItemWithChildren } from '@/types/workflow' import type { AgentLogItemWithChildren } from '@/types/workflow'
type AgentResultPanelProps = { type AgentResultPanelProps = {
@ -12,29 +11,7 @@ const AgentResultPanel = ({
}: AgentResultPanelProps) => { }: AgentResultPanelProps) => {
return ( return (
<div className='overflow-y-auto'> <div className='overflow-y-auto'>
<div className='flex items-center p-1 pr-3 h-8'> <AgentLogNav />
<Button
className='shrink-0 px-[5px]'
size='small'
variant='ghost-accent'
onClick={() => {}}
>
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Back
</Button>
<div className='shrink-0 mx-0.5 system-xs-regular text-divider-deep'>/</div>
<div className='grow px-[5px] system-xs-medium-uppercase'>
Agent strategy
</div>
<Button
className='px-[5px]'
size='small'
variant='ghost-accent'
onClick={() => {}}
>
close
</Button>
</div>
{ {
<div className='p-2'> <div className='p-2'>
{ {

@ -5,7 +5,7 @@ import formatRetryNode from './retry'
import formatAgentNode from './agent' import formatAgentNode from './agent'
const formatToTracingNodeList = (list: NodeTracing[], t: any) => { const formatToTracingNodeList = (list: NodeTracing[], t: any) => {
const allItems = [...list].reverse() const allItems = [...list].sort((a, b) => a.index - b.index)
/* /*
* First handle not change list structure node * First handle not change list structure node
* Because Handle struct node will put the node in different * Because Handle struct node will put the node in different

@ -1,132 +0,0 @@
import type { NodeTracing } from '@/types/workflow'
type TracingNodeProps = {
id: string
uniqueId: string
isParallel: boolean
data: NodeTracing | null
children: TracingNodeProps[]
parallelTitle?: string
branchTitle?: string
hideNodeInfo?: boolean
hideNodeProcessDetail?: boolean
}
function buildLogTree(nodes: NodeTracing[], t: (key: string) => string): TracingNodeProps[] {
const rootNodes: TracingNodeProps[] = []
const parallelStacks: { [key: string]: TracingNodeProps } = {}
const levelCounts: { [key: string]: number } = {}
const parallelChildCounts: { [key: string]: Set<string> } = {}
let uniqueIdCounter = 0
const getUniqueId = () => {
uniqueIdCounter++
return `unique-${uniqueIdCounter}`
}
const getParallelTitle = (parentId: string | null): string => {
const levelKey = parentId || 'root'
if (!levelCounts[levelKey])
levelCounts[levelKey] = 0
levelCounts[levelKey]++
const parentTitle = parentId ? parallelStacks[parentId]?.parallelTitle : ''
const levelNumber = parentTitle ? Number.parseInt(parentTitle.split('-')[1]) + 1 : 1
const letter = parallelChildCounts[levelKey]?.size > 1 ? String.fromCharCode(64 + levelCounts[levelKey]) : ''
return `${t('workflow.common.parallel')}-${levelNumber}${letter}`
}
const getBranchTitle = (parentId: string | null, branchNum: number): string => {
const levelKey = parentId || 'root'
const parentTitle = parentId ? parallelStacks[parentId]?.parallelTitle : ''
const levelNumber = parentTitle ? Number.parseInt(parentTitle.split('-')[1]) + 1 : 1
const letter = parallelChildCounts[levelKey]?.size > 1 ? String.fromCharCode(64 + levelCounts[levelKey]) : ''
const branchLetter = String.fromCharCode(64 + branchNum)
return `${t('workflow.common.branch')}-${levelNumber}${letter}-${branchLetter}`
}
// Count parallel children (for figuring out if we need to use letters)
for (const node of nodes) {
const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null
const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
if (parallel_id) {
const parentKey = parent_parallel_id || 'root'
if (!parallelChildCounts[parentKey])
parallelChildCounts[parentKey] = new Set()
parallelChildCounts[parentKey].add(parallel_id)
}
}
for (const node of nodes) {
const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null
const parallel_start_node_id = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
const parent_parallel_start_node_id = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null
if (!parallel_id || node.node_type === BlockEnum.End) {
rootNodes.push({
id: node.id,
uniqueId: getUniqueId(),
isParallel: false,
data: node,
children: [],
})
}
else {
if (!parallelStacks[parallel_id]) {
const newParallelGroup: TracingNodeProps = {
id: parallel_id,
uniqueId: getUniqueId(),
isParallel: true,
data: null,
children: [],
parallelTitle: '',
}
parallelStacks[parallel_id] = newParallelGroup
if (parent_parallel_id && parallelStacks[parent_parallel_id]) {
const sameBranchIndex = parallelStacks[parent_parallel_id].children.findLastIndex(c =>
c.data?.execution_metadata?.parallel_start_node_id === parent_parallel_start_node_id || c.data?.parallel_start_node_id === parent_parallel_start_node_id,
)
parallelStacks[parent_parallel_id].children.splice(sameBranchIndex + 1, 0, newParallelGroup)
newParallelGroup.parallelTitle = getParallelTitle(parent_parallel_id)
}
else {
newParallelGroup.parallelTitle = getParallelTitle(parent_parallel_id)
rootNodes.push(newParallelGroup)
}
}
const branchTitle = parallel_start_node_id === node.node_id ? getBranchTitle(parent_parallel_id, parallelStacks[parallel_id].children.length + 1) : ''
if (branchTitle) {
parallelStacks[parallel_id].children.push({
id: node.id,
uniqueId: getUniqueId(),
isParallel: false,
data: node,
children: [],
branchTitle,
})
}
else {
let sameBranchIndex = parallelStacks[parallel_id].children.findLastIndex(c =>
c.data?.execution_metadata?.parallel_start_node_id === parallel_start_node_id || c.data?.parallel_start_node_id === parallel_start_node_id,
)
if (parallelStacks[parallel_id].children[sameBranchIndex + 1]?.isParallel)
sameBranchIndex++
parallelStacks[parallel_id].children.splice(sameBranchIndex + 1, 0, {
id: node.id,
uniqueId: getUniqueId(),
isParallel: false,
data: node,
children: [],
branchTitle,
})
}
}
}
return rootNodes
}

@ -137,9 +137,12 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
}) })
// print node structure for debug // print node structure for debug
filteredInParallelSubNodes.forEach((node) => { // filteredInParallelSubNodes.forEach((node) => {
printNodeStructure(node, 0) // const now = Date.now()
}) // console.log(`----- p: ${now} start -----`)
// printNodeStructure(node, 0)
// console.log(`----- p: ${now} end -----`)
// })
const parallelNumRecord: Record<string, number> = { const parallelNumRecord: Record<string, number> = {
num: 0, num: 0,

@ -1,11 +1,7 @@
import { BlockEnum } from '@/app/components/workflow/types'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
const format = (list: NodeTracing[]): NodeTracing[] => { const format = (list: NodeTracing[]): NodeTracing[] => {
const retryNodes = list.filter((item) => { const retryNodes = list.filter((item) => {
const { execution_metadata } = item
const isInIteration = !!execution_metadata?.iteration_id
if (isInIteration || item.node_type === BlockEnum.Iteration) return false
return item.status === 'retry' return item.status === 'retry'
}) })
@ -14,11 +10,21 @@ const format = (list: NodeTracing[]): NodeTracing[] => {
const result = list.filter((item) => { const result = list.filter((item) => {
return item.status !== 'retry' return item.status !== 'retry'
}).map((item) => { }).map((item) => {
const isRetryBelongNode = retryNodeIds.includes(item.node_id) const { execution_metadata } = item
const isInIteration = !!execution_metadata?.iteration_id
const nodeId = item.node_id
const isRetryBelongNode = retryNodeIds.includes(nodeId)
if (isRetryBelongNode) { if (isRetryBelongNode) {
return { return {
...item, ...item,
retryDetail: list.filter(node => node.status === 'retry' && node.node_id === item.node_id), retryDetail: retryNodes.filter((node) => {
if (!isInIteration)
return node.node_id === nodeId
// retry node in iteration
return node.node_id === nodeId && node.execution_metadata?.iteration_index === execution_metadata?.iteration_index
}),
} }
} }
return item return item

@ -728,6 +728,17 @@ const translation = {
modelSelectorTooltips: { modelSelectorTooltips: {
deprecated: 'This model is deprecated', deprecated: 'This model is deprecated',
}, },
outputVars: {
text: 'agent generated content',
files: {
title: 'agent generated files',
type: 'Support type. Now only support image',
transfer_method: 'Transfer method.Value is remote_url or local_file',
url: 'Image url',
upload_file_id: 'Upload file id',
},
json: 'agent generated json',
},
}, },
}, },
tracing: { tracing: {

@ -728,6 +728,17 @@ const translation = {
modelSelectorTooltips: { modelSelectorTooltips: {
deprecated: '此模型已弃用', deprecated: '此模型已弃用',
}, },
outputVars: {
text: 'agent 生成的内容',
files: {
title: 'agent 生成的文件',
type: '支持类型。现在只支持图片',
transfer_method: '传输方式。值为 remote_url 或 local_file',
url: '图片链接',
upload_file_id: '上传文件ID',
},
json: 'agent 生成的json',
},
}, },
}, },
tracing: { tracing: {

@ -147,18 +147,7 @@ export type NodeStartedResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
id: string
node_id: string
iteration_id?: string
parallel_run_id?: string
node_type: string
index: number
predecessor_node_id?: string
inputs: any
created_at: number
extras?: any
}
} }
export type FileResponse = { export type FileResponse = {
@ -176,120 +165,42 @@ export type NodeFinishedResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
id: string
node_id: string
iteration_id?: string
node_type: string
index: number
predecessor_node_id?: string
inputs: any
process_data: any
outputs: any
status: string
error: string
elapsed_time: number
execution_metadata: {
total_tokens: number
total_price: number
currency: string
parallel_id?: string
parallel_start_node_id?: string
iteration_index?: number
iteration_id?: string
parallel_mode_run_id: string
error_strategy?: ErrorHandleTypeEnum
}
created_at: number
files?: FileResponse[]
retry_index?: number
}
} }
export type IterationStartedResponse = { export type IterationStartedResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
id: string
node_id: string
metadata: {
iterator_length: number
iteration_id: string
iteration_index: number
}
created_at: number
extras?: any
}
} }
export type IterationNextResponse = { export type IterationNextResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
id: string
node_id: string
index: number
output: any
extras?: any
created_at: number
parallel_mode_run_id: string
execution_metadata: {
parallel_id?: string
iteration_index: number
parallel_mode_run_id?: string
}
duration?: number
}
} }
export type IterationFinishedResponse = { export type IterationFinishedResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
id: string
node_id: string
outputs: any
extras?: any
status: string
created_at: number
error: string
execution_metadata: {
parallel_id?: string
}
}
} }
export type ParallelBranchStartedResponse = { export type ParallelBranchStartedResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
parallel_id: string
parallel_start_node_id: string
parent_parallel_id: string
parent_parallel_start_node_id: string
iteration_id?: string
created_at: number
}
} }
export type ParallelBranchFinishedResponse = { export type ParallelBranchFinishedResponse = {
task_id: string task_id: string
workflow_run_id: string workflow_run_id: string
event: string event: string
data: { data: NodeTracing
parallel_id: string
parallel_start_node_id: string
parent_parallel_id: string
parent_parallel_start_node_id: string
iteration_id?: string
status: string
created_at: number
error: string
}
} }
export type TextChunkResponse = { export type TextChunkResponse = {

Loading…
Cancel
Save