digitalsee-ai-flow-cli 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +24 -0
- package/.github/workflows/publish.yml +21 -0
- package/CHANGELOG.md +72 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +4 -4
- package/dist/api/client.js.map +1 -1
- package/dist/api/flow.d.ts.map +1 -1
- package/dist/api/flow.js +16 -13
- package/dist/api/flow.js.map +1 -1
- package/dist/api/knowledge.d.ts.map +1 -1
- package/dist/api/knowledge.js +3 -3
- package/dist/api/knowledge.js.map +1 -1
- package/dist/api/node.d.ts.map +1 -1
- package/dist/api/node.js +4 -4
- package/dist/api/node.js.map +1 -1
- package/dist/api/variables.d.ts.map +1 -1
- package/dist/api/variables.js +1 -1
- package/dist/api/variables.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +44 -34
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +24 -26
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/flow/account.d.ts.map +1 -1
- package/dist/commands/flow/account.js +17 -16
- package/dist/commands/flow/account.js.map +1 -1
- package/dist/commands/flow/analyze.d.ts.map +1 -1
- package/dist/commands/flow/analyze.js +42 -42
- package/dist/commands/flow/analyze.js.map +1 -1
- package/dist/commands/flow/create.d.ts.map +1 -1
- package/dist/commands/flow/create.js +7 -6
- package/dist/commands/flow/create.js.map +1 -1
- package/dist/commands/flow/edge.d.ts.map +1 -1
- package/dist/commands/flow/edge.js +36 -35
- package/dist/commands/flow/edge.js.map +1 -1
- package/dist/commands/flow/helpers.d.ts.map +1 -1
- package/dist/commands/flow/helpers.js +84 -74
- package/dist/commands/flow/helpers.js.map +1 -1
- package/dist/commands/flow/list.d.ts.map +1 -1
- package/dist/commands/flow/list.js +7 -7
- package/dist/commands/flow/list.js.map +1 -1
- package/dist/commands/flow/node.d.ts.map +1 -1
- package/dist/commands/flow/node.js +150 -137
- package/dist/commands/flow/node.js.map +1 -1
- package/dist/commands/flow/test.d.ts.map +1 -1
- package/dist/commands/flow/test.js +14 -14
- package/dist/commands/flow/test.js.map +1 -1
- package/dist/commands/flow/update.js +5 -5
- package/dist/commands/flow/update.js.map +1 -1
- package/dist/commands/flow/variables.d.ts.map +1 -1
- package/dist/commands/flow/variables.js +45 -43
- package/dist/commands/flow/variables.js.map +1 -1
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +1 -3
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/knowledge.d.ts.map +1 -1
- package/dist/commands/knowledge.js +68 -70
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/index.js +31 -20
- package/dist/index.js.map +1 -1
- package/dist/services/flowAnalyzer.d.ts.map +1 -1
- package/dist/services/flowAnalyzer.js +256 -203
- package/dist/services/flowAnalyzer.js.map +1 -1
- package/dist/services/skillGenerator.d.ts.map +1 -1
- package/dist/services/skillGenerator.js +31 -31
- package/dist/services/skillGenerator.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -10
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/encoding.d.ts.map +1 -1
- package/dist/utils/encoding.js +3 -3
- package/dist/utils/encoding.js.map +1 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +234 -220
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/oauth.d.ts.map +1 -1
- package/dist/utils/oauth.js +54 -78
- package/dist/utils/oauth.js.map +1 -1
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +20 -16
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/updateCheck.d.ts.map +1 -0
- package/dist/utils/updateCheck.js +138 -0
- package/dist/utils/updateCheck.js.map +1 -0
- package/package.json +15 -7
- package/tsconfig.json +1 -1
- package/dist/utils/auth.d.ts.map +0 -1
- package/dist/utils/auth.js +0 -213
- package/dist/utils/auth.js.map +0 -1
|
@@ -3,22 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.analyzeFlow = analyzeFlow;
|
|
4
4
|
exports.formatNodeType = formatNodeType;
|
|
5
5
|
const NODE_TYPE_LABELS = {
|
|
6
|
-
StartEventNode:
|
|
7
|
-
ActionNode:
|
|
8
|
-
ConditionNode:
|
|
9
|
-
LoopNode:
|
|
10
|
-
SelfLoopNode:
|
|
11
|
-
SwitchNode:
|
|
12
|
-
EndNode:
|
|
13
|
-
AgentNode:
|
|
14
|
-
AISubNode:
|
|
15
|
-
BrowserNode:
|
|
16
|
-
MCPServerNode:
|
|
17
|
-
BuiltinMCPNode:
|
|
18
|
-
MCPToolNode:
|
|
19
|
-
VectorStoreNode:
|
|
20
|
-
StickyNoteNode:
|
|
21
|
-
START:
|
|
6
|
+
StartEventNode: '触发器',
|
|
7
|
+
ActionNode: '动作',
|
|
8
|
+
ConditionNode: '条件',
|
|
9
|
+
LoopNode: '循环',
|
|
10
|
+
SelfLoopNode: '自循环',
|
|
11
|
+
SwitchNode: '分支',
|
|
12
|
+
EndNode: '结束',
|
|
13
|
+
AgentNode: 'AI Agent',
|
|
14
|
+
AISubNode: 'AI 子节点',
|
|
15
|
+
BrowserNode: '浏览器',
|
|
16
|
+
MCPServerNode: 'MCP 服务',
|
|
17
|
+
BuiltinMCPNode: '内置 MCP',
|
|
18
|
+
MCPToolNode: 'MCP 工具',
|
|
19
|
+
VectorStoreNode: '向量存储',
|
|
20
|
+
StickyNoteNode: '便签',
|
|
21
|
+
START: '开始',
|
|
22
22
|
};
|
|
23
23
|
function analyzeFlow(flow) {
|
|
24
24
|
const { nodes = [], edges = [] } = flow;
|
|
@@ -35,8 +35,8 @@ function analyzeFlow(flow) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
for (const edge of edges) {
|
|
38
|
-
const source = edge.source ||
|
|
39
|
-
const target = edge.target ||
|
|
38
|
+
const source = edge.source || '';
|
|
39
|
+
const target = edge.target || '';
|
|
40
40
|
if (adj[source]) {
|
|
41
41
|
adj[source].outgoing.push(target);
|
|
42
42
|
}
|
|
@@ -45,7 +45,11 @@ function analyzeFlow(flow) {
|
|
|
45
45
|
adj[target].inDegree++;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
const
|
|
48
|
+
const nodeIndexMap = new Map();
|
|
49
|
+
nodes.forEach((node, i) => {
|
|
50
|
+
nodeIndexMap.set(node.n_id || node.id, i);
|
|
51
|
+
});
|
|
52
|
+
const executionOrder = topologicalSort(adj, nodeIndexMap);
|
|
49
53
|
const nodeGroups = groupNodes(nodes);
|
|
50
54
|
const dependencyGraph = renderDependencyGraph(adj, executionOrder);
|
|
51
55
|
const flowchart = renderAsciiFlowchart(adj, executionOrder);
|
|
@@ -68,8 +72,16 @@ function analyzeFlow(flow) {
|
|
|
68
72
|
flowchart,
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
|
-
function
|
|
72
|
-
const
|
|
75
|
+
function isTriggerNode(node) {
|
|
76
|
+
const type = node.node_type || 'ActionNode';
|
|
77
|
+
return (type === 'StartEventNode' ||
|
|
78
|
+
type === 'START' ||
|
|
79
|
+
(node.connector_name?.toLowerCase().includes('webhook') ?? false));
|
|
80
|
+
}
|
|
81
|
+
function topologicalSort(adj, nodeIndexMap) {
|
|
82
|
+
const inDegree = {
|
|
83
|
+
...Object.fromEntries(Object.entries(adj).map(([id, info]) => [id, info.inDegree])),
|
|
84
|
+
};
|
|
73
85
|
const queue = [];
|
|
74
86
|
for (const [id, degree] of Object.entries(inDegree)) {
|
|
75
87
|
if (degree === 0) {
|
|
@@ -78,7 +90,17 @@ function topologicalSort(adj) {
|
|
|
78
90
|
}
|
|
79
91
|
const result = [];
|
|
80
92
|
while (queue.length > 0) {
|
|
81
|
-
queue.sort()
|
|
93
|
+
queue.sort((a, b) => {
|
|
94
|
+
const aTrigger = isTriggerNode(adj[a].node);
|
|
95
|
+
const bTrigger = isTriggerNode(adj[b].node);
|
|
96
|
+
if (aTrigger && !bTrigger)
|
|
97
|
+
return -1;
|
|
98
|
+
if (!aTrigger && bTrigger)
|
|
99
|
+
return 1;
|
|
100
|
+
const aIdx = nodeIndexMap.get(a) ?? Infinity;
|
|
101
|
+
const bIdx = nodeIndexMap.get(b) ?? Infinity;
|
|
102
|
+
return aIdx - bIdx;
|
|
103
|
+
});
|
|
82
104
|
const current = queue.shift();
|
|
83
105
|
result.push(current);
|
|
84
106
|
for (const next of adj[current]?.outgoing || []) {
|
|
@@ -88,6 +110,12 @@ function topologicalSort(adj) {
|
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
112
|
}
|
|
113
|
+
// Include remaining nodes (in cycles) so they appear in graph/flowchart output
|
|
114
|
+
const remaining = Object.entries(inDegree)
|
|
115
|
+
.filter(([, d]) => d > 0)
|
|
116
|
+
.map(([id]) => id)
|
|
117
|
+
.sort((a, b) => (nodeIndexMap.get(a) ?? Infinity) - (nodeIndexMap.get(b) ?? Infinity));
|
|
118
|
+
result.push(...remaining);
|
|
91
119
|
return result;
|
|
92
120
|
}
|
|
93
121
|
function groupNodes(nodes) {
|
|
@@ -100,31 +128,29 @@ function groupNodes(nodes) {
|
|
|
100
128
|
others: [],
|
|
101
129
|
};
|
|
102
130
|
for (const node of nodes) {
|
|
103
|
-
const type = node.node_type ||
|
|
104
|
-
if (
|
|
105
|
-
type === "START" ||
|
|
106
|
-
node.connector_name?.toLowerCase().includes("webhook")) {
|
|
131
|
+
const type = node.node_type || 'ActionNode';
|
|
132
|
+
if (isTriggerNode(node)) {
|
|
107
133
|
groups.triggers.push(node);
|
|
108
134
|
}
|
|
109
|
-
else if (type ===
|
|
135
|
+
else if (type === 'ConditionNode') {
|
|
110
136
|
groups.conditions.push(node);
|
|
111
137
|
}
|
|
112
|
-
else if (type ===
|
|
138
|
+
else if (type === 'SwitchNode') {
|
|
113
139
|
groups.conditions.push(node);
|
|
114
140
|
}
|
|
115
|
-
else if (type ===
|
|
141
|
+
else if (type === 'LoopNode' || type === 'SelfLoopNode') {
|
|
116
142
|
groups.loops.push(node);
|
|
117
143
|
}
|
|
118
|
-
else if (type ===
|
|
119
|
-
type ===
|
|
120
|
-
type ===
|
|
121
|
-
type ===
|
|
122
|
-
type ===
|
|
123
|
-
type ===
|
|
124
|
-
type ===
|
|
144
|
+
else if (type === 'AgentNode' ||
|
|
145
|
+
type === 'AISubNode' ||
|
|
146
|
+
type === 'BrowserNode' ||
|
|
147
|
+
type === 'VectorStoreNode' ||
|
|
148
|
+
type === 'MCPServerNode' ||
|
|
149
|
+
type === 'BuiltinMCPNode' ||
|
|
150
|
+
type === 'MCPToolNode') {
|
|
125
151
|
groups.aiNodes.push(node);
|
|
126
152
|
}
|
|
127
|
-
else if (type ===
|
|
153
|
+
else if (type === 'ActionNode' || !type) {
|
|
128
154
|
groups.actions.push(node);
|
|
129
155
|
}
|
|
130
156
|
else {
|
|
@@ -135,7 +161,7 @@ function groupNodes(nodes) {
|
|
|
135
161
|
}
|
|
136
162
|
function renderDependencyGraph(adj, order) {
|
|
137
163
|
if (order.length === 0)
|
|
138
|
-
return
|
|
164
|
+
return '(无节点)';
|
|
139
165
|
const lines = [];
|
|
140
166
|
const depth = {};
|
|
141
167
|
const visiting = new Set();
|
|
@@ -156,7 +182,14 @@ function renderDependencyGraph(adj, order) {
|
|
|
156
182
|
assignDepth(id, 0);
|
|
157
183
|
}
|
|
158
184
|
}
|
|
159
|
-
|
|
185
|
+
// Assign fallback depth to nodes in cycles (not reachable from inDegree-0 nodes)
|
|
186
|
+
let maxDepth = Math.max(...Object.values(depth), 0);
|
|
187
|
+
for (const id of order) {
|
|
188
|
+
if (depth[id] === undefined) {
|
|
189
|
+
maxDepth = maxDepth + 1;
|
|
190
|
+
depth[id] = maxDepth;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
160
193
|
const depthNodes = {};
|
|
161
194
|
for (let i = 0; i <= maxDepth; i++)
|
|
162
195
|
depthNodes[i] = [];
|
|
@@ -171,36 +204,39 @@ function renderDependencyGraph(adj, order) {
|
|
|
171
204
|
const labels = nodeIds.map((nid) => {
|
|
172
205
|
const node = adj[nid]?.node;
|
|
173
206
|
const name = node?.connector_name || node?.action_name || nid;
|
|
174
|
-
const type = NODE_TYPE_LABELS[node?.node_type ||
|
|
207
|
+
const type = NODE_TYPE_LABELS[node?.node_type || ''] || node?.node_type || '';
|
|
175
208
|
return `[${type}] ${name}`;
|
|
176
209
|
});
|
|
177
|
-
lines.push(labels.join(
|
|
210
|
+
lines.push(labels.join(' │ '));
|
|
178
211
|
if (d < maxDepth) {
|
|
179
212
|
const hasChildren = nodeIds.some((nid) => adj[nid]?.outgoing.length > 0);
|
|
180
213
|
if (hasChildren) {
|
|
181
|
-
lines.push(
|
|
182
|
-
lines.push(
|
|
214
|
+
lines.push(' │');
|
|
215
|
+
lines.push(' ▼');
|
|
183
216
|
}
|
|
184
217
|
}
|
|
185
218
|
}
|
|
186
|
-
return lines.join(
|
|
219
|
+
return lines.join('\n');
|
|
187
220
|
}
|
|
188
221
|
function renderAsciiFlowchart(adj, order) {
|
|
189
222
|
if (order.length === 0)
|
|
190
|
-
return
|
|
223
|
+
return '(无节点)';
|
|
191
224
|
function displayWidth(s) {
|
|
192
225
|
let w = 0;
|
|
193
226
|
for (const ch of s) {
|
|
194
227
|
const code = ch.charCodeAt(0);
|
|
195
|
-
w +=
|
|
196
|
-
(code >=
|
|
197
|
-
|
|
228
|
+
w +=
|
|
229
|
+
(code >= 0x4e00 && code <= 0x9fff) ||
|
|
230
|
+
(code >= 0x3000 && code <= 0x303f) ||
|
|
231
|
+
(code >= 0xff00 && code <= 0xffef)
|
|
232
|
+
? 2
|
|
233
|
+
: 1;
|
|
198
234
|
}
|
|
199
235
|
return w;
|
|
200
236
|
}
|
|
201
237
|
function padRight(text, targetWidth) {
|
|
202
238
|
const dw = displayWidth(text);
|
|
203
|
-
return text +
|
|
239
|
+
return text + ' '.repeat(Math.max(0, targetWidth - dw));
|
|
204
240
|
}
|
|
205
241
|
// Assign depths via longest-path DFS
|
|
206
242
|
const depth = {};
|
|
@@ -222,11 +258,34 @@ function renderAsciiFlowchart(adj, order) {
|
|
|
222
258
|
assignDepth(id, 0);
|
|
223
259
|
}
|
|
224
260
|
}
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
const
|
|
228
|
-
|
|
261
|
+
let adjustedMaxDepth = Math.max(...Object.values(depth), 0);
|
|
262
|
+
// Assign fallback depth to cycle nodes (not reachable from inDegree-0 nodes)
|
|
263
|
+
for (const id of order) {
|
|
264
|
+
if (depth[id] === undefined) {
|
|
265
|
+
adjustedMaxDepth = adjustedMaxDepth + 1;
|
|
266
|
+
depth[id] = adjustedMaxDepth;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const maxDepth = adjustedMaxDepth;
|
|
270
|
+
// Pre-compute per-node box widths based on content
|
|
271
|
+
const MIN_BOX_W = 22;
|
|
229
272
|
const GAP = 4;
|
|
273
|
+
function boxHalfLeft(_nid) {
|
|
274
|
+
return Math.floor(boxWidths[_nid] / 2);
|
|
275
|
+
}
|
|
276
|
+
function boxHalfRight(_nid) {
|
|
277
|
+
return Math.ceil(boxWidths[_nid] / 2);
|
|
278
|
+
}
|
|
279
|
+
const boxWidths = {};
|
|
280
|
+
for (const nid of order) {
|
|
281
|
+
const node = adj[nid]?.node;
|
|
282
|
+
const actionName = node?.action_name || '';
|
|
283
|
+
const connectorName = node?.connector_name || '';
|
|
284
|
+
const shortId = nid.length > 22 ? nid.substring(0, 20) + '..' : nid;
|
|
285
|
+
const maxTextW = Math.max(displayWidth(actionName), displayWidth(connectorName || nid), displayWidth(shortId));
|
|
286
|
+
// boxW = maxTextW + 3 (left border "│ " + right border "│")
|
|
287
|
+
boxWidths[nid] = Math.max(Math.max(maxTextW, 0) + 3, MIN_BOX_W);
|
|
288
|
+
}
|
|
230
289
|
const depthNodes = {};
|
|
231
290
|
for (let d = 0; d <= maxDepth; d++) {
|
|
232
291
|
depthNodes[d] = [];
|
|
@@ -243,16 +302,16 @@ function renderAsciiFlowchart(adj, order) {
|
|
|
243
302
|
if (row.length === 0)
|
|
244
303
|
continue;
|
|
245
304
|
if (d === 0) {
|
|
246
|
-
// First row: space evenly
|
|
247
|
-
|
|
248
|
-
const startX = Math.floor(BOX_W / 2);
|
|
305
|
+
// First row: space evenly with per-node widths
|
|
306
|
+
let startX = boxHalfLeft(row[0]);
|
|
249
307
|
for (let i = 0; i < row.length; i++) {
|
|
250
|
-
boxCenters[row[i]] = startX
|
|
308
|
+
boxCenters[row[i]] = startX;
|
|
309
|
+
if (i < row.length - 1) {
|
|
310
|
+
startX += boxHalfRight(row[i]) + GAP + boxHalfLeft(row[i + 1]);
|
|
311
|
+
}
|
|
251
312
|
}
|
|
252
313
|
}
|
|
253
314
|
else {
|
|
254
|
-
// Position children based on parent positions
|
|
255
|
-
// Build child -> parents and parent -> children
|
|
256
315
|
const prevRow = depthNodes[d - 1] || [];
|
|
257
316
|
const childParents = {};
|
|
258
317
|
const parentChildren = {};
|
|
@@ -264,7 +323,6 @@ function renderAsciiFlowchart(adj, order) {
|
|
|
264
323
|
parentChildren[p].push(nid);
|
|
265
324
|
}
|
|
266
325
|
}
|
|
267
|
-
// First, place children near their parents
|
|
268
326
|
const rawPositions = {};
|
|
269
327
|
const placed = new Set();
|
|
270
328
|
// Children of single-child parents: place under parent
|
|
@@ -284,11 +342,13 @@ function renderAsciiFlowchart(adj, order) {
|
|
|
284
342
|
if (children.length === 0)
|
|
285
343
|
continue;
|
|
286
344
|
const pCX = boxCenters[p];
|
|
287
|
-
|
|
345
|
+
// Total width including gaps
|
|
346
|
+
const totalW = children.reduce((sum, c) => sum + boxWidths[c], 0) + (children.length - 1) * GAP;
|
|
347
|
+
const startOffset = pCX - Math.floor(totalW / 2) + boxHalfLeft(children[0]);
|
|
348
|
+
let curCX = startOffset;
|
|
288
349
|
for (let i = 0; i < children.length; i++) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
placed.add(children[i]);
|
|
350
|
+
rawPositions[children[i]] = curCX;
|
|
351
|
+
curCX += boxHalfRight(children[i]) + GAP + boxHalfLeft(children[i + 1] ?? '');
|
|
292
352
|
}
|
|
293
353
|
}
|
|
294
354
|
// Children with multiple parents: average parent positions
|
|
@@ -299,80 +359,103 @@ function renderAsciiFlowchart(adj, order) {
|
|
|
299
359
|
rawPositions[nid] = Math.round(sum / parents.length);
|
|
300
360
|
}
|
|
301
361
|
}
|
|
302
|
-
// Nodes without parents
|
|
362
|
+
// Nodes without parents
|
|
303
363
|
for (const nid of row) {
|
|
304
364
|
if (rawPositions[nid] === undefined) {
|
|
305
|
-
|
|
365
|
+
let accX = boxHalfLeft(nid);
|
|
366
|
+
for (const existing of Object.keys(rawPositions)) {
|
|
367
|
+
accX += boxHalfRight(existing) + GAP + boxHalfLeft(nid);
|
|
368
|
+
}
|
|
369
|
+
rawPositions[nid] = accX;
|
|
306
370
|
}
|
|
307
371
|
}
|
|
308
372
|
// Sort row by raw position and resolve overlaps
|
|
309
373
|
const sorted = [...row].sort((a, b) => (rawPositions[a] ?? 0) - (rawPositions[b] ?? 0));
|
|
310
|
-
// Resolve overlaps with a sweep
|
|
311
|
-
let nextX =
|
|
312
|
-
for (
|
|
374
|
+
// Resolve overlaps with a sweep using actual box widths
|
|
375
|
+
let nextX = boxHalfLeft(sorted[0]);
|
|
376
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
377
|
+
const nid = sorted[i];
|
|
313
378
|
const ideal = rawPositions[nid] ?? nextX;
|
|
314
379
|
boxCenters[nid] = Math.max(nextX, ideal);
|
|
315
|
-
|
|
380
|
+
if (i < sorted.length - 1) {
|
|
381
|
+
const nextNid = sorted[i + 1];
|
|
382
|
+
nextX = boxCenters[nid] + boxHalfRight(nid) + GAP + boxHalfLeft(nextNid);
|
|
383
|
+
}
|
|
316
384
|
}
|
|
317
385
|
}
|
|
318
386
|
}
|
|
319
|
-
// Calculate canvas width
|
|
320
|
-
let canvasW =
|
|
321
|
-
for (const
|
|
322
|
-
const
|
|
387
|
+
// Calculate canvas width using actual box sizes
|
|
388
|
+
let canvasW = 0;
|
|
389
|
+
for (const nid of Object.keys(boxCenters)) {
|
|
390
|
+
const cx = boxCenters[nid];
|
|
391
|
+
const rightEdge = cx + boxHalfRight(nid);
|
|
323
392
|
if (rightEdge > canvasW)
|
|
324
393
|
canvasW = rightEdge;
|
|
325
394
|
}
|
|
326
395
|
// Normalize to start at 0
|
|
327
|
-
const minX = Math.min(...Object.
|
|
396
|
+
const minX = Math.min(...Object.keys(boxCenters).map((nid) => boxCenters[nid] - boxHalfLeft(nid)));
|
|
328
397
|
for (const nid of Object.keys(boxCenters)) {
|
|
329
398
|
boxCenters[nid] -= minX;
|
|
330
399
|
}
|
|
331
400
|
canvasW -= minX;
|
|
332
401
|
function buildBox(nid) {
|
|
333
402
|
const node = adj[nid]?.node;
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
const shortId = nid.length > 22 ? nid.substring(0, 20) +
|
|
337
|
-
const
|
|
403
|
+
const actionName = node?.action_name || '';
|
|
404
|
+
const connectorName = node?.connector_name || nid;
|
|
405
|
+
const shortId = nid.length > 22 ? nid.substring(0, 20) + '..' : nid;
|
|
406
|
+
const boxW = boxWidths[nid];
|
|
407
|
+
const innerW = boxW - 2;
|
|
408
|
+
const padW = innerW - 1;
|
|
338
409
|
return [
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
410
|
+
'┌' + '─'.repeat(innerW) + '┐',
|
|
411
|
+
'│ ' + padRight(connectorName, padW) + '│',
|
|
412
|
+
'│ ' + padRight(shortId, padW) + '│',
|
|
413
|
+
'│ ' + padRight(actionName, padW) + '│',
|
|
414
|
+
'└' + '─'.repeat(innerW) + '┘',
|
|
344
415
|
];
|
|
345
416
|
}
|
|
346
417
|
const BOX_H = 5;
|
|
347
418
|
const canvas = [];
|
|
348
419
|
const rowMap = {};
|
|
420
|
+
// Track occupied box regions to prevent edge lines from drawing through boxes
|
|
421
|
+
const boxRegions = [];
|
|
349
422
|
function ensureRow(y) {
|
|
350
423
|
while (canvas.length <= y) {
|
|
351
|
-
canvas.push(Array(canvasW).fill(
|
|
424
|
+
canvas.push(Array(canvasW).fill(' '));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function isInBoxRegion(cx, y) {
|
|
428
|
+
for (const r of boxRegions) {
|
|
429
|
+
if (cx >= r.x1 && cx < r.x2 && y >= r.y1 && y < r.y2)
|
|
430
|
+
return true;
|
|
352
431
|
}
|
|
432
|
+
return false;
|
|
353
433
|
}
|
|
354
|
-
function drawStr(x, y, s) {
|
|
434
|
+
function drawStr(x, y, s, soft = false) {
|
|
355
435
|
ensureRow(y);
|
|
356
436
|
let cx = x;
|
|
357
437
|
for (const ch of s) {
|
|
358
438
|
if (cx >= 0 && cx < canvasW) {
|
|
359
|
-
canvas[y][cx]
|
|
439
|
+
const cellEmpty = canvas[y][cx] === ' ' || canvas[y][cx] === '';
|
|
440
|
+
if (!soft || (cellEmpty && !isInBoxRegion(cx, y))) {
|
|
441
|
+
canvas[y][cx] = ch;
|
|
442
|
+
}
|
|
360
443
|
}
|
|
361
444
|
const dw = displayWidth(ch);
|
|
362
445
|
for (let j = 1; j < dw && cx + j < canvasW; j++) {
|
|
363
|
-
canvas[y][cx + j] =
|
|
446
|
+
canvas[y][cx + j] = '';
|
|
364
447
|
}
|
|
365
448
|
cx += dw;
|
|
366
449
|
}
|
|
367
450
|
}
|
|
368
|
-
function drawLine(x1, y1, x2, y2, ch) {
|
|
451
|
+
function drawLine(x1, y1, x2, y2, ch, soft = false) {
|
|
369
452
|
if (x1 === x2) {
|
|
370
453
|
for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++)
|
|
371
|
-
drawStr(x1, y, ch);
|
|
454
|
+
drawStr(x1, y, ch, soft);
|
|
372
455
|
}
|
|
373
456
|
else if (y1 === y2) {
|
|
374
457
|
for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++)
|
|
375
|
-
drawStr(x, y1, ch);
|
|
458
|
+
drawStr(x, y1, ch, soft);
|
|
376
459
|
}
|
|
377
460
|
}
|
|
378
461
|
let currentY = 0;
|
|
@@ -382,135 +465,105 @@ function renderAsciiFlowchart(adj, order) {
|
|
|
382
465
|
continue;
|
|
383
466
|
for (const nid of row) {
|
|
384
467
|
const box = buildBox(nid);
|
|
385
|
-
const
|
|
468
|
+
const leftX = boxCenters[nid] - boxHalfLeft(nid);
|
|
386
469
|
for (let i = 0; i < BOX_H; i++) {
|
|
387
|
-
drawStr(
|
|
470
|
+
drawStr(leftX, currentY + i, box[i]);
|
|
388
471
|
}
|
|
389
472
|
rowMap[nid] = currentY;
|
|
473
|
+
boxRegions.push({
|
|
474
|
+
x1: leftX,
|
|
475
|
+
x2: leftX + boxWidths[nid],
|
|
476
|
+
y1: currentY,
|
|
477
|
+
y2: currentY + BOX_H,
|
|
478
|
+
});
|
|
390
479
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
480
|
+
currentY += BOX_H;
|
|
481
|
+
if (d < maxDepth)
|
|
482
|
+
currentY += 3;
|
|
483
|
+
}
|
|
484
|
+
// Draw all edges using unified per-edge routing
|
|
485
|
+
// Backward edges drawn first so their arrows take priority in shared gap space
|
|
486
|
+
const GAP_H = 3;
|
|
487
|
+
// Pass 1: backward edges (src deeper than tgt)
|
|
488
|
+
for (const nid of order) {
|
|
489
|
+
for (const tgt of adj[nid]?.outgoing || []) {
|
|
490
|
+
if (!boxCenters[nid] || !boxCenters[tgt])
|
|
491
|
+
continue;
|
|
492
|
+
if (depth[nid] === undefined || depth[tgt] === undefined)
|
|
493
|
+
continue;
|
|
494
|
+
if (depth[nid] <= depth[tgt])
|
|
495
|
+
continue;
|
|
496
|
+
const srcCX = boxCenters[nid];
|
|
497
|
+
const tgtCX = boxCenters[tgt];
|
|
498
|
+
const srcTop = rowMap[nid];
|
|
499
|
+
const tgtBottom = rowMap[tgt] + BOX_H - 1;
|
|
500
|
+
const midY = srcTop - Math.floor(GAP_H / 2) - 1;
|
|
501
|
+
drawLine(srcCX, srcTop - 2, srcCX, midY, '│', true);
|
|
502
|
+
if (srcCX !== tgtCX) {
|
|
503
|
+
drawLine(Math.min(srcCX, tgtCX), midY, Math.max(srcCX, tgtCX), midY, '─', true);
|
|
504
|
+
drawStr(srcCX, midY, tgtCX > srcCX ? '└' : '┘', true);
|
|
406
505
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
506
|
+
drawLine(tgtCX, midY, tgtCX, tgtBottom + 2, '│', true);
|
|
507
|
+
drawStr(tgtCX, tgtBottom + 1, '▲', false);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// Pass 2: forward edges (src shallower than tgt)
|
|
511
|
+
for (const nid of order) {
|
|
512
|
+
for (const tgt of adj[nid]?.outgoing || []) {
|
|
513
|
+
if (!boxCenters[nid] || !boxCenters[tgt])
|
|
514
|
+
continue;
|
|
515
|
+
if (depth[nid] === undefined || depth[tgt] === undefined)
|
|
516
|
+
continue;
|
|
517
|
+
if (depth[nid] >= depth[tgt])
|
|
518
|
+
continue;
|
|
519
|
+
const srcCX = boxCenters[nid];
|
|
520
|
+
const tgtCX = boxCenters[tgt];
|
|
521
|
+
const srcBottom = rowMap[nid] + BOX_H;
|
|
522
|
+
const tgtTop = rowMap[tgt];
|
|
523
|
+
if (srcBottom >= tgtTop)
|
|
524
|
+
continue;
|
|
525
|
+
if (srcCX === tgtCX) {
|
|
526
|
+
drawLine(srcCX, srcBottom, tgtCX, tgtTop - 2, '│', true);
|
|
412
527
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
continue;
|
|
420
|
-
const childCX = boxCenters[nid];
|
|
421
|
-
if (parents.length === 1) {
|
|
422
|
-
const p = parents[0];
|
|
423
|
-
const siblings = parentChildren[p] || [nid];
|
|
424
|
-
if (siblings.length > 1) {
|
|
425
|
-
// Branching: one parent → multiple children
|
|
426
|
-
if (drawnParent.has(p))
|
|
427
|
-
continue;
|
|
428
|
-
drawnParent.add(p);
|
|
429
|
-
const pCX = boxCenters[p];
|
|
430
|
-
const sibCenters = siblings.map((c) => boxCenters[c]);
|
|
431
|
-
const minCX = Math.min(pCX, ...sibCenters);
|
|
432
|
-
const maxCX = Math.max(pCX, ...sibCenters);
|
|
433
|
-
drawLine(minCX, midY, maxCX, midY, "─");
|
|
434
|
-
// Pick junction char: parent vertical (up) + child directions
|
|
435
|
-
const hasBelow = sibCenters.some((cx) => cx === pCX);
|
|
436
|
-
const hasLeft = sibCenters.some((cx) => cx < pCX);
|
|
437
|
-
const hasRight = sibCenters.some((cx) => cx > pCX);
|
|
438
|
-
if (hasBelow && hasLeft && hasRight)
|
|
439
|
-
drawStr(pCX, midY, "┼");
|
|
440
|
-
else if (hasBelow && hasRight)
|
|
441
|
-
drawStr(pCX, midY, "├");
|
|
442
|
-
else if (hasBelow && hasLeft)
|
|
443
|
-
drawStr(pCX, midY, "┤");
|
|
444
|
-
else
|
|
445
|
-
drawStr(pCX, midY, "┬");
|
|
446
|
-
for (const ccx of sibCenters) {
|
|
447
|
-
drawLine(ccx, midY + 1, ccx, endY - 1, "│");
|
|
448
|
-
drawStr(ccx, endY - 1, "▼");
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
// Single child
|
|
453
|
-
const pCX = boxCenters[p];
|
|
454
|
-
if (pCX === childCX) {
|
|
455
|
-
drawLine(childCX, midY, childCX, endY - 1, "│");
|
|
456
|
-
drawStr(childCX, endY - 1, "▼");
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
drawLine(Math.min(pCX, childCX), midY, Math.max(pCX, childCX), midY, "─");
|
|
460
|
-
drawLine(childCX, midY + 1, childCX, endY - 1, "│");
|
|
461
|
-
drawStr(pCX, midY, childCX > pCX ? "└" : "┘");
|
|
462
|
-
drawStr(childCX, endY - 1, "▼");
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
// Merge: multiple parents → one child
|
|
468
|
-
if (doneMerge.has(nid))
|
|
469
|
-
continue;
|
|
470
|
-
// Group children that share these same parents
|
|
471
|
-
const siblingSet = new Set();
|
|
472
|
-
for (const other of row) {
|
|
473
|
-
const op = childParents[other];
|
|
474
|
-
if (op.length > 1 && op.some((pp) => parents.includes(pp))) {
|
|
475
|
-
siblingSet.add(other);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
const pCenters = parents.map((pp) => boxCenters[pp]);
|
|
479
|
-
const allChildCenters = [...siblingSet].map((c) => boxCenters[c]);
|
|
480
|
-
const minCX = Math.min(...pCenters, ...allChildCenters);
|
|
481
|
-
const maxCX = Math.max(...pCenters, ...allChildCenters);
|
|
482
|
-
drawLine(minCX, midY, maxCX, midY, "─");
|
|
483
|
-
for (const pCX of pCenters)
|
|
484
|
-
drawStr(pCX, midY, "┴");
|
|
485
|
-
for (const c of siblingSet) {
|
|
486
|
-
const ccx = boxCenters[c];
|
|
487
|
-
drawStr(ccx, midY, "┬");
|
|
488
|
-
drawLine(ccx, midY + 1, ccx, endY - 1, "│");
|
|
489
|
-
drawStr(ccx, endY - 1, "▼");
|
|
490
|
-
doneMerge.add(c);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
528
|
+
else {
|
|
529
|
+
const routeY = srcBottom + Math.floor(GAP_H / 2);
|
|
530
|
+
drawLine(srcCX, srcBottom, srcCX, routeY, '│', true);
|
|
531
|
+
drawLine(Math.min(srcCX, tgtCX), routeY, Math.max(srcCX, tgtCX), routeY, '─', true);
|
|
532
|
+
drawStr(srcCX, routeY, tgtCX > srcCX ? '└' : '┘', true);
|
|
533
|
+
drawLine(tgtCX, routeY + 1, tgtCX, tgtTop - 2, '│', true);
|
|
493
534
|
}
|
|
535
|
+
drawStr(tgtCX, tgtTop - 1, '▼', true);
|
|
494
536
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
537
|
+
}
|
|
538
|
+
// Draw same-depth edges (horizontal) just above the target box
|
|
539
|
+
for (const nid of order) {
|
|
540
|
+
const outgoing = adj[nid]?.outgoing || [];
|
|
541
|
+
for (const tgt of outgoing) {
|
|
542
|
+
if (!boxCenters[nid] || !boxCenters[tgt])
|
|
543
|
+
continue;
|
|
544
|
+
if (depth[nid] !== depth[tgt])
|
|
545
|
+
continue;
|
|
546
|
+
if (boxCenters[nid] === boxCenters[tgt])
|
|
547
|
+
continue;
|
|
548
|
+
const tgtTop = rowMap[tgt];
|
|
549
|
+
const y = tgtTop - 1;
|
|
550
|
+
const srcCX = boxCenters[nid];
|
|
551
|
+
const tgtCX = boxCenters[tgt];
|
|
552
|
+
drawLine(Math.min(srcCX, tgtCX), y, Math.max(srcCX, tgtCX), y, '─', true);
|
|
553
|
+
drawStr(tgtCX, y, tgtCX > srcCX ? '▶' : '◀', true);
|
|
501
554
|
}
|
|
502
555
|
}
|
|
503
556
|
const lines = canvas.map((row) => {
|
|
504
|
-
let line =
|
|
557
|
+
let line = '';
|
|
505
558
|
for (const cell of row) {
|
|
506
|
-
if (cell !==
|
|
559
|
+
if (cell !== '')
|
|
507
560
|
line += cell;
|
|
508
561
|
}
|
|
509
562
|
return line.trimEnd();
|
|
510
563
|
});
|
|
511
|
-
return lines.join(
|
|
564
|
+
return lines.join('\n');
|
|
512
565
|
}
|
|
513
566
|
function formatNodeType(type) {
|
|
514
|
-
return NODE_TYPE_LABELS[type] || type ||
|
|
567
|
+
return NODE_TYPE_LABELS[type] || type || '未知';
|
|
515
568
|
}
|
|
516
569
|
//# sourceMappingURL=flowAnalyzer.js.map
|