WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 1a1b2e4

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth-upload
2 parents 25e6613 + 159e964 commit 1a1b2e4

File tree

2 files changed

+149
-80
lines changed

2 files changed

+149
-80
lines changed

custom/imageGenerator.vue

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ onMounted(async () => {
248248
249249
if (resp?.files?.length) {
250250
attachmentFiles.value = resp.files;
251-
console.log('attachmentFiles', attachmentFiles.value);
252251
}
253252
} catch (err) {
254253
console.error('Failed to fetch attachment files', err);
@@ -337,7 +336,7 @@ async function generateImages() {
337336
let error = null;
338337
try {
339338
resp = await callAdminForthApi({
340-
path: `/plugin/${props.meta.pluginInstanceId}/generate_images`,
339+
path: `/plugin/${props.meta.pluginInstanceId}/create-image-generation-job`,
341340
method: 'POST',
342341
body: {
343342
prompt: prompt.value,
@@ -346,16 +345,13 @@ async function generateImages() {
346345
});
347346
} catch (e) {
348347
console.error(e);
349-
} finally {
350-
clearInterval(ticker);
351-
loadingTimer.value = null;
352-
loading.value = false;
353348
}
349+
354350
if (resp?.error) {
355351
error = resp.error;
356352
}
357353
if (!resp) {
358-
error = $t('Error generating images, something went wrong');
354+
error = $t('Error creating image generation job');
359355
}
360356
361357
if (error) {
@@ -371,11 +367,53 @@ async function generateImages() {
371367
return;
372368
}
373369
370+
const jobId = resp.jobId;
371+
let jobStatus = null;
372+
let jobResponse = null;
373+
do {
374+
jobResponse = await callAdminForthApi({
375+
path: `/plugin/${props.meta.pluginInstanceId}/get-image-generation-job-status`,
376+
method: 'POST',
377+
body: { jobId },
378+
});
379+
if (jobResponse?.error) {
380+
error = jobResponse.error;
381+
break;
382+
};
383+
jobStatus = jobResponse?.job?.status;
384+
if (jobStatus === 'failed') {
385+
error = jobResponse?.job?.error || $t('Image generation job failed');
386+
}
387+
if (jobStatus === 'timeout') {
388+
error = jobResponse?.job?.error || $t('Image generation job timeout');
389+
}
390+
await new Promise((resolve) => setTimeout(resolve, 2000));
391+
} while (jobStatus === 'in_progress')
392+
393+
if (error) {
394+
adminforth.alert({
395+
message: error,
396+
variant: 'danger',
397+
timeout: 'unlimited',
398+
});
399+
clearInterval(ticker);
400+
loadingTimer.value = null;
401+
loading.value = false;
402+
return;
403+
}
404+
405+
const respImages = jobResponse?.job?.images || [];
406+
374407
images.value = [
375408
...images.value,
376-
...resp.images,
409+
...respImages,
377410
];
378411
412+
clearInterval(ticker);
413+
loadingTimer.value = null;
414+
loading.value = false;
415+
416+
379417
// images.value = [
380418
// 'https://via.placeholder.com/600x400?text=Image+1',
381419
// 'https://via.placeholder.com/600x400?text=Image+2',
@@ -386,7 +424,6 @@ async function generateImages() {
386424
caurosel.value = new Carousel(
387425
document.getElementById('gallery'),
388426
images.value.map((img, index) => {
389-
console.log('mapping image', img, index);
390427
return {
391428
image: img,
392429
el: document.getElementById('gallery').querySelector(`[data-carousel-item]:nth-child(${index + 1})`),

index.ts

Lines changed: 103 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { PluginOptions } from './types.js';
33
import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResource, Filters, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
44
import { Readable } from "stream";
55
import { RateLimiter } from "adminforth";
6+
import { randomUUID } from "crypto";
67
import { interpretResource } from 'adminforth';
78
import { ActionCheckSource } from 'adminforth';
89

910
const ADMINFORTH_NOT_YET_USED_TAG = 'adminforth-candidate-for-cleanup';
10-
11+
const jobs = new Map();
1112
export default class UploadPlugin extends AdminForthPlugin {
1213
options: PluginOptions;
1314

@@ -36,10 +37,87 @@ export default class UploadPlugin extends AdminForthPlugin {
3637
return this.options.storageAdapter.getDownloadUrl(path, expiresInSeconds);
3738
}
3839
if (this.options.generation?.rateLimit?.limit) {
39-
this.rateLimiter = new RateLimiter(this.options.generation.rateLimit?.limit)
40+
this.rateLimiter = new RateLimiter(this.options.generation.rateLimit?.limit)
4041
}
4142
}
4243

44+
private async generateImages(jobId: string, prompt: string, recordId: any, adminUser: any, headers: any) {
45+
if (this.options.generation.rateLimit?.limit) {
46+
// rate limit
47+
// const { error } = RateLimiter.checkRateLimit(
48+
// this.pluginInstanceId,
49+
// this.options.generation.rateLimit?.limit,
50+
// this.adminforth.auth.getClientIp(headers),
51+
// );
52+
if (!await this.rateLimiter.consume(`${this.pluginInstanceId}-${this.adminforth.auth.getClientIp(headers)}`)) {
53+
jobs.set(jobId, { status: "failed", error: this.options.generation.rateLimit.errorMessage });
54+
return { error: this.options.generation.rateLimit.errorMessage };
55+
}
56+
}
57+
let attachmentFiles = [];
58+
if (this.options.generation.attachFiles) {
59+
// TODO - does it require additional allowed action to check this record id has access to get the image?
60+
// or should we mention in docs that user should do validation in method itself
61+
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get(
62+
[Filters.EQ(this.resourceConfig.columns.find(c => c.primaryKey)?.name, recordId)]
63+
);
64+
65+
66+
if (!record) {
67+
return { error: `Record with id ${recordId} not found` };
68+
}
69+
70+
attachmentFiles = await this.options.generation.attachFiles({ record, adminUser });
71+
// if files is not array, make it array
72+
if (!Array.isArray(attachmentFiles)) {
73+
attachmentFiles = [attachmentFiles];
74+
}
75+
76+
}
77+
78+
let error: string | undefined = undefined;
79+
80+
const STUB_MODE = false;
81+
82+
const images = await Promise.all(
83+
(new Array(this.options.generation.countToGenerate)).fill(0).map(async () => {
84+
if (STUB_MODE) {
85+
await new Promise((resolve) => setTimeout(resolve, 2000));
86+
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
87+
}
88+
const start = +new Date();
89+
let resp;
90+
try {
91+
resp = await this.options.generation.adapter.generate(
92+
{
93+
prompt,
94+
inputFiles: attachmentFiles,
95+
n: 1,
96+
size: this.options.generation.outputSize,
97+
}
98+
)
99+
} catch (e: any) {
100+
error = `No response from image generation provider: ${e.message}. Please check your prompt or try again later.`;
101+
return;
102+
}
103+
104+
if (resp.error) {
105+
console.error('Error generating image', resp.error);
106+
error = resp.error;
107+
return;
108+
}
109+
110+
this.totalCalls++;
111+
this.totalDuration += (+new Date() - start) / 1000;
112+
113+
return resp.imageURLs[0]
114+
115+
})
116+
);
117+
jobs.set(jobId, { status: "completed", images, error });
118+
return { ok: true };
119+
};
120+
43121
instanceUniqueRepresentation(pluginOptions: any) : string {
44122
return `${pluginOptions.pathColumnName}`;
45123
}
@@ -323,81 +401,34 @@ export default class UploadPlugin extends AdminForthPlugin {
323401

324402
server.endpoint({
325403
method: 'POST',
326-
path: `/plugin/${this.pluginInstanceId}/generate_images`,
404+
path: `/plugin/${this.pluginInstanceId}/create-image-generation-job`,
327405
handler: async ({ body, adminUser, headers }) => {
328406
const { prompt, recordId } = body;
329-
if (this.rateLimiter) {
330-
// rate limit
331-
// const { error } = RateLimiter.checkRateLimit(
332-
// this.pluginInstanceId,
333-
// this.options.generation.rateLimit?.limit,
334-
// this.adminforth.auth.getClientIp(headers),
335-
// );
336-
if (!await this.rateLimiter.consume(`${this.pluginInstanceId}-${this.adminforth.auth.getClientIp(headers)}`)) {
337-
return { error: this.options.generation.rateLimit.errorMessage };
338-
}
339-
}
340-
let attachmentFiles = [];
341-
if (this.options.generation.attachFiles) {
342-
// TODO - does it require additional allowed action to check this record id has access to get the image?
343-
// or should we mention in docs that user should do validation in method itself
344-
const record = await this.adminforth.resource(this.resourceConfig.resourceId).get(
345-
[Filters.EQ(this.resourceConfig.columns.find((column: any) => column.primaryKey)?.name, recordId)]
346-
);
347-
348-
if (!record) {
349-
return { error: `Record with id ${recordId} not found` };
350-
}
351-
352-
attachmentFiles = await this.options.generation.attachFiles({ record, adminUser });
353-
// if files is not array, make it array
354-
if (!Array.isArray(attachmentFiles)) {
355-
attachmentFiles = [attachmentFiles];
356-
}
357-
358-
}
359-
360-
let error: string | undefined = undefined;
361-
362-
const STUB_MODE = false;
363-
364-
const images = await Promise.all(
365-
(new Array(this.options.generation.countToGenerate)).fill(0).map(async () => {
366-
if (STUB_MODE) {
367-
await new Promise((resolve) => setTimeout(resolve, 2000));
368-
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
369-
}
370-
const start = +new Date();
371-
let resp;
372-
try {
373-
resp = await this.options.generation.adapter.generate(
374-
{
375-
prompt,
376-
inputFiles: attachmentFiles,
377-
n: 1,
378-
size: this.options.generation.outputSize,
379-
}
380-
)
381-
} catch (e: any) {
382-
error = `No response from image generation provider: ${e.message}. Please check your prompt or try again later.`;
383-
return;
384-
}
385407

386-
if (resp.error) {
387-
console.error('Error generating image', resp.error);
388-
error = resp.error;
389-
return;
390-
}
408+
const jobId = randomUUID();
409+
jobs.set(jobId, { status: "in_progress" });
391410

392-
this.totalCalls++;
393-
this.totalDuration += (+new Date() - start) / 1000;
394-
395-
return resp.imageURLs[0]
411+
this.generateImages(jobId, prompt, recordId, adminUser, headers);
412+
setTimeout(() => jobs.delete(jobId), 1_800_000);
413+
setTimeout(() => {jobs.set(jobId, { status: "timeout" });}, 300_000);
396414

397-
})
398-
);
415+
return { ok: true, jobId };
416+
}
417+
});
399418

400-
return { error, images };
419+
server.endpoint({
420+
method: 'POST',
421+
path: `/plugin/${this.pluginInstanceId}/get-image-generation-job-status`,
422+
handler: async ({ body, adminUser, headers }) => {
423+
const jobId = body.jobId;
424+
if (!jobId) {
425+
return { error: "Can't find job id" };
426+
}
427+
const job = jobs.get(jobId);
428+
if (!job) {
429+
return { error: "Job not found" };
430+
}
431+
return { ok: true, job };
401432
}
402433
});
403434

@@ -459,5 +490,6 @@ export default class UploadPlugin extends AdminForthPlugin {
459490
});
460491

461492
}
493+
462494

463495
}

0 commit comments

Comments
 (0)