@saltcorn/copilot 0.7.3 → 0.7.5

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.
@@ -76,16 +76,6 @@ class GeneratePageSkill {
76
76
  };
77
77
  }
78
78
  provideTools = () => {
79
- let properties = {};
80
- (this.toolargs || []).forEach((arg) => {
81
- properties[arg.name] = {
82
- description: arg.description,
83
- type: arg.argtype,
84
- };
85
- if (arg.options && arg.argtype === "string")
86
- properties[arg.name].enum = arg.options.split(",").map((s) => s.trim());
87
- });
88
-
89
79
  return {
90
80
  type: "function",
91
81
  process: async ({ name, existing_page_name }) => {
@@ -0,0 +1,229 @@
1
+ const Table = require("@saltcorn/data/models/table");
2
+ const View = require("@saltcorn/data/models/view");
3
+ const { fieldProperties } = require("../common");
4
+ const { initial_config_all_fields } = require("@saltcorn/data/plugin-helper");
5
+ const { getState } = require("@saltcorn/data/db/state");
6
+ const {
7
+ div,
8
+ pre,
9
+ code,
10
+ a,
11
+ text,
12
+ escape,
13
+ iframe,
14
+ text_attr,
15
+ } = require("@saltcorn/markup/tags");
16
+ const builderGen = require("../builder-gen");
17
+
18
+ class GenerateViewSkill {
19
+ static skill_name = "Generate View";
20
+
21
+ get skill_label() {
22
+ return "Generate View";
23
+ }
24
+
25
+ constructor(cfg) {
26
+ Object.assign(this, cfg);
27
+ }
28
+
29
+ async systemPrompt() {
30
+ return `If the user asks to generate a view, use the generate_view tool to enter
31
+ a view generation mode. The tool call only requires high-level details to start this sequence.`;
32
+ }
33
+
34
+ get userActions() {
35
+ return {
36
+ async build_copilot_view_gen({
37
+ wfctx,
38
+ name,
39
+ viewpattern,
40
+ table,
41
+ min_role,
42
+ }) {
43
+ const normalizedRole = min_role || "public";
44
+ const tableRow = table ? Table.findOne({ name: table }) : null;
45
+ await View.create({
46
+ name,
47
+ viewtemplate: viewpattern,
48
+ table_id: tableRow?.id,
49
+ table: tableRow,
50
+ min_role: { admin: 1, public: 100, user: 80 }[normalizedRole],
51
+ configuration: wfctx,
52
+ });
53
+ setTimeout(() => getState().refresh_views(), 200);
54
+ return {
55
+ notify: `View saved: <a target="_blank" href="/view/${name}">${name}</a>`,
56
+ };
57
+ },
58
+ };
59
+ }
60
+
61
+ provideTools = () => {
62
+ const state = getState();
63
+ const vts = state.viewtemplates;
64
+ const tables = state.tables;
65
+ const all_vt_names = Object.keys(vts);
66
+ const enabled_vt_names = all_vt_names.filter(
67
+ (vtnm) =>
68
+ vts[vtnm].enable_copilot_viewgen ||
69
+ vts[vtnm].copilot_generate_view_prompt,
70
+ );
71
+ if (!enabled_vt_names.includes("Show")) enabled_vt_names.push("Show");
72
+ //const roles = await User.get_roles();
73
+ const tableless = enabled_vt_names.filter(
74
+ (vtnm) => vts[vtnm].tableless === true,
75
+ );
76
+ const parameters = {
77
+ type: "object",
78
+ required: ["name", "viewpattern"],
79
+ properties: {
80
+ name: {
81
+ description: `The name of the view, this should be a short name which is part of the url. `,
82
+ type: "string",
83
+ },
84
+ viewpattern: {
85
+ description: `The type of view to generate. Some of the view descriptions: ${enabled_vt_names.map((vtnm) => `${vtnm}: ${vts[vtnm].description}.`).join(" ")}`,
86
+ type: "string",
87
+ enum: enabled_vt_names,
88
+ },
89
+ table: {
90
+ description:
91
+ "Which table is this a view on. These viewpatterns are tablesless, do not supply a tablename: " +
92
+ tableless.join(", "),
93
+ type: "string",
94
+ enum: tables.map((t) => t.name),
95
+ },
96
+ min_role: {
97
+ description:
98
+ "The minimum role needed to access the view. For views accessible only by admin, use 'admin', pages with min_role 'public' is publicly accessible and also available to all users",
99
+ type: "string",
100
+ enum: ["admin", "user", "public"],
101
+ },
102
+ },
103
+ };
104
+
105
+ return {
106
+ type: "function",
107
+ function: {
108
+ name: "generate_view",
109
+ description:
110
+ "Generate a view by supplying high-level details. This will trigger a view generation sequence",
111
+ parameters,
112
+ },
113
+ process: async (input) => {
114
+ return "Metadata received";
115
+ },
116
+ postProcess: async ({ tool_call, req, generate, chat }) => {
117
+ const state = getState();
118
+ const vt = state.viewtemplates[tool_call.input.viewpattern];
119
+ const table =
120
+ vt.tableless === true
121
+ ? null
122
+ : Table.findOne({ name: tool_call.input.table });
123
+
124
+ const wfctx = { viewname: tool_call.input.name, table_id: table?.id };
125
+ if (tool_call.input.viewpattern === "Show") {
126
+ const promptFromChat = Array.isArray(chat)
127
+ ? [...chat]
128
+ .reverse()
129
+ .find((item) => item?.role === "user" && item?.content)?.content
130
+ : "";
131
+ const layoutPrompt = promptFromChat || tool_call.input.name || "";
132
+ wfctx.layout = await builderGen.run(
133
+ layoutPrompt,
134
+ "show",
135
+ table?.name,
136
+ chat,
137
+ );
138
+ if (table) {
139
+ const baseCfg = await initial_config_all_fields(false)({
140
+ table_id: table.id,
141
+ });
142
+ if (baseCfg?.columns) wfctx.columns = baseCfg.columns;
143
+ }
144
+ } else {
145
+ const flow = vt.configuration_workflow(req);
146
+ let vt_prompt = "";
147
+ if (vt.copilot_generate_view_prompt) {
148
+ if (typeof vt.copilot_generate_view_prompt === "string")
149
+ vt_prompt = vt.copilot_generate_view_prompt;
150
+ else if (typeof vt.copilot_generate_view_prompt === "function")
151
+ vt_prompt = await vt.copilot_generate_view_prompt(
152
+ tool_call.input,
153
+ );
154
+ }
155
+
156
+ for (const step of flow.steps) {
157
+ const form = await step.form(wfctx);
158
+ const properties = {};
159
+ //TODO onlyWhen
160
+ for (const field of form.fields) {
161
+ //TODO showIf
162
+ properties[field.name] = {
163
+ description:
164
+ field.copilot_description ||
165
+ `${field.label}.${field.sublabel ? ` ${field.sublabel}` : ""}`,
166
+ ...fieldProperties(field),
167
+ };
168
+ }
169
+
170
+ const answer = await generate(
171
+ `${vt_prompt ? vt_prompt + "\n\n" : ""}Now generate the ${step.name} details of the view by calling the generate_view_details tool`,
172
+ {
173
+ tools: [
174
+ {
175
+ type: "function",
176
+ function: {
177
+ name: "generate_view_details",
178
+ description: "Provide view details",
179
+ parameters: {
180
+ type: "object",
181
+ properties,
182
+ },
183
+ },
184
+ },
185
+ ],
186
+ tool_choice: {
187
+ type: "function",
188
+ function: {
189
+ name: "generate_view_details",
190
+ },
191
+ },
192
+ },
193
+ );
194
+ const tc = answer.getToolCalls()[0];
195
+ Object.assign(wfctx, tc.input);
196
+ }
197
+ }
198
+ const view = new View({
199
+ name: tool_call.input.name,
200
+ viewtemplate: tool_call.input.viewpattern,
201
+ table,
202
+ table_id: table?.id,
203
+ min_role: { admin: 1, public: 100, user: 80 }[
204
+ tool_call.input.min_role || "public"
205
+ ],
206
+ configuration: wfctx,
207
+ });
208
+ const runres = await view.run({}, { req });
209
+ return {
210
+ stop: true,
211
+ add_response:
212
+ pre(JSON.stringify(wfctx, null, 2)) +
213
+ div(
214
+ { style: { maxHeight: 800, maxWidth: 500, overflow: "scroll" } },
215
+ runres,
216
+ ),
217
+ add_user_action: {
218
+ name: "build_copilot_view_gen",
219
+ type: "button",
220
+ label: "Save view " + tool_call.input.name,
221
+ input: { wfctx, ...tool_call.input },
222
+ },
223
+ };
224
+ },
225
+ };
226
+ };
227
+ }
228
+
229
+ module.exports = GenerateViewSkill;