Skip to content

Commit d3460b9

Browse files
committed
feat: enhance children's book agent with character consistency and dialog integration
- Add CharacterDesignAgent for detailed, consistent character descriptions across illustrations - Implement dialog text overlay on DALL-E generated images for immersive reading experience - Enhance IllustrationAgent with character consistency prompts and dialog integration - Add unique book naming with topic-based filenames and timestamps - Improve story generation with real narrative content instead of placeholders - Add verbose logging to track dialog text processing and character design - Update workflow with 5-step process: character design → story outline → content refinement → illustrations → PDF - Enhance prompts to ensure actual story progression rather than generic content - Add text cleaning and formatting for optimal readability on illustrations Key improvements: - Character appears identical across all pages (same face, hair, clothing, etc.) - Story dialog text displays directly on illustration bottom with readable formatting - Each book gets unique filename: topic-timestamp.pdf format - Real story content with meaningful dialog and narrative progression
1 parent de7159e commit d3460b9

File tree

5 files changed

+203
-31
lines changed

5 files changed

+203
-31
lines changed

step-11-agent/src/main/java/ca/bazlur/agent/BookCreationOrchestrator.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,30 @@ public interface BookOrchestrationService {
7575
3. Educational element
7676
4. Interactive suggestion
7777
78-
Format the response as:
78+
Format the response EXACTLY as follows (no deviation):
7979
TITLE: [book title]
80-
MAIN_CHARACTER: [character description]
80+
MAIN_CHARACTER: [Detailed physical description including: age, gender, hair color/style, eye color, skin tone, height, clothing style, distinctive features - BE VERY SPECIFIC for consistency]
8181
SETTING: [setting description]
8282
8383
PAGE_1:
84-
TEXT: [page text]
85-
ILLUSTRATION: [detailed description]
84+
TEXT: [Actual story dialog/narration for the page - make it engaging and specific to the story]
85+
ILLUSTRATION: [detailed scene description]
8686
EDUCATIONAL: [educational element]
8787
INTERACTIVE: [interactive suggestion]
8888
89-
[Continue for all pages...]
89+
PAGE_2:
90+
TEXT: [Actual story dialog/narration for the page - make it engaging and specific to the story]
91+
ILLUSTRATION: [detailed scene description]
92+
EDUCATIONAL: [educational element]
93+
INTERACTIVE: [interactive suggestion]
94+
95+
PAGE_3:
96+
TEXT: [Actual story dialog/narration for the page - make it engaging and specific to the story]
97+
ILLUSTRATION: [detailed scene description]
98+
EDUCATIONAL: [educational element]
99+
INTERACTIVE: [interactive suggestion]
100+
101+
CRITICAL: Write actual story content for each page TEXT field, not placeholders. Each page should advance the story with specific dialog or narration.
90102
""")
91103
String createRawBookOutline(@V("topic") String topic,
92104
@V("targetAge") String targetAge,
@@ -158,15 +170,15 @@ private BookOutline parseRawOutline(String rawOutline, int expectedPageCount) {
158170
currentEducational, currentInteractive);
159171
}
160172

161-
// Ensure we have the expected number of pages
173+
// If we don't have enough pages, create meaningful content
162174
while (pages.size() < expectedPageCount) {
163175
var pageNum = pages.size() + 1;
164176
pages.add(PageOutline.builder()
165177
.pageNumber(pageNum)
166-
.text("Continue the adventure...")
167-
.illustrationDescription("Scene showing the story continuation")
168-
.educationalElement("Continued learning")
169-
.interactiveElement("Ask questions about what happens next")
178+
.text("\"Our adventure continues with new discoveries!\"")
179+
.illustrationDescription("The main character exploring a new scene with wonder and excitement")
180+
.educationalElement("Discovery and exploration")
181+
.interactiveElement("What do you think happens next?")
170182
.build());
171183
}
172184

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package ca.bazlur.agent;
2+
3+
import ca.bazlur.config.BookCreationConfig;
4+
import dev.langchain4j.model.openai.OpenAiChatModel;
5+
import dev.langchain4j.service.AiServices;
6+
import dev.langchain4j.service.SystemMessage;
7+
import dev.langchain4j.service.UserMessage;
8+
import dev.langchain4j.service.V;
9+
10+
public class CharacterDesignAgent {
11+
12+
private final CharacterDesignService service;
13+
14+
public CharacterDesignAgent(String apiKey, BookCreationConfig config) {
15+
var model = OpenAiChatModel.builder()
16+
.apiKey(apiKey)
17+
.modelName(config.modelName())
18+
.temperature(0.8) // Higher creativity for character design
19+
.maxTokens(800)
20+
.build();
21+
22+
this.service = AiServices.builder(CharacterDesignService.class)
23+
.chatModel(model)
24+
.build();
25+
}
26+
27+
public interface CharacterDesignService {
28+
29+
@SystemMessage("""
30+
You are a professional children's book character designer. You create detailed,
31+
vivid character descriptions that ensure visual consistency across illustrations.
32+
33+
Your descriptions must be:
34+
- Extremely specific about physical features
35+
- Age-appropriate for the target audience
36+
- Culturally diverse and inclusive
37+
- Memorable and distinctive
38+
- Suitable for the story theme
39+
40+
Include details about: age, gender, hair (color, style, length), eyes (color, shape),
41+
skin tone, height/build, clothing style and colors, distinctive features, personality
42+
traits that show in appearance.
43+
""")
44+
45+
@UserMessage("""
46+
Create a detailed character description for the main character of a children's book with:
47+
48+
Story Topic: {{topic}}
49+
Target Age: {{targetAge}}
50+
Character Role: {{role}}
51+
52+
Return ONLY the character description, being extremely specific about every visual detail
53+
for illustration consistency. Make the character engaging and relatable for {{targetAge}} year olds.
54+
""")
55+
String createDetailedCharacterDescription(@V("topic") String topic,
56+
@V("targetAge") String targetAge,
57+
@V("role") String role);
58+
}
59+
60+
public String generateCharacterDescription(String topic, String targetAge, String role) {
61+
try {
62+
return service.createDetailedCharacterDescription(topic, targetAge, role);
63+
} catch (Exception e) {
64+
System.err.println("⚠️ Failed to generate character description: " + e.getMessage());
65+
return createFallbackCharacterDescription(targetAge, role);
66+
}
67+
}
68+
69+
private String createFallbackCharacterDescription(String targetAge, String role) {
70+
return switch (targetAge) {
71+
case "2-3" -> "A cheerful 3-year-old toddler with curly brown hair, bright green eyes, rosy cheeks, wearing a red striped shirt and blue overalls";
72+
case "4-5" -> "A curious 5-year-old child with shoulder-length blonde hair, brown eyes, freckles on nose, wearing a purple t-shirt and jeans";
73+
case "6-8" -> "An adventurous 7-year-old with black hair in a ponytail, dark brown eyes, confident smile, wearing a green adventure vest and khaki shorts";
74+
default -> "A friendly child with brown hair, kind eyes, and colorful clothing suitable for adventures";
75+
};
76+
}
77+
}

step-11-agent/src/main/java/ca/bazlur/agent/IllustrationAgent.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,35 @@ public IllustrationAgent(String apiKey, String illustrationStyle) {
4040

4141
public ImagePrompt createImagePrompt(String sceneDescription,
4242
String characterDescription,
43+
String dialogText,
4344
int pageNumber) {
4445
var prompt = """
4546
Children's book illustration in %s style.
4647
4748
Scene: %s
4849
49-
Characters: %s
50+
Main Character (MUST maintain exact same appearance): %s
5051
51-
Visual requirements:
52-
- Child-friendly and cheerful
52+
Dialog text to include at bottom: "%s"
53+
54+
CRITICAL REQUIREMENTS:
55+
- Keep the EXACT same character appearance as described above in every detail
56+
- Same face shape, eye color, hair style, clothing, body proportions
57+
- Include the dialog text clearly readable at the bottom of the illustration
58+
- Text should be in a clean, child-friendly font
59+
- Text background should be semi-transparent or white for readability
60+
- Child-friendly and cheerful scene
5361
- Bright, warm colors
54-
- No text or words in the image
5562
- Safe for children ages 2-8
5663
- High quality digital art
57-
- Consistent character appearance
5864
- Professional children's book illustration quality
5965
60-
Style: %s children's book illustration, professional quality
66+
Style: %s children's book illustration with dialog text, professional quality
6167
""".formatted(
6268
style,
6369
sceneDescription,
6470
characterDescription,
71+
dialogText,
6572
style
6673
);
6774

step-11-agent/src/main/java/ca/bazlur/cli/BookCreationCLI.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.nio.file.Files;
1414
import java.nio.file.Path;
1515
import java.nio.file.Paths;
16+
import java.time.LocalDateTime;
17+
import java.time.format.DateTimeFormatter;
1618
import java.util.concurrent.Callable;
1719

1820
@Command(name = "book-creator",
@@ -39,8 +41,7 @@ public class BookCreationCLI implements Callable<Integer> {
3941
private String educationalGoals;
4042

4143
@Option(names = {"-o", "--output"},
42-
description = "Output PDF file path",
43-
defaultValue = "generated-book.pdf")
44+
description = "Output PDF file path (auto-generated if not specified)")
4445
private String outputPath;
4546

4647
@Option(names = {"-s", "--style"},
@@ -104,8 +105,9 @@ public Integer call() throws Exception {
104105
// Execute workflow with progress updates
105106
var book = workflow.executeWithProgress(request);
106107

107-
// Save PDF using modern Path API
108-
var outputFile = Paths.get(outputPath);
108+
// Generate unique filename if not specified
109+
var finalOutputPath = outputPath != null ? outputPath : generateUniqueFileName(topic, book.getTitle());
110+
var outputFile = Paths.get(finalOutputPath);
109111
Files.write(outputFile, book.getPdfContent());
110112

111113
printSuccess(outputFile, book);
@@ -160,6 +162,21 @@ private void printSuccess(Path outputFile, CompleteBook book) throws IOException
160162
System.out.println(summary);
161163
}
162164

165+
private String generateUniqueFileName(String topic, String bookTitle) {
166+
// Clean the topic/title for filename
167+
var cleanName = (bookTitle != null && !bookTitle.equals("A Wonderful Story")) ? bookTitle : topic;
168+
cleanName = cleanName.toLowerCase()
169+
.replaceAll("[^a-z0-9\\s-]", "")
170+
.replaceAll("\\s+", "-")
171+
.replaceAll("-+", "-")
172+
.replaceAll("^-|-$", "");
173+
174+
// Add timestamp for uniqueness
175+
var timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
176+
177+
return String.format("%s-%s.pdf", cleanName, timestamp);
178+
}
179+
163180
public static void main(String[] args) {
164181
var exitCode = new CommandLine(new BookCreationCLI()).execute(args);
165182
System.exit(exitCode);

step-11-agent/src/main/java/ca/bazlur/workflow/BookCreationWorkflow.java

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class BookCreationWorkflow {
1616
private final BookCreationOrchestrator orchestrator;
1717
private final IllustrationAgent illustrationAgent;
1818
private final ContentRefinementAgent refinementAgent;
19+
private final CharacterDesignAgent characterDesignAgent;
1920
private final PDFGenerationAgent pdfAgent;
2021
private final BookCreationConfig config;
2122
private final boolean verbose;
@@ -25,6 +26,7 @@ public BookCreationWorkflow(String apiKey, BookCreationConfig config, boolean ve
2526
this.orchestrator = new BookCreationOrchestrator(apiKey, config);
2627
this.illustrationAgent = new IllustrationAgent(apiKey, "watercolor");
2728
this.refinementAgent = new ContentRefinementAgent(apiKey, config);
29+
this.characterDesignAgent = new CharacterDesignAgent(apiKey, config);
2830
this.pdfAgent = new PDFGenerationAgent();
2931
this.verbose = verbose;
3032
}
@@ -34,15 +36,31 @@ public CompleteBook executeWithProgress(BookRequest request) throws Exception {
3436
var pages = new ArrayList<BookPage>();
3537

3638
try {
37-
// Step 1: Generate book outline
38-
printStep(1, "Generating story outline");
39+
// Step 1: Generate detailed character design
40+
printStep(1, "Creating detailed character design");
41+
var detailedCharacter = characterDesignAgent.generateCharacterDescription(
42+
request.getTopic(),
43+
request.getTargetAge(),
44+
"main character"
45+
);
46+
System.out.println(" ✓ Character design: " +
47+
(detailedCharacter.length() > 100 ?
48+
detailedCharacter.substring(0, 100) + "..." :
49+
detailedCharacter));
50+
51+
// Step 2: Generate book outline
52+
printStep(2, "Generating story outline");
3953
var outline = orchestrator.generateOutline(request);
4054
System.out.println(" ✓ Title: " + outline.getTitle());
4155
System.out.println(" ✓ Main character: " + outline.getMainCharacter());
4256
System.out.println(" ✓ Setting: " + outline.getSetting());
4357

44-
// Step 2: Refine content for each page
45-
printStep(2, "Refining content for " + request.getPageCount() + " pages");
58+
// Use the detailed character design for illustrations
59+
var finalCharacterDescription = detailedCharacter.isEmpty() ?
60+
outline.getMainCharacter() : detailedCharacter;
61+
62+
// Step 3: Refine content for each page
63+
printStep(3, "Refining content for " + request.getPageCount() + " pages");
4664
var refinementTasks = new ArrayList<CompletableFuture<BookPage>>();
4765

4866
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
@@ -56,7 +74,7 @@ public CompleteBook executeWithProgress(BookRequest request) throws Exception {
5674
var preview = pageOutline.getText().length() > 50 ?
5775
pageOutline.getText().substring(0, 50) + "..." :
5876
pageOutline.getText();
59-
System.out.println(" 📄 Page " + (pageIndex + 1) + ": " + preview);
77+
System.out.println(" 📄 Page " + (pageIndex + 1) + " original: " + preview);
6078
}
6179

6280
// Refine text for age appropriateness
@@ -65,6 +83,13 @@ public CompleteBook executeWithProgress(BookRequest request) throws Exception {
6583
request.getTargetAge()
6684
);
6785

86+
if (verbose) {
87+
var refinedPreview = refinedText.length() > 50 ?
88+
refinedText.substring(0, 50) + "..." :
89+
refinedText;
90+
System.out.println(" 📝 Page " + (pageIndex + 1) + " refined: " + refinedPreview);
91+
}
92+
6893
return BookPage.builder()
6994
.pageNumber(pageIndex + 1)
7095
.text(refinedText)
@@ -91,16 +116,16 @@ public CompleteBook executeWithProgress(BookRequest request) throws Exception {
91116
}
92117
}
93118

94-
// Step 3: Generate illustrations (unless dry run)
119+
// Step 4: Generate illustrations (unless dry run)
95120
if (!request.isDryRun()) {
96-
printStep(3, "Generating illustrations");
97-
generateIllustrations(pages, outline.getMainCharacter());
121+
printStep(4, "Generating illustrations with dialog text");
122+
generateIllustrations(pages, finalCharacterDescription);
98123
} else {
99-
printStep(3, "Skipping image generation (dry run mode)");
124+
printStep(4, "Skipping image generation (dry run mode)");
100125
}
101126

102-
// Step 4: Generate PDF
103-
printStep(4, "Creating PDF");
127+
// Step 5: Generate PDF
128+
printStep(5, "Creating PDF");
104129
var book = CompleteBook.builder()
105130
.title(outline.getTitle())
106131
.author("Generated by AI Book Creator")
@@ -131,9 +156,17 @@ public CompleteBook executeWithProgress(BookRequest request) throws Exception {
131156
private void generateIllustrations(List<BookPage> pages, String mainCharacter) {
132157
for (var page : pages) {
133158
try {
159+
// Clean and format the dialog text for display
160+
var dialogText = cleanDialogText(page.getText());
161+
162+
if (verbose) {
163+
System.out.println(" 🎭 Dialog for page " + page.getPageNumber() + ": \"" + dialogText + "\"");
164+
}
165+
134166
var prompt = illustrationAgent.createImagePrompt(
135167
page.getIllustrationDescription(),
136168
mainCharacter,
169+
dialogText,
137170
page.getPageNumber()
138171
);
139172

@@ -151,6 +184,32 @@ private void generateIllustrations(List<BookPage> pages, String mainCharacter) {
151184
}
152185
}
153186

187+
private String cleanDialogText(String text) {
188+
if (text == null || text.trim().isEmpty()) {
189+
return "Let's continue our adventure!";
190+
}
191+
192+
// Remove extra whitespace and limit length for readability on image
193+
var cleaned = text.trim().replaceAll("\\s+", " ");
194+
195+
// Limit to about 60 characters for good readability on illustration
196+
if (cleaned.length() > 60) {
197+
var words = cleaned.split(" ");
198+
var result = new StringBuilder();
199+
for (var word : words) {
200+
if (result.length() + word.length() + 1 <= 60) {
201+
if (result.length() > 0) result.append(" ");
202+
result.append(word);
203+
} else {
204+
break;
205+
}
206+
}
207+
cleaned = result.toString() + "...";
208+
}
209+
210+
return cleaned;
211+
}
212+
154213
private BookPage createFallbackPage(PageOutline pageOutline, int pageNumber) {
155214
return BookPage.builder()
156215
.pageNumber(pageNumber)

0 commit comments

Comments
 (0)