digitalsee-ai-flow-cli 0.1.6 → 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.
Files changed (91) hide show
  1. package/.github/workflows/ci.yml +24 -0
  2. package/.github/workflows/publish.yml +21 -0
  3. package/CHANGELOG.md +72 -0
  4. package/dist/api/client.d.ts.map +1 -1
  5. package/dist/api/client.js +4 -4
  6. package/dist/api/client.js.map +1 -1
  7. package/dist/api/flow.d.ts.map +1 -1
  8. package/dist/api/flow.js +16 -13
  9. package/dist/api/flow.js.map +1 -1
  10. package/dist/api/knowledge.d.ts.map +1 -1
  11. package/dist/api/knowledge.js +3 -3
  12. package/dist/api/knowledge.js.map +1 -1
  13. package/dist/api/node.d.ts.map +1 -1
  14. package/dist/api/node.js +4 -4
  15. package/dist/api/node.js.map +1 -1
  16. package/dist/api/variables.d.ts.map +1 -1
  17. package/dist/api/variables.js +1 -1
  18. package/dist/api/variables.js.map +1 -1
  19. package/dist/commands/auth.d.ts.map +1 -1
  20. package/dist/commands/auth.js +44 -34
  21. package/dist/commands/auth.js.map +1 -1
  22. package/dist/commands/config.d.ts.map +1 -1
  23. package/dist/commands/config.js +24 -26
  24. package/dist/commands/config.js.map +1 -1
  25. package/dist/commands/flow/account.d.ts.map +1 -1
  26. package/dist/commands/flow/account.js +17 -16
  27. package/dist/commands/flow/account.js.map +1 -1
  28. package/dist/commands/flow/analyze.d.ts.map +1 -1
  29. package/dist/commands/flow/analyze.js +42 -42
  30. package/dist/commands/flow/analyze.js.map +1 -1
  31. package/dist/commands/flow/create.d.ts.map +1 -1
  32. package/dist/commands/flow/create.js +7 -6
  33. package/dist/commands/flow/create.js.map +1 -1
  34. package/dist/commands/flow/edge.d.ts.map +1 -1
  35. package/dist/commands/flow/edge.js +36 -35
  36. package/dist/commands/flow/edge.js.map +1 -1
  37. package/dist/commands/flow/helpers.d.ts.map +1 -1
  38. package/dist/commands/flow/helpers.js +84 -74
  39. package/dist/commands/flow/helpers.js.map +1 -1
  40. package/dist/commands/flow/list.d.ts.map +1 -1
  41. package/dist/commands/flow/list.js +7 -7
  42. package/dist/commands/flow/list.js.map +1 -1
  43. package/dist/commands/flow/node.d.ts.map +1 -1
  44. package/dist/commands/flow/node.js +164 -134
  45. package/dist/commands/flow/node.js.map +1 -1
  46. package/dist/commands/flow/test.d.ts.map +1 -1
  47. package/dist/commands/flow/test.js +20 -12
  48. package/dist/commands/flow/test.js.map +1 -1
  49. package/dist/commands/flow/update.js +5 -5
  50. package/dist/commands/flow/update.js.map +1 -1
  51. package/dist/commands/flow/variables.d.ts.map +1 -1
  52. package/dist/commands/flow/variables.js +45 -43
  53. package/dist/commands/flow/variables.js.map +1 -1
  54. package/dist/commands/flow.d.ts.map +1 -1
  55. package/dist/commands/flow.js +1 -3
  56. package/dist/commands/flow.js.map +1 -1
  57. package/dist/commands/knowledge.d.ts.map +1 -1
  58. package/dist/commands/knowledge.js +68 -70
  59. package/dist/commands/knowledge.js.map +1 -1
  60. package/dist/index.js +31 -20
  61. package/dist/index.js.map +1 -1
  62. package/dist/services/flowAnalyzer.d.ts.map +1 -1
  63. package/dist/services/flowAnalyzer.js +256 -203
  64. package/dist/services/flowAnalyzer.js.map +1 -1
  65. package/dist/services/skillGenerator.d.ts.map +1 -1
  66. package/dist/services/skillGenerator.js +31 -31
  67. package/dist/services/skillGenerator.js.map +1 -1
  68. package/dist/types/index.d.ts.map +1 -1
  69. package/dist/utils/config.d.ts.map +1 -1
  70. package/dist/utils/config.js +12 -10
  71. package/dist/utils/config.js.map +1 -1
  72. package/dist/utils/encoding.d.ts.map +1 -1
  73. package/dist/utils/encoding.js +3 -3
  74. package/dist/utils/encoding.js.map +1 -1
  75. package/dist/utils/format.d.ts.map +1 -1
  76. package/dist/utils/format.js +471 -126
  77. package/dist/utils/format.js.map +1 -1
  78. package/dist/utils/oauth.d.ts.map +1 -1
  79. package/dist/utils/oauth.js +54 -78
  80. package/dist/utils/oauth.js.map +1 -1
  81. package/dist/utils/output.d.ts.map +1 -1
  82. package/dist/utils/output.js +20 -16
  83. package/dist/utils/output.js.map +1 -1
  84. package/dist/utils/updateCheck.d.ts.map +1 -0
  85. package/dist/utils/updateCheck.js +138 -0
  86. package/dist/utils/updateCheck.js.map +1 -0
  87. package/package.json +15 -7
  88. package/tsconfig.json +1 -1
  89. package/dist/utils/auth.d.ts.map +0 -1
  90. package/dist/utils/auth.js +0 -213
  91. 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: "AI Agent",
14
- AISubNode: "AI 子节点",
15
- BrowserNode: "浏览器",
16
- MCPServerNode: "MCP 服务",
17
- BuiltinMCPNode: "内置 MCP",
18
- MCPToolNode: "MCP 工具",
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 executionOrder = topologicalSort(adj);
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 topologicalSort(adj) {
72
- const inDegree = { ...Object.fromEntries(Object.entries(adj).map(([id, info]) => [id, info.inDegree])) };
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 || "ActionNode";
104
- if (type === "StartEventNode" ||
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 === "ConditionNode") {
135
+ else if (type === 'ConditionNode') {
110
136
  groups.conditions.push(node);
111
137
  }
112
- else if (type === "SwitchNode") {
138
+ else if (type === 'SwitchNode') {
113
139
  groups.conditions.push(node);
114
140
  }
115
- else if (type === "LoopNode" || type === "SelfLoopNode") {
141
+ else if (type === 'LoopNode' || type === 'SelfLoopNode') {
116
142
  groups.loops.push(node);
117
143
  }
118
- else if (type === "AgentNode" ||
119
- type === "AISubNode" ||
120
- type === "BrowserNode" ||
121
- type === "VectorStoreNode" ||
122
- type === "MCPServerNode" ||
123
- type === "BuiltinMCPNode" ||
124
- type === "MCPToolNode") {
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 === "ActionNode" || !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
- const maxDepth = Math.max(...Object.values(depth), 0);
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 || ""] || 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("\n");
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 += (code >= 0x4e00 && code <= 0x9fff) ||
196
- (code >= 0x3000 && code <= 0x303f) ||
197
- (code >= 0xff00 && code <= 0xffef) ? 2 : 1;
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 + " ".repeat(Math.max(0, targetWidth - dw));
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
- const maxDepth = Math.max(...Object.values(depth), 0);
226
- // Group nodes by depth, ordered by median parent position
227
- const BOX_W = 42;
228
- const INNER_W = BOX_W - 2;
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
- const rowW = row.length * BOX_W + (row.length - 1) * GAP;
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 + i * (BOX_W + GAP);
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
- const halfW = Math.floor(((children.length - 1) * (BOX_W + GAP)) / 2);
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
- const cx = pCX - halfW + i * (BOX_W + GAP);
290
- rawPositions[children[i]] = cx;
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 (shouldn't happen at d>0, but handle)
362
+ // Nodes without parents
303
363
  for (const nid of row) {
304
364
  if (rawPositions[nid] === undefined) {
305
- rawPositions[nid] = Math.floor(BOX_W / 2) + Object.keys(rawPositions).length * (BOX_W + GAP);
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 = Math.floor(BOX_W / 2);
312
- for (const nid of sorted) {
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
- nextX = boxCenters[nid] + BOX_W + GAP;
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 = BOX_W;
321
- for (const cx of Object.values(boxCenters)) {
322
- const rightEdge = cx + Math.floor(BOX_W / 2) + 1;
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.values(boxCenters).map((cx) => cx - Math.floor(BOX_W / 2)));
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 name = node?.connector_name || node?.action_name || nid;
335
- const typeLabel = NODE_TYPE_LABELS[node?.node_type || ""] || node?.node_type || "";
336
- const shortId = nid.length > 22 ? nid.substring(0, 20) + ".." : nid;
337
- const padW = INNER_W - 1;
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
- "┌" + "─".repeat(INNER_W) + "┐",
340
- "│ " + padRight(name, padW) + "│",
341
- "│ " + padRight(shortId, padW) + "│",
342
- "│ " + padRight(typeLabel, padW) + "│",
343
- "└" + "─".repeat(INNER_W) + "┘",
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] = ch;
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 cx = boxCenters[nid] - Math.floor(BOX_W / 2);
468
+ const leftX = boxCenters[nid] - boxHalfLeft(nid);
386
469
  for (let i = 0; i < BOX_H; i++) {
387
- drawStr(cx, currentY + i, box[i]);
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
- if (d > 0) {
392
- const prevRow = depthNodes[d - 1] || [];
393
- const startY = rowMap[prevRow[0]] + BOX_H;
394
- const endY = currentY;
395
- const midY = startY + Math.floor((endY - startY) / 2);
396
- // Build parent ↔ child maps
397
- const childParents = {};
398
- const parentChildren = {};
399
- for (const nid of row) {
400
- childParents[nid] = (adj[nid]?.incoming || []).filter((p) => prevRow.includes(p));
401
- for (const p of childParents[nid]) {
402
- if (!parentChildren[p])
403
- parentChildren[p] = [];
404
- parentChildren[p].push(nid);
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
- // Draw vertical lines from every parent that has children
408
- for (const p of prevRow) {
409
- if ((parentChildren[p]?.length || 0) > 0) {
410
- drawLine(boxCenters[p], startY, boxCenters[p], midY, "│");
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
- // Draw horizontal connections and verticals down to children
414
- const drawnParent = new Set();
415
- const doneMerge = new Set();
416
- for (const nid of row) {
417
- const parents = childParents[nid];
418
- if (parents.length === 0)
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
- currentY += BOX_H;
496
- if (d < maxDepth) {
497
- const nextRow = depthNodes[d + 1] || [];
498
- const hasConnection = row.some((nid) => (adj[nid]?.outgoing || []).some((c) => nextRow.includes(c)));
499
- const arrowLines = hasConnection ? 3 : 1;
500
- currentY += arrowLines;
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("\n");
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