@adminforth/bulk-ai-flow 1.1.5 → 1.2.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 +3 -2
- package/custom/imageGenerationCarousel.vue +462 -0
- package/custom/visionAction.vue +400 -80
- package/custom/visionTable.vue +56 -4
- package/dist/custom/imageGenerationCarousel.vue +462 -0
- package/dist/custom/visionAction.vue +400 -80
- package/dist/custom/visionTable.vue +56 -4
- package/dist/index.js +295 -17
- package/index.ts +346 -26
- package/package.json +1 -1
- package/types.ts +41 -3
package/index.ts
CHANGED
|
@@ -2,16 +2,23 @@ import { AdminForthPlugin, Filters } from "adminforth";
|
|
|
2
2
|
import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
4
|
import { json } from "stream/consumers";
|
|
5
|
-
import Handlebars from 'handlebars';
|
|
5
|
+
import Handlebars, { compile } from 'handlebars';
|
|
6
|
+
import { RateLimiter } from "adminforth";
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
9
10
|
options: PluginOptions;
|
|
10
11
|
uploadPlugin: AdminForthPlugin;
|
|
12
|
+
totalCalls: number;
|
|
13
|
+
totalDuration: number;
|
|
11
14
|
|
|
12
15
|
constructor(options: PluginOptions) {
|
|
13
16
|
super(options, import.meta.url);
|
|
14
17
|
this.options = options;
|
|
18
|
+
|
|
19
|
+
// for calculating average time
|
|
20
|
+
this.totalCalls = 0;
|
|
21
|
+
this.totalDuration = 0;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
// Compile Handlebars templates in outputFields using record fields as context
|
|
@@ -27,28 +34,114 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
27
34
|
}
|
|
28
35
|
return compiled;
|
|
29
36
|
}
|
|
30
|
-
|
|
37
|
+
|
|
38
|
+
private compileOutputFieldsTemplatesNoImage(record: any): Record<string, string> {
|
|
39
|
+
const compiled: Record<string, string> = {};
|
|
40
|
+
for (const [key, templateStr] of Object.entries(this.options.fillPlainFields)) {
|
|
41
|
+
try {
|
|
42
|
+
const tpl = Handlebars.compile(String(templateStr));
|
|
43
|
+
compiled[key] = tpl(record);
|
|
44
|
+
} catch {
|
|
45
|
+
compiled[key] = String(templateStr);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return compiled;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private compileGenerationFieldTemplates(record: any) {
|
|
52
|
+
const compiled: Record<string, any> = {};
|
|
53
|
+
for (const key in this.options.generateImages) {
|
|
54
|
+
try {
|
|
55
|
+
const tpl = Handlebars.compile(String(this.options.generateImages[key].prompt));
|
|
56
|
+
compiled[key] = tpl(record);
|
|
57
|
+
} catch {
|
|
58
|
+
compiled[key] = String(this.options.generateImages[key].prompt);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return compiled;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private checkRateLimit(fieldNameRateLimit: string | undefined, headers: Record<string, string | string[] | undefined>): { error?: string } | void {
|
|
65
|
+
if (fieldNameRateLimit) {
|
|
66
|
+
// rate limit
|
|
67
|
+
const { error } = RateLimiter.checkRateLimit(
|
|
68
|
+
this.pluginInstanceId,
|
|
69
|
+
fieldNameRateLimit,
|
|
70
|
+
this.adminforth.auth.getClientIp(headers),
|
|
71
|
+
);
|
|
72
|
+
if (error) {
|
|
73
|
+
return { error: "Rate limit exceeded" };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
31
78
|
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
32
79
|
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
33
80
|
|
|
34
81
|
//check if options names are provided
|
|
35
82
|
const columns = this.resourceConfig.columns;
|
|
36
83
|
let columnEnums = [];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
if (this.options.fillFieldsFromImages) {
|
|
85
|
+
if (!this.options.attachFiles) {
|
|
86
|
+
throw new Error('⚠️ attachFiles function must be provided in options when fillFieldsFromImages is used');
|
|
87
|
+
}
|
|
88
|
+
if (!this.options.visionAdapter) {
|
|
89
|
+
throw new Error('⚠️ visionAdapter must be provided in options when fillFieldsFromImages is used');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const [key, value] of Object.entries((this.options.fillFieldsFromImages ))) {
|
|
93
|
+
const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
|
|
94
|
+
if (column) {
|
|
95
|
+
if(column.enum){
|
|
96
|
+
(this.options.fillFieldsFromImages as any)[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
|
|
97
|
+
columnEnums.push({
|
|
98
|
+
name: key,
|
|
99
|
+
enum: column.enum,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`⚠️ No column found for key "${key}"`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.options.fillPlainFields) {
|
|
109
|
+
if (!this.options.textCompleteAdapter) {
|
|
110
|
+
throw new Error('⚠️ textCompleteAdapter must be provided in options when fillPlainFields is used');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const [key, value] of Object.entries((this.options.fillPlainFields))) {
|
|
114
|
+
const column = columns.find(c => c.name.toLowerCase() === key.toLowerCase());
|
|
115
|
+
if (column) {
|
|
116
|
+
if(column.enum){
|
|
117
|
+
(this.options.fillPlainFields as any)[key] = `${value} Select ${key} from the list (USE ONLY VALUE FIELD. USE ONLY VALUES FROM THIS LIST): ${JSON.stringify(column.enum)}`;
|
|
118
|
+
columnEnums.push({
|
|
119
|
+
name: key,
|
|
120
|
+
enum: column.enum,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
throw new Error(`⚠️ No column found for key "${key}"`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.options.generateImages && !this.options.imageGenerationAdapter) {
|
|
130
|
+
for (const [key, value] of Object.entries(this.options.generateImages)) {
|
|
131
|
+
if (!this.options.generateImages[key].adapter) {
|
|
132
|
+
throw new Error(`⚠️ No image generation adapter found for key "${key}"`);
|
|
46
133
|
}
|
|
47
|
-
} else {
|
|
48
|
-
throw new Error(`⚠️ No column found for key "${key}"`);
|
|
49
134
|
}
|
|
50
135
|
}
|
|
136
|
+
|
|
51
137
|
|
|
138
|
+
const outputImageFields = [];
|
|
139
|
+
if (this.options.generateImages) {
|
|
140
|
+
for (const [key, value] of Object.entries(this.options.generateImages)) {
|
|
141
|
+
outputImageFields.push(key);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const outputImagesPluginInstanceIds = {};
|
|
52
145
|
//check if Upload plugin is installed on all attachment fields
|
|
53
146
|
if (this.options.generateImages) {
|
|
54
147
|
for (const [key, value] of Object.entries(this.options.generateImages)) {
|
|
@@ -69,12 +162,17 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
69
162
|
Please configure adapter in such way that it will store objects publicly (e.g. for S3 use 'public-read' ACL).
|
|
70
163
|
`);
|
|
71
164
|
}
|
|
72
|
-
this.uploadPlugin = plugin;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
165
|
|
|
166
|
+
outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
|
|
167
|
+
}
|
|
76
168
|
}
|
|
77
169
|
|
|
170
|
+
const outputFields = {
|
|
171
|
+
...this.options.fillFieldsFromImages,
|
|
172
|
+
...this.options.fillPlainFields,
|
|
173
|
+
...(this.options.generateImages || {})
|
|
174
|
+
};
|
|
175
|
+
|
|
78
176
|
|
|
79
177
|
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
80
178
|
|
|
@@ -82,10 +180,16 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
82
180
|
file: this.componentPath('visionAction.vue'),
|
|
83
181
|
meta: {
|
|
84
182
|
pluginInstanceId: this.pluginInstanceId,
|
|
85
|
-
outputFields:
|
|
183
|
+
outputFields: outputFields,
|
|
86
184
|
actionName: this.options.actionName,
|
|
87
185
|
columnEnums: columnEnums,
|
|
186
|
+
outputImageFields: outputImageFields,
|
|
187
|
+
outputPlainFields: this.options.fillPlainFields,
|
|
88
188
|
primaryKey: primaryKeyColumn.name,
|
|
189
|
+
outputImagesPluginInstanceIds: outputImagesPluginInstanceIds,
|
|
190
|
+
isFieldsForAnalizeFromImages: this.options.fillFieldsFromImages ? Object.keys(this.options.fillFieldsFromImages).length > 0 : false,
|
|
191
|
+
isFieldsForAnalizePlain: this.options.fillPlainFields ? Object.keys(this.options.fillPlainFields).length > 0 : false,
|
|
192
|
+
isImageGeneration: this.options.generateImages ? Object.keys(this.options.generateImages).length > 0 : false
|
|
89
193
|
}
|
|
90
194
|
}
|
|
91
195
|
|
|
@@ -110,6 +214,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
110
214
|
}
|
|
111
215
|
|
|
112
216
|
setupEndpoints(server: IHttpServer) {
|
|
217
|
+
|
|
218
|
+
|
|
113
219
|
server.endpoint({
|
|
114
220
|
method: 'POST',
|
|
115
221
|
path: `/plugin/${this.pluginInstanceId}/analyze`,
|
|
@@ -154,6 +260,45 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
154
260
|
return { result };
|
|
155
261
|
}
|
|
156
262
|
});
|
|
263
|
+
|
|
264
|
+
server.endpoint({
|
|
265
|
+
method: 'POST',
|
|
266
|
+
path: `/plugin/${this.pluginInstanceId}/analyze_no_images`,
|
|
267
|
+
handler: async ({ body, adminUser, headers }) => {
|
|
268
|
+
const selectedIds = body.selectedIds || [];
|
|
269
|
+
const tasks = selectedIds.map(async (ID) => {
|
|
270
|
+
// Fetch the record using the provided ID
|
|
271
|
+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
272
|
+
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, ID)] );
|
|
273
|
+
|
|
274
|
+
//create prompt for OpenAI
|
|
275
|
+
const compiledOutputFields = this.compileOutputFieldsTemplatesNoImage(record);
|
|
276
|
+
const prompt = `Analyze the following fields and return a single JSON in format like: {'param1': 'value1', 'param2': 'value2'}.
|
|
277
|
+
Do NOT return array of objects. Do NOT include any Markdown, code blocks, explanations, or extra text. Only return valid JSON.
|
|
278
|
+
Each object must contain the following fields: ${JSON.stringify(compiledOutputFields)} Use the exact field names.
|
|
279
|
+
If it's number field - return only number.`;
|
|
280
|
+
//send prompt to OpenAI and get response
|
|
281
|
+
const { content: chatResponse, finishReason } = await this.options.textCompleteAdapter.complete(prompt, [], 500);
|
|
282
|
+
|
|
283
|
+
const resp: any = (chatResponse as any).response;
|
|
284
|
+
const topLevelError = (chatResponse as any).error;
|
|
285
|
+
if (topLevelError || resp?.error) {
|
|
286
|
+
throw new Error(`ERROR: ${JSON.stringify(topLevelError || resp?.error)}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
//parse response and update record
|
|
290
|
+
const resData = JSON.parse(chatResponse);
|
|
291
|
+
|
|
292
|
+
return resData;
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const result = await Promise.all(tasks);
|
|
296
|
+
|
|
297
|
+
return { result };
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
|
|
157
302
|
server.endpoint({
|
|
158
303
|
method: 'POST',
|
|
159
304
|
path: `/plugin/${this.pluginInstanceId}/get_records`,
|
|
@@ -169,6 +314,8 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
169
314
|
};
|
|
170
315
|
}
|
|
171
316
|
});
|
|
317
|
+
|
|
318
|
+
|
|
172
319
|
server.endpoint({
|
|
173
320
|
method: 'POST',
|
|
174
321
|
path: `/plugin/${this.pluginInstanceId}/get_images`,
|
|
@@ -176,7 +323,9 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
176
323
|
let images = [];
|
|
177
324
|
if(body.body.record){
|
|
178
325
|
for( const record of body.body.record ) {
|
|
179
|
-
|
|
326
|
+
if (this.options.attachFiles) {
|
|
327
|
+
images.push(await this.options.attachFiles({ record: record }));
|
|
328
|
+
}
|
|
180
329
|
}
|
|
181
330
|
}
|
|
182
331
|
return {
|
|
@@ -184,24 +333,195 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
|
|
|
184
333
|
};
|
|
185
334
|
}
|
|
186
335
|
});
|
|
336
|
+
|
|
337
|
+
|
|
187
338
|
server.endpoint({
|
|
188
339
|
method: 'POST',
|
|
189
340
|
path: `/plugin/${this.pluginInstanceId}/update_fields`,
|
|
190
341
|
handler: async ( body ) => {
|
|
191
342
|
const selectedIds = body.body.selectedIds || [];
|
|
192
343
|
const fieldsToUpdate = body.body.fields || {};
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.
|
|
344
|
+
const outputImageFields = [];
|
|
345
|
+
if (this.options.generateImages) {
|
|
346
|
+
for (const [key, value] of Object.entries(this.options.generateImages)) {
|
|
347
|
+
outputImageFields.push(key);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
|
|
351
|
+
const updates = selectedIds.map(async (ID, idx) => {
|
|
352
|
+
const oldRecord = await this.adminforth.resource(this.resourceConfig.resourceId).get( [Filters.EQ(primaryKeyColumn.name, ID)] );
|
|
353
|
+
for (const [key, value] of Object.entries(outputImageFields)) {
|
|
354
|
+
const columnPlugin = this.adminforth.activatedPlugins.find(p =>
|
|
355
|
+
p.resourceConfig!.resourceId === this.resourceConfig.resourceId &&
|
|
356
|
+
p.pluginOptions.pathColumnName === value
|
|
357
|
+
);
|
|
358
|
+
if (columnPlugin) {
|
|
359
|
+
if(columnPlugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
|
|
360
|
+
if (oldRecord[value]) {
|
|
361
|
+
// put tag to delete old file
|
|
362
|
+
try {
|
|
363
|
+
await columnPlugin.pluginOptions.storageAdapter.markKeyForDeletation(oldRecord[value]);
|
|
364
|
+
} catch (e) {
|
|
365
|
+
// file might be e.g. already deleted, so we catch error
|
|
366
|
+
console.error(`Error setting tag to true for object ${oldRecord[value]}. File will not be auto-cleaned up`, e);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (fieldsToUpdate[idx][key] !== null) {
|
|
370
|
+
// remove tag from new file
|
|
371
|
+
// in this case we let it crash if it fails: this is a new file which just was uploaded.
|
|
372
|
+
await columnPlugin.pluginOptions.storageAdapter.markKeyForNotDeletation(fieldsToUpdate[idx][value]);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return this.adminforth.resource(this.resourceConfig.resourceId).update(ID, fieldsToUpdate[idx])
|
|
378
|
+
});
|
|
379
|
+
await Promise.all(updates);
|
|
380
|
+
return { ok: true };
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
server.endpoint({
|
|
386
|
+
method: 'POST',
|
|
387
|
+
path: `/plugin/${this.pluginInstanceId}/regenerate_images`,
|
|
388
|
+
handler: async ({ body, headers }) => {
|
|
389
|
+
const Id = body.recordId || [];
|
|
390
|
+
const prompt = body.prompt || '';
|
|
391
|
+
const fieldName = body.fieldName || '';
|
|
392
|
+
if (this.checkRateLimit(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 = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, Id)]);
|
|
398
|
+
let attachmentFiles
|
|
399
|
+
if(!this.options.attachFiles){
|
|
400
|
+
attachmentFiles = [];
|
|
401
|
+
} else {
|
|
402
|
+
attachmentFiles = await this.options.attachFiles({ record });
|
|
403
|
+
}
|
|
404
|
+
const images = await Promise.all(
|
|
405
|
+
(new Array(this.options.generateImages[fieldName].countToGenerate)).fill(0).map(async () => {
|
|
406
|
+
|
|
407
|
+
if (STUB_MODE) {
|
|
408
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
409
|
+
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let generationAdapter;
|
|
413
|
+
if (this.options.generateImages[fieldName].adapter) {
|
|
414
|
+
generationAdapter = this.options.generateImages[fieldName].adapter;
|
|
415
|
+
} else {
|
|
416
|
+
generationAdapter = this.options.imageGenerationAdapter;
|
|
417
|
+
}
|
|
418
|
+
const resp = await generationAdapter.generate(
|
|
419
|
+
{
|
|
420
|
+
prompt,
|
|
421
|
+
inputFiles: attachmentFiles,
|
|
422
|
+
n: 1,
|
|
423
|
+
size: this.options.generateImages[fieldName].outputSize,
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
return resp.imageURLs[0]
|
|
427
|
+
})
|
|
197
428
|
);
|
|
429
|
+
this.totalCalls++;
|
|
430
|
+
this.totalDuration += (+new Date() - start) / 1000;
|
|
431
|
+
return { images };
|
|
432
|
+
}
|
|
433
|
+
});
|
|
198
434
|
|
|
199
|
-
await Promise.all(updates);
|
|
200
435
|
|
|
201
|
-
|
|
436
|
+
server.endpoint({
|
|
437
|
+
method: 'POST',
|
|
438
|
+
path: `/plugin/${this.pluginInstanceId}/initial_image_generate`,
|
|
439
|
+
handler: async ({ body, headers }) => {
|
|
440
|
+
const selectedIds = body.selectedIds || [];
|
|
441
|
+
const STUB_MODE = false;
|
|
442
|
+
|
|
443
|
+
if (this.checkRateLimit(this.options.bulkGenerationRateLimit, headers)) {
|
|
444
|
+
return { error: "Rate limit exceeded" };
|
|
445
|
+
}
|
|
446
|
+
const start = +new Date();
|
|
447
|
+
const tasks = selectedIds.map(async (ID) => {
|
|
448
|
+
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, ID)]);
|
|
449
|
+
let attachmentFiles
|
|
450
|
+
if(!this.options.attachFiles){
|
|
451
|
+
attachmentFiles = [];
|
|
452
|
+
} else {
|
|
453
|
+
attachmentFiles = await this.options.attachFiles({ record });
|
|
454
|
+
}
|
|
455
|
+
const fieldTasks = Object.keys(this.options?.generateImages || {}).map(async (key) => {
|
|
456
|
+
const prompt = this.compileGenerationFieldTemplates(record)[key];
|
|
457
|
+
let images;
|
|
458
|
+
if (STUB_MODE) {
|
|
459
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
460
|
+
images = `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
461
|
+
} else {
|
|
462
|
+
let generationAdapter;
|
|
463
|
+
if (this.options.generateImages[key].adapter) {
|
|
464
|
+
generationAdapter = this.options.generateImages[key].adapter;
|
|
465
|
+
} else {
|
|
466
|
+
generationAdapter = this.options.imageGenerationAdapter;``
|
|
467
|
+
}
|
|
468
|
+
const resp = await generationAdapter.generate(
|
|
469
|
+
{
|
|
470
|
+
prompt,
|
|
471
|
+
inputFiles: attachmentFiles,
|
|
472
|
+
n: 1,
|
|
473
|
+
size: this.options.generateImages[key].outputSize,
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
images = resp.imageURLs[0];
|
|
477
|
+
}
|
|
478
|
+
return { key, images };
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const fieldResults = await Promise.all(fieldTasks);
|
|
482
|
+
const recordResult: Record<string, string[]> = {};
|
|
483
|
+
|
|
484
|
+
fieldResults.forEach(({ key, images }) => {
|
|
485
|
+
recordResult[key] = images;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
return recordResult;
|
|
489
|
+
});
|
|
490
|
+
const result = await Promise.all(tasks);
|
|
491
|
+
|
|
492
|
+
this.totalCalls++;
|
|
493
|
+
this.totalDuration += (+new Date() - start) / 1000;
|
|
494
|
+
|
|
495
|
+
return { result };
|
|
202
496
|
}
|
|
203
497
|
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
498
|
|
|
207
499
|
|
|
500
|
+
server.endpoint({
|
|
501
|
+
method: 'POST',
|
|
502
|
+
path: `/plugin/${this.pluginInstanceId}/get_generation_prompts`,
|
|
503
|
+
handler: async ({ body, headers }) => {
|
|
504
|
+
const Id = body.recordId || [];
|
|
505
|
+
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, Id)]);
|
|
506
|
+
const compiledGenerationOptions = this.compileGenerationFieldTemplates(record);
|
|
507
|
+
return { generationOptions: compiledGenerationOptions };
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
server.endpoint({
|
|
513
|
+
method: 'GET',
|
|
514
|
+
path: `/plugin/${this.pluginInstanceId}/averageDuration`,
|
|
515
|
+
handler: async () => {
|
|
516
|
+
return {
|
|
517
|
+
totalCalls: this.totalCalls,
|
|
518
|
+
totalDuration: this.totalDuration,
|
|
519
|
+
averageDuration: this.totalCalls ? this.totalDuration / this.totalCalls : null,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
}
|
|
527
|
+
}
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -1,12 +1,50 @@
|
|
|
1
|
-
import { ImageVisionAdapter, AdminUser, IAdminForth, StorageAdapter } from "adminforth";
|
|
1
|
+
import { ImageVisionAdapter, AdminUser, IAdminForth, StorageAdapter, ImageGenerationAdapter, CompletionAdapter } from "adminforth";
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
export interface PluginOptions {
|
|
5
5
|
actionName: string,
|
|
6
|
-
visionAdapter
|
|
6
|
+
visionAdapter?: ImageVisionAdapter,
|
|
7
|
+
textCompleteAdapter?: CompletionAdapter,
|
|
8
|
+
/**
|
|
9
|
+
* The adapter to use for image generation.
|
|
10
|
+
*/
|
|
11
|
+
imageGenerationAdapter?: ImageGenerationAdapter,
|
|
7
12
|
fillFieldsFromImages?: Record<string, string>, // can analyze what is on image and fill fields, typical tasks "find dominant color", "describe what is on image", "clasify to one enum item, e.g. what is on image dog/cat/plant"
|
|
8
|
-
|
|
13
|
+
fillPlainFields?: Record<string, string>,
|
|
9
14
|
attachFiles?: ({ record }: {
|
|
10
15
|
record: any,
|
|
11
16
|
}) => string[] | Promise<string[]>,
|
|
17
|
+
|
|
18
|
+
generateImages?: Record<
|
|
19
|
+
string, {
|
|
20
|
+
// can generate from images or just from another fields, e.g. "remove text from images", "improve image quality", "turn image into ghibli style"
|
|
21
|
+
prompt: string,
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Redefine the adapter for your specific generation task
|
|
25
|
+
*/
|
|
26
|
+
adapter?: ImageGenerationAdapter,
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The size of the generated image.
|
|
30
|
+
*/
|
|
31
|
+
outputSize?: string,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Since AI generation can be expensive, we can limit the number of requests per IP.
|
|
35
|
+
* E.g. 5/1d - 5 requests per day
|
|
36
|
+
* 3/1h - 3 requests per hour
|
|
37
|
+
*/
|
|
38
|
+
rateLimit?: string,
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The number of images to regenerate
|
|
42
|
+
* in one request
|
|
43
|
+
*/
|
|
44
|
+
countToGenerate: number,
|
|
45
|
+
}>,
|
|
46
|
+
/**
|
|
47
|
+
* As rateLimit on generateImages, but applied to bulk generations
|
|
48
|
+
**/
|
|
49
|
+
bulkGenerationRateLimit?: string,
|
|
12
50
|
}
|