@saltcorn/copilot 0.4.1 → 0.4.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/user-copilot.js +103 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/copilot",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "AI assistant for building Saltcorn applications",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/user-copilot.js CHANGED
@@ -68,10 +68,12 @@ const configuration_workflow = (req) =>
68
68
  viewtemplate: "Show",
69
69
  });
70
70
  show_view_opts[t.name] = views.map((v) => v.name);
71
- const lviews = await View.find({
72
- table_id: t.id,
73
- viewtemplate: "List",
74
- });
71
+ const lviews = await View.find_table_views_where(
72
+ t.id,
73
+ ({ state_fields, viewrow }) =>
74
+ viewrow.viewtemplate !== "Edit" &&
75
+ state_fields.every((sf) => !sf.required)
76
+ );
75
77
  list_view_opts[t.name] = lviews.map((v) => v.name);
76
78
  }
77
79
  return new Form({
@@ -203,6 +205,20 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
203
205
  "Copilot"
204
206
  )
205
207
  );
208
+ } else if (tool_call.function.name.startsWith("Query")) {
209
+ const query = JSON.parse(tool_call.function.arguments);
210
+ const queryText = query.sql_id_query
211
+ ? query.sql_id_query
212
+ : JSON.stringify(query, null, 2);
213
+ interactMarkups.push(
214
+ wrapSegment(
215
+ wrapCard(
216
+ "Query " + tool_call.function.name.replace("Query", ""),
217
+ pre(queryText)
218
+ ),
219
+ "Copilot"
220
+ )
221
+ );
206
222
  }
207
223
  }
208
224
  } else
@@ -479,7 +495,10 @@ const getCompletionArguments = async (config) => {
479
495
  ];
480
496
  } else Object.assign(fieldProperties(field), properties[field.name]);
481
497
  });
482
-
498
+ properties.sql_id_query = {
499
+ type: "string",
500
+ description: `An SQL query for this table's primary keys (${table.pk_name}). This must select only the primary keys, for example SELECT ${table.name[0]}."${table.pk_name}" from "${table.name}" ${table.name[0]} JOIN ... where... Use this to join other tables in the database. If you use sql_id_query, use it alone, do not combine with other field queries. Any other fields you want to filter on must be accessed in the WHERE clause`,
501
+ };
483
502
  tools.push({
484
503
  type: "function",
485
504
  function: {
@@ -524,9 +543,39 @@ const getCompletionArguments = async (config) => {
524
543
  },
525
544
  });
526
545
  }
546
+ const tables = (await Table.find({})).filter(
547
+ (t) => !t.external && !t.provider_name
548
+ );
549
+ const schemaPrefix = db.getTenantSchemaPrefix();
527
550
  const systemPrompt =
528
551
  "You are helping users retrieve information and perform actions on a relational database" +
529
- config.sys_prompt;
552
+ config.sys_prompt +
553
+ `
554
+ If you are generating SQL, Your database the following tables in PostgreSQL:
555
+
556
+ ` +
557
+ tables
558
+ .map(
559
+ (t) => `CREATE TABLE "${t.name}" (${
560
+ t.description
561
+ ? `
562
+ /* ${t.description} */`
563
+ : ""
564
+ }
565
+ ${t.fields
566
+ .map(
567
+ (f) =>
568
+ ` "${f.name}" ${
569
+ f.primary_key && f.type?.name === "Integer"
570
+ ? "SERIAL PRIMARY KEY"
571
+ : f.sql_type.replace(schemaPrefix, "")
572
+ }`
573
+ )
574
+ .join(",\n")}
575
+ )`
576
+ )
577
+ .join(";\n\n");
578
+ //console.log("sysprompt", systemPrompt);
530
579
 
531
580
  if (tools.length === 0) tools = undefined;
532
581
  return { tools, systemPrompt };
@@ -647,7 +696,10 @@ const process_interaction = async (
647
696
  result,
648
697
  });
649
698
 
650
- if (typeof result === "object" && Object.keys(result || {}).length) {
699
+ if (
700
+ (typeof result === "object" && Object.keys(result || {}).length) ||
701
+ typeof result === "string"
702
+ ) {
651
703
  responses.push(
652
704
  wrapSegment(
653
705
  wrapCard(
@@ -665,7 +717,10 @@ const process_interaction = async (
665
717
  role: "tool",
666
718
  tool_call_id: tool_call.id,
667
719
  name: tool_call.function.name,
668
- content: result ? JSON.stringify(result) : "Action run",
720
+ content:
721
+ result && typeof result !== "string"
722
+ ? JSON.stringify(result)
723
+ : result || "Action run",
669
724
  },
670
725
  ],
671
726
  });
@@ -674,10 +729,46 @@ const process_interaction = async (
674
729
  name: tool_call.function.name.replace("Query", ""),
675
730
  });
676
731
  const query = JSON.parse(tool_call.function.arguments);
677
- const result = await table.getRows(query, {
678
- forUser: req.user,
679
- forPublic: !req.user,
680
- });
732
+ let result;
733
+ if (query.sql_id_query) {
734
+ const is_sqlite = db.isSQLite;
735
+
736
+ const client = is_sqlite ? db : await db.getClient();
737
+ await client.query(`BEGIN;`);
738
+ if (!is_sqlite) {
739
+ await client.query(
740
+ `SET LOCAL search_path TO "${db.getTenantSchema()}";`
741
+ );
742
+ await client.query(
743
+ `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`
744
+ );
745
+ }
746
+
747
+ const { rows } = await client.query(query.sql_id_query);
748
+ await client.query(`ROLLBACK;`);
749
+
750
+ if (!is_sqlite) client.release(true);
751
+ result = await table.getRows(
752
+ { [table.pk_name]: { in: rows.map((r) => r[table.pk_name]) } },
753
+ {
754
+ forUser: req.user,
755
+ forPublic: !req.user,
756
+ }
757
+ );
758
+ responses.push(
759
+ wrapSegment(
760
+ wrapCard(
761
+ "Query " + tool_call.function.name.replace("Query", ""),
762
+ pre(query.sql_id_query)
763
+ ),
764
+ "Copilot"
765
+ )
766
+ );
767
+ } else
768
+ result = await table.getRows(query, {
769
+ forUser: req.user,
770
+ forPublic: !req.user,
771
+ });
681
772
  await addToContext(run, {
682
773
  interactions: [
683
774
  {