@saltcorn/agents 0.1.3 → 0.2.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/agent-view.js +115 -27
- package/package.json +1 -1
- package/skills/EmbeddingRetrieval.js +46 -11
package/agent-view.js
CHANGED
|
@@ -2,6 +2,7 @@ const Field = require("@saltcorn/data/models/field");
|
|
|
2
2
|
const Table = require("@saltcorn/data/models/table");
|
|
3
3
|
const Form = require("@saltcorn/data/models/form");
|
|
4
4
|
const View = require("@saltcorn/data/models/view");
|
|
5
|
+
const File = require("@saltcorn/data/models/file");
|
|
5
6
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
6
7
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
7
8
|
const db = require("@saltcorn/data/db");
|
|
@@ -26,6 +27,7 @@ const {
|
|
|
26
27
|
small,
|
|
27
28
|
form,
|
|
28
29
|
textarea,
|
|
30
|
+
label,
|
|
29
31
|
a,
|
|
30
32
|
} = require("@saltcorn/markup/tags");
|
|
31
33
|
const { getState } = require("@saltcorn/data/db/state");
|
|
@@ -89,6 +91,19 @@ const configuration_workflow = (req) =>
|
|
|
89
91
|
sublabel:
|
|
90
92
|
"Appears below the input box. Use for additional instructions.",
|
|
91
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: "image_upload",
|
|
96
|
+
label: "Upload images",
|
|
97
|
+
sublabel: "Allow the user to upload images",
|
|
98
|
+
type: "Bool",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "image_base64",
|
|
102
|
+
label: "base64 encode",
|
|
103
|
+
sublabel: "Use base64 encoding in the OpenAI API",
|
|
104
|
+
type: "Bool",
|
|
105
|
+
showIf: { image_upload: true },
|
|
106
|
+
},
|
|
92
107
|
],
|
|
93
108
|
});
|
|
94
109
|
},
|
|
@@ -98,10 +113,30 @@ const configuration_workflow = (req) =>
|
|
|
98
113
|
|
|
99
114
|
const get_state_fields = () => [];
|
|
100
115
|
|
|
116
|
+
const uploadForm = (viewname, req) =>
|
|
117
|
+
span(
|
|
118
|
+
{
|
|
119
|
+
class: "attach_agent_image_wrap",
|
|
120
|
+
},
|
|
121
|
+
label(
|
|
122
|
+
{ class: "btn-link", for: "attach_agent_image" },
|
|
123
|
+
i({ class: "fas fa-paperclip" })
|
|
124
|
+
),
|
|
125
|
+
input({
|
|
126
|
+
id: "attach_agent_image",
|
|
127
|
+
name: "file",
|
|
128
|
+
type: "file",
|
|
129
|
+
class: "d-none",
|
|
130
|
+
accept: "image/*",
|
|
131
|
+
onchange: `agent_file_attach(event)`,
|
|
132
|
+
}),
|
|
133
|
+
span({ class: "ms-2 filename-label" })
|
|
134
|
+
);
|
|
135
|
+
|
|
101
136
|
const run = async (
|
|
102
137
|
table_id,
|
|
103
138
|
viewname,
|
|
104
|
-
{ action_id, show_prev_runs, placeholder, explainer },
|
|
139
|
+
{ action_id, show_prev_runs, placeholder, explainer, image_upload },
|
|
105
140
|
state,
|
|
106
141
|
{ res, req }
|
|
107
142
|
) => {
|
|
@@ -122,13 +157,33 @@ const run = async (
|
|
|
122
157
|
for (const interact of run.context.interactions) {
|
|
123
158
|
switch (interact.role) {
|
|
124
159
|
case "user":
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
160
|
+
console.log(interact.content);
|
|
161
|
+
if (interact.content?.[0]?.type === "image_url") {
|
|
162
|
+
const image_url = interact.content[0].image_url.url;
|
|
163
|
+
if (image_url.startsWith("data"))
|
|
164
|
+
interactMarkups.push(
|
|
165
|
+
div(
|
|
166
|
+
{ class: "interaction-segment" },
|
|
167
|
+
span({ class: "badge bg-secondary" }, "You"),
|
|
168
|
+
"File"
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
else
|
|
172
|
+
interactMarkups.push(
|
|
173
|
+
div(
|
|
174
|
+
{ class: "interaction-segment" },
|
|
175
|
+
span({ class: "badge bg-secondary" }, "You"),
|
|
176
|
+
a({ href: image_url, target: "_blank" }, "File")
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
} else
|
|
180
|
+
interactMarkups.push(
|
|
181
|
+
div(
|
|
182
|
+
{ class: "interaction-segment" },
|
|
183
|
+
span({ class: "badge bg-secondary" }, "You"),
|
|
184
|
+
md.render(interact.content)
|
|
185
|
+
)
|
|
186
|
+
);
|
|
132
187
|
break;
|
|
133
188
|
case "assistant":
|
|
134
189
|
case "system":
|
|
@@ -216,7 +271,7 @@ const run = async (
|
|
|
216
271
|
}
|
|
217
272
|
const input_form = form(
|
|
218
273
|
{
|
|
219
|
-
onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact',
|
|
274
|
+
onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact', new FormData(this), processCopilotResponse);return false;`,
|
|
220
275
|
class: "form-namespace copilot mt-2",
|
|
221
276
|
method: "post",
|
|
222
277
|
},
|
|
@@ -246,6 +301,7 @@ const run = async (
|
|
|
246
301
|
{ class: "submit-button p-2", onclick: "$('form.copilot').submit()" },
|
|
247
302
|
i({ id: "sendbuttonicon", class: "far fa-paper-plane" })
|
|
248
303
|
),
|
|
304
|
+
image_upload && uploadForm(viewname, req),
|
|
249
305
|
explainer && small({ class: "explainer" }, i(explainer))
|
|
250
306
|
)
|
|
251
307
|
);
|
|
@@ -291,17 +347,6 @@ const run = async (
|
|
|
291
347
|
{ class: "card" },
|
|
292
348
|
div(
|
|
293
349
|
{ class: "card-body" },
|
|
294
|
-
script({
|
|
295
|
-
src: `/static_assets/${db.connectObj.version_tag}/mermaid.min.js`,
|
|
296
|
-
}),
|
|
297
|
-
script(
|
|
298
|
-
{ type: "module" },
|
|
299
|
-
`mermaid.initialize({securityLevel: 'loose'${
|
|
300
|
-
getState().getLightDarkMode(req.user) === "dark"
|
|
301
|
-
? ",theme: 'dark',"
|
|
302
|
-
: ""
|
|
303
|
-
}});`
|
|
304
|
-
),
|
|
305
350
|
div({ id: "copilotinteractions" }, runInteractions),
|
|
306
351
|
input_form,
|
|
307
352
|
style(
|
|
@@ -313,11 +358,17 @@ const run = async (
|
|
|
313
358
|
div.prevcopilotrun i.fa-trash-alt {display: none;}
|
|
314
359
|
div.prevcopilotrun:hover i.fa-trash-alt {display: block;}
|
|
315
360
|
.copilot-entry .submit-button:hover { cursor: pointer}
|
|
361
|
+
.copilot-entry span.attach_agent_image_wrap i:hover { cursor: pointer}
|
|
316
362
|
|
|
317
363
|
.copilot-entry .submit-button {
|
|
318
364
|
position: relative;
|
|
319
365
|
top: -1.8rem;
|
|
320
366
|
left: 0.1rem;
|
|
367
|
+
}
|
|
368
|
+
.copilot-entry span.attach_agent_image_wrap {
|
|
369
|
+
position: relative;
|
|
370
|
+
top: -1.8rem;
|
|
371
|
+
left: 0.2rem;
|
|
321
372
|
}
|
|
322
373
|
.copilot-entry .explainer {
|
|
323
374
|
position: relative;
|
|
@@ -333,17 +384,25 @@ const run = async (
|
|
|
333
384
|
text-overflow: ellipsis;}`
|
|
334
385
|
),
|
|
335
386
|
script(`function processCopilotResponse(res) {
|
|
387
|
+
const hadFile = $("input#attach_agent_image").val();
|
|
388
|
+
$("span.filename-label").text("");
|
|
389
|
+
$("input#attach_agent_image").val(null);
|
|
336
390
|
$("#sendbuttonicon").attr("class","far fa-paper-plane");
|
|
337
391
|
const $runidin= $("input[name=run_id")
|
|
338
392
|
if(res.run_id && (!$runidin.val() || $runidin.val()=="undefined"))
|
|
339
393
|
$runidin.val(res.run_id);
|
|
340
394
|
const wrapSegment = (html, who) => '<div class="interaction-segment"><span class="badge bg-secondary">'+who+'</span>'+html+'</div>'
|
|
341
395
|
$("#copilotinteractions").append(wrapSegment('<p>'+$("textarea[name=userinput]").val()+'</p>', "You"))
|
|
342
|
-
|
|
396
|
+
if(hadFile)
|
|
397
|
+
$("#copilotinteractions").append(wrapSegment('File', "You"))
|
|
398
|
+
$("textarea[name=userinput]").val("")
|
|
343
399
|
|
|
344
400
|
if(res.response)
|
|
345
401
|
$("#copilotinteractions").append(res.response)
|
|
346
402
|
}
|
|
403
|
+
function agent_file_attach(e) {
|
|
404
|
+
$(".attach_agent_image_wrap span.filename-label").text(e.target.files[0].name)
|
|
405
|
+
}
|
|
347
406
|
function restore_old_button_elem(btn) {
|
|
348
407
|
const oldText = $(btn).data("old-text");
|
|
349
408
|
btn.html(oldText);
|
|
@@ -403,12 +462,6 @@ const run = async (
|
|
|
403
462
|
: main_chat;
|
|
404
463
|
};
|
|
405
464
|
|
|
406
|
-
/*
|
|
407
|
-
|
|
408
|
-
build a workflow that asks the user for their name and age
|
|
409
|
-
|
|
410
|
-
*/
|
|
411
|
-
|
|
412
465
|
const interact = async (table_id, viewname, config, body, { req, res }) => {
|
|
413
466
|
const { userinput, run_id } = body;
|
|
414
467
|
let run;
|
|
@@ -429,6 +482,41 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
429
482
|
interactions: [{ role: "user", content: userinput }],
|
|
430
483
|
});
|
|
431
484
|
}
|
|
485
|
+
if (config.image_upload && req.files?.file) {
|
|
486
|
+
const file = await File.from_req_files(
|
|
487
|
+
req.files.file,
|
|
488
|
+
req.user ? req.user.id : null,
|
|
489
|
+
100
|
|
490
|
+
// file_field?.attributes?.folder
|
|
491
|
+
);
|
|
492
|
+
const baseUrl = getState().getConfig("base_url").replace(/\/$/, "");
|
|
493
|
+
let imageurl;
|
|
494
|
+
if (
|
|
495
|
+
!config.image_base64 &&
|
|
496
|
+
baseUrl &&
|
|
497
|
+
!baseUrl.includes("http://localhost:")
|
|
498
|
+
) {
|
|
499
|
+
imageurl = `${baseUrl}/files/serve/${file.path_to_serve}`;
|
|
500
|
+
} else {
|
|
501
|
+
const b64 = await file.get_contents("base64");
|
|
502
|
+
imageurl = `data:${file.mimetype};base64,${b64}`;
|
|
503
|
+
}
|
|
504
|
+
await addToContext(run, {
|
|
505
|
+
interactions: [
|
|
506
|
+
{
|
|
507
|
+
role: "user",
|
|
508
|
+
content: [
|
|
509
|
+
{
|
|
510
|
+
type: "image_url",
|
|
511
|
+
image_url: {
|
|
512
|
+
url: imageurl,
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
});
|
|
519
|
+
}
|
|
432
520
|
const action = await Trigger.findOne({ id: config.action_id });
|
|
433
521
|
|
|
434
522
|
return await process_interaction(run, action.configuration, req, action.name);
|
package/package.json
CHANGED
|
@@ -37,17 +37,26 @@ class RetrievalByEmbedding {
|
|
|
37
37
|
const allTables = await Table.find();
|
|
38
38
|
const tableOpts = [];
|
|
39
39
|
const relation_opts = {};
|
|
40
|
+
const list_view_opts = {};
|
|
40
41
|
for (const table of allTables) {
|
|
41
|
-
table.fields
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
for (const f of table.fields) {
|
|
43
|
+
if (f.type?.name !== "PGVector") continue;
|
|
44
|
+
const relNm = `${table.name}.${f.name}`;
|
|
45
|
+
tableOpts.push(relNm);
|
|
46
|
+
list_view_opts[relNm] = [""];
|
|
47
|
+
const fkeys = table.fields.filter((f) => f.is_fkey).map((f) => f.name);
|
|
48
|
+
relation_opts[relNm] = ["", ...fkeys];
|
|
49
|
+
for (const fkeyField of table.fields.filter((f) => f.is_fkey)) {
|
|
50
|
+
const t = Table.findOne(fkeyField.reftable_name);
|
|
51
|
+
const lviews = await View.find_table_views_where(
|
|
52
|
+
t.id,
|
|
53
|
+
({ state_fields, viewrow }) =>
|
|
54
|
+
viewrow.viewtemplate !== "Edit" &&
|
|
55
|
+
state_fields.every((sf) => !sf.required)
|
|
56
|
+
);
|
|
57
|
+
list_view_opts[relNm].push(...lviews.map((v) => v.name));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
61
|
return [
|
|
53
62
|
{
|
|
@@ -74,6 +83,14 @@ class RetrievalByEmbedding {
|
|
|
74
83
|
required: true,
|
|
75
84
|
attributes: { calcOptions: ["vec_field", relation_opts] },
|
|
76
85
|
},
|
|
86
|
+
{
|
|
87
|
+
name: "list_view",
|
|
88
|
+
label: "List view",
|
|
89
|
+
type: "String",
|
|
90
|
+
attributes: {
|
|
91
|
+
calcOptions: ["table_name", list_view_opts],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
77
94
|
{
|
|
78
95
|
name: "hidden_fields",
|
|
79
96
|
label: "Hide fields",
|
|
@@ -90,7 +107,7 @@ class RetrievalByEmbedding {
|
|
|
90
107
|
name: "add_sys_prompt",
|
|
91
108
|
label: "Additional prompt",
|
|
92
109
|
type: "String",
|
|
93
|
-
fieldview: "textarea"
|
|
110
|
+
fieldview: "textarea",
|
|
94
111
|
},
|
|
95
112
|
];
|
|
96
113
|
}
|
|
@@ -153,6 +170,24 @@ class RetrievalByEmbedding {
|
|
|
153
170
|
return { rows: docs };
|
|
154
171
|
}
|
|
155
172
|
},
|
|
173
|
+
renderToolResponse: async ({ response, rows }, { req }) => {
|
|
174
|
+
if (rows) {
|
|
175
|
+
const view = View.findOne({ name: this.list_view });
|
|
176
|
+
|
|
177
|
+
if (view) {
|
|
178
|
+
const viewRes = await view.run(
|
|
179
|
+
{
|
|
180
|
+
[table_docs.pk_name]: {
|
|
181
|
+
in: rows.map((r) => r[table_docs.pk_name]),
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{ req }
|
|
185
|
+
);
|
|
186
|
+
return viewRes;
|
|
187
|
+
} else return "";
|
|
188
|
+
}
|
|
189
|
+
return div({ class: "border border-success p-2 m-2" }, response);
|
|
190
|
+
},
|
|
156
191
|
function: {
|
|
157
192
|
name: this.toolName,
|
|
158
193
|
description: `Search the ${table_docs.name``} archive${
|