feat: add Cloudflare Worker proxy for Gemini image generation

- Created gemini-proxy worker to route requests through US edge
- Updated generate-page to use proxy for bypassing geo-restrictions
- Gemini image generation is blocked in Germany, proxy routes through US

Worker URL: https://gemini-proxy.jeffemmett.workers.dev

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-18 19:45:31 -05:00
parent 779a0806a2
commit 06dc335013
3 changed files with 110 additions and 23 deletions

View File

@ -119,39 +119,48 @@ async function generateImageWithGemini(prompt: string): Promise<string> {
}
// Gemini 2.0 Flash with native image generation (Nano Banana)
// Uses Cloudflare Worker proxy to bypass geo-restrictions
async function generateWithGemini2FlashImage(prompt: string, apiKey: string): Promise<string | null> {
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
contents: [
{
parts: [
{
text: `Generate an image: ${prompt}`,
},
],
},
],
generationConfig: {
responseModalities: ["TEXT", "IMAGE"],
// Use the Cloudflare Worker proxy to route through US
const proxyUrl = process.env.GEMINI_PROXY_URL || "https://gemini-proxy.jeffemmett.workers.dev";
const response = await fetch(proxyUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": apiKey,
},
body: JSON.stringify({
model: "gemini-2.0-flash-exp",
contents: [
{
parts: [
{
text: `Generate an image: ${prompt}`,
},
],
},
}),
}
);
],
generationConfig: {
responseModalities: ["TEXT", "IMAGE"],
},
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error("Gemini 2.0 Flash API error:", response.status, errorText);
console.error("Gemini proxy API error:", response.status, errorText);
return null;
}
const data = await response.json();
// Check for API errors in response
if (data.error) {
console.error("Gemini API error via proxy:", data.error);
return null;
}
// Extract image from response
const parts = data.candidates?.[0]?.content?.parts || [];
for (const part of parts) {

71
worker/gemini-proxy.js Normal file
View File

@ -0,0 +1,71 @@
/**
* Cloudflare Worker: Gemini API Proxy
* Routes requests through US region to bypass geo-restrictions
*/
export default {
async fetch(request, env) {
// Handle CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, X-API-Key",
"Access-Control-Max-Age": "86400",
},
});
}
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
// Get API key from header or env
const apiKey = request.headers.get("X-API-Key") || env.GEMINI_API_KEY;
if (!apiKey) {
return new Response(JSON.stringify({ error: "API key required" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
try {
const body = await request.json();
const { model, contents, generationConfig } = body;
// Forward to Gemini API
const modelName = model || "gemini-2.0-flash-exp";
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:generateContent?key=${apiKey}`;
const geminiResponse = await fetch(geminiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
contents,
generationConfig,
}),
});
const data = await geminiResponse.json();
return new Response(JSON.stringify(data), {
status: geminiResponse.status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
}
},
};

7
worker/wrangler.toml Normal file
View File

@ -0,0 +1,7 @@
name = "gemini-proxy"
main = "gemini-proxy.js"
compatibility_date = "2024-01-01"
# Workers run at edge globally, requests from US users will hit US edge
[placement]
mode = "smart"