@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
package/chat-copilot.js
DELETED
|
@@ -1,770 +0,0 @@
|
|
|
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 { findType } = require("@saltcorn/data/models/discovery");
|
|
7
|
-
const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
8
|
-
const db = require("@saltcorn/data/db");
|
|
9
|
-
const WorkflowRun = require("@saltcorn/data/models/workflow_run");
|
|
10
|
-
const { localeDateTime } = require("@saltcorn/markup");
|
|
11
|
-
const {
|
|
12
|
-
div,
|
|
13
|
-
script,
|
|
14
|
-
domReady,
|
|
15
|
-
pre,
|
|
16
|
-
code,
|
|
17
|
-
input,
|
|
18
|
-
h4,
|
|
19
|
-
style,
|
|
20
|
-
h5,
|
|
21
|
-
button,
|
|
22
|
-
text_attr,
|
|
23
|
-
i,
|
|
24
|
-
p,
|
|
25
|
-
span,
|
|
26
|
-
small,
|
|
27
|
-
form,
|
|
28
|
-
textarea,
|
|
29
|
-
} = require("@saltcorn/markup/tags");
|
|
30
|
-
const { getState } = require("@saltcorn/data/db/state");
|
|
31
|
-
const {
|
|
32
|
-
getCompletion,
|
|
33
|
-
getPromptFromTemplate,
|
|
34
|
-
incompleteCfgMsg,
|
|
35
|
-
} = require("./common");
|
|
36
|
-
const MarkdownIt = require("markdown-it"),
|
|
37
|
-
md = new MarkdownIt();
|
|
38
|
-
|
|
39
|
-
const AGENT_VIEWTEMPLATE_NAME = "Agent Chat";
|
|
40
|
-
const COPILOT_AGENT_TRIGGER_NAME = "Saltcorn Copilot";
|
|
41
|
-
|
|
42
|
-
const getAgentViewtemplate = () => {
|
|
43
|
-
const state = getState();
|
|
44
|
-
return state?.viewtemplates?.[AGENT_VIEWTEMPLATE_NAME];
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const agentActionRegistered = () => {
|
|
48
|
-
const state = getState();
|
|
49
|
-
return !!(state?.actions && (state.actions.Agent || state.actions["Agent"]));
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
let copilotAgentTriggerCreateFailed = false;
|
|
53
|
-
let copilotAgentTriggerCreateLogged = false;
|
|
54
|
-
|
|
55
|
-
const ensureCopilotAgentTrigger = async () => {
|
|
56
|
-
if (!agentActionRegistered()) return null;
|
|
57
|
-
let trigger = await Trigger.findOne({
|
|
58
|
-
name: COPILOT_AGENT_TRIGGER_NAME,
|
|
59
|
-
action: "Agent",
|
|
60
|
-
});
|
|
61
|
-
if (trigger) return trigger;
|
|
62
|
-
|
|
63
|
-
if (copilotAgentTriggerCreateFailed) return null;
|
|
64
|
-
try {
|
|
65
|
-
return await Trigger.create({
|
|
66
|
-
action: "Agent",
|
|
67
|
-
when_trigger: "Never",
|
|
68
|
-
name: COPILOT_AGENT_TRIGGER_NAME,
|
|
69
|
-
configuration: {
|
|
70
|
-
prompt: "",
|
|
71
|
-
sys_prompt:
|
|
72
|
-
"You are Saltcorn Copilot. Help users build Saltcorn applications by proposing database schema, pages, views, workflows and actions.",
|
|
73
|
-
skills: [
|
|
74
|
-
{ skill_type: "Database design" },
|
|
75
|
-
{ skill_type: "Generate Page" },
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
} catch (e) {
|
|
80
|
-
copilotAgentTriggerCreateFailed = true;
|
|
81
|
-
const state = getState();
|
|
82
|
-
const msg =
|
|
83
|
-
e && e.message
|
|
84
|
-
? e.message
|
|
85
|
-
: "Unknown error while auto-creating Copilot Agent trigger";
|
|
86
|
-
if (!copilotAgentTriggerCreateLogged) {
|
|
87
|
-
copilotAgentTriggerCreateLogged = true;
|
|
88
|
-
if (state?.log) state.log(2, `Copilot: ${msg}`);
|
|
89
|
-
else console.error("Copilot:", msg);
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const getAgentChatCfg = async () => {
|
|
96
|
-
const trigger = await ensureCopilotAgentTrigger();
|
|
97
|
-
if (!trigger) return null;
|
|
98
|
-
return {
|
|
99
|
-
action_id: trigger.id,
|
|
100
|
-
show_prev_runs: true,
|
|
101
|
-
prev_runs_closed: false,
|
|
102
|
-
placeholder: "How can I help you?",
|
|
103
|
-
explainer: "",
|
|
104
|
-
image_upload: false,
|
|
105
|
-
stream: true,
|
|
106
|
-
audio_recorder: false,
|
|
107
|
-
layout: "Vertical",
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const get_state_fields = () => [];
|
|
112
|
-
|
|
113
|
-
const runLegacy = async (table_id, viewname, cfg, state, { res, req }) => {
|
|
114
|
-
const prevRuns = (
|
|
115
|
-
await WorkflowRun.find(
|
|
116
|
-
{ trigger_id: null /*started_by: req.user?.id*/ }, //todo uncomment
|
|
117
|
-
{ orderBy: "started_at", orderDesc: true, limit: 30 }
|
|
118
|
-
)
|
|
119
|
-
).filter(
|
|
120
|
-
(r) =>
|
|
121
|
-
r.context.interactions &&
|
|
122
|
-
(r.context.copilot === "_system" || !r.context.copilot)
|
|
123
|
-
);
|
|
124
|
-
const cfgMsg = incompleteCfgMsg();
|
|
125
|
-
if (cfgMsg) return cfgMsg;
|
|
126
|
-
let runInteractions = "";
|
|
127
|
-
if (state.run_id) {
|
|
128
|
-
const run = prevRuns.find((r) => r.id == state.run_id);
|
|
129
|
-
const interactMarkups = [];
|
|
130
|
-
for (const interact of run.context.interactions) {
|
|
131
|
-
switch (interact.role) {
|
|
132
|
-
case "user":
|
|
133
|
-
interactMarkups.push(
|
|
134
|
-
div(
|
|
135
|
-
{ class: "interaction-segment" },
|
|
136
|
-
span({ class: "badge bg-secondary" }, "You"),
|
|
137
|
-
p(interact.content)
|
|
138
|
-
)
|
|
139
|
-
);
|
|
140
|
-
break;
|
|
141
|
-
case "assistant":
|
|
142
|
-
case "system":
|
|
143
|
-
if (interact.tool_calls) {
|
|
144
|
-
for (const tool_call of interact.tool_calls) {
|
|
145
|
-
const markup = await renderToolcall(
|
|
146
|
-
tool_call,
|
|
147
|
-
viewname,
|
|
148
|
-
(run.context.implemented_fcall_ids || []).includes(
|
|
149
|
-
tool_call.id
|
|
150
|
-
),
|
|
151
|
-
run
|
|
152
|
-
);
|
|
153
|
-
interactMarkups.push(
|
|
154
|
-
div(
|
|
155
|
-
{ class: "interaction-segment" },
|
|
156
|
-
span({ class: "badge bg-secondary" }, "Copilot"),
|
|
157
|
-
markup
|
|
158
|
-
)
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
} else
|
|
162
|
-
interactMarkups.push(
|
|
163
|
-
div(
|
|
164
|
-
{ class: "interaction-segment" },
|
|
165
|
-
span({ class: "badge bg-secondary" }, "Copilot"),
|
|
166
|
-
typeof interact.content === "string"
|
|
167
|
-
? md.render(interact.content)
|
|
168
|
-
: interact.content
|
|
169
|
-
)
|
|
170
|
-
);
|
|
171
|
-
break;
|
|
172
|
-
case "tool":
|
|
173
|
-
//ignore
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
runInteractions = interactMarkups.join("");
|
|
178
|
-
}
|
|
179
|
-
const input_form = form(
|
|
180
|
-
{
|
|
181
|
-
onsubmit:
|
|
182
|
-
"event.preventDefault();spin_send_button();view_post('Saltcorn Copilot', 'interact', $(this).serialize(), processCopilotResponse);return false;",
|
|
183
|
-
class: "form-namespace copilot mt-2",
|
|
184
|
-
method: "post",
|
|
185
|
-
},
|
|
186
|
-
input({
|
|
187
|
-
type: "hidden",
|
|
188
|
-
name: "_csrf",
|
|
189
|
-
value: req.csrfToken(),
|
|
190
|
-
}),
|
|
191
|
-
input({
|
|
192
|
-
type: "hidden",
|
|
193
|
-
class: "form-control ",
|
|
194
|
-
name: "run_id",
|
|
195
|
-
value: state.run_id ? +state.run_id : undefined,
|
|
196
|
-
}),
|
|
197
|
-
div(
|
|
198
|
-
{ class: "copilot-entry" },
|
|
199
|
-
textarea({
|
|
200
|
-
class: "form-control",
|
|
201
|
-
name: "userinput",
|
|
202
|
-
"data-fieldname": "userinput",
|
|
203
|
-
placeholder: "How can I help you?",
|
|
204
|
-
id: "inputuserinput",
|
|
205
|
-
rows: "3",
|
|
206
|
-
autofocus: true,
|
|
207
|
-
}),
|
|
208
|
-
span(
|
|
209
|
-
{ class: "submit-button p-2", onclick: "$('form.copilot').submit()" },
|
|
210
|
-
i({ id: "sendbuttonicon", class: "far fa-paper-plane" })
|
|
211
|
-
)
|
|
212
|
-
),
|
|
213
|
-
|
|
214
|
-
i(
|
|
215
|
-
small(
|
|
216
|
-
"Skills you can request: " +
|
|
217
|
-
classesWithSkills()
|
|
218
|
-
.map((ac) => ac.title)
|
|
219
|
-
.join(", ")
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
);
|
|
223
|
-
return {
|
|
224
|
-
widths: [3, 9],
|
|
225
|
-
gx: 3,
|
|
226
|
-
besides: [
|
|
227
|
-
{
|
|
228
|
-
type: "container",
|
|
229
|
-
contents: div(
|
|
230
|
-
div(
|
|
231
|
-
{
|
|
232
|
-
class: "d-flex justify-content-between align-middle mb-2",
|
|
233
|
-
},
|
|
234
|
-
h5("Sessions"),
|
|
235
|
-
|
|
236
|
-
button(
|
|
237
|
-
{
|
|
238
|
-
type: "button",
|
|
239
|
-
class: "btn btn-secondary btn-sm py-0",
|
|
240
|
-
style: "font-size: 0.9em;height:1.5em",
|
|
241
|
-
onclick: "unset_state_field('run_id')",
|
|
242
|
-
title: "New session",
|
|
243
|
-
},
|
|
244
|
-
i({ class: "fas fa-redo fa-sm" })
|
|
245
|
-
)
|
|
246
|
-
),
|
|
247
|
-
prevRuns.map((run) =>
|
|
248
|
-
div(
|
|
249
|
-
{
|
|
250
|
-
onclick: `set_state_field('run_id',${run.id})`,
|
|
251
|
-
class: "prevcopilotrun border p-2",
|
|
252
|
-
},
|
|
253
|
-
localeDateTime(run.started_at),
|
|
254
|
-
|
|
255
|
-
p(
|
|
256
|
-
{ class: "prevrun_content" },
|
|
257
|
-
run.context.interactions[0]?.content
|
|
258
|
-
)
|
|
259
|
-
)
|
|
260
|
-
)
|
|
261
|
-
),
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
type: "container",
|
|
265
|
-
contents: div(
|
|
266
|
-
{ class: "card" },
|
|
267
|
-
div(
|
|
268
|
-
{ class: "card-body" },
|
|
269
|
-
script({
|
|
270
|
-
src: `/static_assets/${db.connectObj.version_tag}/mermaid.min.js`,
|
|
271
|
-
}),
|
|
272
|
-
script(
|
|
273
|
-
{ type: "module" },
|
|
274
|
-
`mermaid.initialize({securityLevel: 'loose'${
|
|
275
|
-
getState().getLightDarkMode(req.user) === "dark"
|
|
276
|
-
? ",theme: 'dark',"
|
|
277
|
-
: ""
|
|
278
|
-
}});`
|
|
279
|
-
),
|
|
280
|
-
div({ id: "copilotinteractions" }, runInteractions),
|
|
281
|
-
input_form,
|
|
282
|
-
style(
|
|
283
|
-
`div.interaction-segment:not(:first-child) {border-top: 1px solid #e7e7e7; }
|
|
284
|
-
div.interaction-segment {padding-top: 5px;padding-bottom: 5px;}
|
|
285
|
-
div.interaction-segment p {margin-bottom: 0px;}
|
|
286
|
-
div.interaction-segment div.card {margin-top: 0.5rem;}
|
|
287
|
-
div.prevcopilotrun:hover {cursor: pointer; background-color: var(--tblr-secondary-bg-subtle, var(--bs-secondary-bg-subtle, gray));}
|
|
288
|
-
.copilot-entry .submit-button:hover { cursor: pointer}
|
|
289
|
-
|
|
290
|
-
.copilot-entry .submit-button {
|
|
291
|
-
position: relative;
|
|
292
|
-
top: -1.8rem;
|
|
293
|
-
left: 0.1rem;
|
|
294
|
-
}
|
|
295
|
-
.copilot-entry {margin-bottom: -1.25rem; margin-top: 1rem;}
|
|
296
|
-
p.prevrun_content {
|
|
297
|
-
white-space: nowrap;
|
|
298
|
-
overflow: hidden;
|
|
299
|
-
margin-bottom: 0px;
|
|
300
|
-
display: block;
|
|
301
|
-
text-overflow: ellipsis;}`
|
|
302
|
-
),
|
|
303
|
-
script(`function processCopilotResponse(res) {
|
|
304
|
-
$("#sendbuttonicon").attr("class","far fa-paper-plane");
|
|
305
|
-
const $runidin= $("input[name=run_id")
|
|
306
|
-
if(res.run_id && (!$runidin.val() || $runidin.val()=="undefined"))
|
|
307
|
-
$runidin.val(res.run_id);
|
|
308
|
-
const wrapSegment = (html, who) => '<div class="interaction-segment"><span class="badge bg-secondary">'+who+'</span>'+html+'</div>'
|
|
309
|
-
$("#copilotinteractions").append(wrapSegment('<p>'+$("textarea[name=userinput]").val()+'</p>', "You"))
|
|
310
|
-
$("textarea[name=userinput]").val("")
|
|
311
|
-
|
|
312
|
-
for(const action of res.actions||[]) {
|
|
313
|
-
$("#copilotinteractions").append(wrapSegment(action, "Copilot"))
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if(res.response)
|
|
318
|
-
$("#copilotinteractions").append(wrapSegment('<p>'+res.response+'</p>', "Copilot"))
|
|
319
|
-
}
|
|
320
|
-
function restore_old_button_elem(btn) {
|
|
321
|
-
const oldText = $(btn).data("old-text");
|
|
322
|
-
btn.html(oldText);
|
|
323
|
-
btn.css({ width: "" }).prop("disabled", false);
|
|
324
|
-
btn.removeData("old-text");
|
|
325
|
-
}
|
|
326
|
-
function processExecuteResponse(res) {
|
|
327
|
-
const btn = $("#exec-"+res.fcall_id)
|
|
328
|
-
restore_old_button_elem($("#exec-"+res.fcall_id))
|
|
329
|
-
btn.prop('disabled', true);
|
|
330
|
-
btn.html('<i class="fas fa-check me-1"></i>Applied')
|
|
331
|
-
btn.removeClass("btn-primary")
|
|
332
|
-
btn.addClass("btn-secondary")
|
|
333
|
-
if(res.postExec) {
|
|
334
|
-
$('#postexec-'+res.fcall_id).html(res.postExec)
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
function submitOnEnter(event) {
|
|
338
|
-
if (event.which === 13) {
|
|
339
|
-
if (!event.repeat) {
|
|
340
|
-
const newEvent = new Event("submit", {cancelable: true});
|
|
341
|
-
event.target.form.dispatchEvent(newEvent);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
event.preventDefault(); // Prevents the addition of a new line in the text field
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
document.getElementById("inputuserinput").addEventListener("keydown", submitOnEnter);
|
|
348
|
-
function spin_send_button() {
|
|
349
|
-
$("#sendbuttonicon").attr("class","fas fa-spinner fa-spin");
|
|
350
|
-
}
|
|
351
|
-
`)
|
|
352
|
-
)
|
|
353
|
-
),
|
|
354
|
-
},
|
|
355
|
-
],
|
|
356
|
-
};
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
const run = async (table_id, viewname, cfg, state, extra) => {
|
|
360
|
-
const agentVt = getAgentViewtemplate();
|
|
361
|
-
if (!agentVt) return await runLegacy(table_id, viewname, cfg, state, extra);
|
|
362
|
-
|
|
363
|
-
const agentCfg = await getAgentChatCfg();
|
|
364
|
-
if (!agentCfg) return await runLegacy(table_id, viewname, cfg, state, extra);
|
|
365
|
-
|
|
366
|
-
const agentView = new View({
|
|
367
|
-
name: viewname,
|
|
368
|
-
viewtemplate: AGENT_VIEWTEMPLATE_NAME,
|
|
369
|
-
configuration: agentCfg,
|
|
370
|
-
min_role: 100,
|
|
371
|
-
});
|
|
372
|
-
return await agentView.run(state, extra);
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
const ellipsize = (s, nchars) => {
|
|
376
|
-
if (!s || !s.length) return "";
|
|
377
|
-
if (s.length <= (nchars || 20)) return text_attr(s);
|
|
378
|
-
return text_attr(s.substr(0, (nchars || 20) - 3)) + "...";
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const actionClasses = [
|
|
382
|
-
require("./actions/generate-workflow"),
|
|
383
|
-
require("./actions/generate-tables"),
|
|
384
|
-
require("./actions/generate-js-action"),
|
|
385
|
-
require("./actions/generate-trigger"),
|
|
386
|
-
require("./actions/generate-page"),
|
|
387
|
-
require("./actions/generate-view"),
|
|
388
|
-
];
|
|
389
|
-
|
|
390
|
-
const classesWithSkills = () => {
|
|
391
|
-
const state = getState();
|
|
392
|
-
const skills = state.copilot_skills || [];
|
|
393
|
-
return [...actionClasses, ...skills];
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
const getCompletionArguments = async () => {
|
|
397
|
-
const tools = [];
|
|
398
|
-
const sysPrompts = [];
|
|
399
|
-
for (const actionClass of classesWithSkills()) {
|
|
400
|
-
tools.push({
|
|
401
|
-
type: "function",
|
|
402
|
-
function: {
|
|
403
|
-
name: actionClass.function_name,
|
|
404
|
-
description: actionClass.description,
|
|
405
|
-
parameters: await actionClass.json_schema(),
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
sysPrompts.push(await actionClass.system_prompt());
|
|
409
|
-
}
|
|
410
|
-
const systemPrompt =
|
|
411
|
-
"You are building application components in a database application builder called Saltcorn.\n\n" +
|
|
412
|
-
sysPrompts.join("\n\n");
|
|
413
|
-
return { tools, systemPrompt };
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
/*
|
|
417
|
-
|
|
418
|
-
build a workflow that asks the user for their name and age
|
|
419
|
-
|
|
420
|
-
*/
|
|
421
|
-
|
|
422
|
-
const executeLegacy = async (table_id, viewname, config, body, { req }) => {
|
|
423
|
-
const { fcall_id, run_id } = body;
|
|
424
|
-
|
|
425
|
-
const run = await WorkflowRun.findOne({ id: +run_id });
|
|
426
|
-
|
|
427
|
-
const fcall = run.context.funcalls[fcall_id];
|
|
428
|
-
const actionClass = classesWithSkills().find(
|
|
429
|
-
(ac) => ac.function_name === (fcall.name || fcall.toolName)
|
|
430
|
-
);
|
|
431
|
-
let result;
|
|
432
|
-
const args = fcall.arguments ? JSON.parse(fcall.arguments) : fcall.input;
|
|
433
|
-
|
|
434
|
-
if (actionClass.follow_on_generate) {
|
|
435
|
-
const toolCallIndex = run.context.interactions.findIndex(
|
|
436
|
-
(i) =>
|
|
437
|
-
i.tool_call_id === fcall_id ||
|
|
438
|
-
(Array.isArray(i.content) &&
|
|
439
|
-
i.content.some((c) => c.toolCallId === fcall_id))
|
|
440
|
-
);
|
|
441
|
-
const follow_on_gen = run.context.interactions.find(
|
|
442
|
-
(i, ix) => i.role === "assistant" && ix > toolCallIndex
|
|
443
|
-
);
|
|
444
|
-
result = await actionClass.execute(args, req, follow_on_gen.content);
|
|
445
|
-
} else {
|
|
446
|
-
result = await actionClass.execute(args, req);
|
|
447
|
-
}
|
|
448
|
-
await addToContext(run, { implemented_fcall_ids: [fcall_id] });
|
|
449
|
-
return { json: { success: "ok", fcall_id, ...(result || {}) } };
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
const interactLegacy = async (table_id, viewname, config, body, { req }) => {
|
|
453
|
-
const { userinput, run_id } = body;
|
|
454
|
-
let run;
|
|
455
|
-
if (!run_id || run_id === "undefined")
|
|
456
|
-
run = await WorkflowRun.create({
|
|
457
|
-
status: "Running",
|
|
458
|
-
started_by: req.user?.id,
|
|
459
|
-
context: {
|
|
460
|
-
copilot: "_system",
|
|
461
|
-
implemented_fcall_ids: [],
|
|
462
|
-
interactions: [{ role: "user", content: userinput }],
|
|
463
|
-
funcalls: {},
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
else {
|
|
467
|
-
run = await WorkflowRun.findOne({ id: +run_id });
|
|
468
|
-
await addToContext(run, {
|
|
469
|
-
interactions: [{ role: "user", content: userinput }],
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
const complArgs = await getCompletionArguments();
|
|
473
|
-
complArgs.chat = run.context.interactions;
|
|
474
|
-
//console.log(complArgs);
|
|
475
|
-
|
|
476
|
-
//build a database for a bicycle rental company
|
|
477
|
-
//add a boolean field called "paid" to the payments table
|
|
478
|
-
|
|
479
|
-
const answer = await getState().functions.llm_generate.run(
|
|
480
|
-
userinput,
|
|
481
|
-
complArgs
|
|
482
|
-
);
|
|
483
|
-
if (answer.ai_sdk)
|
|
484
|
-
for (const tool_call of answer.tool_calls)
|
|
485
|
-
await addToContext(run, {
|
|
486
|
-
interactions: [
|
|
487
|
-
...answer.messages,
|
|
488
|
-
{
|
|
489
|
-
role: "tool",
|
|
490
|
-
content: [
|
|
491
|
-
{
|
|
492
|
-
type: "tool-result",
|
|
493
|
-
toolCallId: tool_call.toolCallId,
|
|
494
|
-
toolName: tool_call.toolName,
|
|
495
|
-
output: {
|
|
496
|
-
type: "text",
|
|
497
|
-
value: "Action suggested to user.",
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
],
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
});
|
|
504
|
-
else
|
|
505
|
-
await addToContext(run, {
|
|
506
|
-
interactions:
|
|
507
|
-
typeof answer === "object" && answer.tool_calls
|
|
508
|
-
? [
|
|
509
|
-
{ role: "assistant", tool_calls: answer.tool_calls },
|
|
510
|
-
...answer.tool_calls.map((tc) => ({
|
|
511
|
-
role: "tool",
|
|
512
|
-
tool_call_id: tc.id || tc.toolCallId,
|
|
513
|
-
name: tc.function?.name || tc.toolName,
|
|
514
|
-
content: "Action suggested to user.",
|
|
515
|
-
})),
|
|
516
|
-
]
|
|
517
|
-
: [{ role: "assistant", content: answer }],
|
|
518
|
-
});
|
|
519
|
-
if (typeof answer === "object" && answer.tool_calls) {
|
|
520
|
-
const actions = [];
|
|
521
|
-
for (const tool_call of answer.tool_calls) {
|
|
522
|
-
await addToContext(run, {
|
|
523
|
-
funcalls: {
|
|
524
|
-
[tool_call.id || tool_call.toolCallId]:
|
|
525
|
-
tool_call.function || tool_call,
|
|
526
|
-
},
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
const followOnGen = await getFollowOnGeneration(tool_call);
|
|
530
|
-
if (followOnGen) {
|
|
531
|
-
const { response_schema, prompt } = followOnGen;
|
|
532
|
-
const follow_on_answer = await getState().functions.llm_generate.run(
|
|
533
|
-
prompt,
|
|
534
|
-
{
|
|
535
|
-
debugResult: true,
|
|
536
|
-
chat: run.context.interactions,
|
|
537
|
-
response_format: response_schema
|
|
538
|
-
? {
|
|
539
|
-
type: "json_schema",
|
|
540
|
-
json_schema: {
|
|
541
|
-
name: "generate_page",
|
|
542
|
-
schema: response_schema,
|
|
543
|
-
},
|
|
544
|
-
}
|
|
545
|
-
: undefined,
|
|
546
|
-
}
|
|
547
|
-
);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
await addToContext(run, {
|
|
551
|
-
interactions: [
|
|
552
|
-
{ role: "user", content: prompt },
|
|
553
|
-
{ role: "assistant", content: follow_on_answer },
|
|
554
|
-
],
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
const markup = await renderToolcall(
|
|
558
|
-
tool_call,
|
|
559
|
-
viewname,
|
|
560
|
-
false,
|
|
561
|
-
run,
|
|
562
|
-
follow_on_answer
|
|
563
|
-
);
|
|
564
|
-
|
|
565
|
-
actions.push(markup);
|
|
566
|
-
} else {
|
|
567
|
-
const markup = await renderToolcall(tool_call, viewname, false, run);
|
|
568
|
-
|
|
569
|
-
actions.push(markup);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return { json: { success: "ok", actions, run_id: run.id } };
|
|
573
|
-
} else
|
|
574
|
-
return {
|
|
575
|
-
json: { success: "ok", response: md.render(answer), run_id: run.id },
|
|
576
|
-
};
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
const runAgentRoute = async (routeName, table_id, viewname, body, extra) => {
|
|
580
|
-
const agentVt = getAgentViewtemplate();
|
|
581
|
-
if (!agentVt?.routes?.[routeName]) return null;
|
|
582
|
-
const agentCfg = await getAgentChatCfg();
|
|
583
|
-
if (!agentCfg) return null;
|
|
584
|
-
return await agentVt.routes[routeName](table_id, viewname, agentCfg, body, extra);
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
const interact = async (table_id, viewname, config, body, extra) => {
|
|
588
|
-
const agentResp = await runAgentRoute(
|
|
589
|
-
"interact",
|
|
590
|
-
table_id,
|
|
591
|
-
viewname,
|
|
592
|
-
body,
|
|
593
|
-
extra,
|
|
594
|
-
);
|
|
595
|
-
if (agentResp) return agentResp;
|
|
596
|
-
return await interactLegacy(table_id, viewname, config, body, extra);
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
// Legacy route used only by the legacy UI. The Agent Chat UI uses `execute_user_action`.
|
|
600
|
-
const execute = async (table_id, viewname, config, body, extra) => {
|
|
601
|
-
return await executeLegacy(table_id, viewname, config, body, extra);
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
const delprevrun = async (table_id, viewname, config, body, extra) => {
|
|
605
|
-
const agentResp = await runAgentRoute(
|
|
606
|
-
"delprevrun",
|
|
607
|
-
table_id,
|
|
608
|
-
viewname,
|
|
609
|
-
body,
|
|
610
|
-
extra,
|
|
611
|
-
);
|
|
612
|
-
if (agentResp) return agentResp;
|
|
613
|
-
return { json: { error: "delprevrun is only available in Agent Chat mode" } };
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const debug_info = async (table_id, viewname, config, body, extra) => {
|
|
617
|
-
const agentResp = await runAgentRoute(
|
|
618
|
-
"debug_info",
|
|
619
|
-
table_id,
|
|
620
|
-
viewname,
|
|
621
|
-
body,
|
|
622
|
-
extra,
|
|
623
|
-
);
|
|
624
|
-
if (agentResp) return agentResp;
|
|
625
|
-
return { json: { error: "debug_info is only available in Agent Chat mode" } };
|
|
626
|
-
};
|
|
627
|
-
|
|
628
|
-
const skillroute = async (table_id, viewname, config, body, extra) => {
|
|
629
|
-
const agentResp = await runAgentRoute(
|
|
630
|
-
"skillroute",
|
|
631
|
-
table_id,
|
|
632
|
-
viewname,
|
|
633
|
-
body,
|
|
634
|
-
extra,
|
|
635
|
-
);
|
|
636
|
-
if (agentResp) return agentResp;
|
|
637
|
-
return { json: { error: "skillroute is only available in Agent Chat mode" } };
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
const execute_user_action = async (table_id, viewname, config, body, extra) => {
|
|
641
|
-
const agentResp = await runAgentRoute(
|
|
642
|
-
"execute_user_action",
|
|
643
|
-
table_id,
|
|
644
|
-
viewname,
|
|
645
|
-
body,
|
|
646
|
-
extra,
|
|
647
|
-
);
|
|
648
|
-
if (agentResp) return agentResp;
|
|
649
|
-
return {
|
|
650
|
-
json: { error: "execute_user_action is only available in Agent Chat mode" },
|
|
651
|
-
};
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
const getFollowOnGeneration = async (tool_call) => {
|
|
655
|
-
const fname = tool_call.function?.name || tool_call.toolName;
|
|
656
|
-
const actionClass = classesWithSkills().find(
|
|
657
|
-
(ac) => ac.function_name === fname
|
|
658
|
-
);
|
|
659
|
-
const args = tool_call.function?.arguments
|
|
660
|
-
? JSON.parse(tool_call.function?.arguments)
|
|
661
|
-
: tool_call.input;
|
|
662
|
-
|
|
663
|
-
if (actionClass.follow_on_generate) {
|
|
664
|
-
return await actionClass.follow_on_generate(args);
|
|
665
|
-
} else return null;
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const renderToolcall = async (
|
|
669
|
-
tool_call,
|
|
670
|
-
viewname,
|
|
671
|
-
implemented,
|
|
672
|
-
run,
|
|
673
|
-
follow_on_answer
|
|
674
|
-
) => {
|
|
675
|
-
const fname = tool_call.function?.name || tool_call.toolName;
|
|
676
|
-
const actionClass = classesWithSkills().find(
|
|
677
|
-
(ac) => ac.function_name === fname
|
|
678
|
-
);
|
|
679
|
-
const args = tool_call.function?.arguments
|
|
680
|
-
? JSON.parse(tool_call.function.arguments)
|
|
681
|
-
: tool_call.input;
|
|
682
|
-
|
|
683
|
-
const inner_markup = await actionClass.render_html(args, follow_on_answer);
|
|
684
|
-
return wrapAction(
|
|
685
|
-
inner_markup,
|
|
686
|
-
viewname,
|
|
687
|
-
tool_call,
|
|
688
|
-
actionClass,
|
|
689
|
-
implemented,
|
|
690
|
-
run
|
|
691
|
-
);
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
const wrapAction = (
|
|
695
|
-
inner_markup,
|
|
696
|
-
viewname,
|
|
697
|
-
tool_call,
|
|
698
|
-
actionClass,
|
|
699
|
-
implemented,
|
|
700
|
-
run
|
|
701
|
-
) =>
|
|
702
|
-
span({ class: "badge bg-info ms-1" }, actionClass.title) +
|
|
703
|
-
div(
|
|
704
|
-
{ class: "card mb-3 bg-secondary-subtle" },
|
|
705
|
-
div(
|
|
706
|
-
{ class: "card-body" },
|
|
707
|
-
inner_markup,
|
|
708
|
-
implemented
|
|
709
|
-
? button(
|
|
710
|
-
{
|
|
711
|
-
type: "button",
|
|
712
|
-
class: "btn btn-secondary d-block mt-3 float-end",
|
|
713
|
-
disabled: true,
|
|
714
|
-
},
|
|
715
|
-
i({ class: "fas fa-check me-1" }),
|
|
716
|
-
"Applied"
|
|
717
|
-
)
|
|
718
|
-
: button(
|
|
719
|
-
{
|
|
720
|
-
type: "button",
|
|
721
|
-
id: "exec-" + tool_call.id,
|
|
722
|
-
class: "btn btn-primary d-block mt-3 float-end",
|
|
723
|
-
onclick: `press_store_button(this, true);view_post('${viewname}', 'execute', {fcall_id: '${
|
|
724
|
-
tool_call.id || tool_call.toolCallId
|
|
725
|
-
}', run_id: ${run.id}}, processExecuteResponse)`,
|
|
726
|
-
},
|
|
727
|
-
"Apply"
|
|
728
|
-
),
|
|
729
|
-
div({ id: "postexec-" + tool_call.id })
|
|
730
|
-
)
|
|
731
|
-
);
|
|
732
|
-
|
|
733
|
-
const addToContext = async (run, newCtx) => {
|
|
734
|
-
if (run.addToContext) return await run.addToContext(newCtx);
|
|
735
|
-
let changed = true;
|
|
736
|
-
Object.keys(newCtx).forEach((k) => {
|
|
737
|
-
if (Array.isArray(run.context[k])) {
|
|
738
|
-
if (!Array.isArray(newCtx[k]))
|
|
739
|
-
throw new Error("Must be array to append to array");
|
|
740
|
-
run.context[k].push(...newCtx[k]);
|
|
741
|
-
changed = true;
|
|
742
|
-
} else if (typeof run.context[k] === "object") {
|
|
743
|
-
if (typeof newCtx[k] !== "object")
|
|
744
|
-
throw new Error("Must be object to append to object");
|
|
745
|
-
Object.assign(run.context[k], newCtx[k]);
|
|
746
|
-
changed = true;
|
|
747
|
-
} else {
|
|
748
|
-
run.context[k] = newCtx[k];
|
|
749
|
-
changed = true;
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
if (changed) await run.update({ context: run.context });
|
|
753
|
-
};
|
|
754
|
-
|
|
755
|
-
module.exports = {
|
|
756
|
-
name: "Saltcorn Copilot",
|
|
757
|
-
display_state_form: false,
|
|
758
|
-
get_state_fields,
|
|
759
|
-
tableless: true,
|
|
760
|
-
singleton: true,
|
|
761
|
-
run,
|
|
762
|
-
routes: {
|
|
763
|
-
interact,
|
|
764
|
-
execute,
|
|
765
|
-
delprevrun,
|
|
766
|
-
debug_info,
|
|
767
|
-
skillroute,
|
|
768
|
-
execute_user_action,
|
|
769
|
-
},
|
|
770
|
-
};
|