346 lines
15 KiB
PHP
346 lines
15 KiB
PHP
<?php
|
|
header('Content-Type: application/json');
|
|
require_once 'config.php';
|
|
|
|
$dataFile = 'data.json';
|
|
|
|
// Helper to read data
|
|
function getData() {
|
|
global $dataFile;
|
|
if (!file_exists($dataFile)) return [];
|
|
$content = file_get_contents($dataFile);
|
|
return json_decode($content, true) ?? [];
|
|
}
|
|
|
|
// Helper to save data
|
|
function saveData($data) {
|
|
global $dataFile;
|
|
file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT));
|
|
}
|
|
|
|
// Helper to generate ID
|
|
function generateId($data) {
|
|
$maxId = 0;
|
|
foreach ($data as $row) {
|
|
$num = intval(str_replace('DEL-', '', $row['Number'] ?? '0'));
|
|
if ($num > $maxId) $maxId = $num;
|
|
}
|
|
return 'DEL-' . str_pad($maxId + 1, 3, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
$action = $_GET['action'] ?? '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
if ($action === 'load') {
|
|
echo json_encode(getData());
|
|
exit;
|
|
}
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if ($action === 'save') {
|
|
if (isset($input['data']) && is_array($input['data'])) {
|
|
saveData($input['data']);
|
|
echo json_encode(['success' => true]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Invalid data format']);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'command') {
|
|
$data = getData(); // Load existing data first!
|
|
$command = trim($input['command']);
|
|
|
|
$commandLower = strtolower($command);
|
|
|
|
// Pre-processing: Common speech-to-text corrections
|
|
$correctionMap = [
|
|
'delivery balls' => 'deliverables',
|
|
'delivery ball' => 'deliverable',
|
|
'delivery' => 'deliverables',
|
|
'liver' => 'deliverables',
|
|
'rose' => 'rows',
|
|
'row' => 'rows',
|
|
'oh oh h' => 'OOH',
|
|
'out of home' => 'OOH'
|
|
];
|
|
foreach ($correctionMap as $wrong => $right) {
|
|
$commandLower = str_replace($wrong, $right, $commandLower);
|
|
}
|
|
|
|
// Pre-processing: Convert number words to digits
|
|
$numberMap = [
|
|
'one' => '1', 'two' => '2', 'three' => '3', 'four' => '4', 'five' => '5',
|
|
'six' => '6', 'seven' => '7', 'eight' => '8', 'nine' => '9', 'ten' => '10',
|
|
'eleven' => '11', 'twelve' => '12', 'twenty' => '20', 'thirty' => '30'
|
|
];
|
|
foreach ($numberMap as $word => $digit) {
|
|
$commandLower = preg_replace('/\b' . $word . '\b/', $digit, $commandLower);
|
|
}
|
|
|
|
// require_once 'config.php'; // Moved to top
|
|
|
|
// If API Key is present, use LLM
|
|
// Debug: Check if key is loaded
|
|
if (isset($GEMINI_API_KEY) && $GEMINI_API_KEY !== 'YOUR_API_KEY_HERE') {
|
|
|
|
// 1. Construct Prompt
|
|
$currentDate = date('Y-m-d');
|
|
$dataContext = json_encode($data);
|
|
$yoloMode = isset($input['yolo_mode']) && $input['yolo_mode'] ? 'TRUE' : 'FALSE';
|
|
|
|
$prompt = "
|
|
You are an intelligent assistant managing an Activation Calendar.
|
|
Current Date: $currentDate
|
|
YOLO MODE: $yoloMode
|
|
|
|
CURRENT DATA (Context for your actions):
|
|
$dataContext
|
|
|
|
Data Schema:
|
|
- Number (Auto-generated, do not invent)
|
|
- Title (String)
|
|
- Status (Enum: Booked, To-do, In Progress, Done) - Default to 'Booked'
|
|
- Category (Enum: 'Digital', 'DOOH', 'Online - Banner - Celtra', 'OOH', 'Print', 'Video')
|
|
- Media (Enum: 'POS', 'OOH', 'Social', 'Community management', 'Online advertising', 'Banner', 'Rich media', 'Landing page', 'Static Image', 'Video', 'Push notifications', '.com', 'Print', 'Digital', 'Direct mail', 'Packaging', 'TV', 'Cinema', 'Radio', 'VOD')
|
|
- Sub-media (String)
|
|
- Format (String) - Extract sizes/dimensions here! e.g., '300x250', 'A4', '10x15cm', 'Full Page', '1080p'.
|
|
- Supply date (YYYY-MM-DD)
|
|
- Live date (YYYY-MM-DD)
|
|
- Language (ISO 2-letter code, UPPERCASE, e.g., 'EN', 'FR', 'ES')
|
|
- Country (ISO 2-letter code, UPPERCASE, e.g., 'GB', 'FR', 'ES')
|
|
- Quantity (Integer)
|
|
|
|
Supported Operations:
|
|
1. 'create': Create new items.
|
|
Output: { \"operation\": \"create\", \"items\": [ { \"Title\": \"...\", \"Format\": \"300x250\", ... } ] }
|
|
|
|
2. 'update': Update existing items.
|
|
Output: { \"operation\": \"update\", \"target_ids\": [\"DEL-001\"], \"values\": { \"Status\": \"Done\" } }
|
|
|
|
3. 'batch_update': Update multiple items with DIFFERENT values.
|
|
Output: { \"operation\": \"batch_update\", \"updates\": [ { \"Number\": \"DEL-001\", \"values\": { \"Title\": \"Row 1\" } } ] }
|
|
|
|
4. 'question': Ask for clarification (ONLY if YOLO MODE is FALSE).
|
|
Output: { \"operation\": \"question\", \"text\": \"Did you mean 2025 or 2026?\" }
|
|
|
|
IMPORTANT BRAIN RULES:
|
|
|
|
0. **CRITICAL - MULTIPLE ITEMS vs QUANTITY**:
|
|
- When user says \"add 10 deliverables\" or \"create 5 banners\", you MUST create that many SEPARATE items in the array.
|
|
- NEVER use Quantity field to represent the count. Quantity should always be 1 unless explicitly stated otherwise.
|
|
- Example: \"Add 10 digital deliverables\" = Create 10 separate objects in the items array, each with Quantity=1.
|
|
- If the request is vague (no title, format, etc.), ask for clarification using the 'question' operation.
|
|
|
|
|
|
1. **FORMAT EXTRACTION (Ultra Complex Logic)**:
|
|
- Look for ANY size or format indication.
|
|
- Pixels: '300x250', '300 by 250', '1920x1080'. Normalize to 'WxH'.
|
|
- Print: 'A4', 'A3', 'Letter', 'Full Page', 'Half Page'.
|
|
- Dimensions: '10x15cm', '5 inches', '4x6\"'. Keep units.
|
|
- If user says \"social media sizes\", INFER standard sizes (e.g., 1080x1080 for Instagram) if possible, or ASK if unsure (unless YOLO is ON).
|
|
|
|
2. **YOLO MODE**:
|
|
- If YOLO MODE is TRUE: NEVER ask questions. GUESS. Make the best assumption and DO IT.
|
|
- If YOLO MODE is FALSE: If the request is ambiguous (e.g., \"next Friday\" when it's unclear, or missing critical category info), return operation 'question'.
|
|
|
|
3. **CONTEXT IS KING**: Use 'CURRENT DATA' to resolve references like \"the French ones\".
|
|
|
|
4. **PRECISE TARGETING**: Use `target_ids` for updates.
|
|
|
|
5. **INFER FIELDS**:
|
|
- \"OOH\" -> Category='OOH', Media='OOH'.
|
|
- \"Instagram Story\" -> Category='Digital', Media='Social', Sub-media='Instagram', Format='9:16'.
|
|
- \"English\" -> Language='EN', \"French\" -> Language='FR', \"Spanish\" -> Language='ES'
|
|
- \"Great Britain\" or \"UK\" -> Country='GB', \"France\" -> Country='FR', \"Spain\" -> Country='ES'
|
|
|
|
6. **PATTERN RECOGNITION**:
|
|
- If user says \"repeat 3 times\" with different languages, create 3 sets of items.
|
|
- Extract formats from phrases like \"200 by 200\", \"300x300\", \"400x400 banner\".
|
|
- Pay attention to sequences: \"first 5\", \"next 4\", \"remaining\" for language/country assignments.
|
|
|
|
CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no conversational text, no markdown.
|
|
Your response must be a single JSON object starting with { and ending with }.
|
|
|
|
If you need clarification, use the 'question' operation:
|
|
{ \\\"operation\\\": \\\"question\\\", \\\"text\\\": \\\"Your question here\\\" }
|
|
|
|
User Command: \"$command\"
|
|
";
|
|
|
|
// 2. Call Gemini API
|
|
// User mentioned 2.5, likely referring to the new 2.0 Flash Experimental
|
|
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=" . $GEMINI_API_KEY;
|
|
$dataPayload = [
|
|
"contents" => [
|
|
[
|
|
"parts" => [
|
|
["text" => $prompt]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($dataPayload));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
|
|
|
// FIX: Disable SSL check for local dev environments (MAMP/XAMPP often lack certs)
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
|
$response = curl_exec($ch);
|
|
|
|
if (curl_errno($ch)) {
|
|
echo json_encode(['success' => false, 'message' => 'Curl error: ' . curl_error($ch)]);
|
|
exit;
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
// 2. Parse LLM Response
|
|
$responseObj = json_decode($response, true);
|
|
|
|
// Check for API Level Errors (e.g. Invalid Key)
|
|
if (isset($responseObj['error'])) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'message' => "Gemini API Error: " . ($responseObj['error']['message'] ?? 'Unknown Error'),
|
|
'debug_raw' => $response
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
$llmText = $responseObj['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
|
|
|
if (empty($llmText)) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'message' => "AI returned an empty response.",
|
|
'debug_raw' => $response
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// Robust JSON Extraction
|
|
// Robust JSON Extraction
|
|
$start = strpos($llmText, '{');
|
|
$end = strrpos($llmText, '}');
|
|
|
|
if ($start !== false && $end !== false) {
|
|
$responseText = substr($llmText, $start, $end - $start + 1);
|
|
} else {
|
|
$responseText = $llmText; // Fallback
|
|
}
|
|
|
|
$llmAction = json_decode($responseText, true);
|
|
|
|
// Add debug info to response
|
|
$debugInfo = [
|
|
'debug_llm' => $llmText,
|
|
'debug_extracted' => $responseText,
|
|
'debug_json_error' => json_last_error_msg()
|
|
];
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE || !$llmAction) {
|
|
echo json_encode(array_merge([
|
|
'success' => false,
|
|
'message' => "Invalid JSON from AI",
|
|
], $debugInfo));
|
|
exit;
|
|
}
|
|
|
|
// 3. Execute Action
|
|
// Map $llmAction to $actionData for compatibility with existing logic below if needed,
|
|
// OR just use $llmAction directly.
|
|
// The previous code used $actionData. Let's stick to $llmAction as per my recent update intent,
|
|
// BUT the code below line 220 might expect $actionData.
|
|
// Let's check the next lines.
|
|
// The view showed `if (!$actionData) ... else { // 3. Execute Logic`.
|
|
// So I should probably replace the whole execution block or alias it.
|
|
|
|
// Let's just handle the execution here as per the NEW logic I wrote in step 528
|
|
// which used $llmAction.
|
|
|
|
if ($llmAction['operation'] === 'create') {
|
|
$newItems = $llmAction['items'];
|
|
$count = 0;
|
|
foreach ($newItems as $item) {
|
|
// Generate ID
|
|
$lastId = 0;
|
|
foreach ($data as $row) {
|
|
$num = intval(str_replace('DEL-', '', $row['Number']));
|
|
if ($num > $lastId) $lastId = $num;
|
|
}
|
|
$newId = 'DEL-' . str_pad($lastId + 1, 3, '0', STR_PAD_LEFT);
|
|
|
|
$item['Number'] = $newId;
|
|
// Ensure defaults
|
|
if (empty($item['Status'])) $item['Status'] = 'Booked';
|
|
if (empty($item['Quantity'])) $item['Quantity'] = 1;
|
|
|
|
$data[] = $item;
|
|
$count++;
|
|
}
|
|
saveData($data);
|
|
echo json_encode(array_merge(['success' => true, 'message' => "Created $count items.", 'count' => $count], $debugInfo));
|
|
|
|
} elseif ($llmAction['operation'] === 'update') {
|
|
$updates = $llmAction['values'];
|
|
$targetIds = $llmAction['target_ids'] ?? [];
|
|
|
|
$count = 0;
|
|
foreach ($data as &$row) {
|
|
$match = false;
|
|
if (!empty($targetIds)) {
|
|
if (in_array($row['Number'], $targetIds)) {
|
|
$match = true;
|
|
}
|
|
} else {
|
|
// Fallback: Filter (simplified)
|
|
$match = true;
|
|
}
|
|
|
|
if ($match) {
|
|
foreach ($updates as $key => $val) {
|
|
$row[$key] = $val;
|
|
}
|
|
$count++;
|
|
}
|
|
}
|
|
saveData($data);
|
|
echo json_encode(array_merge(['success' => true, 'message' => "Updated $count items.", 'count' => $count], $debugInfo));
|
|
|
|
} elseif ($llmAction['operation'] === 'batch_update') {
|
|
$updates = $llmAction['updates'];
|
|
$count = 0;
|
|
foreach ($updates as $update) {
|
|
$id = $update['Number'];
|
|
$values = $update['values'];
|
|
foreach ($data as &$row) {
|
|
if ($row['Number'] === $id) {
|
|
foreach ($values as $key => $val) {
|
|
$row[$key] = $val;
|
|
}
|
|
$count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
saveData($data);
|
|
echo json_encode(array_merge(['success' => true, 'message' => "Batch updated $count items.", 'count' => $count], $debugInfo));
|
|
|
|
} elseif ($llmAction['operation'] === 'question') {
|
|
echo json_encode(array_merge(['success' => true, 'question' => $llmAction['text']], $debugInfo));
|
|
} else {
|
|
echo json_encode(array_merge(['success' => false, 'message' => 'Unknown operation: ' . $llmAction['operation']], $debugInfo));
|
|
}
|
|
exit;
|
|
} // End if (isset($GEMINI_API_KEY))
|
|
} // End if ($action === 'command')
|
|
} // End if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
|
?>
|