Autosave: 20260127-073448

This commit is contained in:
Flatlogic Bot 2026-01-27 07:34:48 +00:00
parent 1e39b8f68a
commit 096fe649f2
9 changed files with 495 additions and 3 deletions

109
ai/CustomOpenAI.php Normal file
View File

@ -0,0 +1,109 @@
<?php
/**
* CustomOpenAI - Direct client for OpenAI API to support multimodal features (Vision).
*/
class CustomOpenAI {
private string $apiKey;
private string $baseUrl = 'https://api.openai.com/v1/chat/completions';
public function __construct(string $apiKey) {
$this->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);
}
}

11
ai/keys.php Normal file
View File

@ -0,0 +1,11 @@
<?php
/**
* ai/keys.php
* Store your private API keys here.
*/
// Define your OpenAI API Key here
define('OPENAI_API_KEY', ''); // <--- PUT YOUR KEY HERE
// You can also use environment variables if you prefer
// define('OPENAI_API_KEY', getenv('OPENAI_API_KEY') ?: '');

104
api/ai_analyze.php Normal file
View File

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

View File

@ -12,11 +12,11 @@ if ($email === '' || $password === '') {
} }
try { 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]); $stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC); $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['user_id'] = $user['id'];
$_SESSION['email'] = $user['email']; $_SESSION['email'] = $user['email'];
echo json_encode(['success' => true]); echo json_encode(['success' => true]);

View File

@ -19,7 +19,7 @@ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
try { 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])) { if ($stmt->execute([$email, $hashedPassword])) {
$_SESSION['user_id'] = db()->lastInsertId(); $_SESSION['user_id'] = db()->lastInsertId();
$_SESSION['email'] = $email; $_SESSION['email'] = $email;

View File

@ -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 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 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 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"}

View File

@ -1,6 +1,8 @@
<?php <?php
header('Content-Type: application/json'); header('Content-Type: application/json');
require_once __DIR__ . '/../ai/LocalAIApi.php'; require_once __DIR__ . '/../ai/LocalAIApi.php';
require_once __DIR__ . '/../ai/CustomOpenAI.php';
@include_once __DIR__ . '/../ai/keys.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'error' => 'Method not allowed']); echo json_encode(['success' => false, 'error' => 'Method not allowed']);
@ -16,7 +18,48 @@ $imagePath = $_FILES['image']['tmp_name'];
$imageSize = filesize($imagePath); $imageSize = filesize($imagePath);
$imageData = base64_encode(file_get_contents($imagePath)); $imageData = base64_encode(file_get_contents($imagePath));
$imageType = $_FILES['image']['type']; $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') . " 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); 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);

View File

@ -368,6 +368,9 @@
<footer class="text-center py-4 mt-5"> <footer class="text-center py-4 mt-5">
<p class="mb-0">&copy; <?php echo date("Y"); ?> FoodieFlow — An AI-powered flow from recipe to grocery — every day, every occasion.</p> <p class="mb-0">&copy; <?php echo date("Y"); ?> FoodieFlow — An AI-powered flow from recipe to grocery — every day, every occasion.</p>
<div class="mt-2">
<a href="test.php" class="text-muted text-decoration-none small"><i class="bi bi-bug"></i> Test Playground</a>
</div>
</footer> </footer>
<!-- Scripts --> <!-- Scripts -->

198
test.php Normal file
View File

@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Playground FoodieFlow</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style>
.test-card {
transition: transform 0.2s;
cursor: pointer;
}
.test-card:hover {
transform: translateY(-5px);
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top mb-4">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/">
<i class="bi bi-flow me-2" style="color: var(--brand-primary);"></i>
<span class="fw-bold">FoodieFlow Test Lab</span>
</a>
<div class="ms-auto">
<a href="/" class="btn btn-outline-primary btn-sm">Back to Home</a>
</div>
</div>
</nav>
<main class="container py-5">
<div class="row mb-4">
<div class="col-md-8 mx-auto">
<div class="alert alert-warning border-0 shadow-sm d-flex align-items-center">
<i class="bi bi-exclamation-triangle-fill fs-4 me-3"></i>
<div>
<strong>Attention:</strong> To use image analysis, you must set your OpenAI API Key in <code>ai/keys.php</code>.
Direct communication with OpenAI is enabled to support multimodal features.
</div>
</div>
</div>
</div>
<div class="row mb-5">
<div class="col-12 text-center">
<h1 class="display-4 fw-bold">Test Playground</h1>
<p class="lead text-muted">A place to test new features before they go live.</p>
</div>
</div>
<div class="row g-4">
<!-- AI Testing Section -->
<div class="col-md-8 mx-auto">
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title d-flex align-items-center mb-4">
<i class="bi bi-magic me-2 text-primary"></i>
Advanced AI Analyzer
</h5>
<div class="mb-4">
<label class="form-label fw-bold">1. Select Input Source</label>
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-file-tab" data-bs-toggle="pill" data-bs-target="#pills-file" type="button" role="tab">Upload File</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-url-tab" data-bs-toggle="pill" data-bs-target="#pills-url" type="button" role="tab">Image URL</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-file" role="tabpanel">
<input type="file" id="ai-file-input" class="form-control">
</div>
<div class="tab-pane fade" id="pills-url" role="tabpanel">
<input type="text" id="ai-url-input" class="form-control" placeholder="https://example.com/image.jpg">
</div>
</div>
</div>
<div class="mb-4">
<label class="form-label fw-bold">2. Custom Prompt</label>
<textarea id="ai-prompt" class="form-control" rows="3">Analyze this image. If it's a dish, identify it and provide a recipe in JSON format: { "name": "...", "ingredients": [...] }</textarea>
</div>
<div class="mb-4">
<label class="form-label fw-bold">3. Select Model</label>
<select id="ai-model" class="form-select">
<option value="gpt-4o">GPT-4o (Best for Vision)</option>
<option value="gpt-4o-mini" selected>GPT-4o Mini (Fast & Efficient)</option>
</select>
</div>
<button id="analyze-btn" class="btn btn-primary w-100 py-2 fw-bold">
<i class="bi bi-cpu me-2"></i> Run Analysis
</button>
<div id="ai-results" class="mt-4 d-none">
<h6 class="fw-bold border-bottom pb-2">Analysis Result:</h6>
<div class="mb-3">
<label class="small text-muted">Text Output:</label>
<div class="bg-white border rounded p-3" id="ai-text-output" style="white-space: pre-wrap;"></div>
</div>
<div id="json-section" class="d-none">
<label class="small text-muted">Extracted Data (JSON):</label>
<pre class="bg-dark text-white p-3 rounded" id="ai-json-output"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="text-center py-4 mt-5">
<p class="mb-0 text-muted">&copy; <?php echo date("Y"); ?> FoodieFlow Lab</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById('analyze-btn')?.addEventListener('click', async () => {
const fileInput = document.getElementById('ai-file-input');
const urlInput = document.getElementById('ai-url-input');
const promptInput = document.getElementById('ai-prompt');
const modelSelect = document.getElementById('ai-model');
const resultsDiv = document.getElementById('ai-results');
const textOutput = document.getElementById('ai-text-output');
const jsonOutput = document.getElementById('ai-json-output');
const jsonSection = document.getElementById('json-section');
const btn = document.getElementById('analyze-btn');
const originalText = btn.innerHTML;
const isUrl = document.getElementById('pills-url-tab').classList.contains('active');
const formData = new FormData();
formData.append('prompt', promptInput.value);
formData.append('model', modelSelect.value);
if (isUrl) {
if (!urlInput.value) {
alert('Please enter an image URL.');
return;
}
formData.append('image_url', urlInput.value);
} else {
if (!fileInput.files.length) {
alert('Please select an image file.');
return;
}
formData.append('image', fileInput.files[0]);
}
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Processing...';
btn.disabled = true;
resultsDiv.classList.add('d-none');
try {
const response = await fetch('api/ai_analyze.php', {
method: 'POST',
body: formData
});
const data = await response.json();
resultsDiv.classList.remove('d-none');
if (data.success) {
textOutput.textContent = data.text || 'No text output returned.';
if (data.data) {
jsonSection.classList.remove('d-none');
jsonOutput.textContent = JSON.stringify(data.data, null, 2);
} else {
jsonSection.classList.add('d-none');
}
} else {
textOutput.innerHTML = `<span class="text-danger">Error: ${data.error}</span>`;
jsonSection.classList.add('d-none');
}
} catch (error) {
console.error('Error:', error);
textOutput.innerHTML = `<span class="text-danger">Failed to connect to the server.</span>`;
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
}
});
</script>
</body>
</html>