@adminforth/upload 1.0.15 β†’ 1.0.16

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
@@ -9,7 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
11
11
  import { ExpirationStatus, GetObjectCommand, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
12
- import { AdminForthPlugin } from "adminforth";
12
+ import { AdminForthPlugin, suggestIfTypo } from "adminforth";
13
+ import { Readable } from "stream";
13
14
  const ADMINFORTH_NOT_YET_USED_TAG = 'adminforth-candidate-for-cleanup';
14
15
  export default class UploadPlugin extends AdminForthPlugin {
15
16
  constructor(options) {
@@ -95,17 +96,36 @@ export default class UploadPlugin extends AdminForthPlugin {
95
96
  modifyResourceConfig: { get: () => super.modifyResourceConfig }
96
97
  });
97
98
  return __awaiter(this, void 0, void 0, function* () {
98
- var _a, _b, _c, _d, _e, _f;
99
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
99
100
  _super.modifyResourceConfig.call(this, adminforth, resourceConfig);
100
101
  // after column to store the path of the uploaded file, add new VirtualColumn,
101
102
  // show only in edit and create views
102
103
  // use component uploader.vue
103
104
  const { pathColumnName } = this.options;
105
+ const pathColumnIndex = resourceConfig.columns.findIndex((column) => column.name === pathColumnName);
106
+ if (pathColumnIndex === -1) {
107
+ throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.name}"`);
108
+ }
109
+ if ((_a = this.options.generation) === null || _a === void 0 ? void 0 : _a.fieldsForContext) {
110
+ (_b = this.options.generation) === null || _b === void 0 ? void 0 : _b.fieldsForContext.forEach((field) => {
111
+ if (!resourceConfig.columns.find((column) => column.name === field)) {
112
+ const similar = suggestIfTypo(resourceConfig.columns.map((column) => column.name), field);
113
+ throw new Error(`Field "${field}" specified in fieldsForContext not found in
114
+ resource "${resourceConfig.label}". ${similar ? `Did you mean "${similar}"?` : ''}`);
115
+ }
116
+ });
117
+ }
104
118
  const pluginFrontendOptions = {
105
119
  allowedExtensions: this.options.allowedFileExtensions,
106
120
  maxFileSize: this.options.maxFileSize,
107
121
  pluginInstanceId: this.pluginInstanceId,
122
+ resourceLabel: resourceConfig.label,
123
+ generateImages: this.options.generation ? true : false,
124
+ pathColumnLabel: resourceConfig.columns[pathColumnIndex].label,
125
+ fieldsForContext: (_c = this.options.generation) === null || _c === void 0 ? void 0 : _c.fieldsForContext,
108
126
  };
127
+ // define components which will be imported from other components
128
+ this.componentPath('imageGenerator.vue');
109
129
  const virtualColumn = {
110
130
  virtual: true,
111
131
  name: `uploader_${this.pluginInstanceId}`,
@@ -121,21 +141,17 @@ export default class UploadPlugin extends AdminForthPlugin {
121
141
  },
122
142
  showIn: ['edit', 'create'],
123
143
  };
124
- const pathColumnIndex = resourceConfig.columns.findIndex((column) => column.name === pathColumnName);
125
- if (pathColumnIndex === -1) {
126
- throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.name}"`);
127
- }
128
144
  if (!resourceConfig.columns[pathColumnIndex].components) {
129
145
  resourceConfig.columns[pathColumnIndex].components = {};
130
146
  }
131
- if (((_a = this.options.preview) === null || _a === void 0 ? void 0 : _a.showInList) || ((_b = this.options.preview) === null || _b === void 0 ? void 0 : _b.showInList) === undefined) {
147
+ if (((_d = this.options.preview) === null || _d === void 0 ? void 0 : _d.showInList) || ((_e = this.options.preview) === null || _e === void 0 ? void 0 : _e.showInList) === undefined) {
132
148
  // add preview column to list
133
149
  resourceConfig.columns[pathColumnIndex].components.list = {
134
150
  file: this.componentPath('preview.vue'),
135
151
  meta: pluginFrontendOptions,
136
152
  };
137
153
  }
138
- if (((_c = this.options.preview) === null || _c === void 0 ? void 0 : _c.showInShow) || ((_d = this.options.preview) === null || _d === void 0 ? void 0 : _d.showInShow) === undefined) {
154
+ if (((_f = this.options.preview) === null || _f === void 0 ? void 0 : _f.showInShow) || ((_g = this.options.preview) === null || _g === void 0 ? void 0 : _g.showInShow) === undefined) {
139
155
  resourceConfig.columns[pathColumnIndex].components.show = {
140
156
  file: this.componentPath('preview.vue'),
141
157
  meta: pluginFrontendOptions,
@@ -156,7 +172,7 @@ export default class UploadPlugin extends AdminForthPlugin {
156
172
  virtualColumn.editingNote = pathColumn.editingNote;
157
173
  // ** HOOKS FOR CREATE **//
158
174
  // add beforeSave hook to save virtual column to path column
159
- resourceConfig.hooks.create.beforeSave.push((_g) => __awaiter(this, [_g], void 0, function* ({ record }) {
175
+ resourceConfig.hooks.create.beforeSave.push((_k) => __awaiter(this, [_k], void 0, function* ({ record }) {
160
176
  if (record[virtualColumn.name]) {
161
177
  record[pathColumnName] = record[virtualColumn.name];
162
178
  delete record[virtualColumn.name];
@@ -164,7 +180,7 @@ export default class UploadPlugin extends AdminForthPlugin {
164
180
  return { ok: true };
165
181
  }));
166
182
  // in afterSave hook, aremove tag adminforth-not-yet-used from the file
167
- resourceConfig.hooks.create.afterSave.push((_h) => __awaiter(this, [_h], void 0, function* ({ record }) {
183
+ resourceConfig.hooks.create.afterSave.push((_l) => __awaiter(this, [_l], void 0, function* ({ record }) {
168
184
  process.env.HEAVY_DEBUG && console.log('πŸ’ΎπŸ’Ύ after save ', record === null || record === void 0 ? void 0 : record.id);
169
185
  if (record[pathColumnName]) {
170
186
  const s3 = new S3({
@@ -188,7 +204,7 @@ export default class UploadPlugin extends AdminForthPlugin {
188
204
  }));
189
205
  // ** HOOKS FOR SHOW **//
190
206
  // add show hook to get presigned URL
191
- resourceConfig.hooks.show.afterDatasourceResponse.push((_j) => __awaiter(this, [_j], void 0, function* ({ response }) {
207
+ resourceConfig.hooks.show.afterDatasourceResponse.push((_m) => __awaiter(this, [_m], void 0, function* ({ response }) {
192
208
  const record = response[0];
193
209
  if (!record) {
194
210
  return { ok: true };
@@ -206,8 +222,8 @@ export default class UploadPlugin extends AdminForthPlugin {
206
222
  return { ok: true };
207
223
  }));
208
224
  // ** HOOKS FOR LIST **//
209
- if (((_e = this.options.preview) === null || _e === void 0 ? void 0 : _e.showInList) || ((_f = this.options.preview) === null || _f === void 0 ? void 0 : _f.showInList) === undefined) {
210
- resourceConfig.hooks.list.afterDatasourceResponse.push((_k) => __awaiter(this, [_k], void 0, function* ({ response }) {
225
+ if (((_h = this.options.preview) === null || _h === void 0 ? void 0 : _h.showInList) || ((_j = this.options.preview) === null || _j === void 0 ? void 0 : _j.showInList) === undefined) {
226
+ resourceConfig.hooks.list.afterDatasourceResponse.push((_o) => __awaiter(this, [_o], void 0, function* ({ response }) {
211
227
  const s3 = new S3({
212
228
  credentials: {
213
229
  accessKeyId: this.options.s3AccessKeyId,
@@ -225,7 +241,7 @@ export default class UploadPlugin extends AdminForthPlugin {
225
241
  }
226
242
  // ** HOOKS FOR DELETE **//
227
243
  // add delete hook which sets tag adminforth-candidate-for-cleanup to true
228
- resourceConfig.hooks.delete.afterSave.push((_l) => __awaiter(this, [_l], void 0, function* ({ record }) {
244
+ resourceConfig.hooks.delete.afterSave.push((_p) => __awaiter(this, [_p], void 0, function* ({ record }) {
229
245
  if (record[pathColumnName]) {
230
246
  const s3 = new S3({
231
247
  credentials: {
@@ -257,7 +273,7 @@ export default class UploadPlugin extends AdminForthPlugin {
257
273
  }));
258
274
  // ** HOOKS FOR EDIT **//
259
275
  // beforeSave
260
- resourceConfig.hooks.edit.beforeSave.push((_m) => __awaiter(this, [_m], void 0, function* ({ record }) {
276
+ resourceConfig.hooks.edit.beforeSave.push((_q) => __awaiter(this, [_q], void 0, function* ({ record }) {
261
277
  // null is when value is removed
262
278
  if (record[virtualColumn.name] || record[virtualColumn.name] === null) {
263
279
  record[pathColumnName] = record[virtualColumn.name];
@@ -265,7 +281,7 @@ export default class UploadPlugin extends AdminForthPlugin {
265
281
  return { ok: true };
266
282
  }));
267
283
  // add edit postSave hook to delete old file and remove tag from new file
268
- resourceConfig.hooks.edit.afterSave.push((_o) => __awaiter(this, [_o], void 0, function* ({ record, oldRecord }) {
284
+ resourceConfig.hooks.edit.afterSave.push((_r) => __awaiter(this, [_r], void 0, function* ({ record, oldRecord }) {
269
285
  if (record[virtualColumn.name] || record[virtualColumn.name] === null) {
270
286
  const s3 = new S3({
271
287
  credentials: {
@@ -372,5 +388,71 @@ export default class UploadPlugin extends AdminForthPlugin {
372
388
  };
373
389
  })
374
390
  });
391
+ // generation: {
392
+ // provider: 'openai-dall-e',
393
+ // countToGenerate: 3,
394
+ // openAiOptions: {
395
+ // model: 'dall-e-3',
396
+ // size: '1792x1024',
397
+ // apiKey: process.env.OPENAI_API_KEY as string,
398
+ // },
399
+ // },
400
+ // curl https://api.openai.com/v1/images/generations \
401
+ // -H "Content-Type: application/json" \
402
+ // -H "Authorization: Bearer $OPENAI_API_KEY" \
403
+ // -d '{
404
+ // "model": "dall-e-3",
405
+ // "prompt": "A cute baby sea otter",
406
+ // "n": 1,
407
+ // "size": "1024x1024"
408
+ // }'
409
+ server.endpoint({
410
+ method: 'POST',
411
+ path: `/plugin/${this.pluginInstanceId}/generate_images`,
412
+ handler: (_c) => __awaiter(this, [_c], void 0, function* ({ body }) {
413
+ const { prompt } = body;
414
+ if (this.options.generation.provider !== 'openai-dall-e') {
415
+ throw new Error(`Provider ${this.options.generation.provider} is not supported`);
416
+ }
417
+ const { model, size, apiKey } = this.options.generation.openAiOptions;
418
+ const url = 'https://api.openai.com/v1/images/generations';
419
+ let error = null;
420
+ const images = yield Promise.all((new Array(this.options.generation.countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
421
+ const response = yield fetch(url, {
422
+ method: 'POST',
423
+ headers: {
424
+ 'Content-Type': 'application/json',
425
+ 'Authorization': `Bearer ${apiKey}`,
426
+ },
427
+ body: JSON.stringify({
428
+ model,
429
+ prompt,
430
+ n: 1,
431
+ size,
432
+ })
433
+ });
434
+ const json = yield response.json();
435
+ if (json.error) {
436
+ console.error('Error generating image', json.error);
437
+ error = json.error;
438
+ return;
439
+ }
440
+ return json;
441
+ })));
442
+ return { error, images };
443
+ })
444
+ });
445
+ server.endpoint({
446
+ method: 'GET',
447
+ path: `/plugin/${this.pluginInstanceId}/cors-proxy`,
448
+ handler: (_d) => __awaiter(this, [_d], void 0, function* ({ query, response }) {
449
+ const { url } = query;
450
+ const resp = yield fetch(url);
451
+ response.setHeader('Content-Type', resp.headers.get('Content-Type'));
452
+ //@ts-ignore
453
+ Readable.fromWeb(resp.body).pipe(response.blobStream());
454
+ return null;
455
+ })
456
+ });
375
457
  }
376
458
  }
package/index.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  import { PluginOptions } from './types.js';
3
3
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
4
4
  import { ExpirationStatus, GetObjectCommand, ObjectCannedACL, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
5
- import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResourcePages, IAdminForth, IHttpServer } from "adminforth";
5
+ import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResourcePages, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
6
+ import { Readable } from "stream";
6
7
 
7
8
  const ADMINFORTH_NOT_YET_USED_TAG = 'adminforth-candidate-for-cleanup';
8
9
 
@@ -91,18 +92,38 @@ export default class UploadPlugin extends AdminForthPlugin {
91
92
  }
92
93
 
93
94
  async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: any) {
94
-
95
95
  super.modifyResourceConfig(adminforth, resourceConfig);
96
96
  // after column to store the path of the uploaded file, add new VirtualColumn,
97
97
  // show only in edit and create views
98
98
  // use component uploader.vue
99
99
  const { pathColumnName } = this.options;
100
+ const pathColumnIndex = resourceConfig.columns.findIndex((column: any) => column.name === pathColumnName);
101
+ if (pathColumnIndex === -1) {
102
+ throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.name}"`);
103
+ }
104
+
105
+ if (this.options.generation?.fieldsForContext) {
106
+ this.options.generation?.fieldsForContext.forEach((field: string) => {
107
+ if (!resourceConfig.columns.find((column: any) => column.name === field)) {
108
+ const similar = suggestIfTypo(resourceConfig.columns.map((column: any) => column.name), field);
109
+ throw new Error(`Field "${field}" specified in fieldsForContext not found in
110
+ resource "${resourceConfig.label}". ${similar ? `Did you mean "${similar}"?` : ''}`);
111
+ }
112
+ });
113
+ }
100
114
 
101
115
  const pluginFrontendOptions = {
102
116
  allowedExtensions: this.options.allowedFileExtensions,
103
117
  maxFileSize: this.options.maxFileSize,
104
118
  pluginInstanceId: this.pluginInstanceId,
119
+ resourceLabel: resourceConfig.label,
120
+ generateImages: this.options.generation ? true : false,
121
+ pathColumnLabel: resourceConfig.columns[pathColumnIndex].label,
122
+ fieldsForContext: this.options.generation?.fieldsForContext,
105
123
  };
124
+ // define components which will be imported from other components
125
+ this.componentPath('imageGenerator.vue');
126
+
106
127
  const virtualColumn: AdminForthResourceColumn = {
107
128
  virtual: true,
108
129
  name: `uploader_${this.pluginInstanceId}`,
@@ -121,10 +142,7 @@ export default class UploadPlugin extends AdminForthPlugin {
121
142
 
122
143
 
123
144
 
124
- const pathColumnIndex = resourceConfig.columns.findIndex((column: any) => column.name === pathColumnName);
125
- if (pathColumnIndex === -1) {
126
- throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.name}"`);
127
- }
145
+
128
146
  if (!resourceConfig.columns[pathColumnIndex].components) {
129
147
  resourceConfig.columns[pathColumnIndex].components = {};
130
148
  }
@@ -409,6 +427,86 @@ export default class UploadPlugin extends AdminForthPlugin {
409
427
  };
410
428
  }
411
429
  });
430
+
431
+ // generation: {
432
+ // provider: 'openai-dall-e',
433
+ // countToGenerate: 3,
434
+ // openAiOptions: {
435
+ // model: 'dall-e-3',
436
+ // size: '1792x1024',
437
+ // apiKey: process.env.OPENAI_API_KEY as string,
438
+ // },
439
+ // },
440
+
441
+ // curl https://api.openai.com/v1/images/generations \
442
+ // -H "Content-Type: application/json" \
443
+ // -H "Authorization: Bearer $OPENAI_API_KEY" \
444
+ // -d '{
445
+ // "model": "dall-e-3",
446
+ // "prompt": "A cute baby sea otter",
447
+ // "n": 1,
448
+ // "size": "1024x1024"
449
+ // }'
450
+
451
+ server.endpoint({
452
+ method: 'POST',
453
+ path: `/plugin/${this.pluginInstanceId}/generate_images`,
454
+ handler: async ({ body }) => {
455
+ const { prompt } = body;
456
+
457
+ if (this.options.generation.provider !== 'openai-dall-e') {
458
+ throw new Error(`Provider ${this.options.generation.provider} is not supported`);
459
+ }
460
+
461
+ const { model, size, apiKey } = this.options.generation.openAiOptions;
462
+ const url = 'https://api.openai.com/v1/images/generations';
463
+
464
+ let error = null;
465
+ const images = await Promise.all(
466
+ (new Array(this.options.generation.countToGenerate)).fill(0).map(async () => {
467
+ const response = await fetch(url, {
468
+ method: 'POST',
469
+ headers: {
470
+ 'Content-Type': 'application/json',
471
+ 'Authorization': `Bearer ${apiKey}`,
472
+ },
473
+ body: JSON.stringify({
474
+ model,
475
+ prompt,
476
+ n: 1,
477
+ size,
478
+ })
479
+ });
480
+
481
+ const json = await response.json();
482
+ if (json.error) {
483
+ console.error('Error generating image', json.error);
484
+ error = json.error;
485
+ return;
486
+ }
487
+
488
+ return json;
489
+
490
+ })
491
+ );
492
+
493
+ return { error, images };
494
+ }
495
+ });
496
+
497
+ server.endpoint({
498
+ method: 'GET',
499
+ path: `/plugin/${this.pluginInstanceId}/cors-proxy`,
500
+ handler: async ({ query, response }) => {
501
+ const { url } = query;
502
+ const resp = await fetch(url);
503
+ response.setHeader('Content-Type', resp.headers.get('Content-Type'));
504
+ //@ts-ignore
505
+ Readable.fromWeb( resp.body ).pipe( response.blobStream() );
506
+ return null
507
+ }
508
+ });
509
+
412
510
  }
413
511
 
414
512
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/upload",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/types.ts CHANGED
@@ -85,4 +85,49 @@ export type PluginOptions = {
85
85
  */
86
86
  previewUrl?: ({s3Path}) => string,
87
87
  }
88
+
89
+
90
+ /**
91
+ * AI image generation options
92
+ */
93
+ generation?: {
94
+ /**
95
+ * The provider to use for image generation
96
+ * for now only 'openai-dall-e' is supported
97
+ */
98
+ provider: string,
99
+
100
+ /**
101
+ * The number of images to generate
102
+ * in one request
103
+ */
104
+ countToGenerate: number,
105
+
106
+ /**
107
+ * Options for OpenAI
108
+ */
109
+ openAiOptions: {
110
+ /**
111
+ * The model to use, e.g. 'dall-e-3'
112
+ */
113
+ model: string,
114
+
115
+ /**
116
+ * The size of the image to generate, e.g. '1792x1024'
117
+ */
118
+ size: string,
119
+
120
+ /**
121
+ * The OpenAI API key
122
+ */
123
+ apiKey: string,
124
+ },
125
+
126
+ /**
127
+ * Fields of record to use for context. if supplied must be array of valid column names for resource
128
+ * where plugin is used.
129
+ */
130
+ fieldsForContext? : string[],
131
+ }
132
+
88
133
  }