From e0ed17a2e654d733bb737cdc488e87db68771ff2 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 6 Jan 2025 15:31:18 +0800 Subject: [PATCH] chore: can generator middle struct --- .../format-log/graph-to-log-struct-2.spec.ts | 93 ++++++++++++++ .../utils/format-log/graph-to-log-struct-2.ts | 115 ++++++++++++++++++ .../utils/format-log/graph-to-log-struct.ts | 30 +++-- 3 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.spec.ts create mode 100644 web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.ts diff --git a/web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.spec.ts b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.spec.ts new file mode 100644 index 0000000000..3956e039d0 --- /dev/null +++ b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.spec.ts @@ -0,0 +1,93 @@ +import { parseDSL } from './graph-to-log-struct-2' + +describe('parseDSL', () => { + test('parse plain flow', () => { + const dsl = 'a -> b -> c' + const result = parseDSL(dsl) + expect(result).toEqual([ + { nodeType: 'plain', nodeId: 'a' }, + { nodeType: 'plain', nodeId: 'b' }, + { nodeType: 'plain', nodeId: 'c' }, + ]) + }) + + test('parse iteration node with flow', () => { + const dsl = '(iteration, a, b -> c)' + const result = parseDSL(dsl) + expect(result).toEqual([ + { + nodeType: 'iteration', + nodeId: 'a', + params: [ + [ + { nodeType: 'plain', nodeId: 'b' }, + { nodeType: 'plain', nodeId: 'c' }, + ], + ], + }, + ]) + }) + + test('parse parallel node with flow', () => { + const dsl = 'a -> (parallel, b, c -> d, e)' + const result = parseDSL(dsl) + expect(result).toEqual([ + { + nodeType: 'plain', + nodeId: 'a', + }, + { + nodeType: 'parallel', + nodeId: 'b', + params: [ + [ + { nodeType: 'plain', nodeId: 'c' }, + { nodeType: 'plain', nodeId: 'd' }, + ], + // single node don't need to be wrapped in an array + { nodeType: 'plain', nodeId: 'e' }, + ], + }, + ]) + }) + + test('parse retry', () => { + const dsl = '(retry, a, 3)' + const result = parseDSL(dsl) + expect(result).toEqual([ + { + nodeType: 'retry', + nodeId: 'a', + params: [3], + }, + ]) + }) + + test('parse nested complex nodes', () => { + const dsl = '(iteration, a, b -> (parallel, e, f -> g, h))' + const result = parseDSL(dsl) + expect(result).toEqual([ + { + nodeType: 'iteration', + nodeId: 'a', + params: [ + [ + { nodeType: 'plain', nodeId: 'b' }, + { + nodeType: 'parallel', + nodeId: 'e', + params: [ + [ + { nodeType: 'plain', nodeId: 'f' }, + { nodeType: 'plain', nodeId: 'g' }, + ], + // single node don't need to be wrapped in an array + { nodeType: 'plain', nodeId: 'h' }, + ], + }, + ], + ], + }, + ]) + }) +}) diff --git a/web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.ts b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.ts new file mode 100644 index 0000000000..a812b0a3c4 --- /dev/null +++ b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct-2.ts @@ -0,0 +1,115 @@ +type NodePlain = { nodeType: 'plain'; nodeId: string } +type NodeComplex = { nodeType: string; nodeId: string; params: (NodePlain | NodeComplex | Node[])[] } +type Node = NodePlain | NodeComplex + +/** + * Parses a DSL string into an array of node objects. + * @param dsl - The input DSL string. + * @returns An array of parsed nodes. + */ +function parseDSL(dsl: string): Node[] { + return parseTopLevelFlow(dsl).map(parseNode) +} + +/** + * Splits a top-level flow string by "->", respecting nested structures. + * @param dsl - The DSL string to split. + * @returns An array of top-level segments. + */ +function parseTopLevelFlow(dsl: string): string[] { + const segments: string[] = [] + let buffer = '' + let nested = 0 + + for (let i = 0; i < dsl.length; i++) { + const char = dsl[i] + if (char === '(') nested++ + if (char === ')') nested-- + if (char === '-' && dsl[i + 1] === '>' && nested === 0) { + segments.push(buffer.trim()) + buffer = '' + i++ // Skip the ">" character + } + else { + buffer += char + } + } + if (buffer.trim()) + segments.push(buffer.trim()) + + return segments +} + +/** + * Parses a single node string. + * If the node is complex (e.g., has parentheses), it extracts the node type, node ID, and parameters. + * @param nodeStr - The node string to parse. + * @returns A parsed node object. + */ +function parseNode(nodeStr: string): Node { + // Check if the node is a complex node + if (nodeStr.startsWith('(') && nodeStr.endsWith(')')) { + const innerContent = nodeStr.slice(1, -1).trim() // Remove outer parentheses + let nested = 0 + let buffer = '' + const parts: string[] = [] + + // Split the inner content by commas, respecting nested parentheses + for (let i = 0; i < innerContent.length; i++) { + const char = innerContent[i] + if (char === '(') nested++ + if (char === ')') nested-- + + if (char === ',' && nested === 0) { + parts.push(buffer.trim()) + buffer = '' + } + else { + buffer += char + } + } + parts.push(buffer.trim()) + + // Extract nodeType, nodeId, and params + const [nodeType, nodeId, ...paramsRaw] = parts + const params = parseParams(paramsRaw) + + return { + nodeType: nodeType.trim(), + nodeId: nodeId.trim(), + params, + } + } + + // If it's not a complex node, treat it as a plain node + return { nodeType: 'plain', nodeId: nodeStr.trim() } +} + +/** + * Parses parameters of a complex node. + * Supports nested flows and complex sub-nodes. + * @param paramParts - The parameters string split by commas. + * @returns An array of parsed parameters (plain nodes, nested nodes, or flows). + */ +function parseParams(paramParts: string[]): (Node | Node[])[] { + return paramParts.map((part) => { + if (part.includes('->')) { + // Parse as a flow and return an array of nodes + return parseTopLevelFlow(part).map(parseNode) + } + else if (part.startsWith('(')) { + // Parse as a nested complex node + return parseNode(part) + } + else if (!isNaN(Number(part.trim()))) { + // Parse as a numeric parameter + return Number(part.trim()) + } + else { + // Parse as a plain node + return parseNode(part) + } + }) +} + +export { parseDSL } diff --git a/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts index d7092f0642..0a3a04da09 100644 --- a/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts +++ b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts @@ -2,6 +2,7 @@ const STEP_SPLIT = '->' const toNodeData = (step: string, info: Record = {}): any => { const [nodeId, title] = step.split('@') + const data: Record = { id: nodeId, node_id: nodeId, @@ -48,8 +49,10 @@ const toIterationNodeData = ({ }) => { const res = [toNodeData(nodeId, { isIteration: true })] // TODO: handle inner node structure - for (let i = 0; i < children.length; i++) - res.push(toNodeData(`${children[i]}`, { inIterationInfo: { iterationId: nodeId, iterationIndex: i } })) + for (let i = 0; i < children.length; i++) { + const step = `${children[i]}` + res.push(toNodeData(step, { inIterationInfo: { iterationId: nodeId, iterationIndex: i } })) + } return res } @@ -104,12 +107,21 @@ export function parseNodeString(input: string): NodeStructure { for (let i = 0; i < parts.length; i++) { const part = parts[i] - if (typeof part === 'string' && part.startsWith('(')) - result.params.push(parseNodeString(part)) - else if (i === 0) - result.node = part as string - else - result.params.push(part as string) + if (typeof part === 'string') { + if (part.startsWith('(')) + result.params.push(parseNodeString(part)) + + if (part.startsWith('[')) { + const content = part.slice(1, -1) + result.params.push(parseNodeString(content)) + } + } + else if (i === 0) { + result.node = part as unknown as string + } + else { + result.params.push(part as unknown as string) + } } return result @@ -130,6 +142,8 @@ const toNodes = (input: string): any[] => { const { node, params } = parseNodeString(step) switch (node) { case 'iteration': + console.log(params) + break res.push(...toIterationNodeData({ nodeId: params[0] as string, children: JSON.parse(params[1] as string) as number[],