Skip to content

Commit 041bbe3

Browse files
committed
initial form
1 parent e8eb395 commit 041bbe3

9 files changed

+3129
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Saltcorn copilot
2+
3+
AI assistant for building Saltcorn applications

action-builder.js

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 View = require("@saltcorn/data/models/view");
5+
const Trigger = require("@saltcorn/data/models/trigger");
6+
const db = require("@saltcorn/data/db");
7+
const Workflow = require("@saltcorn/data/models/workflow");
8+
const { renderForm } = require("@saltcorn/markup");
9+
const { div, script, domReady, pre, code } = require("@saltcorn/markup/tags");
10+
const { getState } = require("@saltcorn/data/db/state");
11+
12+
const get_state_fields = () => [];
13+
14+
const getForm = async ({ viewname, body }) => {
15+
const tables = await Table.find({});
16+
const table_triggers = ["Insert", "Update", "Delete", "Validate"];
17+
18+
const hasChannel = Object.entries(getState().eventTypes)
19+
.filter(([k, v]) => v.hasChannel)
20+
.map(([k, v]) => k);
21+
const fields = [
22+
{
23+
name: "name",
24+
label: "Name",
25+
type: "String",
26+
required: true,
27+
sublabel: "Name of action",
28+
},
29+
{
30+
name: "when_trigger",
31+
label: "When",
32+
input_type: "select",
33+
required: true,
34+
options: Trigger.when_options.map((t) => ({ value: t, label: t })),
35+
sublabel: "Event type which runs the trigger",
36+
help: { topic: "Event types" },
37+
attributes: {
38+
explainers: {
39+
Often: "Every 5 minutes",
40+
Never:
41+
"Not scheduled but can be run as an action from a button click",
42+
},
43+
},
44+
},
45+
{
46+
name: "table_id",
47+
label: "Table",
48+
input_type: "select",
49+
options: [...tables.map((t) => ({ value: t.id, label: t.name }))],
50+
showIf: { when_trigger: table_triggers },
51+
sublabel: "The table for which the trigger condition is checked.",
52+
},
53+
{
54+
name: "channel",
55+
label: "Channel",
56+
type: "String",
57+
sublabel: "Leave blank for all channels",
58+
showIf: { when_trigger: hasChannel },
59+
},
60+
{
61+
name: "prompt",
62+
label: "Action description",
63+
fieldview: "textarea",
64+
sublabel: "What would you like this action to do?",
65+
type: "String",
66+
},
67+
];
68+
const form = new Form({
69+
action: `/view/${viewname}`,
70+
fields,
71+
onChange: "$(this).submit()",
72+
noSubmitButton: true,
73+
additionalButtons: [
74+
{
75+
label: "Generate",
76+
onclick: "generate_action(this)",
77+
class: "btn btn-primary",
78+
},
79+
{
80+
label: "Save as action",
81+
onclick: "save_as_action(this)",
82+
class: "btn btn-primary",
83+
},
84+
],
85+
});
86+
return form;
87+
};
88+
89+
const run = async (table_id, viewname, cfg, state, { res, req }) => {
90+
const form = await getForm({ viewname });
91+
return renderForm(form, req.csrfToken());
92+
};
93+
94+
module.exports = (config) => ({
95+
name: "Action Builder Copilot",
96+
display_state_form: false,
97+
get_state_fields,
98+
tableless: true,
99+
singleton: true,
100+
run,
101+
});

index.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const Workflow = require("@saltcorn/data/models/workflow");
2+
const Form = require("@saltcorn/data/models/form");
3+
const { Configuration, OpenAIApi } = require("./openai/index");
4+
const fsp = require("fs").promises;
5+
6+
const configuration_workflow = () =>
7+
new Workflow({
8+
steps: [
9+
{
10+
name: "API key",
11+
form: async (context) => {
12+
return new Form({
13+
fields: [
14+
{
15+
name: "api_key",
16+
label: "API key",
17+
sublabel: "From your OpenAI account",
18+
type: "String",
19+
},
20+
],
21+
});
22+
},
23+
},
24+
],
25+
});
26+
27+
const functions = ({ api_key }) => ({
28+
gpt_generate: {
29+
run: async (prompt, options = {}) => {
30+
const configuration = new Configuration({
31+
apiKey: api_key,
32+
});
33+
const openai = new OpenAIApi(configuration);
34+
35+
const completion = await openai.createCompletion({
36+
model: "text-davinci-003",
37+
...options,
38+
prompt,
39+
});
40+
return completion;
41+
},
42+
isAsync: true,
43+
description: "Generate text with GPT",
44+
arguments: [{ name: "prompt", type: "String" }],
45+
},
46+
delete_tmp_images: {
47+
run: async (images) => {
48+
if (Array.isArray(images))
49+
for (const img of images) {
50+
await fsp.unlink(img.filePath);
51+
}
52+
else if (images.filePath) await fsp.unlink(images.filePath);
53+
},
54+
isAsync: true,
55+
description: "Delete generated temporary files",
56+
},
57+
});
58+
module.exports = {
59+
sc_plugin_api_version: 1,
60+
configuration_workflow,
61+
viewtemplates: (config) => [require("./action-builder")(config)],
62+
};

0 commit comments

Comments
 (0)