From d36638dab9a0973f8483ab3a1be4f0901ee33893 Mon Sep 17 00:00:00 2001 From: Jean-Phi Baconnais Date: Thu, 30 Oct 2025 17:19:14 +0100 Subject: [PATCH 1/5] :sparkles: Review configuration to facilitate customisation, adding a templates.json file --- .../marketing/cli/GenerateImageCommand.java | 37 +++++----- .../marketing/cli/GenerateVideoCommand.java | 2 +- .../marketing/config/ConfigProperties.java | 67 +++++++++++++++++++ .../marketing/config/FIELDS_PROMPT.java | 17 +++++ .../zenika/marketing/domain/Template.java | 7 +- .../marketing/services/GeminiServices.java | 20 +++--- .../marketing/services/TemplateService.java | 17 +++++ src/main/resources/application.properties | 10 +-- src/main/resources/templates.json | 10 ++- 9 files changed, 152 insertions(+), 35 deletions(-) create mode 100644 src/main/java/zenika/marketing/config/FIELDS_PROMPT.java diff --git a/src/main/java/zenika/marketing/cli/GenerateImageCommand.java b/src/main/java/zenika/marketing/cli/GenerateImageCommand.java index 9ee2975..ed01135 100644 --- a/src/main/java/zenika/marketing/cli/GenerateImageCommand.java +++ b/src/main/java/zenika/marketing/cli/GenerateImageCommand.java @@ -27,7 +27,7 @@ public class GenerateImageCommand implements Runnable { @Option( names = {"-o", "--output"}, - description = "Output filename (default: ${DEFAULT-VALUE})" + description = "Output filename" ) String output; @@ -38,16 +38,16 @@ public class GenerateImageCommand implements Runnable { String templatePath; @Option( - names = {"--file1"}, - description = "Path to first additional image" + names = {"--name"}, + description = "Speaker or writer Name" ) - String file1Path; + String name; @Option( - names = {"--file2"}, - description = "Path to second additional image" + names = {"--title"}, + description = "Blog post or talk title" ) - String file2Path; + String title; @Option( names = {"-m", "--model"}, @@ -68,27 +68,30 @@ public void run() { var template = templateService.waitAValidTemplateByUser(templateName); // Check that the template is of type IMAGE - if (template.type() != MODE_FEATURE.IMAGE) { - Log.error("❌ Error: Template '" + templateName + "' is not an IMAGE template"); + if (!template.type().equals(MODE_FEATURE.IMAGE.toString())) { + Log.error("❌ Error: Template '" + templateName + "' is not an IMAGE template (" + template.type() + ")"); System.exit(1); return; } String templatePrompt = template.prompt(); - String finalOutput = output != null ? output : config.getDefaultResultFilename(); String finalTemplatePath = templatePath != null ? templatePath : config.getDefaultTemplatePath(); - String finalFile1Path = file1Path != null ? file1Path : config.getDefaultFile1Path(); - String finalFile2Path = file2Path != null ? file2Path : config.getDefaultFile2Path(); String finalModel = model != null ? model : config.getDefaultGeminiModelImage(); + config.setDefaultName(name != null ? name : config.getDefaultName()); + config.setDefaultTitle(title != null ? title : config.getDefaultTitle()); + config.setDefaultResultFilename(output != null ? output : config.getDefaultResultFilename()); + + String completedPrompt = templateService.preparePrompt(template, config); + + Log.info("\uD83D\uDCDD \uD83D\uDC49 Prompt: \n \t " + completedPrompt + "\n"); + geminiServices.generateImage( finalModel, - templatePrompt, - finalOutput, - finalTemplatePath, - finalFile1Path, - finalFile2Path + completedPrompt, + config ); + System.exit(0); } catch (Exception e) { Log.error("❌ Error: " + e.getMessage(), e); diff --git a/src/main/java/zenika/marketing/cli/GenerateVideoCommand.java b/src/main/java/zenika/marketing/cli/GenerateVideoCommand.java index 309e178..ba03633 100644 --- a/src/main/java/zenika/marketing/cli/GenerateVideoCommand.java +++ b/src/main/java/zenika/marketing/cli/GenerateVideoCommand.java @@ -68,7 +68,7 @@ public void run() { var template = templateService.waitAValidTemplateByUser(templateName); // Check that the template is of type VIDEO - if (template.type() != MODE_FEATURE.VIDEO) { + if (!template.type().equals(MODE_FEATURE.VIDEO)) { Log.error("❌ Error: Template '" + templateName + "' is not a VIDEO template"); System.exit(1); return; diff --git a/src/main/java/zenika/marketing/config/ConfigProperties.java b/src/main/java/zenika/marketing/config/ConfigProperties.java index 3f39f10..8bf13d2 100644 --- a/src/main/java/zenika/marketing/config/ConfigProperties.java +++ b/src/main/java/zenika/marketing/config/ConfigProperties.java @@ -34,6 +34,12 @@ public class ConfigProperties { @ConfigProperty(name = "app.video.resolution") String defaultVideoResolution; + @ConfigProperty(name = "app.name") + String defaultName; + + @ConfigProperty(name = "app.title") + String defaultTitle; + public String getDefaultGeminiModelImage() { return defaultGeminiModelImage; } @@ -69,4 +75,65 @@ public String getDefaultVideoRatio() { public String getDefaultVideoResolution() { return defaultVideoResolution; } + + public String getDefaultName() { return defaultName; } + + public String getDefaultTitle() { return defaultTitle; } + + public void setDefaultGeminiModelImage(String defaultGeminiModelImage) { + this.defaultGeminiModelImage = defaultGeminiModelImage; + } + + public void setDefaultGeminiVeoModel(String defaultGeminiVeoModel) { + this.defaultGeminiVeoModel = defaultGeminiVeoModel; + } + + public void setDefaultResultFilename(String defaultResultFilename) { + this.defaultResultFilename = defaultResultFilename; + } + + public void setDefaultPrompt(String defaultPrompt) { + this.defaultPrompt = defaultPrompt; + } + + public void setDefaultTemplatePath(String defaultTemplatePath) { + this.defaultTemplatePath = defaultTemplatePath; + } + + public void setDefaultFile1Path(String defaultFile1Path) { + this.defaultFile1Path = defaultFile1Path; + } + + public void setDefaultFile2Path(String defaultFile2Path) { + this.defaultFile2Path = defaultFile2Path; + } + + public void setDefaultVideoRatio(String defaultVideoRatio) { + this.defaultVideoRatio = defaultVideoRatio; + } + + public void setDefaultVideoResolution(String defaultVideoResolution) { + this.defaultVideoResolution = defaultVideoResolution; + } + + public void setDefaultName(String defaultName) { + this.defaultName = defaultName; + } + + public void setDefaultTitle(String defaultTitle) { + this.defaultTitle = defaultTitle; + } + + public String getFieldByValue(String field, ConfigProperties config) { + switch (FIELDS_PROMPT.valueOf(field)) { + case NAME: + return config.getDefaultName(); + case TITLE: + return config.getDefaultTitle(); + case TEMPLATE: + return config.getDefaultTemplatePath(); + default: + return ""; + } + } } diff --git a/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java b/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java new file mode 100644 index 0000000..5f925d8 --- /dev/null +++ b/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java @@ -0,0 +1,17 @@ +package zenika.marketing.config; + +public enum FIELDS_PROMPT { + NAME("NAME"), + TITLE("TITLE"), + TEMPLATE("TEMPLATE"); + + private final String value; + + FIELDS_PROMPT(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/zenika/marketing/domain/Template.java b/src/main/java/zenika/marketing/domain/Template.java index 7c4d8f0..a38008e 100644 --- a/src/main/java/zenika/marketing/domain/Template.java +++ b/src/main/java/zenika/marketing/domain/Template.java @@ -1,7 +1,10 @@ package zenika.marketing.domain; import io.quarkus.runtime.annotations.RegisterForReflection; -import zenika.marketing.config.MODE_FEATURE; + +import java.util.List; @RegisterForReflection -public record Template(String name, String description, MODE_FEATURE type, String prompt) {} \ No newline at end of file +public record Template(String name, String description, String type, String template, String prompt, List fields) { +} + diff --git a/src/main/java/zenika/marketing/services/GeminiServices.java b/src/main/java/zenika/marketing/services/GeminiServices.java index d432dbf..c7248e8 100644 --- a/src/main/java/zenika/marketing/services/GeminiServices.java +++ b/src/main/java/zenika/marketing/services/GeminiServices.java @@ -21,8 +21,7 @@ public class GeminiServices { @Inject ConfigProperties config; - public void generateImage(String model, String prompt, String output, String templatePath, - String file1Path, String file2Path) throws IOException { + public void generateImage(String model, String prompt, ConfigProperties config) throws IOException { try (Client client = new Client.Builder() .apiKey(System.getenv("GOOGLE_API_KEY")) .build()) { @@ -30,20 +29,20 @@ public void generateImage(String model, String prompt, String output, String tem Log.info("✨ Start using Google AI API with model " + model); // Validate files exist - Path templateFile = Path.of(templatePath); - Path file1 = Path.of(file1Path); - Path file2 = Path.of(file2Path); + Path templateFile = Path.of(config.getDefaultTemplatePath()); + Path file1 = Path.of(config.getDefaultFile1Path()); + Path file2 = Path.of(config.getDefaultFile1Path()); if (!Files.exists(templateFile)) { - Log.error("❌ Template file not found: " + templatePath); + Log.error("❌ Template file not found: " + config.getDefaultTemplatePath()); System.exit(1); } if (!Files.exists(file1)) { - Log.error("❌ File 1 not found: " + file1Path); + Log.error("❌ File 1 not found: " + config.getDefaultFile1Path()); System.exit(1); } if (!Files.exists(file2)) { - Log.error("❌ File 2 not found: " + file2Path); + Log.error("❌ File 2 not found: " + config.getDefaultFile2Path()); System.exit(1); } @@ -63,13 +62,13 @@ public void generateImage(String model, String prompt, String output, String tem if (part.inlineData().isPresent()) { var blob = part.inlineData().get(); if (blob.data().isPresent()) { - Files.write(Paths.get(output), blob.data().get()); + Files.write(Paths.get(config.getDefaultResultFilename()), blob.data().get()); break; } } } - Log.info("✨ Image generated: " + output); + Log.info("✨ Image generated: " + config.getDefaultResultFilename()); } } @@ -121,4 +120,5 @@ public void generateVideo(String model, String prompt, String output, String tem Log.info("✨ Video generated: " + output); } } + } diff --git a/src/main/java/zenika/marketing/services/TemplateService.java b/src/main/java/zenika/marketing/services/TemplateService.java index e65d0d8..9050014 100644 --- a/src/main/java/zenika/marketing/services/TemplateService.java +++ b/src/main/java/zenika/marketing/services/TemplateService.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.logging.Log; import jakarta.enterprise.context.ApplicationScoped; +import zenika.marketing.config.ConfigProperties; +import zenika.marketing.config.FIELDS_PROMPT; import zenika.marketing.domain.Template; import java.io.InputStream; @@ -67,4 +69,19 @@ public Template waitAValidTemplateByUser(String templateName) { return selectedTemplate.get(); } + + public String preparePrompt(Template temp, ConfigProperties config) { + var finalPrompt = temp.prompt(); + + // Template (outside fields) + finalPrompt = finalPrompt.replaceFirst("%".concat(FIELDS_PROMPT.TEMPLATE.getValue()).concat("%"), temp.template()); + + if (!temp.fields().isEmpty()) { + for (String field : temp.fields()) { + finalPrompt = finalPrompt.replaceFirst("%".concat(field).concat("%"), config.getFieldByValue(field, config)); + } + } + + return finalPrompt; + } } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b5e8911..1a55453 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,8 +2,6 @@ app.gemini.model=gemini-2.5-flash-image app.gemini.model.veo=veo-3.0-fast-generate-preview -app.result.filename=gemini-generation-veo.mov - app.prompt=I would like to create an image from the template image integrating these elements: \ - replace "Speaker Name" by "Jean-Philippe Baconnais"\ - replace "Talk title" by "OSINT : L art de trouver ce qui ne devrait pas tre trouv". Please center this title and use 2 lines at maximum. \ @@ -17,9 +15,13 @@ app.prompt=I would like to create an image from the template image integrating t #app.prompt=I'd like to create a short animation from the template file to move the speaker photo, the title of the talk. # Template Configuration -app.template.path=images/25-devfestnantes/template-monstre-talk-dark-6.png -app.file1.path=images/25-devfestnantes/logo-devfest-nantes-25.png +app.result.filename=gemini-generation-veo.png +app.template.path=images/templates/blog.png +app.file1.path=images/people/benjamin-bourgeois.png app.file2.path=images/people/jeanphi-baconnais.jpg +app.name=Speaker name +app.title=Talk title + app.media.type=IMAGE app.video.ratio=16:9 app.video.resolution=1080p diff --git a/src/main/resources/templates.json b/src/main/resources/templates.json index 733c60d..e59c493 100644 --- a/src/main/resources/templates.json +++ b/src/main/resources/templates.json @@ -3,24 +3,32 @@ "name": "generate-image-blog-post", "description" : "Generate an image for a blog post", "type": "IMAGE", - "prompt": "I would like to create an image from the template image %s integrating these elements: - replace 'Writer Name' by %s - replace 'Post title' by %s. Please center this title and use 2 lines at maximum. - replace the second white square by the speaker photo (file2). Please don't modify the name of the author and the title of the blog post." + "template": "images/templates/blog.png", + "fields": ["NAME", "TITLE"], + "prompt": "I would like to create an image from the template image available in this file %TEMPLATE% integrating these elements: - replace 'Writer Name' by %NAME% - replace 'Post title' by %TITLE%. Please center this title and use 2 lines at maximum. - replace the second white square by the %s file. Please don't modify the name of the author and the title of the blog post." }, { "name": "generate-image-speaker-event", "description": "Generate an image to announce a speaker for a conference", "type": "IMAGE", + "template": "", + "fields": [], "prompt": "I would like to create an image from the template image %s integrating these elements: - replace 'Speaker Name' by %s - replace 'Talk title' by %s. Please center this title and use 2 lines at maximum. - replace the first white square by the logo of the conference (in file1). - replace the second white square by the speaker photo (file2). Please don't modify the name of the author and the title of the talk." }, { "name": "generate-video-speaker-event", "description": "Generate a video to annunce a speaker for a conference", "type": "VIDEO", + "template": "", + "fields": [], "prompt": "I'd like to create a short animation from the template file to move the speaker photo, the title of the talk." }, { "name": "generate-post-speaker-event", "description": "Generate a Linkedin & Bluesky post", "type": "POST", + "template": "", + "fields": [], "prompt": "I'd like to create two post to announce %s as a speaker for the conference %s in %s. The first one is for Linkedin, the second for Bluesky." } ] \ No newline at end of file From fa1930cb96e2367468cbafc473020c790e53fcd6 Mon Sep 17 00:00:00 2001 From: Jean-Phi Baconnais Date: Thu, 30 Oct 2025 17:32:27 +0100 Subject: [PATCH 2/5] :sparkles: Add photo as a parameter --- README.md | 2 +- .../marketing/cli/GenerateImageCommand.java | 7 +++++ .../marketing/config/ConfigProperties.java | 27 ++++++++++++------- .../marketing/config/FIELDS_PROMPT.java | 1 + .../marketing/services/GeminiServices.java | 14 ++-------- src/main/resources/application.properties | 2 ++ src/main/resources/templates.json | 4 +-- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1b6fc09..79efc81 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The application now provides a rich CLI with Picocli. For detailed CLI documenta mvn quarkus:dev -Dquarkus.args="--help" # Generate image with custom prompt -mvn quarkus:dev -Dquarkus.args="--prompt 'Create a vibrant conference banner' -o output.png" +quarkus dev -Dquarkus.args='image --template-name=generate-image-blog-post --title=DuckDB --name=zMember -o=outpu.png --z-photo=images/people/my-z-member.png' # Generate video mvn quarkus:dev -Dquarkus.args="-t video --prompt 'Conference intro' --vertex" diff --git a/src/main/java/zenika/marketing/cli/GenerateImageCommand.java b/src/main/java/zenika/marketing/cli/GenerateImageCommand.java index ed01135..0f95b09 100644 --- a/src/main/java/zenika/marketing/cli/GenerateImageCommand.java +++ b/src/main/java/zenika/marketing/cli/GenerateImageCommand.java @@ -55,6 +55,12 @@ public class GenerateImageCommand implements Runnable { ) String model; + @Option( + names = {"--z-photo"}, + description = "Zenika photo" + ) + String zPhoto; + @Option( names = {"--template-name"}, description = "Name of the template to use", @@ -81,6 +87,7 @@ public void run() { config.setDefaultName(name != null ? name : config.getDefaultName()); config.setDefaultTitle(title != null ? title : config.getDefaultTitle()); config.setDefaultResultFilename(output != null ? output : config.getDefaultResultFilename()); + config.setZPhoto(zPhoto != null ? zPhoto : config.getDefaultZPhoto()); String completedPrompt = templateService.preparePrompt(template, config); diff --git a/src/main/java/zenika/marketing/config/ConfigProperties.java b/src/main/java/zenika/marketing/config/ConfigProperties.java index 8bf13d2..bcd1e93 100644 --- a/src/main/java/zenika/marketing/config/ConfigProperties.java +++ b/src/main/java/zenika/marketing/config/ConfigProperties.java @@ -40,6 +40,9 @@ public class ConfigProperties { @ConfigProperty(name = "app.title") String defaultTitle; + @ConfigProperty(name = "app.z-photo") + String defaultZPhoto; + public String getDefaultGeminiModelImage() { return defaultGeminiModelImage; } @@ -80,6 +83,8 @@ public String getDefaultVideoResolution() { public String getDefaultTitle() { return defaultTitle; } + public String getDefaultZPhoto() { return defaultZPhoto; } + public void setDefaultGeminiModelImage(String defaultGeminiModelImage) { this.defaultGeminiModelImage = defaultGeminiModelImage; } @@ -124,16 +129,18 @@ public void setDefaultTitle(String defaultTitle) { this.defaultTitle = defaultTitle; } + public void setZPhoto(String defaultZPhoto) { + this.defaultZPhoto = defaultZPhoto; + } + public String getFieldByValue(String field, ConfigProperties config) { - switch (FIELDS_PROMPT.valueOf(field)) { - case NAME: - return config.getDefaultName(); - case TITLE: - return config.getDefaultTitle(); - case TEMPLATE: - return config.getDefaultTemplatePath(); - default: - return ""; - } + return switch (FIELDS_PROMPT.valueOf(field)) { + case NAME -> config.getDefaultName(); + case TITLE -> config.getDefaultTitle(); + case TEMPLATE -> config.getDefaultTemplatePath(); + case Z_PHOTO -> config.getDefaultZPhoto(); + default -> ""; + }; } + } diff --git a/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java b/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java index 5f925d8..415442f 100644 --- a/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java +++ b/src/main/java/zenika/marketing/config/FIELDS_PROMPT.java @@ -3,6 +3,7 @@ public enum FIELDS_PROMPT { NAME("NAME"), TITLE("TITLE"), + Z_PHOTO("Z_PHOTO"), TEMPLATE("TEMPLATE"); private final String value; diff --git a/src/main/java/zenika/marketing/services/GeminiServices.java b/src/main/java/zenika/marketing/services/GeminiServices.java index c7248e8..baca1f1 100644 --- a/src/main/java/zenika/marketing/services/GeminiServices.java +++ b/src/main/java/zenika/marketing/services/GeminiServices.java @@ -30,21 +30,12 @@ public void generateImage(String model, String prompt, ConfigProperties config) // Validate files exist Path templateFile = Path.of(config.getDefaultTemplatePath()); - Path file1 = Path.of(config.getDefaultFile1Path()); - Path file2 = Path.of(config.getDefaultFile1Path()); + Path zPhoto = Path.of(config.getDefaultZPhoto()); if (!Files.exists(templateFile)) { Log.error("❌ Template file not found: " + config.getDefaultTemplatePath()); System.exit(1); } - if (!Files.exists(file1)) { - Log.error("❌ File 1 not found: " + config.getDefaultFile1Path()); - System.exit(1); - } - if (!Files.exists(file2)) { - Log.error("❌ File 2 not found: " + config.getDefaultFile2Path()); - System.exit(1); - } Log.info("🎨 Using template: " + templateFile); @@ -52,8 +43,7 @@ public void generateImage(String model, String prompt, ConfigProperties config) model, Content.fromParts( Part.fromBytes(Files.readAllBytes(templateFile), Utils.getMimeType(templateFile.toString())), - Part.fromBytes(Files.readAllBytes(file1), Utils.getMimeType(file1.toString())), - Part.fromBytes(Files.readAllBytes(file2), Utils.getMimeType(file2.toString())), + Part.fromBytes(Files.readAllBytes(zPhoto), Utils.getMimeType(zPhoto.toString())), Part.fromText(prompt) ), null); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1a55453..94f5d1d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -19,8 +19,10 @@ app.result.filename=gemini-generation-veo.png app.template.path=images/templates/blog.png app.file1.path=images/people/benjamin-bourgeois.png app.file2.path=images/people/jeanphi-baconnais.jpg +# app.name=Speaker name app.title=Talk title +app.z-photo=images/people/benjamin-bourgeois.png app.media.type=IMAGE app.video.ratio=16:9 diff --git a/src/main/resources/templates.json b/src/main/resources/templates.json index e59c493..53dbe87 100644 --- a/src/main/resources/templates.json +++ b/src/main/resources/templates.json @@ -4,8 +4,8 @@ "description" : "Generate an image for a blog post", "type": "IMAGE", "template": "images/templates/blog.png", - "fields": ["NAME", "TITLE"], - "prompt": "I would like to create an image from the template image available in this file %TEMPLATE% integrating these elements: - replace 'Writer Name' by %NAME% - replace 'Post title' by %TITLE%. Please center this title and use 2 lines at maximum. - replace the second white square by the %s file. Please don't modify the name of the author and the title of the blog post." + "fields": ["NAME", "TITLE", "Z_PHOTO"], + "prompt": "I would like to create an image from the template image available in this file %TEMPLATE% integrating these elements: - replace 'Writer Name' by %NAME% - replace 'Post title' by %TITLE%. Please center this title and use 2 lines at maximum. - replace the second white square by the photo from this %Z_PHOTO% file. Please don't modify the name of the author and the title of the blog post." }, { "name": "generate-image-speaker-event", From 2d4d860fb718481039d4c98b1999ea398a1cf0f3 Mon Sep 17 00:00:00 2001 From: Jean-Phi Baconnais Date: Fri, 7 Nov 2025 21:46:24 +0100 Subject: [PATCH 3/5] :sparkles: Add template for 2 speakers --- src/main/resources/templates.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates.json b/src/main/resources/templates.json index 53dbe87..b735b25 100644 --- a/src/main/resources/templates.json +++ b/src/main/resources/templates.json @@ -11,8 +11,16 @@ "name": "generate-image-speaker-event", "description": "Generate an image to announce a speaker for a conference", "type": "IMAGE", - "template": "", - "fields": [], + "template": "images/templates/conf-1.png", + "fields": ["NAME", "TITLE", "Z_PHOTO"], + "prompt": "I would like to create an image from the template image %s integrating these elements: - replace 'Speaker Name' by %s - replace 'Talk title' by %s. Please center this title and use 2 lines at maximum. - replace the first white square by the logo of the conference (in file1). - replace the second white square by the speaker photo (file2). Please don't modify the name of the author and the title of the talk." + }, + { + "name": "generate-image-2-speaker-event", + "description": "Generate an image to announce a talk with 2 speaker for a conference", + "type": "IMAGE", + "template": "images/templates/conf-2.png", + "fields": ["NAME", "TITLE", "Z_PHOTO"], "prompt": "I would like to create an image from the template image %s integrating these elements: - replace 'Speaker Name' by %s - replace 'Talk title' by %s. Please center this title and use 2 lines at maximum. - replace the first white square by the logo of the conference (in file1). - replace the second white square by the speaker photo (file2). Please don't modify the name of the author and the title of the talk." }, { From 4899f1b97ac8fc299700e29725a53c6b11a1fc4d Mon Sep 17 00:00:00 2001 From: Jean-Phi Baconnais Date: Mon, 10 Nov 2025 17:09:15 +0100 Subject: [PATCH 4/5] :wrench: Review Gemini services structure --- .../marketing/cli/GenerateImageCommand.java | 100 ++++++++++++++---- .../marketing/cli/GenerateVideoCommand.java | 4 +- .../services/GeminiImagesServices.java | 43 ++++++++ ...Services.java => GeminiVideoServices.java} | 43 +------- 4 files changed, 127 insertions(+), 63 deletions(-) create mode 100644 src/main/java/zenika/marketing/services/GeminiImagesServices.java rename src/main/java/zenika/marketing/services/{GeminiServices.java => GeminiVideoServices.java} (61%) diff --git a/src/main/java/zenika/marketing/cli/GenerateImageCommand.java b/src/main/java/zenika/marketing/cli/GenerateImageCommand.java index 0f95b09..2cf9ed3 100644 --- a/src/main/java/zenika/marketing/cli/GenerateImageCommand.java +++ b/src/main/java/zenika/marketing/cli/GenerateImageCommand.java @@ -1,13 +1,23 @@ package zenika.marketing.cli; +import com.google.genai.types.Content; +import com.google.genai.types.Part; import io.quarkus.logging.Log; import jakarta.inject.Inject; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import zenika.marketing.config.ConfigProperties; import zenika.marketing.config.MODE_FEATURE; -import zenika.marketing.services.GeminiServices; +import zenika.marketing.domain.Template; +import zenika.marketing.services.GeminiImagesServices; import zenika.marketing.services.TemplateService; +import zenika.marketing.utils.Utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Consumer; @Command( name = "image", @@ -17,7 +27,7 @@ public class GenerateImageCommand implements Runnable { @Inject - GeminiServices geminiServices; + GeminiImagesServices geminiServices; @Inject ConfigProperties config; @@ -68,39 +78,91 @@ public class GenerateImageCommand implements Runnable { ) String templateName; + private final Map> templateHandlers = Map.of( + "generate-image-blog-post", this::generateImageBlogPost, + "generate-image-speaker-event", this::generateImageSpeakerEvent, + "generate-image-2-speaker-event", this::generateImage2SpeakerEvent + ); + @Override public void run() { try { var template = templateService.waitAValidTemplateByUser(templateName); - // Check that the template is of type IMAGE if (!template.type().equals(MODE_FEATURE.IMAGE.toString())) { Log.error("❌ Error: Template '" + templateName + "' is not an IMAGE template (" + template.type() + ")"); System.exit(1); - return; } - String templatePrompt = template.prompt(); - String finalTemplatePath = templatePath != null ? templatePath : config.getDefaultTemplatePath(); - String finalModel = model != null ? model : config.getDefaultGeminiModelImage(); + Consumer