@@ -517,6 +517,15 @@ export const destinoApi = (() => {
517517 }
518518 updated_at : string
519519 last_modified_at : string
520+ name : string
521+ location : string
522+ date : string
523+ type_of_event : string
524+ twitter_handle : string
525+ link : string
526+ target_audience : string
527+ details : string
528+ image_url ?: string
520529 }
521530
522531 // Initialize Supabase client once to reuse across functions
@@ -535,128 +544,172 @@ export const destinoApi = (() => {
535544
536545 return data as EventRecord | null
537546 } ,
538- generateDestinoEvent : async ( event : any ) => {
539- const eventRecord = await _interface . getDestinoEvent ( event . Id )
540- const eventExists = ! ! eventRecord
541-
542- // If exists and updated after last modification, return cached content
543- if ( eventRecord ) {
544- // console.log('record found')
545- // console.log('updated_at:', eventRecord.updated_at)
546- // console.log('LastModifiedDate:', event.LastModifiedDate)
547- // console.log('updated_at date:', new Date(eventRecord.updated_at))
548- // console.log('LastModifiedDate date:', new Date(event.LastModifiedDate))
549- // console.log('comparison result:', new Date(eventRecord.updated_at) > new Date(event.LastModifiedDate))
550-
551- if ( new Date ( eventRecord . updated_at ) > new Date ( event . LastModifiedDate ) ) {
547+ generateDestinoEvent : async ( event : any , forceImageGeneration = false ) => {
548+ try {
549+ console . log ( `[generateDestinoEvent] Starting for event ${ event . Id } ${ forceImageGeneration ? ' (force image generation)' : '' } ` )
550+
551+ if ( ! event || ! event . Id ) {
552+ console . error ( '[generateDestinoEvent] Invalid event data:' , event )
553+ throw new Error ( 'Invalid event data' )
554+ }
555+
556+ const eventRecord = await _interface . getDestinoEvent ( event . Id )
557+ const eventExists = ! ! eventRecord
558+ console . log ( `[generateDestinoEvent] Event exists in database: ${ eventExists } ` )
559+
560+ // If exists and updated after last modification, return cached content
561+ if ( ! forceImageGeneration && eventRecord && new Date ( eventRecord . updated_at ) > new Date ( event . LastModifiedDate ) ) {
552562 return eventRecord . content
553563 }
554- }
555564
556- // Otherwise generate new content
557- console . log ( `Generating new content for Destino event ${ event . Id } ` )
558-
559- const upsert = {
560- event_id : event . Id ,
561- twitter_handle : event . Twitter ,
562- type_of_event : event [ 'Type of Event' ] ,
563- location : event . Location ,
564- link : event . Link ,
565- name : event . Name ,
566- date : event . Date . startDate ,
567- target_audience : event . TargetAudience ,
568- details : event . Details ,
569- updated_at : new Date ( ) . toISOString ( ) ,
570- last_modified_at : event . LastModifiedDate ,
571- } as any
572-
573- if ( ! eventExists ) {
574- const eventCompletion = await openai . beta . chat . completions . parse ( {
575- temperature : 0 ,
576- model : 'gpt-4.1' ,
577- messages : [
578- {
579- role : 'system' ,
580- content :
581- 'You take an event object and generate a simple summary of it in English, Spanish and Portuguese. Max 500 characters. It will be used to advertise the event.' ,
582- } ,
583- { role : 'user' , content : JSON . stringify ( { ...event , description : '' } ) } ,
584- ] ,
585- response_format : zodResponseFormat ( EventSchema , 'summary' ) ,
586- } )
565+ // Otherwise generate new content
566+ console . log ( `[generateDestinoEvent] Generating new content for event ${ event . Id } ` )
567+
568+ const upsert = {
569+ event_id : event . Id ,
570+ twitter_handle : event . Twitter ,
571+ type_of_event : event [ 'Type of Event' ] ,
572+ location : event . Location ,
573+ link : event . Link ,
574+ name : event . Name ,
575+ date : event . Date . startDate ,
576+ target_audience : event . TargetAudience ,
577+ details : event . Details ,
578+ updated_at : new Date ( ) . toISOString ( ) ,
579+ last_modified_at : event . LastModifiedDate ,
580+ } as any
581+
582+ if ( ! eventExists || forceImageGeneration ) {
583+ console . log ( `[generateDestinoEvent] Generating multilingual content for event ${ event . Id } ` )
584+ const eventCompletion = await openai . beta . chat . completions . parse ( {
585+ temperature : 0 ,
586+ model : 'gpt-4.1' ,
587+ messages : [
588+ {
589+ role : 'system' ,
590+ content :
591+ 'You take an event object and generate a simple summary of it in English, Spanish and Portuguese. Max 500 characters. It will be used to advertise the event.' ,
592+ } ,
593+ { role : 'user' , content : JSON . stringify ( { ...event , description : '' } ) } ,
594+ ] ,
595+ response_format : zodResponseFormat ( EventSchema , 'summary' ) ,
596+ } )
587597
588- const content = eventCompletion . choices [ 0 ] . message . parsed as { en : string ; es : string ; pt : string }
598+ if ( ! eventCompletion . choices ?. [ 0 ] ?. message ?. parsed ) {
599+ console . error ( '[generateDestinoEvent] Failed to generate content:' , eventCompletion )
600+ throw new Error ( 'Failed to generate content' )
601+ }
589602
590- const prompt = `
591- Adjust the reference image to match the event. Don't use any text in the generated image.
592-
593- Event name: ${ event . Name }
594- Event location: ${ event . Location }
595- Event summary: ${ content . en }
596- `
603+ // Fetch reference image from Supabase Storage
604+ const { data : imageData , error : imageError } = await supabase . storage . from ( 'destino-events' ) . download ( 'reference/destino.png' )
597605
598- // Fetch reference image from Supabase Storage
599- const { data : imageData , error : imageError } = await supabase . storage . from ( 'destino-events' ) . download ( 'reference/destino.png' )
606+ if ( imageError ) {
607+ console . error ( 'Error fetching reference image:' , imageError )
608+ throw new Error ( 'Failed to fetch reference image' )
609+ }
600610
601- if ( imageError ) {
602- console . error ( 'Error fetching reference image:' , imageError )
603- throw new Error ( 'Failed to fetch reference image' )
604- }
611+ const openAICompatibleImage = await toFile ( imageData , null , { type : 'image/png' } )
605612
606- const openAICompatibleImage = await toFile ( imageData , null , { type : 'image/png' } )
613+ const content = eventCompletion . choices [ 0 ] . message . parsed as { en : string ; es : string ; pt : string }
614+ console . log ( `[generateDestinoEvent] Generated content for event ${ event . Id } :` , {
615+ en : content . en . substring ( 0 , 50 ) + '...' ,
616+ es : content . es . substring ( 0 , 50 ) + '...' ,
617+ pt : content . pt . substring ( 0 , 50 ) + '...' ,
618+ } )
607619
608- const resultImage = await openai . images . edit ( {
609- model : 'gpt-image-1' ,
610- prompt,
611- image : openAICompatibleImage ,
612- // @ts -ignore
613- size : '1536x1024' ,
614- n : 1 ,
615- } )
620+ const prompt = `Adjust the reference image to suit the context of the event.
621+ DO NOT add or include any text in the generated image.
622+ Keep composition of the reference image.
623+
624+ Context:
625+ - Event name: "${ event . Name } "
626+ - Event location: "${ event . Location } "
627+ DO NOT include Event name or event location in the generated image.`
628+ console . log ( `[generateDestinoEvent] Generating image for event ${ event . Id } with prompt:` , prompt )
629+
630+ const resultImage = await openai . images . edit ( {
631+ model : 'gpt-image-1' ,
632+ prompt,
633+ image : openAICompatibleImage ,
634+ // @ts -ignore
635+ size : '1536x1024' ,
636+ n : 1 ,
637+ } )
616638
617- // Save the image to a file
618- const image_base64 = resultImage . data ?. [ 0 ] ?. b64_json
619- const image_bytes = image_base64 ? Buffer . from ( image_base64 , 'base64' ) : null
639+ if ( ! resultImage . data ?. [ 0 ] ?. b64_json ) {
640+ console . error ( '[generateDestinoEvent] Failed to generate image:' , resultImage )
641+ throw new Error ( 'Failed to generate image' )
642+ }
620643
621- let imageUrl = null
644+ // Save the image to a file
645+ const image_base64 = resultImage . data [ 0 ] . b64_json
646+ const image_bytes = Buffer . from ( image_base64 , 'base64' )
647+ console . log ( `[generateDestinoEvent] Image generated for event ${ event . Id } : ${ image_bytes ? 'success' : 'failed' } ` )
622648
623- if ( image_bytes ) {
624- // Twitter resize
625- const resizedImage = await sharp ( image_bytes ) . resize ( 1200 , 675 , { fit : 'cover' } ) . toBuffer ( )
649+ let imageUrl = null
626650
627- // Upload to Supabase Storage
628- const { data : uploadData1 , error : uploadError1 } = await supabase . storage
629- . from ( 'destino-events' )
630- . upload ( `${ event . Id } -twitter.png` , resizedImage , {
651+ if ( image_bytes ) {
652+ console . log ( `[generateDestinoEvent] Processing and uploading images for event ${ event . Id } ` )
653+ // Social media resize
654+ const resizedImage = await sharp ( image_bytes ) . resize ( 1200 , 628 , { fit : 'cover' } ) . jpeg ( { quality : 90 } ) . toBuffer ( )
655+
656+ // Generate timestamp for versioning
657+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' )
658+ const socialImagePath = `${ event . Id } -social-${ timestamp } .jpg`
659+ const originalImagePath = `${ event . Id } -original-${ timestamp } .png`
660+
661+ // Upload to Supabase Storage
662+ const { data : uploadData1 , error : uploadError1 } = await supabase . storage . from ( 'destino-events' ) . upload ( socialImagePath , resizedImage , {
663+ contentType : 'image/jpeg' ,
664+ upsert : true ,
665+ } )
666+
667+ const { data : uploadData2 , error : uploadError2 } = await supabase . storage . from ( 'destino-events' ) . upload ( originalImagePath , image_bytes , {
631668 contentType : 'image/png' ,
632669 upsert : true ,
633670 } )
634671
635- const { data : uploadData2 , error : uploadError2 } = await supabase . storage . from ( 'destino-events' ) . upload ( `${ event . Id } .png` , image_bytes , {
636- contentType : 'image/png' ,
637- upsert : true ,
638- } )
672+ if ( uploadError1 || uploadError2 ) {
673+ console . error ( `[generateDestinoEvent] Error uploading images for event ${ event . Id } :` , uploadError1 || uploadError2 )
674+ throw new Error ( 'Failed to upload images' )
675+ } else {
676+ // Get public URL
677+ const {
678+ data : { publicUrl } ,
679+ } = supabase . storage . from ( 'destino-events' ) . getPublicUrl ( socialImagePath )
680+
681+ imageUrl = publicUrl
682+ console . log ( `[generateDestinoEvent] Images uploaded successfully for event ${ event . Id } . URL: ${ imageUrl } ` )
683+ }
684+ }
685+
686+ upsert . image_url = imageUrl
687+ upsert . content = content
688+ }
689+
690+ // Save to Supabase
691+ console . log ( `[generateDestinoEvent] Saving event ${ event . Id } to database` )
692+ const result = await supabase . from ( 'destino_events' ) . upsert ( upsert , { defaultToNull : false } )
693+ console . log ( `[generateDestinoEvent] Event ${ event . Id } saved successfully` )
639694
640- if ( uploadError1 || uploadError2 ) {
641- console . error ( 'Error uploading image:' , uploadError1 || uploadError2 )
642- } else {
643- // Get public URL
644- const {
645- data : { publicUrl } ,
646- } = supabase . storage . from ( 'destino-events' ) . getPublicUrl ( `${ event . Id } .png` )
695+ // If forceImageGeneration is true, return the image URL
696+ if ( forceImageGeneration ) {
697+ // update image_url in the database
698+ await supabase . from ( 'destino_events' ) . update ( { image_url : upsert . image_url } ) . eq ( 'event_id' , event . Id )
647699
648- imageUrl = publicUrl
700+ console . log ( `[generateDestinoEvent] Returning content and image URL for event ${ event . Id } ` )
701+ return {
702+ content : upsert . content ,
703+ imageUrl : upsert . image_url ,
704+ updated : true ,
649705 }
650706 }
651707
652- upsert . image_url = imageUrl
653- upsert . content = content
708+ return result
709+ } catch ( error : any ) {
710+ console . error ( '[generateDestinoEvent] Error:' , error )
711+ throw error
654712 }
655-
656- // Save to Supabase
657- const result = await supabase . from ( 'destino_events' ) . upsert ( upsert , { defaultToNull : false } )
658-
659- return result
660713 } ,
661714 generateDestinoEvents : async ( ) => {
662715 const events = await fetchFromSalesforce ( )
0 commit comments