@saltcorn/agents 0.6.2 → 0.6.4
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 +163 -42
- package/common.js +23 -6
- package/index.js +1 -0
- package/package.json +1 -1
package/agent-view.js
CHANGED
|
@@ -31,6 +31,7 @@ const {
|
|
|
31
31
|
label,
|
|
32
32
|
a,
|
|
33
33
|
br,
|
|
34
|
+
img,
|
|
34
35
|
} = require("@saltcorn/markup/tags");
|
|
35
36
|
const { getState } = require("@saltcorn/data/db/state");
|
|
36
37
|
const {
|
|
@@ -45,9 +46,12 @@ const {
|
|
|
45
46
|
is_debug_mode,
|
|
46
47
|
get_initial_interactions,
|
|
47
48
|
get_skill_instances,
|
|
49
|
+
saveInteractions,
|
|
48
50
|
} = require("./common");
|
|
49
51
|
const MarkdownIt = require("markdown-it"),
|
|
50
52
|
md = new MarkdownIt();
|
|
53
|
+
const { isWeb } = require("@saltcorn/data/utils");
|
|
54
|
+
const path = require("path");
|
|
51
55
|
|
|
52
56
|
const configuration_workflow = (req) =>
|
|
53
57
|
new Workflow({
|
|
@@ -169,6 +173,7 @@ const uploadForm = (viewname, req) =>
|
|
|
169
173
|
type: "file",
|
|
170
174
|
class: "d-none",
|
|
171
175
|
accept: "image/*",
|
|
176
|
+
multiple: true,
|
|
172
177
|
onchange: `agent_file_attach(event)`,
|
|
173
178
|
}),
|
|
174
179
|
span({ class: "ms-2 filename-label" }),
|
|
@@ -270,6 +275,7 @@ const run = async (
|
|
|
270
275
|
interactMarkups.push(...run.context.html_interactions);
|
|
271
276
|
} else
|
|
272
277
|
for (const interact of run.context.interactions) {
|
|
278
|
+
//legacy
|
|
273
279
|
switch (interact.role) {
|
|
274
280
|
case "user":
|
|
275
281
|
if (interact.content?.[0]?.type === "image_url") {
|
|
@@ -484,7 +490,7 @@ const run = async (
|
|
|
484
490
|
? div(
|
|
485
491
|
div(
|
|
486
492
|
{
|
|
487
|
-
class: "d-flex justify-content-between align-middle mb-2",
|
|
493
|
+
class: "d-flex flex-wrap justify-content-between align-middle mb-2",
|
|
488
494
|
},
|
|
489
495
|
div(
|
|
490
496
|
{ class: "d-flex" },
|
|
@@ -497,7 +503,7 @@ const run = async (
|
|
|
497
503
|
button(
|
|
498
504
|
{
|
|
499
505
|
type: "button",
|
|
500
|
-
class: "btn btn-secondary btn-sm
|
|
506
|
+
class: "btn btn-secondary btn-sm pt-0 pb-1",
|
|
501
507
|
style: "font-size: 0.9em;height:1.5em",
|
|
502
508
|
onclick: "unset_state_field('run_id')",
|
|
503
509
|
title: "New session",
|
|
@@ -513,7 +519,10 @@ const run = async (
|
|
|
513
519
|
},
|
|
514
520
|
div(
|
|
515
521
|
{ class: "d-flex justify-content-between" },
|
|
516
|
-
|
|
522
|
+
span(
|
|
523
|
+
{ class: "text-truncate", style: "min-width:0" },
|
|
524
|
+
localeDateTime(run.started_at),
|
|
525
|
+
),
|
|
517
526
|
i({
|
|
518
527
|
class: "far fa-trash-alt",
|
|
519
528
|
onclick: `delprevrun(event, ${run.id})`,
|
|
@@ -549,7 +558,15 @@ const run = async (
|
|
|
549
558
|
`div.interaction-segment:not(:first-child) {border-top: 1px solid #e7e7e7; }
|
|
550
559
|
div.interaction-segment {padding-top: 5px;padding-bottom: 5px;}
|
|
551
560
|
div.interaction-segment p {margin-bottom: 0px;}
|
|
552
|
-
div.interaction-segment div.card {margin-top: 0.5rem;}
|
|
561
|
+
div.interaction-segment div.card {margin-top: 0.5rem;}
|
|
562
|
+
div.interaction-segment.to-right {
|
|
563
|
+
display: flex;
|
|
564
|
+
flex-direction: row-reverse;
|
|
565
|
+
}
|
|
566
|
+
div.interaction-segment.to-right div.badgewrap {
|
|
567
|
+
display: flex;
|
|
568
|
+
flex-direction: row-reverse;
|
|
569
|
+
}
|
|
553
570
|
div.prevcopilotrun:hover {cursor: pointer; background-color: var(--tblr-secondary-bg-subtle, var(--bs-secondary-bg-subtle, gray));}
|
|
554
571
|
div.prevcopilotrun i.fa-trash-alt {display: none;}
|
|
555
572
|
div.prevcopilotrun:hover i.fa-trash-alt {display: block;}
|
|
@@ -584,9 +601,15 @@ const run = async (
|
|
|
584
601
|
cursor: pointer;
|
|
585
602
|
}
|
|
586
603
|
.copilot-entry span.attach_agent_image_wrap {
|
|
587
|
-
position: relative;
|
|
604
|
+
position: relative;
|
|
588
605
|
top: -1.8rem;
|
|
589
|
-
left: 0.2rem;
|
|
606
|
+
left: 0.2rem;
|
|
607
|
+
}
|
|
608
|
+
.copilot-entry.dragover {
|
|
609
|
+
outline: 2px dashed var(--tblr-primary, #0054a6);
|
|
610
|
+
outline-offset: -2px;
|
|
611
|
+
background: var(--tblr-primary-bg-subtle, rgba(0, 84, 166, 0.05));
|
|
612
|
+
border-radius: 0.25rem;
|
|
590
613
|
}
|
|
591
614
|
.copilot-entry .explainer {
|
|
592
615
|
position: relative;
|
|
@@ -618,19 +641,25 @@ const run = async (
|
|
|
618
641
|
function get_run_id(elem) {
|
|
619
642
|
return $("input[name=run_id").val()
|
|
620
643
|
}
|
|
621
|
-
function processCopilotResponse(res) {
|
|
644
|
+
function processCopilotResponse(res, not_final) {
|
|
622
645
|
console.log("processCopilotResponse", res)
|
|
623
|
-
const
|
|
624
|
-
let fileBadge =
|
|
625
|
-
|
|
646
|
+
const fileInput = $("input#attach_agent_image")[0];
|
|
647
|
+
let fileBadge = "";
|
|
648
|
+
if (fileInput?.files?.length) {
|
|
649
|
+
fileBadge = Array.from(fileInput.files).map(f =>
|
|
650
|
+
'<span class="badge text-bg-info"><i class="fas fa-image me-1"></i>'+f.name+'</span>'
|
|
651
|
+
).join(" ");
|
|
652
|
+
}
|
|
653
|
+
$("span.filename-label").text("").removeClass("me-2");
|
|
654
|
+
_agentDT.items.clear();
|
|
626
655
|
$("input#attach_agent_image").val(null);
|
|
627
|
-
$("#sendbuttonicon").attr("class","far fa-paper-plane");
|
|
656
|
+
if(!not_final || (!${JSON.stringify(dyn_updates)})) $("#sendbuttonicon").attr("class","far fa-paper-plane");
|
|
628
657
|
const $runidin= $("input[name=run_id")
|
|
629
658
|
if(res.run_id && (!$runidin.val() || $runidin.val()=="undefined"))
|
|
630
659
|
$runidin.val(res.run_id);
|
|
631
660
|
const wrapSegment = (html, who) => '<div class="interaction-segment"><span class="badge bg-secondary">'+who+'</span>'+html+'</div>'
|
|
632
661
|
const user_input = $("textarea[name=userinput]").val()
|
|
633
|
-
if(user_input)
|
|
662
|
+
if(user_input && (!${JSON.stringify(dyn_updates)}))
|
|
634
663
|
$("#copilotinteractions").append(wrapSegment('<p>'+user_input+'</p>'+fileBadge, "You"))
|
|
635
664
|
$("textarea[name=userinput]").val("")
|
|
636
665
|
$('form.agent-view div.next_response_scratch').html("")
|
|
@@ -639,8 +668,39 @@ const run = async (
|
|
|
639
668
|
$("#copilotinteractions").append(res.response)
|
|
640
669
|
}
|
|
641
670
|
window.processCopilotResponse = processCopilotResponse;
|
|
671
|
+
window.final_agent_response = () => {
|
|
672
|
+
$("#sendbuttonicon").attr("class","far fa-paper-plane");
|
|
673
|
+
}
|
|
674
|
+
const _agentDT = new DataTransfer();
|
|
675
|
+
function setAgentFiles(files) {
|
|
676
|
+
for (const f of files) _agentDT.items.add(f);
|
|
677
|
+
document.getElementById('attach_agent_image').files = _agentDT.files;
|
|
678
|
+
updateFileLabel();
|
|
679
|
+
}
|
|
680
|
+
function updateFileLabel() {
|
|
681
|
+
const n = _agentDT.files.length;
|
|
682
|
+
const $label = $(".attach_agent_image_wrap span.filename-label");
|
|
683
|
+
if (n === 0) {
|
|
684
|
+
$label.html("").removeClass("me-2");
|
|
685
|
+
} else {
|
|
686
|
+
$label.addClass("me-2");
|
|
687
|
+
const text = n === 1 ? _agentDT.files[0].name : n + " files";
|
|
688
|
+
$label.html(${
|
|
689
|
+
isWeb(req)
|
|
690
|
+
? `text + ' <span class="badge text-bg-secondary" style="cursor:pointer;font-size:.65em;vertical-align:middle" onclick="clearAgentFiles()" title="Remove files">×</span>'`
|
|
691
|
+
: `'<span style="max-width:8em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:middle">' + text + '</span> <span class="badge text-bg-secondary" style="cursor:pointer;font-size:.65em;vertical-align:middle" onclick="clearAgentFiles()" title="Remove files">×</span>'`
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function clearAgentFiles() {
|
|
696
|
+
_agentDT.items.clear();
|
|
697
|
+
$("input#attach_agent_image").val(null);
|
|
698
|
+
updateFileLabel();
|
|
699
|
+
}
|
|
700
|
+
window.clearAgentFiles = clearAgentFiles;
|
|
642
701
|
function agent_file_attach(e) {
|
|
643
|
-
|
|
702
|
+
_agentDT.items.clear();
|
|
703
|
+
setAgentFiles(e.target.files);
|
|
644
704
|
}
|
|
645
705
|
function restore_old_button_elem(btn) {
|
|
646
706
|
const oldText = $(btn).data("old-text");
|
|
@@ -692,6 +752,44 @@ const run = async (
|
|
|
692
752
|
}
|
|
693
753
|
}
|
|
694
754
|
document.getElementById("inputuserinput").addEventListener("keydown", submitOnEnter);
|
|
755
|
+
if (document.getElementById('attach_agent_image')) {
|
|
756
|
+
let _dragCtr = 0;
|
|
757
|
+
const _copilotEntry = document.querySelector('.copilot-entry');
|
|
758
|
+
_copilotEntry.addEventListener('dragover', function(e) {
|
|
759
|
+
e.preventDefault();
|
|
760
|
+
});
|
|
761
|
+
_copilotEntry.addEventListener('dragenter', function(e) {
|
|
762
|
+
e.preventDefault();
|
|
763
|
+
_dragCtr++;
|
|
764
|
+
this.classList.add('dragover');
|
|
765
|
+
});
|
|
766
|
+
_copilotEntry.addEventListener('dragleave', function(e) {
|
|
767
|
+
_dragCtr--;
|
|
768
|
+
if (_dragCtr === 0) this.classList.remove('dragover');
|
|
769
|
+
});
|
|
770
|
+
_copilotEntry.addEventListener('drop', function(e) {
|
|
771
|
+
e.preventDefault();
|
|
772
|
+
_dragCtr = 0;
|
|
773
|
+
this.classList.remove('dragover');
|
|
774
|
+
const imgs = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
|
|
775
|
+
if (imgs.length) setAgentFiles(imgs);
|
|
776
|
+
});
|
|
777
|
+
document.getElementById('inputuserinput').addEventListener('paste', function(e) {
|
|
778
|
+
const items = e.clipboardData?.items;
|
|
779
|
+
if (!items) return;
|
|
780
|
+
const pastedFiles = [];
|
|
781
|
+
for (const item of items) {
|
|
782
|
+
if (item.type.startsWith('image/')) {
|
|
783
|
+
const file = item.getAsFile();
|
|
784
|
+
if (file) pastedFiles.push(file);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (pastedFiles.length) {
|
|
788
|
+
e.preventDefault();
|
|
789
|
+
setAgentFiles(pastedFiles);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
}
|
|
695
793
|
function spin_send_button() {
|
|
696
794
|
$("#sendbuttonicon").attr("class","fas fa-spinner fa-spin");
|
|
697
795
|
};`,
|
|
@@ -753,44 +851,66 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
753
851
|
}
|
|
754
852
|
let fileBadges = "";
|
|
755
853
|
if (config.image_upload && req.files?.file) {
|
|
756
|
-
const
|
|
757
|
-
req.files.file
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
854
|
+
const rawFiles = Array.isArray(req.files.file)
|
|
855
|
+
? req.files.file
|
|
856
|
+
: [req.files.file];
|
|
857
|
+
const badges = [];
|
|
858
|
+
for (const rawFile of rawFiles) {
|
|
859
|
+
const file = await File.from_req_files(
|
|
860
|
+
rawFile,
|
|
861
|
+
req.user ? req.user.id : null,
|
|
862
|
+
100,
|
|
863
|
+
);
|
|
864
|
+
badges.push(
|
|
865
|
+
div(
|
|
866
|
+
{ class: "bg-secondary-subtle p-2 m-2 rounded-2" },
|
|
867
|
+
img({
|
|
868
|
+
src: `/files/resize/${50}/${50}/${file.path_to_serve}`,
|
|
869
|
+
class: "d-block",
|
|
870
|
+
onclick: `expand_thumbnail('${file.path_to_serve}', '${path.basename(file.path_to_serve)}')`,
|
|
871
|
+
}),
|
|
872
|
+
file.filename,
|
|
873
|
+
),
|
|
874
|
+
);
|
|
875
|
+
const baseUrl = getState().getConfig("base_url").replace(/\/$/, "");
|
|
876
|
+
let imageurl;
|
|
877
|
+
if (
|
|
878
|
+
!config.image_base64 &&
|
|
879
|
+
baseUrl &&
|
|
880
|
+
!baseUrl.includes("http://localhost:")
|
|
881
|
+
) {
|
|
882
|
+
imageurl = `${baseUrl}/files/serve/${file.path_to_serve}`;
|
|
883
|
+
} else {
|
|
884
|
+
const b64 = await file.get_contents("base64");
|
|
885
|
+
imageurl = `data:${file.mimetype};base64,${b64}`;
|
|
886
|
+
}
|
|
887
|
+
await getState().functions.llm_add_message.run("image", imageurl, {
|
|
888
|
+
chat: run.context.interactions || [],
|
|
889
|
+
});
|
|
778
890
|
}
|
|
779
|
-
await
|
|
780
|
-
|
|
781
|
-
});
|
|
782
|
-
await addToContext(run, {
|
|
783
|
-
interactions: run.context.interactions || [],
|
|
784
|
-
});
|
|
891
|
+
await saveInteractions(run);
|
|
892
|
+
fileBadges = div({ class: "d-flex" }, badges);
|
|
785
893
|
}
|
|
894
|
+
const userInteractions = wrapSegment(p(userinput) + fileBadges, "You", true);
|
|
895
|
+
|
|
786
896
|
await addToContext(run, {
|
|
787
897
|
interactions: [
|
|
788
898
|
...(run.context.interactions || []),
|
|
789
899
|
{ role: "user", content: userinput },
|
|
790
900
|
],
|
|
791
|
-
html_interactions: [
|
|
901
|
+
html_interactions: [userInteractions],
|
|
792
902
|
});
|
|
793
903
|
const dyn_updates = getState().getConfig("enable_dynamic_updates", true);
|
|
904
|
+
if (dyn_updates) {
|
|
905
|
+
getState().emitDynamicUpdate(
|
|
906
|
+
db.getTenantSchema(),
|
|
907
|
+
{
|
|
908
|
+
eval_js: `processCopilotResponse({response: ${JSON.stringify(userInteractions)}, run_id: ${run.id}}, true)`,
|
|
909
|
+
page_load_tag: req?.headers?.["page-load-tag"],
|
|
910
|
+
},
|
|
911
|
+
[req.user.id],
|
|
912
|
+
);
|
|
913
|
+
}
|
|
794
914
|
const process_promise = process_interaction(
|
|
795
915
|
run,
|
|
796
916
|
action.configuration,
|
|
@@ -945,4 +1065,5 @@ module.exports = {
|
|
|
945
1065
|
table_optional: true,
|
|
946
1066
|
run,
|
|
947
1067
|
routes: { interact, delprevrun, debug_info, skillroute, execute_user_action },
|
|
1068
|
+
mobile_render_server_side: true,
|
|
948
1069
|
};
|
package/common.js
CHANGED
|
@@ -166,14 +166,20 @@ const addToContext = async (run, newCtx) => {
|
|
|
166
166
|
if (changed && run.update) await run.update({ context: run.context });
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
-
const
|
|
169
|
+
const saveInteractions = async (run) => {
|
|
170
|
+
await addToContext(run, {
|
|
171
|
+
interactions: run.context.interactions || [],
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const wrapSegment = (html, who, to_right) =>
|
|
170
176
|
who === null
|
|
171
177
|
? html
|
|
172
|
-
:
|
|
178
|
+
: `<div class="interaction-segment ${to_right ? "to-right" : ""}"><div><div class="badgewrap"><span class="badge bg-secondary">` +
|
|
173
179
|
who +
|
|
174
|
-
"</span>" +
|
|
180
|
+
"</span></div>" +
|
|
175
181
|
html +
|
|
176
|
-
"</div>";
|
|
182
|
+
"</div></div>";
|
|
177
183
|
|
|
178
184
|
const wrapCard = (title, ...inners) =>
|
|
179
185
|
span({ class: "badge bg-info ms-1" }, title) +
|
|
@@ -236,12 +242,13 @@ const process_interaction = async (
|
|
|
236
242
|
interactions: complArgs.chat,
|
|
237
243
|
});
|
|
238
244
|
const responses = [];
|
|
239
|
-
|
|
245
|
+
|
|
246
|
+
const add_response = async (resp, not_final) => {
|
|
240
247
|
if (dyn_updates)
|
|
241
248
|
getState().emitDynamicUpdate(
|
|
242
249
|
db.getTenantSchema(),
|
|
243
250
|
{
|
|
244
|
-
eval_js: `processCopilotResponse({response: ${JSON.stringify(resp)}, run_id: ${run.id}})`,
|
|
251
|
+
eval_js: `processCopilotResponse({response: ${JSON.stringify(resp)}, run_id: ${run.id}}, true)`,
|
|
245
252
|
page_load_tag: req?.headers?.["page-load-tag"],
|
|
246
253
|
},
|
|
247
254
|
[req.user.id],
|
|
@@ -479,6 +486,15 @@ const process_interaction = async (
|
|
|
479
486
|
? answer
|
|
480
487
|
: wrapSegment(md.render(answer), agent_label),
|
|
481
488
|
);
|
|
489
|
+
if (dyn_updates)
|
|
490
|
+
getState().emitDynamicUpdate(
|
|
491
|
+
db.getTenantSchema(),
|
|
492
|
+
{
|
|
493
|
+
eval_js: `final_agent_response()`,
|
|
494
|
+
page_load_tag: req?.headers?.["page-load-tag"],
|
|
495
|
+
},
|
|
496
|
+
[req.user.id],
|
|
497
|
+
);
|
|
482
498
|
|
|
483
499
|
return {
|
|
484
500
|
json: {
|
|
@@ -497,6 +513,7 @@ module.exports = {
|
|
|
497
513
|
get_skill_instances,
|
|
498
514
|
getCompletionArguments,
|
|
499
515
|
addToContext,
|
|
516
|
+
saveInteractions,
|
|
500
517
|
wrapCard,
|
|
501
518
|
wrapSegment,
|
|
502
519
|
process_interaction,
|
package/index.js
CHANGED