diff --git a/web/app/api/generate-page/route.ts b/web/app/api/generate-page/route.ts index b058ec5..880a629 100644 --- a/web/app/api/generate-page/route.ts +++ b/web/app/api/generate-page/route.ts @@ -119,39 +119,48 @@ async function generateImageWithGemini(prompt: string): Promise { } // 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 { - 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) { diff --git a/worker/gemini-proxy.js b/worker/gemini-proxy.js new file mode 100644 index 0000000..25ed37f --- /dev/null +++ b/worker/gemini-proxy.js @@ -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": "*", + }, + }); + } + }, +}; diff --git a/worker/wrangler.toml b/worker/wrangler.toml new file mode 100644 index 0000000..08c93d2 --- /dev/null +++ b/worker/wrangler.toml @@ -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"