@saltcorn/copilot 0.7.1 → 0.7.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 +10 -10
- package/actions/generate-tables.js +20 -4
- package/actions/generate-workflow.js +106 -20
- package/agent-skills/database-design.js +349 -0
- package/agent-skills/pagegen.js +17 -1
- package/agent-skills/workflow.js +638 -0
- package/builder-gen.js +39 -0
- package/chat-copilot.js +174 -4
- package/copilot-as-agent.js +90 -0
- package/index.js +15 -4
- package/package.json +1 -1
- package/page-gen-action.js +311 -87
package/chat-copilot.js
CHANGED
|
@@ -36,9 +36,81 @@ const {
|
|
|
36
36
|
const MarkdownIt = require("markdown-it"),
|
|
37
37
|
md = new MarkdownIt();
|
|
38
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
|
+
|
|
39
111
|
const get_state_fields = () => [];
|
|
40
112
|
|
|
41
|
-
const
|
|
113
|
+
const runLegacy = async (table_id, viewname, cfg, state, { res, req }) => {
|
|
42
114
|
const prevRuns = (
|
|
43
115
|
await WorkflowRun.find(
|
|
44
116
|
{ trigger_id: null /*started_by: req.user?.id*/ }, //todo uncomment
|
|
@@ -284,6 +356,22 @@ const run = async (table_id, viewname, cfg, state, { res, req }) => {
|
|
|
284
356
|
};
|
|
285
357
|
};
|
|
286
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
|
+
|
|
287
375
|
const ellipsize = (s, nchars) => {
|
|
288
376
|
if (!s || !s.length) return "";
|
|
289
377
|
if (s.length <= (nchars || 20)) return text_attr(s);
|
|
@@ -330,7 +418,7 @@ build a workflow that asks the user for their name and age
|
|
|
330
418
|
|
|
331
419
|
*/
|
|
332
420
|
|
|
333
|
-
const
|
|
421
|
+
const executeLegacy = async (table_id, viewname, config, body, { req }) => {
|
|
334
422
|
const { fcall_id, run_id } = body;
|
|
335
423
|
|
|
336
424
|
const run = await WorkflowRun.findOne({ id: +run_id });
|
|
@@ -360,7 +448,7 @@ const execute = async (table_id, viewname, config, body, { req }) => {
|
|
|
360
448
|
return { json: { success: "ok", fcall_id, ...(result || {}) } };
|
|
361
449
|
};
|
|
362
450
|
|
|
363
|
-
const
|
|
451
|
+
const interactLegacy = async (table_id, viewname, config, body, { req }) => {
|
|
364
452
|
const { userinput, run_id } = body;
|
|
365
453
|
let run;
|
|
366
454
|
if (!run_id || run_id === "undefined")
|
|
@@ -487,6 +575,81 @@ const interact = async (table_id, viewname, config, body, { req }) => {
|
|
|
487
575
|
};
|
|
488
576
|
};
|
|
489
577
|
|
|
578
|
+
const runAgentRoute = async (routeName, table_id, viewname, body, extra) => {
|
|
579
|
+
const agentVt = getAgentViewtemplate();
|
|
580
|
+
if (!agentVt?.routes?.[routeName]) return null;
|
|
581
|
+
const agentCfg = await getAgentChatCfg();
|
|
582
|
+
if (!agentCfg) return null;
|
|
583
|
+
return await agentVt.routes[routeName](table_id, viewname, agentCfg, body, extra);
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const interact = async (table_id, viewname, config, body, extra) => {
|
|
587
|
+
const agentResp = await runAgentRoute(
|
|
588
|
+
"interact",
|
|
589
|
+
table_id,
|
|
590
|
+
viewname,
|
|
591
|
+
body,
|
|
592
|
+
extra,
|
|
593
|
+
);
|
|
594
|
+
if (agentResp) return agentResp;
|
|
595
|
+
return await interactLegacy(table_id, viewname, config, body, extra);
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// Legacy route used only by the legacy UI. The Agent Chat UI uses `execute_user_action`.
|
|
599
|
+
const execute = async (table_id, viewname, config, body, extra) => {
|
|
600
|
+
return await executeLegacy(table_id, viewname, config, body, extra);
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const delprevrun = async (table_id, viewname, config, body, extra) => {
|
|
604
|
+
const agentResp = await runAgentRoute(
|
|
605
|
+
"delprevrun",
|
|
606
|
+
table_id,
|
|
607
|
+
viewname,
|
|
608
|
+
body,
|
|
609
|
+
extra,
|
|
610
|
+
);
|
|
611
|
+
if (agentResp) return agentResp;
|
|
612
|
+
return { json: { error: "delprevrun is only available in Agent Chat mode" } };
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const debug_info = async (table_id, viewname, config, body, extra) => {
|
|
616
|
+
const agentResp = await runAgentRoute(
|
|
617
|
+
"debug_info",
|
|
618
|
+
table_id,
|
|
619
|
+
viewname,
|
|
620
|
+
body,
|
|
621
|
+
extra,
|
|
622
|
+
);
|
|
623
|
+
if (agentResp) return agentResp;
|
|
624
|
+
return { json: { error: "debug_info is only available in Agent Chat mode" } };
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const skillroute = async (table_id, viewname, config, body, extra) => {
|
|
628
|
+
const agentResp = await runAgentRoute(
|
|
629
|
+
"skillroute",
|
|
630
|
+
table_id,
|
|
631
|
+
viewname,
|
|
632
|
+
body,
|
|
633
|
+
extra,
|
|
634
|
+
);
|
|
635
|
+
if (agentResp) return agentResp;
|
|
636
|
+
return { json: { error: "skillroute is only available in Agent Chat mode" } };
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const execute_user_action = async (table_id, viewname, config, body, extra) => {
|
|
640
|
+
const agentResp = await runAgentRoute(
|
|
641
|
+
"execute_user_action",
|
|
642
|
+
table_id,
|
|
643
|
+
viewname,
|
|
644
|
+
body,
|
|
645
|
+
extra,
|
|
646
|
+
);
|
|
647
|
+
if (agentResp) return agentResp;
|
|
648
|
+
return {
|
|
649
|
+
json: { error: "execute_user_action is only available in Agent Chat mode" },
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
|
|
490
653
|
const getFollowOnGeneration = async (tool_call) => {
|
|
491
654
|
const fname = tool_call.function?.name || tool_call.toolName;
|
|
492
655
|
const actionClass = classesWithSkills().find(
|
|
@@ -595,5 +758,12 @@ module.exports = {
|
|
|
595
758
|
tableless: true,
|
|
596
759
|
singleton: true,
|
|
597
760
|
run,
|
|
598
|
-
routes: {
|
|
761
|
+
routes: {
|
|
762
|
+
interact,
|
|
763
|
+
execute,
|
|
764
|
+
delprevrun,
|
|
765
|
+
debug_info,
|
|
766
|
+
skillroute,
|
|
767
|
+
execute_user_action,
|
|
768
|
+
},
|
|
599
769
|
};
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
|
|
32
|
+
const get_state_fields = () => [];
|
|
33
|
+
|
|
34
|
+
const sys_prompt = ``;
|
|
35
|
+
const viewname = "Saltcorn Agent copilot";
|
|
36
|
+
|
|
37
|
+
const get_agent_view = () => {
|
|
38
|
+
const agent_action = new Trigger({
|
|
39
|
+
action: "Agent",
|
|
40
|
+
when_trigger: "Never",
|
|
41
|
+
configuration: {
|
|
42
|
+
viewname,
|
|
43
|
+
sys_prompt,
|
|
44
|
+
skills: [
|
|
45
|
+
{ skill_type: "Generate Page" },
|
|
46
|
+
{ skill_type: "Database design" },
|
|
47
|
+
{ skill_type: "Generate Workflow" },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return new View({
|
|
52
|
+
viewtemplate: "Agent Chat",
|
|
53
|
+
name: viewname,
|
|
54
|
+
min_role: 1,
|
|
55
|
+
configuration: {
|
|
56
|
+
agent_action,
|
|
57
|
+
viewname,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const run = async (table_id, viewname, cfg, state, reqres) => {
|
|
62
|
+
return await get_agent_view().run(state, reqres);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const interact = async (table_id, viewname, config, body, reqres) => {
|
|
66
|
+
console.log("copilot interact with body", body);
|
|
67
|
+
const view = get_agent_view();
|
|
68
|
+
return await view.runRoute("interact", body, reqres.res, reqres);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const execute_user_action = async (
|
|
72
|
+
table_id,
|
|
73
|
+
viewname,
|
|
74
|
+
config,
|
|
75
|
+
body,
|
|
76
|
+
reqres,
|
|
77
|
+
) => {
|
|
78
|
+
const view = get_agent_view();
|
|
79
|
+
return await view.runRoute("execute_user_action", body, reqres.res, reqres);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
name: viewname,
|
|
84
|
+
display_state_form: false,
|
|
85
|
+
get_state_fields,
|
|
86
|
+
tableless: true,
|
|
87
|
+
singleton: true,
|
|
88
|
+
run,
|
|
89
|
+
routes: { interact, execute_user_action },
|
|
90
|
+
};
|
package/index.js
CHANGED
|
@@ -4,15 +4,26 @@ const { features } = require("@saltcorn/data/db/state");
|
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
sc_plugin_api_version: 1,
|
|
7
|
-
dependencies: ["@saltcorn/large-language-model"],
|
|
7
|
+
dependencies: ["@saltcorn/large-language-model", "@saltcorn/agents"],
|
|
8
8
|
viewtemplates: features.workflows
|
|
9
|
-
? [
|
|
9
|
+
? [
|
|
10
|
+
require("./chat-copilot"),
|
|
11
|
+
require("./user-copilot"),
|
|
12
|
+
require("./copilot-as-agent"),
|
|
13
|
+
]
|
|
10
14
|
: [require("./action-builder"), require("./database-designer")],
|
|
11
15
|
functions: features.workflows
|
|
12
|
-
? {
|
|
16
|
+
? {
|
|
17
|
+
copilot_generate_layout: require("./builder-gen.js"),
|
|
18
|
+
copilot_generate_workflow: require("./workflow-gen"),
|
|
19
|
+
}
|
|
13
20
|
: {},
|
|
14
21
|
actions: { copilot_generate_page: require("./page-gen-action") },
|
|
15
22
|
exchange: {
|
|
16
|
-
agent_skills: [
|
|
23
|
+
agent_skills: [
|
|
24
|
+
require("./agent-skills/pagegen.js"),
|
|
25
|
+
require("./agent-skills/database-design.js"),
|
|
26
|
+
require("./agent-skills/workflow.js"),
|
|
27
|
+
],
|
|
17
28
|
},
|
|
18
29
|
};
|