tuya-platform-cli 0.1.0
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/README.md +218 -0
- package/examples/rag-workflow.json +63 -0
- package/examples/simple-llm.json +37 -0
- package/package.json +40 -0
- package/src/cli.js +78 -0
- package/src/lib/analyze.js +213 -0
- package/src/lib/cdp-client.js +115 -0
- package/src/lib/chrome.js +115 -0
- package/src/lib/commands/auto-basic.js +482 -0
- package/src/lib/commands/configure.js +158 -0
- package/src/lib/commands/doctor.js +184 -0
- package/src/lib/commands/list-libraries.js +33 -0
- package/src/lib/commands/manual-record.js +82 -0
- package/src/lib/commands/publish.js +63 -0
- package/src/lib/commands/sample-branch-edges.js +391 -0
- package/src/lib/commands/sample-extra-nodes.js +204 -0
- package/src/lib/commands/sample-trial-inputs.js +173 -0
- package/src/lib/commands/shared.js +457 -0
- package/src/lib/config.js +204 -0
- package/src/lib/recorder.js +316 -0
- package/src/lib/report.js +309 -0
- package/src/lib/schema-builder.js +431 -0
- package/src/lib/selectors.js +12 -0
- package/src/lib/steps.js +50 -0
- package/src/lib/util.js +93 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
const START_NODE_ID = "100001";
|
|
2
|
+
|
|
3
|
+
export function validateDefinition(definition) {
|
|
4
|
+
const errors = [];
|
|
5
|
+
|
|
6
|
+
if (!definition.name) {
|
|
7
|
+
errors.push("Missing required field: name");
|
|
8
|
+
}
|
|
9
|
+
if (!Array.isArray(definition.startOutputs) || definition.startOutputs.length === 0) {
|
|
10
|
+
errors.push("startOutputs must be a non-empty array");
|
|
11
|
+
}
|
|
12
|
+
if (!Array.isArray(definition.nodes) || definition.nodes.length === 0) {
|
|
13
|
+
errors.push("nodes must be a non-empty array");
|
|
14
|
+
}
|
|
15
|
+
if (!Array.isArray(definition.edges) || definition.edges.length === 0) {
|
|
16
|
+
errors.push("edges must be a non-empty array");
|
|
17
|
+
}
|
|
18
|
+
if (!Array.isArray(definition.ends) || definition.ends.length === 0) {
|
|
19
|
+
errors.push("ends must be a non-empty array");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const allIds = new Set(["start"]);
|
|
23
|
+
const nodeIds = (definition.nodes ?? []).map((n) => n.id);
|
|
24
|
+
const endIds = (definition.ends ?? []).map((e) => e.id);
|
|
25
|
+
|
|
26
|
+
for (const id of [...nodeIds, ...endIds]) {
|
|
27
|
+
if (allIds.has(id)) {
|
|
28
|
+
errors.push(`Duplicate node id: ${id}`);
|
|
29
|
+
}
|
|
30
|
+
allIds.add(id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const validNodeIds = new Set(allIds);
|
|
34
|
+
|
|
35
|
+
for (const edge of definition.edges ?? []) {
|
|
36
|
+
const [source, target] = edge;
|
|
37
|
+
if (!validNodeIds.has(source)) {
|
|
38
|
+
errors.push(`Edge source "${source}" not found`);
|
|
39
|
+
}
|
|
40
|
+
if (!validNodeIds.has(target)) {
|
|
41
|
+
errors.push(`Edge target "${target}" not found`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const outputMap = new Map();
|
|
46
|
+
for (const output of definition.startOutputs ?? []) {
|
|
47
|
+
outputMap.set(`start.${output.id}`, output);
|
|
48
|
+
}
|
|
49
|
+
for (const node of definition.nodes ?? []) {
|
|
50
|
+
for (const output of node.outputs ?? []) {
|
|
51
|
+
outputMap.set(`${node.id}.${output.id}`, output);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const node of definition.nodes ?? []) {
|
|
56
|
+
for (const input of node.inputs ?? []) {
|
|
57
|
+
if (input.ref && !outputMap.has(input.ref)) {
|
|
58
|
+
errors.push(`Input ref "${input.ref}" in node "${node.id}" not found`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const end of definition.ends ?? []) {
|
|
64
|
+
if (end.input?.ref && !outputMap.has(end.input.ref)) {
|
|
65
|
+
errors.push(`Input ref "${end.input.ref}" in end "${end.id}" not found`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (errors.length > 0) {
|
|
70
|
+
throw new Error(`Definition validation failed:\n${errors.join("\n")}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveNodeId(id) {
|
|
75
|
+
return id === "start" ? START_NODE_ID : id;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveRef(ref, outputMap) {
|
|
79
|
+
const dotIndex = ref.indexOf(".");
|
|
80
|
+
const nodeId = ref.slice(0, dotIndex);
|
|
81
|
+
const outputId = ref.slice(dotIndex + 1);
|
|
82
|
+
const resolvedNodeId = resolveNodeId(nodeId);
|
|
83
|
+
const output = outputMap.get(ref);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
isRef: 1,
|
|
87
|
+
refNode: resolvedNodeId,
|
|
88
|
+
refId: outputId,
|
|
89
|
+
refName: output?.name ?? outputId,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function computeLayout(definition) {
|
|
94
|
+
const allIds = ["start", ...(definition.nodes ?? []).map((n) => n.id), ...(definition.ends ?? []).map((e) => e.id)];
|
|
95
|
+
const adjacency = new Map();
|
|
96
|
+
const inDegree = new Map();
|
|
97
|
+
|
|
98
|
+
for (const id of allIds) {
|
|
99
|
+
adjacency.set(id, []);
|
|
100
|
+
inDegree.set(id, 0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [source, target] of definition.edges) {
|
|
104
|
+
adjacency.get(source).push(target);
|
|
105
|
+
inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const column = new Map();
|
|
109
|
+
const tempInDegree = new Map(inDegree);
|
|
110
|
+
const queue = [];
|
|
111
|
+
|
|
112
|
+
for (const id of allIds) {
|
|
113
|
+
if (tempInDegree.get(id) === 0) {
|
|
114
|
+
queue.push(id);
|
|
115
|
+
column.set(id, 0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
while (queue.length > 0) {
|
|
120
|
+
const current = queue.shift();
|
|
121
|
+
|
|
122
|
+
for (const neighbor of adjacency.get(current)) {
|
|
123
|
+
const newCol = (column.get(current) ?? 0) + 1;
|
|
124
|
+
column.set(neighbor, Math.max(column.get(neighbor) ?? 0, newCol));
|
|
125
|
+
tempInDegree.set(neighbor, tempInDegree.get(neighbor) - 1);
|
|
126
|
+
if (tempInDegree.get(neighbor) === 0) {
|
|
127
|
+
queue.push(neighbor);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const columnGroups = new Map();
|
|
133
|
+
for (const [id, col] of column) {
|
|
134
|
+
if (!columnGroups.has(col)) {
|
|
135
|
+
columnGroups.set(col, []);
|
|
136
|
+
}
|
|
137
|
+
columnGroups.get(col).push(id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const positions = new Map();
|
|
141
|
+
const centerY = 320;
|
|
142
|
+
const xStart = 100;
|
|
143
|
+
const xStep = 360;
|
|
144
|
+
const yStep = 280;
|
|
145
|
+
|
|
146
|
+
for (const [col, ids] of columnGroups) {
|
|
147
|
+
const x = xStart + col * xStep;
|
|
148
|
+
const totalHeight = (ids.length - 1) * yStep;
|
|
149
|
+
const startY = centerY - totalHeight / 2;
|
|
150
|
+
|
|
151
|
+
ids.forEach((id, index) => {
|
|
152
|
+
positions.set(id, { x, y: startY + index * yStep });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return positions;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildStartNode(definition, position) {
|
|
160
|
+
return {
|
|
161
|
+
id: START_NODE_ID,
|
|
162
|
+
type: "Start",
|
|
163
|
+
meta: { position },
|
|
164
|
+
data: {
|
|
165
|
+
title: "Start",
|
|
166
|
+
nodeMeta: {
|
|
167
|
+
title: "开始",
|
|
168
|
+
description: "支持中间过程的消息输出,支持流式和非流式两种方式",
|
|
169
|
+
},
|
|
170
|
+
outputs: definition.startOutputs.map((output) => ({
|
|
171
|
+
disableEdit: false,
|
|
172
|
+
id: output.id,
|
|
173
|
+
name: output.id,
|
|
174
|
+
required: output.required ?? false,
|
|
175
|
+
type: output.type ?? "string",
|
|
176
|
+
})),
|
|
177
|
+
version: 1,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildLLMNode(nodeDef, position, outputMap) {
|
|
183
|
+
const inputs = (nodeDef.inputs ?? []).map((input) => {
|
|
184
|
+
const refResult = input.ref ? resolveRef(input.ref, outputMap) : {};
|
|
185
|
+
return {
|
|
186
|
+
constantValue: "",
|
|
187
|
+
disableEdit: false,
|
|
188
|
+
id: input.id,
|
|
189
|
+
...refResult,
|
|
190
|
+
name: input.name ?? "input",
|
|
191
|
+
type: input.type ?? "string",
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const outputs = (nodeDef.outputs ?? []).map((output) => ({
|
|
196
|
+
disableEdit: false,
|
|
197
|
+
id: output.id,
|
|
198
|
+
name: output.name ?? "output",
|
|
199
|
+
type: output.type ?? "string",
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
const params = nodeDef.params ?? {};
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
id: nodeDef.id,
|
|
206
|
+
type: "LLM",
|
|
207
|
+
meta: { position },
|
|
208
|
+
data: {
|
|
209
|
+
title: nodeDef.title ?? "大模型",
|
|
210
|
+
nodeMeta: {
|
|
211
|
+
description: "调用大语言模型,使用变量和提示词生成回复",
|
|
212
|
+
title: "大模型",
|
|
213
|
+
},
|
|
214
|
+
exceptionSetting: {
|
|
215
|
+
timeout: 60,
|
|
216
|
+
retryTimes: 0,
|
|
217
|
+
strategy: 0,
|
|
218
|
+
portId: "",
|
|
219
|
+
},
|
|
220
|
+
inputs,
|
|
221
|
+
nodeParam: {
|
|
222
|
+
model: params.model ?? "160",
|
|
223
|
+
outputType: params.outputType ?? 1,
|
|
224
|
+
skills: params.skills ?? [],
|
|
225
|
+
systemPrompt: params.systemPrompt ?? "",
|
|
226
|
+
useHistory: params.useHistory ?? 0,
|
|
227
|
+
userPrompt: params.userPrompt ?? "",
|
|
228
|
+
},
|
|
229
|
+
outputs,
|
|
230
|
+
version: 1,
|
|
231
|
+
visionInputs: [],
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function buildSelectNode(nodeDef, position) {
|
|
237
|
+
const conditions = nodeDef.conditions ?? [];
|
|
238
|
+
const selectConditions = {};
|
|
239
|
+
|
|
240
|
+
conditions.forEach((cond, index) => {
|
|
241
|
+
selectConditions[index] = {
|
|
242
|
+
portId: cond.portId,
|
|
243
|
+
conditionList: (cond.rules ?? []).map((rule) => ({
|
|
244
|
+
conditionId: rule.conditionId,
|
|
245
|
+
prerequisites: {
|
|
246
|
+
id: rule.prerequisitesId ?? `pre_${rule.conditionId}`,
|
|
247
|
+
name: rule.ref ? rule.ref.split(".")[1] : "",
|
|
248
|
+
type: rule.type ?? "string",
|
|
249
|
+
isRef: rule.ref ? 1 : 0,
|
|
250
|
+
constantValue: "",
|
|
251
|
+
},
|
|
252
|
+
postcondition: {
|
|
253
|
+
id: rule.postconditionId ?? `post_${rule.conditionId}`,
|
|
254
|
+
name: "",
|
|
255
|
+
type: rule.type ?? "string",
|
|
256
|
+
isRef: 0,
|
|
257
|
+
constantValue: rule.value ?? "",
|
|
258
|
+
},
|
|
259
|
+
judge: rule.judge ?? "=",
|
|
260
|
+
})),
|
|
261
|
+
conditionRelation: cond.conditionRelation ?? "&&",
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
selectConditions["-1"] = {
|
|
266
|
+
portId: nodeDef.elsePortId ?? "condition_else_port",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
id: nodeDef.id,
|
|
271
|
+
type: "Select",
|
|
272
|
+
meta: { position },
|
|
273
|
+
data: {
|
|
274
|
+
title: nodeDef.title ?? "选择器",
|
|
275
|
+
nodeMeta: {
|
|
276
|
+
title: "选择器",
|
|
277
|
+
description: "连接多个下游分支,若设定的条件成立则仅运行对应的分支,若均不成立则只运行\u201C否则\u201D分支",
|
|
278
|
+
},
|
|
279
|
+
exceptionSetting: {
|
|
280
|
+
timeout: 60,
|
|
281
|
+
retryTimes: 0,
|
|
282
|
+
strategy: 0,
|
|
283
|
+
portId: "",
|
|
284
|
+
},
|
|
285
|
+
inputs: [],
|
|
286
|
+
outputs: [],
|
|
287
|
+
nodeParam: {
|
|
288
|
+
selectConditions,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function buildSearchKnowledgeNode(nodeDef, position, outputMap) {
|
|
295
|
+
const inputs = (nodeDef.inputs ?? []).map((input) => {
|
|
296
|
+
const refResult = input.ref ? resolveRef(input.ref, outputMap) : {};
|
|
297
|
+
return {
|
|
298
|
+
constantValue: "",
|
|
299
|
+
disableEdit: false,
|
|
300
|
+
id: input.id,
|
|
301
|
+
...refResult,
|
|
302
|
+
name: input.name ?? "query",
|
|
303
|
+
type: input.type ?? "string",
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const outputs = (nodeDef.outputs ?? []).map((output) => ({
|
|
308
|
+
disableEdit: false,
|
|
309
|
+
id: output.id,
|
|
310
|
+
name: output.name ?? "data",
|
|
311
|
+
type: output.type ?? "string",
|
|
312
|
+
}));
|
|
313
|
+
|
|
314
|
+
const params = nodeDef.params ?? {};
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
id: nodeDef.id,
|
|
318
|
+
type: "SearchKnowledge",
|
|
319
|
+
meta: { position },
|
|
320
|
+
data: {
|
|
321
|
+
title: nodeDef.title ?? "知识库检索",
|
|
322
|
+
nodeMeta: {
|
|
323
|
+
title: "知识库检索",
|
|
324
|
+
description: "在选定的知识中,根据输入变量召回最匹配的信息,并以列表形式返回",
|
|
325
|
+
},
|
|
326
|
+
inputs,
|
|
327
|
+
nodeParam: {
|
|
328
|
+
libResource: params.libResource ?? [],
|
|
329
|
+
maxRecallNum: params.maxRecallNum ?? 5,
|
|
330
|
+
searchStrategy: params.searchStrategy ?? 1,
|
|
331
|
+
similarity: params.similarity ?? 0.7,
|
|
332
|
+
},
|
|
333
|
+
outputs,
|
|
334
|
+
version: 1,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function buildEndNode(endDef, position, outputMap) {
|
|
340
|
+
const inputs = [];
|
|
341
|
+
if (endDef.input) {
|
|
342
|
+
const refResult = endDef.input.ref ? resolveRef(endDef.input.ref, outputMap) : {};
|
|
343
|
+
inputs.push({
|
|
344
|
+
constantValue: "",
|
|
345
|
+
disableEdit: false,
|
|
346
|
+
id: endDef.input.id,
|
|
347
|
+
...refResult,
|
|
348
|
+
name: endDef.input.name ?? refResult.refName ?? "output",
|
|
349
|
+
type: endDef.input.type ?? "string",
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
id: endDef.id,
|
|
355
|
+
type: "End",
|
|
356
|
+
meta: { position },
|
|
357
|
+
data: {
|
|
358
|
+
title: endDef.title ?? "输出",
|
|
359
|
+
nodeMeta: {
|
|
360
|
+
description: "支持中间过程的消息输出,支持流式和非流式两种方式",
|
|
361
|
+
title: "输出",
|
|
362
|
+
},
|
|
363
|
+
nodeParam: {
|
|
364
|
+
isStream: 0,
|
|
365
|
+
outputContent: "",
|
|
366
|
+
saveRecord: true,
|
|
367
|
+
tts: true,
|
|
368
|
+
},
|
|
369
|
+
inputs,
|
|
370
|
+
visionInputs: [],
|
|
371
|
+
audioInputs: [],
|
|
372
|
+
outputs: [],
|
|
373
|
+
version: 1,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function buildSchemaFromDefinition(definition) {
|
|
379
|
+
const outputMap = new Map();
|
|
380
|
+
for (const output of definition.startOutputs) {
|
|
381
|
+
outputMap.set(`start.${output.id}`, { name: output.id, type: output.type ?? "string" });
|
|
382
|
+
}
|
|
383
|
+
for (const node of definition.nodes) {
|
|
384
|
+
for (const output of node.outputs ?? []) {
|
|
385
|
+
outputMap.set(`${node.id}.${output.id}`, { name: output.name ?? "output", type: output.type ?? "string" });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const positions = computeLayout(definition);
|
|
390
|
+
|
|
391
|
+
const nodes = [];
|
|
392
|
+
|
|
393
|
+
nodes.push(buildStartNode(definition, positions.get("start") ?? { x: 100, y: 320 }));
|
|
394
|
+
|
|
395
|
+
for (const nodeDef of definition.nodes) {
|
|
396
|
+
const position = positions.get(nodeDef.id) ?? { x: 460, y: 320 };
|
|
397
|
+
|
|
398
|
+
switch (nodeDef.type) {
|
|
399
|
+
case "LLM":
|
|
400
|
+
nodes.push(buildLLMNode(nodeDef, position, outputMap));
|
|
401
|
+
break;
|
|
402
|
+
case "Select":
|
|
403
|
+
nodes.push(buildSelectNode(nodeDef, position));
|
|
404
|
+
break;
|
|
405
|
+
case "SearchKnowledge":
|
|
406
|
+
nodes.push(buildSearchKnowledgeNode(nodeDef, position, outputMap));
|
|
407
|
+
break;
|
|
408
|
+
default:
|
|
409
|
+
throw new Error(`Unsupported node type: ${nodeDef.type}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for (const endDef of definition.ends) {
|
|
414
|
+
const position = positions.get(endDef.id) ?? { x: 820, y: 320 };
|
|
415
|
+
nodes.push(buildEndNode(endDef, position, outputMap));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const edges = definition.edges.map(([source, target]) => ({
|
|
419
|
+
sourceNodeID: resolveNodeId(source),
|
|
420
|
+
targetNodeID: resolveNodeId(target),
|
|
421
|
+
}));
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
editVersionNo: 1,
|
|
425
|
+
nodes,
|
|
426
|
+
edges,
|
|
427
|
+
workflow: {
|
|
428
|
+
region: definition.region ?? "AZ",
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Centralized CSS selectors for Tuya workflow platform UI elements.
|
|
2
|
+
// These class names are build artifacts and may change with platform updates.
|
|
3
|
+
|
|
4
|
+
export const SELECTORS = {
|
|
5
|
+
NODE_PANEL_ITEM: ".node-panel_nodeItem__1AnsQ",
|
|
6
|
+
FLOW_ACTIVITY_NODE: ".gedit-flow-activity-node",
|
|
7
|
+
CODE_MIRROR_EDITOR: '.cm-content[contenteditable="true"]',
|
|
8
|
+
MODAL_WRAP: ".ant-modal-wrap",
|
|
9
|
+
SELECT_SELECTOR: ".ant-select-selector",
|
|
10
|
+
SELECT_DROPDOWN_OPTION: ".ant-select-dropdown .ant-select-item-option-content",
|
|
11
|
+
MORE_ICON: ".icon-gengduo",
|
|
12
|
+
};
|
package/src/lib/steps.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const STEPS = [
|
|
2
|
+
{
|
|
3
|
+
id: "step-01-list-load",
|
|
4
|
+
title: "工作流列表加载",
|
|
5
|
+
instruction:
|
|
6
|
+
"在浏览器中打开工作流列表页,等待列表、筛选栏和表格加载完成,不要额外点击。",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: "step-02-create-workflow",
|
|
10
|
+
title: "创建工作流",
|
|
11
|
+
instruction:
|
|
12
|
+
'点击“创建工作流”,填写名称“测试工作流”、描述“test”,如弹窗有数据中心字段则选择“美西”,然后确认创建。',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "step-03-detail-load",
|
|
16
|
+
title: "工作流详情加载",
|
|
17
|
+
instruction:
|
|
18
|
+
"进入新建工作流的编辑页,等待编辑器初始化完成,不要立即拖动画布。",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "step-04-add-intent-node",
|
|
22
|
+
title: "添加节点 - 意图识别节点",
|
|
23
|
+
instruction:
|
|
24
|
+
'点击“添加节点”,选择“意图识别”,模式选择“极速”,至少添加一个意图条目并保存该节点配置。',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "step-05-add-llm-node",
|
|
28
|
+
title: "添加节点 - 大模型节点",
|
|
29
|
+
instruction:
|
|
30
|
+
'点击“添加节点”,选择“大模型”,选第一个可用模型,填写系统提示词和用户提示词并确认。',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "step-06-save-workflow",
|
|
34
|
+
title: "保存工作流",
|
|
35
|
+
instruction:
|
|
36
|
+
"执行画布保存,等待保存完成。本步骤是最关键抓取点,保存完成前不要进行其他操作。",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "step-07-trial-run",
|
|
40
|
+
title: "试运行",
|
|
41
|
+
instruction:
|
|
42
|
+
'点击“试运行”,数据中心选“美西”,输入 `USER_TEXT=你好`,执行并等待结果流完全结束。',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "step-08-delete-workflow",
|
|
46
|
+
title: "删除工作流",
|
|
47
|
+
instruction:
|
|
48
|
+
'返回工作流列表页,通过 hover 打开操作菜单,删除刚创建的“测试工作流”并确认。',
|
|
49
|
+
},
|
|
50
|
+
];
|
package/src/lib/util.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import readline from "node:readline/promises";
|
|
5
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
6
|
+
|
|
7
|
+
export function expandHome(inputPath) {
|
|
8
|
+
if (inputPath == null) {
|
|
9
|
+
throw new TypeError("expandHome: path must not be null or undefined");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (inputPath === "~") {
|
|
13
|
+
return os.homedir();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (inputPath.startsWith("~/")) {
|
|
17
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return inputPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function ensureDir(dirPath) {
|
|
24
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
25
|
+
return dirPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function writeJson(filePath, value) {
|
|
29
|
+
await ensureDir(path.dirname(filePath));
|
|
30
|
+
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function writeText(filePath, text) {
|
|
34
|
+
await ensureDir(path.dirname(filePath));
|
|
35
|
+
await fs.writeFile(filePath, text, "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function slugify(value) {
|
|
39
|
+
return value
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
42
|
+
.replace(/^-+|-+$/g, "")
|
|
43
|
+
.slice(0, 64);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function timestampForFile(date = new Date()) {
|
|
47
|
+
return date.toISOString().replace(/[:.]/g, "-");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function safeJsonParse(text) {
|
|
51
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(text);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function prompt(message) {
|
|
63
|
+
const rl = readline.createInterface({ input, output });
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
return await rl.question(message);
|
|
67
|
+
} finally {
|
|
68
|
+
rl.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function sleep(ms) {
|
|
73
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function trimBody(body, limit = 200_000) {
|
|
77
|
+
if (body == null) {
|
|
78
|
+
return body;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stringBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
82
|
+
if (Buffer.byteLength(stringBody, "utf8") <= limit) {
|
|
83
|
+
return stringBody;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const buf = Buffer.from(stringBody, "utf8");
|
|
87
|
+
let end = limit;
|
|
88
|
+
// Avoid truncating in the middle of a multi-byte UTF-8 character
|
|
89
|
+
while (end > 0 && (buf[end] & 0xc0) === 0x80) {
|
|
90
|
+
end -= 1;
|
|
91
|
+
}
|
|
92
|
+
return `${buf.toString("utf8", 0, end)}\n...<truncated>`;
|
|
93
|
+
}
|