@saltcorn/copilot 0.8.1 → 0.8.3
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-page.js +6 -2
- package/actions/generate-tables.js +70 -6
- package/actions/generate-workflow.js +54 -3
- package/agent-skills/pagegen.js +171 -59
- package/agent-skills/registry-editor.js +30 -2
- package/agent-skills/triggergen.js +1 -5
- package/agent-skills/viewgen.js +49 -7
- package/app-constructor/common.js +7 -1
- package/app-constructor/errors.js +749 -61
- package/app-constructor/feedback-action.js +62 -60
- package/app-constructor/feedback.js +1294 -67
- package/app-constructor/fixed-prompts.js +829 -0
- package/app-constructor/phases.js +1485 -0
- package/app-constructor/prompt-generator.js +587 -0
- package/app-constructor/requirements.js +171 -50
- package/app-constructor/research.js +350 -0
- package/app-constructor/run_task.js +234 -73
- package/app-constructor/schema.js +173 -169
- package/app-constructor/tasks.js +96 -537
- package/app-constructor/tools.js +17 -4
- package/app-constructor/view.js +314 -54
- package/builder-gen.js +90 -41
- package/builder-schema.js +6 -0
- package/copilot-as-agent.js +1 -0
- package/index.js +0 -1
- package/js-code-gen.js +1 -0
- package/package.json +1 -1
- package/relation-paths.js +73 -40
- package/standard-prompt.js +1 -0
- package/user-copilot.js +2 -0
- package/workflow-gen.js +1 -0
- package/app-constructor/prompts.js +0 -120
- package/chat-copilot.js +0 -770
|
@@ -1,199 +1,203 @@
|
|
|
1
|
-
const Field = require("@saltcorn/data/models/field");
|
|
2
1
|
const Table = require("@saltcorn/data/models/table");
|
|
3
|
-
const Form = require("@saltcorn/data/models/form");
|
|
4
2
|
const MetaData = require("@saltcorn/data/models/metadata");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
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");
|
|
3
|
+
const { div, pre, p, small, span } = require("@saltcorn/markup/tags");
|
|
4
|
+
const { viewname } = require("./common");
|
|
41
5
|
const GenerateTables = require("../actions/generate-tables");
|
|
42
|
-
const
|
|
6
|
+
const { buildMermaidMarkup } = GenerateTables;
|
|
7
|
+
|
|
8
|
+
const PHASE_COLORS = [
|
|
9
|
+
{ fill: "#93c5fd", stroke: "#1e40af" },
|
|
10
|
+
{ fill: "#86efac", stroke: "#166534" },
|
|
11
|
+
{ fill: "#fcd34d", stroke: "#92400e" },
|
|
12
|
+
{ fill: "#d8b4fe", stroke: "#6b21a8" },
|
|
13
|
+
{ fill: "#f9a8d4", stroke: "#9d174d" },
|
|
14
|
+
{ fill: "#fdba74", stroke: "#9a3412" },
|
|
15
|
+
];
|
|
16
|
+
const NO_PHASE_COLOR = { fill: "#cbd5e1", stroke: "#334155" };
|
|
43
17
|
|
|
44
18
|
const showSchema = async (req) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
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 {
|
|
19
|
+
const allTables = await Table.find({});
|
|
20
|
+
const userTables = allTables.filter((t) => !t.name.startsWith("_sc_"));
|
|
21
|
+
if (!userTables.length) {
|
|
79
22
|
return div(
|
|
80
23
|
{ class: "mt-2" },
|
|
81
|
-
p(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class: "btn btn-primary",
|
|
85
|
-
onclick: `press_store_button(this);view_post("${viewname}", "gen_schema")`,
|
|
86
|
-
},
|
|
87
|
-
"Generate schema"
|
|
24
|
+
p(
|
|
25
|
+
{ class: "text-muted" },
|
|
26
|
+
"No tables in the database yet. Data model tasks in each phase will create them."
|
|
88
27
|
)
|
|
89
28
|
);
|
|
90
29
|
}
|
|
91
|
-
};
|
|
92
30
|
|
|
93
|
-
const
|
|
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({
|
|
31
|
+
const phaseRecords = await MetaData.find({
|
|
100
32
|
type: "CopilotConstructMgr",
|
|
101
|
-
name: "
|
|
33
|
+
name: "table_phase",
|
|
102
34
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
35
|
+
const tablePhaseMap = {};
|
|
36
|
+
for (const r of phaseRecords) tablePhaseMap[r.body.table_name] = r.body;
|
|
37
|
+
|
|
38
|
+
// Collect phases seen, sorted by index
|
|
39
|
+
const phasesSeen = new Map();
|
|
40
|
+
for (const r of phaseRecords)
|
|
41
|
+
if (!phasesSeen.has(r.body.phase_idx))
|
|
42
|
+
phasesSeen.set(r.body.phase_idx, r.body.phase_name);
|
|
43
|
+
const orderedPhases = [...phasesSeen.entries()].sort(([a], [b]) => a - b);
|
|
44
|
+
|
|
45
|
+
// Group table names by phase for the legend
|
|
46
|
+
const phaseTableNames = new Map();
|
|
47
|
+
const unassignedNames = [];
|
|
48
|
+
for (const t of userTables) {
|
|
49
|
+
const entry = tablePhaseMap[t.name];
|
|
50
|
+
if (entry) {
|
|
51
|
+
if (!phaseTableNames.has(entry.phase_idx))
|
|
52
|
+
phaseTableNames.set(entry.phase_idx, []);
|
|
53
|
+
phaseTableNames.get(entry.phase_idx).push(t.name);
|
|
54
|
+
} else {
|
|
55
|
+
unassignedNames.push(t.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
125
58
|
|
|
126
|
-
|
|
59
|
+
// Pre-compute fill color per table name for the client script
|
|
60
|
+
const colorMap = {};
|
|
61
|
+
for (const t of userTables) {
|
|
62
|
+
const entry = tablePhaseMap[t.name];
|
|
63
|
+
colorMap[t.name] = entry
|
|
64
|
+
? PHASE_COLORS[entry.phase_idx % PHASE_COLORS.length]
|
|
65
|
+
: NO_PHASE_COLOR;
|
|
66
|
+
}
|
|
127
67
|
|
|
128
|
-
|
|
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.
|
|
68
|
+
const mmdia = buildMermaidMarkup(userTables);
|
|
131
69
|
|
|
132
|
-
|
|
70
|
+
const tableBadge = (color, name) =>
|
|
71
|
+
span(
|
|
72
|
+
{
|
|
73
|
+
class: "badge rounded-pill",
|
|
74
|
+
style: `background:${color.fill};color:${color.stroke};border:1.5px solid ${color.stroke}`,
|
|
75
|
+
},
|
|
76
|
+
name
|
|
77
|
+
);
|
|
133
78
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
79
|
+
const legendGroups = [
|
|
80
|
+
...(unassignedNames.length
|
|
81
|
+
? [
|
|
82
|
+
div(
|
|
83
|
+
{ class: "d-flex flex-wrap align-items-center gap-1" },
|
|
84
|
+
span({ class: "me-1 text-muted small" }, "Pre-existing:"),
|
|
85
|
+
...unassignedNames.map((n) => tableBadge(NO_PHASE_COLOR, n))
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
: []),
|
|
89
|
+
...orderedPhases
|
|
90
|
+
.map(([idx, name]) => {
|
|
91
|
+
const color = PHASE_COLORS[idx % PHASE_COLORS.length];
|
|
92
|
+
const names = phaseTableNames.get(idx) || [];
|
|
93
|
+
if (!names.length) return null;
|
|
94
|
+
return div(
|
|
95
|
+
{ class: "d-flex flex-wrap align-items-center gap-1" },
|
|
96
|
+
span(
|
|
97
|
+
{ class: "me-1 text-muted small" },
|
|
98
|
+
`Phase ${idx + 1}${name ? `: ${name}` : ""}:`
|
|
99
|
+
),
|
|
100
|
+
...names.map((n) => tableBadge(color, n))
|
|
101
|
+
);
|
|
102
|
+
})
|
|
103
|
+
.filter(Boolean),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
return div(
|
|
107
|
+
{ class: "mt-2" },
|
|
108
|
+
small(
|
|
109
|
+
{ class: "text-muted d-block mb-2" },
|
|
110
|
+
`${userTables.length} table${
|
|
111
|
+
userTables.length !== 1 ? "s" : ""
|
|
112
|
+
} — reflects current database state`
|
|
113
|
+
),
|
|
114
|
+
pre(
|
|
115
|
+
{
|
|
116
|
+
class: "schema-mermaid",
|
|
117
|
+
"data-color-map": JSON.stringify(colorMap),
|
|
118
|
+
},
|
|
119
|
+
mmdia
|
|
120
|
+
),
|
|
121
|
+
legendGroups.length
|
|
122
|
+
? div({ class: "d-flex flex-column gap-2 mt-2" }, ...legendGroups)
|
|
123
|
+
: ""
|
|
144
124
|
);
|
|
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
125
|
};
|
|
165
126
|
|
|
166
|
-
const
|
|
127
|
+
const schema_list_html = async (
|
|
167
128
|
table_id,
|
|
168
129
|
viewname,
|
|
169
130
|
config,
|
|
170
131
|
body,
|
|
171
132
|
{ req, res }
|
|
172
133
|
) => {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
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 });
|
|
134
|
+
const html = await showSchema(req);
|
|
135
|
+
return { json: { html } };
|
|
136
|
+
};
|
|
193
137
|
|
|
194
|
-
|
|
138
|
+
const schema_routes = {
|
|
139
|
+
schema_list_html,
|
|
195
140
|
};
|
|
196
141
|
|
|
197
|
-
const
|
|
142
|
+
const schemaStaticScript = `<script>
|
|
143
|
+
(function(){
|
|
144
|
+
const _schemVn = ${JSON.stringify(viewname)};
|
|
145
|
+
|
|
146
|
+
// Ensure mermaid won't auto-process on its own — we drive rendering manually
|
|
147
|
+
if (typeof mermaid !== 'undefined') {
|
|
148
|
+
try { mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' }); } catch (_) {}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function applyColors(el, colorMap) {
|
|
152
|
+
for (const g of el.querySelectorAll('g[id^="entity-"]')) {
|
|
153
|
+
const name = g.id.replace(/^entity-/, '').replace(/-\\d+$/, '');
|
|
154
|
+
const colors = colorMap[name];
|
|
155
|
+
if (!colors) continue;
|
|
156
|
+
const path = g.querySelector('path');
|
|
157
|
+
if (path) {
|
|
158
|
+
path.style.fill = colors.fill;
|
|
159
|
+
path.style.stroke = colors.stroke;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
198
163
|
|
|
199
|
-
|
|
164
|
+
window.copilotRenderSchemaMermaid = () => {
|
|
165
|
+
const pre = document.querySelector('#schema-list-area .schema-mermaid');
|
|
166
|
+
if (!pre || typeof mermaid === 'undefined') return;
|
|
167
|
+
const colorMap = pre.dataset.colorMap ? JSON.parse(pre.dataset.colorMap) : {};
|
|
168
|
+
const mermaidText = pre.textContent.trim();
|
|
169
|
+
if (!mermaidText) return;
|
|
170
|
+
|
|
171
|
+
const tmp = document.createElement('div');
|
|
172
|
+
tmp.style.cssText = 'position:absolute;left:-9999px;top:0;visibility:hidden';
|
|
173
|
+
tmp.textContent = mermaidText;
|
|
174
|
+
document.body.appendChild(tmp);
|
|
175
|
+
|
|
176
|
+
mermaid.run({ nodes: [tmp], suppressErrors: true })
|
|
177
|
+
.then(() => {
|
|
178
|
+
const target = document.querySelector('#schema-list-area .schema-mermaid');
|
|
179
|
+
if (target) { target.innerHTML = tmp.innerHTML; applyColors(target, colorMap); }
|
|
180
|
+
tmp.remove();
|
|
181
|
+
})
|
|
182
|
+
.catch(e => {
|
|
183
|
+
console.warn('mermaid schema render error', e);
|
|
184
|
+
tmp.remove();
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
window.copilotRefreshSchema = function() {
|
|
189
|
+
view_post(_schemVn, 'schema_list_html', {}, (r) => {
|
|
190
|
+
const el = document.getElementById('schema-list-area');
|
|
191
|
+
if (r && r.html && el) {
|
|
192
|
+
el.innerHTML = r.html;
|
|
193
|
+
copilotRenderSchemaMermaid();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (document.readyState !== 'loading') copilotRenderSchemaMermaid();
|
|
199
|
+
else document.addEventListener('DOMContentLoaded', copilotRenderSchemaMermaid);
|
|
200
|
+
})()
|
|
201
|
+
</script>`;
|
|
202
|
+
|
|
203
|
+
module.exports = { showSchema, schema_routes, schemaStaticScript };
|