@saltcorn/copilot 0.4.2 → 0.4.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/package.json +1 -1
- package/user-copilot.js +86 -93
package/package.json
CHANGED
package/user-copilot.js
CHANGED
|
@@ -107,6 +107,13 @@ const configuration_workflow = (req) =>
|
|
|
107
107
|
calcOptions: ["table_name", list_view_opts],
|
|
108
108
|
},
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
name: "exclude_fields",
|
|
112
|
+
label: "Exclude fields",
|
|
113
|
+
sublabel:
|
|
114
|
+
"Exclude fields from the chat context. Comma-separated list.",
|
|
115
|
+
type: "String",
|
|
116
|
+
},
|
|
110
117
|
],
|
|
111
118
|
}),
|
|
112
119
|
],
|
|
@@ -441,71 +448,19 @@ const getCompletionArguments = async (config) => {
|
|
|
441
448
|
|
|
442
449
|
const table = Table.findOne({ name: tableCfg.table_name });
|
|
443
450
|
|
|
444
|
-
table.fields
|
|
445
|
-
.filter((f) => !f.primary_key)
|
|
446
|
-
.forEach((field) => {
|
|
447
|
-
properties[field.name] = {
|
|
448
|
-
description: field.label + " " + field.description || "",
|
|
449
|
-
};
|
|
450
|
-
if (field.type.name === "String") {
|
|
451
|
-
properties[field.name].anyOf = [
|
|
452
|
-
{ type: "string", description: "Match this value exactly" },
|
|
453
|
-
{
|
|
454
|
-
type: "object",
|
|
455
|
-
properties: {
|
|
456
|
-
ilike: {
|
|
457
|
-
type: "string",
|
|
458
|
-
description: "Case insensitive substring match.",
|
|
459
|
-
},
|
|
460
|
-
in: {
|
|
461
|
-
type: "array",
|
|
462
|
-
description: "Match any one of these values",
|
|
463
|
-
items: {
|
|
464
|
-
type: "string",
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
];
|
|
470
|
-
}
|
|
471
|
-
if (field.type.name === "Integer" || field.type.name === "Float") {
|
|
472
|
-
const basetype = field.type.name === "Integer" ? "integer" : "number";
|
|
473
|
-
properties[field.name].anyOf = [
|
|
474
|
-
{ type: basetype, description: "Match this value exactly" },
|
|
475
|
-
{
|
|
476
|
-
type: "object",
|
|
477
|
-
properties: {
|
|
478
|
-
gt: {
|
|
479
|
-
type: basetype,
|
|
480
|
-
description: "Greater than this value",
|
|
481
|
-
},
|
|
482
|
-
lt: {
|
|
483
|
-
type: basetype,
|
|
484
|
-
description: "Less than this value",
|
|
485
|
-
},
|
|
486
|
-
in: {
|
|
487
|
-
type: "array",
|
|
488
|
-
description: "Match any one of these values",
|
|
489
|
-
items: {
|
|
490
|
-
type: basetype,
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
];
|
|
496
|
-
} else Object.assign(fieldProperties(field), properties[field.name]);
|
|
497
|
-
});
|
|
498
451
|
properties.sql_id_query = {
|
|
499
452
|
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
|
|
453
|
+
description: `An SQL query for this table's primary keys (${table.pk_name}). This must select only the primary keys (even if the user wants a count), 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.`,
|
|
454
|
+
};
|
|
455
|
+
properties.is_count = {
|
|
456
|
+
type: "boolean",
|
|
457
|
+
description: `Is the only desired output a count? Make this true if the user wants a count of rows`,
|
|
501
458
|
};
|
|
502
459
|
tools.push({
|
|
503
460
|
type: "function",
|
|
504
461
|
function: {
|
|
505
462
|
name: "Query" + table.name,
|
|
506
|
-
description: `Query the ${table.name} table
|
|
507
|
-
table.description || ""
|
|
508
|
-
}`,
|
|
463
|
+
description: `Query the ${table.name} table and show the results to the user in a grid format`,
|
|
509
464
|
parameters: {
|
|
510
465
|
type: "object",
|
|
511
466
|
//required: ["action_javascript_code", "action_name"],
|
|
@@ -574,7 +529,13 @@ ${t.fields
|
|
|
574
529
|
.join(",\n")}
|
|
575
530
|
)`
|
|
576
531
|
)
|
|
577
|
-
.join(";\n\n")
|
|
532
|
+
.join(";\n\n") +
|
|
533
|
+
`
|
|
534
|
+
|
|
535
|
+
If the user asks to you show rows from a table, just run the query tool for that table. This will display the result to the user.
|
|
536
|
+
You should not render the results again, that will cause them to be displayed twice. Only if the user asks for a summary
|
|
537
|
+
or a calculation based on the query results should you show that requested summary or calculation.
|
|
538
|
+
`;
|
|
578
539
|
//console.log("sysprompt", systemPrompt);
|
|
579
540
|
|
|
580
541
|
if (tools.length === 0) tools = undefined;
|
|
@@ -611,7 +572,24 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
611
572
|
};
|
|
612
573
|
|
|
613
574
|
const renderQueryInteraction = async (table, result, config, req) => {
|
|
614
|
-
if (result
|
|
575
|
+
if (typeof result === "number")
|
|
576
|
+
return wrapSegment(
|
|
577
|
+
wrapCard(
|
|
578
|
+
"Query " + table.name,
|
|
579
|
+
//div("Query: ", code(JSON.stringify(query))),
|
|
580
|
+
`${result}`
|
|
581
|
+
),
|
|
582
|
+
"Copilot"
|
|
583
|
+
);
|
|
584
|
+
if (result.length === 0)
|
|
585
|
+
return wrapSegment(
|
|
586
|
+
wrapCard(
|
|
587
|
+
"Query " + table.name,
|
|
588
|
+
//div("Query: ", code(JSON.stringify(query))),
|
|
589
|
+
"No rows found"
|
|
590
|
+
),
|
|
591
|
+
"Copilot"
|
|
592
|
+
);
|
|
615
593
|
|
|
616
594
|
const tableCfg = config.tables.find((t) => t.table_name === table.name);
|
|
617
595
|
let viewRes = "";
|
|
@@ -728,47 +706,62 @@ const process_interaction = async (
|
|
|
728
706
|
const table = Table.findOne({
|
|
729
707
|
name: tool_call.function.name.replace("Query", ""),
|
|
730
708
|
});
|
|
709
|
+
const tableCfg = config.tables.find((t) => t.table_name === table.name);
|
|
731
710
|
const query = JSON.parse(tool_call.function.arguments);
|
|
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
711
|
|
|
747
|
-
|
|
748
|
-
await client.query(`ROLLBACK;`);
|
|
712
|
+
const is_sqlite = db.isSQLite;
|
|
749
713
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
forPublic: !req.user,
|
|
756
|
-
}
|
|
714
|
+
const client = is_sqlite ? db : await db.getClient();
|
|
715
|
+
await client.query(`BEGIN;`);
|
|
716
|
+
if (!is_sqlite) {
|
|
717
|
+
await client.query(
|
|
718
|
+
`SET LOCAL search_path TO "${db.getTenantSchema()}";`
|
|
757
719
|
);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
wrapCard(
|
|
761
|
-
"Query " + tool_call.function.name.replace("Query", ""),
|
|
762
|
-
pre(query.sql_id_query)
|
|
763
|
-
),
|
|
764
|
-
"Copilot"
|
|
765
|
-
)
|
|
720
|
+
await client.query(
|
|
721
|
+
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`
|
|
766
722
|
);
|
|
767
|
-
}
|
|
768
|
-
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const { rows } = await client.query(query.sql_id_query);
|
|
726
|
+
await client.query(`ROLLBACK;`);
|
|
727
|
+
|
|
728
|
+
if (!is_sqlite) client.release(true);
|
|
729
|
+
let result;
|
|
730
|
+
const id_query = {
|
|
731
|
+
[table.pk_name]: { in: rows.map((r) => r[table.pk_name]) },
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
if (query.is_count) {
|
|
735
|
+
const role = req.user?.role_id || 100;
|
|
736
|
+
if (role <= table.min_role_read) {
|
|
737
|
+
result = await table.countRows(id_query);
|
|
738
|
+
} else result = "Not authorized";
|
|
739
|
+
} else {
|
|
740
|
+
result = await table.getRows(id_query, {
|
|
769
741
|
forUser: req.user,
|
|
770
742
|
forPublic: !req.user,
|
|
771
743
|
});
|
|
744
|
+
if (tableCfg.exclude_fields) {
|
|
745
|
+
const fields = tableCfg.exclude_fields
|
|
746
|
+
.split(",")
|
|
747
|
+
.map((s) => s.trim());
|
|
748
|
+
fields.forEach((f) => {
|
|
749
|
+
result.forEach((r) => {
|
|
750
|
+
delete r[f];
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
responses.push(
|
|
756
|
+
wrapSegment(
|
|
757
|
+
wrapCard(
|
|
758
|
+
"Query " + tool_call.function.name.replace("Query", ""),
|
|
759
|
+
pre(query.sql_id_query)
|
|
760
|
+
),
|
|
761
|
+
"Copilot"
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
|
|
772
765
|
await addToContext(run, {
|
|
773
766
|
interactions: [
|
|
774
767
|
{
|