@saltcorn/agents 0.8.6 → 0.8.7
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/skills/GenerateImage.js +101 -50
- package/skills/WebSearch.js +74 -10
package/package.json
CHANGED
package/skills/GenerateImage.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
const { div, pre } = require("@saltcorn/markup/tags");
|
|
2
|
-
const Workflow = require("@saltcorn/data/models/workflow");
|
|
3
|
-
const Form = require("@saltcorn/data/models/form");
|
|
4
|
-
const Table = require("@saltcorn/data/models/table");
|
|
5
|
-
const File = require("@saltcorn/data/models/file");
|
|
6
|
-
const View = require("@saltcorn/data/models/view");
|
|
7
1
|
const { getState } = require("@saltcorn/data/db/state");
|
|
8
|
-
const
|
|
9
|
-
const { eval_expression } = require("@saltcorn/data/models/expression");
|
|
10
|
-
const { interpolate } = require("@saltcorn/data/utils");
|
|
2
|
+
const File = require("@saltcorn/data/models/file");
|
|
11
3
|
|
|
12
4
|
class GenerateImage {
|
|
13
5
|
static skill_name = "Image generation";
|
|
14
6
|
|
|
15
7
|
get skill_label() {
|
|
16
|
-
return
|
|
8
|
+
return "Image generation";
|
|
17
9
|
}
|
|
18
10
|
|
|
19
11
|
constructor(cfg) {
|
|
@@ -23,20 +15,31 @@ class GenerateImage {
|
|
|
23
15
|
static async configFields() {
|
|
24
16
|
return [
|
|
25
17
|
{
|
|
26
|
-
name: "
|
|
27
|
-
label: "
|
|
18
|
+
name: "size",
|
|
19
|
+
label: "Size",
|
|
28
20
|
type: "String",
|
|
29
21
|
required: true,
|
|
30
|
-
attributes: {
|
|
22
|
+
attributes: {
|
|
23
|
+
options: [
|
|
24
|
+
"auto",
|
|
25
|
+
"1024x1024",
|
|
26
|
+
"1536x1024",
|
|
27
|
+
"1024x1536",
|
|
28
|
+
"1792x1024",
|
|
29
|
+
"1024x1792",
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
default: "auto",
|
|
31
33
|
},
|
|
32
34
|
{
|
|
33
|
-
name: "
|
|
34
|
-
label: "
|
|
35
|
+
name: "quality",
|
|
36
|
+
label: "Quality",
|
|
35
37
|
type: "String",
|
|
36
38
|
required: true,
|
|
37
39
|
attributes: {
|
|
38
|
-
options: ["auto", "
|
|
40
|
+
options: ["auto", "low", "medium", "high", "standard", "hd"],
|
|
39
41
|
},
|
|
42
|
+
default: "auto",
|
|
40
43
|
},
|
|
41
44
|
{
|
|
42
45
|
name: "format",
|
|
@@ -44,54 +47,102 @@ class GenerateImage {
|
|
|
44
47
|
type: "String",
|
|
45
48
|
required: true,
|
|
46
49
|
attributes: { options: ["png", "jpeg", "webp"] },
|
|
50
|
+
default: "png",
|
|
47
51
|
},
|
|
48
52
|
{
|
|
49
53
|
name: "transparent",
|
|
50
|
-
label: "Transparent",
|
|
54
|
+
label: "Transparent background",
|
|
51
55
|
type: "Bool",
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
name: "save_file",
|
|
55
|
-
label: "Save file",
|
|
56
|
-
type: "String",
|
|
57
|
-
required: true,
|
|
58
|
-
attributes: { options: ["Always", "Never"] }, //, "Button"] },
|
|
56
|
+
sublabel: "Only supported with gpt-image-1.",
|
|
59
57
|
},
|
|
60
58
|
];
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
provideTools() {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
const skill = this;
|
|
63
|
+
return {
|
|
64
|
+
type: "function",
|
|
65
|
+
function: {
|
|
66
|
+
name: "generate_image",
|
|
67
|
+
description:
|
|
68
|
+
"Generate an image from a text prompt. The image is automatically " +
|
|
69
|
+
"displayed to the user in the chat — DO NOT include the image URL or " +
|
|
70
|
+
"any markdown image syntax in your reply. Just confirm in words what " +
|
|
71
|
+
"was generated.",
|
|
72
|
+
parameters: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
prompt: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Detailed description of the image to generate.",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
required: ["prompt"],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
process: async (input, { req }) => {
|
|
84
|
+
const fn = getState().functions.llm_image_generate;
|
|
85
|
+
if (!fn)
|
|
86
|
+
throw new Error(
|
|
87
|
+
"LLM plugin not available (llm_image_generate not registered)",
|
|
77
88
|
);
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
const opts = {};
|
|
90
|
+
if (skill.size && skill.size !== "auto") opts.size = skill.size;
|
|
91
|
+
if (skill.quality && skill.quality !== "auto")
|
|
92
|
+
opts.quality = skill.quality;
|
|
93
|
+
if (skill.format) opts.output_format = skill.format;
|
|
94
|
+
if (skill.transparent) opts.background = "transparent";
|
|
95
|
+
// gpt-image-* family supports the rich options above (quality, format,
|
|
96
|
+
// background). The LLM-plugin's image gen will pick them up.
|
|
97
|
+
const result = await fn.run(input.prompt, opts);
|
|
98
|
+
const b64 = result?.b64_json;
|
|
99
|
+
if (!b64) throw new Error("Image generation returned no data");
|
|
100
|
+
const ext = result?.output_format || skill.format || "png";
|
|
101
|
+
const buf = Buffer.from(b64, "base64");
|
|
102
|
+
const file = await File.from_contents(
|
|
103
|
+
`genimg.${ext}`,
|
|
104
|
+
`image/${ext}`,
|
|
105
|
+
buf,
|
|
106
|
+
req?.user?.id,
|
|
107
|
+
100,
|
|
108
|
+
);
|
|
109
|
+
const sizeOut =
|
|
110
|
+
skill.size && skill.size !== "auto" ? skill.size : "1024x1024";
|
|
111
|
+
// Two response paths:
|
|
112
|
+
// - add_response: rendered HTML the user actually sees in chat
|
|
113
|
+
// (with download overlay). Saved in interactions.
|
|
114
|
+
// - return shape consumed by the LLM tool_response — kept terse
|
|
115
|
+
// and free of any URL so the model cannot leak a broken markdown
|
|
116
|
+
// image link into its textual reply.
|
|
117
|
+
return {
|
|
118
|
+
ok: true,
|
|
119
|
+
message:
|
|
120
|
+
"Image generated and displayed to the user. Do NOT include the image URL or any markdown image syntax in your textual reply.",
|
|
121
|
+
// Hidden internal fields used only by renderToolResponse:
|
|
122
|
+
filename: file.path_to_serve,
|
|
123
|
+
output_format: ext,
|
|
124
|
+
size: sizeOut,
|
|
125
|
+
};
|
|
80
126
|
},
|
|
81
127
|
renderToolResponse: (v) => {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
128
|
+
const sz = v?.size || "1024x1024";
|
|
129
|
+
const [ws, hs] = sz.split("x");
|
|
130
|
+
const w = +ws / 4;
|
|
131
|
+
const h = +hs / 4;
|
|
132
|
+
if (!v?.filename) return "";
|
|
133
|
+
const ext = v.output_format || "png";
|
|
134
|
+
const url = "/files/serve/" + v.filename;
|
|
135
|
+
const dlName = "image-" + Date.now() + "." + ext;
|
|
136
|
+
return (
|
|
137
|
+
`<div class="agent-generated-image">` +
|
|
138
|
+
`<img height="${h}" width="${w}" src="${url}" />` +
|
|
139
|
+
`<a href="${url}" download="${dlName}" ` +
|
|
140
|
+
`class="agent-image-download" title="Download image">` +
|
|
141
|
+
`<i class="fas fa-download"></i></a>` +
|
|
142
|
+
`</div>`
|
|
143
|
+
);
|
|
91
144
|
},
|
|
92
145
|
};
|
|
93
|
-
if (this.transparent) tool.background = "transparent";
|
|
94
|
-
return tool;
|
|
95
146
|
}
|
|
96
147
|
}
|
|
97
148
|
|
package/skills/WebSearch.js
CHANGED
|
@@ -37,7 +37,7 @@ class WebSearchSkill {
|
|
|
37
37
|
label: "Search provider",
|
|
38
38
|
type: "String",
|
|
39
39
|
required: true,
|
|
40
|
-
attributes: { options: ["By URL template"] },
|
|
40
|
+
attributes: { options: ["By URL template", "Firecrawl", "Tavily"] },
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
name: "url_template",
|
|
@@ -47,6 +47,13 @@ class WebSearchSkill {
|
|
|
47
47
|
required: true,
|
|
48
48
|
showIf: { search_provider: "By URL template" },
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
name: "api_key",
|
|
52
|
+
label: "API key",
|
|
53
|
+
type: "String",
|
|
54
|
+
required: true,
|
|
55
|
+
showIf: { search_provider: ["Firecrawl", "Tavily"] },
|
|
56
|
+
},
|
|
50
57
|
{
|
|
51
58
|
name: "header",
|
|
52
59
|
label: "Header",
|
|
@@ -64,16 +71,73 @@ class WebSearchSkill {
|
|
|
64
71
|
return {
|
|
65
72
|
type: "function",
|
|
66
73
|
process: async (row) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
switch (this.search_provider) {
|
|
75
|
+
case "Firecrawl":
|
|
76
|
+
{
|
|
77
|
+
const url = "https://api.firecrawl.dev/v2/search";
|
|
78
|
+
const options = {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: "Bearer " + this.api_key,
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
query: row.search_phrase,
|
|
86
|
+
sources: ["web"],
|
|
87
|
+
categories: [],
|
|
88
|
+
limit: 10,
|
|
89
|
+
scrapeOptions: {
|
|
90
|
+
onlyMainContent: false,
|
|
91
|
+
maxAge: 172800000,
|
|
92
|
+
parsers: ["pdf"],
|
|
93
|
+
formats: [],
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const response = await fetch(url, options);
|
|
99
|
+
const data = await response.json();
|
|
100
|
+
return data.data.web;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
case "Tavily":
|
|
104
|
+
{
|
|
105
|
+
const url = "https://api.tavily.com/search";
|
|
106
|
+
const options = {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: {
|
|
109
|
+
Authorization: "Bearer " + this.api_key,
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
query: row.search_phrase,
|
|
114
|
+
search_depth: "advanced",
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const response = await fetch(url, options);
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
return data.results;
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
case "By URL template":
|
|
124
|
+
default:
|
|
125
|
+
{
|
|
126
|
+
const fOpts = { method: "GET" };
|
|
127
|
+
if (this.header) {
|
|
128
|
+
const [key, val] = this.header.split(":");
|
|
129
|
+
const myHeaders = new Headers();
|
|
130
|
+
myHeaders.append(key, val.trim());
|
|
131
|
+
fOpts.headers = myHeaders;
|
|
132
|
+
}
|
|
133
|
+
const url = interpolate(this.url_template, {
|
|
134
|
+
q: row.search_phrase,
|
|
135
|
+
});
|
|
136
|
+
const resp = await fetch(url);
|
|
137
|
+
return await resp.text();
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
73
140
|
}
|
|
74
|
-
const url = interpolate(this.url_template, { q: row.search_phrase });
|
|
75
|
-
const resp = await fetch(url);
|
|
76
|
-
return await resp.text();
|
|
77
141
|
},
|
|
78
142
|
function: {
|
|
79
143
|
name: "web_search",
|