Autosave: 20260127-073448
This commit is contained in:
parent
1e39b8f68a
commit
096fe649f2
109
ai/CustomOpenAI.php
Normal file
109
ai/CustomOpenAI.php
Normal 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
11
ai/keys.php
Normal 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
104
api/ai_analyze.php
Normal 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()]);
|
||||
}
|
||||
@ -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]);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<?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']);
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -368,6 +368,9 @@
|
||||
|
||||
<footer class="text-center py-4 mt-5">
|
||||
<p class="mb-0">© <?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>
|
||||
|
||||
<!-- Scripts -->
|
||||
|
||||
198
test.php
Normal file
198
test.php
Normal 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">© <?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>
|
||||
Loading…
x
Reference in New Issue
Block a user