@saltcorn/copilot 0.2.0 → 0.3.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/action-builder.js +5 -1
- package/actions/generate-js-action.js +108 -0
- package/actions/generate-tables.js +259 -0
- package/actions/generate-workflow.js +320 -0
- package/chat-copilot.js +471 -0
- package/common.js +47 -1
- package/index.js +3 -1
- package/package.json +2 -2
- package/prompts/action-builder.txt +1 -5
- package/workflow-gen.js +4 -258
- package/GenWorkflowCodePage.js +0 -333
package/action-builder.js
CHANGED
|
@@ -128,10 +128,14 @@ const runPost = async (
|
|
|
128
128
|
form.hasErrors = false;
|
|
129
129
|
form.errors = {};
|
|
130
130
|
|
|
131
|
-
const
|
|
131
|
+
const partPrompt = await getPromptFromTemplate(
|
|
132
132
|
"action-builder.txt",
|
|
133
133
|
form.values.prompt
|
|
134
134
|
);
|
|
135
|
+
const fullPrompt =
|
|
136
|
+
partPrompt +
|
|
137
|
+
"\n\nWrite the JavaScript code to implement the following functionality:\n\n" +
|
|
138
|
+
form.values.prompt;
|
|
135
139
|
|
|
136
140
|
const completion = await getCompletion("JavaScript", fullPrompt);
|
|
137
141
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const WorkflowStep = require("@saltcorn/data/models/workflow_step");
|
|
3
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
4
|
+
const Table = require("@saltcorn/data/models/table");
|
|
5
|
+
const Field = require("@saltcorn/data/models/field");
|
|
6
|
+
const { apply, removeAllWhiteSpace } = require("@saltcorn/data/utils");
|
|
7
|
+
const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
|
|
8
|
+
const { a, pre, script, div, code } = require("@saltcorn/markup/tags");
|
|
9
|
+
const { fieldProperties, getPromptFromTemplate } = require("../common");
|
|
10
|
+
|
|
11
|
+
class GenerateJsAction {
|
|
12
|
+
static title = "Generate JavaScript Action";
|
|
13
|
+
static function_name = "generate_js_action";
|
|
14
|
+
static description = "Generate Javascript Action";
|
|
15
|
+
|
|
16
|
+
static async json_schema() {
|
|
17
|
+
return {
|
|
18
|
+
type: "object",
|
|
19
|
+
required: ["action_javascript_code", "action_name"],
|
|
20
|
+
properties: {
|
|
21
|
+
action_javascript_code: {
|
|
22
|
+
description: "JavaScript code that constitutes the action",
|
|
23
|
+
type: "string",
|
|
24
|
+
},
|
|
25
|
+
action_name: {
|
|
26
|
+
description:
|
|
27
|
+
"A human-readable label for the action. Can include spaces and mixed case, should be 1-5 words.",
|
|
28
|
+
type: "string",
|
|
29
|
+
},
|
|
30
|
+
action_description: {
|
|
31
|
+
description: "A description of the purpose of the action.",
|
|
32
|
+
type: "string",
|
|
33
|
+
},
|
|
34
|
+
when_trigger: {
|
|
35
|
+
description:
|
|
36
|
+
"When the action should trigger. Optional, leave blank if unspecified or workflow will be run on button click",
|
|
37
|
+
type: "string",
|
|
38
|
+
enum: ["Insert", "Delete", "Update", "Daily", "Hourly", "Weekly"],
|
|
39
|
+
},
|
|
40
|
+
trigger_table: {
|
|
41
|
+
description:
|
|
42
|
+
"If the action trigger is Insert, Delete or Update, the name of the table that triggers the workflow",
|
|
43
|
+
type: "string",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
static async system_prompt() {
|
|
49
|
+
const partPrompt = await getPromptFromTemplate("action-builder.txt", "");
|
|
50
|
+
return (
|
|
51
|
+
`Use the generate_js_action to generate actions based on JavaScript code. ` +
|
|
52
|
+
partPrompt
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
static render_html({
|
|
56
|
+
action_javascript_code,
|
|
57
|
+
action_name,
|
|
58
|
+
action_description,
|
|
59
|
+
when_trigger,
|
|
60
|
+
trigger_table,
|
|
61
|
+
}) {
|
|
62
|
+
return (
|
|
63
|
+
div({class: "mb-3"},
|
|
64
|
+
`${action_name}${when_trigger ? `: ${when_trigger}` : ""}${
|
|
65
|
+
trigger_table ? ` on ${trigger_table}` : ""
|
|
66
|
+
}`
|
|
67
|
+
) + pre(code(action_javascript_code))
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
static async execute(
|
|
71
|
+
{
|
|
72
|
+
action_javascript_code,
|
|
73
|
+
action_name,
|
|
74
|
+
action_description,
|
|
75
|
+
when_trigger,
|
|
76
|
+
trigger_table,
|
|
77
|
+
},
|
|
78
|
+
req
|
|
79
|
+
) {
|
|
80
|
+
let table_id;
|
|
81
|
+
if (trigger_table) {
|
|
82
|
+
const table = Table.findOne({ name: trigger_table });
|
|
83
|
+
if (!table) return { postExec: `Table not found: ${trigger_table}` };
|
|
84
|
+
table_id = table.id;
|
|
85
|
+
}
|
|
86
|
+
const trigger = await Trigger.create({
|
|
87
|
+
name: action_name,
|
|
88
|
+
when_trigger: when_trigger || "Never",
|
|
89
|
+
table_id,
|
|
90
|
+
action: "run_js_code",
|
|
91
|
+
configuration: {code: action_javascript_code},
|
|
92
|
+
});
|
|
93
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req?.user, {
|
|
94
|
+
entity_type: "Trigger",
|
|
95
|
+
entity_name: trigger.name,
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
postExec:
|
|
99
|
+
"Action created. " +
|
|
100
|
+
a(
|
|
101
|
+
{ target: "_blank", href: `/actions/configure/${trigger.id}` },
|
|
102
|
+
"Configure action."
|
|
103
|
+
),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = GenerateJsAction;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const WorkflowStep = require("@saltcorn/data/models/workflow_step");
|
|
3
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
4
|
+
const Table = require("@saltcorn/data/models/table");
|
|
5
|
+
const Field = require("@saltcorn/data/models/field");
|
|
6
|
+
const { apply, removeAllWhiteSpace } = require("@saltcorn/data/utils");
|
|
7
|
+
const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
|
|
8
|
+
const { a, pre, script, div } = require("@saltcorn/markup/tags");
|
|
9
|
+
const { fieldProperties } = require("../common");
|
|
10
|
+
|
|
11
|
+
class GenerateTables {
|
|
12
|
+
static title = "Generate Tables";
|
|
13
|
+
static function_name = "generate_tables";
|
|
14
|
+
static description = "Generate database tables";
|
|
15
|
+
|
|
16
|
+
static async json_schema() {
|
|
17
|
+
const types = Object.values(getState().types);
|
|
18
|
+
const fieldTypeCfg = types.map((ty) => {
|
|
19
|
+
const properties = {
|
|
20
|
+
data_type: { const: ty.name },
|
|
21
|
+
};
|
|
22
|
+
const attrs = apply(ty.attributes, {}) || [];
|
|
23
|
+
attrs.forEach((a) => {
|
|
24
|
+
properties[a.name] = {
|
|
25
|
+
description:
|
|
26
|
+
a.copilot_description ||
|
|
27
|
+
`${a.label}.${a.sublabel ? ` ${a.sublabel}` : ""}`,
|
|
28
|
+
...fieldProperties(a),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
type: "object",
|
|
33
|
+
description: ty.copilot_description || ty.description,
|
|
34
|
+
properties,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
fieldTypeCfg.push({
|
|
38
|
+
type: "object",
|
|
39
|
+
description:
|
|
40
|
+
"A foreign key to a different table. This will reference the primary key on another table.",
|
|
41
|
+
properties: {
|
|
42
|
+
data_type: { const: "ForeignKey" },
|
|
43
|
+
reference_table: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Name of the table being referenced",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
fieldTypeCfg.push({
|
|
50
|
+
type: "object",
|
|
51
|
+
description:
|
|
52
|
+
"A reference (file path) to a file on disk. This can be used for example to hold images or documents",
|
|
53
|
+
properties: {
|
|
54
|
+
data_type: { const: "File" },
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
type: "object",
|
|
59
|
+
required: ["tables"],
|
|
60
|
+
properties: {
|
|
61
|
+
tables: {
|
|
62
|
+
type: "array",
|
|
63
|
+
items: {
|
|
64
|
+
type: "object",
|
|
65
|
+
required: ["table_name", "fields"],
|
|
66
|
+
properties: {
|
|
67
|
+
table_name: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "The name of the table",
|
|
70
|
+
},
|
|
71
|
+
fields: {
|
|
72
|
+
type: "array",
|
|
73
|
+
items: {
|
|
74
|
+
type: "object",
|
|
75
|
+
required: [
|
|
76
|
+
"name",
|
|
77
|
+
"label",
|
|
78
|
+
"type_and_configuration",
|
|
79
|
+
"importance",
|
|
80
|
+
],
|
|
81
|
+
properties: {
|
|
82
|
+
name: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description:
|
|
85
|
+
"The field name. Must be a valid identifier in both SQL and JavaScript, all lower case, snake_case (underscore instead of spaces)",
|
|
86
|
+
},
|
|
87
|
+
label: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description:
|
|
90
|
+
"A human-readable label for the field. Should be short, 1-4 words, can have spaces and mixed case.",
|
|
91
|
+
},
|
|
92
|
+
not_null: {
|
|
93
|
+
type: "boolean",
|
|
94
|
+
description:
|
|
95
|
+
"A value is required and the field will be NOT NULL in the database",
|
|
96
|
+
},
|
|
97
|
+
unique: {
|
|
98
|
+
type: "boolean",
|
|
99
|
+
description:
|
|
100
|
+
"The value is unique - different rows must have different values for this field",
|
|
101
|
+
},
|
|
102
|
+
type_and_configuration: { anyOf: fieldTypeCfg },
|
|
103
|
+
importance: {
|
|
104
|
+
type: "number",
|
|
105
|
+
description:
|
|
106
|
+
"How important is this field if only some fields can be displayed to the user. From 1 (least important) to 10 (most important).",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static async system_prompt() {
|
|
119
|
+
const tableLines = [];
|
|
120
|
+
const tables = await Table.find({});
|
|
121
|
+
tables.forEach((table) => {
|
|
122
|
+
const fieldLines = table.fields.map(
|
|
123
|
+
(f) =>
|
|
124
|
+
` * ${f.name} with type: ${f.pretty_type.replace(
|
|
125
|
+
"Key to",
|
|
126
|
+
"ForeignKey referencing"
|
|
127
|
+
)}.${f.description ? ` ${f.description}` : ""}`
|
|
128
|
+
);
|
|
129
|
+
tableLines.push(
|
|
130
|
+
`${table.name}${
|
|
131
|
+
table.description ? `: ${table.description}.` : "."
|
|
132
|
+
} Contains the following fields:\n${fieldLines.join("\n")}`
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
return `Use the generate_tables tool to construct one or more database tables.
|
|
136
|
+
|
|
137
|
+
Do not call this tool more than once. It should only be called once. If you are
|
|
138
|
+
building more than one table, use one call to the generate_tables tool to build all the
|
|
139
|
+
tables.
|
|
140
|
+
|
|
141
|
+
The argument to generate_tables is an array of tables, each with an array of fields. You do not
|
|
142
|
+
need to specify a primary key, a primary key called id with autoincrementing integers is
|
|
143
|
+
autmatically generated.
|
|
144
|
+
|
|
145
|
+
The database already contains the following tables:
|
|
146
|
+
|
|
147
|
+
${tableLines.join("\n\n")}
|
|
148
|
+
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
static render_html({ tables }) {
|
|
152
|
+
const sctables = this.process_tables(tables);
|
|
153
|
+
const mmdia = buildMermaidMarkup(sctables);
|
|
154
|
+
return (
|
|
155
|
+
pre({ class: "mermaid" }, mmdia) +
|
|
156
|
+
script(`mermaid.run({querySelector: 'pre.mermaid'});`)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static async execute({ tables }, req) {
|
|
161
|
+
const sctables = this.process_tables(tables);
|
|
162
|
+
for (const table of sctables) await Table.create(table.name);
|
|
163
|
+
for (const table of sctables) {
|
|
164
|
+
for (const field of table.fields) {
|
|
165
|
+
field.table = Table.findOne({ name: table.name });
|
|
166
|
+
await Field.create(field);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
Trigger.emitEvent("AppChange", `Tables created`, req?.user, {
|
|
170
|
+
entity_type: "Table",
|
|
171
|
+
entity_names: sctables.map((t) => t.name),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static process_tables(tables) {
|
|
176
|
+
return tables.map((table) => {
|
|
177
|
+
return new Table({
|
|
178
|
+
name: table.table_name,
|
|
179
|
+
fields: table.fields.map((f) => {
|
|
180
|
+
const { data_type, reference_table, ...attributes } =
|
|
181
|
+
f.type_and_configuration;
|
|
182
|
+
let type = data_type;
|
|
183
|
+
const scattributes = { ...attributes, importance: f.importance };
|
|
184
|
+
if (data_type === "ForeignKey") {
|
|
185
|
+
type = `Key to ${reference_table}`;
|
|
186
|
+
let refTableHere = tables.find(
|
|
187
|
+
(t) => t.table_name === reference_table
|
|
188
|
+
);
|
|
189
|
+
if (refTableHere) {
|
|
190
|
+
const strFields = refTableHere.fields.filter(
|
|
191
|
+
(f) => f.type_and_configuration.data_type === "String"
|
|
192
|
+
);
|
|
193
|
+
if (strFields.length) {
|
|
194
|
+
const maxImp = strFields.reduce(function (prev, current) {
|
|
195
|
+
return prev && prev.importance > current.importance
|
|
196
|
+
? prev
|
|
197
|
+
: current;
|
|
198
|
+
});
|
|
199
|
+
if (maxImp) scattributes.summary_field = maxImp.name;
|
|
200
|
+
}
|
|
201
|
+
} else if (reference_table === "users") {
|
|
202
|
+
scattributes.summary_field = "email";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
...f,
|
|
208
|
+
type,
|
|
209
|
+
required: f.not_null,
|
|
210
|
+
attributes: scattributes,
|
|
211
|
+
};
|
|
212
|
+
}),
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const EOL = "\n";
|
|
219
|
+
const indentString = (str, indent) => `${" ".repeat(indent)}${str}`;
|
|
220
|
+
|
|
221
|
+
const srcCardinality = (field) => (field.required ? "||" : "|o");
|
|
222
|
+
|
|
223
|
+
const buildTableMarkup = (table) => {
|
|
224
|
+
const fields = table.getFields();
|
|
225
|
+
const members = fields
|
|
226
|
+
// .filter((f) => !f.reftable_name)
|
|
227
|
+
.map((f) =>
|
|
228
|
+
indentString(`${removeAllWhiteSpace(f.type_name)} ${f.name}`, 6)
|
|
229
|
+
)
|
|
230
|
+
.join(EOL);
|
|
231
|
+
const keys = table
|
|
232
|
+
.getForeignKeys()
|
|
233
|
+
.map((f) =>
|
|
234
|
+
indentString(
|
|
235
|
+
`"${table.name}"${srcCardinality(f)}--|| "${f.reftable_name}" : "${
|
|
236
|
+
f.name
|
|
237
|
+
}"`,
|
|
238
|
+
2
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
.join(EOL);
|
|
242
|
+
return `${keys}
|
|
243
|
+
"${table.name}" {${EOL}${members}${EOL} }`;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const buildMermaidMarkup = (tables) => {
|
|
247
|
+
const lines = tables.map((table) => buildTableMarkup(table)).join(EOL);
|
|
248
|
+
return `${indentString("erDiagram", 2)}${EOL}${lines}`;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
module.exports = GenerateTables;
|
|
252
|
+
|
|
253
|
+
/* todo
|
|
254
|
+
|
|
255
|
+
- tag
|
|
256
|
+
- generate descriptions
|
|
257
|
+
- generate views
|
|
258
|
+
|
|
259
|
+
*/
|