@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/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 run = async (table_id, viewname, cfg, state, { res, req }) => {
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 execute = async (table_id, viewname, config, body, { req }) => {
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 interact = async (table_id, viewname, config, body, { req }) => {
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: { interact, execute },
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
- ? [require("./chat-copilot"), require("./user-copilot")]
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
- ? { copilot_generate_workflow: require("./workflow-gen") }
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: [require("./agent-skills/pagegen.js")],
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/copilot",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "AI assistant for building Saltcorn applications",
5
5
  "main": "index.js",
6
6
  "dependencies": {