true, 'user' => [ 'name' => 'User', 'preferred_username' => 'anonymous@nano-banana-pro.com' ] ]; // Check authentication (with graceful fallback) try { if (file_exists(__DIR__ . '/AuthMiddleware.php')) { require_once 'AuthMiddleware.php'; $auth = new AuthMiddleware(); $authStatus = $auth->isAuthenticated(); if (!$authStatus['authenticated']) { http_response_code(401); echo json_encode([ 'success' => false, 'error' => 'Authentication required', 'requiresAuth' => true ]); exit; } } } catch (Exception $e) { // Log error but continue without auth (for testing) error_log("Auth check failed in api.php: " . $e->getMessage()); } class NanoBananaProAPI { private $apiKey; private $baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models'; private $model = 'gemini-3-pro-image-preview'; public function __construct($apiKey) { $this->apiKey = $apiKey; } public function generateImage($prompt, $aspectRatio = '16:9', $imageSize = '2K', $inputImage = null, $referenceImages = []) { $parts = []; // Add reference images first (up to 14 allowed by Gemini 3 Pro) if (!empty($referenceImages)) { error_log("Adding " . count($referenceImages) . " reference image(s) to request"); foreach ($referenceImages as $index => $refImg) { $data = preg_replace('/\s+/', '', $refImg['data']); // Basic validation if (!preg_match('/^[A-Za-z0-9+\/]+={0,2}$/', $data)) { error_log("Base64 validation failed for reference image $index"); continue; } $parts[] = [ 'inline_data' => [ 'mime_type' => $refImg['mime_type'] ?? 'image/jpeg', 'data' => $data ] ]; error_log("Added reference image $index (mime_type: " . ($refImg['mime_type'] ?? 'image/jpeg') . ")"); } } // If there's an input image (for editing existing), add it if ($inputImage) { error_log("Edit mode: Input image size = " . strlen($inputImage) . " chars"); // Clean any whitespace from base64 data $inputImage = preg_replace('/\s+/', '', $inputImage); // Basic validation - check if it looks like base64 if (!preg_match('/^[A-Za-z0-9+\/]+={0,2}$/', $inputImage)) { error_log("Base64 validation failed for input image"); error_log("First 100 chars: " . substr($inputImage, 0, 100)); throw new Exception("Invalid image data format - not valid base64"); } $parts[] = [ 'inline_data' => [ 'mime_type' => 'image/jpeg', // Use jpeg to match API output 'data' => $inputImage ] ]; error_log("Added input image to request (mime_type: image/jpeg)"); } else if (empty($referenceImages)) { error_log("Generation mode: No input image or reference images"); } // Add the text prompt $parts[] = ['text' => $prompt]; $payload = [ 'contents' => [ ['parts' => $parts] ], 'generationConfig' => [ 'responseModalities' => ['IMAGE'], 'imageConfig' => [ 'aspectRatio' => $aspectRatio, 'imageSize' => $imageSize ] ] ]; return $this->makeRequest($payload); } private function makeRequest($payload, $retryCount = 0) { $url = "{$this->baseUrl}/{$this->model}:generateContent"; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-goog-api-key: ' . $this->apiKey ], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_TIMEOUT => 120 // 2 minute timeout for image generation ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_errno($ch)) { $error = curl_error($ch); @curl_close($ch); throw new Exception('cURL error: ' . $error); } @curl_close($ch); if ($httpCode !== 200) { $errorData = json_decode($response, true); $errorMessage = $errorData['error']['message'] ?? "HTTP $httpCode"; $errorStatus = $errorData['error']['status'] ?? 'UNKNOWN'; // Log full error details for debugging error_log("API Error - HTTP $httpCode (Status: $errorStatus)"); error_log("Error message: " . $errorMessage); error_log("Full response: " . $response); // Handle specific error types if ($httpCode === 500 && stripos($errorMessage, 'internal') !== false && $retryCount < 2) { // Retry on internal errors (up to 2 times) error_log("Retrying request due to internal error (attempt " . ($retryCount + 1) . ")"); sleep(2); // Wait 2 seconds before retry return $this->makeRequest($payload, $retryCount + 1); } if ($httpCode === 429 || $errorStatus === 'RESOURCE_EXHAUSTED') { throw new Exception("API rate limit exceeded. Please wait a moment and try again."); } if ($errorStatus === 'INVALID_ARGUMENT') { throw new Exception("Invalid request format. This might be due to corrupted image data. Try clicking 'Start New Image' and generating fresh."); } throw new Exception("API error: $errorMessage (HTTP $httpCode, Status: $errorStatus)"); } return json_decode($response, true); } public function extractImageData($response) { // Log the response for debugging error_log("API Response: " . json_encode($response)); // Check for finish reasons that indicate content issues if (isset($response['candidates'][0]['finishReason'])) { $finishReason = $response['candidates'][0]['finishReason']; $finishMessage = $response['candidates'][0]['finishMessage'] ?? ''; if ($finishReason === 'IMAGE_RECITATION') { throw new Exception('Image generation blocked by content filter. Try a more creative and descriptive prompt. Avoid simple geometric shapes or common objects. Example: "A futuristic cityscape at sunset with flying cars" instead of "a red circle".'); } if ($finishReason === 'SAFETY') { throw new Exception('Image generation blocked by safety filters. Please try a different prompt.'); } if ($finishReason !== 'STOP' && !empty($finishMessage)) { throw new Exception('Image generation failed: ' . $finishMessage); } } if (isset($response['candidates'][0]['content']['parts'])) { foreach ($response['candidates'][0]['content']['parts'] as $part) { if (isset($part['inline_data']['data'])) { return [ 'base64' => $part['inline_data']['data'], 'mime_type' => $part['inline_data']['mime_type'] ?? 'image/png' ]; } // Check for inlineData (alternative format) if (isset($part['inlineData']['data'])) { return [ 'base64' => $part['inlineData']['data'], 'mime_type' => $part['inlineData']['mimeType'] ?? 'image/png' ]; } } } // Provide detailed error with response structure $errorDetails = "Response structure: " . json_encode(array_keys($response)); if (isset($response['candidates'][0])) { $errorDetails .= " | Candidate keys: " . json_encode(array_keys($response['candidates'][0])); } throw new Exception('No image data found in API response. ' . $errorDetails); } } // Handle API requests try { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { throw new Exception('Invalid request method'); } $action = $_POST['action'] ?? null; if (!$action) { throw new Exception('No action specified'); } // Handle reset action if ($action === 'reset') { $sessionManager->reset(); echo json_encode(['success' => true]); exit; } // Handle generate action if ($action === 'generate') { $prompt = $_POST['prompt'] ?? null; $aspectRatio = $_POST['aspectRatio'] ?? '16:9'; $imageSize = $_POST['imageSize'] ?? '2K'; $uploadedImage = $_POST['uploadedImage'] ?? null; $uploadedImageType = $_POST['uploadedImageType'] ?? null; // Collect reference images (up to 14) $referenceImages = []; $refCount = intval($_POST['referenceImageCount'] ?? 0); for ($i = 0; $i < min($refCount, 14); $i++) { if (isset($_POST["referenceImage_$i"])) { $referenceImages[] = [ 'data' => $_POST["referenceImage_$i"], 'mime_type' => $_POST["referenceImageType_$i"] ?? 'image/jpeg' ]; } } if (!empty($referenceImages)) { error_log("Received " . count($referenceImages) . " reference images from frontend"); } // Check if API key is configured if (!defined('GEMINI_API_KEY') || empty(GEMINI_API_KEY)) { throw new Exception('API key not configured. Please set GEMINI_API_KEY in config.php'); } // Handle uploaded image $currentImage = $sessionManager->getCurrentImage(); if ($uploadedImage && !$currentImage) { error_log("Processing uploaded image (type: $uploadedImageType)"); // If there's a prompt, apply it to the uploaded image if ($prompt) { error_log("Applying prompt to uploaded image: $prompt"); // Initialize API $api = new NanoBananaProAPI(GEMINI_API_KEY); // Generate/edit image with the uploaded image as input $response = $api->generateImage($prompt, $aspectRatio, $imageSize, $uploadedImage); $imageData = $api->extractImageData($response); // Save edited image to disk $filename = $sessionManager->saveImage($imageData['base64'], $imageData['mime_type']); $sessionManager->setCurrentImage($filename, $imageData['mime_type']); // Add to conversation history $sessionManager->addToHistory('Uploaded image + ' . $prompt, 'upload_edit'); } else { // Just uploaded, no prompt yet - save to disk $filename = $sessionManager->saveImage($uploadedImage, $uploadedImageType); $sessionManager->setCurrentImage($filename, $uploadedImageType); // Add to conversation history $sessionManager->addToHistory('Image uploaded', 'upload'); } echo json_encode([ 'success' => true, 'message' => $prompt ? 'Image uploaded and edited successfully' : 'Image uploaded successfully' ]); exit; } // Regular generation/editing flow if (!$prompt) { throw new Exception('Prompt is required'); } // Initialize API $api = new NanoBananaProAPI(GEMINI_API_KEY); // Get current image if editing $currentImage = $sessionManager->getCurrentImage(); $inputImage = $currentImage ? $currentImage['data'] : null; // Generate or edit image (with optional reference images) $response = $api->generateImage($prompt, $aspectRatio, $imageSize, $inputImage, $referenceImages); $imageData = $api->extractImageData($response); // Save to disk $filename = $sessionManager->saveImage($imageData['base64'], $imageData['mime_type']); $sessionManager->setCurrentImage($filename, $imageData['mime_type']); // Add to conversation history $sessionManager->addToHistory($prompt, $inputImage ? 'edit' : 'generate'); // Log to webhook try { $userEmail = $authStatus['user']['preferred_username'] ?? $authStatus['user']['email'] ?? 'anonymous@nano-banana-pro.com'; $webhookSettings = [ 'prompt' => $prompt, 'aspectRatio' => $aspectRatio, 'imageSize' => $imageSize, 'actionType' => $inputImage ? 'edit' : 'generate', 'model' => 'Google Imagen 3' ]; logImageGeneration($prompt, $imageData['base64'], $imageData['mime_type'], $webhookSettings, $userEmail, $inputImage ? 'edit' : 'generate'); } catch (Exception $e) { // Don't fail if webhook fails error_log("Webhook logging failed: " . $e->getMessage()); } echo json_encode([ 'success' => true, 'message' => 'Image generated successfully' ]); exit; } throw new Exception('Invalid action'); } catch (Exception $e) { http_response_code(500); // Log detailed error info error_log("Exception in api.php: " . $e->getMessage()); error_log("Stack trace: " . $e->getTraceAsString()); echo json_encode([ 'success' => false, 'error' => $e->getMessage(), 'debug' => [ 'file' => basename($e->getFile()), 'line' => $e->getLine(), 'timestamp' => date('Y-m-d H:i:s') ] ]); exit; }