// =====================================================
// SentinelAI - Knowledge Graph Reputationnel Engine v3.0
// Dimensions: Trust · Authority · Sentiment · Influence · E-E-A-T
// =====================================================
const state = {
startTime: Date.now(),
domain: '',
entities: [],
keywords: [],
competitors: [],
dimensions: ['trust', 'authority', 'sentiment', 'influence', 'eeat'],
kgNodes: [],
kgLinks: [],
scores: { trust: 0, authority: 0, sentiment: 0, influence: 0, eeat: 0, global: 0 },
scoreBreakdown: {},
logs: [],
packets: [],
canvasAnimId: null,
hoveredNode: null,
analyzing: false,
analysisData: null,
};
const ENTITY_TYPES = [
{ type: 'brand', label: 'Marque', icon: 'building-2', color: 'violet' },
{ type: 'person', label: 'Personne', icon: 'user', color: 'cyan' },
{ type: 'organization', label: 'Organisation', icon: 'briefcase', color: 'purple' },
{ type: 'concept', label: 'Concept', icon: 'lightbulb', color: 'amber' },
{ type: 'product', label: 'Produit', icon: 'package', color: 'green' },
{ type: 'location', label: 'Localisation', icon: 'map-pin', color: 'blue' },
{ type: 'competitor', label: 'Concurrent', icon: 'swords', color: 'red' },
{ type: 'influencer', label: 'Influenceur', icon: 'star', color: 'fuchsia' },
];
const RELATION_TYPES = [
{ type: 'owns', label: 'possède', color: '#8b5cf6' },
{ type: 'works-for', label: 'travaille pour', color: '#06b6d4' },
{ type: 'competes-with', label: 'concurrence', color: '#ef4444' },
{ type: 'collaborates', label: 'collabore', color: '#22c55e' },
{ type: 'mentions', label: 'mentionne', color: '#f59e0b' },
{ type: 'endorses', label: 'recommande', color: '#a855f7' },
{ type: 'related-to', label: 'lié à', color: '#64748b' },
{ type: 'influences', label: 'influence', color: '#ec4899' },
];
const CognitiveAI = {
neural: { layers: [5, 10, 10, 8, 3], activations: [], weights: [], biases: [], pulses: [] },
active: false, automation: false, iterations: 0, decisions: [], patterns: [], predictions: [],
autoActions: 0, confidence: 0, learningRate: 0.01, automationInterval: null, neuralAnimId: null,
geneticPopulation: [], geneticGeneration: 0, bestFitness: 0,
initNeural() {
this.neural.weights = []; this.neural.biases = [];
for (let i = 0; i < this.neural.layers.length - 1; i++) {
const lw = [], lb = [];
for (let j = 0; j < this.neural.layers[i + 1]; j++) {
const nw = [];
for (let k = 0; k < this.neural.layers[i]; k++) nw.push(Math.random() * 2 - 1);
lw.push(nw); lb.push(Math.random() * 0.5 - 0.25);
}
this.neural.weights.push(lw); this.neural.biases.push(lb);
}
this.neural.activations = this.neural.layers.map(n => new Array(n).fill(0));
this.neural.pulses = [];
},
sigmoid(x) { return 1 / (1 + Math.exp(-x)); },
forwardPass(inputs) {
this.neural.activations[0] = inputs.slice();
for (let i = 0; i < this.neural.weights.length; i++) {
const next = [];
for (let j = 0; j < this.neural.weights[i].length; j++) {
let sum = this.neural.biases[i][j];
for (let k = 0; k < this.neural.weights[i][j].length; k++)
sum += this.neural.activations[i][k] * this.neural.weights[i][j][k];
next.push(this.sigmoid(sum));
}
this.neural.activations[i + 1] = next;
for (let j = 0; j < next.length; j++) {
if (Math.random() < 0.3)
this.neural.pulses.push({ layer: i, fromNeuron: Math.floor(Math.random() * this.neural.layers[i]), toNeuron: j, progress: 0, speed: 0.03 + Math.random() * 0.02, intensity: next[j] });
}
}
return this.neural.activations[this.neural.activations.length - 1];
},
recognizePatterns() {
const patterns = [];
const isolated = state.kgNodes.filter(n => n.isolated);
if (isolated.length > 0) patterns.push({ type: 'Isolated Entities', description: `${isolated.length} entité(s) isolée(s) sans relations`, confidence: 0.85 + Math.random() * 0.1, severity: 'critical' });
const negativeEntities = state.kgNodes.filter(n => n.sentiment < 30);
if (negativeEntities.length > 0) patterns.push({ type: 'Negative Sentiment Cluster', description: `${negativeEntities.length} entité(s) avec sentiment négatif`, confidence: 0.80, severity: 'high' });
if (state.scores.trust < 50) patterns.push({ type: 'Trust Deficit', description: `Score TRUST ${state.scores.trust}/100 — confiance insuffisante`, confidence: 0.82, severity: 'high' });
if (state.scores.authority < 50) patterns.push({ type: 'Authority Gap', description: `Score AUTHORITY ${state.scores.authority}/100 — autorité faible`, confidence: 0.80, severity: 'high' });
if (state.scores.eeat < 50) patterns.push({ type: 'E-E-A-T Weakness', description: `Score E-E-A-T ${state.scores.eeat}/100 — expertise non démontrée`, confidence: 0.78, severity: 'high' });
if (state.scores.influence < 40) patterns.push({ type: 'Low Influence', description: `Score INFLUENCE ${state.scores.influence}/100 — portée limitée`, confidence: 0.75, severity: 'medium' });
const competitors = state.kgNodes.filter(n => n.type === 'competitor');
if (competitors.length > state.kgNodes.filter(n => n.type === 'brand').length) patterns.push({ type: 'Competitor Dominance', description: `${competitors.length} concurrents vs peu d'entités principales`, confidence: 0.72, severity: 'medium' });
const density = state.kgLinks.length / Math.max(state.kgNodes.length, 1);
if (density < 1.5) patterns.push({ type: 'Low Graph Density', description: `Densité ${density.toFixed(2)} — objectif > 2.0`, confidence: 0.80, severity: 'medium' });
this.patterns = patterns;
return patterns;
},
predictWeaknesses() {
const predictions = [];
if (state.scores.global < 250) predictions.push({ prediction: `Score global ${state.scores.global}/500 — réputation en danger`, probability: 0.85, timeframe: '30 jours', action: 'Activer auto-remédiation cognitive' });
if (state.scores.trust < 40) predictions.push({ prediction: 'TRUST critique — perte de confiance clients probable', probability: 0.80, timeframe: '14 jours', action: 'Renforcer signaux de confiance (avis, certifications)' });
if (state.scores.sentiment < 40) predictions.push({ prediction: 'SENTIMENT négatif — bad reputation risk', probability: 0.82, timeframe: 'En cours', action: 'Campagne de gestion de réputation en ligne' });
if (state.scores.eeat < 40) predictions.push({ prediction: 'E-E-A-T faible — non éligible pour AI Overviews', probability: 0.78, timeframe: '60 jours', action: 'Publier contenu expert + credentials' });
if (state.kgNodes.filter(n => n.isolated).length > 3) predictions.push({ prediction: `${state.kgNodes.filter(n => n.isolated).length} entités isolées — graphe fragmenté`, probability: 0.75, timeframe: 'Immédiat', action: 'Créer relations entre entités isolées' });
if (state.scores.influence < 30) predictions.push({ prediction: 'INFLUENCE très faible — invisibilité dans le graphe', probability: 0.70, timeframe: '90 jours', action: 'Stratégie netlinking + relations publiques' });
this.predictions = predictions;
return predictions;
},
makeDecisions() {
const decisions = [];
if (this.patterns.some(p => p.type === 'Isolated Entities')) decisions.push({ decision: 'Créer relations pour entités isolées', action: 'auto-remediation', priority: 'CRITIQUE', impact: '+15-20 points cohérence', confidence: 0.90 });
if (this.patterns.some(p => p.type === 'Trust Deficit')) decisions.push({ decision: 'Renforcer signaux de confiance (avis, certifications, HTTPS)', action: 'trust-signals', priority: 'HAUTE', impact: '+10-15 points TRUST', confidence: 0.85 });
if (this.patterns.some(p => p.type === 'Authority Gap')) decisions.push({ decision: 'Acquérir backlinks autorité + publications expert', action: 'authority-building', priority: 'HAUTE', impact: '+12-18 points AUTHORITY', confidence: 0.88 });
if (this.patterns.some(p => p.type === 'Negative Sentiment Cluster')) decisions.push({ decision: 'Campagne de gestion de réputation + contenu positif', action: 'reputation-management', priority: 'HAUTE', impact: '+15-20 points SENTIMENT', confidence: 0.82 });
if (this.patterns.some(p => p.type === 'E-E-A-T Weakness')) decisions.push({ decision: 'Publier contenu expert + credentials + bios auteurs', action: 'eeat-content', priority: 'HAUTE', impact: '+15-20 points E-E-A-T', confidence: 0.85 });
if (this.patterns.some(p => p.type === 'Low Influence')) decisions.push({ decision: 'Stratégie netlinking + relations publiques + guest posts', action: 'influence-strategy', priority: 'MOYENNE', impact: '+10-15 points INFLUENCE', confidence: 0.78 });
if (this.patterns.some(p => p.type === 'Competitor Dominance')) decisions.push({ decision: 'Renforcer entités principales vs concurrents', action: 'entity-strengthening', priority: 'MOYENNE', impact: '+8-12 points position', confidence: 0.75 });
if (this.patterns.some(p => p.type === 'Low Graph Density')) decisions.push({ decision: 'Augmenter densité du graphe de connaissances', action: 'graph-density', priority: 'MOYENNE', impact: '+10-15 points cohérence', confidence: 0.80 });
const scoreGap = 500 - state.scores.global;
if (scoreGap > 100) decisions.push({ decision: `Plan de récupération: ${scoreGap} points à regagner sur 500`, action: 'plan-global', priority: 'STRATÉGIQUE', impact: `Score cible: ${Math.min(450, state.scores.global + 100)}/500`, confidence: 0.88 });
this.decisions = decisions;
return decisions;
},
autoRemediate() {
if (!state.analysisData) { showToast('Lancez d\'abord la construction du Knowledge Graph', true); return; }
addLog('IA: Démarrage auto-remédiation réputationnelle...', 'system');
let actionsTaken = 0;
const isolated = state.kgNodes.filter(n => n.isolated);
isolated.forEach((entity, i) => {
setTimeout(() => {
const siblings = state.kgNodes.filter(n => n.type !== 'competitor' && n.id !== entity.id && !n.isolated);
if (siblings.length > 0) {
const numLinks = Math.min(3, siblings.length);
for (let j = 0; j < numLinks; j++) {
const sib = siblings[Math.floor(Math.random() * siblings.length)];
const rel = RELATION_TYPES[Math.floor(Math.random() * RELATION_TYPES.length)];
state.kgLinks.push({ from: sib.id, to: entity.id, relType: rel.type, relLabel: rel.label, aiGenerated: true });
}
entity.isolated = false; actionsTaken++; this.autoActions++; this.updateAutoCount();
addLog(`IA: Relation créée → ${entity.name} (auto-remédiation)`, 'success');
}
}, i * 200);
});
const negative = state.kgNodes.filter(n => n.sentiment < 30);
negative.forEach((entity, i) => {
setTimeout(() => {
entity.sentiment = Math.min(100, entity.sentiment + 25); actionsTaken++; this.autoActions++; this.updateAutoCount();
addLog(`IA: Sentiment amélioré → ${entity.name} (+25)`, 'success');
}, (isolated.length + i) * 200 + 500);
});
setTimeout(() => {
recalculateScores(); updateStats(); renderScoreBreakdown(); renderEntityList(); generateReport(); generateStrategy(); renderRecommendations(); spawnPackets();
this.confidence = Math.min(0.98, 0.7 + (actionsTaken * 0.03));
this.renderStatus(); this.renderMetrics();
addLog(`IA: Auto-remédiation terminée — ${actionsTaken} actions`, 'success');
addLog(`IA: Score réputationnel mis à jour: ${state.scores.global}/500`, 'system');
showToast(`Auto-remédiation: ${actionsTaken} actions ! Score: ${state.scores.global}/500`);
}, (isolated.length + negative.length) * 200 + 1000);
},
deepLearn() {
if (!state.analysisData) { showToast('Lancez d\'abord la construction', true); return; }
addLog('IA: Démarrage apprentissage profond réputationnel...', 'system');
if (!this.active) this.initNeural();
for (let epoch = 0; epoch < 5; epoch++) {
setTimeout(() => {
for (let i = 0; i < this.neural.weights.length; i++) for (let j = 0; j < this.neural.weights[i].length; j++) for (let k = 0; k < this.neural.weights[i][j].length; k++) this.neural.weights[i][j][k] += (Math.random() - 0.5) * this.learningRate;
const inputs = [state.scores.trust / 100, state.scores.authority / 100, state.scores.sentiment / 100, state.scores.influence / 100, state.scores.eeat / 100];
const outputs = this.forwardPass(inputs);
this.confidence = Math.min(0.99, this.confidence + 0.05); this.iterations++; this.renderStatus(); this.renderLearning();
addLog(`IA: Epoch ${epoch + 1}/5 — Convergence: ${(outputs.reduce((a, b) => a + b, 0) / outputs.length * 100).toFixed(1)}%`, 'info');
if (epoch === 4) { addLog(`IA: Deep Learning terminé — Confiance: ${(this.confidence * 100).toFixed(0)}%`, 'success'); showToast(`Deep Learning terminé ! Confiance: ${(this.confidence * 100).toFixed(0)}%`); }
}, epoch * 800);
}
},
geneticOptimize() {
if (!state.analysisData) { showToast('Lancez d\'abord la construction', true); return; }
addLog('IA: Démarrage algorithme génétique réputationnel...', 'system');
const popSize = 10;
this.geneticPopulation = Array.from({ length: popSize }, () => ({ genes: Array(10).fill(0).map(() => Math.random()), fitness: 0 }));
for (let gen = 0; gen < 5; gen++) {
setTimeout(() => {
this.geneticPopulation.forEach(ind => {
const inputs = [state.scores.trust / 100, state.scores.authority / 100, state.scores.sentiment / 100, state.scores.influence / 100, state.scores.eeat / 100];
const outputs = this.forwardPass(inputs);
ind.fitness = outputs.reduce((a, b) => a + b, 0) / outputs.length + (Math.random() * 0.1);
});
this.geneticPopulation.sort((a, b) => b.fitness - a.fitness);
this.bestFitness = this.geneticPopulation[0].fitness; this.geneticGeneration = gen + 1;
const elite = this.geneticPopulation.slice(0, popSize / 2);
this.geneticPopulation = [...elite, ...elite.map(ind => ({ genes: ind.genes.map(g => g + (Math.random() - 0.5) * 0.1), fitness: 0 }))];
this.renderLearning();
addLog(`IA: Génération ${gen + 1}/5 — Fitness: ${(this.bestFitness * 100).toFixed(1)}%`, 'info');
if (gen === 4) { addLog(`IA: Algo génétique terminé — Fitness: ${(this.bestFitness * 100).toFixed(1)}%`, 'success'); showToast(`Algo génétique terminé ! Fitness: ${(this.bestFitness * 100).toFixed(1)}%`); }
}, gen * 1000);
}
},
runCycle() {
if (!state.analysisData) { showToast('Lancez d\'abord la construction', true); return; }
addLog('IA: Cycle cognitif complet réputationnel...', 'system');
if (!this.active) this.initNeural();
const inputs = [state.scores.trust / 100, state.scores.authority / 100, state.scores.sentiment / 100, state.scores.influence / 100, state.scores.eeat / 100];
this.forwardPass(inputs);
this.recognizePatterns(); this.predictWeaknesses(); this.makeDecisions();
this.iterations++; this.confidence = Math.min(0.95, 0.6 + this.iterations * 0.02);
this.renderStatus(); this.renderDecisions(); this.renderMetrics(); this.renderLearning();
addLog(`IA: Cycle #${this.iterations} — ${this.patterns.length} patterns, ${this.predictions.length} prédictions, ${this.decisions.length} décisions`, 'success');
showToast(`Cycle cognitif #${this.iterations} terminé !`);
},
activate() {
if (this.active) { this.deactivate(); return; }
if (!state.analysisData) { showToast('Lancez d\'abord la construction', true); return; }
this.active = true; this.initNeural(); this.renderStatus(); this.startNeuralAnimation(); this.runCycle();
const label = document.getElementById('aiActivateLabel'); if (label) label.textContent = 'Désactiver l\'IA';
addLog('IA: Moteur cognitif réputationnel activé — Neural Reputation Engine v3.0', 'system');
showToast('Moteur cognitif IA activé ! 🧠');
},
deactivate() {
this.active = false; this.automation = false; this.stopAutomation(); this.stopNeuralAnimation();
const toggle = document.getElementById('aiAutomationToggle'); if (toggle) toggle.checked = false;
this.renderStatus(); this.renderToggle();
const label = document.getElementById('aiActivateLabel'); if (label) label.textContent = 'Activer l\'IA';
addLog('IA: Moteur cognitif désactivé', 'system'); showToast('Moteur cognitif IA désactivé');
},
toggleAutomation() {
const toggle = document.getElementById('aiAutomationToggle');
const isChecked = toggle ? toggle.checked : false;
if (!this.active) { if (toggle) toggle.checked = false; this.renderToggle(); showToast('Activez d\'abord l\'IA', true); return; }
this.automation = isChecked;
if (this.automation) { this.startAutomation(); addLog('IA: Automatisation activée', 'system'); showToast('Automatisation IA activée ! ⚙️'); }
else { this.stopAutomation(); addLog('IA: Automatisation désactivée', 'system'); showToast('Automatisation IA désactivée'); }
this.renderToggle();
},
startAutomation() {
this.automationInterval = setInterval(() => {
if (this.active && this.automation) {
this.runCycle();
if (this.patterns.some(p => p.severity === 'critical') && Math.random() > 0.5) { addLog('IA: Pattern critique — auto-remédiation', 'warning'); this.autoRemediate(); }
}
}, 5000);
},
stopAutomation() { if (this.automationInterval) { clearInterval(this.automationInterval); this.automationInterval = null; } },
startNeuralAnimation() {
const canvas = document.getElementById('neuralCanvas'); if (!canvas) return;
function resize() { const rect = canvas.parentElement.getBoundingClientRect(); canvas.width = rect.width - 8; canvas.height = 220; }
resize(); window.addEventListener('resize', resize);
const animate = () => {
if (!this.active) { this.neuralAnimId = null; return; }
const ctx = canvas.getContext('2d'), w = canvas.width, h = canvas.height;
ctx.clearRect(0, 0, w, h);
const lc = this.neural.layers.length, ls = w / (lc + 1);
const np = this.neural.layers.map((count, li) => { const pos = [], ns = h / (count + 1); for (let i = 0; i < count; i++) pos.push({ x: ls * (li + 1), y: ns * (i + 1) }); return pos; });
for (let i = 0; i < this.neural.weights.length; i++) for (let j = 0; j < this.neural.weights[i].length; j++) for (let k = 0; k < this.neural.weights[i][j].length; k++) {
const f = np[i][k], t = np[i + 1][j], wt = this.neural.weights[i][j][k];
ctx.beginPath(); ctx.moveTo(f.x, f.y); ctx.lineTo(t.x, t.y);
ctx.strokeStyle = wt > 0 ? `rgba(139,92,246,${Math.abs(wt) * 0.15})` : `rgba(99,102,241,${Math.abs(wt) * 0.15})`;
ctx.lineWidth = Math.abs(wt) * 1.5; ctx.stroke();
}
this.neural.pulses = this.neural.pulses.filter(p => {
p.progress += p.speed; if (p.progress >= 1) return false;
const f = np[p.layer][p.fromNeuron], t = np[p.layer + 1][p.toNeuron];
const px = f.x + (t.x - f.x) * p.progress, py = f.y + (t.y - f.y) * p.progress;
ctx.beginPath(); ctx.arc(px, py, 4, 0, Math.PI * 2); ctx.fillStyle = `rgba(139,92,246,${p.intensity * 0.4})`; ctx.fill();
ctx.beginPath(); ctx.arc(px, py, 2, 0, Math.PI * 2); ctx.fillStyle = '#a78bfa'; ctx.fill();
return true;
});
for (let i = 0; i < np.length; i++) for (let j = 0; j < np[i].length; j++) {
const pos = np[i][j], act = this.neural.activations[i][j] || 0, r = i === 0 || i === np.length - 1 ? 7 : 6;
const gr = r + 6 + act * 4;
const grad = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, gr);
grad.addColorStop(0, `rgba(139,92,246,${act * 0.4})`); grad.addColorStop(1, 'rgba(139,92,246,0)');
ctx.beginPath(); ctx.arc(pos.x, pos.y, gr, 0, Math.PI * 2); ctx.fillStyle = grad; ctx.fill();
ctx.beginPath(); ctx.arc(pos.x, pos.y, r, 0, Math.PI * 2); ctx.fillStyle = '#1e293b';
ctx.strokeStyle = act > 0.5 ? '#a78bfa' : act > 0.2 ? '#8b5cf6' : '#6366f1'; ctx.lineWidth = 2; ctx.fill(); ctx.stroke();
if (act > 0.1) { ctx.beginPath(); ctx.arc(pos.x, pos.y, 2.5, 0, Math.PI * 2); ctx.fillStyle = `rgba(167,139,250,${act})`; ctx.fill(); }
}
const labels = ['INPUT', 'HIDDEN', 'HIDDEN', 'HIDDEN', 'OUTPUT'];
ctx.font = '8px JetBrains Mono'; ctx.textAlign = 'center';
for (let i = 0; i < lc; i++) { ctx.fillStyle = '#475569'; ctx.fillText(labels[i] || `L${i}`, ls * (i + 1), h - 5); }
if (Math.random() < 0.3 && this.neural.pulses.length < 30) {
const layer = Math.floor(Math.random() * (lc - 1));
this.neural.pulses.push({ layer, fromNeuron: Math.floor(Math.random() * this.neural.layers[layer]), toNeuron: Math.floor(Math.random() * this.neural.layers[layer + 1]), progress: 0, speed: 0.03 + Math.random() * 0.02, intensity: 0.5 + Math.random() * 0.5 });
}
this.neuralAnimId = requestAnimationFrame(animate);
};
animate();
},
stopNeuralAnimation() {
if (this.neuralAnimId) { cancelAnimationFrame(this.neuralAnimId); this.neuralAnimId = null; }
const canvas = document.getElementById('neuralCanvas'); if (canvas) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); }
},
updateAutoCount() { const el = document.getElementById('aiAutoCount'); if (el) el.textContent = this.autoActions; },
renderStatus() {
const dot = document.getElementById('aiStatusDot'), text = document.getElementById('aiStatusText');
const confEl = document.getElementById('aiConfidence'), iterEl = document.getElementById('aiIterations');
if (this.active) { if (dot) dot.className = 'w-2 h-2 bg-green-400 rounded-full animate-pulse'; if (text) { text.textContent = 'ACTIVE'; text.className = 'text-xs font-mono text-green-400'; } }
else { if (dot) dot.className = 'w-2 h-2 bg-slate-600 rounded-full'; if (text) { text.textContent = 'DORMANT'; text.className = 'text-xs font-mono text-slate-500'; } }
if (confEl) confEl.textContent = `${Math.round(this.confidence * 100)}%`;
if (iterEl) iterEl.textContent = this.iterations;
this.updateAutoCount();
},
renderToggle() {
const bg = document.getElementById('automationToggleBg'), knob = document.getElementById('automationToggleKnob');
if (this.automation) { if (bg) bg.className = 'w-10 h-5 bg-violet-500 rounded-full transition-colors'; if (knob) knob.className = 'absolute left-5 top-0.5 w-4 h-4 bg-white rounded-full transition-all'; }
else { if (bg) bg.className = 'w-10 h-5 bg-slate-800 rounded-full transition-colors'; if (knob) knob.className = 'absolute left-0.5 top-0.5 w-4 h-4 bg-slate-600 rounded-full transition-all'; }
},
renderDecisions() {
const list = document.getElementById('aiDecisionsList'); if (!list) return;
if (this.decisions.length === 0) { list.innerHTML = '
Aucune décision IA
'; return; }
const pc = { 'CRITIQUE': 'text-red-400 bg-red-500/10 border-red-500/20', 'HAUTE': 'text-amber-400 bg-amber-500/10 border-amber-500/20', 'MOYENNE': 'text-cyan-400 bg-cyan-500/10 border-cyan-500/20', 'STRATÉGIQUE': 'text-violet-400 bg-violet-500/10 border-violet-500/20' };
list.innerHTML = this.decisions.map((d, i) => {
const c = pc[d.priority] || pc['MOYENNE'], [tc, bc, bd] = c.split(' ');
return `${d.decision}${d.priority}
Impact: ${d.impact} · Confiance: ${(d.confidence * 100).toFixed(0)}%
`;
}).join('');
lucide.createIcons();
},
renderMetrics() {
const pe = document.getElementById('aiPatterns'), pre = document.getElementById('aiPredictions');
if (pe) { if (this.patterns.length > 0) pe.innerHTML = this.patterns.map(p => `${p.type}${(p.confidence * 100).toFixed(0)}%
`).join(''); else pe.textContent = '—'; }
if (pre) { if (this.predictions.length > 0) pre.innerHTML = this.predictions.map(p => `${p.prediction}
Prob: ${(p.probability * 100).toFixed(0)}% · ${p.timeframe}
`).join(''); else pre.textContent = '—'; }
},
renderLearning() {
const el = document.getElementById('aiLearning'); if (!el) return;
if (this.geneticGeneration > 0) el.innerHTML = `Génération: ${this.geneticGeneration}
Fitness: ${(this.bestFitness * 100).toFixed(1)}%
`;
else if (this.iterations > 0) el.innerHTML = `Itérations: ${this.iterations}
Confiance: ${(this.confidence * 100).toFixed(0)}%
`;
else el.textContent = '—';
},
};
// ---- Init ----
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons(); startClock(); initCanvas();
CognitiveAI.renderStatus(); CognitiveAI.renderToggle();
addLog('Knowledge Graph Reputationnel v3.0 initialisé', 'system');
addLog('Dimensions: TRUST · AUTHORITY · SENTIMENT · INFLUENCE · E-E-A-T', 'info');
addLog('IA: Neural Reputation Engine v3.0 — en attente d\'activation', 'system');
addLog('En attente de configuration...', 'info');
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
const input = document.getElementById('domainInput');
if (input && document.activeElement === input) { e.preventDefault(); buildKnowledgeGraph(); }
}
});
});
function startClock() {
function update() { const now = new Date(); const el = document.getElementById('systemTime'); if (el) el.textContent = now.toUTCString().split(' ')[4] + ' UTC'; }
update(); setInterval(update, 1000);
}
// ---- Build Knowledge Graph ----
function buildKnowledgeGraph() {
const di = document.getElementById('domainInput'), ei = document.getElementById('entitiesInput'), ki = document.getElementById('keywordsInput'), ci = document.getElementById('competitorsInput');
if (!di || !di.value.trim()) { showToast('Entrez un domaine/marque cible', true); return; }
if (state.analyzing) { showToast('Construction déjà en cours'); return; }
state.domain = di.value.trim();
state.entities = ei && ei.value.trim() ? ei.value.trim().split('\n').map(s => s.trim()).filter(s => s) : [];
state.keywords = ki && ki.value.trim() ? ki.value.trim().split('\n').map(s => s.trim()).filter(s => s) : ['expert', 'professionnel', 'service', 'qualité'];
state.competitors = ci && ci.value.trim() ? ci.value.trim().split('\n').map(s => s.trim()).filter(s => s) : [];
state.dimensions = Array.from(document.querySelectorAll('.dim-cb:checked')).map(cb => cb.value);
if (state.dimensions.length === 0) state.dimensions = ['trust', 'authority', 'sentiment', 'influence', 'eeat'];
state.analyzing = true;
addLog(`Construction du Knowledge Graph pour ${state.domain}...`, 'system');
addLog(`Entités: ${state.entities.length} · Mots-clés: ${state.keywords.length} · Concurrents: ${state.competitors.length}`, 'info');
const procDiv = document.getElementById('processing'), stagesEl = document.getElementById('stages'), barEl = document.getElementById('progressBar');
if (procDiv) procDiv.classList.remove('hidden');
const stages = [
{ msg: '→ Extraction des entités du domaine cible...', dur: 500 },
{ msg: '→ Identification des types d\'entités (marque, personne, org)...', dur: 500 },
{ msg: '→ Génération des relations inter-entités...', dur: 500 },
{ msg: '→ Calcul des scores TRUST et AUTHORITY...', dur: 500 },
{ msg: '→ Analyse SENTIMENT et INFLUENCE...', dur: 500 },
{ msg: '→ Évaluation E-E-A-T (Expertise, Experience, Authority, Trust)...', dur: 500 },
{ msg: '→ Détection des entités isolées et clusters négatifs...', dur: 400 },
{ msg: '→ Optimisation cognitive IA du graphe...', dur: 400 },
];
let progress = 0, stageIndex = 0;
if (stagesEl) stagesEl.innerHTML = '';
function processStage() {
if (stageIndex >= stages.length) { state.analyzing = false; if (procDiv) procDiv.classList.add('hidden'); generateKGResults(); return; }
const stage = stages[stageIndex]; addLog(stage.msg.replace('→ ', ''), 'info');
if (stagesEl) { const sd = document.createElement('div'); sd.className = 'flex items-center gap-2'; sd.innerHTML = `${stage.msg}`; stagesEl.appendChild(sd); lucide.createIcons(); }
progress += (100 / stages.length); if (barEl) barEl.style.width = `${progress}%`;
setTimeout(() => { stageIndex++; processStage(); }, stage.dur);
}
processStage();
}
function generateKGResults() {
const domain = state.domain, entities = state.entities, keywords = state.keywords, competitors = state.competitors;
state.kgNodes = []; state.kgLinks = []; let nodeId = 0;
// Primary entity (domain)
const primaryNode = {
id: nodeId++, name: domain, type: 'brand', label: 'Marque Principale',
trust: 60 + Math.floor(Math.random() * 30), authority: 55 + Math.floor(Math.random() * 30),
sentiment: 50 + Math.floor(Math.random() * 35), influence: 45 + Math.floor(Math.random() * 35),
eeat: 50 + Math.floor(Math.random() * 30), isolated: false,
x: 0.5, y: 0.5, level: 0,
};
state.kgNodes.push(primaryNode);
// User-defined entities
entities.forEach((entity, i) => {
const et = ENTITY_TYPES[Math.floor(Math.random() * 4)]; // brand, person, org, concept
const angle = (i / Math.max(entities.length, 1)) * Math.PI * 2;
const radius = 0.25;
const node = {
id: nodeId++, name: entity, type: et.type, label: et.label,
trust: 30 + Math.floor(Math.random() * 60), authority: 25 + Math.floor(Math.random() * 60),
sentiment: 20 + Math.floor(Math.random() * 70), influence: 20 + Math.floor(Math.random() * 60),
eeat: 25 + Math.floor(Math.random() * 60), isolated: Math.random() > 0.6,
x: 0.5 + Math.cos(angle) * radius, y: 0.5 + Math.sin(angle) * radius * 0.8, level: 1,
};
state.kgNodes.push(node);
if (!node.isolated) {
const rel = RELATION_TYPES[Math.floor(Math.random() * RELATION_TYPES.length)];
state.kgLinks.push({ from: primaryNode.id, to: node.id, relType: rel.type, relLabel: rel.label, color: rel.color });
}
});
// Competitor entities
competitors.forEach((comp, i) => {
const angle = (i / Math.max(competitors.length, 1)) * Math.PI * 2 + Math.PI;
const radius = 0.35;
const node = {
id: nodeId++, name: comp, type: 'competitor', label: 'Concurrent',
trust: 20 + Math.floor(Math.random() * 50), authority: 30 + Math.floor(Math.random() * 50),
sentiment: 15 + Math.floor(Math.random() * 50), influence: 25 + Math.floor(Math.random() * 50),
eeat: 20 + Math.floor(Math.random() * 50), isolated: false,
x: 0.5 + Math.cos(angle) * radius, y: 0.5 + Math.sin(angle) * radius * 0.8, level: 2,
};
state.kgNodes.push(node);
state.kgLinks.push({ from: primaryNode.id, to: node.id, relType: 'competes-with', relLabel: 'concurrence', color: '#ef4444' });
});
// Keyword/concept entities
keywords.forEach((kw, i) => {
const angle = (i / Math.max(keywords.length, 1)) * Math.PI * 2 + Math.PI / 2;
const radius = 0.2;
const node = {
id: nodeId++, name: kw, type: 'concept', label: 'Concept',
trust: 40 + Math.floor(Math.random() * 40), authority: 35 + Math.floor(Math.random() * 40),
sentiment: 40 + Math.floor(Math.random() * 50), influence: 30 + Math.floor(Math.random() * 40),
eeat: 35 + Math.floor(Math.random() * 45), isolated: Math.random() > 0.7,
x: 0.5 + Math.cos(angle) * radius, y: 0.5 + Math.sin(angle) * radius * 0.7, level: 1,
};
state.kgNodes.push(node);
if (!node.isolated) {
const rel = RELATION_TYPES[Math.floor(Math.random() * RELATION_TYPES.length)];
state.kgLinks.push({ from: primaryNode.id, to: node.id, relType: rel.type, relLabel: rel.label, color: rel.color });
}
});
// Generate inter-entity relationships
const nonCompetitors = state.kgNodes.filter(n => n.type !== 'competitor' && n.type !== 'brand');
nonCompetitors.forEach((node, i) => {
if (Math.random() > 0.5) {
const other = nonCompetitors[Math.floor(Math.random() * nonCompetitors.length)];
if (other.id !== node.id) {
const rel = RELATION_TYPES[Math.floor(Math.random() * RELATION_TYPES.length)];
state.kgLinks.push({ from: node.id, to: other.id, relType: rel.type, relLabel: rel.label, color: rel.color });
}
}
});
// Calculate scores
recalculateScores();
state.analysisData = { domain, entities: state.kgNodes.length, relations: state.kgLinks.length, isolated: state.kgNodes.filter(n => n.isolated).length, dimensions: state.dimensions };
const ph = document.getElementById('kgPlaceholder'); if (ph) ph.classList.add('hidden');
updateStats(); renderScoreBreakdown(); renderEntityList(); generateReport(); generateStrategy(); renderRecommendations(); spawnPackets(); animateKG();
addLog(`Knowledge Graph construit: ${state.kgNodes.length} entités, ${state.kgLinks.length} relations`, 'success');
addLog(`Scores: TRUST=${state.scores.trust} AUTH=${state.scores.authority} SENT=${state.scores.sentiment} INFL=${state.scores.influence} E-E-A-T=${state.scores.eeat} Global=${state.scores.global}/500`, 'info');
addLog(`${state.kgNodes.filter(n => n.isolated).length} entité(s) isolée(s) détectée(s)`, 'warning');
CognitiveAI.renderStatus(); addLog('IA: Données reçues — moteur cognitif prêt', 'system');
showToast(`Knowledge Graph construit ! ${state.kgNodes.length} entités · Score: ${state.scores.global}/500`);
}
function recalculateScores() {
const total = state.kgNodes.length;
const nonIsolated = state.kgNodes.filter(n => !n.isolated);
const avgTrust = nonIsolated.length > 0 ? nonIsolated.reduce((a, n) => a + n.trust, 0) / nonIsolated.length : 0;
const avgAuthority = nonIsolated.length > 0 ? nonIsolated.reduce((a, n) => a + n.authority, 0) / nonIsolated.length : 0;
const avgSentiment = nonIsolated.length > 0 ? nonIsolated.reduce((a, n) => a + n.sentiment, 0) / nonIsolated.length : 0;
const avgInfluence = nonIsolated.length > 0 ? nonIsolated.reduce((a, n) => a + n.influence, 0) / nonIsolated.length : 0;
const avgEEAT = nonIsolated.length > 0 ? nonIsolated.reduce((a, n) => a + n.eeat, 0) / nonIsolated.length : 0;
const connectedRatio = nonIsolated.length / Math.max(total, 1);
const linkDensity = state.kgLinks.length / Math.max(total, 1);
state.scores.trust = Math.round(avgTrust * 0.7 + connectedRatio * 30);
state.scores.authority = Math.round(avgAuthority * 0.6 + Math.min(linkDensity * 10, 40));
state.scores.sentiment = Math.round(avgSentiment * 0.8 + connectedRatio * 20);
state.scores.influence = Math.round(avgInfluence * 0.6 + Math.min(linkDensity * 10, 40));
state.scores.eeat = Math.round(avgEEAT * 0.7 + connectedRatio * 30);
state.scores.global = state.scores.trust + state.scores.authority + state.scores.sentiment + state.scores.influence + state.scores.eeat;
state.scoreBreakdown = { connectivity: Math.round(connectedRatio * 100), density: Math.round(Math.min(linkDensity * 50, 100)), entities: total, isolated: state.kgNodes.filter(n => n.isolated).length };
}
function updateStats() {
animateCounter(document.getElementById('statTrust'), state.scores.trust);
animateCounter(document.getElementById('statAuthority'), state.scores.authority);
animateCounter(document.getElementById('statSentiment'), state.scores.sentiment);
animateCounter(document.getElementById('statInfluence'), state.scores.influence);
animateCounter(document.getElementById('statEEAT'), state.scores.eeat);
const ecEl = document.getElementById('entityCount'); if (ecEl) ecEl.textContent = state.kgNodes.length;
const rcEl = document.getElementById('relationCount'); if (rcEl) rcEl.textContent = state.kgLinks.length;
const rsEl = document.getElementById('repScore'); if (rsEl) rsEl.textContent = state.scores.global;
const ring = document.getElementById('scoreRing'), numEl = document.getElementById('scoreNumber');
if (ring) { const c = 2 * Math.PI * 50; ring.style.strokeDashoffset = c - (state.scores.global / 500) * c; }
if (numEl) animateCounter(numEl, state.scores.global, 1000);
}
function animateCounter(el, target, duration = 800) {
if (!el) return;
const start = parseInt(el.textContent) || 0, startTime = performance.now();
function step(ct) { const e = ct - startTime, p = Math.min(e / duration, 1), eased = 1 - Math.pow(1 - p, 3); el.textContent = Math.floor(start + (target - start) * eased).toLocaleString(); if (p < 1) requestAnimationFrame(step); }
requestAnimationFrame(step);
}
function renderScoreBreakdown() {
const c = document.getElementById('scoreDimensions'); if (!c) return;
const dims = [
{ name: 'TRUST — Confiance', value: state.scores.trust, max: 100, color: '#8b5cf6', icon: 'shield-check', desc: 'Avis, certifications, HTTPS, transparence' },
{ name: 'AUTHORITY — Autorité', value: state.scores.authority, max: 100, color: '#06b6d4', icon: 'award', desc: 'Backlinks autorité, publications expert' },
{ name: 'SENTIMENT — Sentiment', value: state.scores.sentiment, max: 100, color: '#a855f7', icon: 'smile', desc: 'Tone positif, mentions favorables' },
{ name: 'INFLUENCE — Influence', value: state.scores.influence, max: 100, color: '#f59e0b', icon: 'trending-up', desc: 'Portée réseau, citations, partages' },
{ name: 'E-E-A-T — Expertise', value: state.scores.eeat, max: 100, color: '#22c55e', icon: 'badge-check', desc: 'Expertise, Expérience, Autorité, Trust' },
];
c.innerHTML = dims.map(d => `${d.name}
${d.value}/${d.max}${d.desc}
`).join('');
lucide.createIcons();
}
function renderEntityList() {
const list = document.getElementById('entityList'), countEl = document.getElementById('entityListCount');
if (!list) return;
if (countEl) countEl.textContent = state.kgNodes.length;
if (state.kgNodes.length === 0) { list.innerHTML = 'Aucune entité analysée
'; return; }
const typeIcons = { brand: 'building-2', person: 'user', organization: 'briefcase', concept: 'lightbulb', product: 'package', location: 'map-pin', competitor: 'swords', influencer: 'star' };
list.innerHTML = state.kgNodes.map((n, i) => {
const repScore = Math.round((n.trust + n.authority + n.sentiment + n.influence + n.eeat) / 5);
const repColor = repScore > 60 ? 'text-green-400' : repScore > 40 ? 'text-amber-400' : 'text-red-400';
const sentIcon = n.sentiment > 60 ? 'smile' : n.sentiment > 40 ? 'meh' : 'frown';
const sentColor = n.sentiment > 60 ? 'text-green-400' : n.sentiment > 40 ? 'text-amber-400' : 'text-red-400';
return `
${n.name}${n.label}${n.isolated ? 'isolé' : ''}
T:${n.trust}
A:${n.authority}
${n.sentiment}
I:${n.influence}
E:${n.eeat}
${repScore}
`;
}).join('');
lucide.createIcons();
}
function generateReport() {
const o = document.getElementById('reportOutput'); if (!o || !state.analysisData) return;
const r = state.analysisData;
let c = `╔══════════════════════════════════════════════════════════╗\n║ RAPPORT KNOWLEDGE GRAPH REPUTATIONNEL (IA v3.0) ║\n╚══════════════════════════════════════════════════════════╝\n\n`;
c += `Domaine/Marque: ${r.domain}\nDate: ${new Date().toISOString()}\nDimensions: ${r.dimensions.join(', ').toUpperCase()}\n${'─'.repeat(60)}\n\n`;
c += `=== SCORE RÉPUTATIONNEL GLOBAL ===\n\n Score Global: ${state.scores.global}/500\n\n TRUST: ${state.scores.trust}/100\n AUTHORITY: ${state.scores.authority}/100\n SENTIMENT: ${state.scores.sentiment}/100\n INFLUENCE: ${state.scores.influence}/100\n E-E-A-T: ${state.scores.eeat}/100\n\n`;
c += `=== STRUCTURE DU GRAPHE ===\n\n Entités totales: ${r.entities}\n Relations: ${r.relations}\n Entités isolées: ${r.isolated}\n Connectivité: ${state.scoreBreakdown.connectivity}%\n Densité: ${state.scoreBreakdown.density}%\n\n`;
c += `=== ENTITÉS ANALYSÉES ===\n\n`;
state.kgNodes.forEach((n, i) => {
const repScore = Math.round((n.trust + n.authority + n.sentiment + n.influence + n.eeat) / 5);
c += ` [${i + 1}] ${n.name} (${n.label})\n`;
c += ` T=${n.trust} A=${n.authority} S=${n.sentiment} I=${n.influence} E=${n.eeat} → ${repScore}/100\n`;
c += ` ${n.isolated ? '⚠ ISOLÉE' : '✓ Connectée'}\n\n`;
});
c += `=== RELATIONS DÉTECTÉES ===\n\n`;
state.kgLinks.slice(0, 20).forEach((l, i) => {
const from = state.kgNodes.find(n => n.id === l.from), to = state.kgNodes.find(n => n.id === l.to);
if (from && to) c += ` [${i + 1}] ${from.name} —${l.relLabel}→ ${to.name}\n`;
});
if (state.kgLinks.length > 20) c += `\n ... et ${state.kgLinks.length - 20} autres relations\n`;
c += `\n${'─'.repeat(60)}\nGénéré par SentinelAI Knowledge Graph Reputationnel v3.0\n`;
o.textContent = c;
}
function generateStrategy() {
const o = document.getElementById('strategyOutput'); if (!o || !state.analysisData) return;
let c = `╔══════════════════════════════════════════════════════════╗\n║ STRATÉGIE DE RENFORCEMENT RÉPUTATIONNEL (IA v3.0) ║\n╚══════════════════════════════════════════════════════════╝\n\n`;
c += `Domaine: ${state.domain}\nScore global: ${state.scores.global}/500\n${'─'.repeat(60)}\n\n`;
c += `=== PHASE 1 — RENFORCER TRUST ===\n\n 1. Collecter et afficher avis clients vérifiés\n 2. Afficher certifications et labels de confiance\n 3. Sécuriser: HTTPS, privacy policy, mentions légales\n 4. Transparence: prix, CGV, contact visible\n\n`;
c += `=== PHASE 2 — DÉVELOPPER AUTHORITY ===\n\n 1. Acquérir backlinks de sites autorité (DA>50)\n 2. Publier contenu expert sur blogs/médias\n 3. Participer à conférences, podcasts, interviews\n 4. Obtenir mentions dans presse spécialisée\n\n`;
c += `=== PHASE 3 — AMÉLIORER SENTIMENT ===\n\n 1. Répondre aux avis négatifs (tous supports)\n 2. Encourager avis positifs post-achat\n 3. Surveiller mentions sur réseaux sociaux\n 4. Créer contenu positif: témoignages, case studies\n\n`;
c += `=== PHASE 4 — AUGMENTER INFLUENCE ===\n\n 1. Stratégie netlinking ciblée\n 2. Guest posts sur blogs influents\n 3. Relations publiques + partenariats\n 4. Créer contenu shareable (infographies, études)\n\n`;
c += `=== PHASE 5 — OPTIMISER E-E-A-T ===\n\n 1. Pages auteurs avec bio + credentials + photo\n 2. Schema.org: Author, Organization, Expert\n 3. Publier contenu expert: guides, études, recherches\n 4. Obtenir mentions de médias reconnus\n 5. Dates de mise à jour visibles sur contenu\n\n`;
c += `=== PHASE 6 — DENSIFIER LE GRAPHE ===\n\n 1. Connecter entités isolées (${state.kgNodes.filter(n => n.isolated).length} détectées)\n 2. Créer relations inter-entités\n 3. Ajouter co-occurrences dans le contenu\n 4. Structurer données avec schema.org (Entity relations)\n\n`;
c += `=== ACTIONS PRIORITAIRES ===\n\n`;
const isolated = state.kgNodes.filter(n => n.isolated);
if (isolated.length > 0) { c += ` [CRITIQUE] Connecter ${isolated.length} entité(s) isolée(s)\n`; }
if (state.scores.trust < 50) { c += ` [HAUTE] Renforcer TRUST (${state.scores.trust}/100)\n`; }
if (state.scores.sentiment < 50) { c += ` [HAUTE] Améliorer SENTIMENT (${state.scores.sentiment}/100)\n`; }
if (state.scores.eeat < 50) { c += ` [HAUTE] Optimiser E-E-A-T (${state.scores.eeat}/100)\n`; }
if (state.scores.authority < 50) { c += ` [HAUTE] Développer AUTHORITY (${state.scores.authority}/100)\n`; }
if (state.scores.influence < 50) { c += ` [MOYENNE] Augmenter INFLUENCE (${state.scores.influence}/100)\n`; }
c += `\n${'─'.repeat(60)}\nGénéré par SentinelAI Knowledge Graph Reputationnel v3.0\n`;
o.textContent = c;
}
function renderRecommendations() {
const c = document.getElementById('recommendationsContent'); if (!c) return;
const recs = [
{ icon: 'link', color: 'violet', title: 'Connecter les entités isolées', desc: `${state.kgNodes.filter(n => n.isolated).length} entité(s) isolée(s). Créer des relations pour densifier le graphe.`, priority: state.kgNodes.filter(n => n.isolated).length > 0 ? 'Haute' : 'Faible' },
{ icon: 'shield-check', color: 'violet', title: 'Renforcer les signaux de confiance', desc: `Score TRUST: ${state.scores.trust}/100. Avis, certifications, HTTPS, transparence.`, priority: state.scores.trust < 60 ? 'Haute' : 'Moyenne' },
{ icon: 'award', color: 'cyan', title: 'Développer l\'autorité', desc: `Score AUTHORITY: ${state.scores.authority}/100. Backlinks autorité, publications expert.`, priority: state.scores.authority < 60 ? 'Haute' : 'Moyenne' },
{ icon: 'smile', color: 'purple', title: 'Améliorer le sentiment', desc: `Score SENTIMENT: ${state.scores.sentiment}/100. Gérer avis négatifs, encourager positifs.`, priority: state.scores.sentiment < 60 ? 'Haute' : 'Moyenne' },
{ icon: 'trending-up', color: 'amber', title: 'Augmenter l\'influence', desc: `Score INFLUENCE: ${state.scores.influence}/100. Netlinking, guest posts, relations publiques.`, priority: state.scores.influence < 50 ? 'Haute' : 'Moyenne' },
{ icon: 'badge-check', color: 'green', title: 'Optimiser E-E-A-T', desc: `Score E-E-A-T: ${state.scores.eeat}/100. Pages auteurs, schema.org, contenu expert.`, priority: state.scores.eeat < 60 ? 'Haute' : 'Moyenne' },
{ icon: 'share-2', color: 'violet', title: 'Densifier le graphe de connaissances', desc: `Densité: ${state.scoreBreakdown.density}%. Créer plus de relations inter-entités.`, priority: state.scoreBreakdown.density < 60 ? 'Haute' : 'Faible' },
{ icon: 'swords', color: 'red', title: 'Surveiller la concurrence', desc: `${state.kgNodes.filter(n => n.type === 'competitor').length} concurrent(s) dans le graphe. Analyser leur réputation.`, priority: 'Moyenne' },
];
const pc = { 'Haute': 'text-red-400 bg-red-500/10 border-red-500/20', 'Moyenne': 'text-amber-400 bg-amber-500/10 border-amber-500/20', 'Faible': 'text-green-400 bg-green-500/10 border-green-500/20' };
const cc = { violet: 'text-violet-400 bg-violet-500/10', cyan: 'text-cyan-400 bg-cyan-500/10', purple: 'text-purple-400 bg-purple-500/10', amber: 'text-amber-400 bg-amber-500/10', green: 'text-green-400 bg-green-500/10', red: 'text-red-400 bg-red-500/10' };
c.innerHTML = recs.map((r, i) => {
const p = pc[r.priority] || pc['Moyenne'], [ptc, pbc, pbd] = p.split(' ');
const col = cc[r.color] || cc.violet, [tc, bc] = col.split(' ');
return `
${r.title}${r.priority}
${r.desc}
`;
}).join('');
lucide.createIcons();
}
// ---- Canvas ----
function initCanvas() {
const canvas = document.getElementById('kgCanvas'); if (!canvas) return;
function resize() { const rect = canvas.parentElement.getBoundingClientRect(); canvas.width = rect.width - 16; canvas.height = 480; }
resize(); window.addEventListener('resize', resize);
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect(), mx = e.clientX - rect.left, my = e.clientY - rect.top;
state.hoveredNode = null;
for (const node of state.kgNodes) {
const nx = node.x * canvas.width, ny = node.y * canvas.height;
const r = node.type === 'brand' ? 18 : 12;
if (Math.sqrt((mx - nx) ** 2 + (my - ny) ** 2) < r + 4) { state.hoveredNode = node; break; }
}
const tt = document.getElementById('kgTooltip');
if (state.hoveredNode) {
canvas.style.cursor = 'pointer';
if (tt) {
tt.classList.remove('hidden'); tt.style.left = (mx + 12) + 'px'; tt.style.top = (my - 40) + 'px';
const repScore = Math.round((state.hoveredNode.trust + state.hoveredNode.authority + state.hoveredNode.sentiment + state.hoveredNode.influence + state.hoveredNode.eeat) / 5);
const repColor = repScore > 60 ? 'text-green-400' : repScore > 40 ? 'text-amber-400' : 'text-red-400';
tt.innerHTML = `${state.hoveredNode.name}
${state.hoveredNode.label}
T:${state.hoveredNode.trust} A:${state.hoveredNode.authority}
S:${state.hoveredNode.sentiment} I:${state.hoveredNode.influence}
E:${state.hoveredNode.eeat}
Réputation: ${repScore}/100
${state.hoveredNode.isolated ? 'ISOLÉE
' : ''}`;
}
} else { canvas.style.cursor = 'grab'; if (tt) tt.classList.add('hidden'); }
});
canvas.addEventListener('mouseleave', () => { state.hoveredNode = null; const tt = document.getElementById('kgTooltip'); if (tt) tt.classList.add('hidden'); });
}
function spawnPackets() { state.packets = []; if (state.kgLinks.length === 0) return; for (let i = 0; i < Math.min(state.kgLinks.length, 25); i++) spawnPacket(); }
function spawnPacket() { if (state.kgLinks.length === 0 || state.packets.length > 35) return; const link = state.kgLinks[Math.floor(Math.random() * state.kgLinks.length)]; const f = state.kgNodes.find(n => n.id === link.from), t = state.kgNodes.find(n => n.id === link.to); if (!f || !t) return; state.packets.push({ fromId: link.from, toId: link.to, progress: 0, speed: Math.random() * 0.012 + 0.006, color: link.color || '#a78bfa' }); }
function animateKG() {
const canvas = document.getElementById('kgCanvas');
if (!canvas) { state.canvasAnimId = requestAnimationFrame(animateKG); return; }
const ctx = canvas.getContext('2d'), w = canvas.width, h = canvas.height;
ctx.clearRect(0, 0, w, h);
// Draw links
state.kgLinks.forEach(link => {
const f = state.kgNodes.find(n => n.id === link.from), t = state.kgNodes.find(n => n.id === link.to);
if (!f || !t) return;
ctx.beginPath(); ctx.moveTo(f.x * w, f.y * h); ctx.lineTo(t.x * w, t.y * h);
ctx.strokeStyle = (link.color || '#8b5cf6') + '30'; ctx.lineWidth = 1; ctx.stroke();
});
// Draw packets
state.packets = state.packets.filter(p => {
p.progress += p.speed; if (p.progress >= 1) return false;
const f = state.kgNodes.find(n => n.id === p.fromId), t = state.kgNodes.find(n => n.id === p.toId);
if (!f || !t) return false;
const px = f.x * w + (t.x * w - f.x * w) * p.progress, py = f.y * h + (t.y * h - f.y * h) * p.progress;
ctx.beginPath(); ctx.arc(px, py, 4, 0, Math.PI * 2); ctx.fillStyle = p.color + '40'; ctx.fill();
ctx.beginPath(); ctx.arc(px, py, 2, 0, Math.PI * 2); ctx.fillStyle = p.color; ctx.fill();
return true;
});
// Draw nodes
state.kgNodes.forEach(node => {
const nx = node.x * w, ny = node.y * h, isH = state.hoveredNode === node;
const r = node.type === 'brand' ? 18 : 12;
const repScore = Math.round((node.trust + node.authority + node.sentiment + node.influence + node.eeat) / 5);
const nodeColor = node.isolated ? '#64748b' : node.type === 'competitor' ? '#ef4444' : repScore > 60 ? '#22c55e' : repScore > 40 ? '#06b6d4' : '#8b5cf6';
// Glow
const grad = ctx.createRadialGradient(nx, ny, 0, nx, ny, r + 12);
grad.addColorStop(0, nodeColor + '40'); grad.addColorStop(1, 'rgba(0,0,0,0)');
ctx.beginPath(); ctx.arc(nx, ny, r + 12, 0, Math.PI * 2); ctx.fillStyle = grad; ctx.fill();
// Circle
ctx.beginPath(); ctx.arc(nx, ny, isH ? r + 3 : r, 0, Math.PI * 2); ctx.fillStyle = '#1e293b'; ctx.strokeStyle = nodeColor; ctx.lineWidth = 2.5; ctx.fill(); ctx.stroke();
// Inner dot
if (!node.isolated) { ctx.beginPath(); ctx.arc(nx, ny, 3, 0, Math.PI * 2); ctx.fillStyle = nodeColor; ctx.fill(); }
// Label
if (node.type === 'brand' || isH) { ctx.fillStyle = '#cbd5e1'; ctx.font = '10px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'top'; ctx.fillText(node.name, nx, ny + r + 5); }
});
if (Math.random() < 0.08) spawnPacket();
state.canvasAnimId = requestAnimationFrame(animateKG);
}
// ---- Copy / Export ----
function copyToClipboard(t) { if (!t) return Promise.reject('Empty'); if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(t); return new Promise((res, rej) => { const ta = document.createElement('textarea'); ta.value = t; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); res(); } catch (e) { rej(e); } finally { document.body.removeChild(ta); } }); }
function copyReport() { const e = document.getElementById('reportOutput'); if (e) copyToClipboard(e.textContent).then(() => showToast('Rapport copié !')).catch(() => showToast('Échec', true)); }
function copyStrategy() { const e = document.getElementById('strategyOutput'); if (e) copyToClipboard(e.textContent).then(() => showToast('Stratégie copiée !')).catch(() => showToast('Échec', true)); }
function exportReport() { const e = document.getElementById('reportOutput'); if (!e || !e.textContent || e.textContent === 'En attente de génération...') { showToast('Lancez d\'abord la construction', true); return; } const b = new Blob([e.textContent], { type: 'text/plain' }); const u = URL.createObjectURL(b); const a = document.createElement('a'); a.href = u; a.download = `knowledge-graph-${state.domain.replace(/[^a-zA-Z0-9]/g, '_')}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(u); showToast('Rapport exporté !'); }
function switchTab(tab, btn) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
if (btn) btn.classList.add('active');
document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
const c = document.getElementById('tab-' + tab); if (c) c.classList.remove('hidden');
}
function addLog(message, type = 'info') { const now = new Date(); const timeStr = now.toTimeString().split(' ')[0]; state.logs.unshift({ message, type, time: timeStr }); if (state.logs.length > 50) state.logs.pop(); renderLogs(); }
function renderLogs() {
const el = document.getElementById('activityLog'); if (!el) return;
const cm = { info: 'text-slate-400', success: 'text-green-400', warning: 'text-amber-400', error: 'text-red-400', system: 'text-violet-400' };
const pm = { info: 'INF', success: 'OK ', warning: 'WRN', error: 'ERR', system: 'SYS' };
el.innerHTML = state.logs.map(l => `${l.time}[${pm[l.type] || 'INF'}]${l.message}
`).join('');
}
function clearLogs() { state.logs = []; renderLogs(); showToast('Journal effacé'); }
function toggleMobileMenu() { const m = document.getElementById('mobileMenu'); if (m) m.classList.toggle('hidden'); }
function showToast(message, isError) {
const t = document.getElementById('toast'), m = document.getElementById('toastMsg');
if (!t || !m) return; m.textContent = message;
const iconContainer = t.querySelector('.rounded-lg');
if (iconContainer) {
const iconEl = iconContainer.querySelector('i');
if (iconEl) {
if (isError) { iconContainer.className = 'w-8 h-8 bg-red-500/20 rounded-lg flex items-center justify-center'; iconEl.setAttribute('data-lucide', 'x'); iconEl.className = 'w-4 h-4 text-red-400'; }
else { iconContainer.className = 'w-8 h-8 bg-violet-500/20 rounded-lg flex items-center justify-center'; iconEl.setAttribute('data-lucide', 'check'); iconEl.className = 'w-4 h-4 text-violet-400'; }
lucide.createIcons();
}
}
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2500);
}