Skip to content

Commit 6d01073

Browse files
Use self-hosted Gemini for images
Switched blog image generation to self-hosted Gemini API (remove Lovable gateway usage) and updated code to fetch/generate header images directly from internal CMS providers, uploading to Supabase storage and storing public URLs. This replaces Lovable API key usage with internal Gemini key-based integration. X-Lovable-Edit-ID: edt-98f1bf2d-6f6e-4a42-89a8-7f4095144ac8 Co-authored-by: magnusfroste <38864257+magnusfroste@users.noreply.github.com>
2 parents 8843d99 + cbdcbbb commit 6d01073

File tree

1 file changed

+25
-19
lines changed
  • supabase/functions/agent-execute

1 file changed

+25
-19
lines changed

supabase/functions/agent-execute/index.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,30 +1411,36 @@ async function executeBlogAction(
14111411
}
14121412
}
14131413

1414-
// Strategy 2: Gemini image generation via Lovable AI gateway
1415-
const lovableApiKey = Deno.env.get('LOVABLE_API_KEY');
1416-
if (!featuredImage && lovableApiKey) {
1414+
// Strategy 2: Gemini image generation (direct API, self-hosted)
1415+
const geminiKeyImg = Deno.env.get('GEMINI_API_KEY');
1416+
if (!featuredImage && geminiKeyImg) {
14171417
try {
1418-
const imgResp = await fetch('https://ai.gateway.lovable.dev/v1/chat/completions', {
1419-
method: 'POST',
1420-
headers: { 'Authorization': `Bearer ${lovableApiKey}`, 'Content-Type': 'application/json' },
1421-
body: JSON.stringify({
1422-
model: 'google/gemini-2.5-flash-image',
1423-
messages: [{ role: 'user', content: `Generate a professional, modern blog header image for an article titled "${title}" about "${topic}". The image should be visually striking, landscape oriented, suitable as a blog featured image. No text in the image.` }],
1424-
modalities: ['image', 'text'],
1425-
}),
1426-
});
1418+
const imgPrompt = `Generate a professional, modern blog header image for an article titled "${title}" about "${topic}". The image should be visually striking, landscape oriented, suitable as a blog featured image. No text in the image.`;
1419+
const imgResp = await fetch(
1420+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${geminiKeyImg}`,
1421+
{
1422+
method: 'POST',
1423+
headers: { 'Content-Type': 'application/json' },
1424+
body: JSON.stringify({
1425+
contents: [{ parts: [{ text: imgPrompt }] }],
1426+
generationConfig: {
1427+
responseModalities: ['IMAGE', 'TEXT'],
1428+
},
1429+
}),
1430+
}
1431+
);
14271432
if (imgResp.ok) {
14281433
const imgData = await imgResp.json();
1429-
const base64Url = imgData.choices?.[0]?.message?.images?.[0]?.image_url?.url;
1430-
if (base64Url) {
1431-
// Upload base64 image to Supabase storage
1432-
const base64Data = base64Url.replace(/^data:image\/\w+;base64,/, '');
1433-
const imageBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
1434-
const fileName = `blog/${slug}-${Date.now()}.png`;
1434+
const parts = imgData.candidates?.[0]?.content?.parts || [];
1435+
const imagePart = parts.find((p: any) => p.inlineData?.mimeType?.startsWith('image/'));
1436+
if (imagePart?.inlineData?.data) {
1437+
const mimeType = imagePart.inlineData.mimeType || 'image/png';
1438+
const ext = mimeType.includes('jpeg') ? 'jpg' : 'png';
1439+
const imageBytes = Uint8Array.from(atob(imagePart.inlineData.data), c => c.charCodeAt(0));
1440+
const fileName = `blog/${slug}-${Date.now()}.${ext}`;
14351441
const { error: uploadErr } = await supabase.storage
14361442
.from('cms-images')
1437-
.upload(fileName, imageBytes, { contentType: 'image/png', upsert: true });
1443+
.upload(fileName, imageBytes, { contentType: mimeType, upsert: true });
14381444
if (!uploadErr) {
14391445
const { data: urlData } = supabase.storage.from('cms-images').getPublicUrl(fileName);
14401446
featuredImage = urlData.publicUrl;

0 commit comments

Comments
 (0)