Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
141 changes: 113 additions & 28 deletions src/main/java/zenika/marketing/cli/GenerateImageCommand.java
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -17,7 +27,7 @@
public class GenerateImageCommand implements Runnable {

@Inject
GeminiServices geminiServices;
GeminiImagesServices geminiServices;

@Inject
ConfigProperties config;
Expand All @@ -27,7 +37,7 @@ public class GenerateImageCommand implements Runnable {

@Option(
names = {"-o", "--output"},
description = "Output filename (default: ${DEFAULT-VALUE})"
description = "Output filename"
)
String output;

Expand All @@ -38,61 +48,136 @@ 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"},
description = "Gemini model to use"
)
String model;

@Option(
names = {"--photo1"},
description = "First photo"
)
String photo1;

@Option(
names = {"--photo2"},
description = "Second photo"
)
String photo2;

@Option(
names = {"--template-name"},
description = "Name of the template to use",
required = true
)
String templateName;

private final Map<String, Consumer<Template>> 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() != 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();

geminiServices.generateImage(
finalModel,
templatePrompt,
finalOutput,
finalTemplatePath,
finalFile1Path,
finalFile2Path
);
Consumer<Template> handler = templateHandlers.get(template.name());
if (handler == null) {
Log.error("❌ Error: No handler found for template '" + template.name() + "'. Please implement it in GenerateImageCommand.");
System.exit(1);
}
handler.accept(template);

System.exit(0);
} catch (Exception e) {
Log.error("❌ Error: " + e.getMessage(), e);
System.exit(1);
}
}

/**
* Generate an image for a blog post template.
* - 1 title
* - 1 writer
* - 1 writer photo
*
* @param template: template configuration
*/
private void generateImageBlogPost(Template template) {
Content content = null;

String completedPrompt = templateService.preparePrompt(template, config);

Path templateFile = Path.of(config.getDefaultTemplatePath());
Path zPhoto = Path.of(config.getDefaultZPhoto());

checkisFileExist(templateFile);
checkisFileExist(zPhoto);

try {
content = Content.fromParts(
Part.fromBytes(Files.readAllBytes(templateFile), Utils.getMimeType(templateFile.toString())),
Part.fromBytes(Files.readAllBytes(zPhoto), Utils.getMimeType(zPhoto.toString())),
Part.fromText(completedPrompt)
);
} catch (IOException e) {
Log.error("❌ Error: " + e.getMessage(), e);
System.exit(1);
}

prepareCallGemini(template, content, completedPrompt);
}

private void checkisFileExist(Path pathFile) {
if (!Files.exists(pathFile)) {
Log.error("❌ File not found: " + pathFile);
System.exit(1);
}
}

private void generateImageSpeakerEvent(Template template) {
Log.infof("-> generateImageBlogPost %s", template.name());
Content content = null;
prepareCallGemini(template, content, "");
}

private void generateImage2SpeakerEvent(Template template) {
Log.infof("-> generateImageBlogPost %s", template.name());
Content content = null;
prepareCallGemini(template, content, "");
}

private void prepareCallGemini(Template template, Content content, String prompt) {
config.setDefaultResultFilename(output != null ? output : config.getDefaultResultFilename());
String finalModel = model != null ? model : config.getDefaultGeminiModelImage();

Log.infof("-> generateImageBlogPost %s", template.name());
Log.info("\uD83D\uDCDD \uD83D\uDC49 Prompt: \n \t " + prompt + "\n");

try {
geminiServices.generateImage(finalModel, config, content);
} catch (IOException e) {
Log.error("❌ Error: " + e.getMessage(), e);
System.exit(1);
}
}
}
6 changes: 3 additions & 3 deletions src/main/java/zenika/marketing/cli/GenerateVideoCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import picocli.CommandLine.Option;
import zenika.marketing.config.ConfigProperties;
import zenika.marketing.config.MODE_FEATURE;
import zenika.marketing.services.GeminiServices;
import zenika.marketing.services.GeminiVideoServices;
import zenika.marketing.services.TemplateService;

@Command(
Expand All @@ -17,7 +17,7 @@
public class GenerateVideoCommand implements Runnable {

@Inject
GeminiServices geminiServices;
GeminiVideoServices geminiServices;

@Inject
ConfigProperties config;
Expand Down Expand Up @@ -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;
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/zenika/marketing/config/ConfigProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ public class ConfigProperties {
@ConfigProperty(name = "app.video.resolution")
String defaultVideoResolution;

@ConfigProperty(name = "app.name")
String defaultName;

@ConfigProperty(name = "app.title")
String defaultTitle;

@ConfigProperty(name = "app.z-photo")
String defaultZPhoto;

public String getDefaultGeminiModelImage() {
return defaultGeminiModelImage;
}
Expand Down Expand Up @@ -69,4 +78,69 @@ public String getDefaultVideoRatio() {
public String getDefaultVideoResolution() {
return defaultVideoResolution;
}

public String getDefaultName() { return defaultName; }

public String getDefaultTitle() { return defaultTitle; }

public String getDefaultZPhoto() { return defaultZPhoto; }

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 void setZPhoto(String defaultZPhoto) {
this.defaultZPhoto = defaultZPhoto;
}

public String getFieldByValue(String field, ConfigProperties config) {
return switch (FIELDS_PROMPT.valueOf(field)) {
case NAME -> config.getDefaultName();
case TITLE -> config.getDefaultTitle();
case TEMPLATE -> config.getDefaultTemplatePath();
case Z_PHOTO -> config.getDefaultZPhoto();
default -> "";
};
}

}
18 changes: 18 additions & 0 deletions src/main/java/zenika/marketing/config/FIELDS_PROMPT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package zenika.marketing.config;

public enum FIELDS_PROMPT {
NAME("NAME"),
TITLE("TITLE"),
Z_PHOTO("Z_PHOTO"),
TEMPLATE("TEMPLATE");

private final String value;

FIELDS_PROMPT(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
7 changes: 5 additions & 2 deletions src/main/java/zenika/marketing/domain/Template.java
Original file line number Diff line number Diff line change
@@ -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) {}
public record Template(String name, String description, String type, String template, String prompt, List<String> fields) {
}

Loading