$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; } // Sheet Management Endpoints if ($action === 'list_sheets') { $sheets = getUserSheets($CURRENT_USER); echo json_encode(['success' => true, 'sheets' => $sheets]); exit; } if ($action === 'load_sheet') { $sheetId = $_GET['id'] ?? ''; $data = loadSheetData($CURRENT_USER, $sheetId); if ($data !== null) { echo json_encode(['success' => true, 'data' => $data]); } else { echo json_encode(['success' => false, 'message' => 'Sheet not found']); } 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; } // Sheet Management POST Endpoints if ($action === 'save_sheet') { $name = $input['name'] ?? ''; $data = getData(); // Get current data $sheet = createSheet($CURRENT_USER, $name, $data); echo json_encode(['success' => true, 'sheet' => $sheet]); exit; } if ($action === 'duplicate_sheet') { $sheetId = $input['id'] ?? ''; $sheet = duplicateSheet($CURRENT_USER, $sheetId); if ($sheet) { echo json_encode(['success' => true, 'sheet' => $sheet]); } else { echo json_encode(['success' => false, 'message' => 'Failed to duplicate sheet']); } exit; } if ($action === 'delete_sheet') { $sheetId = $input['id'] ?? ''; deleteSheet($CURRENT_USER, $sheetId); echo json_encode(['success' => true]); exit; } if ($action === 'rename_sheet') { $sheetId = $input['id'] ?? ''; $newName = $input['name'] ?? ''; $success = renameSheet($CURRENT_USER, $sheetId, $newName); echo json_encode(['success' => $success]); exit; } if ($action === 'update_sheet') { $sheetId = $input['id'] ?? ''; $data = $input['data'] ?? []; updateSheet($CURRENT_USER, $sheetId, $data); echo json_encode(['success' => true]); exit; } if ($action === 'command') { $sheetId = $input['sheet_id'] ?? ''; if (empty($sheetId)) { echo json_encode(['success' => false, 'message' => 'Please create or select a sheet first.']); exit; } $data = loadSheetData($CURRENT_USER, $sheetId) ?? []; $command = trim($input['command']); // Log the incoming command logActivity("Command received: $command", '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'; // Get conversation history from input (if provided) $conversationHistory = isset($input['history']) ? $input['history'] : ''; $prompt = " You are an intelligent assistant managing an Activation Calendar. Current Date: $currentDate YOLO MODE: $yoloMode CONVERSATION HISTORY: $conversationHistory 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', 'Print', 'Out of Home', 'Video') - Media (Dependent on Category - see below) - Sub-media (Dependent on Category AND Media - see below) HIERARCHY RULES (Strictly follow these combinations): 1. Category: Digital - Media: Online Advertising -> Sub-media: Banner, Rich Media, Landing Page, Static Image, Video, Push notifications, .com - Media: Social -> Sub-media: GIF, Video, Static Image, Multi-Asset Build - Media: Community management -> Sub-media: (None) - Media: POS -> Sub-media: Digital 2. Category: Print - Media: POS -> Sub-media: Print - Media: Out of Home -> Sub-media: Print - Media: Direct Mail -> Sub-media: Print - Media: Packaging -> Sub-media: Print 3. Category: Out of Home - Media: Out of Home -> Sub-media: Print, Digital 4. Category: Video - Media: POS -> Sub-media: Video - Media: Online Advertising -> Sub-media: Video - Media: Social -> Sub-media: Video - Media: Broadcast -> Sub-media: TV, Cinema, Radio, VOD - 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. - **MATH VALIDATION (MANDATORY)**: * BEFORE creating items, COUNT how many items your pattern will create. * If the user says \"X items\" but your pattern creates Y items where X ≠ Y, you MUST use 'question' operation. * Example: User says \"10 print ads, 2 formats, 4 countries, alternating men/women\" Your calculation: 2 formats × 4 countries × 2 titles = 16 items Since 16 ≠ 10, return: {\"operation\": \"question\", \"text\": \"I calculated 16 items (2 formats × 4 countries × 2 titles). Did you mean 16 items total, or should I create only 10 items with a different pattern?\"} * ONLY create items if the math matches OR if the user didn't specify a total count. * **EXCEPTION - CONFIRMATION**: If the user's input is a confirmation of the count (e.g., \"Yes\", \"20\", \"Correct\"), DO NOT ask again. EXECUTE with the confirmed count. * **CHECK HISTORY**: If you just asked \"I calculated X...\", and user says \"X\", PROCEED immediately. - If the request is vague (no title, format, etc.), ask for clarification using the 'question' operation. 1. **FORMAT EXTRACTION (Ultra Complex Logic)**: - **STRICT RULE**: ALWAYS use 'x' as the separator for dimensions. NEVER use 'by'. - **Pixel Dimensions**: Convert '300 by 250' -> '300x250'. Convert '1920 by 1080' -> '1920x1080'. - **Physical Dimensions**: Convert '30 by 30 cm' -> '30x30cm'. Convert '10 x 15 cm' -> '10x15cm'. Keep the unit attached or separated by space, but use 'x' for the numbers. - **Print**: 'A4', 'A3', 'Letter', 'Full Page', 'Half Page'. - **Social**: If user says \"social media sizes\", INFER standard sizes (e.g., 1080x1080 for Instagram) if possible, or ASK if unsure (unless YOLO is ON). - **Examples**: * \"300 by 300\" -> \"300x300\" * \"30x30 cm\" -> \"30x30cm\" * \"30 by 30 cm\" -> \"30x30cm\" 2. **YOLO MODE (HIGHEST PRIORITY)**: - If YOLO MODE is TRUE: **YOU ARE FORBIDDEN FROM ASKING QUESTIONS.** - You MUST GUESS any missing information. - Example: If user says \"banners\" and nothing else, create 1 banner with default settings. - Example: If user says \"2027\" in YOLO mode, assume it's the date for the previous request and EXECUTE. - NEVER return 'question' operation when YOLO is TRUE. 3. **CLARIFICATION RECOVERY (CRITICAL)**: - **CONTEXT MERGING**: The user's current input is likely an ANSWER to your previous question. - **DO NOT** treat the input (e.g., \"2027\", \"300x300\") as a standalone command. - **COMBINE** it with the previous user messages in the history to form a complete request. - **Example Flow**: 1. User: \"Create ads\" (Missing format) 2. AI: \"What format?\" 3. User: \"300x250\" -> **INTERNAL THOUGHT**: \"User said 300x250. Previous was 'Create ads'. Combined: 'Create ads 300x250'.\" -> **ACTION**: Execute the creation. DO NOT ask again. - If the user provides *some* missing info but not *all*, AND YOLO is FALSE, you *can* ask for the remaining info, but acknowledge what you received. - **BUT**: If the user seems frustrated or repeats info, JUST EXECUTE with best guesses. 4. **CONTEXT IS KING**: Use 'CURRENT DATA' to resolve references like \"the French ones\". 5. **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'])) { $errorMsg = $responseObj['error']['message'] ?? 'Unknown Error'; logActivity("Gemini API Error: $errorMsg", '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++; } updateSheet($CURRENT_USER, $sheetId, $data); logActivity("Created $count items via AI", 'SUCCESS'); 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++; } } updateSheet($CURRENT_USER, $sheetId, $data); logActivity("Updated $count items via AI", 'SUCCESS'); 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; } } } updateSheet($CURRENT_USER, $sheetId, $data); logActivity("Batch updated $count items via AI", 'SUCCESS'); echo json_encode(array_merge(['success' => true, 'message' => "Batch updated $count items.", 'count' => $count], $debugInfo)); } elseif ($llmAction['operation'] === 'question') { logActivity("AI asked question: " . $llmAction['text'], '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') ?>