@adminforth/upload 2.12.0 → 2.13.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 CHANGED
@@ -11,5 +11,5 @@ custom/preview.vue
11
11
  custom/tsconfig.json
12
12
  custom/uploader.vue
13
13
 
14
- sent 54,028 bytes received 134 bytes 108,324.00 bytes/sec
15
- total size is 53,539 speedup is 0.99
14
+ sent 59,817 bytes received 134 bytes 119,902.00 bytes/sec
15
+ total size is 59,335 speedup is 0.99
@@ -34,14 +34,43 @@
34
34
 
35
35
  <!-- Thumbnails -->
36
36
  <div class="mt-2 flex flex-wrap gap-2">
37
- <img
38
- v-for="(img, idx) in attachmentFiles"
39
- :key="idx"
40
- :src="img"
41
- class="w-20 h-20 object-cover rounded cursor-pointer border hover:border-blue-500 transition"
42
- :alt="`Generated image ${idx + 1}`"
43
- @click="zoomImage(img)"
37
+ <div class="group relative" v-for="(img, key) in requestAttachmentFilesUrls">
38
+ <img
39
+ :key="key"
40
+ :src="img"
41
+ class="w-20 h-20 object-cover rounded cursor-pointer border hover:border-blue-500 transition"
42
+ :alt="`Generated image ${key + 1}`"
43
+ @click="zoomImage(img)"
44
+ />
45
+ <div
46
+ class="opacity-0 group-hover:opacity-100 flex items-center justify-center w-5 h-5 bg-black absolute -top-2 -end-2 rounded-full border-2 border-white cursor-pointer hover:border-gray-300 hover:scale-110"
47
+ @click="removeFileFromList(key)"
48
+ >
49
+ <Tooltip class="absolute top-0 end-0">
50
+ <div>
51
+ <div class="w-4 h-4 absolute"></div>
52
+ <IconCloseOutline class="w-3 h-3 text-white hover:text-gray-300" />
53
+ </div>
54
+ <template #tooltip>
55
+ Remove file
56
+ </template>
57
+ </Tooltip>
58
+ </div>
59
+ </div>
60
+ <input
61
+ ref="fileInput"
62
+ class="hidden"
63
+ type="file"
64
+ @change="handleAddFile"
65
+ accept="image/*"
44
66
  />
67
+ <button v-if="!uploading" @click="fileInput?.click()" type="button" class="relative group hover:border-gray-500 transition border-gray-300 flex items-center justify-center w-20 h-20 border-2 border-dashed rounded-md">
68
+ <div class="flex flex-col items-center justify-center gap-2 mt-4 mb-4">
69
+ <IconCloseOutline class="group-hover:text-gray-500 transition rotate-45 w-6 h-6 text-gray-300 hover:text-gray-300" />
70
+ <p class="text-gray-300 group-hover:text-gray-500 transition bottom-0">Ctrl + v</p>
71
+ </div>
72
+ </button>
73
+ <Skeleton v-else type="image" class="w-20 h-20" />
45
74
  </div>
46
75
 
47
76
  <!-- Fullscreen Modal -->
@@ -175,38 +204,39 @@
175
204
 
176
205
  <script setup lang="ts">
177
206
 
178
- import { ref, onMounted, nextTick, Ref, h, computed, watch, reactive } from 'vue'
207
+ import { ref, onMounted, nextTick, Ref, watch, onUnmounted } from 'vue'
179
208
  import { Carousel } from 'flowbite';
180
209
  import { callAdminForthApi } from '@/utils';
181
210
  import { useI18n } from 'vue-i18n';
182
211
  import adminforth from '@/adminforth';
183
212
  import { ProgressBar } from '@/afcl';
184
213
  import * as Handlebars from 'handlebars';
214
+ import { IconCloseOutline } from '@iconify-prerendered/vue-flowbite';
215
+ import { Tooltip, Skeleton } from '@/afcl'
216
+ import { useRoute } from 'vue-router';
217
+
218
+ const { t: $t, t } = useI18n();
219
+ const route = useRoute();
185
220
 
186
- const { t: $t } = useI18n();
187
221
 
188
222
  const prompt = ref('');
189
223
  const emit = defineEmits(['close', 'uploadImage']);
190
- const props = defineProps(['meta', 'record']);
224
+ const props = defineProps({
225
+ meta: Object,
226
+ record: Object,
227
+ humanifySize: Function,
228
+ });
191
229
  const images = ref([]);
192
230
  const loading = ref(false);
193
231
  const attachmentFiles = ref<string[]>([])
232
+ const requestAttachmentFiles = ref<Blob[] | null>([]);
233
+ const requestAttachmentFilesUrls = ref<string[]>([]);
194
234
  const stopGeneration = ref(false);
195
-
196
- function minifyField(field: string): string {
197
- if (field.length > 100) {
198
- return field.slice(0, 100) + '...';
199
- }
200
- return field;
201
- }
235
+ const fileInput = ref<HTMLInputElement | null>(null);
202
236
 
203
237
  const caurosel = ref(null);
204
238
  onMounted(async () => {
205
239
  // Initialize carousel
206
- const context = {
207
- field: props.meta.pathColumnLabel,
208
- resource: props.meta.resourceLabel,
209
- };
210
240
  let template = '';
211
241
  if (props.meta.generationPrompt) {
212
242
  template = props.meta.generationPrompt;
@@ -230,13 +260,20 @@ onMounted(async () => {
230
260
  });
231
261
 
232
262
  if (resp?.files?.length) {
233
- attachmentFiles.value = resp.files;
263
+ requestAttachmentFilesUrls.value = resp.files;
234
264
  }
235
265
  } catch (err) {
236
266
  console.error('Failed to fetch attachment files', err);
237
267
  }
268
+
269
+ for ( const fileUrl in requestAttachmentFilesUrls.value ) {
270
+ const image = await fetch(fileUrl);
271
+ const imageBlob = await image.blob();
272
+ requestAttachmentFiles.value!.push(imageBlob);
273
+ }
238
274
  });
239
275
 
276
+
240
277
  async function slide(direction: number) {
241
278
  if (!caurosel.value) return;
242
279
  const curPos = caurosel.value.getActiveItem().position;
@@ -323,11 +360,13 @@ async function generateImages() {
323
360
  method: 'POST',
324
361
  body: {
325
362
  prompt: prompt.value,
326
- recordId: props.record[props.meta.recorPkFieldName]
363
+ recordId: props.record[props.meta.recorPkFieldName],
364
+ requestAttachmentFiles: requestAttachmentFilesUrls.value,
327
365
  },
328
366
  });
329
367
  } catch (e) {
330
368
  console.error(e);
369
+ return;
331
370
  }
332
371
 
333
372
  if (resp?.error) {
@@ -451,4 +490,129 @@ watch(zoomedImage, async (val) => {
451
490
  }).show()
452
491
  }
453
492
  })
493
+
494
+ async function handleAddFile(event, clipboardFile = null) {
495
+ if (clipboardFile) {
496
+ clipboardFile = renameFile(clipboardFile, `pasted_image_${Date.now()}.png`);
497
+ }
498
+ const files = event?.target?.files || (clipboardFile ? [clipboardFile] : []);
499
+ for (let i = 0; i < files.length; i++) {
500
+ if (requestAttachmentFiles.value.find((f: any) => f.name === files[i].name)) {
501
+ adminforth.alert({
502
+ message: $t('File with the same name already added'),
503
+ variant: 'warning',
504
+ timeout: 5000,
505
+ });
506
+ continue;
507
+ }
508
+ const file = files[i];
509
+ const fileUrl = await uploadFile(file);
510
+ if (!fileUrl) continue;
511
+ requestAttachmentFiles.value!.push(file);
512
+ requestAttachmentFilesUrls.value.push(fileUrl);
513
+ }
514
+ fileInput.value!.value = '';
515
+ }
516
+
517
+ function removeFileFromList(index: number) {
518
+ requestAttachmentFiles.value!.splice(index, 1);
519
+ requestAttachmentFilesUrls.value.splice(index, 1);
520
+ }
521
+
522
+ const uploading = ref(false);
523
+
524
+ async function uploadFile(file: any): Promise<string> {
525
+ const { name, size, type } = file;
526
+
527
+ let imgPreview = '';
528
+
529
+ const extension = name.split('.').pop();
530
+ const nameNoExtension = name.replace(`.${extension}`, '');
531
+
532
+ if (props.meta.maxFileSize && size > props.meta.maxFileSize) {
533
+ adminforth.alert({
534
+ message: t('Sorry but the file size {size} is too large. Please upload a file with a maximum size of {maxFileSize}', {
535
+ size: props.humanifySize(size),
536
+ maxFileSize: props.humanifySize(props.meta.maxFileSize),
537
+ }),
538
+ variant: 'danger'
539
+ });
540
+ return;
541
+ }
542
+
543
+ try {
544
+ uploading.value = true;
545
+ const { uploadUrl, uploadExtraParams, filePath, error, previewUrl } = await callAdminForthApi({
546
+ path: `/plugin/${props.meta.pluginInstanceId}/get_file_upload_url`,
547
+ method: 'POST',
548
+ body: {
549
+ originalFilename: nameNoExtension,
550
+ contentType: type,
551
+ size,
552
+ originalExtension: extension,
553
+ recordPk: route?.params?.primaryKey,
554
+ },
555
+ });
556
+ if (error) {
557
+ adminforth.alert({
558
+ message: t('File was not uploaded because of error: {error}', { error }),
559
+ variant: 'danger'
560
+ });
561
+ imgPreview = null;
562
+ return;
563
+ }
564
+
565
+ const xhr = new XMLHttpRequest();
566
+ const success = await new Promise((resolve) => {
567
+ xhr.addEventListener('loadend', () => {
568
+ const success = xhr.readyState === 4 && xhr.status === 200;
569
+ // try to read response
570
+ resolve(success);
571
+ });
572
+ xhr.open('PUT', uploadUrl, true);
573
+ xhr.setRequestHeader('Content-Type', type);
574
+ uploadExtraParams && Object.entries(uploadExtraParams).forEach(([key, value]: [string, string]) => {
575
+ xhr.setRequestHeader(key, value);
576
+ })
577
+ xhr.send(file);
578
+ });
579
+
580
+ if (success) {
581
+ imgPreview = previewUrl;
582
+ } else {
583
+ throw new Error('File upload failed');
584
+ }
585
+ } catch (err) {
586
+ uploading.value = false;
587
+ console.error('File upload failed', err);
588
+ }
589
+ uploading.value = false;
590
+ return imgPreview;
591
+ }
592
+
593
+ async function uploadImageOnPaste(event) {
594
+ const items = event.clipboardData?.items;
595
+ if (!items) return;
596
+
597
+ for (let item of items) {
598
+ if (item.type.startsWith('image/')) {
599
+ const file = item.getAsFile();
600
+ if (file) {
601
+ await handleAddFile(null, file);
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ function renameFile(file, newName) {
608
+ return new File([file], newName, { type: file.type });
609
+ }
610
+
611
+ onMounted(() => {
612
+ document.addEventListener('paste', uploadImageOnPaste);
613
+ });
614
+
615
+ onUnmounted(() => {
616
+ document.removeEventListener('paste', uploadImageOnPaste);
617
+ });
454
618
  </script>
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <div class="relative w-full">
3
3
  <ImageGenerator v-if="showImageGen" @close="showImageGen = false" :record="record" :meta="meta"
4
- @uploadImage="uploadGeneratedImage"
4
+ @uploadImage="uploadGeneratedImage"
5
+ :humanifySize="humanifySize"
5
6
  ></ImageGenerator>
6
7
 
7
8
  <button v-if="meta.generateImages"
@@ -34,14 +34,43 @@
34
34
 
35
35
  <!-- Thumbnails -->
36
36
  <div class="mt-2 flex flex-wrap gap-2">
37
- <img
38
- v-for="(img, idx) in attachmentFiles"
39
- :key="idx"
40
- :src="img"
41
- class="w-20 h-20 object-cover rounded cursor-pointer border hover:border-blue-500 transition"
42
- :alt="`Generated image ${idx + 1}`"
43
- @click="zoomImage(img)"
37
+ <div class="group relative" v-for="(img, key) in requestAttachmentFilesUrls">
38
+ <img
39
+ :key="key"
40
+ :src="img"
41
+ class="w-20 h-20 object-cover rounded cursor-pointer border hover:border-blue-500 transition"
42
+ :alt="`Generated image ${key + 1}`"
43
+ @click="zoomImage(img)"
44
+ />
45
+ <div
46
+ class="opacity-0 group-hover:opacity-100 flex items-center justify-center w-5 h-5 bg-black absolute -top-2 -end-2 rounded-full border-2 border-white cursor-pointer hover:border-gray-300 hover:scale-110"
47
+ @click="removeFileFromList(key)"
48
+ >
49
+ <Tooltip class="absolute top-0 end-0">
50
+ <div>
51
+ <div class="w-4 h-4 absolute"></div>
52
+ <IconCloseOutline class="w-3 h-3 text-white hover:text-gray-300" />
53
+ </div>
54
+ <template #tooltip>
55
+ Remove file
56
+ </template>
57
+ </Tooltip>
58
+ </div>
59
+ </div>
60
+ <input
61
+ ref="fileInput"
62
+ class="hidden"
63
+ type="file"
64
+ @change="handleAddFile"
65
+ accept="image/*"
44
66
  />
67
+ <button v-if="!uploading" @click="fileInput?.click()" type="button" class="relative group hover:border-gray-500 transition border-gray-300 flex items-center justify-center w-20 h-20 border-2 border-dashed rounded-md">
68
+ <div class="flex flex-col items-center justify-center gap-2 mt-4 mb-4">
69
+ <IconCloseOutline class="group-hover:text-gray-500 transition rotate-45 w-6 h-6 text-gray-300 hover:text-gray-300" />
70
+ <p class="text-gray-300 group-hover:text-gray-500 transition bottom-0">Ctrl + v</p>
71
+ </div>
72
+ </button>
73
+ <Skeleton v-else type="image" class="w-20 h-20" />
45
74
  </div>
46
75
 
47
76
  <!-- Fullscreen Modal -->
@@ -175,38 +204,39 @@
175
204
 
176
205
  <script setup lang="ts">
177
206
 
178
- import { ref, onMounted, nextTick, Ref, h, computed, watch, reactive } from 'vue'
207
+ import { ref, onMounted, nextTick, Ref, watch, onUnmounted } from 'vue'
179
208
  import { Carousel } from 'flowbite';
180
209
  import { callAdminForthApi } from '@/utils';
181
210
  import { useI18n } from 'vue-i18n';
182
211
  import adminforth from '@/adminforth';
183
212
  import { ProgressBar } from '@/afcl';
184
213
  import * as Handlebars from 'handlebars';
214
+ import { IconCloseOutline } from '@iconify-prerendered/vue-flowbite';
215
+ import { Tooltip, Skeleton } from '@/afcl'
216
+ import { useRoute } from 'vue-router';
217
+
218
+ const { t: $t, t } = useI18n();
219
+ const route = useRoute();
185
220
 
186
- const { t: $t } = useI18n();
187
221
 
188
222
  const prompt = ref('');
189
223
  const emit = defineEmits(['close', 'uploadImage']);
190
- const props = defineProps(['meta', 'record']);
224
+ const props = defineProps({
225
+ meta: Object,
226
+ record: Object,
227
+ humanifySize: Function,
228
+ });
191
229
  const images = ref([]);
192
230
  const loading = ref(false);
193
231
  const attachmentFiles = ref<string[]>([])
232
+ const requestAttachmentFiles = ref<Blob[] | null>([]);
233
+ const requestAttachmentFilesUrls = ref<string[]>([]);
194
234
  const stopGeneration = ref(false);
195
-
196
- function minifyField(field: string): string {
197
- if (field.length > 100) {
198
- return field.slice(0, 100) + '...';
199
- }
200
- return field;
201
- }
235
+ const fileInput = ref<HTMLInputElement | null>(null);
202
236
 
203
237
  const caurosel = ref(null);
204
238
  onMounted(async () => {
205
239
  // Initialize carousel
206
- const context = {
207
- field: props.meta.pathColumnLabel,
208
- resource: props.meta.resourceLabel,
209
- };
210
240
  let template = '';
211
241
  if (props.meta.generationPrompt) {
212
242
  template = props.meta.generationPrompt;
@@ -230,13 +260,20 @@ onMounted(async () => {
230
260
  });
231
261
 
232
262
  if (resp?.files?.length) {
233
- attachmentFiles.value = resp.files;
263
+ requestAttachmentFilesUrls.value = resp.files;
234
264
  }
235
265
  } catch (err) {
236
266
  console.error('Failed to fetch attachment files', err);
237
267
  }
268
+
269
+ for ( const fileUrl in requestAttachmentFilesUrls.value ) {
270
+ const image = await fetch(fileUrl);
271
+ const imageBlob = await image.blob();
272
+ requestAttachmentFiles.value!.push(imageBlob);
273
+ }
238
274
  });
239
275
 
276
+
240
277
  async function slide(direction: number) {
241
278
  if (!caurosel.value) return;
242
279
  const curPos = caurosel.value.getActiveItem().position;
@@ -323,11 +360,13 @@ async function generateImages() {
323
360
  method: 'POST',
324
361
  body: {
325
362
  prompt: prompt.value,
326
- recordId: props.record[props.meta.recorPkFieldName]
363
+ recordId: props.record[props.meta.recorPkFieldName],
364
+ requestAttachmentFiles: requestAttachmentFilesUrls.value,
327
365
  },
328
366
  });
329
367
  } catch (e) {
330
368
  console.error(e);
369
+ return;
331
370
  }
332
371
 
333
372
  if (resp?.error) {
@@ -451,4 +490,129 @@ watch(zoomedImage, async (val) => {
451
490
  }).show()
452
491
  }
453
492
  })
493
+
494
+ async function handleAddFile(event, clipboardFile = null) {
495
+ if (clipboardFile) {
496
+ clipboardFile = renameFile(clipboardFile, `pasted_image_${Date.now()}.png`);
497
+ }
498
+ const files = event?.target?.files || (clipboardFile ? [clipboardFile] : []);
499
+ for (let i = 0; i < files.length; i++) {
500
+ if (requestAttachmentFiles.value.find((f: any) => f.name === files[i].name)) {
501
+ adminforth.alert({
502
+ message: $t('File with the same name already added'),
503
+ variant: 'warning',
504
+ timeout: 5000,
505
+ });
506
+ continue;
507
+ }
508
+ const file = files[i];
509
+ const fileUrl = await uploadFile(file);
510
+ if (!fileUrl) continue;
511
+ requestAttachmentFiles.value!.push(file);
512
+ requestAttachmentFilesUrls.value.push(fileUrl);
513
+ }
514
+ fileInput.value!.value = '';
515
+ }
516
+
517
+ function removeFileFromList(index: number) {
518
+ requestAttachmentFiles.value!.splice(index, 1);
519
+ requestAttachmentFilesUrls.value.splice(index, 1);
520
+ }
521
+
522
+ const uploading = ref(false);
523
+
524
+ async function uploadFile(file: any): Promise<string> {
525
+ const { name, size, type } = file;
526
+
527
+ let imgPreview = '';
528
+
529
+ const extension = name.split('.').pop();
530
+ const nameNoExtension = name.replace(`.${extension}`, '');
531
+
532
+ if (props.meta.maxFileSize && size > props.meta.maxFileSize) {
533
+ adminforth.alert({
534
+ message: t('Sorry but the file size {size} is too large. Please upload a file with a maximum size of {maxFileSize}', {
535
+ size: props.humanifySize(size),
536
+ maxFileSize: props.humanifySize(props.meta.maxFileSize),
537
+ }),
538
+ variant: 'danger'
539
+ });
540
+ return;
541
+ }
542
+
543
+ try {
544
+ uploading.value = true;
545
+ const { uploadUrl, uploadExtraParams, filePath, error, previewUrl } = await callAdminForthApi({
546
+ path: `/plugin/${props.meta.pluginInstanceId}/get_file_upload_url`,
547
+ method: 'POST',
548
+ body: {
549
+ originalFilename: nameNoExtension,
550
+ contentType: type,
551
+ size,
552
+ originalExtension: extension,
553
+ recordPk: route?.params?.primaryKey,
554
+ },
555
+ });
556
+ if (error) {
557
+ adminforth.alert({
558
+ message: t('File was not uploaded because of error: {error}', { error }),
559
+ variant: 'danger'
560
+ });
561
+ imgPreview = null;
562
+ return;
563
+ }
564
+
565
+ const xhr = new XMLHttpRequest();
566
+ const success = await new Promise((resolve) => {
567
+ xhr.addEventListener('loadend', () => {
568
+ const success = xhr.readyState === 4 && xhr.status === 200;
569
+ // try to read response
570
+ resolve(success);
571
+ });
572
+ xhr.open('PUT', uploadUrl, true);
573
+ xhr.setRequestHeader('Content-Type', type);
574
+ uploadExtraParams && Object.entries(uploadExtraParams).forEach(([key, value]: [string, string]) => {
575
+ xhr.setRequestHeader(key, value);
576
+ })
577
+ xhr.send(file);
578
+ });
579
+
580
+ if (success) {
581
+ imgPreview = previewUrl;
582
+ } else {
583
+ throw new Error('File upload failed');
584
+ }
585
+ } catch (err) {
586
+ uploading.value = false;
587
+ console.error('File upload failed', err);
588
+ }
589
+ uploading.value = false;
590
+ return imgPreview;
591
+ }
592
+
593
+ async function uploadImageOnPaste(event) {
594
+ const items = event.clipboardData?.items;
595
+ if (!items) return;
596
+
597
+ for (let item of items) {
598
+ if (item.type.startsWith('image/')) {
599
+ const file = item.getAsFile();
600
+ if (file) {
601
+ await handleAddFile(null, file);
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ function renameFile(file, newName) {
608
+ return new File([file], newName, { type: file.type });
609
+ }
610
+
611
+ onMounted(() => {
612
+ document.addEventListener('paste', uploadImageOnPaste);
613
+ });
614
+
615
+ onUnmounted(() => {
616
+ document.removeEventListener('paste', uploadImageOnPaste);
617
+ });
454
618
  </script>
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <div class="relative w-full">
3
3
  <ImageGenerator v-if="showImageGen" @close="showImageGen = false" :record="record" :meta="meta"
4
- @uploadImage="uploadGeneratedImage"
4
+ @uploadImage="uploadGeneratedImage"
5
+ :humanifySize="humanifySize"
5
6
  ></ImageGenerator>
6
7
 
7
8
  <button v-if="meta.generateImages"
package/dist/index.js CHANGED
@@ -90,35 +90,16 @@ export default class UploadPlugin extends AdminForthPlugin {
90
90
  markKeyForDeletion(filePath) {
91
91
  return this.callStorageAdapter('markKeyForDeletion', 'markKeyForDeletation', filePath);
92
92
  }
93
- generateImages(jobId, prompt, recordId, adminUser, headers) {
93
+ generateImages(jobId, prompt, requestAttachmentFiles, recordId, adminUser, headers) {
94
94
  return __awaiter(this, void 0, void 0, function* () {
95
- var _a, _b;
95
+ var _a;
96
96
  if ((_a = this.options.generation.rateLimit) === null || _a === void 0 ? void 0 : _a.limit) {
97
- // rate limit
98
- // const { error } = RateLimiter.checkRateLimit(
99
- // this.pluginInstanceId,
100
- // this.options.generation.rateLimit?.limit,
101
- // this.adminforth.auth.getClientIp(headers),
102
- // );
103
97
  if (!(yield this.rateLimiter.consume(`${this.pluginInstanceId}-${this.adminforth.auth.getClientIp(headers)}`))) {
104
98
  jobs.set(jobId, { status: "failed", error: this.options.generation.rateLimit.errorMessage });
105
99
  return { error: this.options.generation.rateLimit.errorMessage };
106
100
  }
107
101
  }
108
- let attachmentFiles = [];
109
- if (this.options.generation.attachFiles) {
110
- // TODO - does it require additional allowed action to check this record id has access to get the image?
111
- // or should we mention in docs that user should do validation in method itself
112
- 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, recordId)]);
113
- if (!record) {
114
- return { error: `Record with id ${recordId} not found` };
115
- }
116
- attachmentFiles = yield this.options.generation.attachFiles({ record, adminUser });
117
- // if files is not array, make it array
118
- if (!Array.isArray(attachmentFiles)) {
119
- attachmentFiles = [attachmentFiles];
120
- }
121
- }
102
+ let attachmentFiles = requestAttachmentFiles;
122
103
  let error = undefined;
123
104
  const STUB_MODE = false;
124
105
  const images = yield Promise.all((new Array(this.options.generation.countToGenerate)).fill(0).map(() => __awaiter(this, void 0, void 0, function* () {
@@ -390,10 +371,10 @@ export default class UploadPlugin extends AdminForthPlugin {
390
371
  method: 'POST',
391
372
  path: `/plugin/${this.pluginInstanceId}/create-image-generation-job`,
392
373
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
393
- const { prompt, recordId } = body;
374
+ const { prompt, recordId, requestAttachmentFiles } = body;
394
375
  const jobId = randomUUID();
395
376
  jobs.set(jobId, { status: "in_progress" });
396
- this.generateImages(jobId, prompt, recordId, adminUser, headers);
377
+ this.generateImages(jobId, prompt, requestAttachmentFiles, recordId, adminUser, headers);
397
378
  setTimeout(() => jobs.delete(jobId), 1800000);
398
379
  setTimeout(() => { jobs.set(jobId, { status: "timeout" }); }, 300000);
399
380
  return { ok: true, jobId };
package/index.ts CHANGED
@@ -104,39 +104,14 @@ export default class UploadPlugin extends AdminForthPlugin {
104
104
  return this.callStorageAdapter('markKeyForDeletion', 'markKeyForDeletation', filePath);
105
105
  }
106
106
 
107
- private async generateImages(jobId: string, prompt: string, recordId: any, adminUser: any, headers: any) {
107
+ private async generateImages(jobId: string, prompt: string,requestAttachmentFiles: string[], recordId: any, adminUser: any, headers: any) {
108
108
  if (this.options.generation.rateLimit?.limit) {
109
- // rate limit
110
- // const { error } = RateLimiter.checkRateLimit(
111
- // this.pluginInstanceId,
112
- // this.options.generation.rateLimit?.limit,
113
- // this.adminforth.auth.getClientIp(headers),
114
- // );
115
109
  if (!await this.rateLimiter.consume(`${this.pluginInstanceId}-${this.adminforth.auth.getClientIp(headers)}`)) {
116
110
  jobs.set(jobId, { status: "failed", error: this.options.generation.rateLimit.errorMessage });
117
111
  return { error: this.options.generation.rateLimit.errorMessage };
118
112
  }
119
113
  }
120
- let attachmentFiles = [];
121
- if (this.options.generation.attachFiles) {
122
- // TODO - does it require additional allowed action to check this record id has access to get the image?
123
- // or should we mention in docs that user should do validation in method itself
124
- const record = await this.adminforth.resource(this.resourceConfig.resourceId).get(
125
- [Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, recordId)]
126
- );
127
-
128
-
129
- if (!record) {
130
- return { error: `Record with id ${recordId} not found` };
131
- }
132
-
133
- attachmentFiles = await this.options.generation.attachFiles({ record, adminUser });
134
- // if files is not array, make it array
135
- if (!Array.isArray(attachmentFiles)) {
136
- attachmentFiles = [attachmentFiles];
137
- }
138
-
139
- }
114
+ let attachmentFiles = requestAttachmentFiles;
140
115
 
141
116
  let error: string | undefined = undefined;
142
117
 
@@ -448,12 +423,11 @@ export default class UploadPlugin extends AdminForthPlugin {
448
423
  method: 'POST',
449
424
  path: `/plugin/${this.pluginInstanceId}/create-image-generation-job`,
450
425
  handler: async ({ body, adminUser, headers }) => {
451
- const { prompt, recordId } = body;
452
-
426
+ const { prompt, recordId, requestAttachmentFiles } = body;
453
427
  const jobId = randomUUID();
454
428
  jobs.set(jobId, { status: "in_progress" });
455
429
 
456
- this.generateImages(jobId, prompt, recordId, adminUser, headers);
430
+ this.generateImages(jobId, prompt, requestAttachmentFiles, recordId, adminUser, headers);
457
431
  setTimeout(() => jobs.delete(jobId), 1_800_000);
458
432
  setTimeout(() => {jobs.set(jobId, { status: "timeout" });}, 300_000);
459
433
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/upload",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "Plugin for uploading files for adminforth",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",