@saltcorn/copilot 0.7.5 → 0.8.1
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-trigger.js +61 -0
- package/actions/generate-workflow.js +89 -37
- package/actions/install-plugin-action.js +103 -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/pagegen.js +19 -6
- package/agent-skills/registry-editor.js +911 -0
- package/agent-skills/triggergen.js +263 -0
- package/agent-skills/viewgen.js +431 -29
- 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 +120 -0
- package/app-constructor/requirements.js +156 -0
- package/app-constructor/run_task.js +146 -0
- package/app-constructor/schema.js +199 -0
- package/app-constructor/taskchart.js +70 -0
- package/app-constructor/tasks.js +585 -0
- package/app-constructor/tools.js +81 -0
- package/app-constructor/view.js +209 -0
- package/builder-gen.js +590 -68
- package/builder-schema.js +26 -6
- package/chat-copilot.js +1 -0
- package/common.js +20 -0
- package/copilot-as-agent.js +7 -1
- package/index.js +23 -1
- package/js-code-gen.js +65 -0
- package/package.json +1 -1
- package/relation-paths.js +236 -0
- package/tests/builder-gen.test.js +56 -0
|
@@ -0,0 +1,156 @@
|
|
|
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, tool_choice } = require("./common");
|
|
39
|
+
const { requirements_tool } = require("./tools");
|
|
40
|
+
|
|
41
|
+
const requirementsList = async (req) => {
|
|
42
|
+
const rs = await MetaData.find(
|
|
43
|
+
{
|
|
44
|
+
type: "CopilotConstructMgr",
|
|
45
|
+
name: "requirement",
|
|
46
|
+
},
|
|
47
|
+
{ orderBy: "written_at" },
|
|
48
|
+
);
|
|
49
|
+
const starFieldview = getState().types.Integer.fieldviews.show_star_rating;
|
|
50
|
+
|
|
51
|
+
if (rs.length) {
|
|
52
|
+
return div(
|
|
53
|
+
{ class: "mt-2" },
|
|
54
|
+
mkTable(
|
|
55
|
+
[
|
|
56
|
+
{ label: "Requirement", key: (m) => m.body.requirement },
|
|
57
|
+
{
|
|
58
|
+
label: "Priority",
|
|
59
|
+
key: (m) =>
|
|
60
|
+
starFieldview.run(m.body.priority, req, { min: 1, max: 5 }),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
label: "Delete",
|
|
64
|
+
key: (r) =>
|
|
65
|
+
button(
|
|
66
|
+
{
|
|
67
|
+
class: "btn btn-outline-danger btn-sm",
|
|
68
|
+
onclick: `view_post("${viewname}", "del_req", {id:${r.id}})`,
|
|
69
|
+
},
|
|
70
|
+
i({ class: "fas fa-trash-alt" }),
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
rs,
|
|
75
|
+
),
|
|
76
|
+
button(
|
|
77
|
+
{
|
|
78
|
+
class: "btn btn-outline-danger mb-4",
|
|
79
|
+
onclick: `view_post("${viewname}", "del_all_reqs")`,
|
|
80
|
+
},
|
|
81
|
+
"Delete all",
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
return div(
|
|
86
|
+
{ class: "mt-2" },
|
|
87
|
+
p("No requirements found"),
|
|
88
|
+
button(
|
|
89
|
+
{
|
|
90
|
+
class: "btn btn-primary",
|
|
91
|
+
onclick: `press_store_button(this);view_post("${viewname}", "gen_reqs")`,
|
|
92
|
+
},
|
|
93
|
+
"Generate requirements",
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const gen_reqs = async (table_id, viewname, config, body, { req, res }) => {
|
|
100
|
+
const spec = await MetaData.findOne({
|
|
101
|
+
type: "CopilotConstructMgr",
|
|
102
|
+
name: "spec",
|
|
103
|
+
});
|
|
104
|
+
if (!spec) throw new Error("Specification not found");
|
|
105
|
+
const answer = await getState().functions.llm_generate.run(
|
|
106
|
+
`Generate the requirements for this application:
|
|
107
|
+
|
|
108
|
+
Description: ${spec.body.description}
|
|
109
|
+
Audience: ${spec.body.audience}
|
|
110
|
+
Core features: ${spec.body.core_features}
|
|
111
|
+
Out of scope: ${spec.body.out_of_scope}
|
|
112
|
+
Visual style: ${spec.body.visual_style}
|
|
113
|
+
|
|
114
|
+
Now use the make_requirements tool to list the requirements for this software application
|
|
115
|
+
`,
|
|
116
|
+
{
|
|
117
|
+
tools: [requirements_tool],
|
|
118
|
+
...tool_choice("make_requirements"),
|
|
119
|
+
systemPrompt:
|
|
120
|
+
"You are a project manager. The user wants to build an application, and you must analyse their application description",
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const tc = answer.getToolCalls()[0];
|
|
125
|
+
|
|
126
|
+
for (const reqm of tc.input.requirements)
|
|
127
|
+
await MetaData.create({
|
|
128
|
+
type: "CopilotConstructMgr",
|
|
129
|
+
name: "requirement",
|
|
130
|
+
body: reqm,
|
|
131
|
+
user_id: req.user?.id,
|
|
132
|
+
});
|
|
133
|
+
return { json: { reload_page: true } };
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const del_req = async (table_id, viewname, config, body, { req, res }) => {
|
|
137
|
+
const r = await MetaData.findOne({
|
|
138
|
+
id: body.id,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!r) throw new Error("Requirement not found");
|
|
142
|
+
await r.delete();
|
|
143
|
+
return { json: { reload_page: true } };
|
|
144
|
+
};
|
|
145
|
+
const del_all_reqs = async (table_id, viewname, config, body, { req, res }) => {
|
|
146
|
+
const rs = await MetaData.find({
|
|
147
|
+
type: "CopilotConstructMgr",
|
|
148
|
+
name: "requirement",
|
|
149
|
+
});
|
|
150
|
+
for (const r of rs) await r.delete();
|
|
151
|
+
return { json: { reload_page: true } };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const req_routes = { gen_reqs, del_req, del_all_reqs };
|
|
155
|
+
|
|
156
|
+
module.exports = { requirementsList, req_routes };
|
|
@@ -0,0 +1,146 @@
|
|
|
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 User = require("@saltcorn/data/models/user");
|
|
12
|
+
const { viewname } = require("./common");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {number} md_id - MetaData id of the task to run
|
|
16
|
+
* @param {object} req - Express request (may be empty `{}` from scheduler)
|
|
17
|
+
*/
|
|
18
|
+
const runTask = async (md_id, req) => {
|
|
19
|
+
const md = await MetaData.findOne({
|
|
20
|
+
id: md_id,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!md) return { error: "Task not found" };
|
|
24
|
+
const spec = await MetaData.findOne({
|
|
25
|
+
type: "CopilotConstructMgr",
|
|
26
|
+
name: "spec",
|
|
27
|
+
});
|
|
28
|
+
if (!spec) return { error: "Specification not found" };
|
|
29
|
+
const agent_action = new Trigger({
|
|
30
|
+
action: "Agent",
|
|
31
|
+
when_trigger: "Never",
|
|
32
|
+
configuration: {
|
|
33
|
+
viewname: viewname,
|
|
34
|
+
sys_prompt:
|
|
35
|
+
"Each task creates exactly one view or one page. " +
|
|
36
|
+
"Never create more than one view or page per task, even if the description mentions multiple. " +
|
|
37
|
+
"Call the view or page tool exactly once and then stop.",
|
|
38
|
+
prompt: "{{prompt}}",
|
|
39
|
+
skills: [
|
|
40
|
+
{ skill_type: "Generate Page", yoloMode: true },
|
|
41
|
+
{ skill_type: "Database design", yoloMode: true },
|
|
42
|
+
{ skill_type: "Generate Workflow", yoloMode: true },
|
|
43
|
+
{ skill_type: "Generate trigger", yoloMode: true },
|
|
44
|
+
{ skill_type: "Generate View", yoloMode: true },
|
|
45
|
+
{ skill_type: "Install Plugin", yoloMode: true },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const prompt = `You are engaged in building the following application:
|
|
50
|
+
|
|
51
|
+
Description: ${spec.body.description}
|
|
52
|
+
Audience: ${spec.body.audience}
|
|
53
|
+
Core features: ${spec.body.core_features}
|
|
54
|
+
Out of scope: ${spec.body.out_of_scope}
|
|
55
|
+
Visual style: ${spec.body.visual_style}
|
|
56
|
+
|
|
57
|
+
Important: The database schema is already fully implemented. Do NOT use generate_tables or modify any tables or fields — all tables and fields already exist.
|
|
58
|
+
|
|
59
|
+
Important: Some fields are non-stored (virtual) calculated fields — they have no database column and are computed on-the-fly by Saltcorn. Never include such fields in modify_row, SQL UPDATE statements, or recalculate_stored_fields calls. Only fields that exist as actual database columns (regular fields and stored calculated fields) can be written. If a calculated field needs updating, it will refresh automatically when the fields it depends on change.
|
|
60
|
+
|
|
61
|
+
Important: The "users" table is built-in. Passwords are platform-managed — never add a password field to a view. Signup uses a built-in form, not an Edit view.
|
|
62
|
+
|
|
63
|
+
Your task now is:
|
|
64
|
+
${md.body.description}`;
|
|
65
|
+
const safeReq = req?.__
|
|
66
|
+
? req
|
|
67
|
+
: { ...req, __: (s) => s, user: req?.user };
|
|
68
|
+
|
|
69
|
+
await md.update({ body: { ...md.body, status: "Running" } });
|
|
70
|
+
const actionres = await agent_action.runWithoutRow({
|
|
71
|
+
row: { prompt },
|
|
72
|
+
req: safeReq,
|
|
73
|
+
user: safeReq.user,
|
|
74
|
+
});
|
|
75
|
+
const run_id = actionres.json.run_id;
|
|
76
|
+
const run = await WorkflowRun.findOne({ id: run_id });
|
|
77
|
+
await agent_action.runWithoutRow({
|
|
78
|
+
row: {
|
|
79
|
+
prompt:
|
|
80
|
+
"Write a description of what you did, for the purposes of a progress report. Write 1-4 sentences. Do not use any tools or write any code",
|
|
81
|
+
},
|
|
82
|
+
req: safeReq,
|
|
83
|
+
run,
|
|
84
|
+
user: safeReq.user,
|
|
85
|
+
});
|
|
86
|
+
const lastInteraction =
|
|
87
|
+
run.context.interactions[run.context.interactions.length - 1];
|
|
88
|
+
const lastText =
|
|
89
|
+
typeof lastInteraction.content === "string"
|
|
90
|
+
? lastInteraction.content
|
|
91
|
+
: lastInteraction.content.text
|
|
92
|
+
? lastInteraction.content.text
|
|
93
|
+
: Array.isArray(lastInteraction.content)
|
|
94
|
+
? lastInteraction.content[0].text
|
|
95
|
+
: lastInteraction.content;
|
|
96
|
+
await MetaData.create({
|
|
97
|
+
type: "CopilotConstructMgr",
|
|
98
|
+
name: "progress",
|
|
99
|
+
body: { text: lastText, run_id, task_id: md.id },
|
|
100
|
+
user_id: req?.user?.id,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await md.update({ body: { ...md.body, status: "Done", run_id } });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Run the next startable task
|
|
108
|
+
* @param {boolean} [once=false] - true: run one task and stop, false: iterate all tasks
|
|
109
|
+
*/
|
|
110
|
+
const runNextTask = async (once = false) => {
|
|
111
|
+
if (!once) {
|
|
112
|
+
const settings = await MetaData.findOne({
|
|
113
|
+
type: "CopilotConstructMgr",
|
|
114
|
+
name: "settings",
|
|
115
|
+
});
|
|
116
|
+
if (!settings?.body?.running) return;
|
|
117
|
+
}
|
|
118
|
+
const tasks = await MetaData.find(
|
|
119
|
+
{
|
|
120
|
+
type: "CopilotConstructMgr",
|
|
121
|
+
name: "task",
|
|
122
|
+
},
|
|
123
|
+
{ orderBy: "id" }
|
|
124
|
+
);
|
|
125
|
+
if (tasks.some((t) => t.body.status === "Running")) return;
|
|
126
|
+
const todos = tasks.filter(
|
|
127
|
+
(t) => !t.body.status || t.body.status === "To do"
|
|
128
|
+
);
|
|
129
|
+
const done = tasks.filter((t) => t.body.status === "Done");
|
|
130
|
+
const done_names = new Set(done.map((t) => t.body.name));
|
|
131
|
+
|
|
132
|
+
const startable = todos.filter((t) =>
|
|
133
|
+
t.body.depends_on.every((nm) => done_names.has(nm))
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (startable[0]) {
|
|
137
|
+
console.log("running task", startable[0]);
|
|
138
|
+
const taskUser = startable[0].user_id
|
|
139
|
+
? await User.findOne({ id: startable[0].user_id })
|
|
140
|
+
: null;
|
|
141
|
+
await runTask(startable[0].id, { user: taskUser, __: (s) => s });
|
|
142
|
+
if (!once) await runNextTask();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
module.exports = { runTask, runNextTask };
|
|
@@ -0,0 +1,199 @@
|
|
|
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, tool_choice } = require("./common");
|
|
39
|
+
const { requirements_tool } = require("./tools");
|
|
40
|
+
const { saltcorn_description, existing_tables_list } = require("./prompts");
|
|
41
|
+
const GenerateTables = require("../actions/generate-tables");
|
|
42
|
+
const GenerateTablesSkill = require("../agent-skills/database-design");
|
|
43
|
+
|
|
44
|
+
const showSchema = async (req) => {
|
|
45
|
+
const schema = await MetaData.findOne({
|
|
46
|
+
type: "CopilotConstructMgr",
|
|
47
|
+
name: "schema",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (schema) {
|
|
51
|
+
const preview = GenerateTables.render_html(
|
|
52
|
+
{ tables: schema.body.tables },
|
|
53
|
+
true
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return div(
|
|
57
|
+
{ class: "mt-2" },
|
|
58
|
+
preview,
|
|
59
|
+
!schema.body.implemented &&
|
|
60
|
+
div(
|
|
61
|
+
{ class: "mb-4 d-block mt-3" },
|
|
62
|
+
button(
|
|
63
|
+
{
|
|
64
|
+
class: "btn btn-primary me-2",
|
|
65
|
+
onclick: `view_post("${viewname}", "implement_schema")`,
|
|
66
|
+
},
|
|
67
|
+
"Implement schema"
|
|
68
|
+
),
|
|
69
|
+
button(
|
|
70
|
+
{
|
|
71
|
+
class: "btn btn-outline-danger",
|
|
72
|
+
onclick: `view_post("${viewname}", "del_schema")`,
|
|
73
|
+
},
|
|
74
|
+
"Delete schema"
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
return div(
|
|
80
|
+
{ class: "mt-2" },
|
|
81
|
+
p("Schema not found"),
|
|
82
|
+
button(
|
|
83
|
+
{
|
|
84
|
+
class: "btn btn-primary",
|
|
85
|
+
onclick: `press_store_button(this);view_post("${viewname}", "gen_schema")`,
|
|
86
|
+
},
|
|
87
|
+
"Generate schema"
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const gen_schema = async (table_id, viewname, config, body, { req, res }) => {
|
|
94
|
+
const spec = await MetaData.findOne({
|
|
95
|
+
type: "CopilotConstructMgr",
|
|
96
|
+
name: "spec",
|
|
97
|
+
});
|
|
98
|
+
if (!spec) throw new Error("Specification not found");
|
|
99
|
+
const rs = await MetaData.find({
|
|
100
|
+
type: "CopilotConstructMgr",
|
|
101
|
+
name: "requirement",
|
|
102
|
+
});
|
|
103
|
+
if (!rs.length) throw new Error("No requirements found");
|
|
104
|
+
|
|
105
|
+
const databaseDesignTool = new GenerateTablesSkill({}).provideTools();
|
|
106
|
+
const existing_tables = await Table.find({});
|
|
107
|
+
const answer = await getState().functions.llm_generate.run(
|
|
108
|
+
`Generate the database schema for this application:
|
|
109
|
+
|
|
110
|
+
Description: ${spec.body.description}
|
|
111
|
+
Audience: ${spec.body.audience}
|
|
112
|
+
Core features: ${spec.body.core_features}
|
|
113
|
+
Out of scope: ${spec.body.out_of_scope}
|
|
114
|
+
Visual style: ${spec.body.visual_style}
|
|
115
|
+
|
|
116
|
+
These are the requirements of the application:
|
|
117
|
+
|
|
118
|
+
${rs.map((r) => `* ${r.body.requirement}`).join("\n")}
|
|
119
|
+
|
|
120
|
+
${saltcorn_description}
|
|
121
|
+
|
|
122
|
+
${existing_tables_list(existing_tables)}
|
|
123
|
+
|
|
124
|
+
Design a complete database schema that covers ALL requirements listed above. Every distinct entity in the application must have its own table. Do not produce a minimal or partial schema — all tables needed to implement every requirement must be included in this single call. Do not leave any tables for a later step.
|
|
125
|
+
|
|
126
|
+
The tables listed above are already implemented in the database — include them in the schema as-is so the full data model is visible, but do not change their fields. Only add new tables for entities not yet covered. The implementation step will skip any table whose name already exists.
|
|
127
|
+
|
|
128
|
+
For every field that must be unique (e.g. unique email, unique slug, unique combination keys expressed as individual unique fields), set unique=true on that field.
|
|
129
|
+
For every field that must not be empty, set not_null=true.
|
|
130
|
+
Do NOT leave uniqueness or required constraints for a later step — express them fully in this schema.
|
|
131
|
+
|
|
132
|
+
Note: ownership configuration (automatically populating a FK-to-users field from the logged-in user) is a VIEW-level concern and cannot be expressed in the schema. Do not attempt to annotate fields as "ownership fields" here — simply define the foreign key field normally. Ownership will be configured when the Edit views are generated.
|
|
133
|
+
|
|
134
|
+
Now use the ${
|
|
135
|
+
databaseDesignTool.function.name
|
|
136
|
+
} tool to generate the complete database schema for this software application
|
|
137
|
+
`,
|
|
138
|
+
{
|
|
139
|
+
tools: [databaseDesignTool],
|
|
140
|
+
...tool_choice(databaseDesignTool.function.name),
|
|
141
|
+
systemPrompt:
|
|
142
|
+
"You are a database designer. The user wants to build an application, and you must analyse their application description and requirements and design a complete schema. Every entity needed by any requirement must have its own table. Never produce a partial schema.",
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const tc = answer.getToolCalls()[0];
|
|
147
|
+
|
|
148
|
+
await MetaData.create({
|
|
149
|
+
type: "CopilotConstructMgr",
|
|
150
|
+
name: "schema",
|
|
151
|
+
body: { tables: tc.input.tables, implemented: false },
|
|
152
|
+
user_id: req.user?.id,
|
|
153
|
+
});
|
|
154
|
+
return { json: { reload_page: true } };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const del_schema = async (table_id, viewname, config, body, { req, res }) => {
|
|
158
|
+
const rs = await MetaData.find({
|
|
159
|
+
type: "CopilotConstructMgr",
|
|
160
|
+
name: "schema",
|
|
161
|
+
});
|
|
162
|
+
for (const r of rs) await r.delete();
|
|
163
|
+
return { json: { reload_page: true } };
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const implement_schema = async (
|
|
167
|
+
table_id,
|
|
168
|
+
viewname,
|
|
169
|
+
config,
|
|
170
|
+
body,
|
|
171
|
+
{ req, res }
|
|
172
|
+
) => {
|
|
173
|
+
const md = await MetaData.findOne({
|
|
174
|
+
type: "CopilotConstructMgr",
|
|
175
|
+
name: "schema",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { apply_copilot_tables } = new GenerateTablesSkill({}).userActions;
|
|
179
|
+
const existingNames = new Set((await Table.find({})).map((t) => t.name));
|
|
180
|
+
const newTables = md.body.tables.filter((t) => {
|
|
181
|
+
if (existingNames.has(t.name)) {
|
|
182
|
+
getState().log(
|
|
183
|
+
2,
|
|
184
|
+
`AppConstructor: skipping table "${t.name}" — already exists in database`
|
|
185
|
+
);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
});
|
|
190
|
+
await apply_copilot_tables({ tables: newTables, user: req.user });
|
|
191
|
+
md.body.implemented = true;
|
|
192
|
+
await md.update({ body: md.body });
|
|
193
|
+
|
|
194
|
+
return { json: { reload_page: true } };
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const schema_routes = { gen_schema, del_schema, implement_schema };
|
|
198
|
+
|
|
199
|
+
module.exports = { showSchema, schema_routes };
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
a,
|
|
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
|
+
const { runTask, runNextTask } = require("./run_task");
|
|
40
|
+
|
|
41
|
+
const makeTaskChart = async (req) => {
|
|
42
|
+
const rs = await MetaData.find(
|
|
43
|
+
{
|
|
44
|
+
type: "CopilotConstructMgr",
|
|
45
|
+
name: "task",
|
|
46
|
+
},
|
|
47
|
+
{ orderBy: "written_at" },
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const taskIds = {};
|
|
51
|
+
rs.forEach((md) => {
|
|
52
|
+
taskIds[md.body.name] = md.id;
|
|
53
|
+
});
|
|
54
|
+
return div(
|
|
55
|
+
pre({
|
|
56
|
+
class: "mermaid",
|
|
57
|
+
"mm-src": `flowchart LR
|
|
58
|
+
${rs.map((md) => ` task${md.id}["${md.body.name}"]`).join("\n")}
|
|
59
|
+
${rs.map((md) => (md.body.depends_on || []).map((depon) => ` task${taskIds[depon]} --> task${md.id}`).join("\n")).join("\n")}
|
|
60
|
+
${rs
|
|
61
|
+
.filter((m) => m.body.status === "Done")
|
|
62
|
+
.map((md) => ` style task${md.id} fill:#777`)
|
|
63
|
+
.join("\n")}
|
|
64
|
+
|
|
65
|
+
`,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
module.exports = { makeTaskChart };
|