- Tech Live: TECL → TL - TechLive Qatar: added (TLQ) - TechLive Cyber: added (TLCYB) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
565 lines
22 KiB
PHP
565 lines
22 KiB
PHP
<?php
|
|
header('Content-Type: application/json');
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
require_once __DIR__ . '/auth.php';
|
|
require_once __DIR__ . '/sheet_helpers.php';
|
|
|
|
// ---------------------------------------------------------------
|
|
// PUBLIC ENDPOINT: create_session
|
|
// Must be handled BEFORE the auth guard
|
|
// ---------------------------------------------------------------
|
|
$action = $_GET['action'] ?? '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'create_session') {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$idToken = $input['id_token'] ?? '';
|
|
|
|
if (empty($idToken)) {
|
|
http_response_code(400);
|
|
echo json_encode(['success' => false, 'message' => 'Missing id_token']);
|
|
exit;
|
|
}
|
|
|
|
// Decode JWT payload (middle segment, base64url)
|
|
$parts = explode('.', $idToken);
|
|
if (count($parts) !== 3) {
|
|
http_response_code(400);
|
|
echo json_encode(['success' => false, 'message' => 'Malformed token']);
|
|
exit;
|
|
}
|
|
|
|
$payloadJson = base64_decode(strtr($parts[1], '-_', '+/') . str_repeat('=', (4 - strlen($parts[1]) % 4) % 4));
|
|
$payload = json_decode($payloadJson, true);
|
|
|
|
if (!$payload) {
|
|
http_response_code(400);
|
|
echo json_encode(['success' => false, 'message' => 'Cannot decode token payload']);
|
|
exit;
|
|
}
|
|
|
|
// Validate claims
|
|
$iss = $payload['iss'] ?? '';
|
|
$aud = $payload['aud'] ?? '';
|
|
$exp = $payload['exp'] ?? 0;
|
|
|
|
if (strpos($iss, AZURE_TENANT_ID) === false) {
|
|
http_response_code(401);
|
|
echo json_encode(['success' => false, 'message' => 'Invalid issuer']);
|
|
exit;
|
|
}
|
|
if ($aud !== AZURE_CLIENT_ID) {
|
|
http_response_code(401);
|
|
echo json_encode(['success' => false, 'message' => 'Invalid audience']);
|
|
exit;
|
|
}
|
|
if (time() > $exp) {
|
|
http_response_code(401);
|
|
echo json_encode(['success' => false, 'message' => 'Token expired']);
|
|
exit;
|
|
}
|
|
|
|
$email = $payload['preferred_username'] ?? $payload['email'] ?? '';
|
|
if (empty($email)) {
|
|
http_response_code(401);
|
|
echo json_encode(['success' => false, 'message' => 'No email claim in token']);
|
|
exit;
|
|
}
|
|
|
|
session_regenerate_id(true);
|
|
$_SESSION['user_email'] = strtolower(trim($email));
|
|
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// AUTH GUARD — all other actions require a valid session
|
|
// ---------------------------------------------------------------
|
|
if (empty($_SESSION['user_email'])) {
|
|
http_response_code(401);
|
|
echo json_encode(['success' => false, 'message' => 'Unauthorised']);
|
|
exit;
|
|
}
|
|
|
|
$CURRENT_USER = $_SESSION['user_email'];
|
|
$dataFile = getUserDataFile($CURRENT_USER);
|
|
$logFile = 'activity.log';
|
|
|
|
// ---------------------------------------------------------------
|
|
// LOGOUT (server-side session destroy; client handles MSAL)
|
|
// ---------------------------------------------------------------
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'logout') {
|
|
$_SESSION = [];
|
|
session_destroy();
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
|
|
// Helper to log activity
|
|
function logActivity($message, $type = 'INFO') {
|
|
global $logFile, $CURRENT_USER;
|
|
$timestamp = date('Y-m-d H:i:s');
|
|
$logEntry = "[$timestamp] [$type] [$CURRENT_USER] $message\n";
|
|
file_put_contents($logFile, $logEntry, FILE_APPEND);
|
|
}
|
|
|
|
// 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 filename from a row
|
|
function generateFilename($item) {
|
|
$omgid = trim($item['OMGID'] ?? '');
|
|
$domain = trim($item['Domain'] ?? '');
|
|
$subteam = trim($item['Subteam'] ?? '');
|
|
$brand = trim($item['Brand'] ?? '');
|
|
$event = trim($item['Event'] ?? '');
|
|
$initiative = trim($item['Initiative'] ?? '');
|
|
$yy = trim($item['YY'] ?? '');
|
|
$seq = trim($item['Sequence'] ?? '');
|
|
$assetName = trim($item['AssetName'] ?? '');
|
|
$version = trim($item['Version'] ?? '');
|
|
|
|
if ($domain === 'EVNT') {
|
|
$parts = ['EVNT'];
|
|
if ($event) $parts[] = $event;
|
|
if ($yy) $parts[] = $yy;
|
|
if ($seq) $parts[] = $seq;
|
|
$middle = implode('-', $parts);
|
|
} else {
|
|
$parts = [];
|
|
if ($domain) $parts[] = $domain;
|
|
if ($subteam) $parts[] = $subteam;
|
|
if ($brand) $parts[] = $brand;
|
|
if ($initiative) $parts[] = $initiative;
|
|
if ($yy) $parts[] = $yy;
|
|
if ($seq) $parts[] = $seq;
|
|
$middle = implode('-', $parts);
|
|
}
|
|
|
|
$suffix = '';
|
|
if ($assetName) $suffix .= '_' . $assetName;
|
|
if ($version) $suffix .= '_v' . $version;
|
|
|
|
if (empty($omgid) && empty($middle)) return '';
|
|
return ($omgid ? $omgid . ' - ' : '') . $middle . $suffix;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Load Campaign
|
|
if ($action === 'load_campaign') {
|
|
$name = $_GET['name'] ?? '';
|
|
$file = __DIR__ . '/campaigns/' . preg_replace('/[^a-z0-9_-]/i', '', $name) . '.json';
|
|
if (file_exists($file)) {
|
|
$data = json_decode(file_get_contents($file), true);
|
|
echo json_encode(['success' => true, 'data' => $data]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Campaign 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;
|
|
}
|
|
|
|
// Save Campaign
|
|
if ($action === 'save_campaign') {
|
|
$name = preg_replace('/[^a-z0-9_-]/i', '', $input['name'] ?? 'untitled');
|
|
$data = $input['data'] ?? [];
|
|
$file = __DIR__ . '/campaigns/' . $name . '.json';
|
|
if (!is_dir(__DIR__ . '/campaigns/')) {
|
|
mkdir(__DIR__ . '/campaigns/', 0755, true);
|
|
}
|
|
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
|
|
// Sheet Management POST Endpoints
|
|
if ($action === 'save_sheet') {
|
|
$name = $input['name'] ?? '';
|
|
$data = getData();
|
|
$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') {
|
|
$data = $input['data'] ?? [];
|
|
saveData($data);
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'command') {
|
|
$data = getData() ?? [];
|
|
$command = trim($input['command']);
|
|
|
|
logActivity("Command received: $command", 'COMMAND');
|
|
|
|
$commandLower = strtolower($command);
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (isset($GEMINI_API_KEY) && $GEMINI_API_KEY !== 'YOUR_API_KEY_HERE') {
|
|
|
|
$currentDate = date('Y-m-d');
|
|
$currentYear2Digit = date('y');
|
|
$dataContext = json_encode($data);
|
|
$yoloMode = isset($input['yolo_mode']) && $input['yolo_mode'] ? 'TRUE' : 'FALSE';
|
|
$conversationHistory = isset($input['history']) ? $input['history'] : '';
|
|
$campaignName = isset($input['campaign_name']) ? trim($input['campaign_name']) : '';
|
|
|
|
$globalContext = '';
|
|
if (!empty($campaignName)) {
|
|
$globalContext .= "\nGLOBAL CAMPAIGN NAME: $campaignName";
|
|
}
|
|
|
|
$prompt = "
|
|
You are an intelligent assistant managing a JOB NAMING TOOL for Dow Jones.
|
|
Current Date: $currentDate
|
|
YOLO MODE: $yoloMode
|
|
$globalContext
|
|
|
|
CONVERSATION HISTORY:
|
|
$conversationHistory
|
|
|
|
CURRENT DATA (Context for your actions):
|
|
$dataContext
|
|
|
|
=== DOW JONES JOB NAMING CONVENTION ===
|
|
|
|
FORMAT: [OMGID] - [Domain]-[Subteam]-[Brand]-[Initiative]-[YY]-[Sequence]_[AssetName]_v[Version]
|
|
EVENT FORMAT: [OMGID] - EVNT-[EventAbbrev]-[YY]-[Sequence]_[AssetName]_v[Version]
|
|
|
|
EXAMPLES:
|
|
- 000000 - PMKT-ACQ-WSJ-BIC-26-01_MetaBanner_v1
|
|
- 000001 - PMKT-ENGRT-WSJ-BIC-26-01_Email_v1
|
|
- 000002 - EVNT-GFF-26-02_Agenda_v3
|
|
|
|
=== VALID VALUES ===
|
|
|
|
DOMAIN options: PMKT (Performance Marketing), BRND (Brand), EVNT (Event), B2B
|
|
|
|
SUBTEAM options (used when Domain is NOT EVNT):
|
|
- ACQ (Acquisition), B2B, CMKT (Content Marketing), ENGRT (Engagement/Retention), ENT (Enterprise)
|
|
|
|
BRAND options (used when Domain is NOT EVNT):
|
|
- WSJ (Wall Street Journal), WSJ+ (Wall Street Journal+), BAR (Barron's), MW (MarketWatch)
|
|
- DF (Dragonfly), DJE (Dow Jones Energy), FAC (Factiva), FE (Free Expression)
|
|
- GRI (Global Risk Insights), NWS (Newswires), OA (Oxford Analytica), RSK (Risk), RSKC (Risk Center)
|
|
- DJRJ (Risk Journal), R&C (Risk & Compliance), WECR (World ECR)
|
|
|
|
EVENT options (used ONLY when Domain is EVNT, replaces Subteam+Brand):
|
|
- GH (Global Horizons), DJRJS (Risk Journal Summit), WECR (World ECR)
|
|
- FEOE (Free Expressions Opinion Event), FOH (Future of Health), GFF (Global Food Forum)
|
|
- JH (Journal House), TL (TechLive), TLQ (TechLive Qatar), TLCYB (TechLive Cyber), FOE (The Future of Everything)
|
|
- WSJIL (WSJ Invest Live), BODC (Board of Directors Council)
|
|
- CCOC (CCO Council), CEOC (CEO Council), CFOC (CFO Council), CMOC (CMO Council)
|
|
- CPOC (CPO Council), TECC (Technology Council), WSJLI (WSJ Leadership Institute)
|
|
|
|
OMGID: 6-digit numeric string (e.g., 000000, 000001)
|
|
YY: 2-digit year (e.g., 26 for 2026). Default to current year: $currentYear2Digit
|
|
Sequence: 2-digit sequence number (e.g., 01, 02, 03)
|
|
Initiative: Free text abbreviation (e.g., BIC, DEMO, LAUNCH)
|
|
AssetName: Descriptive name with no spaces (e.g., MetaBanner, Email, Agenda, LinkedInAd)
|
|
Version: Integer version number (e.g., 1, 2, 3)
|
|
|
|
=== DATA SCHEMA ===
|
|
|
|
Each item has these fields:
|
|
- OMGID (String, 6 digits with leading zeros)
|
|
- Domain (String, one of: PMKT, BRND, EVNT, B2B)
|
|
- Subteam (String, one of: ACQ, B2B, CMKT, ENGRT, ENT) - leave empty for EVNT domain
|
|
- Brand (String, one of the brand abbreviations above) - leave empty for EVNT domain
|
|
- Event (String, one of the event abbreviations above) - ONLY used for EVNT domain, leave empty otherwise
|
|
- Initiative (String, free text)
|
|
- YY (String, 2 digits)
|
|
- Sequence (String, 2 digits with leading zero)
|
|
- AssetName (String, no spaces, PascalCase)
|
|
- Version (String, integer)
|
|
- Filename (Auto-generated, READ-ONLY - DO NOT create this field)
|
|
|
|
=== SUPPORTED OPERATIONS ===
|
|
|
|
1. 'create': Create new job name entries
|
|
Output: { \"operation\": \"create\", \"items\": [ { \"OMGID\": \"000000\", \"Domain\": \"PMKT\", \"Subteam\": \"ACQ\", \"Brand\": \"WSJ\", \"Event\": \"\", \"Initiative\": \"BIC\", \"YY\": \"26\", \"Sequence\": \"01\", \"AssetName\": \"MetaBanner\", \"Version\": \"1\" } ] }
|
|
|
|
2. 'update': Update existing entries by OMGID
|
|
Output: { \"operation\": \"update\", \"target_ids\": [\"000000\"], \"values\": { \"Version\": \"2\" } }
|
|
|
|
3. 'batch_update': Update multiple items with DIFFERENT values
|
|
Output: { \"operation\": \"batch_update\", \"updates\": [ { \"OMGID\": \"000000\", \"values\": { \"AssetName\": \"LinkedInAd\" } } ] }
|
|
|
|
4. 'question': Ask for clarification (ONLY if YOLO MODE is FALSE)
|
|
Output: { \"operation\": \"question\", \"text\": \"What domain is this for?\" }
|
|
|
|
=== CRITICAL RULES ===
|
|
|
|
1. **MULTIPLE ITEMS**: When user says 'create 5 jobs', create 5 separate items in the array. Auto-increment Sequence (01, 02, 03...) and OMGID for each.
|
|
|
|
2. **EVENT DOMAIN**: When Domain is EVNT, do NOT populate Subteam or Brand. Use the Event field instead. The user might say the full event name - map it to the abbreviation.
|
|
|
|
3. **NON-EVENT DOMAINS**: When Domain is PMKT, BRND, or B2B, populate Subteam and Brand. Leave Event empty.
|
|
|
|
4. **OMGID AUTO-INCREMENT**: If user doesn't specify OMGID, look at existing data and auto-increment from the highest existing OMGID. If no data exists, start at 000000.
|
|
|
|
5. **SEQUENCE AUTO-INCREMENT**: Within a batch of items sharing the same Domain+Subteam+Brand+Initiative+YY (or Domain+Event+YY for events), auto-increment sequence starting from 01.
|
|
|
|
6. **ASSET NAME**: Should be PascalCase with no spaces (e.g., MetaBanner, LinkedInAd, EmailHeader). If user gives a name with spaces, convert it.
|
|
|
|
7. **NAME RESOLUTION**: When user says full names, map to abbreviations:
|
|
- 'Performance Marketing' -> PMKT, 'Acquisition' -> ACQ, 'Wall Street Journal' -> WSJ
|
|
- 'Global Food Forum' -> GFF, 'Tech Live' -> TECL, etc.
|
|
|
|
8. **YOLO MODE**: If TRUE, NEVER ask questions. Guess missing info:
|
|
- Missing Domain -> PMKT
|
|
- Missing Subteam -> ACQ
|
|
- Missing Brand -> WSJ
|
|
- Missing Initiative -> GEN
|
|
- Missing YY -> $currentYear2Digit
|
|
- Missing AssetName -> Asset
|
|
- Missing Version -> 1
|
|
|
|
9. **CLARIFICATION RECOVERY**: User's input may be answering a previous question. Combine with conversation history and EXECUTE.
|
|
|
|
10. **CONTEXT**: Use CURRENT DATA to resolve references like 'change all version numbers to 2' or 'update the GFF events'.
|
|
|
|
CRITICAL: Respond with ONLY valid JSON. No explanations, no markdown, no conversational text.
|
|
Your response must be a single JSON object starting with { and ending with }.
|
|
|
|
User Command: \"$command\"
|
|
";
|
|
|
|
// Call Gemini API
|
|
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash: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']);
|
|
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);
|
|
|
|
$responseObj = json_decode($response, true);
|
|
|
|
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: " . $errorMsg,
|
|
'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
|
|
$start = strpos($llmText, '{');
|
|
$end = strrpos($llmText, '}');
|
|
|
|
if ($start !== false && $end !== false) {
|
|
$responseText = substr($llmText, $start, $end - $start + 1);
|
|
} else {
|
|
$responseText = $llmText;
|
|
}
|
|
|
|
$llmAction = json_decode($responseText, true);
|
|
|
|
$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;
|
|
}
|
|
|
|
// Execute Action
|
|
if ($llmAction['operation'] === 'create') {
|
|
$newItems = $llmAction['items'];
|
|
$count = 0;
|
|
foreach ($newItems as $item) {
|
|
if (isset($item['AssetName'])) {
|
|
$item['AssetName'] = str_replace(' ', '', $item['AssetName']);
|
|
}
|
|
$item['Filename'] = generateFilename($item);
|
|
$data[] = $item;
|
|
$count++;
|
|
}
|
|
saveData($data);
|
|
logActivity("Created $count job names via AI", 'SUCCESS');
|
|
echo json_encode(array_merge(['success' => true, 'message' => "Created $count job names.", '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['OMGID'] ?? '', $targetIds)) {
|
|
$match = true;
|
|
}
|
|
} else {
|
|
$match = true;
|
|
}
|
|
|
|
if ($match) {
|
|
foreach ($updates as $key => $val) {
|
|
$row[$key] = $val;
|
|
}
|
|
$row['Filename'] = generateFilename($row);
|
|
$count++;
|
|
}
|
|
}
|
|
saveData($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['OMGID'];
|
|
$values = $update['values'];
|
|
foreach ($data as &$row) {
|
|
if (($row['OMGID'] ?? '') === $id) {
|
|
foreach ($values as $key => $val) {
|
|
$row[$key] = $val;
|
|
}
|
|
$row['Filename'] = generateFilename($row);
|
|
$count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
saveData($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;
|
|
}
|
|
}
|
|
}
|
|
?>
|