@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/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
- if (plugin && plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
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
  }