Skip to content

Commit 1910358

Browse files
authored
feat: Gemini generateContent API detection + purple palette + no-text hook
Model detection: gemini-* uses generateContent(), imagen-* uses generateImages(). Purple #7c3aed in DEFAULT_INSTRUCTIONS + enrichment prompt. First scene no-text instruction. Removed negativePrompt (unsupported on Gemini). Removed seed from Imagen path.
1 parent 2d6d472 commit 1910358

2 files changed

Lines changed: 85 additions & 13 deletions

File tree

app/api/cron/check-research/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ Return ONLY a JSON object:
686686
"list": { "items": ["Item 1", "Item 2"], "icon": "🚀" },
687687
"comparison": { "leftLabel": "A", "rightLabel": "B", "rows": [{ "left": "...", "right": "..." }] },
688688
"mockup": { "deviceType": "browser | phone | terminal", "screenContent": "..." },
689-
"imagePrompts": ["Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. [specific visual for this scene]. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable labels."]
689+
"imagePrompts": ["Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. [specific visual for this scene]. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable labels."]
690690
}
691691
],
692692
"cta": "string - call to action"
@@ -697,8 +697,9 @@ Return ONLY a JSON object:
697697
Requirements:
698698
- 3-5 scenes totaling 60-90 seconds
699699
- Use at least 2 different scene types
700-
- Each scene MUST include 2-5 imagePrompts following this exact template: "Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. [specific visual]. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable labels."
700+
- Each scene MUST include 2-5 imagePrompts following this exact template: "Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. [specific visual]. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable labels."
701701
- imagePrompts should describe specific 2D infographic visuals that illustrate the narration content
702+
- The FIRST scene's imagePrompts must be purely visual and eye-catching — no text labels, no annotations, no words. This is the thumbnail/hook frame.
702703
- Do NOT include any script text, titles, or word overlays in the video. The narration audio carries all words.
703704
- Think of each imagePrompt as a frame that will be shown for 3-5 seconds while the narration plays
704705
- Include REAL code snippets from the research where applicable

lib/services/gemini-infographics.ts

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,97 @@ function getAI(): GoogleGenAI {
8585
// ---------------------------------------------------------------------------
8686

8787
/**
88-
* Generate a single infographic image using Imagen 4 Fast.
88+
* Detect if a model uses the Gemini generateContent API (for image generation)
89+
* vs the Imagen generateImages API.
90+
*
91+
* Gemini image models: gemini-*-image-*, gemini-*-flash-image-*
92+
* Imagen models: imagen-*
93+
*/
94+
function isGeminiImageModel(model: string): boolean {
95+
return model.startsWith('gemini-');
96+
}
97+
98+
/**
99+
* Generate a single image using Gemini's generateContent API.
100+
* Used for models like gemini-3.1-flash-image-preview.
101+
*/
102+
async function generateWithGeminiContent(
103+
prompt: string,
104+
model: string,
105+
aspectRatio: string,
106+
): Promise<{ imageBase64: string; mimeType: string }> {
107+
const ai = getAI();
108+
109+
const response = await ai.models.generateContent({
110+
model,
111+
contents: prompt,
112+
config: {
113+
responseModalities: ['IMAGE'],
114+
imageConfig: {
115+
aspectRatio: aspectRatio as '1:1' | '3:4' | '4:3' | '9:16' | '16:9',
116+
},
117+
},
118+
});
119+
120+
// Extract image from response
121+
const parts = response.candidates?.[0]?.content?.parts;
122+
if (!parts || parts.length === 0) {
123+
throw new Error(`Gemini returned no parts for prompt "${prompt.slice(0, 80)}…"`);
124+
}
125+
126+
// Find the image part
127+
for (const part of parts) {
128+
if (part.inlineData?.data) {
129+
return {
130+
imageBase64: part.inlineData.data,
131+
mimeType: part.inlineData.mimeType || 'image/png',
132+
};
133+
}
134+
}
135+
136+
throw new Error(`Gemini returned no image data for prompt "${prompt.slice(0, 80)}…"`);
137+
}
138+
139+
/**
140+
* Generate a single infographic image.
141+
*
142+
* Automatically detects whether to use Gemini generateContent (for gemini-*
143+
* models) or Imagen generateImages (for imagen-* models).
89144
*
90145
* @param request - Prompt and generation options.
91-
* @param model - Imagen model ID (e.g. "imagen-4.0-fast-generate-001").
146+
* @param model - Model ID (e.g. "gemini-3.1-flash-image-preview" or "imagen-4.0-fast-generate-001").
92147
* @returns InfographicResult with base64 image bytes.
93148
* @throws If the API call fails or no image is returned.
94149
*/
95150
export async function generateInfographic(
96151
request: InfographicRequest,
97152
model: string = "imagen-4.0-fast-generate-001",
98153
): Promise<InfographicResult> {
154+
const aspectRatio = request.aspectRatio ?? "16:9";
155+
156+
if (isGeminiImageModel(model)) {
157+
// Gemini path: generateContent with responseModalities: ["IMAGE"]
158+
const result = await generateWithGeminiContent(
159+
request.prompt,
160+
model,
161+
aspectRatio,
162+
);
163+
return {
164+
imageBase64: result.imageBase64,
165+
mimeType: result.mimeType,
166+
prompt: request.prompt,
167+
};
168+
}
169+
170+
// Imagen path: generateImages (existing code)
99171
const ai = getAI();
100172

101173
const response = await ai.models.generateImages({
102174
model,
103175
prompt: request.prompt,
104176
config: {
105177
numberOfImages: 1,
106-
aspectRatio: request.aspectRatio ?? "16:9",
178+
aspectRatio: aspectRatio,
107179
...(request.negativePrompt && { negativePrompt: request.negativePrompt }),
108180
},
109181
});
@@ -117,7 +189,6 @@ export async function generateInfographic(
117189
}
118190

119191
const imageBytes = generated.image.imageBytes;
120-
// imageBytes may be a Uint8Array or base64 string depending on SDK version
121192
const imageBase64 =
122193
typeof imageBytes === "string"
123194
? imageBytes
@@ -210,11 +281,11 @@ export function buildInfographicPrompt(
210281

211282
/** Default infographic instructions if Sanity contentConfig is not set up */
212283
const DEFAULT_INSTRUCTIONS: string[] = [
213-
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. A high-level technical architecture overview showing system components and data flow. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
214-
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. A comparison chart showing key features and alternatives side by side. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
215-
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. A step-by-step workflow diagram showing the process from start to finish. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
216-
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. A timeline of key developments, milestones, and version releases. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
217-
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no purple, no blue. A pros and cons visual summary with clear icons and labels. Highlighted elements filled with bright green (#15b27b) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
284+
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. A high-level technical architecture overview showing system components and data flow. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
285+
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. A comparison chart showing key features and alternatives side by side. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
286+
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. A step-by-step workflow diagram showing the process from start to finish. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
287+
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. A timeline of key developments, milestones, and version releases. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
288+
'Infographic 2D architecture style. STRICTLY black (#000000) background only \u2014 no gradients, no blue. A pros and cons visual summary with clear icons and labels. Highlighted elements filled with vivid purple (#7c3aed) only. White lines connecting components and white text annotations. Large, readable text labels suitable for mobile viewing at 360px width. No watermarks.',
218289
];
219290

220291
// ---------------------------------------------------------------------------
@@ -318,7 +389,7 @@ export async function generateFromScenePrompts(
318389
// Generate horizontal (16:9)
319390
try {
320391
const hResult = await generateInfographic(
321-
{ prompt, aspectRatio: "16:9", negativePrompt: "purple background, blue gradient, watermark, blurry text, small text" },
392+
{ prompt, aspectRatio: "16:9" },
322393
model,
323394
);
324395
horizontal.push(hResult);
@@ -330,7 +401,7 @@ export async function generateFromScenePrompts(
330401
// Generate vertical (9:16)
331402
try {
332403
const vResult = await generateInfographic(
333-
{ prompt, aspectRatio: "9:16", negativePrompt: "purple background, blue gradient, watermark, blurry text, small text" },
404+
{ prompt, aspectRatio: "9:16" },
334405
model,
335406
);
336407
vertical.push(vResult);

0 commit comments

Comments
 (0)