From 096fe649f22347b2e0f6afbf6437e48eea6a0229 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 27 Jan 2026 07:34:48 +0000 Subject: [PATCH] Autosave: 20260127-073448 --- ai/CustomOpenAI.php | 109 ++++++++++++++++++++++++ ai/keys.php | 11 +++ api/ai_analyze.php | 104 +++++++++++++++++++++++ api/login.php | 4 +- api/register.php | 2 +- api/scan_debug.log | 24 ++++++ api/scan_recipe.php | 43 ++++++++++ index.php | 3 + test.php | 198 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 495 insertions(+), 3 deletions(-) create mode 100644 ai/CustomOpenAI.php create mode 100644 ai/keys.php create mode 100644 api/ai_analyze.php create mode 100644 test.php diff --git a/ai/CustomOpenAI.php b/ai/CustomOpenAI.php new file mode 100644 index 0000000..bd0942e --- /dev/null +++ b/ai/CustomOpenAI.php @@ -0,0 +1,109 @@ +apiKey = $apiKey; + } + + /** + * Analyze an image (base64 or URL) with a prompt. + */ + public function analyze(array $params): array { + $model = $params['model'] ?? 'gpt-4o'; + $prompt = $params['prompt'] ?? 'Analyze this image'; + $content = [['type' => 'text', 'text' => $prompt]]; + + if (!empty($params['image_base64'])) { + $type = $params['image_type'] ?? 'image/jpeg'; + $content[] = [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => "data:$type;base64," . $params['image_base64'] + ] + ]; + } elseif (!empty($params['image_url'])) { + $content[] = [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => $params['image_url'] + ] + ]; + } + + $payload = [ + 'model' => $model, + 'messages' => [ + [ + 'role' => 'user', + 'content' => $content + ] + ], + 'max_tokens' => 2000 + ]; + + return $this->request($payload); + } + + private function request(array $payload): array { + $ch = curl_init($this->baseUrl); + $jsonPayload = json_encode($payload); + + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $jsonPayload, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $this->apiKey + ], + CURLOPT_TIMEOUT => 60 + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + if ($response === false) { + return [ + 'success' => false, + 'error' => 'cURL Error: ' . $curlError + ]; + } + + $decoded = json_decode($response, true); + if ($httpCode !== 200) { + return [ + 'success' => false, + 'error' => $decoded['error']['message'] ?? 'OpenAI API Error (Status: ' . $httpCode . ')', + 'raw' => $decoded + ]; + } + + return [ + 'success' => true, + 'data' => $decoded + ]; + } + + public static function extractText(array $response): string { + return $response['data']['choices'][0]['message']['content'] ?? ''; + } + + public static function decodeJson(array $response): ?array { + $text = self::extractText($response); + if (empty($text)) return null; + + $decoded = json_decode($text, true); + if (is_array($decoded)) return $decoded; + + // Try stripping markdown fences + $stripped = preg_replace('/^```json|```$/m', '', trim($text)); + return json_decode($stripped, true); + } +} diff --git a/ai/keys.php b/ai/keys.php new file mode 100644 index 0000000..9d4b61e --- /dev/null +++ b/ai/keys.php @@ -0,0 +1,11 @@ + false, 'error' => 'Method not allowed']); + exit; +} + +$prompt = $_POST['prompt'] ?? "Analyze this image and describe what you see."; +$model = $_POST['model'] ?? 'gpt-4o'; +$imageUrl = $_POST['image_url'] ?? null; +$apiKey = defined('OPENAI_API_KEY') ? OPENAI_API_KEY : ''; + +// Prefer CustomOpenAI if key is available +$useCustom = !empty($apiKey); + +if ($useCustom) { + $ai = new CustomOpenAI($apiKey); + $params = [ + 'model' => $model, + 'prompt' => $prompt + ]; + + if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { + $params['image_base64'] = base64_encode(file_get_contents($_FILES['image']['tmp_name'])); + $params['image_type'] = $_FILES['image']['type']; + } elseif (!empty($imageUrl)) { + $params['image_url'] = $imageUrl; + } + + $response = $ai->analyze($params); + + if ($response['success']) { + echo json_encode([ + 'success' => true, + 'text' => CustomOpenAI::extractText($response), + 'data' => CustomOpenAI::decodeJson($response), + 'source' => 'CustomOpenAI' + ]); + } else { + echo json_encode(['success' => false, 'error' => $response['error']]); + } + exit; +} + +// Fallback to LocalAIApi (original logic) +$content = [['type' => 'text', 'text' => $prompt]]; + +// Handle file upload +if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { + $imagePath = $_FILES['image']['tmp_name']; + $imageData = base64_encode(file_get_contents($imagePath)); + $imageType = $_FILES['image']['type']; + $content[] = [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => "data:$imageType;base64,$imageData", + 'detail' => 'auto' + ] + ]; +} elseif (!empty($imageUrl)) { + // Handle image URL + $content[] = [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => $imageUrl, + 'detail' => 'auto' + ] + ]; +} else { + // If no image, it's just a text prompt (optional but allowed) +} + +try { + $response = LocalAIApi::createResponse([ + 'model' => $model, + 'input' => [ + [ + 'role' => 'user', + 'content' => $content + ] + ] + ]); + + if (!$response['success']) { + throw new Exception($response['error'] ?? 'AI request failed'); + } + + $text = LocalAIApi::extractText($response); + $json = LocalAIApi::decodeJsonFromResponse($response); + + echo json_encode([ + 'success' => true, + 'text' => $text, + 'data' => $json, + 'raw' => $response + ]); + +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/api/login.php b/api/login.php index 03ac5d5..30aa37d 100644 --- a/api/login.php +++ b/api/login.php @@ -12,11 +12,11 @@ if ($email === '' || $password === '') { } try { - $stmt = db()->prepare("SELECT id, email, password FROM users WHERE email = ?"); + $stmt = db()->prepare("SELECT id, email, password_hash FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(PDO::FETCH_ASSOC); - if ($user && password_verify($password, $user['password'])) { + if ($user && password_verify($password, $user['password_hash'])) { $_SESSION['user_id'] = $user['id']; $_SESSION['email'] = $user['email']; echo json_encode(['success' => true]); diff --git a/api/register.php b/api/register.php index 3379991..b8256aa 100644 --- a/api/register.php +++ b/api/register.php @@ -19,7 +19,7 @@ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $hashedPassword = password_hash($password, PASSWORD_DEFAULT); try { - $stmt = db()->prepare("INSERT INTO users (email, password) VALUES (?, ?)"); + $stmt = db()->prepare("INSERT INTO users (email, password_hash) VALUES (?, ?)"); if ($stmt->execute([$email, $hashedPassword])) { $_SESSION['user_id'] = db()->lastInsertId(); $_SESSION['email'] = $email; diff --git a/api/scan_debug.log b/api/scan_debug.log index d0d6b77..c525f06 100644 --- a/api/scan_debug.log +++ b/api/scan_debug.log @@ -18,3 +18,27 @@ 2026-01-26 20:37:58 AI Request to https://flatlogic.com/projects/35604/ai-request: {"model":"gpt-4o","input":[{"role":"user","content":[{"type":"text","text":"Analyze this image. If it's a photo of a dish, identify what it is and generate a possible recipe for it. If it's a photo of a written recipe, extract the information. \nExtract or generate the following information in JSON format:\n- name: The name of the dish\/recipe\n- category: One of 'Drinks', 'Breakfast', 'Dinner', 'Appetizers'\n- ingredients: An array of objects, each with:\n - name: Name of the ingredient\n - quantity: Numeric quantity (float)\n - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'piece', 'pack')\n- guests: The default number of guests\/servings the recipe is for (integer)\n\nImportant: \n1. The quantities should be for 1 person if possible, or for the number of guests specified. If you're generating a recipe for a dish, default to 1 or 2 guests.\n2. Return ONLY the JSON object.\n3. If you can't determine something, provide a best guess or null."},{"type":"image_url","imag... [TRUNCATED] 2026-01-26 20:37:58 fetchStatus(1152) status: 200, body: {"status":"failed","error":"the server responded with status 400"} 2026-01-26 20:37:58 AI Scan Error: failed +2026-01-26 22:39:24 Processing image: type=image/jpeg, size=216567 bytes +2026-01-26 22:39:24 Data URL prefix: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD... +2026-01-26 22:39:24 AI Request to https://flatlogic.com/projects/35604/ai-request: {"model":"gpt-4o","input":[{"role":"user","content":[{"type":"text","text":"Analyze this image. If it's a photo of a dish, identify what it is and create a possible recipe for it. If it's a photo of a written recipe, extract the information from it.\nProvide the information in JSON format IN ENGLISH:\n- name: Dish\/recipe name\n- category: One of the following values: 'Drinks', 'Breakfast', 'Dinner', 'Appetizers'\n- ingredients: Array of objects, each containing:\n - name: Ingredient name\n - quantity: Numeric quantity (float)\n - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'pcs', 'pack')\n- guests: Default number of guests\/portions (integer)\n\nImportant:\n1. Quantities should be specified per 1 person if possible. If you are creating a recipe for a dish, default to 1 or 2 guests.\n2. Return ONLY the JSON object.\n3. If something cannot be determined, make the most accurate assumption or specify null."},{"type":"image_url","image_url":{"url":"data:image\/jpeg;base... [TRUNCATED] +2026-01-26 22:39:30 fetchStatus(1153) status: 200, body: {"status":"pending"} +2026-01-26 22:39:35 fetchStatus(1153) status: 200, body: {"status":"failed","error":"the server responded with status 400"} +2026-01-26 22:39:35 AI Scan Error: failed +2026-01-26 22:39:43 Processing image: type=image/jpeg, size=85908 bytes +2026-01-26 22:39:43 Data URL prefix: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD... +2026-01-26 22:39:43 AI Request to https://flatlogic.com/projects/35604/ai-request: {"model":"gpt-4o","input":[{"role":"user","content":[{"type":"text","text":"Analyze this image. If it's a photo of a dish, identify what it is and create a possible recipe for it. If it's a photo of a written recipe, extract the information from it.\nProvide the information in JSON format IN ENGLISH:\n- name: Dish\/recipe name\n- category: One of the following values: 'Drinks', 'Breakfast', 'Dinner', 'Appetizers'\n- ingredients: Array of objects, each containing:\n - name: Ingredient name\n - quantity: Numeric quantity (float)\n - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'pcs', 'pack')\n- guests: Default number of guests\/portions (integer)\n\nImportant:\n1. Quantities should be specified per 1 person if possible. If you are creating a recipe for a dish, default to 1 or 2 guests.\n2. Return ONLY the JSON object.\n3. If something cannot be determined, make the most accurate assumption or specify null."},{"type":"image_url","image_url":{"url":"data:image\/jpeg;base... [TRUNCATED] +2026-01-26 22:39:44 fetchStatus(1154) status: 200, body: {"status":"failed","error":"the server responded with status 400"} +2026-01-26 22:39:44 AI Scan Error: failed +2026-01-27 06:46:26 Processing image: type=image/jpeg, size=268319 bytes +2026-01-27 06:46:26 Data URL prefix: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD... +2026-01-27 06:46:26 AI Request to https://flatlogic.com/projects/35604/ai-request: {"model":"gpt-4o","input":[{"role":"user","content":[{"type":"text","text":"Analyze this image. If it's a photo of a dish, identify what it is and create a possible recipe for it. If it's a photo of a written recipe, extract the information from it.\nProvide the information in JSON format IN ENGLISH:\n- name: Dish\/recipe name\n- category: One of the following values: 'Drinks', 'Breakfast', 'Dinner', 'Appetizers'\n- ingredients: Array of objects, each containing:\n - name: Ingredient name\n - quantity: Numeric quantity (float)\n - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'pcs', 'pack')\n- guests: Default number of guests\/portions (integer)\n\nImportant:\n1. Quantities should be specified per 1 person if possible. If you are creating a recipe for a dish, default to 1 or 2 guests.\n2. Return ONLY the JSON object.\n3. If something cannot be determined, make the most accurate assumption or specify null."},{"type":"image_url","image_url":{"url":"data:image\/jpeg;base... [TRUNCATED] +2026-01-27 06:46:31 fetchStatus(1156) status: 200, body: {"status":"pending"} +2026-01-27 06:46:36 fetchStatus(1156) status: 200, body: {"status":"failed","error":"the server responded with status 400"} +2026-01-27 06:46:36 AI Scan Error: failed +2026-01-27 06:46:44 Processing image: type=image/jpeg, size=85908 bytes +2026-01-27 06:46:44 Data URL prefix: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD... +2026-01-27 06:46:44 AI Request to https://flatlogic.com/projects/35604/ai-request: {"model":"gpt-4o","input":[{"role":"user","content":[{"type":"text","text":"Analyze this image. If it's a photo of a dish, identify what it is and create a possible recipe for it. If it's a photo of a written recipe, extract the information from it.\nProvide the information in JSON format IN ENGLISH:\n- name: Dish\/recipe name\n- category: One of the following values: 'Drinks', 'Breakfast', 'Dinner', 'Appetizers'\n- ingredients: Array of objects, each containing:\n - name: Ingredient name\n - quantity: Numeric quantity (float)\n - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'pcs', 'pack')\n- guests: Default number of guests\/portions (integer)\n\nImportant:\n1. Quantities should be specified per 1 person if possible. If you are creating a recipe for a dish, default to 1 or 2 guests.\n2. Return ONLY the JSON object.\n3. If something cannot be determined, make the most accurate assumption or specify null."},{"type":"image_url","image_url":{"url":"data:image\/jpeg;base... [TRUNCATED] +2026-01-27 06:46:44 fetchStatus(1157) status: 200, body: {"status":"failed","error":"the server responded with status 400"} +2026-01-27 06:46:44 AI Scan Error: failed +2026-01-27 07:32:16 AI Request to https://flatlogic.com/projects/35604/ai-request: {"model":"gpt-4o-mini","input":[{"role":"user","content":[{"type":"text","text":"Analyze this image. If it's a dish, identify it and provide a recipe in JSON format: { \"name\": \"...\", \"ingredients\": [...] }"},{"type":"image_url","image_url":{"url":"data:image\/webp;base64,UklGRhK1AABXRUJQVlA4IAa1AACQPAKdASqmAmoBPlUmj0UjoiEnqPUcWPAKiU3REdmQXFpa77txeh2tz7hpr\/5fpE\/lc4FZu\/fuG6DyWeRe2f5\/95\/z\/rJ7F+xPLl6n80f\/J9bn639ij9jP2o7FP\/B9O3+w8+jpLOrr6E\/\/w+0\/+7+T6zFvNv5fvP8onx799\/0n\/l9j\/HX7F\/deY38+\/NX9f1mf2Xfr809RT81\/rH\/N9O2Dl2i\/V9CP4T\/I+aH+h\/8PT39z\/2v\/q9w3+pf4D\/y+vv\/K8Yf83\/vP22+Av+s\/4H\/z\/578vPpw\/xv\/v\/wv+F60fqv\/7f7X4Hf6P\/hP2W9qn\/+\/7\/4KfsV\/9fdZ\/Y\/\/4HN0vygqkj13cTU+ufy8eCpP9bLemXYHORiiXbAfOOUjOOt\/bNCTJJeEfioXA2s+09q8pmOOZ0vSOwDYAYRyxZLm0kZggHXZB0cl9wG2NnSlwukM2uBXyLDz8EMveC0gcDHesG0tnbRdaBNSdaO1KrwZIoYghbkS+EeYHyvAFEnLzpqrVEkKEjjNxoPKflcuw\/lnU\/iyoGk5wNjUHc44c3OSVdBFfbmdbCrQ\/hee3pLddPPa95GuBysCi\/F28ZJC5Me4MXYhZu8F2iun96szFlLSMZW12npp54onZzx\/rM4ZKl40obwCMX7... [TRUNCATED] +2026-01-27 07:32:17 fetchStatus(1158) status: 200, body: {"status":"failed","error":"the server responded with status 400"} diff --git a/api/scan_recipe.php b/api/scan_recipe.php index fdf32d6..37ef9ca 100644 --- a/api/scan_recipe.php +++ b/api/scan_recipe.php @@ -1,6 +1,8 @@ false, 'error' => 'Method not allowed']); @@ -16,7 +18,48 @@ $imagePath = $_FILES['image']['tmp_name']; $imageSize = filesize($imagePath); $imageData = base64_encode(file_get_contents($imagePath)); $imageType = $_FILES['image']['type']; +$apiKey = defined('OPENAI_API_KEY') ? OPENAI_API_KEY : ''; +$prompt = "Analyze this image. If it's a photo of a dish, identify what it is and create a possible recipe for it. If it's a photo of a written recipe, extract the information from it. +Provide the information in JSON format IN ENGLISH: +- name: Dish/recipe name +- category: One of the following values: 'Drinks', 'Breakfast', 'Dinner', 'Appetizers' +- ingredients: Array of objects, each containing: + - name: Ingredient name + - quantity: Numeric quantity (float) + - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'pcs', 'pack') +- guests: Default number of guests/portions (integer) + +Important: +1. Quantities should be specified per 1 person if possible. If you are creating a recipe for a dish, default to 1 or 2 guests. +2. Return ONLY the JSON object. +3. If something cannot be determined, make the most accurate assumption or specify null."; + +if (!empty($apiKey)) { + $ai = new CustomOpenAI($apiKey); + $response = $ai->analyze([ + 'model' => 'gpt-4o', + 'prompt' => $prompt, + 'image_base64' => $imageData, + 'image_type' => $imageType + ]); + + if (!$response['success']) { + echo json_encode(['success' => false, 'error' => $response['error']]); + exit; + } + + $recipeData = CustomOpenAI::decodeJson($response); + if (!$recipeData) { + echo json_encode(['success' => false, 'error' => 'Failed to parse AI response as JSON.']); + exit; + } + + echo json_encode(['success' => true, 'data' => $recipeData, 'source' => 'CustomOpenAI']); + exit; +} + +// Fallback to LocalAIApi file_put_contents(__DIR__ . '/scan_debug.log', date('Y-m-d H:i:s') . " Processing image: type=$imageType, size=$imageSize bytes" . PHP_EOL, FILE_APPEND); file_put_contents(__DIR__ . '/scan_debug.log', date('Y-m-d H:i:s') . " Data URL prefix: " . substr("data:$imageType;base64,$imageData", 0, 50) . "..." . PHP_EOL, FILE_APPEND); diff --git a/index.php b/index.php index de8827a..74cafba 100644 --- a/index.php +++ b/index.php @@ -368,6 +368,9 @@ diff --git a/test.php b/test.php new file mode 100644 index 0000000..3bceb10 --- /dev/null +++ b/test.php @@ -0,0 +1,198 @@ + + + + + + Test Playground — FoodieFlow + + + + + + + + + + + + + + +
+
+
+
+ +
+ Attention: To use image analysis, you must set your OpenAI API Key in ai/keys.php. + Direct communication with OpenAI is enabled to support multimodal features. +
+
+
+
+ +
+
+

Test Playground

+

A place to test new features before they go live.

+
+
+ +
+ +
+
+
+
+ + Advanced AI Analyzer +
+ +
+ + +
+
+ +
+
+ +
+
+
+ +
+ + +
+ +
+ + +
+ + + +
+
Analysis Result:
+
+ +
+
+
+ +

+                            
+
+
+
+
+
+
+ + + + + + +