@saltcorn/agents 0.3.6 → 0.4.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/agent-view.js CHANGED
@@ -42,6 +42,7 @@ const {
42
42
  process_interaction,
43
43
  find_image_tool,
44
44
  is_debug_mode,
45
+ get_initial_interactions,
45
46
  } = require("./common");
46
47
  const MarkdownIt = require("markdown-it"),
47
48
  md = new MarkdownIt();
@@ -81,6 +82,11 @@ const configuration_workflow = (req) =>
81
82
  label: "Show previous runs",
82
83
  type: "Bool",
83
84
  },
85
+ {
86
+ name: "prev_runs_closed",
87
+ label: "Initially closed",
88
+ type: "Bool",
89
+ },
84
90
  {
85
91
  name: "placeholder",
86
92
  label: "Placeholder",
@@ -114,7 +120,16 @@ const configuration_workflow = (req) =>
114
120
  ],
115
121
  });
116
122
 
117
- const get_state_fields = () => [];
123
+ const get_state_fields = async (table_id) =>
124
+ table_id
125
+ ? [
126
+ {
127
+ name: "id",
128
+ type: "Integer",
129
+ primary_key: true,
130
+ },
131
+ ]
132
+ : [];
118
133
 
119
134
  const uploadForm = (viewname, req) =>
120
135
  span(
@@ -139,7 +154,14 @@ const uploadForm = (viewname, req) =>
139
154
  const run = async (
140
155
  table_id,
141
156
  viewname,
142
- { action_id, show_prev_runs, placeholder, explainer, image_upload },
157
+ {
158
+ action_id,
159
+ show_prev_runs,
160
+ prev_runs_closed,
161
+ placeholder,
162
+ explainer,
163
+ image_upload,
164
+ },
143
165
  state,
144
166
  { res, req }
145
167
  ) => {
@@ -154,6 +176,15 @@ const run = async (
154
176
  const cfgMsg = incompleteCfgMsg();
155
177
  if (cfgMsg) return cfgMsg;
156
178
  let runInteractions = "";
179
+ let triggering_row_id;
180
+ if (table_id) {
181
+ const table = Table.findOne(table_id);
182
+ const pk = table?.pk_name;
183
+ if (table && state[pk])
184
+ //triggering_row = await table.getRow({ [pk]: state[pk] });
185
+ triggering_row_id = state[pk];
186
+ }
187
+ const initial_q = state.run_id ? undefined : state._q;
157
188
  if (state.run_id) {
158
189
  const run = prevRuns.find((r) => r.id == state.run_id);
159
190
  const interactMarkups = [];
@@ -303,17 +334,26 @@ const run = async (
303
334
  name: "run_id",
304
335
  value: state.run_id ? +state.run_id : undefined,
305
336
  }),
337
+ input({
338
+ type: "hidden",
339
+ class: "form-control ",
340
+ name: "triggering_row_id",
341
+ value: triggering_row_id || "",
342
+ }),
306
343
  div(
307
344
  { class: "copilot-entry" },
308
- textarea({
309
- class: "form-control",
310
- name: "userinput",
311
- "data-fieldname": "userinput",
312
- placeholder: placeholder || "How can I help you?",
313
- id: "inputuserinput",
314
- rows: "3",
315
- autofocus: true,
316
- }),
345
+ textarea(
346
+ {
347
+ class: "form-control",
348
+ name: "userinput",
349
+ "data-fieldname": "userinput",
350
+ placeholder: placeholder || "How can I help you?",
351
+ id: "inputuserinput",
352
+ rows: "3",
353
+ autofocus: true,
354
+ },
355
+ initial_q
356
+ ),
317
357
  span(
318
358
  { class: "submit-button p-2", onclick: "$('form.copilot').submit()" },
319
359
  i({ id: "sendbuttonicon", class: "far fa-paper-plane" })
@@ -333,8 +373,14 @@ const run = async (
333
373
  {
334
374
  class: "d-flex justify-content-between align-middle mb-2",
335
375
  },
336
- h5("Sessions"),
337
-
376
+ div(
377
+ { class: "d-flex" },
378
+ i({
379
+ class: "fas fa-caret-down me-1 session-open-sessions",
380
+ onclick: "close_session_list()",
381
+ }),
382
+ h5(req.__("Sessions"))
383
+ ),
338
384
  button(
339
385
  {
340
386
  type: "button",
@@ -369,6 +415,17 @@ const run = async (
369
415
  { class: "card" },
370
416
  div(
371
417
  { class: "card-body" },
418
+ div(
419
+ {
420
+ class: "open-prev-runs",
421
+ style: prev_runs_closed ? {} : { display: "none" },
422
+ onclick: "open_session_list()",
423
+ },
424
+ i({
425
+ class: "fas fa-caret-right me-1",
426
+ }),
427
+ req.__("Sessions")
428
+ ),
372
429
  div({ id: "copilotinteractions" }, runInteractions),
373
430
  input_form,
374
431
  style(
@@ -393,6 +450,9 @@ const run = async (
393
450
  left: 0.1rem;
394
451
  cursor: pointer;
395
452
  }
453
+ .session-open-sessions, .open-prev-runs {
454
+ cursor: pointer;
455
+ }
396
456
  .copilot-entry span.attach_agent_image_wrap {
397
457
  position: relative;
398
458
  top: -1.8rem;
@@ -403,6 +463,9 @@ const run = async (
403
463
  top: -1.2rem;
404
464
  display: block;
405
465
  }
466
+ .col-0 {
467
+ width: 0%
468
+ }
406
469
  .copilot-entry {margin-bottom: -1.25rem; margin-top: 1rem;}
407
470
  p.prevrun_content {
408
471
  white-space: nowrap;
@@ -411,7 +474,17 @@ const run = async (
411
474
  display: block;
412
475
  text-overflow: ellipsis;}`
413
476
  ),
414
- script(`function processCopilotResponse(res) {
477
+ script(
478
+ `
479
+ function close_session_list() {
480
+ $("div.prev-runs-list").hide().parents(".col-3").removeClass("col-3").addClass("was-col-3").parent().children(".col-9").removeClass("col-9").addClass("col-12")
481
+ $("div.open-prev-runs").show()
482
+ }
483
+ function open_session_list() {
484
+ $("div.prev-runs-list").show().parents(".was-col-3").removeClass(["was-col-3","col-0","d-none"]).addClass("col-3").parent().children(".col-12").removeClass("col-12").addClass("col-9")
485
+ $("div.open-prev-runs").hide()
486
+ }
487
+ function processCopilotResponse(res) {
415
488
  const hadFile = $("input#attach_agent_image").val();
416
489
  $("span.filename-label").text("");
417
490
  $("input#attach_agent_image").val(null);
@@ -441,10 +514,9 @@ const run = async (
441
514
  const $runidin= $("input[name=run_id")
442
515
  const runid = $runidin.val()
443
516
  if(runid)
444
- view_post('${viewname}', 'debug_info', {run_id:runid}, show_agent_debug_info)
517
+ view_post('${viewname}', 'debug_info', {run_id:runid, triggering_row_id:$("input[name=triggering_row_id").val()}, show_agent_debug_info)
445
518
  }
446
519
  function show_agent_debug_info(info) {
447
- console.log(info)
448
520
  ensure_modal_exists_and_closed();
449
521
  $("#scmodal .modal-body").html(info.debug_html);
450
522
  $("#scmodal .modal-title").html(decodeURIComponent("Agent session information"));
@@ -484,17 +556,20 @@ const run = async (
484
556
  document.getElementById("inputuserinput").addEventListener("keydown", submitOnEnter);
485
557
  function spin_send_button() {
486
558
  $("#sendbuttonicon").attr("class","fas fa-spinner fa-spin");
487
- }
488
- `)
559
+ };`,
560
+ initial_q && domReady("$('form.copilot').submit()")
561
+ )
489
562
  )
490
563
  );
491
564
  return show_prev_runs
492
565
  ? {
493
- widths: [3, 9],
566
+ widths: prev_runs_closed ? [0, 12] : [3, 9],
567
+ colClasses: prev_runs_closed ? ["was-col-3 d-none"] : undefined,
494
568
  gx: 3,
495
569
  besides: [
496
570
  {
497
571
  type: "container",
572
+ customClass: "prev-runs-list",
498
573
  contents: prev_runs_side_bar,
499
574
  },
500
575
  {
@@ -507,20 +582,34 @@ const run = async (
507
582
  };
508
583
 
509
584
  const interact = async (table_id, viewname, config, body, { req, res }) => {
510
- const { userinput, run_id } = body;
585
+ const { userinput, run_id, triggering_row_id } = body;
586
+ const action = await Trigger.findOne({ id: config.action_id });
587
+
511
588
  let run;
512
- if (!run_id || run_id === "undefined")
589
+ let triggering_row;
590
+ if (table_id && triggering_row_id) {
591
+ const table = Table.findOne(table_id);
592
+ const pk = table?.pk_name;
593
+ if (table) triggering_row = await table.getRow({ [pk]: triggering_row_id });
594
+ }
595
+ if (!run_id || run_id === "undefined") {
596
+ const ini_interacts = await get_initial_interactions(
597
+ action.configuration,
598
+ req.user,
599
+ triggering_row
600
+ );
513
601
  run = await WorkflowRun.create({
514
602
  status: "Running",
515
603
  started_by: req.user?.id,
516
604
  trigger_id: config.action_id,
517
605
  context: {
518
606
  implemented_fcall_ids: [],
519
- interactions: [{ role: "user", content: userinput }],
607
+ interactions: [...ini_interacts, { role: "user", content: userinput }],
520
608
  funcalls: {},
609
+ triggering_row_id,
521
610
  },
522
611
  });
523
- else {
612
+ } else {
524
613
  run = await WorkflowRun.findOne({ id: +run_id });
525
614
  await addToContext(run, {
526
615
  interactions: [{ role: "user", content: userinput }],
@@ -561,9 +650,15 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
561
650
  ],
562
651
  });
563
652
  }
564
- const action = await Trigger.findOne({ id: config.action_id });
565
653
 
566
- return await process_interaction(run, action.configuration, req, action.name);
654
+ return await process_interaction(
655
+ run,
656
+ action.configuration,
657
+ req,
658
+ action.name,
659
+ [],
660
+ triggering_row
661
+ );
567
662
  };
568
663
 
569
664
  const delprevrun = async (table_id, viewname, config, body, { req, res }) => {
@@ -578,13 +673,19 @@ const delprevrun = async (table_id, viewname, config, body, { req, res }) => {
578
673
  };
579
674
 
580
675
  const debug_info = async (table_id, viewname, config, body, { req, res }) => {
581
- const { run_id } = body;
676
+ const { run_id, triggering_row_id } = body;
582
677
  const action = await Trigger.findOne({ id: config.action_id });
583
-
678
+ let triggering_row;
679
+ if (table_id && triggering_row_id) {
680
+ const table = Table.findOne(table_id);
681
+ const pk = table?.pk_name;
682
+ if (table) triggering_row = await table.getRow({ [pk]: triggering_row_id });
683
+ }
584
684
  const run = await WorkflowRun.findOne({ id: +run_id });
585
685
  const complArgs = await getCompletionArguments(
586
686
  action.configuration,
587
- req.user
687
+ req.user,
688
+ triggering_row
588
689
  );
589
690
  const debug_html = div(
590
691
  div(h4("System prompt"), pre(complArgs.systemPrompt)),
package/common.js CHANGED
@@ -1,11 +1,28 @@
1
1
  const { getState } = require("@saltcorn/data/db/state");
2
2
  const { div, span } = require("@saltcorn/markup/tags");
3
3
  const Trigger = require("@saltcorn/data/models/trigger");
4
+ const { interpolate } = require("@saltcorn/data/utils");
4
5
 
5
6
  const MarkdownIt = require("markdown-it"),
6
7
  md = new MarkdownIt();
7
8
 
9
+ const nubBy = (f, xs) => {
10
+ const vs = new Set();
11
+ return xs.filter((x) => {
12
+ const y = f(x);
13
+ if (vs.has(y)) return false;
14
+ vs.add(y);
15
+ return true;
16
+ });
17
+ };
18
+
8
19
  const get_skills = () => {
20
+ const state = getState();
21
+ const exchange_skills = nubBy(
22
+ (c) => c.constructor.name,
23
+ state.exchange?.agent_skills || []
24
+ );
25
+
9
26
  return [
10
27
  require("./skills/FTSRetrieval"),
11
28
  require("./skills/EmbeddingRetrieval"),
@@ -15,6 +32,7 @@ const get_skills = () => {
15
32
  require("./skills/GenerateImage"),
16
33
  require("./skills/ModelContextProtocol"),
17
34
  //require("./skills/AdaptiveFeedback"),
35
+ ...exchange_skills,
18
36
  ];
19
37
  };
20
38
 
@@ -61,14 +79,26 @@ const find_image_tool = (config) => {
61
79
  }
62
80
  };
63
81
 
64
- const getCompletionArguments = async (config, user) => {
82
+ const get_initial_interactions = async (config, user, triggering_row) => {
83
+ const interacts = [];
84
+ const skills = get_skill_instances(config);
85
+ for (const skill of skills) {
86
+ const its = await skill.initialInteractions?.({ user, triggering_row });
87
+ if (its) interacts.push(...its);
88
+ }
89
+ return interacts;
90
+ };
91
+
92
+ const getCompletionArguments = async (config, user, triggering_row) => {
65
93
  let tools = [];
66
94
 
67
- let sysPrompts = [config.sys_prompt];
95
+ let sysPrompts = [
96
+ interpolate(config.sys_prompt, triggering_row || {}, user, "System prompt"),
97
+ ];
68
98
 
69
99
  const skills = get_skill_instances(config);
70
100
  for (const skill of skills) {
71
- const sysPr = await skill.systemPrompt?.({ user });
101
+ const sysPr = await skill.systemPrompt?.({ user, triggering_row });
72
102
  if (sysPr) sysPrompts.push(sysPr);
73
103
  const skillTools = skill.provideTools?.();
74
104
  if (skillTools && Array.isArray(skillTools)) tools.push(...skillTools);
@@ -163,9 +193,14 @@ const process_interaction = async (
163
193
  config,
164
194
  req,
165
195
  agent_label = "Copilot",
166
- prevResponses = []
196
+ prevResponses = [],
197
+ triggering_row = {}
167
198
  ) => {
168
- const complArgs = await getCompletionArguments(config, req.user);
199
+ const complArgs = await getCompletionArguments(
200
+ config,
201
+ req.user,
202
+ triggering_row
203
+ );
169
204
  complArgs.chat = run.context.interactions.map(only_response_text_if_present);
170
205
  //complArgs.debugResult = true;
171
206
  //console.log("complArgs", JSON.stringify(complArgs, null, 2));
@@ -300,10 +335,14 @@ const process_interaction = async (
300
335
  }
301
336
  }
302
337
  if (hasResult)
303
- return await process_interaction(run, config, req, agent_label, [
304
- ...prevResponses,
305
- ...responses,
306
- ]);
338
+ return await process_interaction(
339
+ run,
340
+ config,
341
+ req,
342
+ agent_label,
343
+ [...prevResponses, ...responses],
344
+ triggering_row
345
+ );
307
346
  } else if (typeof answer === "string")
308
347
  responses.push(wrapSegment(md.render(answer), agent_label));
309
348
 
@@ -330,4 +369,6 @@ module.exports = {
330
369
  process_interaction,
331
370
  find_image_tool,
332
371
  is_debug_mode,
372
+ get_initial_interactions,
373
+ nubBy,
333
374
  };
package/index.js CHANGED
@@ -51,7 +51,8 @@ module.exports = {
51
51
  {
52
52
  name: "sys_prompt",
53
53
  label: "System prompt",
54
- sublabel: "Additional information for the system prompt",
54
+ sublabel:
55
+ "Additional information for the system prompt. Use interpolations <code>{{ }}</code> to access triggering row variables or user",
55
56
  type: "String",
56
57
  fieldview: "textarea",
57
58
  },
@@ -84,7 +85,14 @@ module.exports = {
84
85
  funcalls: {},
85
86
  },
86
87
  });
87
- return await process_interaction(run, configuration, req);
88
+ return await process_interaction(
89
+ run,
90
+ configuration,
91
+ req,
92
+ undefined,
93
+ [],
94
+ row
95
+ );
88
96
  },
89
97
  },
90
98
  },
@@ -0,0 +1 @@
1
+ { "Sessions": "Sitzungen" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.3.6",
3
+ "version": "0.4.1",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -6,6 +6,7 @@ const View = require("@saltcorn/data/models/view");
6
6
  const { getState } = require("@saltcorn/data/db/state");
7
7
  const db = require("@saltcorn/data/db");
8
8
  const { interpolate } = require("@saltcorn/data/utils");
9
+ const { nubBy } = require("../common");
9
10
 
10
11
  class RetrievalByFullTextSearch {
11
12
  static skill_name = "Retrieval by full-text search";
@@ -105,30 +106,45 @@ class RetrievalByFullTextSearch {
105
106
  const table = Table.findOne(this.table_name);
106
107
  return {
107
108
  type: "function",
108
- process: async ({ phrase }, { req }) => {
109
+ process: async (arg, { req }) => {
109
110
  const scState = getState();
110
111
  const language = scState.pg_ts_config;
111
112
  const use_websearch = scState.getConfig("search_use_websearch", false);
112
- const rows = await table.getRows({
113
- _fts: {
114
- fields: table.fields,
115
- searchTerm: phrase,
116
- language,
117
- use_websearch,
118
- table: table.name,
119
- schema: db.isSQLite ? undefined : db.getTenantSchema(),
120
- },
121
- });
122
- if (this.hidden_fields) {
123
- const hidden_fields = this.hidden_fields
124
- .split(",")
125
- .map((s) => s.trim());
126
- rows.forEach((r) => {
127
- hidden_fields.forEach((k) => {
128
- delete r[k];
129
- });
113
+ let rows = [];
114
+ const phrases =
115
+ arg.query?.phrases ||
116
+ arg.phrases ||
117
+ (arg.phrase
118
+ ? [arg.phrase]
119
+ : arg.query?.phrase
120
+ ? [arg.query?.phrase]
121
+ : []);
122
+ for (const phrase of phrases) {
123
+ const my_rows = await table.getRows({
124
+ _fts: {
125
+ fields: table.fields,
126
+ searchTerm: phrase,
127
+ language,
128
+ use_websearch,
129
+ table: table.name,
130
+ schema: db.isSQLite ? undefined : db.getTenantSchema(),
131
+ },
130
132
  });
133
+ if (this.hidden_fields) {
134
+ const hidden_fields = this.hidden_fields
135
+ .split(",")
136
+ .map((s) => s.trim());
137
+ my_rows.forEach((r) => {
138
+ hidden_fields.forEach((k) => {
139
+ delete r[k];
140
+ });
141
+ });
142
+ }
143
+ rows.push(...my_rows);
131
144
  }
145
+ const pk = table.pk_name;
146
+ rows = nubBy((r) => r[pk], rows);
147
+
132
148
  if (rows.length)
133
149
  if (!this.doc_format) return { rows };
134
150
  else {
@@ -139,7 +155,8 @@ class RetrievalByFullTextSearch {
139
155
  }
140
156
  else
141
157
  return {
142
- responseText: "There are no rows related to: " + phrase,
158
+ responseText:
159
+ "There are no rows related to: " + phrases.join(" or "),
143
160
  };
144
161
  },
145
162
  /*renderToolCall({ phrase }, { req }) {
@@ -163,15 +180,41 @@ class RetrievalByFullTextSearch {
163
180
  name: this.toolName,
164
181
  description: `Search the ${this.table_name} database table${
165
182
  table.description ? ` (${table.description})` : ""
166
- } by a search phrase matched against all fields in the table with full text search. The retrieved rows will be returned`,
183
+ } by one or several search phrase or multiple search phrases matched against all fields in the table with full text search. The retrieved rows will be returned. If you want to search for documents matching any of several phrases, call this tool once with the phrases argument and give all the phrases you want to search for in one tool call.`,
167
184
  parameters: {
168
185
  type: "object",
169
- required: ["phrase"],
186
+ required: ["query"],
170
187
  properties: {
171
- phrase: {
172
- type: "string",
173
- description:
174
- "The phrase to search the table with. The search phrase is the synatx used by web search engines: use double quotes for exact match, unquoted text for words in any order, dash (minus sign) to exclude a word. Do not use SQL or any other formal query language.",
188
+ query: {
189
+ anyOf: [
190
+ {
191
+ type: "object",
192
+ required: ["phrase"],
193
+ properties: {
194
+ phrase: {
195
+ type: "string",
196
+ description:
197
+ "The phrase to search the table with. The search phrase is the synatx used by web search engines: use double quotes for exact match, unquoted text for words in any order, dash (minus sign) to exclude a word. Do not use SQL or any other formal query language.",
198
+ },
199
+ },
200
+ },
201
+ {
202
+ type: "object",
203
+ required: ["phrases"],
204
+ description:
205
+ "Search the table by any of a number of phrases. This will return any document that matches one or the other of the phrases",
206
+ properties: {
207
+ phrases: {
208
+ type: "array",
209
+ description:
210
+ "A phrase to search the table with. The search phrase is the synatx used by web search engines: use double quotes for exact match, unquoted text for words in any order, dash (minus sign) to exclude a word. Do not use SQL or any other formal query language.",
211
+ items: {
212
+ type: "string",
213
+ },
214
+ },
215
+ },
216
+ },
217
+ ],
175
218
  },
176
219
  },
177
220
  },
@@ -2,11 +2,14 @@ const { div, pre } = require("@saltcorn/markup/tags");
2
2
  const Workflow = require("@saltcorn/data/models/workflow");
3
3
  const Form = require("@saltcorn/data/models/form");
4
4
  const Table = require("@saltcorn/data/models/table");
5
+ const Trigger = require("@saltcorn/data/models/trigger");
6
+ const User = require("@saltcorn/data/models/user");
5
7
  const View = require("@saltcorn/data/models/view");
6
8
  const { getState } = require("@saltcorn/data/db/state");
7
9
  const db = require("@saltcorn/data/db");
8
10
  const { eval_expression } = require("@saltcorn/data/models/expression");
9
11
  const { interpolate } = require("@saltcorn/data/utils");
12
+ const vm = require("vm");
10
13
 
11
14
  class PreloadData {
12
15
  static skill_name = "Preload Data";
@@ -19,28 +22,69 @@ class PreloadData {
19
22
  Object.assign(this, cfg);
20
23
  }
21
24
 
22
- async systemPrompt({ user }) {
23
- const prompts = [];
24
- if (this.add_sys_prompt) prompts.push(this.add_sys_prompt);
25
- const table = Table.findOne(this.table_name);
26
- const q = eval_expression(
27
- this.preload_query,
28
- {},
25
+ async run_the_code({ user, triggering_row }) {
26
+ const sysState = getState();
27
+ const f = vm.runInNewContext(`async () => {${this.code}\n}`, {
28
+ Table,
29
+ row: triggering_row,
30
+ context: triggering_row,
29
31
  user,
30
- "PreloadData query"
31
- );
32
+ User,
33
+ File,
34
+ Buffer,
35
+ Trigger,
36
+ setTimeout,
37
+ interpolate,
38
+ require,
39
+ getConfig: (k) =>
40
+ sysState.isFixedConfig(k) ? undefined : sysState.getConfig(k),
41
+ ...(triggering_row || {}),
42
+ ...sysState.eval_context,
43
+ });
44
+ return await f();
45
+ }
46
+
47
+ async initialInteractions({ user, triggering_row }) {
48
+ if (this._stashed_initial_interactions)
49
+ return this._stashed_initial_interactions;
50
+ if (this.data_source === "Code") {
51
+ const result = await this.run_the_code({ user, triggering_row });
52
+ return result?.interactions;
53
+ }
54
+ }
32
55
 
33
- const rows = await table.getRows(q);
34
- if (this.contents_expr) {
35
- for (const row of rows)
36
- prompts.push(interpolate(this.contents_expr, row, user));
56
+ async systemPrompt({ user, triggering_row }) {
57
+ const prompts = [];
58
+ if (this.data_source === "Code") {
59
+ const result = await this.run_the_code({ user, triggering_row });
60
+ if (typeof result === "string") return result;
61
+ if (result?.interactions)
62
+ this._stashed_initial_interactions = result?.interactions;
63
+ if (result?.systemPrompt) return result.systemPrompt;
37
64
  } else {
38
- const hidden_fields = this.hidden_fields.split(",").map((s) => s.trim());
39
- for (const row of rows) {
40
- hidden_fields.forEach((k) => {
41
- delete row[k];
42
- });
43
- prompts.push(JSON.stringify(row));
65
+ if (this.add_sys_prompt) prompts.push(this.add_sys_prompt);
66
+ const table = Table.findOne(this.table_name);
67
+ const q = eval_expression(
68
+ this.preload_query,
69
+ {},
70
+ user,
71
+ "PreloadData query"
72
+ );
73
+
74
+ const rows = await table.getRows(q);
75
+ if (this.contents_expr) {
76
+ for (const row of rows)
77
+ prompts.push(interpolate(this.contents_expr, row, user));
78
+ } else {
79
+ const hidden_fields = this.hidden_fields
80
+ .split(",")
81
+ .map((s) => s.trim());
82
+ for (const row of rows) {
83
+ hidden_fields.forEach((k) => {
84
+ delete row[k];
85
+ });
86
+ prompts.push(JSON.stringify(row));
87
+ }
44
88
  }
45
89
  }
46
90
  return prompts.join("\n");
@@ -50,6 +94,34 @@ class PreloadData {
50
94
  const allTables = await Table.find();
51
95
 
52
96
  return [
97
+ {
98
+ name: "data_source",
99
+ label: "Data source",
100
+ type: "String",
101
+ required: true,
102
+ attributes: { options: ["Table", "Code"] },
103
+ },
104
+ {
105
+ name: "code",
106
+ label: "Code",
107
+ input_type: "code",
108
+ attributes: { mode: "application/javascript" },
109
+ showIf: { data_source: "Code" },
110
+ class: "validate-statements",
111
+ sublabel:
112
+ "Return string or object <code>{systemPrompt: string, interactions: Interaction[]}</code>",
113
+ validator(s) {
114
+ try {
115
+ let AsyncFunction = Object.getPrototypeOf(
116
+ async function () {}
117
+ ).constructor;
118
+ AsyncFunction(s);
119
+ return true;
120
+ } catch (e) {
121
+ return e.message;
122
+ }
123
+ },
124
+ },
53
125
  {
54
126
  name: "table_name",
55
127
  label: "Table",
@@ -57,23 +129,28 @@ class PreloadData {
57
129
  type: "String",
58
130
  required: true,
59
131
  attributes: { options: allTables.map((t) => t.name) },
132
+ showIf: { data_source: "Table" },
60
133
  },
61
134
  {
62
135
  name: "preload_query",
63
136
  label: "Query",
64
137
  type: "String",
65
138
  class: "validate-expression",
139
+ showIf: { data_source: "Table" },
66
140
  },
67
141
  {
68
142
  name: "add_sys_prompt",
69
143
  label: "Additional prompt",
70
144
  type: "String",
145
+ showIf: { data_source: "Table" },
71
146
  fieldview: "textarea",
72
147
  },
73
148
  {
74
149
  name: "contents_expr",
75
150
  label: "Contents string",
76
151
  type: "String",
152
+ fieldview: "textarea",
153
+ showIf: { data_source: "Table" },
77
154
  sublabel:
78
155
  "Use handlebars (<code>{{ }}</code>) to access fields in each retrieved row",
79
156
  },
@@ -81,6 +158,7 @@ class PreloadData {
81
158
  name: "hidden_fields",
82
159
  label: "Hide fields",
83
160
  type: "String",
161
+ showIf: { data_source: "Table" },
84
162
  sublabel:
85
163
  "Comma-separated list of fields to hide from the prompt, if not using the contents string",
86
164
  },