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

This commit is contained in:
zxhlyh 2024-12-27 16:18:41 +08:00
commit 0631cf0c4f
24 changed files with 306 additions and 595 deletions

View File

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

View File

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

View File

@ -10,8 +10,6 @@ export const useWorkflowNodeIterationNext = () => {
const handleWorkflowNodeIterationNext = useCallback((params: IterationNextResponse) => {
const {
workflowRunningData,
setWorkflowRunningData,
iterTimes,
setIterTimes,
} = workflowStore.getState()
@ -22,17 +20,6 @@ export const useWorkflowNodeIterationNext = () => {
setNodes,
} = 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 newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!

View File

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

View File

@ -3,7 +3,6 @@ import { useStoreApi } from 'reactflow'
import produce from 'immer'
import type {
NodeFinishedResponse,
NodeTracing,
} from '@/types/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store'
@ -16,8 +15,6 @@ export const useWorkflowNodeRetry = () => {
const {
workflowRunningData,
setWorkflowRunningData,
iterParallelLogMap,
setIterParallelLogMap,
} = workflowStore.getState()
const {
getNodes,
@ -25,65 +22,9 @@ export const useWorkflowNodeRetry = () => {
} = store.getState()
const nodes = getNodes()
const currentNode = nodes.find(node => node.id === data.node_id)!
const nodeParent = nodes.find(node => node.id === currentNode.parentId)
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]
}
}))
}
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
draft.tracing!.push(data)
}))
const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!

View File

@ -24,8 +24,6 @@ export const useWorkflowNodeStarted = () => {
const {
workflowRunningData,
setWorkflowRunningData,
iterParallelLogMap,
setIterParallelLogMap,
} = workflowStore.getState()
const {
getNodes,
@ -35,84 +33,54 @@ export const useWorkflowNodeStarted = () => {
transform,
} = store.getState()
const nodes = getNodes()
const node = nodes.find(node => node.id === data.node_id)
if (node?.parentId) {
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
const tracing = draft.tracing!
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 {
setViewport,
} = reactflow
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
const currentNode = nodes[currentNodeIndex]
const position = currentNode.position
const zoom = transform[2]
if (!currentNode.parentId) {
setViewport({
x: (containerParams.clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
y: (containerParams.clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
zoom: transform[2],
})
}
const newNodes = produce(nodes, (draft) => {
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
draft[currentNodeIndex].data._waitingRun = false
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
draft.tracing!.push({
...data,
status: NodeRunningStatus.Running,
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
const incomeEdges = draft.filter((edge) => {
return edge.target === data.node_id
})
}))
incomeEdges.forEach((edge) => {
const incomeNode = nodes.find(node => node.id === edge.source)!
if (
(!incomeNode.data._runningBranchId && edge.sourceHandle === 'source')
|| (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId)
) {
edge.data = {
...edge.data,
_sourceRunningStatus: incomeNode.data._runningStatus,
_targetRunningStatus: NodeRunningStatus.Running,
_waitingRun: false,
}
const {
setViewport,
} = reactflow
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
const currentNode = nodes[currentNodeIndex]
const position = currentNode.position
const zoom = transform[2]
if (!currentNode.parentId) {
setViewport({
x: (containerParams.clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
y: (containerParams.clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
zoom: transform[2],
})
}
const newNodes = produce(nodes, (draft) => {
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
draft[currentNodeIndex].data._waitingRun = false
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
const incomeEdges = draft.filter((edge) => {
return edge.target === data.node_id
})
incomeEdges.forEach((edge) => {
const incomeNode = nodes.find(node => node.id === edge.source)!
if (
(!incomeNode.data._runningBranchId && edge.sourceHandle === 'source')
|| (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId)
) {
edge.data = {
...edge.data,
_sourceRunningStatus: incomeNode.data._runningStatus,
_targetRunningStatus: NodeRunningStatus.Running,
_waitingRun: false,
}
})
}
})
setEdges(newEdges)
}
})
setEdges(newEdges)
}, [workflowStore, store, reactflow])
return {

View File

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

View File

@ -19,6 +19,7 @@ export type Strategy = {
agent_strategy_name: string
agent_strategy_label: string
agent_parameters?: ToolVarInputs
agent_output_schema: Record<string, any>
}
export type AgentStrategyProps = {
@ -37,44 +38,6 @@ type MultipleToolSelectorSchema = CustomSchema<'array[tools]'>
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
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',
label: {
en_US: 'Instruction',
@ -139,7 +102,12 @@ const devMockForm = [{
pt_BR: '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) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props

View File

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

View File

@ -1,5 +1,5 @@
'use client'
import type { FC } from 'react'
import type { FC, ReactNode } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
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 = {
className?: string
title?: string
children: JSX.Element
children: ReactNode
}
const OutputVars: FC<Props> = ({

View File

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

View File

@ -5,11 +5,24 @@ import Field from '../_base/components/field'
import { AgentStrategy } from '../_base/components/agent-strategy'
import useConfig from './use-config'
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 { inputs, setInputs, currentStrategy } = useConfig(props.id, props.data)
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' >
<AgentStrategy
strategy={inputs.agent_strategy_name ? {
@ -17,6 +30,7 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
agent_strategy_name: inputs.agent_strategy_name!,
agent_parameters: inputs.agent_parameters,
agent_strategy_label: inputs.agent_strategy_label!,
agent_output_schema: inputs.output_schema,
} : undefined}
onStrategyChange={(strategy) => {
setInputs({
@ -25,9 +39,10 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
agent_strategy_name: strategy?.agent_strategy_name,
agent_parameters: strategy?.agent_parameters,
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 || {}}
onFormValueChange={value => setInputs({
...inputs,
@ -35,6 +50,33 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
})}
/>
</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>
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import Button from '@/app/components/base/button'
import { RiArrowLeftLine } from '@remixicon/react'
import AgentLogItem from './agent-log-item'
import AgentLogNav from './agent-log-nav'
import type { AgentLogItemWithChildren } from '@/types/workflow'
type AgentResultPanelProps = {
@ -12,29 +11,7 @@ const AgentResultPanel = ({
}: AgentResultPanelProps) => {
return (
<div className='overflow-y-auto'>
<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' />
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>
<AgentLogNav />
{
<div className='p-2'>
{

View File

@ -5,7 +5,7 @@ import formatRetryNode from './retry'
import formatAgentNode from './agent'
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
* Because Handle struct node will put the node in different

View File

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

View File

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

View File

@ -1,11 +1,7 @@
import { BlockEnum } from '@/app/components/workflow/types'
import type { NodeTracing } from '@/types/workflow'
const format = (list: NodeTracing[]): NodeTracing[] => {
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'
})
@ -14,11 +10,21 @@ const format = (list: NodeTracing[]): NodeTracing[] => {
const result = list.filter((item) => {
return item.status !== 'retry'
}).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) {
return {
...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

View File

@ -728,6 +728,17 @@ const translation = {
modelSelectorTooltips: {
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: {

View File

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

View File

@ -147,18 +147,7 @@ export type NodeStartedResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
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
}
data: NodeTracing
}
export type FileResponse = {
@ -176,120 +165,42 @@ export type NodeFinishedResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
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
}
data: NodeTracing
}
export type IterationStartedResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
id: string
node_id: string
metadata: {
iterator_length: number
iteration_id: string
iteration_index: number
}
created_at: number
extras?: any
}
data: NodeTracing
}
export type IterationNextResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
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
}
data: NodeTracing
}
export type IterationFinishedResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
id: string
node_id: string
outputs: any
extras?: any
status: string
created_at: number
error: string
execution_metadata: {
parallel_id?: string
}
}
data: NodeTracing
}
export type ParallelBranchStartedResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
parallel_id: string
parallel_start_node_id: string
parent_parallel_id: string
parent_parallel_start_node_id: string
iteration_id?: string
created_at: number
}
data: NodeTracing
}
export type ParallelBranchFinishedResponse = {
task_id: string
workflow_run_id: string
event: string
data: {
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
}
data: NodeTracing
}
export type TextChunkResponse = {