@saltcorn/copilot 0.2.0 → 0.3.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/action-builder.js +5 -1
- package/actions/generate-js-action.js +108 -0
- package/actions/generate-tables.js +260 -0
- package/actions/generate-workflow.js +320 -0
- package/chat-copilot.js +471 -0
- package/common.js +47 -1
- package/index.js +3 -1
- package/package.json +2 -2
- package/prompts/action-builder.txt +1 -5
- package/workflow-gen.js +4 -258
- package/GenWorkflowCodePage.js +0 -333
package/chat-copilot.js
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
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
|
+
|
|
37
|
+
const get_state_fields = () => [];
|
|
38
|
+
|
|
39
|
+
const run = async (table_id, viewname, cfg, state, { res, req }) => {
|
|
40
|
+
const prevRuns = (
|
|
41
|
+
await WorkflowRun.find(
|
|
42
|
+
{ trigger_id: null },
|
|
43
|
+
{ orderBy: "started_at", orderDesc: true, limit: 30 }
|
|
44
|
+
)
|
|
45
|
+
).filter((r) => r.context.interactions);
|
|
46
|
+
const cfgMsg = incompleteCfgMsg();
|
|
47
|
+
if (cfgMsg) return cfgMsg;
|
|
48
|
+
let runInteractions = "";
|
|
49
|
+
if (state.run_id) {
|
|
50
|
+
const run = prevRuns.find((r) => r.id == state.run_id);
|
|
51
|
+
const interactMarkups = [];
|
|
52
|
+
for (const interact of run.context.interactions) {
|
|
53
|
+
switch (interact.role) {
|
|
54
|
+
case "user":
|
|
55
|
+
interactMarkups.push(
|
|
56
|
+
div(
|
|
57
|
+
{ class: "interaction-segment" },
|
|
58
|
+
span({ class: "badge bg-secondary" }, "You"),
|
|
59
|
+
p(interact.content)
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
break;
|
|
63
|
+
case "assistant":
|
|
64
|
+
case "system":
|
|
65
|
+
if (interact.tool_calls) {
|
|
66
|
+
for (const tool_call of interact.tool_calls) {
|
|
67
|
+
const markup = await renderToolcall(
|
|
68
|
+
tool_call,
|
|
69
|
+
viewname,
|
|
70
|
+
(run.context.implemented_fcall_ids || []).includes(
|
|
71
|
+
tool_call.id
|
|
72
|
+
),
|
|
73
|
+
run
|
|
74
|
+
);
|
|
75
|
+
interactMarkups.push(
|
|
76
|
+
div(
|
|
77
|
+
{ class: "interaction-segment" },
|
|
78
|
+
span({ class: "badge bg-secondary" }, "Copilot"),
|
|
79
|
+
markup
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} else
|
|
84
|
+
interactMarkups.push(
|
|
85
|
+
div(
|
|
86
|
+
{ class: "interaction-segment" },
|
|
87
|
+
span({ class: "badge bg-secondary" }, "Copilot"),
|
|
88
|
+
p(interact.content)
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
break;
|
|
92
|
+
case "tool":
|
|
93
|
+
//ignore
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
runInteractions = interactMarkups.join("");
|
|
98
|
+
}
|
|
99
|
+
const input_form = form(
|
|
100
|
+
{
|
|
101
|
+
onsubmit:
|
|
102
|
+
"event.preventDefault();spin_send_button();view_post('Saltcorn Copilot', 'interact', $(this).serialize(), processCopilotResponse);return false;",
|
|
103
|
+
class: "form-namespace copilot mt-2",
|
|
104
|
+
method: "post",
|
|
105
|
+
},
|
|
106
|
+
input({
|
|
107
|
+
type: "hidden",
|
|
108
|
+
name: "_csrf",
|
|
109
|
+
value: req.csrfToken(),
|
|
110
|
+
}),
|
|
111
|
+
input({
|
|
112
|
+
type: "hidden",
|
|
113
|
+
class: "form-control ",
|
|
114
|
+
name: "run_id",
|
|
115
|
+
value: state.run_id ? +state.run_id : undefined,
|
|
116
|
+
}),
|
|
117
|
+
div(
|
|
118
|
+
{ class: "copilot-entry" },
|
|
119
|
+
textarea({
|
|
120
|
+
class: "form-control",
|
|
121
|
+
name: "userinput",
|
|
122
|
+
"data-fieldname": "userinput",
|
|
123
|
+
placeholder: "How can I help you?",
|
|
124
|
+
id: "inputuserinput",
|
|
125
|
+
rows: "3",
|
|
126
|
+
autofocus: true,
|
|
127
|
+
}),
|
|
128
|
+
span(
|
|
129
|
+
{ class: "submit-button p-2", onclick: "$('form.copilot').submit()" },
|
|
130
|
+
i({ id: "sendbuttonicon", class: "far fa-paper-plane" })
|
|
131
|
+
)
|
|
132
|
+
),
|
|
133
|
+
|
|
134
|
+
i(
|
|
135
|
+
small(
|
|
136
|
+
"Skills you can request: " +
|
|
137
|
+
actionClasses.map((ac) => ac.title).join(", ")
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
widths: [3, 9],
|
|
143
|
+
gx: 3,
|
|
144
|
+
besides: [
|
|
145
|
+
{
|
|
146
|
+
type: "container",
|
|
147
|
+
contents: div(
|
|
148
|
+
div(
|
|
149
|
+
{
|
|
150
|
+
class: "d-flex justify-content-between align-middle mb-2",
|
|
151
|
+
},
|
|
152
|
+
h5("Sessions"),
|
|
153
|
+
|
|
154
|
+
button(
|
|
155
|
+
{
|
|
156
|
+
type: "button",
|
|
157
|
+
class: "btn btn-secondary btn-sm",
|
|
158
|
+
onclick: "unset_state_field('run_id')",
|
|
159
|
+
title: "New session",
|
|
160
|
+
},
|
|
161
|
+
i({ class: "fas fa-redo" })
|
|
162
|
+
)
|
|
163
|
+
),
|
|
164
|
+
prevRuns.map((run) =>
|
|
165
|
+
div(
|
|
166
|
+
{
|
|
167
|
+
onclick: `set_state_field('run_id',${run.id})`,
|
|
168
|
+
class: "prevcopilotrun",
|
|
169
|
+
},
|
|
170
|
+
localeDateTime(run.started_at),
|
|
171
|
+
|
|
172
|
+
p(
|
|
173
|
+
{ class: "prevrun_content" },
|
|
174
|
+
run.context.interactions[0]?.content
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
),
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: "container",
|
|
182
|
+
contents: div(
|
|
183
|
+
{ class: "card" },
|
|
184
|
+
div(
|
|
185
|
+
{ class: "card-body" },
|
|
186
|
+
script({
|
|
187
|
+
src: `/static_assets/${db.connectObj.version_tag}/mermaid.min.js`,
|
|
188
|
+
}),
|
|
189
|
+
script(
|
|
190
|
+
{ type: "module" },
|
|
191
|
+
`mermaid.initialize({securityLevel: 'loose'${
|
|
192
|
+
getState().getLightDarkMode(req.user) === "dark"
|
|
193
|
+
? ",theme: 'dark',"
|
|
194
|
+
: ""
|
|
195
|
+
}});`
|
|
196
|
+
),
|
|
197
|
+
div({ id: "copilotinteractions" }, runInteractions),
|
|
198
|
+
input_form,
|
|
199
|
+
style(
|
|
200
|
+
`div.interaction-segment:not(:first-child) {border-top: 1px solid #e7e7e7; }
|
|
201
|
+
div.interaction-segment {padding-top: 5px;padding-bottom: 5px;}
|
|
202
|
+
div.interaction-segment p {margin-bottom: 0px;}
|
|
203
|
+
div.interaction-segment div.card {margin-top: 0.5rem;}
|
|
204
|
+
div.prevcopilotrun {border-top: 1px solid #e7e7e7;border-right: 1px solid #e7e7e7;padding-top:3px; padding-bottom:3px; padding-right: 1rem;}
|
|
205
|
+
div.prevcopilotrun:hover {cursor: pointer}
|
|
206
|
+
.copilot-entry .submit-button:hover { cursor: pointer}
|
|
207
|
+
|
|
208
|
+
.copilot-entry .submit-button {
|
|
209
|
+
position: relative;
|
|
210
|
+
top: -1.8rem;
|
|
211
|
+
left: 0.1rem;
|
|
212
|
+
}
|
|
213
|
+
.copilot-entry {margin-bottom: -1.25rem; margin-top: 1rem;}
|
|
214
|
+
p.prevrun_content {
|
|
215
|
+
white-space: nowrap;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
margin-bottom: 0px;
|
|
218
|
+
display: block;
|
|
219
|
+
text-overflow: ellipsis;}`
|
|
220
|
+
),
|
|
221
|
+
script(`function processCopilotResponse(res) {
|
|
222
|
+
$("#sendbuttonicon").attr("class","far fa-paper-plane");
|
|
223
|
+
const $runidin= $("input[name=run_id")
|
|
224
|
+
if(res.run_id && (!$runidin.val() || $runidin.val()=="undefined"))
|
|
225
|
+
$runidin.val(res.run_id);
|
|
226
|
+
const wrapSegment = (html, who) => '<div class="interaction-segment"><span class="badge bg-secondary">'+who+'</span>'+html+'</div>'
|
|
227
|
+
$("#copilotinteractions").append(wrapSegment('<p>'+$("textarea[name=userinput]").val()+'</p>', "You"))
|
|
228
|
+
$("textarea[name=userinput]").val("")
|
|
229
|
+
|
|
230
|
+
for(const action of res.actions||[]) {
|
|
231
|
+
$("#copilotinteractions").append(wrapSegment(action, "Copilot"))
|
|
232
|
+
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if(res.response)
|
|
236
|
+
$("#copilotinteractions").append(wrapSegment('<p>'+res.response+'</p>', "Copilot"))
|
|
237
|
+
}
|
|
238
|
+
function restore_old_button_elem(btn) {
|
|
239
|
+
const oldText = $(btn).data("old-text");
|
|
240
|
+
btn.html(oldText);
|
|
241
|
+
btn.css({ width: "" });
|
|
242
|
+
btn.removeData("old-text");
|
|
243
|
+
}
|
|
244
|
+
function processExecuteResponse(res) {
|
|
245
|
+
const btn = $("#exec-"+res.fcall_id)
|
|
246
|
+
restore_old_button_elem($("#exec-"+res.fcall_id))
|
|
247
|
+
btn.prop('disabled', true);
|
|
248
|
+
btn.html('<i class="fas fa-check me-1"></i>Applied')
|
|
249
|
+
btn.removeClass("btn-primary")
|
|
250
|
+
btn.addClass("btn-secondary")
|
|
251
|
+
if(res.postExec) {
|
|
252
|
+
$('#postexec-'+res.fcall_id).html(res.postExec)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function submitOnEnter(event) {
|
|
256
|
+
if (event.which === 13) {
|
|
257
|
+
if (!event.repeat) {
|
|
258
|
+
const newEvent = new Event("submit", {cancelable: true});
|
|
259
|
+
event.target.form.dispatchEvent(newEvent);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
event.preventDefault(); // Prevents the addition of a new line in the text field
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
document.getElementById("inputuserinput").addEventListener("keydown", submitOnEnter);
|
|
266
|
+
function spin_send_button() {
|
|
267
|
+
$("#sendbuttonicon").attr("class","fas fa-spinner fa-spin");
|
|
268
|
+
}
|
|
269
|
+
`)
|
|
270
|
+
)
|
|
271
|
+
),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const ellipsize = (s, nchars) => {
|
|
278
|
+
if (!s || !s.length) return "";
|
|
279
|
+
if (s.length <= (nchars || 20)) return text_attr(s);
|
|
280
|
+
return text_attr(s.substr(0, (nchars || 20) - 3)) + "...";
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const actionClasses = [
|
|
284
|
+
require("./actions/generate-workflow"),
|
|
285
|
+
require("./actions/generate-tables"),
|
|
286
|
+
require("./actions/generate-js-action"),
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const getCompletionArguments = async () => {
|
|
290
|
+
const tools = [];
|
|
291
|
+
const sysPrompts = [];
|
|
292
|
+
for (const actionClass of actionClasses) {
|
|
293
|
+
tools.push({
|
|
294
|
+
type: "function",
|
|
295
|
+
function: {
|
|
296
|
+
name: actionClass.function_name,
|
|
297
|
+
description: actionClass.description,
|
|
298
|
+
parameters: await actionClass.json_schema(),
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
sysPrompts.push(await actionClass.system_prompt());
|
|
302
|
+
}
|
|
303
|
+
const systemPrompt =
|
|
304
|
+
"You are building application components in a database application builder called Saltcorn.\n\n" +
|
|
305
|
+
sysPrompts.join("\n\n");
|
|
306
|
+
return { tools, systemPrompt };
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/*
|
|
310
|
+
|
|
311
|
+
build a workflow that asks the user for their name and age
|
|
312
|
+
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
const execute = async (table_id, viewname, config, body, { req }) => {
|
|
316
|
+
const { fcall_id, run_id } = body;
|
|
317
|
+
|
|
318
|
+
const run = await WorkflowRun.findOne({ id: +run_id });
|
|
319
|
+
|
|
320
|
+
const fcall = run.context.funcalls[fcall_id];
|
|
321
|
+
const actionClass = actionClasses.find(
|
|
322
|
+
(ac) => ac.function_name === fcall.name
|
|
323
|
+
);
|
|
324
|
+
const result = await actionClass.execute(JSON.parse(fcall.arguments), req);
|
|
325
|
+
await addToContext(run, { implemented_fcall_ids: [fcall_id] });
|
|
326
|
+
return { json: { success: "ok", fcall_id, ...(result || {}) } };
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const interact = async (table_id, viewname, config, body, { req }) => {
|
|
330
|
+
const { userinput, run_id } = body;
|
|
331
|
+
let run;
|
|
332
|
+
if (!run_id || run_id === "undefined")
|
|
333
|
+
run = await WorkflowRun.create({
|
|
334
|
+
status: "Running",
|
|
335
|
+
context: {
|
|
336
|
+
implemented_fcall_ids: [],
|
|
337
|
+
interactions: [{ role: "user", content: userinput }],
|
|
338
|
+
funcalls: {},
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
else {
|
|
342
|
+
run = await WorkflowRun.findOne({ id: +run_id });
|
|
343
|
+
await addToContext(run, {
|
|
344
|
+
interactions: [{ role: "user", content: userinput }],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const complArgs = await getCompletionArguments();
|
|
348
|
+
complArgs.chat = run.context.interactions;
|
|
349
|
+
//console.log(complArgs);
|
|
350
|
+
|
|
351
|
+
//build a database for a bicycle rental company
|
|
352
|
+
//add a boolean field called "paid" to the payments table
|
|
353
|
+
|
|
354
|
+
const answer = await getState().functions.llm_generate.run(
|
|
355
|
+
userinput,
|
|
356
|
+
complArgs
|
|
357
|
+
);
|
|
358
|
+
await addToContext(run, {
|
|
359
|
+
interactions:
|
|
360
|
+
typeof answer === "object" && answer.tool_calls
|
|
361
|
+
? [
|
|
362
|
+
{ role: "assistant", tool_calls: answer.tool_calls },
|
|
363
|
+
...answer.tool_calls.map((tc) => ({
|
|
364
|
+
role: "tool",
|
|
365
|
+
tool_call_id: tc.id,
|
|
366
|
+
name: tc.function.name,
|
|
367
|
+
content: "Action suggested to user.",
|
|
368
|
+
})),
|
|
369
|
+
]
|
|
370
|
+
: [{ role: "assistant", content: answer }],
|
|
371
|
+
});
|
|
372
|
+
console.log("answer", answer);
|
|
373
|
+
|
|
374
|
+
if (typeof answer === "object" && answer.tool_calls) {
|
|
375
|
+
const actions = [];
|
|
376
|
+
for (const tool_call of answer.tool_calls) {
|
|
377
|
+
await addToContext(run, {
|
|
378
|
+
funcalls: { [tool_call.id]: tool_call.function },
|
|
379
|
+
});
|
|
380
|
+
const markup = await renderToolcall(tool_call, viewname, false, run);
|
|
381
|
+
|
|
382
|
+
actions.push(markup);
|
|
383
|
+
}
|
|
384
|
+
return { json: { success: "ok", actions, run_id: run.id } };
|
|
385
|
+
} else return { json: { success: "ok", response: answer, run_id: run.id } };
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const renderToolcall = async (tool_call, viewname, implemented, run) => {
|
|
389
|
+
const fname = tool_call.function.name;
|
|
390
|
+
const actionClass = actionClasses.find((ac) => ac.function_name === fname);
|
|
391
|
+
const args = JSON.parse(tool_call.function.arguments);
|
|
392
|
+
|
|
393
|
+
const inner_markup = await actionClass.render_html(args);
|
|
394
|
+
return wrapAction(
|
|
395
|
+
inner_markup,
|
|
396
|
+
viewname,
|
|
397
|
+
tool_call,
|
|
398
|
+
actionClass,
|
|
399
|
+
implemented,
|
|
400
|
+
run
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const wrapAction = (
|
|
405
|
+
inner_markup,
|
|
406
|
+
viewname,
|
|
407
|
+
tool_call,
|
|
408
|
+
actionClass,
|
|
409
|
+
implemented,
|
|
410
|
+
run
|
|
411
|
+
) =>
|
|
412
|
+
div(
|
|
413
|
+
{ class: "card mb-3" },
|
|
414
|
+
div({ class: "card-header" }, h5(actionClass.title)),
|
|
415
|
+
div(
|
|
416
|
+
{ class: "card-body" },
|
|
417
|
+
inner_markup,
|
|
418
|
+
implemented
|
|
419
|
+
? button(
|
|
420
|
+
{
|
|
421
|
+
type: "button",
|
|
422
|
+
class: "btn btn-secondary d-block mt-3 float-end",
|
|
423
|
+
disabled: true,
|
|
424
|
+
},
|
|
425
|
+
i({ class: "fas fa-check me-1" }),
|
|
426
|
+
"Applied"
|
|
427
|
+
)
|
|
428
|
+
: button(
|
|
429
|
+
{
|
|
430
|
+
type: "button",
|
|
431
|
+
id: "exec-" + tool_call.id,
|
|
432
|
+
class: "btn btn-primary d-block mt-3 float-end",
|
|
433
|
+
onclick: `press_store_button(this, true);view_post('${viewname}', 'execute', {fcall_id: '${tool_call.id}', run_id: ${run.id}}, processExecuteResponse)`,
|
|
434
|
+
},
|
|
435
|
+
"Apply"
|
|
436
|
+
),
|
|
437
|
+
div({ id: "postexec-" + tool_call.id })
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
const addToContext = async (run, newCtx) => {
|
|
442
|
+
if (run.addToContext) return await run.addToContext(newCtx);
|
|
443
|
+
let changed = true;
|
|
444
|
+
Object.keys(newCtx).forEach((k) => {
|
|
445
|
+
if (Array.isArray(run.context[k])) {
|
|
446
|
+
if (!Array.isArray(newCtx[k]))
|
|
447
|
+
throw new Error("Must be array to append to array");
|
|
448
|
+
run.context[k].push(...newCtx[k]);
|
|
449
|
+
changed = true;
|
|
450
|
+
} else if (typeof run.context[k] === "object") {
|
|
451
|
+
if (typeof newCtx[k] !== "object")
|
|
452
|
+
throw new Error("Must be object to append to object");
|
|
453
|
+
Object.assign(run.context[k], newCtx[k]);
|
|
454
|
+
changed = true;
|
|
455
|
+
} else {
|
|
456
|
+
run.context[k] = newCtx[k];
|
|
457
|
+
changed = true;
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
if (changed) await run.update({ context: run.context });
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
module.exports = {
|
|
464
|
+
name: "Saltcorn Copilot",
|
|
465
|
+
display_state_form: false,
|
|
466
|
+
get_state_fields,
|
|
467
|
+
tableless: true,
|
|
468
|
+
singleton: true,
|
|
469
|
+
run,
|
|
470
|
+
routes: { interact, execute },
|
|
471
|
+
};
|
package/common.js
CHANGED
|
@@ -57,4 +57,50 @@ const incompleteCfgMsg = () => {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const toArrayOfStrings = (opts) => {
|
|
61
|
+
if (typeof opts === "string") return opts.split(",").map((s) => s.trim());
|
|
62
|
+
if (Array.isArray(opts))
|
|
63
|
+
return opts.map((o) => (typeof o === "string" ? o : o.value || o.name));
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const fieldProperties = (field) => {
|
|
67
|
+
const props = {};
|
|
68
|
+
const typeName = field.type?.name || field.type || field.input_type;
|
|
69
|
+
if (field.isRepeat) {
|
|
70
|
+
props.type = "array";
|
|
71
|
+
const properties = {};
|
|
72
|
+
field.fields.map((f) => {
|
|
73
|
+
properties[f.name] = {
|
|
74
|
+
description: f.sublabel || f.label,
|
|
75
|
+
...fieldProperties(f),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
props.items = {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
switch (typeName) {
|
|
84
|
+
case "String":
|
|
85
|
+
props.type = "string";
|
|
86
|
+
if (field.attributes?.options)
|
|
87
|
+
props.enum = toArrayOfStrings(field.attributes.options);
|
|
88
|
+
break;
|
|
89
|
+
case "Bool":
|
|
90
|
+
props.type = "boolean";
|
|
91
|
+
break;
|
|
92
|
+
case "Integer":
|
|
93
|
+
props.type = "integer";
|
|
94
|
+
break;
|
|
95
|
+
case "Float":
|
|
96
|
+
props.type = "number";
|
|
97
|
+
break;
|
|
98
|
+
case "select":
|
|
99
|
+
props.type = "string";
|
|
100
|
+
if (field.options) props.enum = toArrayOfStrings(field.options);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
return props;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
module.exports = { getCompletion, getPromptFromTemplate, incompleteCfgMsg, fieldProperties };
|
package/index.js
CHANGED
|
@@ -11,7 +11,9 @@ module.exports = {
|
|
|
11
11
|
sc_plugin_api_version: 1,
|
|
12
12
|
//configuration_workflow,
|
|
13
13
|
dependencies: ["@saltcorn/large-language-model"],
|
|
14
|
-
viewtemplates:
|
|
14
|
+
viewtemplates: features.workflows
|
|
15
|
+
? [require("./chat-copilot")]
|
|
16
|
+
: [require("./action-builder"), require("./database-designer")],
|
|
15
17
|
functions: features.workflows
|
|
16
18
|
? { copilot_generate_workflow: require("./workflow-gen") }
|
|
17
19
|
: {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/copilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "AI assistant for building Saltcorn applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"eslintConfig": {
|
|
16
16
|
"extends": "eslint:recommended",
|
|
17
17
|
"parserOptions": {
|
|
18
|
-
"ecmaVersion":
|
|
18
|
+
"ecmaVersion": 2022
|
|
19
19
|
},
|
|
20
20
|
"env": {
|
|
21
21
|
"node": true,
|
|
@@ -300,8 +300,4 @@ value should be an object with keys that are field variable names. Example:
|
|
|
300
300
|
return { set_fields: {
|
|
301
301
|
zidentifier: `${name.toUpperCase()}-${id}`
|
|
302
302
|
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
Write the JavaScript code to implement the following functionality:
|
|
306
|
-
|
|
307
|
-
{{ userPrompt }}
|
|
303
|
+
}
|