@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
|
@@ -37,6 +37,58 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
37
37
|
const renderLayout = require("@saltcorn/markup/layout");
|
|
38
38
|
const { viewname, tool_choice } = require("./common");
|
|
39
39
|
const { requirements_tool } = require("./tools");
|
|
40
|
+
const { PromptGenerator } = require("./prompt-generator");
|
|
41
|
+
|
|
42
|
+
const requirementsStaticScript = `<script>
|
|
43
|
+
const _reqsVn = ${JSON.stringify(viewname)};
|
|
44
|
+
|
|
45
|
+
window.copilotRefreshReqs = () => {
|
|
46
|
+
view_post(_reqsVn, 'req_list_html', {}, (r) => {
|
|
47
|
+
const a = document.getElementById('req-list-area');
|
|
48
|
+
if (r && r.html && a) {
|
|
49
|
+
a.innerHTML = r.html;
|
|
50
|
+
if (typeof copilotInitReqsState === 'function') copilotInitReqsState();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
window.copilotGenReqs = function() {
|
|
56
|
+
const area = document.getElementById('req-gen-area');
|
|
57
|
+
if (area) area.innerHTML =
|
|
58
|
+
'<p><i class="fas fa-spinner fa-spin me-2"></i>Generating requirements, please wait...</p>';
|
|
59
|
+
view_post(_reqsVn, 'gen_reqs', {}, () => {});
|
|
60
|
+
if (!window.dynamic_updates_cfg?.enabled) {
|
|
61
|
+
const poll = () => {
|
|
62
|
+
view_post(_reqsVn, 'req_status', {}, (resp) => {
|
|
63
|
+
if (resp && !resp.generating) {
|
|
64
|
+
if (typeof copilotRefreshReqs === 'function') copilotRefreshReqs();
|
|
65
|
+
} else setTimeout(poll, 3000);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
setTimeout(poll, 3000);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function copilotInitReqsState() {
|
|
73
|
+
const isGenerating = !!document.getElementById('reqs-generating-state');
|
|
74
|
+
if (isGenerating) {
|
|
75
|
+
const poll = () => {
|
|
76
|
+
view_post(_reqsVn, 'req_status', {}, (resp) => {
|
|
77
|
+
if (resp && !resp.generating) {
|
|
78
|
+
if (typeof copilotRefreshReqs === 'function') copilotRefreshReqs();
|
|
79
|
+
} else setTimeout(poll, 3000);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
if (!window.dynamic_updates_cfg?.enabled) setTimeout(poll, 3000);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
window.copilotInitReqsState = copilotInitReqsState;
|
|
86
|
+
|
|
87
|
+
(function () {
|
|
88
|
+
if (document.readyState !== 'loading') copilotInitReqsState();
|
|
89
|
+
else document.addEventListener('DOMContentLoaded', copilotInitReqsState);
|
|
90
|
+
})();
|
|
91
|
+
</script>`;
|
|
40
92
|
|
|
41
93
|
const requirementsList = async (req) => {
|
|
42
94
|
const rs = await MetaData.find(
|
|
@@ -44,7 +96,7 @@ const requirementsList = async (req) => {
|
|
|
44
96
|
type: "CopilotConstructMgr",
|
|
45
97
|
name: "requirement",
|
|
46
98
|
},
|
|
47
|
-
{ orderBy: "written_at" }
|
|
99
|
+
{ orderBy: "written_at" }
|
|
48
100
|
);
|
|
49
101
|
const starFieldview = getState().types.Integer.fieldviews.show_star_rating;
|
|
50
102
|
|
|
@@ -53,7 +105,21 @@ const requirementsList = async (req) => {
|
|
|
53
105
|
{ class: "mt-2" },
|
|
54
106
|
mkTable(
|
|
55
107
|
[
|
|
56
|
-
{
|
|
108
|
+
{
|
|
109
|
+
label: "Requirement",
|
|
110
|
+
key: (m) =>
|
|
111
|
+
m.body.requirement +
|
|
112
|
+
(m.body.source === "feedback"
|
|
113
|
+
? span(
|
|
114
|
+
{
|
|
115
|
+
class: "badge bg-warning text-dark ms-2 fw-normal",
|
|
116
|
+
title: `From feedback: ${m.body.feedback_title || ""}`,
|
|
117
|
+
},
|
|
118
|
+
i({ class: "fas fa-comment-alt me-1" }),
|
|
119
|
+
"feedback"
|
|
120
|
+
)
|
|
121
|
+
: ""),
|
|
122
|
+
},
|
|
57
123
|
{
|
|
58
124
|
label: "Priority",
|
|
59
125
|
key: (m) =>
|
|
@@ -67,70 +133,97 @@ const requirementsList = async (req) => {
|
|
|
67
133
|
class: "btn btn-outline-danger btn-sm",
|
|
68
134
|
onclick: `view_post("${viewname}", "del_req", {id:${r.id}})`,
|
|
69
135
|
},
|
|
70
|
-
i({ class: "fas fa-trash-alt" })
|
|
136
|
+
i({ class: "fas fa-trash-alt" })
|
|
71
137
|
),
|
|
72
138
|
},
|
|
73
139
|
],
|
|
74
|
-
rs
|
|
140
|
+
rs
|
|
75
141
|
),
|
|
76
142
|
button(
|
|
77
143
|
{
|
|
78
144
|
class: "btn btn-outline-danger mb-4",
|
|
79
145
|
onclick: `view_post("${viewname}", "del_all_reqs")`,
|
|
80
146
|
},
|
|
81
|
-
"Delete all"
|
|
82
|
-
)
|
|
147
|
+
"Delete all"
|
|
148
|
+
)
|
|
83
149
|
);
|
|
84
|
-
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const generating = await MetaData.findOne({
|
|
153
|
+
type: "CopilotConstructMgr",
|
|
154
|
+
name: "generating_requirements",
|
|
155
|
+
});
|
|
156
|
+
if (generating) {
|
|
85
157
|
return div(
|
|
86
158
|
{ class: "mt-2" },
|
|
87
|
-
p(
|
|
88
|
-
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
"Generate requirements",
|
|
94
|
-
),
|
|
159
|
+
p(
|
|
160
|
+
{ id: "reqs-generating-state" },
|
|
161
|
+
i({ class: "fas fa-spinner fa-spin me-2" }),
|
|
162
|
+
"Generating requirements, please wait..."
|
|
163
|
+
)
|
|
95
164
|
);
|
|
96
165
|
}
|
|
166
|
+
|
|
167
|
+
return div(
|
|
168
|
+
{ class: "mt-2", id: "req-gen-area" },
|
|
169
|
+
p("No requirements found"),
|
|
170
|
+
button(
|
|
171
|
+
{ class: "btn btn-primary", onclick: `copilotGenReqs()` },
|
|
172
|
+
"Generate requirements"
|
|
173
|
+
)
|
|
174
|
+
);
|
|
97
175
|
};
|
|
98
176
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
177
|
+
const doGenReqs = async (userId) => {
|
|
178
|
+
const generatingMd = await MetaData.create({
|
|
101
179
|
type: "CopilotConstructMgr",
|
|
102
|
-
name: "
|
|
180
|
+
name: "generating_requirements",
|
|
181
|
+
body: {},
|
|
182
|
+
user_id: userId,
|
|
103
183
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
184
|
+
try {
|
|
185
|
+
const generator = await PromptGenerator.createInstance();
|
|
186
|
+
if (!generator.spec) throw new Error("Specification not found");
|
|
187
|
+
const answer = await getState().functions.llm_generate.run(
|
|
188
|
+
generator.requirementsPlanPrompt(),
|
|
189
|
+
{
|
|
190
|
+
tools: [requirements_tool],
|
|
191
|
+
...tool_choice("make_requirements"),
|
|
192
|
+
systemPrompt:
|
|
193
|
+
"You are a project manager extracting requirements from a written specification.\n" +
|
|
194
|
+
"Only include what is explicitly stated — do not infer or add plausible extras.",
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
const tc = answer.getToolCalls()[0];
|
|
198
|
+
for (const reqm of tc.input.requirements)
|
|
199
|
+
await MetaData.create({
|
|
200
|
+
type: "CopilotConstructMgr",
|
|
201
|
+
name: "requirement",
|
|
202
|
+
body: reqm,
|
|
203
|
+
user_id: userId,
|
|
204
|
+
});
|
|
205
|
+
} finally {
|
|
206
|
+
await generatingMd.delete();
|
|
207
|
+
try {
|
|
208
|
+
getState().emitDynamicUpdate(db.getTenantSchema(), {
|
|
209
|
+
eval_js:
|
|
210
|
+
"if(typeof copilotRefreshReqs==='function')copilotRefreshReqs();",
|
|
211
|
+
});
|
|
212
|
+
} catch (_) {}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
123
215
|
|
|
124
|
-
|
|
216
|
+
const gen_reqs = async (table_id, viewname, config, body, { req, res }) => {
|
|
217
|
+
doGenReqs(req.user?.id).catch((e) => console.error("gen_reqs error", e));
|
|
218
|
+
return { json: { success: true } };
|
|
219
|
+
};
|
|
125
220
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
133
|
-
return { json: { reload_page: true } };
|
|
221
|
+
const req_status = async (table_id, viewname, config, body, { req, res }) => {
|
|
222
|
+
const generating = await MetaData.findOne({
|
|
223
|
+
type: "CopilotConstructMgr",
|
|
224
|
+
name: "generating_requirements",
|
|
225
|
+
});
|
|
226
|
+
return { json: { generating: !!generating } };
|
|
134
227
|
};
|
|
135
228
|
|
|
136
229
|
const del_req = async (table_id, viewname, config, body, { req, res }) => {
|
|
@@ -140,7 +233,12 @@ const del_req = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
140
233
|
|
|
141
234
|
if (!r) throw new Error("Requirement not found");
|
|
142
235
|
await r.delete();
|
|
143
|
-
return {
|
|
236
|
+
return {
|
|
237
|
+
json: {
|
|
238
|
+
eval_js:
|
|
239
|
+
"if(typeof copilotRefreshReqs==='function')copilotRefreshReqs();",
|
|
240
|
+
},
|
|
241
|
+
};
|
|
144
242
|
};
|
|
145
243
|
const del_all_reqs = async (table_id, viewname, config, body, { req, res }) => {
|
|
146
244
|
const rs = await MetaData.find({
|
|
@@ -148,9 +246,32 @@ const del_all_reqs = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
148
246
|
name: "requirement",
|
|
149
247
|
});
|
|
150
248
|
for (const r of rs) await r.delete();
|
|
151
|
-
return {
|
|
249
|
+
return {
|
|
250
|
+
json: {
|
|
251
|
+
eval_js:
|
|
252
|
+
"if(typeof copilotRefreshReqs==='function')copilotRefreshReqs();",
|
|
253
|
+
},
|
|
254
|
+
};
|
|
152
255
|
};
|
|
153
256
|
|
|
154
|
-
|
|
257
|
+
/** Route: returns the rendered requirements list HTML for AJAX refresh. */
|
|
258
|
+
const req_list_html = async (
|
|
259
|
+
table_id,
|
|
260
|
+
viewname,
|
|
261
|
+
config,
|
|
262
|
+
body,
|
|
263
|
+
{ req, res }
|
|
264
|
+
) => {
|
|
265
|
+
const html = await requirementsList(req);
|
|
266
|
+
return { json: { html } };
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const req_routes = {
|
|
270
|
+
gen_reqs,
|
|
271
|
+
req_status,
|
|
272
|
+
del_req,
|
|
273
|
+
del_all_reqs,
|
|
274
|
+
req_list_html,
|
|
275
|
+
};
|
|
155
276
|
|
|
156
|
-
module.exports = { requirementsList, req_routes };
|
|
277
|
+
module.exports = { requirementsList, requirementsStaticScript, req_routes };
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
const MetaData = require("@saltcorn/data/models/metadata");
|
|
2
|
+
const {
|
|
3
|
+
div,
|
|
4
|
+
script,
|
|
5
|
+
domReady,
|
|
6
|
+
button,
|
|
7
|
+
i,
|
|
8
|
+
p,
|
|
9
|
+
label,
|
|
10
|
+
textarea,
|
|
11
|
+
form,
|
|
12
|
+
h5,
|
|
13
|
+
small,
|
|
14
|
+
} = require("@saltcorn/markup/tags");
|
|
15
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
16
|
+
const db = require("@saltcorn/data/db");
|
|
17
|
+
const { viewname, tool_choice } = require("./common");
|
|
18
|
+
const { PromptGenerator } = require("./prompt-generator");
|
|
19
|
+
|
|
20
|
+
const questions_tool = {
|
|
21
|
+
type: "function",
|
|
22
|
+
function: {
|
|
23
|
+
name: "ask_questions",
|
|
24
|
+
description: "Ask the user clarifying questions about the application",
|
|
25
|
+
parameters: {
|
|
26
|
+
type: "object",
|
|
27
|
+
required: ["questions"],
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
properties: {
|
|
30
|
+
questions: {
|
|
31
|
+
type: "array",
|
|
32
|
+
maxItems: 10,
|
|
33
|
+
description: "List of clarifying questions, maximum 10",
|
|
34
|
+
items: { type: "string" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const spinnerHtml =
|
|
42
|
+
"<p>" +
|
|
43
|
+
i({ class: "fas fa-spinner fa-spin me-2" }) +
|
|
44
|
+
"Generating questions, please wait...</p>";
|
|
45
|
+
|
|
46
|
+
// Pure HTML for each state — no embedded scripts
|
|
47
|
+
const researchPanelHtml = async (req) => {
|
|
48
|
+
const generating = await MetaData.findOne({
|
|
49
|
+
type: "CopilotConstructMgr",
|
|
50
|
+
name: "generating_research",
|
|
51
|
+
});
|
|
52
|
+
if (generating) return spinnerHtml;
|
|
53
|
+
|
|
54
|
+
const questions_md = await MetaData.findOne({
|
|
55
|
+
type: "CopilotConstructMgr",
|
|
56
|
+
name: "research_questions",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (questions_md) {
|
|
60
|
+
const answers_md = await MetaData.findOne({
|
|
61
|
+
type: "CopilotConstructMgr",
|
|
62
|
+
name: "research_answers",
|
|
63
|
+
});
|
|
64
|
+
const questions = questions_md.body.questions || [];
|
|
65
|
+
const saved = answers_md?.body || {};
|
|
66
|
+
|
|
67
|
+
const fieldRows = questions
|
|
68
|
+
.map((q, idx) => {
|
|
69
|
+
const fname = `question${idx + 1}`;
|
|
70
|
+
return div(
|
|
71
|
+
{ class: "mb-3" },
|
|
72
|
+
label({ class: "form-label fw-semibold", for: fname }, q),
|
|
73
|
+
textarea(
|
|
74
|
+
{ class: "form-control", id: fname, name: fname, rows: 3 },
|
|
75
|
+
saved[fname] || ""
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
})
|
|
79
|
+
.join("");
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
h5({ class: "mb-2" }, "Specification questions") +
|
|
83
|
+
small(
|
|
84
|
+
{ class: "text-muted d-block mb-3" },
|
|
85
|
+
"Answer these questions to help generate more accurate requirements and tasks. " +
|
|
86
|
+
"You can skip any question."
|
|
87
|
+
) +
|
|
88
|
+
form(
|
|
89
|
+
{ id: "research-form" },
|
|
90
|
+
fieldRows,
|
|
91
|
+
button(
|
|
92
|
+
{
|
|
93
|
+
type: "button",
|
|
94
|
+
class: "btn btn-primary me-2",
|
|
95
|
+
onclick: "copilotSubmitResearch()",
|
|
96
|
+
},
|
|
97
|
+
"Save answers"
|
|
98
|
+
),
|
|
99
|
+
button(
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
class: "btn btn-outline-secondary",
|
|
103
|
+
onclick: "copilotRegenResearch()",
|
|
104
|
+
},
|
|
105
|
+
"Regenerate questions"
|
|
106
|
+
),
|
|
107
|
+
button(
|
|
108
|
+
{
|
|
109
|
+
type: "button",
|
|
110
|
+
class: "btn btn-outline-danger ms-2",
|
|
111
|
+
onclick: "copilotDelAllResearch()",
|
|
112
|
+
},
|
|
113
|
+
"Delete all"
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
p("Generate clarifying questions based on your specification.") +
|
|
121
|
+
button(
|
|
122
|
+
{ class: "btn btn-primary", onclick: "copilotGenResearch()" },
|
|
123
|
+
"Generate questions"
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Outer wrapper rendered once on page load — includes the single script block
|
|
129
|
+
const researchPanel = async (req) => {
|
|
130
|
+
const generating = await MetaData.findOne({
|
|
131
|
+
type: "CopilotConstructMgr",
|
|
132
|
+
name: "generating_research",
|
|
133
|
+
});
|
|
134
|
+
const innerHtml = await researchPanelHtml(req);
|
|
135
|
+
|
|
136
|
+
return div(
|
|
137
|
+
{ class: "mt-2" },
|
|
138
|
+
div({ id: "research-panel" }, innerHtml),
|
|
139
|
+
script(
|
|
140
|
+
domReady(`
|
|
141
|
+
const _vn = ${JSON.stringify(viewname)};
|
|
142
|
+
function researchStartPoll() {
|
|
143
|
+
const poll = () => {
|
|
144
|
+
view_post(_vn, 'research_status', {}, (resp) => {
|
|
145
|
+
if (resp && !resp.generating) {
|
|
146
|
+
view_post(_vn, 'research_html', {}, (r) => {
|
|
147
|
+
if (r && r.html) document.getElementById('research-panel').innerHTML = r.html;
|
|
148
|
+
});
|
|
149
|
+
} else setTimeout(poll, 3000);
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
setTimeout(poll, 3000);
|
|
153
|
+
}
|
|
154
|
+
window.copilotRefreshResearch = () => {
|
|
155
|
+
view_post(_vn, 'research_html', {}, (r) => {
|
|
156
|
+
const a = document.getElementById('research-panel');
|
|
157
|
+
if (r && r.html && a) a.innerHTML = r.html;
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
window.copilotGenResearch = window.copilotRegenResearch = () => {
|
|
161
|
+
document.getElementById('research-panel').innerHTML = ${JSON.stringify(
|
|
162
|
+
spinnerHtml
|
|
163
|
+
)};
|
|
164
|
+
view_post(_vn, 'gen_research', {}, () => {});
|
|
165
|
+
if (!window.dynamic_updates_cfg?.enabled) researchStartPoll();
|
|
166
|
+
};
|
|
167
|
+
window.copilotDelAllResearch = () => {
|
|
168
|
+
view_post(_vn, 'del_all_research', {}, () => {
|
|
169
|
+
view_post(_vn, 'research_html', {}, (r) => {
|
|
170
|
+
if (r && r.html) document.getElementById('research-panel').innerHTML = r.html;
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
window.copilotSubmitResearch = () => {
|
|
175
|
+
const data = {};
|
|
176
|
+
const f = document.getElementById('research-form');
|
|
177
|
+
for (const el of f.querySelectorAll('textarea')) data[el.name] = el.value;
|
|
178
|
+
view_post(_vn, 'submit_research', data);
|
|
179
|
+
};
|
|
180
|
+
${
|
|
181
|
+
generating
|
|
182
|
+
? "if (!window.dynamic_updates_cfg?.enabled) researchStartPoll();"
|
|
183
|
+
: ""
|
|
184
|
+
}
|
|
185
|
+
`)
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const doGenResearch = async (userId) => {
|
|
191
|
+
const generatingMd = await MetaData.create({
|
|
192
|
+
type: "CopilotConstructMgr",
|
|
193
|
+
name: "generating_research",
|
|
194
|
+
body: {},
|
|
195
|
+
user_id: userId,
|
|
196
|
+
});
|
|
197
|
+
try {
|
|
198
|
+
const generator = await PromptGenerator.createInstance();
|
|
199
|
+
if (!generator.spec) throw new Error("Specification not found");
|
|
200
|
+
const answer = await getState().functions.llm_generate.run(
|
|
201
|
+
generator.researchQuestionsPrompt(),
|
|
202
|
+
{
|
|
203
|
+
tools: [questions_tool],
|
|
204
|
+
...tool_choice("ask_questions"),
|
|
205
|
+
systemPrompt:
|
|
206
|
+
"You are a requirements analyst. Ask only the clarifying questions that are\n" +
|
|
207
|
+
"genuinely needed — fewer is better. 10 is a hard maximum, not a target.\n" +
|
|
208
|
+
"Each question must be short, clear, and easy to understand —\n" +
|
|
209
|
+
"avoid technical jargon where possible and keep sentences simple.",
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
const tc = answer.getToolCalls()[0];
|
|
213
|
+
const existing = await MetaData.findOne({
|
|
214
|
+
type: "CopilotConstructMgr",
|
|
215
|
+
name: "research_questions",
|
|
216
|
+
});
|
|
217
|
+
if (existing) {
|
|
218
|
+
await existing.update({ body: { questions: tc.input.questions } });
|
|
219
|
+
} else {
|
|
220
|
+
await MetaData.create({
|
|
221
|
+
type: "CopilotConstructMgr",
|
|
222
|
+
name: "research_questions",
|
|
223
|
+
body: { questions: tc.input.questions },
|
|
224
|
+
user_id: userId,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const oldAnswers = await MetaData.findOne({
|
|
228
|
+
type: "CopilotConstructMgr",
|
|
229
|
+
name: "research_answers",
|
|
230
|
+
});
|
|
231
|
+
if (oldAnswers) await oldAnswers.delete();
|
|
232
|
+
} finally {
|
|
233
|
+
await generatingMd.delete();
|
|
234
|
+
try {
|
|
235
|
+
getState().emitDynamicUpdate(db.getTenantSchema(), {
|
|
236
|
+
eval_js:
|
|
237
|
+
"if(typeof copilotRefreshResearch==='function')copilotRefreshResearch();",
|
|
238
|
+
});
|
|
239
|
+
} catch (_) {}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const gen_research = async (table_id, viewname, config, body, { req, res }) => {
|
|
244
|
+
doGenResearch(req.user?.id).catch((e) =>
|
|
245
|
+
console.error("gen_research error", e)
|
|
246
|
+
);
|
|
247
|
+
return { json: { success: true } };
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const research_status = async (
|
|
251
|
+
table_id,
|
|
252
|
+
viewname,
|
|
253
|
+
config,
|
|
254
|
+
body,
|
|
255
|
+
{ req, res }
|
|
256
|
+
) => {
|
|
257
|
+
const generating = await MetaData.findOne({
|
|
258
|
+
type: "CopilotConstructMgr",
|
|
259
|
+
name: "generating_research",
|
|
260
|
+
});
|
|
261
|
+
return { json: { generating: !!generating } };
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const research_html = async (
|
|
265
|
+
table_id,
|
|
266
|
+
viewname,
|
|
267
|
+
config,
|
|
268
|
+
body,
|
|
269
|
+
{ req, res }
|
|
270
|
+
) => {
|
|
271
|
+
const html = await researchPanelHtml(req);
|
|
272
|
+
return { json: { html } };
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const submit_research = async (
|
|
276
|
+
table_id,
|
|
277
|
+
viewname,
|
|
278
|
+
config,
|
|
279
|
+
body,
|
|
280
|
+
{ req, res }
|
|
281
|
+
) => {
|
|
282
|
+
const { _csrf, ...answers } = body;
|
|
283
|
+
const existing = await MetaData.findOne({
|
|
284
|
+
type: "CopilotConstructMgr",
|
|
285
|
+
name: "research_answers",
|
|
286
|
+
});
|
|
287
|
+
if (existing) {
|
|
288
|
+
await existing.update({ body: answers });
|
|
289
|
+
} else {
|
|
290
|
+
await MetaData.create({
|
|
291
|
+
type: "CopilotConstructMgr",
|
|
292
|
+
name: "research_answers",
|
|
293
|
+
body: answers,
|
|
294
|
+
user_id: req.user?.id,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return { json: { success: true, notify_success: "Answers saved" } };
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const del_all_research = async (
|
|
301
|
+
table_id,
|
|
302
|
+
viewname,
|
|
303
|
+
config,
|
|
304
|
+
body,
|
|
305
|
+
{ req, res }
|
|
306
|
+
) => {
|
|
307
|
+
for (const name of ["research_questions", "research_answers"]) {
|
|
308
|
+
const md = await MetaData.findOne({ type: "CopilotConstructMgr", name });
|
|
309
|
+
if (md) await md.delete();
|
|
310
|
+
}
|
|
311
|
+
return { json: { success: true } };
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const getResearchAnswersText = async () => {
|
|
315
|
+
const questions_md = await MetaData.findOne({
|
|
316
|
+
type: "CopilotConstructMgr",
|
|
317
|
+
name: "research_questions",
|
|
318
|
+
});
|
|
319
|
+
const answers_md = await MetaData.findOne({
|
|
320
|
+
type: "CopilotConstructMgr",
|
|
321
|
+
name: "research_answers",
|
|
322
|
+
});
|
|
323
|
+
if (!questions_md || !answers_md) return null;
|
|
324
|
+
const questions = questions_md.body.questions || [];
|
|
325
|
+
const answers = answers_md.body || {};
|
|
326
|
+
const pairs = questions
|
|
327
|
+
.map((q, idx) => {
|
|
328
|
+
const a = answers[`question${idx + 1}`];
|
|
329
|
+
if (!a || !a.trim()) return null;
|
|
330
|
+
return `Question: ${q}\nAnswer: ${a.trim()}`;
|
|
331
|
+
})
|
|
332
|
+
.filter(Boolean);
|
|
333
|
+
if (!pairs.length) return null;
|
|
334
|
+
return pairs.join("\n\n");
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const research_routes = {
|
|
338
|
+
gen_research,
|
|
339
|
+
research_status,
|
|
340
|
+
research_html,
|
|
341
|
+
submit_research,
|
|
342
|
+
del_all_research,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
module.exports = {
|
|
346
|
+
researchPanel,
|
|
347
|
+
research_routes,
|
|
348
|
+
getResearchAnswersText,
|
|
349
|
+
questions_tool,
|
|
350
|
+
};
|