@saltcorn/copilot 0.5.3 → 0.6.0

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/index.js CHANGED
@@ -2,7 +2,6 @@ const Workflow = require("@saltcorn/data/models/workflow");
2
2
  const Form = require("@saltcorn/data/models/form");
3
3
  const { features } = require("@saltcorn/data/db/state");
4
4
 
5
-
6
5
  module.exports = {
7
6
  sc_plugin_api_version: 1,
8
7
  dependencies: ["@saltcorn/large-language-model"],
@@ -12,4 +11,5 @@ module.exports = {
12
11
  functions: features.workflows
13
12
  ? { copilot_generate_workflow: require("./workflow-gen") }
14
13
  : {},
14
+ actions: { copilot_generate_page: require("./page-gen-action") },
15
15
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/copilot",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "AI assistant for building Saltcorn applications",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -0,0 +1,179 @@
1
+ const { eval_expression } = require("@saltcorn/data/models/expression");
2
+ const { interpolate } = require("@saltcorn/data/utils");
3
+ const { getState } = require("@saltcorn/data/db/state");
4
+ const File = require("@saltcorn/data/models/file");
5
+ const Page = require("@saltcorn/data/models/page");
6
+
7
+ const GeneratePage = require("./actions/generate-page");
8
+
9
+ module.exports = {
10
+ description: "Generate page with AI copilot",
11
+ configFields: ({ table, mode }) => {
12
+ if (mode === "workflow") {
13
+ return [
14
+ {
15
+ name: "page_name",
16
+ label: "Page name",
17
+ sublabel:
18
+ "Leave blank to not save a Saltcorn page. Use interpolations {{ }} to access variables in the context",
19
+ type: "String",
20
+ },
21
+ {
22
+ name: "prompt_template",
23
+ label: "Prompt",
24
+ sublabel:
25
+ "Prompt text. Use interpolations {{ }} to access variables in the context",
26
+ type: "String",
27
+ fieldview: "textarea",
28
+ required: true,
29
+ },
30
+ {
31
+ name: "answer_field",
32
+ label: "Answer variable",
33
+ sublabel: "Optional. Set the generated HTML to this context variable",
34
+ type: "String",
35
+ required: true,
36
+ },
37
+ // ...override_fields,
38
+ {
39
+ name: "model",
40
+ label: "Model",
41
+ sublabel: "Override default model name",
42
+ type: "String",
43
+ },
44
+ ];
45
+ } else if (table) {
46
+ const textFields = table.fields
47
+ .filter((f) => f.type?.sql_name === "text")
48
+ .map((f) => f.name);
49
+
50
+ return [
51
+ {
52
+ name: "prompt_field",
53
+ label: "Prompt field",
54
+ sublabel: "Field with the text of the prompt",
55
+ type: "String",
56
+ required: true,
57
+ attributes: { options: [...textFields, "Formula"] },
58
+ },
59
+ {
60
+ name: "prompt_formula",
61
+ label: "Prompt formula",
62
+ type: "String",
63
+ showIf: { prompt_field: "Formula" },
64
+ },
65
+ {
66
+ name: "answer_field",
67
+ label: "Answer field",
68
+ sublabel: "Output field will be set to the generated answer",
69
+ type: "String",
70
+ required: true,
71
+ attributes: { options: textFields },
72
+ },
73
+ // ...override_fields,
74
+ ];
75
+ }
76
+ },
77
+ run: async ({
78
+ row,
79
+ table,
80
+ user,
81
+ mode,
82
+ configuration: {
83
+ page_name,
84
+ prompt_field,
85
+ prompt_formula,
86
+ prompt_template,
87
+ answer_field,
88
+ chat_history_field,
89
+ model,
90
+ },
91
+ }) => {
92
+ let prompt;
93
+ if (mode === "workflow") prompt = interpolate(prompt_template, row, user);
94
+ else if (prompt_field === "Formula" || mode === "workflow")
95
+ prompt = eval_expression(
96
+ prompt_formula,
97
+ row,
98
+ user,
99
+ "llm_generate prompt formula"
100
+ );
101
+ else prompt = row[prompt_field];
102
+ const opts = {};
103
+
104
+ if (model) opts.model = model;
105
+ const tools = [];
106
+ const systemPrompt = await GeneratePage.system_prompt();
107
+ tools.push({
108
+ type: "function",
109
+ function: {
110
+ name: GeneratePage.function_name,
111
+ description: GeneratePage.description,
112
+ parameters: await GeneratePage.json_schema(),
113
+ },
114
+ });
115
+ const { llm_generate } = getState().functions;
116
+
117
+ const initial_ans = await llm_generate.run(prompt, {
118
+ tools,
119
+ systemPrompt,
120
+ });
121
+ const initial_info = initial_ans.tool_calls[0].input;
122
+ const full = await GeneratePage.follow_on_generate(initial_info);
123
+
124
+ const page_html = await getState().functions.llm_generate.run(
125
+ `${prompt}.
126
+
127
+ The page title is: ${initial_info.title}.
128
+ Further page description: ${initial_info.description}.
129
+
130
+ Generate the HTML for the web page using the Bootstrap 5 CSS framework.
131
+ If you need to include the standard bootstrap CSS and javascript files, they are available as:
132
+
133
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
134
+
135
+ and
136
+
137
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
138
+
139
+ Just generate HTML code, do not wrap in markdown code tags`,
140
+ {
141
+ debugResult: true,
142
+ response_format: full.response_schema
143
+ ? {
144
+ type: "json_schema",
145
+ json_schema: {
146
+ name: "generate_page",
147
+ schema: full.response_schema,
148
+ },
149
+ }
150
+ : undefined,
151
+ }
152
+ );
153
+
154
+ const use_page_name = page_name ? interpolate(page_name, row, user) : "";
155
+ if (use_page_name) {
156
+ //save to a file
157
+ const file = await File.from_contents(
158
+ `${use_page_name}.html`,
159
+ "text/html",
160
+ page_html,
161
+ user.id,
162
+ 100
163
+ );
164
+
165
+ //create page
166
+ await Page.create({
167
+ name: use_page_name,
168
+ title: initial_info.title,
169
+ description: initial_info.description,
170
+ min_role: 100,
171
+ layout: { html_file: file.path_to_serve },
172
+ });
173
+ getState().refresh_pages()
174
+ }
175
+ const upd = answer_field ? { [answer_field]: page_html } : {};
176
+ if (mode === "workflow") return upd;
177
+ else if (answer_field) await table.updateRow(upd, row[table.pk_name]);
178
+ },
179
+ };