@@ -3,9 +3,10 @@ import { PluginOptions } from './types.js';
33import { AdminForthPlugin , AdminForthResourceColumn , AdminForthResource , Filters , IAdminForth , IHttpServer , suggestIfTypo } from "adminforth" ;
44import { Readable } from "stream" ;
55import { RateLimiter } from "adminforth" ;
6+ import { randomUUID } from "crypto" ;
67
78const ADMINFORTH_NOT_YET_USED_TAG = 'adminforth-candidate-for-cleanup' ;
8-
9+ const jobs = new Map ( ) ;
910export default class UploadPlugin extends AdminForthPlugin {
1011 options : PluginOptions ;
1112
@@ -25,6 +26,82 @@ export default class UploadPlugin extends AdminForthPlugin {
2526 this . totalDuration = 0 ;
2627 }
2728
29+ private async generateImages ( jobId : string , prompt : string , recordId : any , adminUser : any , headers : any ) {
30+ if ( this . options . generation . rateLimit ?. limit ) {
31+ // rate limit
32+ const { error } = RateLimiter . checkRateLimit (
33+ this . pluginInstanceId ,
34+ this . options . generation . rateLimit ?. limit ,
35+ this . adminforth . auth . getClientIp ( headers ) ,
36+ ) ;
37+ if ( error ) {
38+ return { error : this . options . generation . rateLimit . errorMessage } ;
39+ }
40+ }
41+ let attachmentFiles = [ ] ;
42+ if ( this . options . generation . attachFiles ) {
43+ // TODO - does it require additional allowed action to check this record id has access to get the image?
44+ // or should we mention in docs that user should do validation in method itself
45+ const record = await this . adminforth . resource ( this . resourceConfig . resourceId ) . get (
46+ [ Filters . EQ ( this . resourceConfig . columns . find ( c => c . primaryKey ) ?. name , recordId ) ]
47+ ) ;
48+
49+
50+ if ( ! record ) {
51+ return { error : `Record with id ${ recordId } not found` } ;
52+ }
53+
54+ attachmentFiles = await this . options . generation . attachFiles ( { record, adminUser } ) ;
55+ // if files is not array, make it array
56+ if ( ! Array . isArray ( attachmentFiles ) ) {
57+ attachmentFiles = [ attachmentFiles ] ;
58+ }
59+
60+ }
61+
62+ let error : string | undefined = undefined ;
63+
64+ const STUB_MODE = false ;
65+
66+ const images = await Promise . all (
67+ ( new Array ( this . options . generation . countToGenerate ) ) . fill ( 0 ) . map ( async ( ) => {
68+ if ( STUB_MODE ) {
69+ await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
70+ return `https://picsum.photos/200/300?random=${ Math . floor ( Math . random ( ) * 1000 ) } ` ;
71+ }
72+ const start = + new Date ( ) ;
73+ let resp ;
74+ try {
75+ resp = await this . options . generation . adapter . generate (
76+ {
77+ prompt,
78+ inputFiles : attachmentFiles ,
79+ n : 1 ,
80+ size : this . options . generation . outputSize ,
81+ }
82+ )
83+ } catch ( e : any ) {
84+ error = `No response from image generation provider: ${ e . message } . Please check your prompt or try again later.` ;
85+ return ;
86+ }
87+
88+ if ( resp . error ) {
89+ console . error ( 'Error generating image' , resp . error ) ;
90+ error = resp . error ;
91+ return ;
92+ }
93+
94+ this . totalCalls ++ ;
95+ this . totalDuration += ( + new Date ( ) - start ) / 1000 ;
96+
97+ return resp . imageURLs [ 0 ]
98+
99+ } )
100+ ) ;
101+ jobs . set ( jobId , { status : "completed" , images, error } ) ;
102+ return { ok : true } ;
103+ } ;
104+
28105 instanceUniqueRepresentation ( pluginOptions : any ) : string {
29106 return `${ pluginOptions . pathColumnName } ` ;
30107 }
@@ -341,81 +418,32 @@ export default class UploadPlugin extends AdminForthPlugin {
341418
342419 server . endpoint ( {
343420 method : 'POST' ,
344- path : `/plugin/${ this . pluginInstanceId } /generate_images ` ,
421+ path : `/plugin/${ this . pluginInstanceId } /create-image-generation-job ` ,
345422 handler : async ( { body, adminUser, headers } ) => {
346423 const { prompt, recordId } = body ;
347- if ( this . options . generation . rateLimit ?. limit ) {
348- // rate limit
349- const { error } = RateLimiter . checkRateLimit (
350- this . pluginInstanceId ,
351- this . options . generation . rateLimit ?. limit ,
352- this . adminforth . auth . getClientIp ( headers ) ,
353- ) ;
354- if ( error ) {
355- return { error : this . options . generation . rateLimit . errorMessage } ;
356- }
357- }
358- let attachmentFiles = [ ] ;
359- if ( this . options . generation . attachFiles ) {
360- // TODO - does it require additional allowed action to check this record id has access to get the image?
361- // or should we mention in docs that user should do validation in method itself
362- const record = await this . adminforth . resource ( this . resourceConfig . resourceId ) . get (
363- [ Filters . EQ ( this . resourceConfig . columns . find ( ( column : any ) => column . primaryKey ) ?. name , recordId ) ]
364- ) ;
365-
366- if ( ! record ) {
367- return { error : `Record with id ${ recordId } not found` } ;
368- }
369-
370- attachmentFiles = await this . options . generation . attachFiles ( { record, adminUser } ) ;
371- // if files is not array, make it array
372- if ( ! Array . isArray ( attachmentFiles ) ) {
373- attachmentFiles = [ attachmentFiles ] ;
374- }
375-
376- }
377-
378- let error : string | undefined = undefined ;
379-
380- const STUB_MODE = false ;
381424
382- const images = await Promise . all (
383- ( new Array ( this . options . generation . countToGenerate ) ) . fill ( 0 ) . map ( async ( ) => {
384- if ( STUB_MODE ) {
385- await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
386- return `https://picsum.photos/200/300?random=${ Math . floor ( Math . random ( ) * 1000 ) } ` ;
387- }
388- const start = + new Date ( ) ;
389- let resp ;
390- try {
391- resp = await this . options . generation . adapter . generate (
392- {
393- prompt,
394- inputFiles : attachmentFiles ,
395- n : 1 ,
396- size : this . options . generation . outputSize ,
397- }
398- )
399- } catch ( e : any ) {
400- error = `No response from image generation provider: ${ e . message } . Please check your prompt or try again later.` ;
401- return ;
402- }
425+ const jobId = randomUUID ( ) ;
426+ jobs . set ( jobId , { status : "in_progress" } ) ;
403427
404- if ( resp . error ) {
405- console . error ( 'Error generating image' , resp . error ) ;
406- error = resp . error ;
407- return ;
408- }
409-
410- this . totalCalls ++ ;
411- this . totalDuration += ( + new Date ( ) - start ) / 1000 ;
412-
413- return resp . imageURLs [ 0 ]
414-
415- } )
416- ) ;
428+ setTimeout ( async ( ) => await this . generateImages ( jobId , prompt , recordId , adminUser , headers ) , 100 ) ;
429+
430+ return { ok : true , jobId } ;
431+ }
432+ } ) ;
417433
418- return { error, images } ;
434+ server . endpoint ( {
435+ method : 'POST' ,
436+ path : `/plugin/${ this . pluginInstanceId } /get-image-generation-job-status` ,
437+ handler : async ( { body, adminUser, headers } ) => {
438+ const jobId = body . jobId ;
439+ if ( ! jobId ) {
440+ return { error : "Can't find job id" } ;
441+ }
442+ const job = jobs . get ( jobId ) ;
443+ if ( ! job ) {
444+ return { error : "Job not found" } ;
445+ }
446+ return { ok : true , job } ;
419447 }
420448 } ) ;
421449
@@ -457,5 +485,6 @@ export default class UploadPlugin extends AdminForthPlugin {
457485 } ) ;
458486
459487 }
488+
460489
461490}
0 commit comments