diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx
index a18ddad65d..82d8a302c5 100644
--- a/web/app/components/workflow/header/view-history.tsx
+++ b/web/app/components/workflow/header/view-history.tsx
@@ -202,7 +202,7 @@ const ViewHistory = ({
{`Test ${isChatMode ? 'Chat' : 'Run'}#${item.sequence_number}`}
- {item.created_by_account.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
+ {item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx
index 557fb8b06e..c1249f7224 100644
--- a/web/app/components/workflow/run/index.tsx
+++ b/web/app/components/workflow/run/index.tsx
@@ -61,7 +61,7 @@ const RunPanel: FC = ({ hideResult, activeTab = 'RESULT', runID, getRe
const { data: nodeList } = await fetchTracingList({
url: `/apps/${appID}/workflow-runs/${runID}/node-executions`,
})
- setList(formatNodeList(nodeList))
+ setList(formatNodeList(nodeList, t))
}
catch (err) {
notify({
diff --git a/web/app/components/workflow/run/tracing-panel.tsx b/web/app/components/workflow/run/tracing-panel.tsx
index 109283c511..f8af35219b 100644
--- a/web/app/components/workflow/run/tracing-panel.tsx
+++ b/web/app/components/workflow/run/tracing-panel.tsx
@@ -11,13 +11,9 @@ import {
RiArrowDownSLine,
RiMenu4Line,
} from '@remixicon/react'
-import { useTranslation } from 'react-i18next'
import { useLogs } from './hooks'
import NodePanel from './node'
import SpecialResultPanel from './special-result-panel'
-import {
- BlockEnum,
-} from '@/app/components/workflow/types'
import type { NodeTracing } from '@/types/workflow'
type TracingPanelProps = {
@@ -27,145 +23,14 @@ type TracingPanelProps = {
hideNodeProcessDetail?: boolean
}
-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 } = {}
- 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
-}
-
const TracingPanel: FC = ({
list,
className,
hideNodeInfo = false,
hideNodeProcessDetail = false,
}) => {
- const { t } = useTranslation()
- const treeNodes = buildLogTree(list, t)
+ const treeNodes = list
+ console.log(treeNodes)
const [collapsedNodes, setCollapsedNodes] = useState>(new Set())
const [hoveredParallel, setHoveredParallel] = useState(null)
@@ -219,13 +84,16 @@ const TracingPanel: FC = ({
setAgentResultList,
} = useLogs()
- const renderNode = (node: TracingNodeProps) => {
- if (node.isParallel) {
+
+ const renderNode = (node: NodeTracing) => {
+ const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode
+ if (isParallelFirstNode) {
+ const parallelDetail = node.parallelDetail!
const isCollapsed = collapsedNodes.has(node.id)
const isHovered = hoveredParallel === node.id
return (
handleParallelMouseEnter(node.id)}
@@ -242,7 +110,7 @@ const TracingPanel: FC
= ({
{isHovered ? : }
- {node.parallelTitle}
+ {parallelDetail.parallelTitle}
= ({
'absolute top-0 bottom-0 left-[5px] w-[2px]',
isHovered ? 'bg-text-accent-secondary' : 'bg-divider-subtle',
)}>
- {node.children.map(renderNode)}
+ {parallelDetail.children!.map(renderNode)}
)
@@ -262,12 +130,12 @@ const TracingPanel: FC = ({
else {
const isHovered = hoveredParallel === node.id
return (
-
+
- {node.branchTitle}
+ {node?.parallelDetail?.branchTitle}
{
+const formatToTracingNodeList = (list: NodeTracing[], t: any) => {
const allItems = [...list].reverse()
- const formattedIterationList = formatIterationNode(allItems)
- const formattedRetryList = formatRetryNode(formattedIterationList)
- const formattedAgentList = formatAgentNode(formattedRetryList)
+ /*
+ * First handle not change list structure node
+ * Because Handle struct node will put the node in different
+ */
+ const formattedAgentList = formatAgentNode(allItems)
+ const formattedRetryList = formatRetryNode(formattedAgentList) // retry one node
+ // would change the structure of the list. Iteration and parallel can include each other.
+ const formattedIterationList = formatIterationNode(formattedRetryList)
+ const formattedParallelList = formatParallelNode(formattedIterationList, t)
- const result = formattedAgentList
+ const result = formattedParallelList
// console.log(allItems)
// console.log(result)
diff --git a/web/app/components/workflow/run/utils/format-log/parallel/index.backup.ts b/web/app/components/workflow/run/utils/format-log/parallel/index.backup.ts
new file mode 100644
index 0000000000..f30d03c839
--- /dev/null
+++ b/web/app/components/workflow/run/utils/format-log/parallel/index.backup.ts
@@ -0,0 +1,132 @@
+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 } = {}
+ 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
+}
diff --git a/web/app/components/workflow/run/utils/format-log/parallel/index.ts b/web/app/components/workflow/run/utils/format-log/parallel/index.ts
index e69de29bb2..214285d39b 100644
--- a/web/app/components/workflow/run/utils/format-log/parallel/index.ts
+++ b/web/app/components/workflow/run/utils/format-log/parallel/index.ts
@@ -0,0 +1,127 @@
+import { BlockEnum } from '@/app/components/workflow/types'
+import type { NodeTracing } from '@/types/workflow'
+
+function addTitle({
+ list, level, parallelNumRecord,
+}: {
+ list: NodeTracing[], level: number, parallelNumRecord: Record
+}, t: any) {
+ let branchIndex = 0
+ list.forEach((node) => {
+ const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
+ const parallel_start_node_id = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
+
+ const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End
+ if (isNotInParallel)
+ return
+
+ const isParallelStartNode = node.parallelDetail?.isParallelStartNode
+ if (isParallelStartNode)
+ parallelNumRecord.num++
+
+ const letter = parallelNumRecord.num > 1 ? String.fromCharCode(64 + level) : ''
+ const parallelLevelInfo = `${parallelNumRecord.num}${letter}`
+
+ if (isParallelStartNode) {
+ node.parallelDetail!.isParallelStartNode = true
+ node.parallelDetail!.parallelTitle = `${t('workflow.common.parallel')}-${parallelLevelInfo}`
+ }
+
+ const isBrachStartNode = parallel_start_node_id === node.node_id
+ if (isBrachStartNode) {
+ branchIndex++
+ const branchLetter = String.fromCharCode(64 + branchIndex)
+ if (!node.parallelDetail) {
+ node.parallelDetail = {
+ branchTitle: '',
+ }
+ }
+
+ node.parallelDetail!.branchTitle = `${t('workflow.common.branch')}-${parallelLevelInfo}-${branchLetter}`
+ }
+
+ if (node.parallelDetail?.children && node.parallelDetail.children.length > 0) {
+ addTitle({
+ list: node.parallelDetail.children,
+ level: level + 1,
+ parallelNumRecord,
+ }, t)
+ }
+ })
+}
+
+// list => group by parallel_id(parallel tree).
+const format = (list: NodeTracing[], t: any): NodeTracing[] => {
+ const result: NodeTracing[] = [...list]
+ const parallelFirstNodeMap: Record = {}
+ // list to tree by parent_parallel_start_node_id and parallel_start_node_id
+ result.forEach((node) => {
+ 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 parent_parallel_start_node_id = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null
+ const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End
+ if (isNotInParallel)
+ return
+
+ const isParallelStartNode = !parallelFirstNodeMap[parallel_id]
+ if (isParallelStartNode) {
+ const selfNode = { ...node }
+ node.parallelDetail = {
+ isParallelStartNode: true,
+ children: [selfNode],
+ }
+ parallelFirstNodeMap[parallel_id] = node.node_id
+ const isRootLevel = !parent_parallel_id
+ if (isRootLevel)
+ return
+
+ const parentParallelStartNode = result.find(item => item.node_id === parent_parallel_start_node_id)
+ // append to parent parallel start node
+ if (parentParallelStartNode) {
+ if (!parentParallelStartNode?.parallelDetail) {
+ parentParallelStartNode!.parallelDetail = {
+ children: [],
+ }
+ }
+ if (parentParallelStartNode!.parallelDetail.children)
+ parentParallelStartNode!.parallelDetail.children.push(node)
+ }
+ }
+
+ // append to parallel start node
+ const parallelStartNode = result.find(item => item.node_id === parallelFirstNodeMap[parallel_id])
+ if (parallelStartNode && parallelStartNode.parallelDetail && parallelStartNode!.parallelDetail!.children)
+ parallelStartNode!.parallelDetail!.children.push(node)
+ })
+
+ const filteredInParallelSubNodes = result.filter((node) => {
+ const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
+ const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End
+ if (isNotInParallel)
+ return true
+
+ const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null
+
+ if (parent_parallel_id)
+ return false
+
+ const isParallelStartNode = node.parallelDetail?.isParallelStartNode
+ if (!isParallelStartNode)
+ return false
+
+ return true
+ })
+
+ const parallelNumRecord: Record = {
+ num: 0,
+ }
+
+ addTitle({
+ list: filteredInParallelSubNodes,
+ level: 1,
+ parallelNumRecord,
+ }, t)
+
+ return filteredInParallelSubNodes
+}
+export default format
diff --git a/web/types/workflow.ts b/web/types/workflow.ts
index c89f5536a0..29fc91b06b 100644
--- a/web/types/workflow.ts
+++ b/web/types/workflow.ts
@@ -68,12 +68,18 @@ export type NodeTracing = {
expand?: boolean // for UI
details?: NodeTracing[][] // iteration detail
retryDetail?: NodeTracing[] // retry detail
- agentLog?: AgentLogItemWithChildren[]
+ retry_index?: number
+ parallelDetail?: { // parallel detail. if is in parallel, this field will be set
+ isParallelStartNode?: boolean
+ parallelTitle?: string
+ branchTitle?: string
+ children?: NodeTracing[]
+ }
parallel_id?: string
parallel_start_node_id?: string
parent_parallel_id?: string
parent_parallel_start_node_id?: string
- retry_index?: number
+ agentLog?: AgentLogItemWithChildren[] // agent log
}
export type FetchWorkflowDraftResponse = {