feat(ai-web): 完成自研流程图的保存

This commit is contained in:
2025-07-03 23:40:41 +08:00
parent abdbb5ed03
commit d979b3941d
10 changed files with 92 additions and 376 deletions

View File

@@ -1,19 +1,10 @@
import {type Connection, type Node} from '@xyflow/react'
import {uuid} from 'licia'
import {expect, test} from 'vitest'
import {
atLeastOneEndNodeError,
atLeastOneStartNodeError,
checkAddConnection,
checkAddNode,
checkSave,
hasCycleError,
hasRedundantEdgeError,
multiEndNodeError,
multiStartNodeError,
nodeToSelfError,
sourceNodeNotFoundError,
startNodeToEndNodeError,
targetNodeNotFoundError,
} from './FlowChecker.tsx'
@@ -29,9 +20,6 @@ const createNode = (id: string, type: string): Node => {
}
}
const createStartNode = (id: string): Node => createNode(id, 'start-node')
const createEndNode = (id: string): Node => createNode(id, 'end-node')
const createConnection = function (source: string, target: string, sourceHandle: string | null = null, targetHandle: string | null = null): Connection {
return {
source,
@@ -41,31 +29,13 @@ const createConnection = function (source: string, target: string, sourceHandle:
}
}
/* check add node */
test(multiStartNodeError().message, () => {
expect(() => checkAddNode('start-node', [createStartNode(uuid())], [])).toThrowError(multiStartNodeError())
})
test(multiEndNodeError().message, () => {
expect(() => checkAddNode('end-node', [createEndNode(uuid())], [])).toThrowError(multiEndNodeError())
})
/* check add connection */
test(sourceNodeNotFoundError().message, () => {
expect(() => checkAddConnection(createConnection('a', 'b'), [], []))
})
test(targetNodeNotFoundError().message, () => {
expect(() => checkAddConnection(createConnection('a', 'b'), [createStartNode('a')], []))
})
test(startNodeToEndNodeError().message, () => {
expect(() => checkAddConnection(
createConnection('a', 'b'),
[createStartNode('a'), createEndNode('b')],
[]
))
expect(() => checkAddConnection(createConnection('a', 'b'), [createNode('a', 'normal-node')], []))
})
test(nodeToSelfError().message, () => {
@@ -90,24 +60,3 @@ test(hasCycleError().message, () => {
checkAddConnection(JSON.parse('{\n "source": "yp-yYfKUzC",\n "sourceHandle": null,\n "target": "N4HQPN-NYZ",\n "targetHandle": null\n}'), nodes, edges)
}).toThrowError(hasCycleError())
})
test(hasRedundantEdgeError().message, () => {
expect(() => {
// language=JSON
const {
nodes,
edges,
} = JSON.parse('{\n "nodes": [\n {\n "id": "TCxPixrdkI",\n "type": "start-node",\n "position": {\n "x": -256,\n "y": 109.5\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 83\n },\n "selected": false,\n "dragging": false\n },\n {\n "id": "tGs78_ietp",\n "type": "llm-node",\n "position": {\n "x": 108,\n "y": -2.5\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 105\n },\n "selected": false,\n "dragging": false\n },\n {\n "id": "OeZdaU7LpY",\n "type": "llm-node",\n "position": {\n "x": 111,\n "y": 196\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 105\n },\n "selected": false,\n "dragging": false\n },\n {\n "id": "LjfoCYZo-E",\n "type": "knowledge-node",\n "position": {\n "x": 497.62196259607214,\n "y": -10.792497317791003\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 75\n },\n "selected": true,\n "dragging": false\n },\n {\n "id": "sQM_22GYB5",\n "type": "end-node",\n "position": {\n "x": 874.3164534765615,\n "y": 151.70316541496913\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 75\n },\n "selected": false,\n "dragging": false\n },\n {\n "id": "KpMH_xc3ZZ",\n "type": "llm-node",\n "position": {\n "x": 529.6286840434341,\n "y": 150.4721376669937\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 75\n },\n "selected": false,\n "dragging": false\n }\n ],\n "edges": [\n {\n "source": "TCxPixrdkI",\n "sourceHandle": "source",\n "target": "tGs78_ietp",\n "targetHandle": "target",\n "id": "xy-edge__TCxPixrdkIsource-tGs78_ietptarget"\n },\n {\n "source": "TCxPixrdkI",\n "sourceHandle": "source",\n "target": "OeZdaU7LpY",\n "targetHandle": "target",\n "id": "xy-edge__TCxPixrdkIsource-OeZdaU7LpYtarget"\n },\n {\n "source": "tGs78_ietp",\n "sourceHandle": "source",\n "target": "LjfoCYZo-E",\n "targetHandle": "target",\n "id": "xy-edge__tGs78_ietpsource-LjfoCYZo-Etarget"\n },\n {\n "source": "LjfoCYZo-E",\n "sourceHandle": "source",\n "target": "KpMH_xc3ZZ",\n "targetHandle": "target",\n "id": "xy-edge__LjfoCYZo-Esource-KpMH_xc3ZZtarget"\n },\n {\n "source": "OeZdaU7LpY",\n "sourceHandle": "source",\n "target": "KpMH_xc3ZZ",\n "targetHandle": "target",\n "id": "xy-edge__OeZdaU7LpYsource-KpMH_xc3ZZtarget"\n },\n {\n "source": "KpMH_xc3ZZ",\n "sourceHandle": "source",\n "target": "sQM_22GYB5",\n "targetHandle": "target",\n "id": "xy-edge__KpMH_xc3ZZsource-sQM_22GYB5target"\n }\n ],\n "data": {\n "tGs78_ietp": {\n "model": "qwen3",\n "outputs": {\n "text": {\n "type": "string"\n }\n },\n "systemPrompt": "你是个聪明人"\n },\n "OeZdaU7LpY": {\n "model": "qwen3",\n "outputs": {\n "text": {\n "type": "string"\n }\n },\n "systemPrompt": "你也是个聪明人"\n }\n }\n}')
// language=JSON
checkAddConnection(JSON.parse('{\n "source": "OeZdaU7LpY",\n "sourceHandle": "source",\n "target": "LjfoCYZo-E",\n "targetHandle": "target"\n}'), nodes, edges)
}).toThrowError(hasRedundantEdgeError())
})
/* check save */
test(atLeastOneStartNodeError().message, () => {
expect(() => checkSave([], [], {})).toThrowError(atLeastOneStartNodeError())
})
test(atLeastOneEndNodeError().message, () => {
expect(() => checkSave([createStartNode(uuid())], [], {})).toThrowError(atLeastOneEndNodeError())
})