172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
/**
|
|
* Simple image generation API that proxies to RunPod/Gemini
|
|
* Returns base64 image data directly (no file storage)
|
|
* Used by canvas-website MycroZine tool
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = await request.json();
|
|
const { prompt } = body;
|
|
|
|
if (!prompt) {
|
|
return NextResponse.json(
|
|
{ error: "Missing required field: prompt" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Generate image via RunPod proxy
|
|
const imageBase64 = await generateImageWithRunPod(prompt);
|
|
|
|
if (!imageBase64) {
|
|
return NextResponse.json(
|
|
{ error: "Image generation failed" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{
|
|
success: true,
|
|
imageData: imageBase64,
|
|
mimeType: "image/png",
|
|
},
|
|
{
|
|
headers: {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
},
|
|
}
|
|
);
|
|
} catch (error) {
|
|
console.error("Image generation error:", error);
|
|
return NextResponse.json(
|
|
{ error: error instanceof Error ? error.message : "Failed to generate image" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
async function generateImageWithRunPod(prompt: string): Promise<string | null> {
|
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
const runpodApiKey = process.env.RUNPOD_API_KEY;
|
|
const runpodEndpointId = process.env.RUNPOD_GEMINI_ENDPOINT_ID || "ntqjz8cdsth42i";
|
|
|
|
if (!apiKey) {
|
|
console.error("GEMINI_API_KEY not configured");
|
|
return null;
|
|
}
|
|
|
|
if (!runpodApiKey) {
|
|
console.error("RUNPOD_API_KEY not configured, trying direct API");
|
|
return generateDirectGeminiImage(prompt, apiKey);
|
|
}
|
|
|
|
const runpodUrl = `https://api.runpod.ai/v2/${runpodEndpointId}/runsync`;
|
|
|
|
try {
|
|
const response = await fetch(runpodUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${runpodApiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
input: {
|
|
api_key: apiKey,
|
|
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("RunPod API error:", response.status, errorText);
|
|
return null;
|
|
}
|
|
|
|
const result = await response.json();
|
|
const data = result.output || result;
|
|
|
|
if (data.error) {
|
|
console.error("Gemini API error via RunPod:", data.error);
|
|
return null;
|
|
}
|
|
|
|
// Extract image from response
|
|
const parts = data.candidates?.[0]?.content?.parts || [];
|
|
for (const part of parts) {
|
|
if (part.inlineData?.mimeType?.startsWith("image/")) {
|
|
console.log("Generated image via RunPod proxy");
|
|
return part.inlineData.data;
|
|
}
|
|
}
|
|
|
|
console.error("No image in response");
|
|
return null;
|
|
} catch (error) {
|
|
console.error("RunPod request error:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function generateDirectGeminiImage(prompt: string, apiKey: string): Promise<string | null> {
|
|
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`;
|
|
|
|
try {
|
|
const response = await fetch(geminiUrl, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
contents: [{ parts: [{ text: `Generate an image: ${prompt}` }] }],
|
|
generationConfig: { responseModalities: ["TEXT", "IMAGE"] },
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error("Direct Gemini API error:", response.status);
|
|
return null;
|
|
}
|
|
|
|
const data = await response.json();
|
|
const parts = data.candidates?.[0]?.content?.parts || [];
|
|
for (const part of parts) {
|
|
if (part.inlineData?.mimeType?.startsWith("image/")) {
|
|
return part.inlineData.data;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error("Direct Gemini error:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Allow CORS for canvas-website
|
|
export async function OPTIONS() {
|
|
return new NextResponse(null, {
|
|
status: 200,
|
|
headers: {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
},
|
|
});
|
|
}
|