@adminforth/bulk-ai-flow 1.8.0 → 1.9.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/build.log +2 -2
- package/custom/ImageGenerationCarousel.vue +48 -19
- package/custom/VisionAction.vue +185 -136
- package/custom/VisionTable.vue +33 -25
- package/dist/custom/ImageGenerationCarousel.vue +48 -19
- package/dist/custom/VisionAction.vue +185 -136
- package/dist/custom/VisionTable.vue +33 -25
- package/dist/index.js +317 -198
- package/index.ts +321 -209
- package/package.json +1 -1
- package/types.ts +9 -0
package/dist/index.js
CHANGED
|
@@ -10,6 +10,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { AdminForthPlugin, Filters } from "adminforth";
|
|
11
11
|
import Handlebars from 'handlebars';
|
|
12
12
|
import { RateLimiter } from "adminforth";
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
const STUB_MODE = false;
|
|
15
|
+
const jobs = new Map();
|
|
13
16
|
export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
14
17
|
constructor(options) {
|
|
15
18
|
super(options, import.meta.url);
|
|
@@ -51,11 +54,257 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
}
|
|
57
|
+
analyze_image(jobId, recordId, adminUser, headers) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
60
|
+
const selectedId = recordId;
|
|
61
|
+
let isError = false;
|
|
62
|
+
if (typeof ((_a = this.options.rateLimits) === null || _a === void 0 ? void 0 : _a.fillFieldsFromImages) === 'string') {
|
|
63
|
+
if (this.checkRateLimit("fillFieldsFromImages", this.options.rateLimits.fillFieldsFromImages, headers)) {
|
|
64
|
+
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
|
|
65
|
+
return { error: "Rate limit exceeded" };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Fetch the record using the provided ID
|
|
69
|
+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
70
|
+
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, selectedId)]);
|
|
71
|
+
//recieve image URLs to analyze
|
|
72
|
+
const attachmentFiles = yield this.options.attachFiles({ record: record });
|
|
73
|
+
if (STUB_MODE) {
|
|
74
|
+
yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 8000) + 1000));
|
|
75
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
else if (attachmentFiles.length !== 0) {
|
|
79
|
+
//create prompt for OpenAI
|
|
80
|
+
const compiledOutputFields = this.compileOutputFieldsTemplates(record);
|
|
81
|
+
const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
82
|
+
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
83
|
+
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
|
|
84
|
+
Image URLs:`;
|
|
85
|
+
//send prompt to OpenAI and get response
|
|
86
|
+
let chatResponse;
|
|
87
|
+
try {
|
|
88
|
+
chatResponse = yield this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
isError = true;
|
|
92
|
+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to analyze images' });
|
|
93
|
+
return { ok: false, error: 'AI provider refused to analyze images' };
|
|
94
|
+
}
|
|
95
|
+
if (!isError) {
|
|
96
|
+
const resp = chatResponse.response;
|
|
97
|
+
const topLevelError = chatResponse.error;
|
|
98
|
+
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
99
|
+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}` });
|
|
100
|
+
}
|
|
101
|
+
const textOutput = (_g = (_f = (_e = (_d = (_c = (_b = resp === null || resp === void 0 ? void 0 : resp.output) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.content) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.text) !== null && _f !== void 0 ? _f : resp === null || resp === void 0 ? void 0 : resp.output_text) !== null && _g !== void 0 ? _g : (_k = (_j = (_h = resp === null || resp === void 0 ? void 0 : resp.choices) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.message) === null || _k === void 0 ? void 0 : _k.content;
|
|
102
|
+
if (!textOutput || typeof textOutput !== 'string') {
|
|
103
|
+
jobs.set(jobId, { status: 'failed', error: 'Unexpected AI response format' });
|
|
104
|
+
}
|
|
105
|
+
//parse response and update record
|
|
106
|
+
const resData = JSON.parse(textOutput);
|
|
107
|
+
const result = resData;
|
|
108
|
+
jobs.set(jobId, { status: 'completed', result });
|
|
109
|
+
return { ok: true };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
analyzeNoImages(jobId, recordId, adminUser, headers) {
|
|
116
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
var _a;
|
|
118
|
+
const selectedId = recordId;
|
|
119
|
+
let isError = false;
|
|
120
|
+
if (typeof ((_a = this.options.rateLimits) === null || _a === void 0 ? void 0 : _a.fillPlainFields) === 'string') {
|
|
121
|
+
if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
|
|
122
|
+
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
|
|
123
|
+
return { error: "Rate limit exceeded" };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (STUB_MODE) {
|
|
127
|
+
yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
|
|
128
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
133
|
+
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, selectedId)]);
|
|
134
|
+
const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
|
|
135
|
+
const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
136
|
+
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
137
|
+
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
|
|
138
|
+
If it's number field - return only number.`;
|
|
139
|
+
//send prompt to OpenAI and get response
|
|
140
|
+
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
141
|
+
let resp;
|
|
142
|
+
try {
|
|
143
|
+
const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
|
|
144
|
+
resp = chatResponse.response;
|
|
145
|
+
const topLevelError = chatResponse.error;
|
|
146
|
+
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
147
|
+
isError = true;
|
|
148
|
+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}` });
|
|
149
|
+
}
|
|
150
|
+
resp = chatResponse;
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
isError = true;
|
|
154
|
+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to fill fields' });
|
|
155
|
+
return { ok: false, error: 'AI provider refused to fill fields' };
|
|
156
|
+
}
|
|
157
|
+
const resData = JSON.parse(resp);
|
|
158
|
+
const result = resData;
|
|
159
|
+
jobs.set(jobId, { status: 'completed', result });
|
|
160
|
+
return { ok: true };
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
initialImageGenerate(jobId, recordId, adminUser, headers) {
|
|
165
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
166
|
+
var _a, _b, _c;
|
|
167
|
+
const selectedId = recordId;
|
|
168
|
+
let isError = false;
|
|
169
|
+
if (typeof ((_a = this.options.rateLimits) === null || _a === void 0 ? void 0 : _a.generateImages) === 'string') {
|
|
170
|
+
if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
|
|
171
|
+
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
|
|
172
|
+
return { error: "Rate limit exceeded" };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const start = +new Date();
|
|
176
|
+
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_b = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _b === void 0 ? void 0 : _b.name, selectedId)]);
|
|
177
|
+
let attachmentFiles;
|
|
178
|
+
if (!this.options.attachFiles) {
|
|
179
|
+
attachmentFiles = [];
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
attachmentFiles = yield this.options.attachFiles({ record });
|
|
183
|
+
}
|
|
184
|
+
const fieldTasks = Object.keys(((_c = this.options) === null || _c === void 0 ? void 0 : _c.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
|
|
185
|
+
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
186
|
+
let images;
|
|
187
|
+
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
188
|
+
jobs.set(jobId, { status: 'failed', error: "No attachment files found" });
|
|
189
|
+
return { key, images: [] };
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
if (STUB_MODE) {
|
|
193
|
+
yield new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
|
|
194
|
+
images = `https://pic.re/image`;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
let generationAdapter;
|
|
198
|
+
if (this.options.generateImages[key].adapter) {
|
|
199
|
+
generationAdapter = this.options.generateImages[key].adapter;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
generationAdapter = this.options.imageGenerationAdapter;
|
|
203
|
+
}
|
|
204
|
+
let resp;
|
|
205
|
+
try {
|
|
206
|
+
resp = yield generationAdapter.generate({
|
|
207
|
+
prompt,
|
|
208
|
+
inputFiles: attachmentFiles,
|
|
209
|
+
n: 1,
|
|
210
|
+
size: this.options.generateImages[key].outputSize,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
jobs.set(jobId, { status: 'failed', error: "AI provider refused to generate image" });
|
|
215
|
+
isError = true;
|
|
216
|
+
return { key, images: [] };
|
|
217
|
+
}
|
|
218
|
+
images = resp.imageURLs[0];
|
|
219
|
+
}
|
|
220
|
+
return { key, images };
|
|
221
|
+
}
|
|
222
|
+
}));
|
|
223
|
+
const fieldResults = yield Promise.all(fieldTasks);
|
|
224
|
+
const recordResult = {};
|
|
225
|
+
fieldResults.forEach(({ key, images }) => {
|
|
226
|
+
recordResult[key] = images;
|
|
227
|
+
});
|
|
228
|
+
const result = recordResult;
|
|
229
|
+
this.totalCalls++;
|
|
230
|
+
this.totalDuration += (+new Date() - start) / 1000;
|
|
231
|
+
if (!isError) {
|
|
232
|
+
jobs.set(jobId, { status: 'completed', result });
|
|
233
|
+
return { ok: true };
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
return { ok: false, error: 'Error during image generation' };
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
regenerateImage(jobId, recordId, fieldName, prompt, adminUser, headers) {
|
|
241
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
242
|
+
var _a;
|
|
243
|
+
const Id = recordId;
|
|
244
|
+
let isError = false;
|
|
245
|
+
if (this.checkRateLimit(fieldName, this.options.generateImages[fieldName].rateLimit, headers)) {
|
|
246
|
+
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
|
|
247
|
+
return { error: "Rate limit exceeded" };
|
|
248
|
+
}
|
|
249
|
+
const start = +new Date();
|
|
250
|
+
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_a = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _a === void 0 ? void 0 : _a.name, Id)]);
|
|
251
|
+
let attachmentFiles;
|
|
252
|
+
if (!this.options.attachFiles) {
|
|
253
|
+
attachmentFiles = [];
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
attachmentFiles = yield this.options.attachFiles({ record });
|
|
257
|
+
}
|
|
258
|
+
const images = yield Promise.all((new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
260
|
+
jobs.set(jobId, { status: 'failed', error: "No attachment files found" });
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
if (STUB_MODE) {
|
|
264
|
+
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
265
|
+
jobs.set(jobId, { status: 'completed', result: {} });
|
|
266
|
+
return `https://pic.re/image`;
|
|
267
|
+
}
|
|
268
|
+
let generationAdapter;
|
|
269
|
+
if (this.options.generateImages[fieldName].adapter) {
|
|
270
|
+
generationAdapter = this.options.generateImages[fieldName].adapter;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
generationAdapter = this.options.imageGenerationAdapter;
|
|
274
|
+
}
|
|
275
|
+
let resp;
|
|
276
|
+
try {
|
|
277
|
+
resp = yield generationAdapter.generate({
|
|
278
|
+
prompt,
|
|
279
|
+
inputFiles: attachmentFiles,
|
|
280
|
+
n: 1,
|
|
281
|
+
size: this.options.generateImages[fieldName].outputSize,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
jobs.set(jobId, { status: 'failed', error: "AI provider refused to generate image" });
|
|
286
|
+
isError = true;
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
return resp.imageURLs[0];
|
|
290
|
+
})));
|
|
291
|
+
this.totalCalls++;
|
|
292
|
+
this.totalDuration += (+new Date() - start) / 1000;
|
|
293
|
+
if (!isError) {
|
|
294
|
+
jobs.set(jobId, { status: 'completed', result: { [fieldName]: images } });
|
|
295
|
+
return { ok: true };
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
return { ok: false, error: 'Error during image generation' };
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
54
302
|
modifyResourceConfig(adminforth, resourceConfig) {
|
|
55
303
|
const _super = Object.create(null, {
|
|
56
304
|
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
57
305
|
});
|
|
58
306
|
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
var _a, _b, _c, _d;
|
|
59
308
|
_super.modifyResourceConfig.call(this, adminforth, resourceConfig);
|
|
60
309
|
//check if options names are provided
|
|
61
310
|
const columns = this.resourceConfig.columns;
|
|
@@ -96,9 +345,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
96
345
|
for (const [key, value] of Object.entries(this.options.generateImages)) {
|
|
97
346
|
const plugin = adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.resourceConfig.resourceId &&
|
|
98
347
|
p.pluginOptions.pathColumnName === key);
|
|
99
|
-
|
|
100
|
-
outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
|
|
101
|
-
}
|
|
348
|
+
outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
|
|
102
349
|
}
|
|
103
350
|
}
|
|
104
351
|
const outputFields = Object.assign(Object.assign(Object.assign({}, this.options.fillFieldsFromImages), this.options.fillPlainFields), (this.options.generateImages || {}));
|
|
@@ -118,6 +365,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
118
365
|
isFieldsForAnalizePlain: this.options.fillPlainFields ? Object.keys(this.options.fillPlainFields).length > 0 : false,
|
|
119
366
|
isImageGeneration: this.options.generateImages ? Object.keys(this.options.generateImages).length > 0 : false,
|
|
120
367
|
isAttachFiles: this.options.attachFiles ? true : false,
|
|
368
|
+
disabledWhenNoCheckboxes: true,
|
|
369
|
+
refreshRates: {
|
|
370
|
+
fillFieldsFromImages: ((_a = this.options.refreshRates) === null || _a === void 0 ? void 0 : _a.fillFieldsFromImages) || 2000,
|
|
371
|
+
fillPlainFields: ((_b = this.options.refreshRates) === null || _b === void 0 ? void 0 : _b.fillPlainFields) || 1000,
|
|
372
|
+
generateImages: ((_c = this.options.refreshRates) === null || _c === void 0 ? void 0 : _c.generateImages) || 5000,
|
|
373
|
+
regenerateImages: ((_d = this.options.refreshRates) === null || _d === void 0 ? void 0 : _d.regenerateImages) || 5000,
|
|
374
|
+
}
|
|
121
375
|
}
|
|
122
376
|
};
|
|
123
377
|
if (!resourceConfig.options.pageInjections) {
|
|
@@ -174,6 +428,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
174
428
|
if (!plugin) {
|
|
175
429
|
throw new Error(`Plugin for attachment field '${key}' not found in resource '${this.resourceConfig.resourceId}', please check if Upload Plugin is installed on the field ${key}`);
|
|
176
430
|
}
|
|
431
|
+
if (!plugin.pluginOptions || !plugin.pluginOptions.storageAdapter) {
|
|
432
|
+
throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}' is missing a storageAdapter configuration.`);
|
|
433
|
+
}
|
|
434
|
+
if (typeof plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly !== 'function') {
|
|
435
|
+
throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}' uses a storage adapter without 'objectCanBeAccesedPublicly' method.`);
|
|
436
|
+
}
|
|
177
437
|
if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
|
|
178
438
|
throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
|
|
179
439
|
uses adapter which is not configured to store objects in public way, so it will produce only signed private URLs which can not be used in HTML text of blog posts.
|
|
@@ -187,86 +447,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
187
447
|
return `${this.pluginOptions.actionName}`;
|
|
188
448
|
}
|
|
189
449
|
setupEndpoints(server) {
|
|
190
|
-
server.endpoint({
|
|
191
|
-
method: 'POST',
|
|
192
|
-
path: `/plugin/${this.pluginInstanceId}/analyze`,
|
|
193
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
194
|
-
var _b;
|
|
195
|
-
const selectedIds = body.selectedIds || [];
|
|
196
|
-
if (typeof ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.fillFieldsFromImages) === 'string') {
|
|
197
|
-
if (this.checkRateLimit("fillFieldsFromImages", this.options.rateLimits.fillFieldsFromImages, headers)) {
|
|
198
|
-
return { error: "Rate limit exceeded" };
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
|
|
202
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
203
|
-
// Fetch the record using the provided ID
|
|
204
|
-
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
205
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
|
|
206
|
-
//recieve image URLs to analyze
|
|
207
|
-
const attachmentFiles = yield this.options.attachFiles({ record: record });
|
|
208
|
-
if (attachmentFiles.length !== 0) {
|
|
209
|
-
//create prompt for OpenAI
|
|
210
|
-
const compiledOutputFields = this.compileOutputFieldsTemplates(record);
|
|
211
|
-
const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
212
|
-
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
213
|
-
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
|
|
214
|
-
Image URLs:`;
|
|
215
|
-
//send prompt to OpenAI and get response
|
|
216
|
-
const chatResponse = yield this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
|
|
217
|
-
const resp = chatResponse.response;
|
|
218
|
-
const topLevelError = chatResponse.error;
|
|
219
|
-
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
220
|
-
throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
|
|
221
|
-
}
|
|
222
|
-
const textOutput = (_f = (_e = (_d = (_c = (_b = (_a = resp === null || resp === void 0 ? void 0 : resp.output) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : resp === null || resp === void 0 ? void 0 : resp.output_text) !== null && _f !== void 0 ? _f : (_j = (_h = (_g = resp === null || resp === void 0 ? void 0 : resp.choices) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.message) === null || _j === void 0 ? void 0 : _j.content;
|
|
223
|
-
if (!textOutput || typeof textOutput !== 'string') {
|
|
224
|
-
throw new Error('Unexpected AI response format');
|
|
225
|
-
}
|
|
226
|
-
//parse response and update record
|
|
227
|
-
const resData = JSON.parse(textOutput);
|
|
228
|
-
return resData;
|
|
229
|
-
}
|
|
230
|
-
;
|
|
231
|
-
}));
|
|
232
|
-
const result = yield Promise.all(tasks);
|
|
233
|
-
return { result };
|
|
234
|
-
})
|
|
235
|
-
});
|
|
236
|
-
server.endpoint({
|
|
237
|
-
method: 'POST',
|
|
238
|
-
path: `/plugin/${this.pluginInstanceId}/analyze_no_images`,
|
|
239
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
240
|
-
var _b;
|
|
241
|
-
const selectedIds = body.selectedIds || [];
|
|
242
|
-
if (typeof ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.fillPlainFields) === 'string') {
|
|
243
|
-
if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
|
|
244
|
-
return { error: "Rate limit exceeded" };
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
|
|
248
|
-
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
249
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
|
|
250
|
-
const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
|
|
251
|
-
const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
252
|
-
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
253
|
-
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
|
|
254
|
-
If it's number field - return only number.`;
|
|
255
|
-
//send prompt to OpenAI and get response
|
|
256
|
-
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
257
|
-
const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
|
|
258
|
-
const resp = chatResponse.response;
|
|
259
|
-
const topLevelError = chatResponse.error;
|
|
260
|
-
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
261
|
-
throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
|
|
262
|
-
}
|
|
263
|
-
const resData = JSON.parse(chatResponse);
|
|
264
|
-
return resData;
|
|
265
|
-
}));
|
|
266
|
-
const result = yield Promise.all(tasks);
|
|
267
|
-
return { result };
|
|
268
|
-
})
|
|
269
|
-
});
|
|
270
450
|
server.endpoint({
|
|
271
451
|
method: 'POST',
|
|
272
452
|
path: `/plugin/${this.pluginInstanceId}/get_records`,
|
|
@@ -381,121 +561,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
381
561
|
}
|
|
382
562
|
})
|
|
383
563
|
});
|
|
384
|
-
server.endpoint({
|
|
385
|
-
method: 'POST',
|
|
386
|
-
path: `/plugin/${this.pluginInstanceId}/regenerate_images`,
|
|
387
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, headers }) {
|
|
388
|
-
var _b;
|
|
389
|
-
const Id = body.recordId || [];
|
|
390
|
-
const prompt = body.prompt || '';
|
|
391
|
-
const fieldName = body.fieldName || '';
|
|
392
|
-
if (this.checkRateLimit(fieldName, this.options.generateImages[fieldName].rateLimit, headers)) {
|
|
393
|
-
return { error: "Rate limit exceeded" };
|
|
394
|
-
}
|
|
395
|
-
const start = +new Date();
|
|
396
|
-
const STUB_MODE = false;
|
|
397
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_b = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _b === void 0 ? void 0 : _b.name, Id)]);
|
|
398
|
-
let attachmentFiles;
|
|
399
|
-
if (!this.options.attachFiles) {
|
|
400
|
-
attachmentFiles = [];
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
attachmentFiles = yield this.options.attachFiles({ record });
|
|
404
|
-
}
|
|
405
|
-
const images = yield Promise.all((new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
|
|
406
|
-
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
if (STUB_MODE) {
|
|
410
|
-
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
411
|
-
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
412
|
-
}
|
|
413
|
-
let generationAdapter;
|
|
414
|
-
if (this.options.generateImages[fieldName].adapter) {
|
|
415
|
-
generationAdapter = this.options.generateImages[fieldName].adapter;
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
generationAdapter = this.options.imageGenerationAdapter;
|
|
419
|
-
}
|
|
420
|
-
const resp = yield generationAdapter.generate({
|
|
421
|
-
prompt,
|
|
422
|
-
inputFiles: attachmentFiles,
|
|
423
|
-
n: 1,
|
|
424
|
-
size: this.options.generateImages[fieldName].outputSize,
|
|
425
|
-
});
|
|
426
|
-
return resp.imageURLs[0];
|
|
427
|
-
})));
|
|
428
|
-
this.totalCalls++;
|
|
429
|
-
this.totalDuration += (+new Date() - start) / 1000;
|
|
430
|
-
return { images };
|
|
431
|
-
})
|
|
432
|
-
});
|
|
433
|
-
server.endpoint({
|
|
434
|
-
method: 'POST',
|
|
435
|
-
path: `/plugin/${this.pluginInstanceId}/initial_image_generate`,
|
|
436
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, headers }) {
|
|
437
|
-
var _b;
|
|
438
|
-
const selectedIds = body.selectedIds || [];
|
|
439
|
-
const STUB_MODE = false;
|
|
440
|
-
if (typeof ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.generateImages) === 'string') {
|
|
441
|
-
if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
|
|
442
|
-
return { error: "Rate limit exceeded" };
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
const start = +new Date();
|
|
446
|
-
const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
|
|
447
|
-
var _a, _b;
|
|
448
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ((_a = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _a === void 0 ? void 0 : _a.name, ID)]);
|
|
449
|
-
let attachmentFiles;
|
|
450
|
-
if (!this.options.attachFiles) {
|
|
451
|
-
attachmentFiles = [];
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
attachmentFiles = yield this.options.attachFiles({ record });
|
|
455
|
-
}
|
|
456
|
-
const fieldTasks = Object.keys(((_b = this.options) === null || _b === void 0 ? void 0 : _b.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
|
|
457
|
-
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
458
|
-
let images;
|
|
459
|
-
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
460
|
-
return { key, images: [] };
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
if (STUB_MODE) {
|
|
464
|
-
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
465
|
-
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
let generationAdapter;
|
|
469
|
-
if (this.options.generateImages[key].adapter) {
|
|
470
|
-
generationAdapter = this.options.generateImages[key].adapter;
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
generationAdapter = this.options.imageGenerationAdapter;
|
|
474
|
-
}
|
|
475
|
-
const resp = yield generationAdapter.generate({
|
|
476
|
-
prompt,
|
|
477
|
-
inputFiles: attachmentFiles,
|
|
478
|
-
n: 1,
|
|
479
|
-
size: this.options.generateImages[key].outputSize,
|
|
480
|
-
});
|
|
481
|
-
images = resp.imageURLs[0];
|
|
482
|
-
}
|
|
483
|
-
return { key, images };
|
|
484
|
-
}
|
|
485
|
-
}));
|
|
486
|
-
const fieldResults = yield Promise.all(fieldTasks);
|
|
487
|
-
const recordResult = {};
|
|
488
|
-
fieldResults.forEach(({ key, images }) => {
|
|
489
|
-
recordResult[key] = images;
|
|
490
|
-
});
|
|
491
|
-
return recordResult;
|
|
492
|
-
}));
|
|
493
|
-
const result = yield Promise.all(tasks);
|
|
494
|
-
this.totalCalls++;
|
|
495
|
-
this.totalDuration += (+new Date() - start) / 1000;
|
|
496
|
-
return { result };
|
|
497
|
-
})
|
|
498
|
-
});
|
|
499
564
|
server.endpoint({
|
|
500
565
|
method: 'POST',
|
|
501
566
|
path: `/plugin/${this.pluginInstanceId}/get_generation_prompts`,
|
|
@@ -518,5 +583,59 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
518
583
|
};
|
|
519
584
|
})
|
|
520
585
|
});
|
|
586
|
+
server.endpoint({
|
|
587
|
+
method: 'POST',
|
|
588
|
+
path: `/plugin/${this.pluginInstanceId}/create-job`,
|
|
589
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
590
|
+
const { actionType, recordId } = body;
|
|
591
|
+
const jobId = randomUUID();
|
|
592
|
+
jobs.set(jobId, { status: "in_progress" });
|
|
593
|
+
if (!actionType) {
|
|
594
|
+
jobs.set(jobId, { status: "failed", error: "Missing action type" });
|
|
595
|
+
return { error: "Missing action type" };
|
|
596
|
+
}
|
|
597
|
+
if (!recordId) {
|
|
598
|
+
jobs.set(jobId, { status: "failed", error: "Missing record id" });
|
|
599
|
+
return { error: "Missing record id" };
|
|
600
|
+
}
|
|
601
|
+
switch (actionType) {
|
|
602
|
+
case 'generate_images':
|
|
603
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.initialImageGenerate(jobId, recordId, adminUser, headers); }), 100);
|
|
604
|
+
break;
|
|
605
|
+
case 'analyze_no_images':
|
|
606
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.analyzeNoImages(jobId, recordId, adminUser, headers); }), 100);
|
|
607
|
+
break;
|
|
608
|
+
case 'analyze':
|
|
609
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.analyze_image(jobId, recordId, adminUser, headers); }), 100);
|
|
610
|
+
break;
|
|
611
|
+
case 'regenerate_images':
|
|
612
|
+
if (!body.prompt || !body.fieldName) {
|
|
613
|
+
jobs.set(jobId, { status: "failed", error: "Missing prompt or field name" });
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () { return yield this.regenerateImage(jobId, recordId, body.fieldName, body.prompt, adminUser, headers); }), 100);
|
|
617
|
+
break;
|
|
618
|
+
default:
|
|
619
|
+
jobs.set(jobId, { status: "failed", error: "Unknown action type" });
|
|
620
|
+
}
|
|
621
|
+
setTimeout(() => jobs.delete(jobId), 300000);
|
|
622
|
+
return { ok: true, jobId };
|
|
623
|
+
})
|
|
624
|
+
});
|
|
625
|
+
server.endpoint({
|
|
626
|
+
method: 'POST',
|
|
627
|
+
path: `/plugin/${this.pluginInstanceId}/get-job-status`,
|
|
628
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
629
|
+
const jobId = body.jobId;
|
|
630
|
+
if (!jobId) {
|
|
631
|
+
return { error: "Can't find job id" };
|
|
632
|
+
}
|
|
633
|
+
const job = jobs.get(jobId);
|
|
634
|
+
if (!job) {
|
|
635
|
+
return { error: "Job not found" };
|
|
636
|
+
}
|
|
637
|
+
return { ok: true, job };
|
|
638
|
+
})
|
|
639
|
+
});
|
|
521
640
|
}
|
|
522
641
|
}
|