@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/builder-schema.js
CHANGED
|
@@ -41,6 +41,23 @@ const TOOLBOX_BY_MODE = {
|
|
|
41
41
|
],
|
|
42
42
|
list: [
|
|
43
43
|
"blank",
|
|
44
|
+
"list_columns",
|
|
45
|
+
"list_column",
|
|
46
|
+
"field",
|
|
47
|
+
"join_field",
|
|
48
|
+
"view_link",
|
|
49
|
+
"action",
|
|
50
|
+
"link",
|
|
51
|
+
"aggregation",
|
|
52
|
+
"view",
|
|
53
|
+
"container",
|
|
54
|
+
"dropdown_menu",
|
|
55
|
+
"line_break",
|
|
56
|
+
],
|
|
57
|
+
listcolumns: [
|
|
58
|
+
"blank",
|
|
59
|
+
"list_columns",
|
|
60
|
+
"list_column",
|
|
44
61
|
"field",
|
|
45
62
|
"join_field",
|
|
46
63
|
"view_link",
|
|
@@ -131,7 +148,6 @@ const buildSegmentDef = ({
|
|
|
131
148
|
});
|
|
132
149
|
|
|
133
150
|
const buildBuilderSchema = ({ mode, ctx }) => {
|
|
134
|
-
console.log({ ctx });
|
|
135
151
|
const normalizedMode = (mode || ctx?.mode || "show").toLowerCase();
|
|
136
152
|
const fields = ctx?.fields || [];
|
|
137
153
|
const actions = ctx?.actions || [];
|
|
@@ -141,9 +157,13 @@ const buildBuilderSchema = ({ mode, ctx }) => {
|
|
|
141
157
|
Array.isArray(rawIcons) ? rawIcons : Object.keys(rawIcons)
|
|
142
158
|
).slice(0, 15);
|
|
143
159
|
|
|
144
|
-
const
|
|
160
|
+
const isEditMode = normalizedMode === "edit" || normalizedMode === "filter";
|
|
161
|
+
const visibleFields = isEditMode
|
|
162
|
+
? fields.filter((f) => !f.calculated)
|
|
163
|
+
: fields;
|
|
164
|
+
const fieldNames = visibleFields.map((f) => f.name).filter(Boolean);
|
|
145
165
|
const fieldviewOptions = Array.from(
|
|
146
|
-
new Set(
|
|
166
|
+
new Set(visibleFields.flatMap((f) => f.fieldviews || []).filter(Boolean))
|
|
147
167
|
);
|
|
148
168
|
|
|
149
169
|
const defs = {
|
|
@@ -240,11 +260,11 @@ const buildBuilderSchema = ({ mode, ctx }) => {
|
|
|
240
260
|
},
|
|
241
261
|
imageLocation: makeEnum(
|
|
242
262
|
IMAGE_LOCATIONS,
|
|
243
|
-
"Image location for card/container backgrounds."
|
|
263
|
+
"Image location for card/container backgrounds."
|
|
244
264
|
),
|
|
245
265
|
imageSize: makeEnum(
|
|
246
266
|
IMAGE_SIZES,
|
|
247
|
-
"Image sizing for Card or Body locations."
|
|
267
|
+
"Image sizing for Card or Body locations."
|
|
248
268
|
),
|
|
249
269
|
gradStartColor: {
|
|
250
270
|
type: "string",
|
|
@@ -633,7 +653,7 @@ const buildBuilderSchema = ({ mode, ctx }) => {
|
|
|
633
653
|
{ $ref: "#/$defs/join_field" },
|
|
634
654
|
{ $ref: "#/$defs/list_column" },
|
|
635
655
|
{ $ref: "#/$defs/list_columns" },
|
|
636
|
-
{ $ref: "#/$defs/page" }
|
|
656
|
+
{ $ref: "#/$defs/page" }
|
|
637
657
|
);
|
|
638
658
|
|
|
639
659
|
Object.assign(defs, {
|
package/common.js
CHANGED
|
@@ -14,6 +14,24 @@ const MarkdownIt = require("markdown-it"),
|
|
|
14
14
|
md = new MarkdownIt();
|
|
15
15
|
const HTMLParser = require("node-html-parser");
|
|
16
16
|
|
|
17
|
+
const getLlmConfigurationSafe = async () => {
|
|
18
|
+
const fn = getState().functions?.llm_get_configuration;
|
|
19
|
+
if (!fn?.run) return null;
|
|
20
|
+
try {
|
|
21
|
+
return await fn.run();
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const canUseResponseFormat = (llmConfig) => {
|
|
28
|
+
const backend = llmConfig?.backend;
|
|
29
|
+
if (!backend) return false;
|
|
30
|
+
if (backend === "AI SDK") return true;
|
|
31
|
+
if (backend === "OpenAI") return !!llmConfig?.responses_api;
|
|
32
|
+
return false;
|
|
33
|
+
};
|
|
34
|
+
|
|
17
35
|
const boxHandledStyles = new Set([
|
|
18
36
|
"margin",
|
|
19
37
|
"margin-top",
|
|
@@ -364,4 +382,6 @@ module.exports = {
|
|
|
364
382
|
splitContainerStyle,
|
|
365
383
|
walk_response,
|
|
366
384
|
parseHTML,
|
|
385
|
+
getLlmConfigurationSafe,
|
|
386
|
+
canUseResponseFormat,
|
|
367
387
|
};
|
package/copilot-as-agent.js
CHANGED
|
@@ -3,6 +3,7 @@ const Table = require("@saltcorn/data/models/table");
|
|
|
3
3
|
const Form = require("@saltcorn/data/models/form");
|
|
4
4
|
const View = require("@saltcorn/data/models/view");
|
|
5
5
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
6
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
6
7
|
const { findType } = require("@saltcorn/data/models/discovery");
|
|
7
8
|
const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
8
9
|
const db = require("@saltcorn/data/db");
|
|
@@ -46,6 +47,11 @@ const get_agent_view = () => {
|
|
|
46
47
|
{ skill_type: "Database design" },
|
|
47
48
|
{ skill_type: "Generate Workflow" },
|
|
48
49
|
{ skill_type: "Generate View" },
|
|
50
|
+
{ skill_type: "Registry editor" },
|
|
51
|
+
{ skill_type: "Javascript Action" },
|
|
52
|
+
...(typeof Plugin.loadAndSaveNewPlugin === "function"
|
|
53
|
+
? [{ skill_type: "Install Plugin" }]
|
|
54
|
+
: []),
|
|
49
55
|
],
|
|
50
56
|
},
|
|
51
57
|
});
|
|
@@ -64,7 +70,6 @@ const run = async (table_id, viewname, cfg, state, reqres) => {
|
|
|
64
70
|
};
|
|
65
71
|
|
|
66
72
|
const interact = async (table_id, viewname, config, body, reqres) => {
|
|
67
|
-
console.log("copilot interact with body", body);
|
|
68
73
|
const view = get_agent_view();
|
|
69
74
|
return await view.runRoute("interact", body, reqres.res, reqres);
|
|
70
75
|
};
|
package/index.js
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
2
2
|
const Form = require("@saltcorn/data/models/form");
|
|
3
3
|
const { features } = require("@saltcorn/data/db/state");
|
|
4
|
+
const db = require("@saltcorn/data/db");
|
|
5
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
6
|
+
const { viewname } = require("./app-constructor/common.js");
|
|
7
|
+
|
|
8
|
+
const headers = [
|
|
9
|
+
{
|
|
10
|
+
script: `/static_assets/${db.connectObj.version_tag}/mermaid.min.js`,
|
|
11
|
+
onlyViews: [viewname],
|
|
12
|
+
},
|
|
13
|
+
];
|
|
4
14
|
|
|
5
15
|
module.exports = {
|
|
6
16
|
sc_plugin_api_version: 1,
|
|
17
|
+
headers,
|
|
7
18
|
dependencies: ["@saltcorn/large-language-model", "@saltcorn/agents"],
|
|
8
19
|
viewtemplates: features.workflows
|
|
9
20
|
? [
|
|
10
21
|
require("./chat-copilot"),
|
|
11
22
|
require("./user-copilot"),
|
|
12
23
|
require("./copilot-as-agent"),
|
|
24
|
+
require("./app-constructor/view.js"),
|
|
13
25
|
]
|
|
14
26
|
: [require("./action-builder"), require("./database-designer")],
|
|
15
27
|
functions: features.workflows
|
|
@@ -17,15 +29,25 @@ module.exports = {
|
|
|
17
29
|
copilot_standard_prompt: require("./standard-prompt.js"),
|
|
18
30
|
copilot_generate_layout: require("./builder-gen.js"),
|
|
19
31
|
copilot_generate_workflow: require("./workflow-gen"),
|
|
32
|
+
copilot_generate_javascript: require("./js-code-gen.js"),
|
|
20
33
|
}
|
|
21
34
|
: {},
|
|
22
|
-
actions: {
|
|
35
|
+
actions: {
|
|
36
|
+
copilot_generate_page: require("./page-gen-action"),
|
|
37
|
+
app_constructor_feedback: require("./app-constructor/feedback-action.js"),
|
|
38
|
+
},
|
|
23
39
|
exchange: {
|
|
24
40
|
agent_skills: [
|
|
25
41
|
require("./agent-skills/pagegen.js"),
|
|
26
42
|
require("./agent-skills/database-design.js"),
|
|
27
43
|
require("./agent-skills/workflow.js"),
|
|
28
44
|
require("./agent-skills/viewgen.js"),
|
|
45
|
+
require("./agent-skills/registry-editor.js"),
|
|
46
|
+
require("./agent-skills/js-action.js"),
|
|
47
|
+
require("./agent-skills/app-constructor-context.js"),
|
|
48
|
+
...(typeof Plugin.loadAndSaveNewPlugin === "function"
|
|
49
|
+
? [require("./agent-skills/install-plugin.js")]
|
|
50
|
+
: []),
|
|
29
51
|
],
|
|
30
52
|
},
|
|
31
53
|
};
|
package/js-code-gen.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const { getPromptFromTemplate } = require("./common");
|
|
3
|
+
const Table = require("@saltcorn/data/models/table");
|
|
4
|
+
|
|
5
|
+
const stripCodeFences = (text) => {
|
|
6
|
+
let s = String(text || "").trim();
|
|
7
|
+
s = s.replace(/^```(?:javascript|js|ts|typescript)?\s*\n?/i, "");
|
|
8
|
+
s = s.replace(/\n?```\s*$/i, "");
|
|
9
|
+
return s.trim();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getTableContext = (table_name) => {
|
|
13
|
+
if (!table_name) return null;
|
|
14
|
+
const table = Table.findOne({ name: table_name });
|
|
15
|
+
if (!table) return null;
|
|
16
|
+
const fields = table.getFields ? table.getFields() : table.fields || [];
|
|
17
|
+
return { table, fieldNames: fields.map((f) => f.name) };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
run: async (description, existing_code, table_name) => {
|
|
22
|
+
const systemPrompt = await getPromptFromTemplate(
|
|
23
|
+
"action-builder.txt",
|
|
24
|
+
description
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const tableCtx = getTableContext(table_name);
|
|
28
|
+
let contextInfo;
|
|
29
|
+
if (tableCtx) {
|
|
30
|
+
contextInfo =
|
|
31
|
+
`\n\nThis action runs in the context of the "${table_name}" table. ` +
|
|
32
|
+
`Available variables: row (with fields: ${tableCtx.fieldNames.join(", ")}), ` +
|
|
33
|
+
`user, table, console, Actions, Table, File, User.`;
|
|
34
|
+
} else {
|
|
35
|
+
contextInfo =
|
|
36
|
+
"\n\nAvailable variables: user, console, Actions, Table, File, User.";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let prompt;
|
|
40
|
+
if (existing_code && existing_code.trim()) {
|
|
41
|
+
prompt =
|
|
42
|
+
`Modify the following JavaScript code based on this instruction: ${description}\n\n` +
|
|
43
|
+
`Existing code:\n${existing_code}` +
|
|
44
|
+
`${contextInfo}\n\n` +
|
|
45
|
+
`Return only the modified JavaScript code. Do not include any explanation or markdown code fences.`;
|
|
46
|
+
} else {
|
|
47
|
+
prompt =
|
|
48
|
+
`Generate JavaScript code for the following task: ${description}` +
|
|
49
|
+
`${contextInfo}\n\n` +
|
|
50
|
+
`Only return the JavaScript code. Do not include any explanation or markdown code fences.`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = await getState().functions.llm_generate.run(prompt, {
|
|
54
|
+
systemPrompt,
|
|
55
|
+
});
|
|
56
|
+
return stripCodeFences(result);
|
|
57
|
+
},
|
|
58
|
+
isAsync: true,
|
|
59
|
+
description: "Generate JavaScript code for an action",
|
|
60
|
+
arguments: [
|
|
61
|
+
{ name: "description", type: "String" },
|
|
62
|
+
{ name: "existing_code", type: "String" },
|
|
63
|
+
{ name: "table_name", type: "String" },
|
|
64
|
+
],
|
|
65
|
+
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const View = require("@saltcorn/data/models/view");
|
|
3
|
+
const Table = require("@saltcorn/data/models/table");
|
|
4
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
5
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
6
|
+
const WorkflowStep = require("@saltcorn/data/models/workflow_step");
|
|
7
|
+
const WorkflowRun = require("@saltcorn/data/models/workflow_run");
|
|
8
|
+
|
|
9
|
+
const { mockReqRes } = require("@saltcorn/data/tests/mocks");
|
|
10
|
+
const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals");
|
|
11
|
+
|
|
12
|
+
afterAll(require("@saltcorn/data/db").close);
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
await require("@saltcorn/data/db/reset_schema")();
|
|
15
|
+
await require("@saltcorn/data/db/fixtures")();
|
|
16
|
+
|
|
17
|
+
getState().registerPlugin("base", require("@saltcorn/data/base-plugin"));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
|
|
22
|
+
RUN WITH:
|
|
23
|
+
saltcorn dev:plugin-test -d ~/copilot -o ~/large-language-model/
|
|
24
|
+
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
jest.setTimeout(60000);
|
|
28
|
+
|
|
29
|
+
const configs = require("./configs.js");
|
|
30
|
+
|
|
31
|
+
for (const nameconfig of configs) {
|
|
32
|
+
const { name, ...config } = nameconfig;
|
|
33
|
+
describe("copilot_generate_layout with " + name, () => {
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
getState().registerPlugin(
|
|
36
|
+
"@saltcorn/large-language-model",
|
|
37
|
+
require("@saltcorn/large-language-model"),
|
|
38
|
+
config,
|
|
39
|
+
);
|
|
40
|
+
getState().registerPlugin("@saltcorn/copilot", require(".."));
|
|
41
|
+
});
|
|
42
|
+
for (const mode of ["page", "show", "edit", "filter"])
|
|
43
|
+
it("generates simple layout in mode " + mode, async () => {
|
|
44
|
+
const genres = await getState().functions.copilot_generate_layout.run(
|
|
45
|
+
"A container with a text element that says Hello World in H3",
|
|
46
|
+
mode,
|
|
47
|
+
mode === "page" ? null : "books",
|
|
48
|
+
);
|
|
49
|
+
const innerRes = genres.above ? genres.above[0] : genres;
|
|
50
|
+
expect(innerRes.type).toBe("container");
|
|
51
|
+
expect(innerRes.contents.type).toBe("blank");
|
|
52
|
+
expect(innerRes.contents.contents).toBe("Hello World");
|
|
53
|
+
expect(innerRes.contents.textStyle).toBe("h3");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|