Autosave: 20260126-195712

This commit is contained in:
Flatlogic Bot 2026-01-26 19:57:12 +00:00
parent 4ea0efca37
commit 05f4c9c979
3 changed files with 129 additions and 1 deletions

69
api/scan_recipe.php Normal file
View File

@ -0,0 +1,69 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../ai/LocalAIApi.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => 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()]);
}

View File

@ -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'),

View File

@ -123,8 +123,17 @@
</select>
</div>
<div class="mb-3">
<label for="recipeImage" class="form-label">Recipe Image</label>
<label for="recipeImage" class="form-label d-flex justify-content-between">
Recipe Image
<button type="button" id="ai-scan-btn" class="btn btn-outline-primary btn-sm">
<i class="bi bi-magic"></i> AI Scan
</button>
</label>
<input type="file" class="form-control" id="recipeImage">
<div id="ai-scan-loading" class="text-primary mt-2 d-none">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
AI is identifying the dish and recipe...
</div>
</div>
<hr class="my-4 border-secondary">