Files
html/products/wevialife-api.php
2026-04-17 14:20:01 +02:00

1008 lines
58 KiB
PHP

<?php
header('Content-Type: application/json', 'Host: weval-consulting.com');
header('Access-Control-Allow-Origin: *');
if($_SERVER['REQUEST_METHOD']==='OPTIONS'){http_response_code(200);exit;}
$dataDir = '/var/www/weval/wevia-ia/wevialife-data';
@mkdir($dataDir, 0755, true);
$action = $_GET['action'] ?? 'status';
// === EMAIL INTEGRATION FOR WEVIA LIFE ===
// Handles: Gmail IMAP, Outlook/Namecheap IMAP, Local sync
// IMAP proxy through S95 (to avoid IP lockouts)
function imap_via_proxy($server, $port, $email, $pass_b64) {
// Try local first
$mb = "{" . $server . ":" . $port . "/imap/ssl/novalidate-cert}INBOX";
$conn = @imap_open($mb, $email, base64_decode($pass_b64), 0, 1);
if ($conn) return $conn;
// If local fails (IP blocked), try via S95
$php = '$c=@imap_open("{'.$server.':'.$port.'/imap/ssl/novalidate-cert}INBOX","'.$email.'",base64_decode("'.$pass_b64.'"),0,1);if($c){$i=imap_check($c);$t=$i->Nmsgs;$s=max(1,$t-19);$r=[];$ov=imap_fetch_overview($c,"$s:$t",0);foreach(array_reverse($ov?:[])as$o){$r[]=$o->uid."|".$o->from."|".$o->subject."|".$o->date."|".($o->seen??0);}imap_close($c);echo json_encode(["ok"=>1,"count"=>$t,"emails"=>$r]);}else echo json_encode(["error"=>imap_last_error()]);';
$url = "http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=" . urlencode("php -r " . escapeshellarg($php));
$ctx = stream_context_create(["http" => ["timeout" => 25]]);
$resp = @file_get_contents($url, false, $ctx);
if ($resp) {
$data = json_decode($resp, true);
if (isset($data["output"])) {
$output = json_decode($data["output"], true);
if ($output && isset($output["ok"])) {
// Store results for later use
$GLOBALS["_imap_proxy_result"] = $output;
return "S95_PROXY";
}
}
}
return false;
}
$emailSourcesFile = "$dataDir/email-sources.json";
function loadEmailSources($f) {
return file_exists($f) ? json_decode(file_get_contents($f), true) ?: [] : [];
}
function saveEmailSources($f, $d) {
file_put_contents($f, json_encode($d, JSON_PRETTY_PRINT));
}
// === EMAIL ACTIONS ===
// WEVIA Life v2 - Dashboard API endpoints
// Added to wevialife-api.php
// === DASHBOARD: Morning Brief ===
// Auto-rebalance Eisenhower on each brief load
function rebalance_eisenhower($pdo) {
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='eliminate', requires_action='f' WHERE (resolved IS NULL OR resolved = false) AND category='transactional' AND (subject LIKE '%created%' OR subject LIKE '%rebuilt%' OR subject LIKE '%Server %' OR subject LIKE '%Verify%' OR subject LIKE '%confirmation code%')");
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='eliminate', requires_action='f' WHERE (resolved IS NULL OR resolved = false) AND category='newsletter'");
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='eliminate' WHERE (resolved IS NULL OR resolved = false) AND category='transactional' AND subject NOT LIKE '%Payment%' AND subject NOT LIKE '%Warning%' AND subject NOT LIKE '%expired%' AND requires_action='f'");
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='schedule', importance_score=4 WHERE category LIKE '%opportunity%'");
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='do_first', urgency_score=5, requires_action='t' WHERE (resolved IS NULL OR resolved = false) AND (subject LIKE '%Payment Warning%' OR subject LIKE '%SSL%expired%' OR subject LIKE '%NOTICE%expired%')");
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='schedule' WHERE category LIKE '%risk%' AND eisenhower_quadrant='do_first' AND subject NOT LIKE '%expired%' AND subject NOT LIKE '%Payment%' AND subject NOT LIKE '%Warning%'");
$pdo->exec("UPDATE admin.email_classifications SET eisenhower_quadrant='schedule' WHERE category LIKE '%opportunity%risk%'");
}
// === DISMISS ALERT ===
if ($action === 'dismiss_alert') {
$input = json_decode(file_get_contents('php://input'), true);
$id = (int)($input['id'] ?? 0);
$note = $input['note'] ?? 'Dismissed from UI';
if (!$id) { echo json_encode(['error' => 'missing id']); exit; }
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$pdo->exec("SET search_path TO admin,public");
$stmt = $pdo->prepare("UPDATE email_classifications SET resolved = true, resolved_note = ?, eisenhower_quadrant = 'eliminate', requires_action = false WHERE id = ?");
$stmt->execute([$note, $id]);
echo json_encode(['ok' => true, 'dismissed' => $stmt->rowCount()]);
exit;
}
// === AUTO-RESOLVE ===
if ($action === 'auto_resolve') {
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$pdo->exec("SET search_path TO admin,public");
$total = 0;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: 2FA/code', eisenhower_quadrant='eliminate', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND (LOWER(subject) LIKE '%your%code%' OR LOWER(subject) LIKE '%verification%code%' OR LOWER(subject) LIKE '%confirm%email%')");
$total += (int)$r;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: password', eisenhower_quadrant='eliminate', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND (LOWER(subject) LIKE '%password%changed%' OR LOWER(subject) LIKE '%password%reset%')");
$total += (int)$r;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: server ops', eisenhower_quadrant='eliminate', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND (LOWER(subject) LIKE '%server%created%' OR LOWER(subject) LIKE '%server%rebuilt%' OR LOWER(subject) LIKE '%hardware reset%')");
$total += (int)$r;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: old newsletter', eisenhower_quadrant='eliminate', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND category IN ('newsletter','spam') AND received_at < NOW() - INTERVAL '7 days'");
$total += (int)$r;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: old transactional', eisenhower_quadrant='eliminate', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND category='transactional' AND received_at < NOW() - INTERVAL '14 days'");
$total += (int)$r;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: sent by user', eisenhower_quadrant='eliminate', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND folder='SENT'");
$total += (int)$r;
$sent_subjs = $pdo->query("SELECT REPLACE(subject, '[SENT] ', '') FROM email_classifications WHERE folder='SENT'")->fetchAll(PDO::FETCH_COLUMN);
$matched = 0;
foreach ($sent_subjs as $s) {
$s = trim(str_replace(['RE: ','Re: ','Fwd: '], '', $s));
if (strlen($s) < 10) continue;
$r = $pdo->exec("UPDATE email_classifications SET resolved=true, resolved_note='Auto: replied', requires_action=false WHERE (resolved IS NULL OR resolved=false) AND folder='INBOX' AND subject LIKE '%" . pg_escape_string(substr($s,0,50)) . "%'");
$matched += (int)$r;
}
$total += $matched;
$stats = $pdo->query("SELECT COALESCE(resolved,false) as r, COUNT(*) FROM email_classifications GROUP BY resolved")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['resolved' => $total, 'matched_replies' => $matched, 'stats' => $stats]);
exit;
}
// === SYNC SENT ===
if ($action === 'sync_sent') {
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$pdo->exec("SET search_path TO admin,public");
$mb = '{server105.web-hosting.com:993/imap/ssl/novalidate-cert}';
$conn = @imap_open($mb.'INBOX.Sent', 'ymahboub@weval-consulting.com', base64_decode('WUBAY2luZTE5ODVAQA=='), 0, 1);
if (!$conn) { echo json_encode(['error'=>imap_last_error()]); exit; }
$info = imap_check($conn); $total = $info->Nmsgs;
$start = max(1, $total - 99);
$ov = imap_fetch_overview($conn, "$start:$total", 0);
$stmt = $pdo->prepare("INSERT INTO email_classifications (uid, folder, from_email, from_name, to_email, subject, received_at, category, eisenhower_quadrant, summary, resolved, resolved_note) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT (uid,folder) DO NOTHING");
$ok = 0;
foreach (array_reverse($ov?:[]) as $o) {
$uid=(int)($o->uid??0); if(!$uid) continue;
$subj=isset($o->subject)?@imap_utf8($o->subject):'';
$to=isset($o->to)?@imap_utf8($o->to):'';
$date=isset($o->date)?date('Y-m-d H:i:s',strtotime($o->date)):date('Y-m-d');
$stmt->execute([$uid,'SENT','ymahboub@weval-consulting.com','Yacine (ENVOYE)',$to,'[SENT] '.$subj,$date,'sent_by_user','eliminate',"ENVOYE: $subj",true,'Sent by user']);
$ok++;
}
imap_close($conn);
echo json_encode(['synced'=>$ok,'total_sent'=>$total]);
exit;
}
// === DESKTOP FILE SYNC — receives files from Razer Blade Sentinel ===
if ($action === 'desktop_sync') {
header('Content-Type: application/json');
$uploadDir = '/var/www/weval/wevia-ia/wevialife-data/documents/';
@mkdir($uploadDir, 0755, true);
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$pdo->exec("SET search_path TO admin,public");
// Accept POST with file content (base64) or multipart upload
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (isset($input['files']) && is_array($input['files'])) {
// Batch sync: [{name, content_b64, modified}]
$synced = 0;
foreach ($input['files'] as $file) {
$name = preg_replace('/[^a-zA-Z0-9._\-\s]/', '_', $file['name'] ?? 'unknown');
$content = base64_decode($file['content_b64'] ?? '');
$modified = $file['modified'] ?? date('Y-m-d H:i:s');
if (!$content) continue;
$dest = $uploadDir . $name;
file_put_contents($dest, $content);
// Extract text for indexing
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$text = '';
if (in_array($ext, ['txt','md','csv','json','log'])) {
$text = mb_substr($content, 0, 50000);
} elseif ($ext === 'pdf') {
@exec("pdftotext " . escapeshellarg($dest) . " - 2>/dev/null", $lines);
$text = implode("\n", $lines);
}
// Upsert to DB
$existing = $pdo->prepare("SELECT id FROM admin.documents WHERE filename = ?");
$existing->execute([$name]);
if ($existing->fetch()) {
$pdo->prepare("UPDATE admin.documents SET content_text = ?, filesize = ?, indexed_at = NOW(), source = 'desktop_sync' WHERE filename = ?")->execute([mb_substr($text, 0, 50000), strlen($content), $name]);
} else {
$pdo->prepare("INSERT INTO admin.documents (filename, filepath, filetype, filesize, content_text, source, indexed_at) VALUES (?,?,?,?,?,?,NOW())")->execute([$name, $dest, $ext, strlen($content), mb_substr($text, 0, 50000), 'desktop_sync']);
}
$synced++;
}
echo json_encode(['ok' => true, 'synced' => $synced]);
exit;
}
// Single file upload via multipart
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$name = preg_replace('/[^a-zA-Z0-9._\-\s]/', '_', $file['name']);
$dest = $uploadDir . $name;
move_uploaded_file($file['tmp_name'], $dest);
$pdo->prepare("INSERT INTO admin.documents (filename, filepath, filetype, filesize, source, indexed_at) VALUES (?,?,?,?,?,NOW()) ON CONFLICT DO NOTHING")->execute([$name, $dest, pathinfo($name, PATHINFO_EXTENSION), $file['size'], 'desktop_sync']);
echo json_encode(['ok' => true, 'file' => $name]);
exit;
}
}
// GET: list synced desktop files
$docs = $pdo->query("SELECT filename, filesize, source, indexed_at FROM admin.documents WHERE source = 'desktop_sync' ORDER BY indexed_at DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['documents' => $docs, 'total' => count($docs), 'sync_dir' => 'C:\\Users\\Yace\\Desktop\\WEVIA LIFE']);
exit;
}
// === CLAUDE MULTI-ACCOUNT SYNC ===
if ($action === 'claude_sync') {
header('Content-Type: application/json');
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$pdo->exec("SET search_path TO admin,public");
$input = json_decode(file_get_contents('php://input'), true);
$account = $input['account_id'] ?? $_GET['account'] ?? '';
// POST: receive transcripts
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($input['files'])) {
if (!$account) { echo json_encode(['error'=>'missing account_id']); exit; }
$uploadDir = '/var/www/weval/wevia-ia/wevialife-data/documents/';
@mkdir($uploadDir, 0755, true);
$synced = 0;
foreach ($input['files'] as $file) {
$name = preg_replace('/[^a-zA-Z0-9._\-\s]/', '_', $file['name'] ?? 'unknown');
$prefix = strtoupper($account);
$dest_name = "{$prefix}-{$name}";
$content = base64_decode($file['content_b64'] ?? '');
if (!$content) continue;
$dest = $uploadDir . $dest_name;
file_put_contents($dest, $content);
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$text = in_array($ext, ['txt','md','csv','json','log']) ? mb_substr($content, 0, 50000) : '';
$existing = $pdo->prepare("SELECT id FROM documents WHERE filename = ?");
$existing->execute([$dest_name]);
if ($existing->fetch()) {
$pdo->prepare("UPDATE documents SET content_text=?, filesize=?, indexed_at=NOW(), claude_account=? WHERE filename=?")->execute([mb_substr($text,0,50000), strlen($content), $account, $dest_name]);
} else {
$pdo->prepare("INSERT INTO documents (filename,filepath,filetype,filesize,content_text,source,claude_account,indexed_at) VALUES (?,?,?,?,?,?,?,NOW())")->execute([$dest_name, $dest, $ext, strlen($content), mb_substr($text,0,50000), 'claude_transcript', $account]);
}
$synced++;
}
// Update account stats
$pdo->prepare("UPDATE claude_accounts SET last_sync=NOW(), total_transcripts=(SELECT COUNT(*) FROM documents WHERE claude_account=?), total_size_bytes=(SELECT COALESCE(SUM(filesize),0) FROM documents WHERE claude_account=?), last_transcript=(SELECT filename FROM documents WHERE claude_account=? ORDER BY indexed_at DESC LIMIT 1) WHERE account_id=?")->execute([$account,$account,$account,$account]);
echo json_encode(['ok'=>true,'synced'=>$synced,'account'=>$account]);
exit;
}
// GET: monitoring dashboard data
$accounts = $pdo->query("SELECT * FROM claude_accounts ORDER BY account_id")->fetchAll(PDO::FETCH_ASSOC);
$total_docs = $pdo->query("SELECT COUNT(*) FROM documents WHERE source IN ('claude_transcript','claude_output')")->fetchColumn();
$total_size = $pdo->query("SELECT COALESCE(SUM(filesize),0) FROM documents WHERE source IN ('claude_transcript','claude_output')")->fetchColumn();
$recent = $pdo->query("SELECT filename, claude_account, filesize, source, filetype, indexed_at FROM documents WHERE source IN ('claude_transcript','claude_output') ORDER BY indexed_at DESC LIMIT 30")->fetchAll(PDO::FETCH_ASSOC);
$by_account = $pdo->query("SELECT claude_account, COUNT(*) as cnt, COALESCE(SUM(filesize),0) as sz FROM documents WHERE source IN ('claude_transcript','claude_output') GROUP BY claude_account")->fetchAll(PDO::FETCH_ASSOC);
$by_source = $pdo->query("SELECT source, filetype, COUNT(*) as cnt FROM documents GROUP BY source, filetype ORDER BY source, cnt DESC")->fetchAll(PDO::FETCH_ASSOC);
$all_sources = $pdo->query("SELECT source, COUNT(*) as cnt, COALESCE(SUM(filesize),0) as sz FROM documents GROUP BY source ORDER BY cnt DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['accounts'=>$accounts, 'total_docs'=>(int)$total_docs, 'total_size'=>(int)$total_size, 'recent'=>$recent, 'by_account'=>$by_account, 'by_source'=>$by_source, 'all_sources'=>$all_sources]);
exit;
}
if ($action === 'morning_brief') {
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$today = date('Y-m-d');
rebalance_eisenhower($pdo);
// Urgent items (do_first)
$urgent = $pdo->query("SELECT * FROM admin.email_classifications WHERE eisenhower_quadrant='do_first' AND received_at > NOW() - INTERVAL '7 days' ORDER BY urgency_score DESC, received_at DESC LIMIT 10")->fetchAll(PDO::FETCH_ASSOC);
// Scheduled items
$scheduled = $pdo->query("SELECT * FROM admin.email_classifications WHERE eisenhower_quadrant='schedule' AND received_at > NOW() - INTERVAL '7 days' ORDER BY importance_score DESC LIMIT 10")->fetchAll(PDO::FETCH_ASSOC);
// Opportunities
$opportunities = $pdo->query("SELECT * FROM admin.email_classifications WHERE category='opportunity' AND received_at > NOW() - INTERVAL '14 days' ORDER BY importance_score DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
// Risks
$risks = $pdo->query("SELECT * FROM admin.email_classifications WHERE category='risk' AND received_at > NOW() - INTERVAL '14 days' ORDER BY urgency_score DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
// Deadlines coming up
$deadlines = $pdo->query("SELECT * FROM admin.email_classifications WHERE extracted_deadlines != '[]' AND received_at > NOW() - INTERVAL '30 days' ORDER BY received_at DESC LIMIT 10")->fetchAll(PDO::FETCH_ASSOC);
// Actions required
$actions = $pdo->query("SELECT * FROM admin.email_classifications WHERE requires_action=true AND received_at > NOW() - INTERVAL '7 days' ORDER BY urgency_score DESC, importance_score DESC LIMIT 15")->fetchAll(PDO::FETCH_ASSOC);
// Stats
$stats = $pdo->query("SELECT
COUNT(*) as total,
SUM(CASE WHEN eisenhower_quadrant='do_first' THEN 1 ELSE 0 END) as do_first,
SUM(CASE WHEN eisenhower_quadrant='schedule' THEN 1 ELSE 0 END) as schedule_count,
SUM(CASE WHEN eisenhower_quadrant='delegate' THEN 1 ELSE 0 END) as delegate_count,
SUM(CASE WHEN eisenhower_quadrant='eliminate' THEN 1 ELSE 0 END) as eliminate_count,
SUM(CASE WHEN category='opportunity' THEN 1 ELSE 0 END) as opportunities_count,
SUM(CASE WHEN category='risk' THEN 1 ELSE 0 END) as risks_count,
SUM(CASE WHEN requires_action=true THEN 1 ELSE 0 END) as actions_count
FROM admin.email_classifications")->fetch(PDO::FETCH_ASSOC);
// Unread today
$today_count = $pdo->query("SELECT COUNT(*) FROM admin.email_classifications WHERE DATE(received_at)='$today'")->fetchColumn();
echo json_encode([
'date' => $today,
'stats' => $stats,
'today_count' => (int)$today_count,
'urgent' => $urgent,
'scheduled' => $scheduled,
'opportunities' => $opportunities,
'risks' => $risks,
'deadlines' => $deadlines,
'actions' => $actions
]);
exit;
}
// === EISENHOWER MATRIX ===
if ($action === 'eisenhower') {
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$days = (int)($_GET['days'] ?? 7);
$q = $pdo->prepare("SELECT id, uid, folder, from_name, from_email, subject, received_at, category, urgency_score, importance_score, eisenhower_quadrant, requires_action, summary, suggested_action, extracted_deadlines FROM admin.email_classifications WHERE received_at > NOW() - INTERVAL '$days days' ORDER BY urgency_score DESC, importance_score DESC");
$q->execute();
$all = $q->fetchAll(PDO::FETCH_ASSOC);
$matrix = ['do_first'=>[], 'schedule'=>[], 'delegate'=>[], 'eliminate'=>[]];
foreach ($all as $item) {
$quad = $item['eisenhower_quadrant'] ?? 'eliminate';
if (isset($matrix[$quad])) $matrix[$quad][] = $item;
}
echo json_encode(['matrix' => $matrix, 'total' => count($all), 'days' => $days]);
exit;
}
// === DOCUMENT UPLOAD ===
if ($action === 'upload') {
$uploadDir = '/var/www/weval/wevia-ia/wevialife-data/documents/';
@mkdir($uploadDir, 0755, true);
if (!isset($_FILES['file'])) { echo json_encode(['error' => 'no file']); exit; }
$file = $_FILES['file'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed = ['pdf','doc','docx','xls','xlsx','ppt','pptx','txt','csv','jpg','jpeg','png','gif','md'];
if (!in_array($ext, $allowed)) { echo json_encode(['error' => 'type not allowed: '.$ext]); exit; }
$dest = $uploadDir . date('Ymd_His') . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $file['name']);
move_uploaded_file($file['tmp_name'], $dest);
// Extract text based on type
$text = '';
if ($ext === 'txt' || $ext === 'md' || $ext === 'csv') {
$text = file_get_contents($dest);
} elseif ($ext === 'pdf') {
$text = shell_exec("pdftotext '$dest' - 2>/dev/null") ?: '';
if (!trim($text)) {
// OCR fallback for scanned PDFs
$text = shell_exec("tesseract '$dest' stdout 2>/dev/null") ?: '';
}
} elseif (in_array($ext, ['docx','doc'])) {
// Extract text from DOCX via unzip
$tmpDir = sys_get_temp_dir() . '/docx_' . uniqid();
@mkdir($tmpDir, 0755, true);
shell_exec("unzip -o '$dest' -d '$tmpDir' 2>/dev/null");
$xmlFile = $tmpDir . '/word/document.xml';
if (file_exists($xmlFile)) {
$xml = file_get_contents($xmlFile);
$text = strip_tags(str_replace(['</w:p>', '</w:r>'], ["\n", ' '], $xml));
$text = preg_replace('/\s+/', ' ', $text);
}
shell_exec("rm -rf '$tmpDir'");
} elseif (in_array($ext, ['xlsx','xls','csv'])) {
if ($ext === 'csv') {
$text = file_get_contents($dest);
} else {
// Extract from xlsx via unzip
$tmpDir = sys_get_temp_dir() . '/xlsx_' . uniqid();
@mkdir($tmpDir, 0755, true);
shell_exec("unzip -o '$dest' -d '$tmpDir' 2>/dev/null");
$sheets = glob($tmpDir . '/xl/worksheets/sheet*.xml');
$text = '';
foreach ($sheets as $sheet) {
$xml = file_get_contents($sheet);
$text .= strip_tags($xml) . "\n";
}
// Also get shared strings
$ss = $tmpDir . '/xl/sharedStrings.xml';
if (file_exists($ss)) {
$xml = file_get_contents($ss);
$text .= strip_tags($xml);
}
shell_exec("rm -rf '$tmpDir'");
}
} elseif (in_array($ext, ['pptx','ppt'])) {
$tmpDir = sys_get_temp_dir() . '/pptx_' . uniqid();
@mkdir($tmpDir, 0755, true);
shell_exec("unzip -o '$dest' -d '$tmpDir' 2>/dev/null");
$slides = glob($tmpDir . '/ppt/slides/slide*.xml');
$text = '';
foreach ($slides as $slide) {
$xml = file_get_contents($slide);
$text .= strip_tags(str_replace(['</a:p>'], ["\n"], $xml)) . "\n---\n";
}
shell_exec("rm -rf '$tmpDir'");
} elseif (in_array($ext, ['jpg','jpeg','png','gif'])) {
// OCR on images
$text = shell_exec("tesseract '$dest' stdout 2>/dev/null") ?: '';
}
// For DOCX/XLSX/PPTX - will use PHPOffice later
// Store in DB
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$ins = $pdo->prepare("INSERT INTO admin.documents (filename, filepath, filetype, filesize, content_text, source) VALUES (?,?,?,?,?,?)");
$ins->execute([$file['name'], $dest, $ext, $file['size'], mb_substr($text, 0, 50000), 'upload']);
echo json_encode(['ok' => true, 'id' => $pdo->lastInsertId(), 'filename' => $file['name'], 'size' => $file['size']]);
exit;
}
// === DOCUMENTS LIST ===
if ($action === 'documents') {
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$docs = $pdo->query("SELECT id, filename, filetype, filesize, summary, indexed_at, source FROM admin.documents ORDER BY indexed_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['documents' => $docs, 'total' => count($docs)]);
exit;
}
// === CLASSIFY STATUS ===
if ($action === 'classify_status') {
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$total = $pdo->query("SELECT COUNT(*) FROM admin.email_classifications")->fetchColumn();
$byQuad = $pdo->query("SELECT eisenhower_quadrant, COUNT(*) as cnt FROM admin.email_classifications GROUP BY eisenhower_quadrant")->fetchAll(PDO::FETCH_ASSOC);
$byCat = $pdo->query("SELECT category, COUNT(*) as cnt FROM admin.email_classifications GROUP BY category ORDER BY cnt DESC")->fetchAll(PDO::FETCH_ASSOC);
$latest = $pdo->query("SELECT classified_at FROM admin.email_classifications ORDER BY classified_at DESC LIMIT 1")->fetchColumn();
echo json_encode(['total' => (int)$total, 'by_quadrant' => $byQuad, 'by_category' => $byCat, 'latest' => $latest]);
exit;
}
// WEVIA Life - RAG Chat + Enhanced Upload
if ($action === 'ai_chat') {
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$question = $input['message'] ?? $_REQUEST['message'] ?? '';
if (!$question) { echo json_encode(['error' => 'empty']); exit; }
require_once '/opt/wevads/vault/credentials.php';
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$context = "";
// Search emails by keywords
$words = array_filter(explode(' ', preg_replace('/[^a-zA-Z0-9 ]/ui', ' ', $question)));
$keywords = array_slice(array_filter($words, fn($w) => strlen($w) >= 3), 0, 5);
$emails = [];
if ($keywords) {
$clauses = []; $params = [];
foreach ($keywords as $kw) {
$clauses[] = "(LOWER(subject) LIKE ? OR LOWER(from_name) LIKE ? OR LOWER(summary) LIKE ?)";
$p = '%' . strtolower($kw) . '%';
$params = array_merge($params, [$p, $p, $p]);
}
$stmt = $pdo->prepare("SELECT from_name, from_email, subject, received_at, category, eisenhower_quadrant, summary, suggested_action, raw_body_preview FROM admin.email_classifications WHERE " . implode(' OR ', $clauses) . " ORDER BY received_at DESC LIMIT 10");
$stmt->execute($params);
$emails = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($emails) {
$context .= "=== EMAILS ===\n";
foreach ($emails as $e) {
$context .= "De: {$e['from_name']} | Objet: {$e['subject']} | Date: {$e['received_at']} | Cat: {$e['category']}\n";
if ($e['summary']) $context .= "Resume: {$e['summary']}\n";
if ($e['suggested_action']) $context .= "Action: {$e['suggested_action']}\n";
$context .= "---\n";
}
}
}
// Stats
$s = $pdo->query("SELECT COUNT(*) as t, SUM(CASE WHEN eisenhower_quadrant='do_first' THEN 1 ELSE 0 END) as u, SUM(CASE WHEN category LIKE '%opportunity%' THEN 1 ELSE 0 END) as o, SUM(CASE WHEN category LIKE '%risk%' THEN 1 ELSE 0 END) as r FROM admin.email_classifications")->fetch(PDO::FETCH_ASSOC);
$context .= "\nSTATS: {$s['t']} emails analyses, {$s['u']} urgents, {$s['o']} opportunites, {$s['r']} risques\n";
// Documents
$docs = $pdo->query("SELECT filename, filetype, summary, content_text FROM admin.documents WHERE content_text IS NOT NULL AND content_text != '' ORDER BY indexed_at DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
if ($docs) {
$context .= "\n=== DOCUMENTS ===\n";
foreach ($docs as $d) {
$context .= "Fichier: {$d['filename']} | Resume: " . mb_substr($d['summary'] ?? $d['content_text'], 0, 300) . "\n---\n";
}
}
// Recent important
$recent = $pdo->query("SELECT from_name, subject, summary, eisenhower_quadrant FROM admin.email_classifications WHERE eisenhower_quadrant IN ('do_first','schedule') ORDER BY received_at DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
if ($recent) {
$context .= "\n=== RECENTS IMPORTANTS ===\n";
foreach ($recent as $e) $context .= "{$e['from_name']}: {$e['subject']} [{$e['eisenhower_quadrant']}] {$e['summary']}\n";
}
// Call Groq 70B (fallback 8B)
$sys = "Tu es WEVIA Life, assistant executif IA souverain de WEVAL Consulting. Tu assistes Yacine Mahboub (CEO) et son equipe dirigeante (Ambre: Pharma/Marketing, Kaouther: Ethica Tunisie).
WEVAL = cabinet conseil transformation digitale (SAP, Cloud, IA, Cybersecurite, Supply Chain, Life Sciences). Casablanca HQ, Paris, 8 pays, 200+ projets, partenaires SAP/Vistex/Huawei Cloud/IQVIA.
Produits: WEVADS (217K emails/jour, 7.3M contacts), Ethica (48,899 HCPs MA/TN/DZ, 98%% emails), WEVIA (51 modeles Ollama GPU souverain), CRM (7 companies pipeline).
REGLES: Reponds en francais, concis et actionnable. Structure: bullet points decisions, tableaux comparaisons. Propose 2-3 options concretes. Cite les sources (expediteur, date). Signale les risques business. ZERO envoi automatique sur tous les serveurs - tout envoi valide manuellement.";
$models = ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant'];
$reply = null;
foreach ($models as $model) {
$ch = curl_init('https://api.groq.com/openai/v1/chat/completions');
curl_setopt_array($ch, [CURLOPT_HTTPHEADER=>['Authorization: Bearer '.GROQ_KEY,'Content-Type: application/json'],CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode(['model'=>$model,'messages'=>[['role'=>'system','content'=>$sys],['role'=>'user','content'=>"CONTEXTE:\n".mb_substr($context,0,4000)."\n\nQUESTION: $question"]],'temperature'=>0.3,'max_tokens'=>800]),CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>25]);
$resp = curl_exec($ch); $hc = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($hc === 200) {
$data = json_decode($resp, true);
$reply = $data['choices'][0]['message']['content'] ?? null;
if ($reply) break;
}
}
echo json_encode(['reply' => $reply ?? 'Service temporairement indisponible.', 'context_emails' => count($emails), 'context_docs' => count($docs)]);
exit;
}
// === ENHANCED UPLOAD WITH PARSING ===
if ($action === 'upload_parse') {
$uploadDir = '/var/www/weval/wevia-ia/wevialife-data/documents/';
@mkdir($uploadDir, 0755, true);
if (!isset($_FILES['file'])) { echo json_encode(['error' => 'no file']); exit; }
$file = $_FILES['file'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$dest = $uploadDir . date('Ymd_His') . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $file['name']);
move_uploaded_file($file['tmp_name'], $dest);
// Parse text
require_once '/var/www/weval/wevia-ia/scripts/doc-parser.php';
$parsed = parseDocument($dest, $ext);
$analysis = analyzeDocument($parsed['text'], $file['name']);
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'", "postgres", "");
$ins = $pdo->prepare("INSERT INTO admin.documents (filename,filepath,filetype,filesize,content_text,summary,extracted_deadlines,extracted_actions,page_count,source) VALUES (?,?,?,?,?,?,?,?,?,?)");
$ins->execute([$file['name'], $dest, $ext, $file['size'], mb_substr($parsed['text'], 0, 50000), $analysis['summary'] ?? '', json_encode($analysis['deadlines'] ?? []), json_encode($analysis['actions'] ?? []), $parsed['pages'], 'upload']);
// Create action items from document
if (!empty($analysis['actions'])) {
foreach ($analysis['actions'] as $act) {
$pdo->prepare("INSERT INTO admin.action_items (title, description, source_type, source_id, urgency_score, importance_score, eisenhower_quadrant, status) VALUES (?,?,?,?,?,?,?,?)")->execute([
$act['title'] ?? 'Action', $file['name'], 'document', $pdo->lastInsertId(),
$act['priority'] === 'high' ? 4 : ($act['priority'] === 'medium' ? 3 : 2),
$act['priority'] === 'high' ? 4 : ($act['priority'] === 'medium' ? 3 : 2),
$act['priority'] === 'high' ? 'do_first' : 'schedule', 'pending'
]);
}
}
echo json_encode(['ok'=>true, 'id'=>$pdo->lastInsertId(), 'filename'=>$file['name'], 'pages'=>$parsed['pages'], 'text_length'=>strlen($parsed['text']), 'summary'=>$analysis['summary']??'', 'actions'=>$analysis['actions']??[]]);
exit;
}
if ($action === 'email_sources') {
$sources = loadEmailSources($emailSourcesFile);
// Don't expose passwords
$safe = array_map(function($s) {
return ['id'=>$s['id'],'name'=>$s['name'],'email'=>$s['email'],'type'=>$s['type'],'server'=>$s['server'],'status'=>$s['status']??'configured','last_sync'=>$s['last_sync']??null,'count'=>$s['count']??0];
}, $sources);
echo json_encode(['sources' => $safe]);
exit;
}
if ($action === 'email_connect') {
$input = json_decode(file_get_contents('php://input'), true);
$sources = loadEmailSources($emailSourcesFile);
$source = [
'id' => 'src_' . uniqid(),
'name' => $input['name'] ?? '',
'email' => $input['email'] ?? '',
'type' => $input['type'] ?? 'imap',
'server' => $input['server'] ?? '',
'port' => (int)($input['port'] ?? 993),
'password' => base64_encode($input['password'] ?? ''),
'ssl' => $input['ssl'] ?? true,
'status' => 'testing',
'created_at' => date('Y-m-d H:i:s')
];
// Test connection
$mailbox = '{' . $source['server'] . ':' . $source['port'] . '/imap/ssl/novalidate-cert}INBOX';
$conn = @imap_open($mailbox, $source['email'], base64_decode($source['password']), 0, 1);
if ($conn) {
$info = imap_check($conn);
$source['status'] = 'active';
$source['count'] = $info->Nmsgs ?? 0;
$source['last_sync'] = date('Y-m-d H:i:s');
imap_close($conn);
} else {
$source['status'] = 'error';
$source['error'] = imap_last_error();
}
// Remove existing source with same email
$sources = array_values(array_filter($sources, fn($s) => $s['email'] !== $source['email']));
$sources[] = $source;
saveEmailSources($emailSourcesFile, $sources);
echo json_encode(['ok' => true, 'status' => $source['status'], 'error' => $source['error'] ?? null, 'count' => $source['count'] ?? 0]);
exit;
}
if ($action === 'email_disconnect') {
$id = $_GET['id'] ?? '';
$sources = loadEmailSources($emailSourcesFile);
$sources = array_values(array_filter($sources, fn($s) => $s['id'] !== $id));
saveEmailSources($emailSourcesFile, $sources);
echo json_encode(['ok' => true]);
exit;
}
if ($action === 'email_fetch') {
$sources = loadEmailSources($emailSourcesFile);
$allEmails = [];
$limit = (int)($_GET['limit'] ?? 50);
$folders = ["INBOX"];
foreach ($sources as $s) {
if ($s['status'] !== 'active') continue;
$pwd = base64_decode($s['password'] ?? '');
$server = $s['server'] ?? 'server105.web-hosting.com';
$port = $s['port'] ?? 993;
foreach ($folders as $folder) {
$mb = "{{$server}:{$port}/imap/ssl/novalidate-cert}{$folder}";
$conn = @imap_open($mb, $s['email'], $pwd, 0, 1);
if (!$conn) continue;
$info = imap_check($conn);
$total = $info->Nmsgs;
$start = max(1, $total - $limit + 1);
$ov = imap_fetch_overview($conn, "$start:$total", 0);
foreach (array_reverse($ov ?: []) as $o) {
$allEmails[] = [
'uid' => $o->uid,
'from' => isset($o->from) ? mb_decode_mimeheader($o->from) : '',
'subject' => isset($o->subject) ? mb_decode_mimeheader($o->subject) : '',
'date' => $o->date ?? '',
'seen' => ($o->seen ?? 0) ? true : false,
'folder' => $folder,
'source' => $s['email'],
'source_id' => $s['id']
];
}
imap_close($conn);
}
}
usort($allEmails, function($a, $b) { return strtotime($b['date'] ?? '0') - strtotime($a['date'] ?? '0'); });
echo json_encode(['emails' => $allEmails, 'total' => count($allEmails)]);
exit;
}
if (false && $action === 'email_fetch_disabled') {
$srcId = $_GET['source'] ?? '';
$page = (int)($_GET['page'] ?? 1);
$perPage = 20;
$search = $_GET['q'] ?? '';
$sources = loadEmailSources($emailSourcesFile);
$source = null;
foreach ($sources as $s) { if ($s['id'] === $srcId || $s['email'] === $srcId) { $source = $s; break; } }
if (!$source) {
// If no specific source, try all active sources
$allEmails = [];
foreach ($sources as $s) {
if ($s['status'] !== 'active') continue;
$mailbox = '{' . $s['server'] . ':' . $s['port'] . '/imap/ssl/novalidate-cert}INBOX';
$conn = @imap_open($mailbox, $s['email'], base64_decode($s['password']), 0, 1);
if (!$conn) continue;
$emails = fetchEmails($conn, $s, $search, $perPage);
$allEmails = array_merge($allEmails, $emails);
imap_close($conn);
}
usort($allEmails, fn($a,$b) => strtotime($b['date']) - strtotime($a['date']));
echo json_encode(['emails' => array_slice($allEmails, 0, $perPage), 'total' => count($allEmails)]);
exit;
}
$mailbox = '{' . $source['server'] . ':' . $source['port'] . '/imap/ssl/novalidate-cert}INBOX';
$conn = @imap_open($mailbox, $source['email'], base64_decode($source['password']), 0, 1);
if (!$conn) {
echo json_encode(['error' => 'Connection failed: ' . imap_last_error()]);
exit;
}
$emails = fetchEmails($conn, $source, $search, $perPage);
$info = imap_check($conn);
imap_close($conn);
echo json_encode(['emails' => $emails, 'total' => $info->Nmsgs ?? 0, 'source' => $source['email']]);
exit;
}
if ($action === 'email_read') {
$srcId = $_GET['source'] ?? '';
$uid = (int)($_GET['uid'] ?? 0);
$sources = loadEmailSources($emailSourcesFile);
$source = null;
foreach ($sources as $s) { if ($s['id'] === $srcId || $s['email'] === $srcId) { $source = $s; break; } }
if (!$source || !$uid) { echo json_encode(['error' => 'Invalid']); exit; }
$folder = $_GET['folder'] ?? 'INBOX';
$url = 'http://10.1.0.3:5890/api/imap-proxy.php?a=read&e=' . urlencode($source['email']) . '&sv=' . urlencode($source['server']) . '&pw=' . urlencode($source['password']) . '&uid=' . $uid . '&folder=' . urlencode($folder);
$ctx = stream_context_create(['http' => ['timeout' => 30]]);
$resp = @file_get_contents($url, false, $ctx);
if ($resp) { $d = json_decode($resp, true); if ($d) { $d['source'] = $source['email']; echo json_encode($d); exit; } }
echo json_encode(['error' => 'proxy failed']);
exit;
}
if (false && $action === 'email_read_disabled') {
$srcId = $_GET['source'] ?? '';
$uid = (int)($_GET['uid'] ?? 0);
$sources = loadEmailSources($emailSourcesFile);
$source = null;
foreach ($sources as $s) { if ($s['id'] === $srcId || $s['email'] === $srcId) { $source = $s; break; } }
if (!$source || !$uid) { echo json_encode(['error' => 'Invalid']); exit; }
$mailbox = '{' . $source['server'] . ':' . $source['port'] . '/imap/ssl/novalidate-cert}INBOX';
$conn = @imap_open($mailbox, $source['email'], base64_decode($source['password']), 0, 1);
if (!$conn) { echo json_encode(['error' => 'Connection failed']); exit; }
$msgno = imap_msgno($conn, $uid);
if (!$msgno) { imap_close($conn); echo json_encode(['error' => 'Message not found']); exit; }
$header = imap_headerinfo($conn, $msgno);
$body = getEmailBody($conn, $msgno);
imap_close($conn);
echo json_encode([
'uid' => $uid,
'from' => decodeHeader($header->fromaddress ?? ''),
'to' => decodeHeader($header->toaddress ?? ''),
'subject' => decodeHeader($header->subject ?? ''),
'date' => date('Y-m-d H:i:s', strtotime($header->date ?? '')),
'body' => $body,
'source' => $source['email']
]);
exit;
}
if ($action === 'email_search') {
$q = $_GET['q'] ?? '';
if (!$q) { echo json_encode(['emails' => []]); exit; }
$sources = loadEmailSources($emailSourcesFile);
$allEmails = [];
foreach ($sources as $s) {
if ($s['status'] !== 'active') continue;
$mailbox = '{' . $s['server'] . ':' . $s['port'] . '/imap/ssl/novalidate-cert}INBOX';
$conn = @imap_open($mailbox, $s['email'], base64_decode($s['password']), 0, 1);
if (!$conn) continue;
$emails = fetchEmails($conn, $s, $q, 30);
$allEmails = array_merge($allEmails, $emails);
imap_close($conn);
}
usort($allEmails, fn($a,$b) => strtotime($b['date']) - strtotime($a['date']));
echo json_encode(['emails' => array_slice($allEmails, 0, 30), 'query' => $q]);
exit;
}
// === HELPER FUNCTIONS ===
function fetchEmails($conn, $source, $search = '', $limit = 20) {
$emails = [];
if ($search) {
$uids = imap_search($conn, 'SUBJECT "' . addslashes($search) . '"', SE_UID);
if (!$uids) $uids = imap_search($conn, 'FROM "' . addslashes($search) . '"', SE_UID);
if (!$uids) $uids = imap_search($conn, 'BODY "' . addslashes($search) . '"', SE_UID);
if (!$uids) return [];
$uids = array_reverse($uids);
$uids = array_slice($uids, 0, $limit);
} else {
$info = imap_check($conn);
$total = $info->Nmsgs;
if ($total == 0) return [];
$start = max(1, $total - $limit + 1);
$range = "$start:$total";
$overview = imap_fetch_overview($conn, $range, 0);
if (!$overview) return [];
$uids = array_map(fn($o) => $o->uid, $overview);
$uids = array_reverse($uids);
}
foreach ($uids as $uid) {
$msgno = @imap_msgno($conn, $uid);
if (!$msgno) continue;
$header = @imap_headerinfo($conn, $msgno);
if (!$header) continue;
$emails[] = [
'uid' => $uid,
'from' => decodeHeader($header->fromaddress ?? ''),
'from_email' => $header->from[0]->mailbox . '@' . ($header->from[0]->host ?? ''),
'to' => decodeHeader($header->toaddress ?? ''),
'subject' => decodeHeader($header->subject ?? '(sans sujet)'),
'date' => date('Y-m-d H:i:s', strtotime($header->date ?? '')),
'seen' => ($header->Unseen ?? '') !== 'U',
'size' => $header->Size ?? 0,
'source' => $source['email'],
'source_id' => $source['id']
];
}
return $emails;
}
function getEmailBody($conn, $msgno) {
$struct = imap_fetchstructure($conn, $msgno);
$body = '';
if ($struct->type == 0) { // Simple
$body = imap_fetchbody($conn, $msgno, 1);
if ($struct->encoding == 3) $body = base64_decode($body);
if ($struct->encoding == 4) $body = quoted_printable_decode($body);
} else { // Multipart
$body = getMultipartBody($conn, $msgno, $struct);
}
// Try to detect charset and convert to UTF-8
if ($struct->parameters ?? null) {
foreach ($struct->parameters as $p) {
if (strtolower($p->attribute) === 'charset' && strtolower($p->value) !== 'utf-8') {
$body = @mb_convert_encoding($body, 'UTF-8', $p->value) ?: $body;
}
}
}
return mb_substr($body, 0, 50000); // Limit size
}
function getMultipartBody($conn, $msgno, $struct, $prefix = '') {
$body = '';
foreach ($struct->parts as $i => $part) {
$partNum = $prefix ? "$prefix." . ($i + 1) : ($i + 1);
if ($part->type == 0 && ($part->subtype === 'HTML' || $part->subtype === 'PLAIN')) {
$text = imap_fetchbody($conn, $msgno, $partNum);
if ($part->encoding == 3) $text = base64_decode($text);
if ($part->encoding == 4) $text = quoted_printable_decode($text);
if ($part->subtype === 'HTML') return $text;
$body = $text;
}
if (isset($part->parts)) {
$sub = getMultipartBody($conn, $msgno, $part, $partNum);
if ($sub) return $sub;
}
}
return $body;
}
function decodeHeader($str) {
$decoded = imap_mime_header_decode($str);
$result = '';
foreach ($decoded as $part) {
$charset = $part->charset;
$text = $part->text;
if ($charset && $charset !== 'default' && strtolower($charset) !== 'utf-8') {
$text = @mb_convert_encoding($text, 'UTF-8', $charset) ?: $text;
}
$result .= $text;
}
return $result ?: $str;
}
// EMAIL RESPONSE DRAFTER GLM5 Alibaba cascade
if ($action === 'draft_response') {
$uid = $_REQUEST['uid'] ?? ''; $folder = $_REQUEST['folder'] ?? 'INBOX'; $tone = $_REQUEST['tone'] ?? 'professional'; $instructions = $_REQUEST['instructions'] ?? '';
if (!$uid) { echo json_encode(['error'=>'uid required']); exit; }
$classif = null;
try { $pdo_c = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'","postgres",""); $st = $pdo_c->prepare("SELECT * FROM admin.email_classifications WHERE uid=? AND folder=?"); $st->execute([$uid, $folder]); $classif = $st->fetch(PDO::FETCH_ASSOC); } catch(Exception $e) {}
$sf = "/var/www/weval/wevia-ia/wevialife-data/email-sources.json"; $sources = file_exists($sf) ? json_decode(file_get_contents($sf), true) : [];
$email_body = ''; $email_from = $classif['from_email'] ?? ''; $email_subject = $classif['subject'] ?? ''; $email_date = $classif['received_at'] ?? '';
foreach ($sources as $src) { if ($src['status'] !== 'active') continue; $params = 'e='.urlencode($src['email']).'&sv='.urlencode($src['server']).'&pw='.urlencode($src['password']); $url = "http://10.1.0.3:5890/api/imap-proxy.php?a=read&$params&folder=$folder&uid=$uid"; $resp = @file_get_contents($url, false, stream_context_create(['http'=>['timeout'=>15]])); if ($resp) { $data = json_decode($resp, true); if ($data && isset($data['body'])) { $email_body = strip_tags($data['body']); $email_from = $data['from'] ?? $email_from; $email_subject = $data['subject'] ?? $email_subject; break; } } }
$history = [];
if ($email_from) { try { $fc = preg_replace('/.*<|>.*/', '', $email_from); $st2 = $pdo_c->prepare("SELECT subject, category, summary, eisenhower_quadrant, received_at FROM admin.email_classifications WHERE from_email LIKE ? ORDER BY received_at DESC LIMIT 5"); $st2->execute(['%'.trim($fc).'%']); $history = $st2->fetchAll(PDO::FETCH_ASSOC); } catch(Exception $e) {} }
$ht = ''; if ($history) { $ht = "\nHISTORIQUE:\n"; foreach ($history as $h) { $ht .= "- [{$h['received_at']}] {$h['subject']} ({$h['category']}): {$h['summary']}\n"; } }
$urg = $classif ? "Urgence: {$classif['urgency_score']}/5, Quadrant: {$classif['eisenhower_quadrant']}, Cat: {$classif['category']}" : "Non classifie";
$sug = $classif['suggested_action'] ?? '';
$tones = ['professional'=>'professionnel et courtois','friendly'=>'amical et chaleureux','formal'=>'formel et institutionnel','brief'=>'bref et direct (max 3 phrases)'];
$td = $tones[$tone] ?? 'professionnel';
$sp = "Tu es l'assistant executif de Yacine Mahboub, CEO de WEVAL Consulting. Redige des reponses email au nom de Yacine. Ton: $td. Sig: Yacine Mahboub | CEO WEVAL Consulting | +212 6 57 78 52 92";
$up = "Redige une reponse:\nDE: $email_from\nSUJET: $email_subject\nDATE: $email_date\n$urg\n".($sug?"ACTION: $sug\n":"")."$ht\nCONTENU:\n".mb_substr($email_body,0,2000)."\n".($instructions?"INSTRUCTIONS: $instructions\n":"")."\nRedige UNIQUEMENT le corps. Francais sauf si email en anglais.";
$draft = null; $prov = 'none';
$nk = trim(@file_get_contents('/var/www/html/api/blade-tasks/nvidia-key.txt'));
$pvs = [
['n'=>'Sovereign-Cerebras','u'=>'http://127.0.0.1:4000/v1/chat/completions','k'=>'local','m'=>'auto','t'=>10,'x'=>[]],
['n'=>'GLM-5','u'=>'https://integrate.api.nvidia.com/v1/chat/completions','k'=>$nk,'m'=>'z-ai/glm5','t'=>15,'x'=>['stream'=>false]],
['n'=>'Alibaba','u'=>'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions','k'=>'sk-34db1ad3152443cd86563d1bfc576c30','m'=>'qwen-plus','t'=>15,'x'=>[]],
['n'=>'Ollama-WEVIA','u'=>'http://127.0.0.1:11434/v1/chat/completions','k'=>'local','m'=>'weval-brain-v3','t'=>30,'x'=>[]]
];
foreach ($pvs as $p) { if (!$p['k'] && $p['n'] !== 'Sovereign-Cerebras' && $p['n'] !== 'Ollama-WEVIA') continue; $b = array_merge(['model'=>$p['m'],'temperature'=>0.4,'max_tokens'=>800,'messages'=>[['role'=>'system','content'=>$sp],['role'=>'user','content'=>$up]]], $p['x']); $ch = curl_init($p['u']); curl_setopt_array($ch, [CURLOPT_HTTPHEADER=>(in_array($p['n'],['Sovereign-Cerebras','Ollama-WEVIA']) ? ['Content-Type: application/json'] : ['Authorization: Bearer '.$p['k'],'Content-Type: application/json']),CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode($b),CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>$p['t']]); $r = curl_exec($ch); $hc = curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch); if ($hc===200 && $r) { $d=json_decode($r,true); $t=trim($d['choices'][0]['message']['content']??''); $t=preg_replace('/^```\w*\s*/','',trim($t)); $t=preg_replace('/\s*```$/','',$t); if(strlen($t)>20){$draft=$t;$prov=$p['n'];break;} } }
echo json_encode(['ok'=>(bool)$draft,'draft'=>$draft?:'Erreur generation.','provider'=>$prov,'email'=>['from'=>$email_from,'subject'=>$email_subject,'date'=>$email_date],'classification'=>$classif?['category'=>$classif['category'],'urgency'=>$classif['urgency_score'],'quadrant'=>$classif['eisenhower_quadrant'],'summary'=>$classif['summary']]:null,'tone'=>$tone,'history_count'=>count($history)],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); exit;
}
if ($action === 'all_emails') {
$limit = intval($_REQUEST['limit'] ?? 50); $offset = intval($_REQUEST['offset'] ?? 0);
try { $pdo_c = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'","postgres","");
$total = $pdo_c->query("SELECT count(*) FROM admin.email_classifications")->fetchColumn();
$st = $pdo_c->prepare("SELECT uid, folder, from_email, from_name, subject, received_at, category, urgency_score, importance_score, eisenhower_quadrant, requires_action, summary, suggested_action FROM admin.email_classifications ORDER BY urgency_score DESC, received_at DESC LIMIT ? OFFSET ?"); $st->execute([$limit, $offset]); $emails = $st->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['ok'=>true,'count'=>count($emails),'total'=>(int)$total,'offset'=>$offset,'emails'=>$emails],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); } catch(Exception $e) { echo json_encode(['error'=>$e->getMessage()]); } exit;
}
if ($action === 'urgent_to_respond') {
$limit = intval($_REQUEST['limit'] ?? 10); $offset = intval($_REQUEST['offset'] ?? 0);
try { $pdo_c = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'","postgres",""); $st = $pdo_c->prepare("SELECT uid, folder, from_email, from_name, subject, received_at, category, urgency_score, importance_score, eisenhower_quadrant, requires_action, summary, suggested_action FROM admin.email_classifications WHERE eisenhower_quadrant IN ('do_first','schedule') AND requires_action = true ORDER BY CASE eisenhower_quadrant WHEN 'do_first' THEN 1 WHEN 'schedule' THEN 2 ELSE 3 END, urgency_score DESC, importance_score DESC LIMIT ? OFFSET ?"); $st->execute([$limit, $offset]); $emails = $st->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['ok'=>true,'count'=>count($emails),'emails'=>$emails],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); } catch(Exception $e) { echo json_encode(['error'=>$e->getMessage()]); } exit;
}
if ($action === 'batch_draft') {
$limit = intval($_REQUEST['limit'] ?? 5); $tone = $_REQUEST['tone'] ?? 'professional';
try { $pdo_c = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db;options='--search_path=admin,public'","postgres",""); $st = $pdo_c->prepare("SELECT uid, folder, from_email, subject, urgency_score, eisenhower_quadrant, summary FROM admin.email_classifications WHERE eisenhower_quadrant IN ('do_first','schedule') AND requires_action = true ORDER BY CASE eisenhower_quadrant WHEN 'do_first' THEN 1 ELSE 2 END, urgency_score DESC LIMIT ?"); $st->execute([$limit]); $emails = $st->fetchAll(PDO::FETCH_ASSOC);
$results = [];
foreach ($emails as $em) { $url = "https://weval-consulting.com/products/wevialife-api.php?action=draft_response&uid=".urlencode($em['uid'])."&folder=".urlencode($em['folder'])."&tone=$tone"; $r = @file_get_contents($url, false, stream_context_create(['http'=>['timeout'=>25],'ssl'=>['verify_peer'=>false,'verify_peer_name'=>false]])); $d = $r ? json_decode($r, true) : null; $results[] = ['uid'=>$em['uid'],'subject'=>$em['subject'],'from'=>$em['from_email'],'urgency'=>$em['urgency_score'],'quadrant'=>$em['eisenhower_quadrant'],'draft'=>$d['draft']??'Erreur','provider'=>$d['provider']??'none']; usleep(1000000); }
echo json_encode(['ok'=>true,'count'=>count($results),'drafts'=>$results],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); } catch(Exception $e) { echo json_encode(['error'=>$e->getMessage()]); } exit;
}
switch($action) {
case 'sources':
$docs = [];
$f = "$dataDir/documents.json";
if(file_exists($f)) $docs = json_decode(file_get_contents($f), true) ?: [];
echo json_encode(['documents' => $docs, 'count' => count($docs)]);
break;
case 'vault_list':
$items = [];
$f = "$dataDir/vault.json";
if(file_exists($f)) $items = json_decode(file_get_contents($f), true) ?: [];
echo json_encode(['items' => $items, 'count' => count($items)]);
break;
case 'alerts':
echo json_encode(['alerts' => [], 'count' => 0]);
break;
case 'chat':
$input = json_decode(file_get_contents('php://input'), true);
$msg = $input['message'] ?? '';
if(!$msg) { echo json_encode(['error' => 'no message']); break; }
// Proxy to WEVIA chatbot
$ch = curl_init('https://127.0.0.1/api/weval-ia');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Host: weval-consulting.com'],
CURLOPT_POSTFIELDS => json_encode(['message' => $msg, 'conversationId' => 'wevialife-' . uniqid(), 'mode' => 'widget']),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0
]);
$resp = curl_exec($ch);
curl_close($ch);
$data = json_decode($resp, true);
echo json_encode([
'reply' => $data['response'] ?? 'Service temporairement indisponible.',
'response' => $data['response'] ?? 'Service temporairement indisponible.',
'provider' => 'WEVIA Life',
'model' => $data['provider'] ?? 'WEVIA',
'context_docs' => 0,
'sources_used' => []
]);
break;
case 'upload':
if(empty($_FILES['file'])) { echo json_encode(['error' => 'no file']); break; }
$file = $_FILES['file'];
$id = uniqid('doc_');
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$dest = "$dataDir/$id.$ext";
move_uploaded_file($file['tmp_name'], $dest);
$f = "$dataDir/documents.json";
$docs = file_exists($f) ? json_decode(file_get_contents($f), true) ?: [] : [];
$docs[] = ['id' => $id, 'name' => $file['name'], 'size' => $file['size'], 'type' => $ext, 'path' => $dest, 'created' => date('Y-m-d H:i:s')];
file_put_contents($f, json_encode($docs, JSON_PRETTY_PRINT));
echo json_encode(['ok' => true, 'id' => $id, 'name' => $file['name']]);
break;
case 'delete_doc':
$id = $_GET['id'] ?? '';
$f = "$dataDir/documents.json";
$docs = file_exists($f) ? json_decode(file_get_contents($f), true) ?: [] : [];
$docs = array_values(array_filter($docs, function($d) use($id) { return $d['id'] !== $id; }));
file_put_contents($f, json_encode($docs, JSON_PRETTY_PRINT));
echo json_encode(['ok' => true]);
break;
case 'vault_add':
$input = json_decode(file_get_contents('php://input'), true);
$f = "$dataDir/vault.json";
$items = file_exists($f) ? json_decode(file_get_contents($f), true) ?: [] : [];
$item = ['id' => uniqid('v_'), 'title' => $input['title'] ?? '', 'content' => $input['content'] ?? '', 'tags' => $input['tags'] ?? [], 'created' => date('Y-m-d H:i:s')];
$items[] = $item;
file_put_contents($f, json_encode($items, JSON_PRETTY_PRINT));
echo json_encode(['ok' => true, 'item' => $item]);
break;
case 'vault_get':
$id = $_GET['id'] ?? '';
$f = "$dataDir/vault.json";
$items = file_exists($f) ? json_decode(file_get_contents($f), true) ?: [] : [];
$found = null;
foreach($items as $i) { if($i['id'] === $id) { $found = $i; break; } }
echo json_encode($found ?: ['error' => 'not found']);
break;
case 'vault_delete':
$id = $_GET['id'] ?? '';
$f = "$dataDir/vault.json";
$items = file_exists($f) ? json_decode(file_get_contents($f), true) ?: [] : [];
$items = array_values(array_filter($items, function($i) use($id) { return $i['id'] !== $id; }));
file_put_contents($f, json_encode($items, JSON_PRETTY_PRINT));
echo json_encode(['ok' => true]);
break;
default:
echo json_encode(['status' => 'ok', 'version' => '1.0', 'app' => 'WEVIA Life']);
}