Browse Source

feat(05-02): convert large images to URLs with cleanup

- Import uploadImageForUrl, shouldUseImageUrl, deleteImages from @/lib/images
- Process images for Replicate/fal providers before generation
- Small images (<256KB) use data URIs directly
- Large images converted to temporary URLs via /api/images/[id]
- Cleanup uploaded images in finally block (success or failure)
- Gemini path unchanged (handles base64 directly)
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
2195a6ed9c
  1. 207
      src/app/api/generate/route.ts

207
src/app/api/generate/route.ts

@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
import { GoogleGenAI } from "@google/genai";
import { GenerateRequest, GenerateResponse, ModelType, SelectedModel, ProviderType } from "@/types";
import { GenerationInput, GenerationOutput, ProviderModel } from "@/lib/providers/types";
import { uploadImageForUrl, shouldUseImageUrl, deleteImages } from "@/lib/images";
export const maxDuration = 300; // 5 minute timeout for API calls
export const dynamic = 'force-dynamic'; // Ensure this route is always dynamic
@ -567,96 +568,150 @@ export async function POST(request: NextRequest) {
);
}
// Build generation input
const genInput: GenerationInput = {
model: {
id: selectedModel!.modelId,
name: selectedModel!.displayName,
provider: "replicate",
capabilities: ["text-to-image"],
description: null,
},
prompt,
images,
parameters,
};
const result = await generateWithReplicate(requestId, replicateApiKey, genInput);
if (!result.success) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: result.error || "Generation failed",
},
{ status: 500 }
);
// Process images: convert large images to URLs for provider to fetch
const baseUrl = new URL(request.url).origin;
const uploadedImageIds: string[] = [];
const processedImages: string[] = [];
if (images && images.length > 0) {
for (const image of images) {
if (shouldUseImageUrl(image)) {
const { url, id } = uploadImageForUrl(image, baseUrl);
uploadedImageIds.push(id);
processedImages.push(url);
console.log(`[API:${requestId}] Converted large image to URL: ${url}`);
} else {
processedImages.push(image);
console.log(`[API:${requestId}] Using base64 directly (small image)`);
}
}
}
// Return first output image
const outputImage = result.outputs?.[0]?.data;
if (!outputImage) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: "No image in generation output",
try {
// Build generation input
const genInput: GenerationInput = {
model: {
id: selectedModel!.modelId,
name: selectedModel!.displayName,
provider: "replicate",
capabilities: ["text-to-image"],
description: null,
},
{ status: 500 }
);
}
prompt,
images: processedImages,
parameters,
};
const result = await generateWithReplicate(requestId, replicateApiKey, genInput);
if (!result.success) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: result.error || "Generation failed",
},
{ status: 500 }
);
}
// Return first output image
const outputImage = result.outputs?.[0]?.data;
if (!outputImage) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: "No image in generation output",
},
{ status: 500 }
);
}
return NextResponse.json<GenerateResponse>({
success: true,
image: outputImage,
});
return NextResponse.json<GenerateResponse>({
success: true,
image: outputImage,
});
} finally {
// Clean up uploaded images
if (uploadedImageIds.length > 0) {
deleteImages(uploadedImageIds);
console.log(`[API:${requestId}] Cleaned up ${uploadedImageIds.length} uploaded images`);
}
}
}
if (provider === "fal") {
// Get fal.ai API key from request headers (optional - fal.ai works without key but rate limited)
const falApiKey = request.headers.get("X-Fal-API-Key");
// Build generation input
const genInput: GenerationInput = {
model: {
id: selectedModel!.modelId,
name: selectedModel!.displayName,
provider: "fal",
capabilities: ["text-to-image"],
description: null,
},
prompt,
images,
parameters,
};
const result = await generateWithFal(requestId, falApiKey, genInput);
if (!result.success) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: result.error || "Generation failed",
},
{ status: 500 }
);
// Process images: convert large images to URLs for provider to fetch
const baseUrl = new URL(request.url).origin;
const uploadedImageIds: string[] = [];
const processedImages: string[] = [];
if (images && images.length > 0) {
for (const image of images) {
if (shouldUseImageUrl(image)) {
const { url, id } = uploadImageForUrl(image, baseUrl);
uploadedImageIds.push(id);
processedImages.push(url);
console.log(`[API:${requestId}] Converted large image to URL: ${url}`);
} else {
processedImages.push(image);
console.log(`[API:${requestId}] Using base64 directly (small image)`);
}
}
}
// Return first output image
const outputImage = result.outputs?.[0]?.data;
if (!outputImage) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: "No image in generation output",
try {
// Build generation input
const genInput: GenerationInput = {
model: {
id: selectedModel!.modelId,
name: selectedModel!.displayName,
provider: "fal",
capabilities: ["text-to-image"],
description: null,
},
{ status: 500 }
);
}
prompt,
images: processedImages,
parameters,
};
const result = await generateWithFal(requestId, falApiKey, genInput);
if (!result.success) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: result.error || "Generation failed",
},
{ status: 500 }
);
}
// Return first output image
const outputImage = result.outputs?.[0]?.data;
if (!outputImage) {
return NextResponse.json<GenerateResponse>(
{
success: false,
error: "No image in generation output",
},
{ status: 500 }
);
}
return NextResponse.json<GenerateResponse>({
success: true,
image: outputImage,
});
return NextResponse.json<GenerateResponse>({
success: true,
image: outputImage,
});
} finally {
// Clean up uploaded images
if (uploadedImageIds.length > 0) {
deleteImages(uploadedImageIds);
console.log(`[API:${requestId}] Cleaned up ${uploadedImageIds.length} uploaded images`);
}
}
}
// Default: Use Gemini

Loading…
Cancel
Save