@saltcorn/copilot 0.7.5 → 0.8.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/actions/generate-js-action.js +43 -5
- package/actions/generate-tables.js +281 -96
- package/actions/generate-workflow.js +83 -36
- package/actions/install-plugin-action.js +103 -0
- package/agent-skills/app-constructor-context.js +25 -0
- package/agent-skills/database-design.js +139 -87
- package/agent-skills/install-plugin.js +111 -0
- package/agent-skills/js-action.js +183 -0
- package/agent-skills/registry-editor.js +911 -0
- package/agent-skills/viewgen.js +239 -21
- package/agent-skills/workflow.js +52 -2
- package/app-constructor/common.js +12 -0
- package/app-constructor/errors.js +102 -0
- package/app-constructor/feedback-action.js +175 -0
- package/app-constructor/feedback.js +112 -0
- package/app-constructor/progress.js +116 -0
- package/app-constructor/prompts.js +58 -0
- package/app-constructor/requirements.js +156 -0
- package/app-constructor/run_task.js +128 -0
- package/app-constructor/schema.js +186 -0
- package/app-constructor/taskchart.js +70 -0
- package/app-constructor/tasks.js +401 -0
- package/app-constructor/tools.js +81 -0
- package/app-constructor/view.js +209 -0
- package/builder-gen.js +534 -66
- package/builder-schema.js +26 -6
- package/common.js +20 -0
- package/copilot-as-agent.js +6 -1
- package/index.js +23 -1
- package/js-code-gen.js +65 -0
- package/package.json +1 -1
- package/tests/builder-gen.test.js +56 -0
package/agent-skills/viewgen.js
CHANGED
|
@@ -15,6 +15,90 @@ const {
|
|
|
15
15
|
} = require("@saltcorn/markup/tags");
|
|
16
16
|
const builderGen = require("../builder-gen");
|
|
17
17
|
|
|
18
|
+
const collectLayoutFieldNames = (segment, out = new Set()) => {
|
|
19
|
+
if (!segment || typeof segment !== "object") return out;
|
|
20
|
+
if (Array.isArray(segment)) {
|
|
21
|
+
segment.forEach((s) => collectLayoutFieldNames(s, out));
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
if (segment.type === "field" && segment.field_name)
|
|
25
|
+
out.add(segment.field_name);
|
|
26
|
+
if (segment.above) collectLayoutFieldNames(segment.above, out);
|
|
27
|
+
if (segment.besides) collectLayoutFieldNames(segment.besides, out);
|
|
28
|
+
if (segment.contents) collectLayoutFieldNames(segment.contents, out);
|
|
29
|
+
if (Array.isArray(segment.tabs))
|
|
30
|
+
segment.tabs.forEach((t) => collectLayoutFieldNames(t?.contents, out));
|
|
31
|
+
return out;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const findFilterFieldSegment = (segment) => {
|
|
35
|
+
if (!segment || typeof segment !== "object") return null;
|
|
36
|
+
if (segment.type === "field") return segment;
|
|
37
|
+
if (segment.type === "dropdown_filter" || segment.type === "toggle_filter") {
|
|
38
|
+
return { field_name: segment.field_name, fieldview: "edit" };
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(segment.above)) {
|
|
41
|
+
for (const item of segment.above) {
|
|
42
|
+
const found = findFilterFieldSegment(item);
|
|
43
|
+
if (found) return found;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(segment.besides)) {
|
|
47
|
+
for (const item of segment.besides) {
|
|
48
|
+
const found = findFilterFieldSegment(item);
|
|
49
|
+
if (found) return found;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (segment.contents) {
|
|
53
|
+
if (Array.isArray(segment.contents)) {
|
|
54
|
+
for (const item of segment.contents) {
|
|
55
|
+
const found = findFilterFieldSegment(item);
|
|
56
|
+
if (found) return found;
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
const found = findFilterFieldSegment(segment.contents);
|
|
60
|
+
if (found) return found;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(segment.tabs)) {
|
|
64
|
+
for (const tab of segment.tabs) {
|
|
65
|
+
if (tab?.contents) {
|
|
66
|
+
const found = findFilterFieldSegment(tab.contents);
|
|
67
|
+
if (found) return found;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(segment.contents) && Array.isArray(segment.contents[0])) {
|
|
72
|
+
for (const row of segment.contents) {
|
|
73
|
+
if (Array.isArray(row)) {
|
|
74
|
+
for (const cell of row) {
|
|
75
|
+
const found = findFilterFieldSegment(cell);
|
|
76
|
+
if (found) return found;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const normalizeFilterField = (segment) => ({
|
|
85
|
+
type: "field",
|
|
86
|
+
field_name: segment.field_name,
|
|
87
|
+
fieldview: segment.fieldview || "edit",
|
|
88
|
+
textStyle: segment.textStyle || "",
|
|
89
|
+
block: segment.block ?? false,
|
|
90
|
+
configuration: segment.configuration || {},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const toFilterColumn = (segment) => ({
|
|
94
|
+
type: "Field",
|
|
95
|
+
field_name: segment.field_name,
|
|
96
|
+
fieldview: segment.fieldview || "edit",
|
|
97
|
+
textStyle: segment.textStyle || "",
|
|
98
|
+
block: segment.block ?? false,
|
|
99
|
+
configuration: segment.configuration || {},
|
|
100
|
+
});
|
|
101
|
+
|
|
18
102
|
class GenerateViewSkill {
|
|
19
103
|
static skill_name = "Generate View";
|
|
20
104
|
|
|
@@ -27,8 +111,11 @@ class GenerateViewSkill {
|
|
|
27
111
|
}
|
|
28
112
|
|
|
29
113
|
async systemPrompt() {
|
|
30
|
-
return
|
|
31
|
-
|
|
114
|
+
return (
|
|
115
|
+
`If the user asks to generate a view, use the generate_view tool to enter ` +
|
|
116
|
+
`a view generation mode. The tool call only requires high-level details to start this sequence.\n` +
|
|
117
|
+
`The Edit viewtemplate serves both create (no id in state) and edit (id in state) — one view covers both modes.`
|
|
118
|
+
);
|
|
32
119
|
}
|
|
33
120
|
|
|
34
121
|
get userActions() {
|
|
@@ -50,6 +137,10 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
50
137
|
min_role: { admin: 1, public: 100, user: 80 }[normalizedRole],
|
|
51
138
|
configuration: wfctx,
|
|
52
139
|
});
|
|
140
|
+
const vt = getState().viewtemplates[viewpattern];
|
|
141
|
+
if (vt?.copilot_post_create) {
|
|
142
|
+
await vt.copilot_post_create({ name, configuration: wfctx });
|
|
143
|
+
}
|
|
53
144
|
setTimeout(() => getState().refresh_views(), 200);
|
|
54
145
|
return {
|
|
55
146
|
notify: `View saved: <a target="_blank" href="/view/${name}">${name}</a>`,
|
|
@@ -66,13 +157,17 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
66
157
|
const enabled_vt_names = all_vt_names.filter(
|
|
67
158
|
(vtnm) =>
|
|
68
159
|
vts[vtnm].enable_copilot_viewgen ||
|
|
69
|
-
vts[vtnm].copilot_generate_view_prompt
|
|
160
|
+
vts[vtnm].copilot_generate_view_prompt
|
|
70
161
|
);
|
|
71
162
|
if (!enabled_vt_names.includes("Show")) enabled_vt_names.push("Show");
|
|
163
|
+
if (!enabled_vt_names.includes("Edit")) enabled_vt_names.push("Edit");
|
|
164
|
+
if (!enabled_vt_names.includes("List")) enabled_vt_names.push("List");
|
|
165
|
+
if (!enabled_vt_names.includes("Filter")) enabled_vt_names.push("Filter");
|
|
72
166
|
//const roles = await User.get_roles();
|
|
73
167
|
const tableless = enabled_vt_names.filter(
|
|
74
|
-
(vtnm) => vts[vtnm].tableless === true
|
|
168
|
+
(vtnm) => vts[vtnm].tableless === true
|
|
75
169
|
);
|
|
170
|
+
const roles = state.roles;
|
|
76
171
|
const parameters = {
|
|
77
172
|
type: "object",
|
|
78
173
|
required: ["name", "viewpattern"],
|
|
@@ -82,7 +177,9 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
82
177
|
type: "string",
|
|
83
178
|
},
|
|
84
179
|
viewpattern: {
|
|
85
|
-
description: `The type of view to generate. Some of the view descriptions: ${enabled_vt_names
|
|
180
|
+
description: `The type of view to generate. Some of the view descriptions: ${enabled_vt_names
|
|
181
|
+
.map((vtnm) => `${vtnm}: ${vts[vtnm].description}.`)
|
|
182
|
+
.join(" ")}`,
|
|
86
183
|
type: "string",
|
|
87
184
|
enum: enabled_vt_names,
|
|
88
185
|
},
|
|
@@ -97,7 +194,7 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
97
194
|
description:
|
|
98
195
|
"The minimum role needed to access the view. For views accessible only by admin, use 'admin', pages with min_role 'public' is publicly accessible and also available to all users",
|
|
99
196
|
type: "string",
|
|
100
|
-
enum: ["admin", "user", "public"],
|
|
197
|
+
enum: roles ? roles.map((r) => r.role) : ["admin", "user", "public"],
|
|
101
198
|
},
|
|
102
199
|
},
|
|
103
200
|
};
|
|
@@ -122,25 +219,87 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
122
219
|
: Table.findOne({ name: tool_call.input.table });
|
|
123
220
|
|
|
124
221
|
const wfctx = { viewname: tool_call.input.name, table_id: table?.id };
|
|
125
|
-
|
|
222
|
+
const viewpattern = tool_call.input.viewpattern;
|
|
223
|
+
const builderModeByPattern = {
|
|
224
|
+
Show: "show",
|
|
225
|
+
Edit: "edit",
|
|
226
|
+
List: "listcolumns",
|
|
227
|
+
Filter: "filter",
|
|
228
|
+
};
|
|
229
|
+
const builderMode = builderModeByPattern[viewpattern];
|
|
230
|
+
if (builderMode) {
|
|
231
|
+
const extractText = (c) => {
|
|
232
|
+
if (typeof c === "string") return c;
|
|
233
|
+
if (Array.isArray(c)) {
|
|
234
|
+
const textPart = c.find(
|
|
235
|
+
(p) => p?.type === "text" || typeof p === "string"
|
|
236
|
+
);
|
|
237
|
+
return (
|
|
238
|
+
textPart?.text || (typeof textPart === "string" ? textPart : "")
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return "";
|
|
242
|
+
};
|
|
243
|
+
const isToolResultMessage = (item) => {
|
|
244
|
+
if (!Array.isArray(item?.content)) return false;
|
|
245
|
+
return item.content.every((p) => p?.type === "tool_result");
|
|
246
|
+
};
|
|
126
247
|
const promptFromChat = Array.isArray(chat)
|
|
127
|
-
?
|
|
128
|
-
.
|
|
129
|
-
|
|
248
|
+
? (() => {
|
|
249
|
+
const userMsgs = chat.filter(
|
|
250
|
+
(item) =>
|
|
251
|
+
item?.role === "user" &&
|
|
252
|
+
item?.content &&
|
|
253
|
+
!isToolResultMessage(item)
|
|
254
|
+
);
|
|
255
|
+
return userMsgs.length ? extractText(userMsgs[0].content) : "";
|
|
256
|
+
})()
|
|
130
257
|
: "";
|
|
131
258
|
const layoutPrompt = promptFromChat || tool_call.input.name || "";
|
|
132
259
|
wfctx.layout = await builderGen.run(
|
|
133
260
|
layoutPrompt,
|
|
134
|
-
|
|
261
|
+
builderMode,
|
|
135
262
|
table?.name,
|
|
136
|
-
|
|
263
|
+
null,
|
|
264
|
+
chat
|
|
137
265
|
);
|
|
138
|
-
if (table) {
|
|
266
|
+
if (table && viewpattern !== "Filter") {
|
|
139
267
|
const baseCfg = await initial_config_all_fields(false)({
|
|
140
268
|
table_id: table.id,
|
|
141
269
|
});
|
|
142
270
|
if (baseCfg?.columns) wfctx.columns = baseCfg.columns;
|
|
143
271
|
}
|
|
272
|
+
if (viewpattern === "Edit" && table) {
|
|
273
|
+
const layoutFieldNames = collectLayoutFieldNames(wfctx.layout);
|
|
274
|
+
const fields = table.fields || [];
|
|
275
|
+
const fixed = {};
|
|
276
|
+
for (const f of fields) {
|
|
277
|
+
if (f.primary_key || f.calculated) continue;
|
|
278
|
+
if (layoutFieldNames.has(f.name)) continue;
|
|
279
|
+
if (f.type === "Key" && f.reftable_name === "users") {
|
|
280
|
+
fixed[`preset_${f.name}`] = "LoggedIn";
|
|
281
|
+
fixed[`_block_${f.name}`] = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (Object.keys(fixed).length > 0) wfctx.fixed = fixed;
|
|
285
|
+
wfctx.destination_type = "Back to referer";
|
|
286
|
+
}
|
|
287
|
+
if (viewpattern === "Filter") {
|
|
288
|
+
const filterFieldSegment = findFilterFieldSegment(wfctx.layout);
|
|
289
|
+
if (filterFieldSegment) {
|
|
290
|
+
const normalized = normalizeFilterField(filterFieldSegment);
|
|
291
|
+
wfctx.layout = normalized;
|
|
292
|
+
wfctx.columns = [toFilterColumn(normalized)];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (
|
|
298
|
+
viewpattern === "Show" ||
|
|
299
|
+
viewpattern === "Edit" ||
|
|
300
|
+
viewpattern === "Filter"
|
|
301
|
+
) {
|
|
302
|
+
// No extra configuration steps for these modes.
|
|
144
303
|
} else {
|
|
145
304
|
const flow = vt.configuration_workflow(req);
|
|
146
305
|
let vt_prompt = "";
|
|
@@ -149,26 +308,64 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
149
308
|
vt_prompt = vt.copilot_generate_view_prompt;
|
|
150
309
|
else if (typeof vt.copilot_generate_view_prompt === "function")
|
|
151
310
|
vt_prompt = await vt.copilot_generate_view_prompt(
|
|
152
|
-
tool_call.input
|
|
311
|
+
tool_call.input
|
|
153
312
|
);
|
|
154
313
|
}
|
|
155
314
|
|
|
315
|
+
const prefilledFields = new Set();
|
|
316
|
+
if (wfctx.layout !== undefined) prefilledFields.add("layout");
|
|
317
|
+
if (wfctx.columns !== undefined) prefilledFields.add("columns");
|
|
318
|
+
|
|
319
|
+
// For List views: pre-fill view_to_create with the best Edit view for the table
|
|
320
|
+
if (viewpattern === "List" && table) {
|
|
321
|
+
const candidateViews = await View.find_table_views_where(
|
|
322
|
+
table.id,
|
|
323
|
+
({ state_fields, viewrow }) =>
|
|
324
|
+
viewrow.name !== tool_call.input.name &&
|
|
325
|
+
state_fields.every((sf) => !sf.required)
|
|
326
|
+
);
|
|
327
|
+
if (candidateViews.length > 0) {
|
|
328
|
+
const editView =
|
|
329
|
+
candidateViews.find((v) =>
|
|
330
|
+
v.name.toLowerCase().includes("edit")
|
|
331
|
+
) || candidateViews[0];
|
|
332
|
+
wfctx.view_to_create =
|
|
333
|
+
editView.select_option?.name || editView.name;
|
|
334
|
+
wfctx.create_view_display = "Popup";
|
|
335
|
+
wfctx.create_view_location = "Top right";
|
|
336
|
+
prefilledFields.add("view_to_create");
|
|
337
|
+
prefilledFields.add("create_view_display");
|
|
338
|
+
prefilledFields.add("create_view_location");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
156
342
|
for (const step of flow.steps) {
|
|
343
|
+
if (typeof step.form !== "function") continue;
|
|
157
344
|
const form = await step.form(wfctx);
|
|
158
345
|
const properties = {};
|
|
159
346
|
//TODO onlyWhen
|
|
160
347
|
for (const field of form.fields) {
|
|
348
|
+
if (prefilledFields.has(field.name)) continue;
|
|
161
349
|
//TODO showIf
|
|
162
350
|
properties[field.name] = {
|
|
163
351
|
description:
|
|
164
352
|
field.copilot_description ||
|
|
165
|
-
`${field.label}.${
|
|
353
|
+
`${field.label}.${
|
|
354
|
+
field.sublabel ? ` ${field.sublabel}` : ""
|
|
355
|
+
}`,
|
|
166
356
|
...fieldProperties(field),
|
|
167
357
|
};
|
|
358
|
+
if (!properties[field.name].type) {
|
|
359
|
+
properties[field.name].type = "string";
|
|
360
|
+
}
|
|
168
361
|
}
|
|
169
362
|
|
|
363
|
+
if (!Object.keys(properties).length) continue;
|
|
364
|
+
|
|
170
365
|
const answer = await generate(
|
|
171
|
-
`${vt_prompt ? vt_prompt + "\n\n" : ""}Now generate the ${
|
|
366
|
+
`${vt_prompt ? vt_prompt + "\n\n" : ""}Now generate the ${
|
|
367
|
+
step.name
|
|
368
|
+
} details of the view by calling the generate_view_details tool`,
|
|
172
369
|
{
|
|
173
370
|
tools: [
|
|
174
371
|
{
|
|
@@ -189,22 +386,43 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
189
386
|
name: "generate_view_details",
|
|
190
387
|
},
|
|
191
388
|
},
|
|
192
|
-
}
|
|
389
|
+
}
|
|
193
390
|
);
|
|
194
391
|
const tc = answer.getToolCalls()[0];
|
|
392
|
+
await getState().functions.llm_add_message.run(
|
|
393
|
+
"tool_response",
|
|
394
|
+
{ type: "text", value: "Details provided" },
|
|
395
|
+
{ chat, tool_call: tc }
|
|
396
|
+
);
|
|
195
397
|
Object.assign(wfctx, tc.input);
|
|
196
398
|
}
|
|
197
399
|
}
|
|
400
|
+
const roleName = tool_call.input.min_role || "public";
|
|
401
|
+
const rolesState = getState().roles;
|
|
402
|
+
const min_role = rolesState
|
|
403
|
+
? (rolesState.find((r) => r.role === roleName) || { id: 100 }).id
|
|
404
|
+
: { admin: 1, public: 100, user: 80 }[roleName] ?? 100;
|
|
198
405
|
const view = new View({
|
|
199
406
|
name: tool_call.input.name,
|
|
200
407
|
viewtemplate: tool_call.input.viewpattern,
|
|
201
408
|
table,
|
|
202
409
|
table_id: table?.id,
|
|
203
|
-
min_role
|
|
204
|
-
tool_call.input.min_role || "public"
|
|
205
|
-
],
|
|
410
|
+
min_role,
|
|
206
411
|
configuration: wfctx,
|
|
207
412
|
});
|
|
413
|
+
if (this.yoloMode) {
|
|
414
|
+
await this.userActions.build_copilot_view_gen({
|
|
415
|
+
wfctx,
|
|
416
|
+
name: tool_call.input.name,
|
|
417
|
+
viewpattern: tool_call.input.viewpattern,
|
|
418
|
+
table: tool_call.input.table,
|
|
419
|
+
min_role: tool_call.input.min_role,
|
|
420
|
+
});
|
|
421
|
+
return {
|
|
422
|
+
stop: true,
|
|
423
|
+
add_response: `View ${tool_call.input.name} created.`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
208
426
|
const runres = await view.run({}, { req });
|
|
209
427
|
return {
|
|
210
428
|
stop: true,
|
|
@@ -212,7 +430,7 @@ a view generation mode. The tool call only requires high-level details to start
|
|
|
212
430
|
pre(JSON.stringify(wfctx, null, 2)) +
|
|
213
431
|
div(
|
|
214
432
|
{ style: { maxHeight: 800, maxWidth: 500, overflow: "scroll" } },
|
|
215
|
-
runres
|
|
433
|
+
runres
|
|
216
434
|
),
|
|
217
435
|
add_user_action: {
|
|
218
436
|
name: "build_copilot_view_gen",
|
package/agent-skills/workflow.js
CHANGED
|
@@ -550,11 +550,50 @@ class GenerateWorkflowSkill {
|
|
|
550
550
|
ensureActionCatalog();
|
|
551
551
|
}
|
|
552
552
|
|
|
553
|
+
static async configFields() {
|
|
554
|
+
return [
|
|
555
|
+
{
|
|
556
|
+
name: "context_vars",
|
|
557
|
+
label: "Initial context variables",
|
|
558
|
+
input_type: "code",
|
|
559
|
+
attributes: { mode: "application/json" },
|
|
560
|
+
sublabel:
|
|
561
|
+
'JSON object of key-value pairs pre-loaded into the workflow context at startup (e.g. {"ELEVENLABS_API_KEY": "sk-..."}). Keys are available by name in every run_js_code step.',
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
_parseContextVars() {
|
|
567
|
+
if (!this.context_vars) return null;
|
|
568
|
+
try {
|
|
569
|
+
const vars =
|
|
570
|
+
typeof this.context_vars === "string"
|
|
571
|
+
? JSON.parse(this.context_vars)
|
|
572
|
+
: this.context_vars;
|
|
573
|
+
return Object.keys(vars).length ? vars : null;
|
|
574
|
+
} catch {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
553
579
|
async systemPrompt() {
|
|
554
|
-
|
|
580
|
+
const base = await GenerateWorkflow.system_prompt();
|
|
581
|
+
const vars = this._parseContextVars();
|
|
582
|
+
if (!vars) return base;
|
|
583
|
+
const keyList = Object.keys(vars).join(", ");
|
|
584
|
+
return (
|
|
585
|
+
base +
|
|
586
|
+
`\n\nThe following values are pre-loaded into the workflow context before the first step runs: ${keyList}. ` +
|
|
587
|
+
`Use them directly by name in run_js_code steps (e.g. \`${
|
|
588
|
+
Object.keys(vars)[0]
|
|
589
|
+
}\`) ` +
|
|
590
|
+
`or via the context object (e.g. \`context.${Object.keys(vars)[0]}\`). ` +
|
|
591
|
+
`Do not ask the user to supply these values — they are already available.`
|
|
592
|
+
);
|
|
555
593
|
}
|
|
556
594
|
|
|
557
595
|
get userActions() {
|
|
596
|
+
const context_vars = this._parseContextVars();
|
|
558
597
|
return {
|
|
559
598
|
async apply_copilot_workflow({ user, ...raw }) {
|
|
560
599
|
const payload = ensureWorkflowHasSteps(normalizeWorkflowPayload(raw));
|
|
@@ -563,7 +602,11 @@ class GenerateWorkflowSkill {
|
|
|
563
602
|
return {
|
|
564
603
|
notify: `Cannot create workflow: ${analysis.blocking.join("; ")}`,
|
|
565
604
|
};
|
|
566
|
-
const result = await GenerateWorkflow.execute(
|
|
605
|
+
const result = await GenerateWorkflow.execute(
|
|
606
|
+
payload,
|
|
607
|
+
{ user },
|
|
608
|
+
context_vars
|
|
609
|
+
);
|
|
567
610
|
return {
|
|
568
611
|
notify:
|
|
569
612
|
result?.postExec ||
|
|
@@ -608,6 +651,13 @@ class GenerateWorkflowSkill {
|
|
|
608
651
|
const canCreate =
|
|
609
652
|
analysis.blocking.length === 0 &&
|
|
610
653
|
preparedPayload.workflow_steps.length > 0;
|
|
654
|
+
if (this.yoloMode && canCreate) {
|
|
655
|
+
await this.userActions.apply_copilot_workflow(preparedPayload);
|
|
656
|
+
return {
|
|
657
|
+
stop: true,
|
|
658
|
+
add_response: `Workflow ${preparedPayload.workflow_name || "(unnamed)"} created.`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
611
661
|
return {
|
|
612
662
|
stop: true,
|
|
613
663
|
add_response: `${issuesHtml}${previewHtml}`,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const Field = require("@saltcorn/data/models/field");
|
|
2
|
+
const Table = require("@saltcorn/data/models/table");
|
|
3
|
+
const Form = require("@saltcorn/data/models/form");
|
|
4
|
+
const MetaData = require("@saltcorn/data/models/metadata");
|
|
5
|
+
const View = require("@saltcorn/data/models/view");
|
|
6
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
7
|
+
const { findType } = require("@saltcorn/data/models/discovery");
|
|
8
|
+
const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
9
|
+
const db = require("@saltcorn/data/db");
|
|
10
|
+
const WorkflowRun = require("@saltcorn/data/models/workflow_run");
|
|
11
|
+
const {
|
|
12
|
+
localeDateTime,
|
|
13
|
+
renderForm,
|
|
14
|
+
mkTable,
|
|
15
|
+
post_delete_btn,
|
|
16
|
+
} = require("@saltcorn/markup");
|
|
17
|
+
const {
|
|
18
|
+
div,
|
|
19
|
+
script,
|
|
20
|
+
domReady,
|
|
21
|
+
pre,
|
|
22
|
+
code,
|
|
23
|
+
input,
|
|
24
|
+
h4,
|
|
25
|
+
style,
|
|
26
|
+
h5,
|
|
27
|
+
button,
|
|
28
|
+
text_attr,
|
|
29
|
+
i,
|
|
30
|
+
p,
|
|
31
|
+
span,
|
|
32
|
+
small,
|
|
33
|
+
form,
|
|
34
|
+
textarea,
|
|
35
|
+
} = require("@saltcorn/markup/tags");
|
|
36
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
37
|
+
const renderLayout = require("@saltcorn/markup/layout");
|
|
38
|
+
const { viewname } = require("./common");
|
|
39
|
+
|
|
40
|
+
const errorList = async (req) => {
|
|
41
|
+
const errs = await MetaData.find({
|
|
42
|
+
type: "CopilotConstructMgr",
|
|
43
|
+
name: "error",
|
|
44
|
+
});
|
|
45
|
+
if (errs.length) {
|
|
46
|
+
return div(
|
|
47
|
+
{ class: "mt-2" },
|
|
48
|
+
mkTable(
|
|
49
|
+
[
|
|
50
|
+
{ label: "Status", key: (m) => m.body.status },
|
|
51
|
+
{
|
|
52
|
+
label: "Error",
|
|
53
|
+
key: (m) => pre(JSON.stringify(m.body.error, null, 2)),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: "Delete",
|
|
57
|
+
key: (r) =>
|
|
58
|
+
button(
|
|
59
|
+
{
|
|
60
|
+
class: "btn btn-outline-danger btn-sm",
|
|
61
|
+
onclick: `view_post("${viewname}", "del_err", {id:${r.id}})`,
|
|
62
|
+
},
|
|
63
|
+
i({ class: "fas fa-trash-alt" }),
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
errs,
|
|
68
|
+
),
|
|
69
|
+
button(
|
|
70
|
+
{
|
|
71
|
+
class: "btn btn-outline-danger",
|
|
72
|
+
onclick: `view_post("${viewname}", "del_all_errs")`,
|
|
73
|
+
},
|
|
74
|
+
"Delete all",
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
return div({ class: "mt-2" }, p("No errors"));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const del_err = async (table_id, viewname, config, body, { req, res }) => {
|
|
83
|
+
const r = await MetaData.findOne({
|
|
84
|
+
id: body.id,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!r) throw new Error("Error not found");
|
|
88
|
+
await r.delete();
|
|
89
|
+
return { json: { reload_page: true } };
|
|
90
|
+
};
|
|
91
|
+
const del_all_errs = async (table_id, viewname, config, body, { req, res }) => {
|
|
92
|
+
const rs = await MetaData.find({
|
|
93
|
+
type: "CopilotConstructMgr",
|
|
94
|
+
name: "error",
|
|
95
|
+
});
|
|
96
|
+
for (const r of rs) await r.delete();
|
|
97
|
+
return { json: { reload_page: true } };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const error_routes = { del_err, del_all_errs };
|
|
101
|
+
|
|
102
|
+
module.exports = { errorList, error_routes };
|