diff --git a/api/scan_recipe.php b/api/scan_recipe.php new file mode 100644 index 0000000..8ec6b67 --- /dev/null +++ b/api/scan_recipe.php @@ -0,0 +1,69 @@ + false, 'error' => 'Method not allowed']); + exit; +} + +if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) { + echo json_encode(['success' => false, 'error' => 'No image uploaded or upload error']); + exit; +} + +$imagePath = $_FILES['image']['tmp_name']; +$imageData = base64_encode(file_get_contents($imagePath)); +$imageType = $_FILES['image']['type']; + +$prompt = "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. +Extract or generate the following information in JSON format: +- name: The name of the dish/recipe +- category: One of 'Drinks', 'Breakfast', 'Dinner', 'Appetizers' +- ingredients: An array of objects, each with: + - name: Name of the ingredient + - quantity: Numeric quantity (float) + - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'piece', 'pack') +- guests: The default number of guests/servings the recipe is for (integer) + +Important: +1. 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. +2. Return ONLY the JSON object. +3. If you can't determine something, provide a best guess or null."; + +try { + $response = LocalAIApi::createResponse([ + 'input' => [ + [ + 'role' => 'user', + 'content' => [ + [ + 'type' => 'text', + 'text' => $prompt + ], + [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => "data:$imageType;base64,$imageData" + ] + ] + ] + ] + ] + ]); + + if (!$response['success']) { + throw new Exception($response['error'] ?? 'AI request failed'); + } + + $recipeData = LocalAIApi::decodeJsonFromResponse($response); + + if (!$recipeData) { + throw new Exception('Failed to parse AI response as JSON. Raw response: ' . LocalAIApi::extractText($response)); + } + + echo json_encode(['success' => true, 'data' => $recipeData]); + +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/assets/js/main.js b/assets/js/main.js index f95301e..6a1dfee 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -432,6 +432,54 @@ const app = { attachEventListeners() { app.dom.addIngredientBtn.addEventListener('click', () => app.ui.addIngredientRow()); + app.dom.aiScanBtn.addEventListener('click', async function() { + const file = app.dom.recipeImage.files[0]; + if (!file) { + alert('Please select an image first.'); + return; + } + + app.dom.aiScanBtn.disabled = true; + app.dom.aiScanLoading.classList.remove('d-none'); + + const formData = new FormData(); + formData.append('image', file); + + try { + const response = await fetch('api/scan_recipe.php', { + method: 'POST', + body: formData + }); + const result = await response.json(); + + if (result.success) { + const data = result.data; + if (data.name) app.dom.recipeNameInput.value = data.name; + if (data.category) app.dom.recipeCategoryInput.value = data.category; + if (data.guests) app.dom.guestCountInput.value = data.guests; + + if (data.ingredients && Array.isArray(data.ingredients)) { + app.dom.ingredientsContainer.innerHTML = ''; + data.ingredients.forEach(ing => { + app.ui.addIngredientRow({ + name: ing.name || '', + quantity: ing.quantity || '', + unit: ing.unit || 'g' + }); + }); + } + } else { + alert('AI Scan failed: ' + result.error); + } + } catch (error) { + console.error('Error during AI scan:', error); + alert('An error occurred during AI scanning.'); + } finally { + app.dom.aiScanBtn.disabled = false; + app.dom.aiScanLoading.classList.add('d-none'); + } + }); + app.dom.ingredientsContainer.addEventListener('click', function(e) { if (e.target.classList.contains('remove-ingredient')) { e.target.closest('.ingredient-row').remove(); @@ -759,6 +807,8 @@ const app = { recipeIdInput: document.getElementById('recipeId'), recipeCategoryInput: document.getElementById('recipeCategory'), recipeImage: document.getElementById('recipeImage'), + aiScanBtn: document.getElementById('ai-scan-btn'), + aiScanLoading: document.getElementById('ai-scan-loading'), recipeSearchInput: document.getElementById('recipe-search'), categoryFilters: document.getElementById('category-filters'), addProductBtn: document.getElementById('add-product-btn'), diff --git a/index.php b/index.php index 3b6f718..c2a3de9 100644 --- a/index.php +++ b/index.php @@ -123,8 +123,17 @@