@adminforth/bulk-ai-flow 1.8.1 → 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 +42 -13
- package/custom/VisionAction.vue +153 -64
- package/custom/VisionTable.vue +3 -1
- package/dist/custom/ImageGenerationCarousel.vue +42 -13
- package/dist/custom/VisionAction.vue +153 -64
- package/dist/custom/VisionTable.vue +3 -1
- package/dist/index.js +310 -195
- package/index.ts +315 -207
- 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;
|
|
@@ -116,6 +365,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
116
365
|
isFieldsForAnalizePlain: this.options.fillPlainFields ? Object.keys(this.options.fillPlainFields).length > 0 : false,
|
|
117
366
|
isImageGeneration: this.options.generateImages ? Object.keys(this.options.generateImages).length > 0 : false,
|
|
118
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
|
+
}
|
|
119
375
|
}
|
|
120
376
|
};
|
|
121
377
|
if (!resourceConfig.options.pageInjections) {
|
|
@@ -191,86 +447,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
191
447
|
return `${this.pluginOptions.actionName}`;
|
|
192
448
|
}
|
|
193
449
|
setupEndpoints(server) {
|
|
194
|
-
server.endpoint({
|
|
195
|
-
method: 'POST',
|
|
196
|
-
path: `/plugin/${this.pluginInstanceId}/analyze`,
|
|
197
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
198
|
-
var _b;
|
|
199
|
-
const selectedIds = body.selectedIds || [];
|
|
200
|
-
if (typeof ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.fillFieldsFromImages) === 'string') {
|
|
201
|
-
if (this.checkRateLimit("fillFieldsFromImages", this.options.rateLimits.fillFieldsFromImages, headers)) {
|
|
202
|
-
return { error: "Rate limit exceeded" };
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
|
|
206
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
207
|
-
// Fetch the record using the provided ID
|
|
208
|
-
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
209
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
|
|
210
|
-
//recieve image URLs to analyze
|
|
211
|
-
const attachmentFiles = yield this.options.attachFiles({ record: record });
|
|
212
|
-
if (attachmentFiles.length !== 0) {
|
|
213
|
-
//create prompt for OpenAI
|
|
214
|
-
const compiledOutputFields = this.compileOutputFieldsTemplates(record);
|
|
215
|
-
const prompt = `Analyze the following image(s) and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
216
|
-
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
217
|
-
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names. If it's number field - return only number.
|
|
218
|
-
Image URLs:`;
|
|
219
|
-
//send prompt to OpenAI and get response
|
|
220
|
-
const chatResponse = yield this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
|
|
221
|
-
const resp = chatResponse.response;
|
|
222
|
-
const topLevelError = chatResponse.error;
|
|
223
|
-
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
224
|
-
throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
|
|
225
|
-
}
|
|
226
|
-
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;
|
|
227
|
-
if (!textOutput || typeof textOutput !== 'string') {
|
|
228
|
-
throw new Error('Unexpected AI response format');
|
|
229
|
-
}
|
|
230
|
-
//parse response and update record
|
|
231
|
-
const resData = JSON.parse(textOutput);
|
|
232
|
-
return resData;
|
|
233
|
-
}
|
|
234
|
-
;
|
|
235
|
-
}));
|
|
236
|
-
const result = yield Promise.all(tasks);
|
|
237
|
-
return { result };
|
|
238
|
-
})
|
|
239
|
-
});
|
|
240
|
-
server.endpoint({
|
|
241
|
-
method: 'POST',
|
|
242
|
-
path: `/plugin/${this.pluginInstanceId}/analyze_no_images`,
|
|
243
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
244
|
-
var _b;
|
|
245
|
-
const selectedIds = body.selectedIds || [];
|
|
246
|
-
if (typeof ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.fillPlainFields) === 'string') {
|
|
247
|
-
if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
|
|
248
|
-
return { error: "Rate limit exceeded" };
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
|
|
252
|
-
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
253
|
-
const record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(primaryKeyColumn.name, ID)]);
|
|
254
|
-
const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
|
|
255
|
-
const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
256
|
-
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
257
|
-
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
|
|
258
|
-
If it's number field - return only number.`;
|
|
259
|
-
//send prompt to OpenAI and get response
|
|
260
|
-
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
|
|
261
|
-
const { content: chatResponse } = yield this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
|
|
262
|
-
const resp = chatResponse.response;
|
|
263
|
-
const topLevelError = chatResponse.error;
|
|
264
|
-
if (topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error)) {
|
|
265
|
-
throw new Error(`ERROR: ${JSON.stringify(topLevelError || (resp === null || resp === void 0 ? void 0 : resp.error))}`);
|
|
266
|
-
}
|
|
267
|
-
const resData = JSON.parse(chatResponse);
|
|
268
|
-
return resData;
|
|
269
|
-
}));
|
|
270
|
-
const result = yield Promise.all(tasks);
|
|
271
|
-
return { result };
|
|
272
|
-
})
|
|
273
|
-
});
|
|
274
450
|
server.endpoint({
|
|
275
451
|
method: 'POST',
|
|
276
452
|
path: `/plugin/${this.pluginInstanceId}/get_records`,
|
|
@@ -385,121 +561,6 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
385
561
|
}
|
|
386
562
|
})
|
|
387
563
|
});
|
|
388
|
-
server.endpoint({
|
|
389
|
-
method: 'POST',
|
|
390
|
-
path: `/plugin/${this.pluginInstanceId}/regenerate_images`,
|
|
391
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, headers }) {
|
|
392
|
-
var _b;
|
|
393
|
-
const Id = body.recordId || [];
|
|
394
|
-
const prompt = body.prompt || '';
|
|
395
|
-
const fieldName = body.fieldName || '';
|
|
396
|
-
if (this.checkRateLimit(fieldName, this.options.generateImages[fieldName].rateLimit, headers)) {
|
|
397
|
-
return { error: "Rate limit exceeded" };
|
|
398
|
-
}
|
|
399
|
-
const start = +new Date();
|
|
400
|
-
const STUB_MODE = false;
|
|
401
|
-
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)]);
|
|
402
|
-
let attachmentFiles;
|
|
403
|
-
if (!this.options.attachFiles) {
|
|
404
|
-
attachmentFiles = [];
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
attachmentFiles = yield this.options.attachFiles({ record });
|
|
408
|
-
}
|
|
409
|
-
const images = yield Promise.all((new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
|
|
410
|
-
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
if (STUB_MODE) {
|
|
414
|
-
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
415
|
-
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
416
|
-
}
|
|
417
|
-
let generationAdapter;
|
|
418
|
-
if (this.options.generateImages[fieldName].adapter) {
|
|
419
|
-
generationAdapter = this.options.generateImages[fieldName].adapter;
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
generationAdapter = this.options.imageGenerationAdapter;
|
|
423
|
-
}
|
|
424
|
-
const resp = yield generationAdapter.generate({
|
|
425
|
-
prompt,
|
|
426
|
-
inputFiles: attachmentFiles,
|
|
427
|
-
n: 1,
|
|
428
|
-
size: this.options.generateImages[fieldName].outputSize,
|
|
429
|
-
});
|
|
430
|
-
return resp.imageURLs[0];
|
|
431
|
-
})));
|
|
432
|
-
this.totalCalls++;
|
|
433
|
-
this.totalDuration += (+new Date() - start) / 1000;
|
|
434
|
-
return { images };
|
|
435
|
-
})
|
|
436
|
-
});
|
|
437
|
-
server.endpoint({
|
|
438
|
-
method: 'POST',
|
|
439
|
-
path: `/plugin/${this.pluginInstanceId}/initial_image_generate`,
|
|
440
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, headers }) {
|
|
441
|
-
var _b;
|
|
442
|
-
const selectedIds = body.selectedIds || [];
|
|
443
|
-
const STUB_MODE = false;
|
|
444
|
-
if (typeof ((_b = this.options.rateLimits) === null || _b === void 0 ? void 0 : _b.generateImages) === 'string') {
|
|
445
|
-
if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
|
|
446
|
-
return { error: "Rate limit exceeded" };
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
const start = +new Date();
|
|
450
|
-
const tasks = selectedIds.map((ID) => __awaiter(this, void 0, void 0, function* () {
|
|
451
|
-
var _a, _b;
|
|
452
|
-
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)]);
|
|
453
|
-
let attachmentFiles;
|
|
454
|
-
if (!this.options.attachFiles) {
|
|
455
|
-
attachmentFiles = [];
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
attachmentFiles = yield this.options.attachFiles({ record });
|
|
459
|
-
}
|
|
460
|
-
const fieldTasks = Object.keys(((_b = this.options) === null || _b === void 0 ? void 0 : _b.generateImages) || {}).map((key) => __awaiter(this, void 0, void 0, function* () {
|
|
461
|
-
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
462
|
-
let images;
|
|
463
|
-
if (this.options.attachFiles && attachmentFiles.length === 0) {
|
|
464
|
-
return { key, images: [] };
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
if (STUB_MODE) {
|
|
468
|
-
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
469
|
-
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
let generationAdapter;
|
|
473
|
-
if (this.options.generateImages[key].adapter) {
|
|
474
|
-
generationAdapter = this.options.generateImages[key].adapter;
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
generationAdapter = this.options.imageGenerationAdapter;
|
|
478
|
-
}
|
|
479
|
-
const resp = yield generationAdapter.generate({
|
|
480
|
-
prompt,
|
|
481
|
-
inputFiles: attachmentFiles,
|
|
482
|
-
n: 1,
|
|
483
|
-
size: this.options.generateImages[key].outputSize,
|
|
484
|
-
});
|
|
485
|
-
images = resp.imageURLs[0];
|
|
486
|
-
}
|
|
487
|
-
return { key, images };
|
|
488
|
-
}
|
|
489
|
-
}));
|
|
490
|
-
const fieldResults = yield Promise.all(fieldTasks);
|
|
491
|
-
const recordResult = {};
|
|
492
|
-
fieldResults.forEach(({ key, images }) => {
|
|
493
|
-
recordResult[key] = images;
|
|
494
|
-
});
|
|
495
|
-
return recordResult;
|
|
496
|
-
}));
|
|
497
|
-
const result = yield Promise.all(tasks);
|
|
498
|
-
this.totalCalls++;
|
|
499
|
-
this.totalDuration += (+new Date() - start) / 1000;
|
|
500
|
-
return { result };
|
|
501
|
-
})
|
|
502
|
-
});
|
|
503
564
|
server.endpoint({
|
|
504
565
|
method: 'POST',
|
|
505
566
|
path: `/plugin/${this.pluginInstanceId}/get_generation_prompts`,
|
|
@@ -522,5 +583,59 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
522
583
|
};
|
|
523
584
|
})
|
|
524
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
|
+
});
|
|
525
640
|
}
|
|
526
641
|
}
|