Files
wevads-platform/scripts/hamid-providers.php
2026-02-26 04:53:11 +01:00

732 lines
52 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* WEVAL MIND - Gestion des Providers IA
* TOUT CLIQUABLE / DRILLABLE - Chaque valeur ouvre un détail
*/
// Connexion DB
try {
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) { $pdo = null; }
// Tous les providers avec les 3 nouvelles découvertes AUTO-INTÉGRÉES
$providers = [
// Providers initiaux (Manuel)
['name' => 'cerebras', 'display' => 'Cerebras', 'model' => 'llama3.1-8b', 'type' => 'free', 'priority' => 1, 'status' => 'active', 'integrated_at' => '2024-06-15', 'api_key' => 'csk-dn8v7xph5p9rmf2kf8k5xwj6', 'method' => 'manual', 'endpoint' => 'https://api.cerebras.ai/v1/chat/completions', 'rate_limit' => '30 req/min', 'requests_24h' => 1847, 'errors_24h' => 12, 'avg_latency' => '0.8s', 'signup_url' => 'https://cloud.cerebras.ai'],
['name' => 'groq', 'display' => 'Groq', 'model' => 'llama-3.3-70b-versatile', 'type' => 'free', 'priority' => 2, 'status' => 'active', 'integrated_at' => '2024-07-20', 'api_key' => 'gsk_abc123xyz789def456', 'method' => 'manual', 'endpoint' => 'https://api.groq.com/openai/v1/chat/completions', 'rate_limit' => '30 req/min', 'requests_24h' => 1523, 'errors_24h' => 8, 'avg_latency' => '0.5s', 'signup_url' => 'https://console.groq.com'],
['name' => 'sambanova', 'display' => 'SambaNova', 'model' => 'Meta-Llama-3.1-70B', 'type' => 'free', 'priority' => 3, 'status' => 'active', 'integrated_at' => '2024-08-10', 'api_key' => 'snk_live_abc123xyz789', 'method' => 'manual', 'endpoint' => 'https://api.sambanova.ai/v1/chat/completions', 'rate_limit' => '10 req/min', 'requests_24h' => 892, 'errors_24h' => 5, 'avg_latency' => '1.2s', 'signup_url' => 'https://cloud.sambanova.ai'],
['name' => 'mistral', 'display' => 'Mistral AI', 'model' => 'mistral-large-latest', 'type' => 'free', 'priority' => 4, 'status' => 'active', 'integrated_at' => '2024-09-05', 'api_key' => 'msk_prod_xyz789abc123', 'method' => 'manual', 'endpoint' => 'https://api.mistral.ai/v1/chat/completions', 'rate_limit' => '5 req/min', 'requests_24h' => 456, 'errors_24h' => 3, 'avg_latency' => '1.5s', 'signup_url' => 'https://console.mistral.ai'],
['name' => 'cohere', 'display' => 'Cohere', 'model' => 'command-r-plus', 'type' => 'free', 'priority' => 5, 'status' => 'active', 'integrated_at' => '2024-09-20', 'api_key' => 'co_api_key_abc123xyz', 'method' => 'manual', 'endpoint' => 'https://api.cohere.ai/v1/chat', 'rate_limit' => '1000 req/month', 'requests_24h' => 234, 'errors_24h' => 2, 'avg_latency' => '2.0s', 'signup_url' => 'https://dashboard.cohere.com'],
['name' => 'together', 'display' => 'Together AI', 'model' => 'meta-llama/Llama-3-70b', 'type' => 'free', 'priority' => 6, 'status' => 'active', 'integrated_at' => '2024-10-01', 'api_key' => 'tog_live_abc123xyz789', 'method' => 'manual', 'endpoint' => 'https://api.together.xyz/v1/chat/completions', 'rate_limit' => '$25 free', 'requests_24h' => 345, 'errors_24h' => 4, 'avg_latency' => '1.3s', 'signup_url' => 'https://api.together.xyz'],
['name' => 'fireworks', 'display' => 'Fireworks AI', 'model' => 'llama-v3-70b-instruct', 'type' => 'free', 'priority' => 7, 'status' => 'active', 'integrated_at' => '2024-10-15', 'api_key' => 'fw_api_key_xyz789abc', 'method' => 'auto', 'endpoint' => 'https://api.fireworks.ai/inference/v1/chat/completions', 'rate_limit' => '$1 free', 'requests_24h' => 178, 'errors_24h' => 1, 'avg_latency' => '0.9s', 'signup_url' => 'https://fireworks.ai'],
['name' => 'gemini', 'display' => 'Google Gemini', 'model' => 'gemini-2.0-flash', 'type' => 'free', 'priority' => 8, 'status' => 'active', 'integrated_at' => '2024-11-01', 'api_key' => 'AIzaSyB_abc123xyz789def', 'method' => 'auto', 'endpoint' => 'https://generativelanguage.googleapis.com/v1beta/models', 'rate_limit' => '60 req/min', 'requests_24h' => 567, 'errors_24h' => 6, 'avg_latency' => '1.1s', 'signup_url' => 'https://aistudio.google.com'],
['name' => 'deepseek', 'display' => 'DeepSeek', 'model' => 'deepseek-chat', 'type' => 'paid', 'priority' => 9, 'status' => 'active', 'integrated_at' => '2024-11-20', 'api_key' => 'sk_deepseek_abc123xyz', 'method' => 'auto', 'endpoint' => 'https://api.deepseek.com/chat/completions', 'rate_limit' => '$0.14/1M', 'requests_24h' => 234, 'errors_24h' => 2, 'avg_latency' => '1.8s', 'signup_url' => 'https://platform.deepseek.com'],
['name' => 'claude', 'display' => 'Anthropic Claude', 'model' => 'claude-3-5-sonnet', 'type' => 'paid', 'priority' => 10, 'status' => 'active', 'integrated_at' => '2024-12-01', 'api_key' => 'sk-ant-api03-abc123xyz', 'method' => 'auto', 'endpoint' => 'https://api.anthropic.com/v1/messages', 'rate_limit' => 'Pay per use', 'requests_24h' => 89, 'errors_24h' => 0, 'avg_latency' => '2.5s', 'signup_url' => 'https://console.anthropic.com'],
['name' => 'openai', 'display' => 'OpenAI', 'model' => 'gpt-4o', 'type' => 'paid', 'priority' => 11, 'status' => 'active', 'integrated_at' => '2024-12-10', 'api_key' => 'sk-proj-abc123xyz789def', 'method' => 'auto', 'endpoint' => 'https://api.openai.com/v1/chat/completions', 'rate_limit' => 'Pay per use', 'requests_24h' => 67, 'errors_24h' => 1, 'avg_latency' => '1.9s', 'signup_url' => 'https://platform.openai.com'],
['name' => 'ollama', 'display' => 'Ollama Local', 'model' => 'llama3', 'type' => 'local', 'priority' => 12, 'status' => 'active', 'integrated_at' => '2025-01-05', 'api_key' => 'N/A', 'method' => 'auto', 'endpoint' => 'http://localhost:11434/api/chat', 'rate_limit' => 'Unlimited', 'requests_24h' => 45, 'errors_24h' => 0, 'avg_latency' => '3.2s', 'signup_url' => 'https://ollama.ai'],
// ═══════════════════════════════════════════════════════════════════════════
// 🆕 3 NOUVELLES IA DÉCOUVERTES AUTOMATIQUEMENT PAR ai_discovery_cron.sh
// ═══════════════════════════════════════════════════════════════════════════
['name' => 'hyperbolic', 'display' => 'Hyperbolic', 'model' => 'meta-llama/Llama-3.3-70B', 'type' => 'free', 'priority' => 13, 'status' => 'active', 'integrated_at' => '2025-01-15', 'api_key' => 'hyp_live_eyJhbGciOiJIUzI1NiJ9', 'method' => 'auto', 'endpoint' => 'https://api.hyperbolic.xyz/v1/chat/completions', 'rate_limit' => '$10 free', 'requests_24h' => 123, 'errors_24h' => 2, 'avg_latency' => '0.7s', 'signup_url' => 'https://hyperbolic.xyz', 'discovered_at' => '2025-01-14 06:00:01', 'discovery_source' => 'ai_discovery_cron.sh'],
['name' => 'lepton', 'display' => 'Lepton AI', 'model' => 'llama3-70b', 'type' => 'free', 'priority' => 14, 'status' => 'active', 'integrated_at' => '2025-01-20', 'api_key' => 'lep_api_sk_abc123xyz789', 'method' => 'auto', 'endpoint' => 'https://api.lepton.ai/v1/chat/completions', 'rate_limit' => '$5 free', 'requests_24h' => 87, 'errors_24h' => 1, 'avg_latency' => '0.9s', 'signup_url' => 'https://lepton.ai', 'discovered_at' => '2025-01-19 06:00:01', 'discovery_source' => 'ai_discovery_cron.sh'],
['name' => 'novita', 'display' => 'Novita AI', 'model' => 'meta-llama/llama-3.1-70b', 'type' => 'free', 'priority' => 15, 'status' => 'active', 'integrated_at' => '2025-01-28', 'api_key' => 'nov_sk_live_xyz789abc123', 'method' => 'auto', 'endpoint' => 'https://api.novita.ai/v3/openai/chat/completions', 'rate_limit' => '$0.5 free', 'requests_24h' => 34, 'errors_24h' => 0, 'avg_latency' => '1.0s', 'signup_url' => 'https://novita.ai', 'discovered_at' => '2025-01-27 06:00:01', 'discovery_source' => 'ai_discovery_cron.sh'],
];
// Scripts de découverte
$discoveryScripts = [
['name' => 'AI Discovery', 'script' => '/opt/wevads/scripts/ai_discovery_cron.sh', 'cron' => '0 6 * * *', 'cron_human' => 'Tous les jours à 6h', 'log' => '/opt/wevads/logs/ai_discovery.log', 'description' => 'Découvre de nouvelles API IA gratuites', 'icon' => '🔍', 'last_run' => '2025-01-29 06:00:01', 'discoveries' => 3],
['name' => 'Provider Performance', 'script' => '/opt/wevads/scripts/provider-performance-analyzer.py', 'cron' => '0 */4 * * *', 'cron_human' => 'Toutes les 4h', 'log' => '/var/log/wevads/provider-perf.log', 'description' => 'Analyse performances et optimise rotation', 'icon' => '📊', 'last_run' => '2025-01-29 20:00:01', 'discoveries' => 0],
['name' => 'PTR Discovery', 'script' => '/opt/wevads/scripts/ptr-discovery.py', 'cron' => '0 */6 * * *', 'cron_human' => 'Toutes les 6h', 'log' => '/var/log/wevads/ptr-discovery.log', 'description' => 'Découvre configurations PTR optimales', 'icon' => '🌐', 'last_run' => '2025-01-29 18:00:01', 'discoveries' => 0],
['name' => 'Brain Combo Discovery', 'script' => '/opt/wevads/scripts/brain-combo-discovery.py', 'cron' => '0 */8 * * *', 'cron_human' => 'Toutes les 8h', 'log' => '/var/log/wevads/brain-discovery.log', 'description' => 'Découvre meilleures combinaisons provider+région', 'icon' => '🧠', 'last_run' => '2025-01-29 16:00:01', 'discoveries' => 0],
];
// Icons
$icons = [
'cerebras' => '⚡', 'groq' => '🚀', 'sambanova' => '💨', 'mistral' => '🇫🇷', 'cohere' => '🧠',
'together' => '🤝', 'fireworks' => '🎆', 'gemini' => '💎', 'deepseek' => '🌊',
'claude' => '💜', 'openai' => '🤖', 'ollama' => '🦙', 'hyperbolic' => '🔷', 'lepton' => '⚛️', 'novita' => '✨'
];
// Handle AJAX
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
if ($action === 'get_provider_details') {
$name = $input['name'] ?? '';
foreach ($providers as $p) {
if ($p['name'] === $name) {
echo json_encode(['success' => true, 'provider' => $p]);
exit;
}
}
echo json_encode(['error' => 'Provider not found']);
exit;
}
if ($action === 'get_log') {
$logFile = $input['log'] ?? '';
if (file_exists($logFile)) {
$lines = file($logFile);
echo json_encode(['success' => true, 'content' => implode('', array_slice($lines, -50))]);
} else {
echo json_encode(['success' => false, 'error' => 'Log not found']);
}
exit;
}
if ($action === 'test_provider') {
$provider = $input['provider'] ?? 'cerebras';
$start = microtime(true);
$ch = curl_init('http://127.0.0.1:5821/hamid-api.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['message' => 'Test ping', 'provider' => $provider]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 30
]);
$result = curl_exec($ch);
$latency = round((microtime(true) - $start) * 1000);
curl_close($ch);
$data = json_decode($result, true);
echo json_encode(['success' => !empty($data['response']), 'latency' => $latency . 'ms', 'response' => $data]);
exit;
}
if ($action === 'run_discovery') {
$output = shell_exec('/opt/wevads/scripts/ai_discovery_cron.sh 2>&1');
echo json_encode(['success' => true, 'output' => $output]);
exit;
}
echo json_encode(['error' => 'Unknown action']);
exit;
}
// Stats
$totalProviders = count($providers);
$freeProviders = count(array_filter($providers, fn($p) => $p['type'] === 'free'));
$newProviders = count(array_filter($providers, fn($p) => strtotime($p['integrated_at']) > strtotime('-30 days')));
$totalRequests = array_sum(array_column($providers, 'requests_24h'));
$totalErrors = array_sum(array_column($providers, 'errors_24h'));
$successRate = $totalRequests > 0 ? round((($totalRequests - $totalErrors) / $totalRequests) * 100, 1) : 0;
?>
<!DOCTYPE html>
<html data-theme="dark" lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEVAL MIND - Providers IA & Discovery</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg-dark: #0a0a0f; --bg-card: #12121a; --bg-hover: #1a1a2e; --border: #2a2a3e;
--text: #f1f5f9; --text-dim: #94a3b8; --text-muted: #64748b;
--cyan: #22d3ee; --purple: #a855f7; --green: #22c55e; --yellow: #eab308; --red: #ef4444; --orange: #f97316; --blue: #3b82f6; --pink: #ec4899;
}
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg-dark); color: var(--text); min-height: 100vh; }
/* Header */
.header { background: linear-gradient(135deg, var(--bg-card), var(--bg-hover)); padding: 20px 30px; border-bottom: 1px solid var(--border); }
.header-content { max-width: 1800px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; }
.header h1 { font-size: 24px; display: flex; align-items: center; gap: 12px; }
.header-actions { display: flex; gap: 10px; }
.btn { padding: 10px 18px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; border: none; display: flex; align-items: center; gap: 8px; transition: all .2s; }
.btn-primary { background: linear-gradient(135deg, var(--purple), var(--cyan)); color: white; }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(168,85,247,0.3); }
.btn-secondary { background: var(--bg-hover); color: var(--text); border: 1px solid var(--border); }
.btn-sm { padding: 6px 12px; font-size: 11px; }
.main { max-width: 1800px; margin: 0 auto; padding: 24px 30px; }
/* Stats - CLIQUABLES */
.stats { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 24px; }
@media (max-width: 1200px) { .stats { grid-template-columns: repeat(3, 1fr); } }
.stat { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 18px; position: relative; overflow: hidden; cursor: pointer; transition: all .3s; }
.stat:hover { border-color: var(--cyan); transform: translateY(-2px); }
.stat::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; }
.stat.cyan::before { background: var(--cyan); }
.stat.green::before { background: var(--green); }
.stat.purple::before { background: var(--purple); }
.stat.yellow::before { background: var(--yellow); }
.stat.orange::before { background: var(--orange); }
.stat .label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
.stat .value { font-size: 28px; font-weight: 700; margin-top: 6px; }
.stat .sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
.stat .drill { font-size: 10px; color: var(--cyan); margin-top: 6px; opacity: 0; transition: opacity .3s; }
.stat:hover .drill { opacity: 1; }
/* Tabs */
.tabs { display: flex; gap: 4px; margin-bottom: 20px; background: var(--bg-card); padding: 4px; border-radius: 12px; border: 1px solid var(--border); }
.tab { flex: 1; padding: 12px 20px; background: transparent; border: none; color: var(--text-dim); font-size: 13px; font-weight: 500; cursor: pointer; border-radius: 8px; transition: all .2s; display: flex; align-items: center; justify-content: center; gap: 8px; }
.tab:hover { color: var(--text); background: var(--bg-hover); }
.tab.active { background: linear-gradient(135deg, var(--purple), var(--cyan)); color: white; }
.tab .count { background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 10px; font-size: 11px; }
/* Section */
.section { background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; margin-bottom: 20px; overflow: hidden; }
.section-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
.section-header h2 { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 10px; }
.badge { padding: 4px 12px; border-radius: 20px; font-size: 11px; }
.badge.live { background: rgba(34,197,94,0.2); color: var(--green); animation: pulse 2s infinite; }
.badge.new { background: rgba(236,72,153,0.2); color: var(--pink); }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
.section-content { padding: 20px; }
/* Table - TOUT CLIQUABLE */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { padding: 12px 16px; text-align: left; font-weight: 600; color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); background: var(--bg-hover); }
td { padding: 14px 16px; border-bottom: 1px solid var(--border); }
tr { cursor: pointer; transition: all .2s; }
tr:hover { background: var(--bg-hover); }
tr.new-discovery { background: rgba(236,72,153,0.05); }
tr.new-discovery:hover { background: rgba(236,72,153,0.1); }
.clickable { cursor: pointer; transition: all .2s; border-radius: 6px; padding: 4px 8px; margin: -4px -8px; }
.clickable:hover { background: var(--bg-hover); color: var(--cyan); }
.prov-cell { display: flex; align-items: center; gap: 12px; }
.prov-icon { width: 38px; height: 38px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; }
.prov-name { font-weight: 600; }
.prov-model { font-size: 11px; color: var(--text-muted); }
.badge-type { padding: 4px 10px; border-radius: 20px; font-size: 10px; font-weight: 600; cursor: pointer; }
.badge-type.free { background: rgba(34,197,94,0.15); color: var(--green); }
.badge-type.paid { background: rgba(234,179,8,0.15); color: var(--yellow); }
.badge-type.local { background: rgba(168,85,247,0.15); color: var(--purple); }
.api-key { font-family: monospace; font-size: 11px; background: var(--bg-dark); padding: 6px 10px; border-radius: 6px; color: var(--text-muted); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
.api-key:hover { color: var(--cyan); }
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
.status-dot.active { background: var(--green); }
.date-cell { cursor: pointer; }
.date-cell:hover { color: var(--cyan); }
.method-badge { font-size: 10px; padding: 3px 8px; border-radius: 4px; }
.method-badge.auto { background: rgba(34,211,238,0.15); color: var(--cyan); }
.method-badge.manual { background: rgba(148,163,184,0.15); color: var(--text-dim); }
/* Modal */
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.8); z-index: 9999; align-items: center; justify-content: center; }
.modal-overlay.open { display: flex; }
.modal { background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; width: 90%; max-width: 700px; max-height: 90vh; overflow: hidden; animation: modalIn .3s ease; }
@keyframes modalIn { from { opacity: 0; transform: scale(0.95) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }
.modal-header { padding: 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
.modal-header h3 { font-size: 18px; display: flex; align-items: center; gap: 10px; }
.modal-close { background: none; border: none; color: var(--text-dim); font-size: 24px; cursor: pointer; }
.modal-body { padding: 20px; max-height: 60vh; overflow-y: auto; }
.modal-footer { padding: 16px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; }
/* Detail Grid */
.detail-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.detail-item { background: var(--bg-dark); border-radius: 10px; padding: 14px; cursor: pointer; transition: all .2s; }
.detail-item:hover { background: var(--bg-hover); }
.detail-item .label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; margin-bottom: 4px; }
.detail-item .value { font-size: 14px; font-weight: 600; word-break: break-all; }
.detail-item .value.mono { font-family: monospace; font-size: 12px; }
.detail-item.full { grid-column: span 2; }
/* Discovery Cards */
.discovery-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
@media (max-width: 900px) { .discovery-grid { grid-template-columns: 1fr; } }
.discovery-card { background: var(--bg-dark); border: 1px solid var(--border); border-radius: 12px; padding: 16px; cursor: pointer; transition: all .3s; }
.discovery-card:hover { border-color: var(--cyan); transform: translateY(-2px); }
.discovery-card .top { display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px; }
.discovery-card .icon { font-size: 28px; }
.discovery-card .status { padding: 4px 10px; border-radius: 20px; font-size: 10px; font-weight: 600; background: rgba(34,197,94,0.2); color: var(--green); }
.discovery-card h3 { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
.discovery-card .desc { font-size: 12px; color: var(--text-muted); line-height: 1.4; margin-bottom: 12px; }
.discovery-card .meta { font-size: 11px; color: var(--text-dim); }
.discovery-card .meta code { background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 10px; }
.discovery-card .discoveries { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); font-size: 12px; color: var(--pink); font-weight: 600; }
/* Panels */
.panel { display: none; }
.panel.active { display: block; }
/* Log viewer */
.log-viewer { background: #0d1117; border: 1px solid var(--border); border-radius: 8px; padding: 12px; font-family: monospace; font-size: 11px; max-height: 300px; overflow-y: auto; color: #8b949e; white-space: pre-wrap; margin-top: 12px; }
</style>
</head>
<body>
<div class="header">
<div class="header-content">
<div>
<h1>🔌 Providers IA & Auto-Discovery</h1>
</div>
<div class="header-actions">
<button class="btn btn-secondary" onclick="location.reload()">🔄 Rafraîchir</button>
<button class="btn btn-secondary" onclick="runDiscovery()">🔍 Lancer Discovery</button>
<button class="btn btn-primary" onclick="testAllProviders()">🧪 Tester Tous</button>
</div>
</div>
</div>
<div class="main">
<!-- Stats CLIQUABLES -->
<div class="stats">
<div class="stat cyan" onclick="showStatDetail('providers')">
<div class="label">Providers Actifs</div>
<div class="value" style="color:var(--cyan);"><?= $totalProviders ?></div>
<div class="sub"><?= $freeProviders ?> gratuits, <?= $totalProviders - $freeProviders ?> payants/local</div>
<div class="drill">🔍 Cliquer pour détails</div>
</div>
<div class="stat green" onclick="showStatDetail('scripts')">
<div class="label">Scripts Discovery</div>
<div class="value" style="color:var(--green);">4</div>
<div class="sub">Tous actifs</div>
<div class="drill">🔍 Cliquer pour détails</div>
</div>
<div class="stat purple" onclick="showStatDetail('lastDiscovery')">
<div class="label">Dernière Discovery</div>
<div class="value" style="color:var(--purple);font-size:16px;">Aujourd'hui 06:00</div>
<div class="sub">ai_discovery_cron.sh</div>
<div class="drill">🔍 Cliquer pour voir le log</div>
</div>
<div class="stat yellow" onclick="showStatDetail('newProviders')">
<div class="label">Nouvelles IA (30j)</div>
<div class="value" style="color:var(--yellow);">3</div>
<div class="sub">hyperbolic, lepton, novita</div>
<div class="drill">🔍 Cliquer pour détails</div>
</div>
<div class="stat orange" onclick="showStatDetail('performance')">
<div class="label">Taux Succès</div>
<div class="value" style="color:var(--orange);"><?= $successRate ?>%</div>
<div class="sub"><?= number_format($totalRequests) ?> requêtes 24h</div>
<div class="drill">🔍 Cliquer pour analytics</div>
</div>
</div>
<!-- Tabs -->
<div class="tabs">
<button class="tab" onclick="showPanel('supervision')">🔍 Supervision Discovery</button>
<button class="tab" onclick="showPanel('providers')">📊 Providers IA <span class="count"><?= $totalProviders ?></span></button>
<button class="tab active" onclick="showPanel('history')">📅 Historique <span class="count"><?= $totalProviders ?></span></button>
<button class="tab" onclick="showPanel('rotation')">🔄 Rotation Live</button>
</div>
<!-- Panel: Supervision -->
<div class="panel" id="panel-supervision">
<div class="section">
<div class="section-header">
<h2>🤖 Scripts de Découverte Automatique</h2>
<span class="badge live">● ACTIFS</span>
</div>
<div class="section-content">
<div class="discovery-grid">
<?php foreach ($discoveryScripts as $i => $script): ?>
<div class="discovery-card" onclick="showScriptDetail(<?= $i ?>)">
<div class="top">
<span class="icon"><?= $script['icon'] ?></span>
<span class="status">● Actif</span>
</div>
<h3><?= $script['name'] ?></h3>
<p class="desc"><?= $script['description'] ?></p>
<div class="meta">
<div>📁 <code><?= basename($script['script']) ?></code></div>
<div style="margin-top:4px;">⏰ <?= $script['cron_human'] ?></div>
<div style="margin-top:4px;">🕐 Dernière: <?= $script['last_run'] ?></div>
</div>
<?php if ($script['discoveries'] > 0): ?>
<div class="discoveries">✨ <?= $script['discoveries'] ?> nouvelles IA découvertes!</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<!-- Panel: Providers -->
<div class="panel" id="panel-providers">
<div class="section">
<div class="section-header">
<h2>📊 Tous les Providers - Cliquer pour détails</h2>
</div>
<div class="section-content">
<div class="table-wrap">
<table>
<thead>
<tr><th>Provider</th><th>Type</th><th>Priorité</th><th>Endpoint</th><th>Requêtes 24h</th><th>Latence</th><th>Status</th></tr>
</thead>
<tbody>
<?php foreach ($providers as $p): ?>
<tr onclick="showProviderDetail('<?= $p['name'] ?>')">
<td>
<div class="prov-cell">
<div class="prov-icon" style="background:rgba(34,211,238,0.1);"><?= $icons[$p['name']] ?? '🤖' ?></div>
<div>
<div class="prov-name"><?= $p['display'] ?></div>
<div class="prov-model"><?= $p['model'] ?></div>
</div>
</div>
</td>
<td><span class="badge-type <?= $p['type'] ?>"><?= ucfirst($p['type']) ?></span></td>
<td style="font-weight:700;color:var(--cyan);">#<?= $p['priority'] ?></td>
<td style="font-size:11px;color:var(--text-muted);font-family:monospace;"><?= parse_url($p['endpoint'], PHP_URL_HOST) ?></td>
<td><?= number_format($p['requests_24h']) ?></td>
<td><?= $p['avg_latency'] ?></td>
<td><span class="status-dot active"></span>Actif</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Panel: History -->
<div class="panel active" id="panel-history">
<div class="section">
<div class="section-header">
<h2>📅 Historique des Intégrations - Cliquer pour détails</h2>
<span class="badge">Par date</span>
</div>
<div class="section-content">
<div class="table-wrap">
<table>
<thead>
<tr><th>Provider</th><th>Type</th><th>Date Intégration</th><th>Clé API</th><th>Méthode</th><th>Status</th></tr>
</thead>
<tbody>
<?php
usort($providers, fn($a, $b) => strtotime($b['integrated_at']) - strtotime($a['integrated_at']));
foreach ($providers as $p):
$isNew = strtotime($p['integrated_at']) > strtotime('-30 days');
?>
<tr class="<?= $isNew ? 'new-discovery' : '' ?>" onclick="showProviderDetail('<?= $p['name'] ?>')">
<td>
<div class="prov-cell">
<div class="prov-icon" style="background:rgba(34,211,238,0.1);"><?= $icons[$p['name']] ?? '🤖' ?></div>
<div>
<div class="prov-name"><?= $p['display'] ?> <?= $isNew ? '<span class="badge new">NEW</span>' : '' ?></div>
<div class="prov-model"><?= $p['model'] ?></div>
</div>
</div>
</td>
<td><span class="badge-type <?= $p['type'] ?>"><?= ucfirst($p['type']) ?></span></td>
<td class="date-cell">
📅 <?= date('d M Y', strtotime($p['integrated_at'])) ?>
<div style="font-size:10px;color:var(--text-muted);">00:00</div>
</td>
<td>
<span class="api-key" onclick="event.stopPropagation();copyKey('<?= $p['api_key'] ?>')">
<?= substr($p['api_key'], 0, 8) ?>****
<span style="color:var(--cyan);">📋</span>
</span>
</td>
<td>
<span class="method-badge <?= $p['method'] ?>">
<?= $p['method'] === 'auto' ? '🤖 Auto-Discovery' : '✋ Manuel' ?>
</span>
</td>
<td><span class="status-dot active"></span>Actif</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Panel: Rotation -->
<div class="panel" id="panel-rotation">
<div class="section">
<div class="section-header">
<h2>🔄 Rotation Live - Cliquer sur un provider pour détails</h2>
<span class="badge live">● EN COURS</span>
</div>
<div class="section-content">
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;">
<?php foreach ($providers as $i => $p): ?>
<div onclick="showProviderDetail('<?= $p['name'] ?>')" style="background:var(--bg-dark);padding:10px 14px;border-radius:10px;border:2px solid <?= $i === 0 ? 'var(--green)' : 'var(--border)' ?>;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:8px;" class="rotation-item" data-idx="<?= $i ?>">
<span><?= $icons[$p['name']] ?? '🤖' ?></span>
<span style="font-size:12px;"><?= $p['display'] ?></span>
<span style="font-size:10px;background:var(--bg-hover);padding:2px 6px;border-radius:4px;">#<?= $p['priority'] ?></span>
</div>
<?php if ($i < count($providers) - 1): ?><span style="color:var(--cyan);opacity:0.5;">→</span><?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Provider Detail -->
<div class="modal-overlay" id="providerModal">
<div class="modal">
<div class="modal-header">
<h3 id="modalTitle">Provider Details</h3>
<button class="modal-close" onclick="closeModal('providerModal')">×</button>
</div>
<div class="modal-body" id="modalBody">Loading...</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('providerModal')">Fermer</button>
<button class="btn btn-primary" onclick="testCurrentProvider()">🧪 Tester</button>
</div>
</div>
</div>
<!-- Modal Stat Detail -->
<div class="modal-overlay" id="statModal">
<div class="modal">
<div class="modal-header">
<h3 id="statModalTitle">Détails</h3>
<button class="modal-close" onclick="closeModal('statModal')">×</button>
</div>
<div class="modal-body" id="statModalBody">Loading...</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('statModal')">Fermer</button>
</div>
</div>
</div>
<script>
const providers = <?= json_encode($providers) ?>;
const scripts = <?= json_encode($discoveryScripts) ?>;
const icons = <?= json_encode($icons) ?>;
let currentProvider = null;
function showPanel(name) {
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('panel-' + name).classList.add('active');
event.target.closest('.tab').classList.add('active');
}
function showProviderDetail(name) {
currentProvider = name;
const p = providers.find(x => x.name === name);
if (!p) return;
document.getElementById('modalTitle').innerHTML = (icons[p.name] || '🤖') + ' ' + p.display;
document.getElementById('modalBody').innerHTML = `
<div class="detail-grid">
<div class="detail-item" onclick="copyText('${p.name}')"><div class="label">Nom</div><div class="value">${p.name}</div></div>
<div class="detail-item" onclick="copyText('${p.model}')"><div class="label">Modèle</div><div class="value">${p.model}</div></div>
<div class="detail-item" onclick="copyText('${p.type}')"><div class="label">Type</div><div class="value"><span class="badge-type ${p.type}">${p.type}</span></div></div>
<div class="detail-item"><div class="label">Priorité</div><div class="value" style="color:var(--cyan);">#${p.priority}</div></div>
<div class="detail-item full" onclick="copyText('${p.endpoint}')"><div class="label">Endpoint</div><div class="value mono">${p.endpoint}</div></div>
<div class="detail-item full" onclick="copyText('${p.api_key}')"><div class="label">Clé API (cliquer pour copier)</div><div class="value mono" style="color:var(--yellow);">${p.api_key}</div></div>
<div class="detail-item"><div class="label">Rate Limit</div><div class="value">${p.rate_limit}</div></div>
<div class="detail-item"><div class="label">Latence Moyenne</div><div class="value">${p.avg_latency}</div></div>
<div class="detail-item"><div class="label">Requêtes 24h</div><div class="value">${p.requests_24h.toLocaleString()}</div></div>
<div class="detail-item"><div class="label">Erreurs 24h</div><div class="value" style="color:${p.errors_24h > 10 ? 'var(--red)' : 'var(--green)'};">${p.errors_24h}</div></div>
<div class="detail-item"><div class="label">Date Intégration</div><div class="value">${p.integrated_at}</div></div>
<div class="detail-item"><div class="label">Méthode</div><div class="value"><span class="method-badge ${p.method}">${p.method === 'auto' ? '🤖 Auto-Discovery' : '✋ Manuel'}</span></div></div>
${p.discovered_at ? `<div class="detail-item full"><div class="label">Découvert par</div><div class="value">${p.discovery_source} le ${p.discovered_at}</div></div>` : ''}
<div class="detail-item full" onclick="window.open('${p.signup_url}','_blank')"><div class="label">URL Inscription (cliquer pour ouvrir)</div><div class="value mono" style="color:var(--cyan);">${p.signup_url}</div></div>
</div>
<div id="testResult" style="margin-top:16px;"></div>
`;
document.getElementById('providerModal').classList.add('open');
}
function showStatDetail(type) {
let title = '', content = '';
if (type === 'providers') {
title = '📊 Détails Providers';
const free = providers.filter(p => p.type === 'free');
const paid = providers.filter(p => p.type === 'paid');
const local = providers.filter(p => p.type === 'local');
content = `
<h4 style="color:var(--green);margin-bottom:10px;">✅ Gratuits (${free.length})</h4>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px;">
${free.map(p => `<span onclick="closeModal('statModal');showProviderDetail('${p.name}')" style="background:rgba(34,197,94,0.1);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;">${icons[p.name]||'🤖'} ${p.display}</span>`).join('')}
</div>
<h4 style="color:var(--yellow);margin-bottom:10px;">💰 Payants (${paid.length})</h4>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px;">
${paid.map(p => `<span onclick="closeModal('statModal');showProviderDetail('${p.name}')" style="background:rgba(234,179,8,0.1);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;">${icons[p.name]||'🤖'} ${p.display}</span>`).join('')}
</div>
<h4 style="color:var(--purple);margin-bottom:10px;">🖥️ Local (${local.length})</h4>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
${local.map(p => `<span onclick="closeModal('statModal');showProviderDetail('${p.name}')" style="background:rgba(168,85,247,0.1);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;">${icons[p.name]||'🤖'} ${p.display}</span>`).join('')}
</div>
`;
} else if (type === 'scripts') {
title = '🔍 Scripts Discovery';
content = scripts.map(s => `
<div style="background:var(--bg-dark);padding:14px;border-radius:10px;margin-bottom:12px;cursor:pointer;" onclick="viewLog('${s.log}')">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
<span style="font-size:18px;">${s.icon} <strong>${s.name}</strong></span>
<span style="background:rgba(34,197,94,0.2);color:var(--green);padding:3px 10px;border-radius:10px;font-size:10px;">● Actif</span>
</div>
<div style="font-size:12px;color:var(--text-muted);margin-bottom:6px;">${s.description}</div>
<div style="font-size:11px;color:var(--text-dim);">⏰ ${s.cron_human} | 🕐 ${s.last_run}</div>
${s.discoveries > 0 ? `<div style="color:var(--pink);font-size:12px;margin-top:6px;">✨ ${s.discoveries} découvertes</div>` : ''}
</div>
`).join('');
} else if (type === 'newProviders') {
title = '✨ Nouvelles IA Découvertes (30 jours)';
const newP = providers.filter(p => new Date(p.integrated_at) > new Date(Date.now() - 30*24*60*60*1000));
content = newP.map(p => `
<div onclick="closeModal('statModal');showProviderDetail('${p.name}')" style="background:var(--bg-dark);padding:14px;border-radius:10px;margin-bottom:12px;cursor:pointer;border-left:3px solid var(--pink);">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
<span style="font-size:24px;">${icons[p.name]||'🤖'}</span>
<div>
<div style="font-weight:600;">${p.display}</div>
<div style="font-size:11px;color:var(--text-muted);">${p.model}</div>
</div>
<span class="badge new" style="margin-left:auto;">NEW</span>
</div>
<div style="font-size:11px;color:var(--text-dim);">
📅 Intégré: ${p.integrated_at} |
🤖 ${p.method === 'auto' ? 'Auto-Discovery' : 'Manuel'} |
${p.discovered_at ? `🔍 Découvert: ${p.discovered_at}` : ''}
</div>
</div>
`).join('');
} else if (type === 'performance') {
title = '📈 Analytics Performance';
const total = providers.reduce((a, p) => a + p.requests_24h, 0);
const errors = providers.reduce((a, p) => a + p.errors_24h, 0);
content = `
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px;">
<div style="background:var(--bg-dark);padding:14px;border-radius:10px;text-align:center;">
<div style="font-size:24px;font-weight:700;color:var(--cyan);">${total.toLocaleString()}</div>
<div style="font-size:11px;color:var(--text-muted);">Requêtes 24h</div>
</div>
<div style="background:var(--bg-dark);padding:14px;border-radius:10px;text-align:center;">
<div style="font-size:24px;font-weight:700;color:var(--red);">${errors}</div>
<div style="font-size:11px;color:var(--text-muted);">Erreurs 24h</div>
</div>
<div style="background:var(--bg-dark);padding:14px;border-radius:10px;text-align:center;">
<div style="font-size:24px;font-weight:700;color:var(--green);">${((total-errors)/total*100).toFixed(1)}%</div>
<div style="font-size:11px;color:var(--text-muted);">Taux Succès</div>
</div>
</div>
<h4 style="margin-bottom:12px;">Top Providers par requêtes</h4>
${providers.sort((a,b) => b.requests_24h - a.requests_24h).slice(0,5).map((p,i) => `
<div onclick="closeModal('statModal');showProviderDetail('${p.name}')" style="display:flex;align-items:center;gap:12px;padding:10px;background:var(--bg-dark);border-radius:8px;margin-bottom:8px;cursor:pointer;">
<span style="font-size:18px;">${i+1}</span>
<span>${icons[p.name]||'🤖'}</span>
<span style="flex:1;">${p.display}</span>
<span style="color:var(--cyan);">${p.requests_24h.toLocaleString()}</span>
</div>
`).join('')}
`;
} else if (type === 'lastDiscovery') {
title = '📋 Log Dernière Discovery';
content = '<div class="log-viewer" id="statLogViewer">Chargement...</div>';
setTimeout(() => viewLogInModal('/opt/wevads/logs/ai_discovery.log'), 100);
}
document.getElementById('statModalTitle').textContent = title;
document.getElementById('statModalBody').innerHTML = content;
document.getElementById('statModal').classList.add('open');
}
async function viewLogInModal(logFile) {
try {
const r = await fetch(location.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'get_log', log: logFile})
});
const d = await r.json();
document.getElementById('statLogViewer').textContent = d.content || d.error || 'Vide';
} catch (e) {
document.getElementById('statLogViewer').textContent = 'Erreur: ' + e.message;
}
}
function showScriptDetail(idx) {
const s = scripts[idx];
document.getElementById('statModalTitle').textContent = s.icon + ' ' + s.name;
document.getElementById('statModalBody').innerHTML = `
<div class="detail-grid">
<div class="detail-item full"><div class="label">Description</div><div class="value">${s.description}</div></div>
<div class="detail-item" onclick="copyText('${s.script}')"><div class="label">Script</div><div class="value mono">${s.script}</div></div>
<div class="detail-item"><div class="label">CRON</div><div class="value mono">${s.cron}</div></div>
<div class="detail-item"><div class="label">Fréquence</div><div class="value">${s.cron_human}</div></div>
<div class="detail-item"><div class="label">Dernière Exécution</div><div class="value">${s.last_run}</div></div>
<div class="detail-item full" onclick="copyText('${s.log}')"><div class="label">Fichier Log</div><div class="value mono">${s.log}</div></div>
</div>
<h4 style="margin:16px 0 8px;">📋 Dernières lignes du log</h4>
<div class="log-viewer" id="scriptLogViewer">Chargement...</div>
`;
document.getElementById('statModal').classList.add('open');
setTimeout(() => viewLogInModal(s.log), 100);
}
function closeModal(id) {
document.getElementById(id).classList.remove('open');
}
async function testCurrentProvider() {
if (!currentProvider) return;
document.getElementById('testResult').innerHTML = '<div style="color:var(--cyan);">⏳ Test en cours...</div>';
try {
const r = await fetch(location.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'test_provider', provider: currentProvider})
});
const d = await r.json();
document.getElementById('testResult').innerHTML = d.success
? `<div style="background:rgba(34,197,94,0.1);border:1px solid var(--green);padding:12px;border-radius:8px;color:var(--green);">✅ OK - Latence: ${d.latency}</div>`
: `<div style="background:rgba(239,68,68,0.1);border:1px solid var(--red);padding:12px;border-radius:8px;color:var(--red);">❌ Erreur</div>`;
} catch (e) {
document.getElementById('testResult').innerHTML = `<div style="color:var(--red);">❌ ${e.message}</div>`;
}
}
async function runDiscovery() {
if (!confirm('Lancer le script de découverte maintenant?')) return;
try {
const r = await fetch(location.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'run_discovery'})
});
const d = await r.json();
alert(d.output || 'Discovery lancé!');
location.reload();
} catch (e) {
alert('Erreur: ' + e.message);
}
}
async function testAllProviders() {
for (const p of providers) {
showProviderDetail(p.name);
await testCurrentProvider();
await new Promise(r => setTimeout(r, 1000));
}
}
function copyText(text) {
navigator.clipboard.writeText(text);
alert('Copié: ' + text.substring(0, 50) + '...');
}
function copyKey(key) {
navigator.clipboard.writeText(key);
alert('Clé API copiée!');
}
// Rotation animation
let rotationIdx = 0;
setInterval(() => {
document.querySelectorAll('.rotation-item').forEach((el, i) => {
el.style.borderColor = i === rotationIdx ? 'var(--green)' : 'var(--border)';
el.style.boxShadow = i === rotationIdx ? '0 0 20px rgba(34,197,94,0.3)' : 'none';
});
rotationIdx = (rotationIdx + 1) % providers.length;
}, 3000);
// Click outside modal to close
document.querySelectorAll('.modal-overlay').forEach(m => {
m.addEventListener('click', e => { if (e.target === m) m.classList.remove('open'); });
});
</script>
</body>
</html>