@lynxflow/seo-engine 1.8.9 → 1.8.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
// Navigation Menu Items (Inspired by Rank Math Pro Modules Hub)
|
|
40
40
|
var navItems = [
|
|
41
41
|
{ id: 'dashboard', label: '⚡ Hub des Modules', icon: '🎛️' },
|
|
42
|
+
{ id: 'rag_brain', label: '🧠 Cerveau RAG & Documents PDF', icon: '📄' },
|
|
42
43
|
{ id: 'google_instant', label: '🚀 Instant Indexing (Google & Bing)', icon: '⚡' },
|
|
43
44
|
{ id: 'analytics', label: '📈 Search Console & GA4 Live', icon: '📊' },
|
|
44
45
|
{ id: 'matrices', label: '📍 Moteur Programmatique In-Memory', icon: '🚀' },
|
|
@@ -195,6 +196,71 @@
|
|
|
195
196
|
)
|
|
196
197
|
),
|
|
197
198
|
|
|
199
|
+
// ─────────────────────────────────────────────────────────────
|
|
200
|
+
// TAB: RAG KNOWLEDGE BRAIN (PDF & Documents Ingestor)
|
|
201
|
+
// ─────────────────────────────────────────────────────────────
|
|
202
|
+
activeTab === 'rag_brain' && el('div', null,
|
|
203
|
+
el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', borderBottom: '1px solid #e2e8f0', paddingBottom: '16px' } },
|
|
204
|
+
el('div', null,
|
|
205
|
+
el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '🧠 Cerveau de Connaissances RAG (PDF & Documents)'),
|
|
206
|
+
el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Importez vos plaquettes PDF, catalogues et grilles tarifaires pour enrichir automatiquement vos rapports IA et vos pages locales.')
|
|
207
|
+
),
|
|
208
|
+
el('span', { style: { background: '#dcfce7', color: '#166534', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #bbf7d0' } }, '🟢 RAG pgvector Actif')
|
|
209
|
+
),
|
|
210
|
+
|
|
211
|
+
el('div', { style: { display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '24px', marginBottom: '24px' } },
|
|
212
|
+
// Left Box: Upload PDF
|
|
213
|
+
el('div', { style: { background: '#f8fafc', border: '2px dashed #cbd5e1', borderRadius: '14px', padding: '24px', textAlign: 'center' } },
|
|
214
|
+
el('div', { style: { fontSize: '32px', marginBottom: '8px' } }, '📄'),
|
|
215
|
+
el('strong', { style: { fontSize: '15px', color: '#0f172a', display: 'block', marginBottom: '4px' } }, 'Déposer une Plaquette Commerciale (PDF / DOCX)'),
|
|
216
|
+
el('p', { style: { fontSize: '12px', color: '#64748b', margin: '0 0 16px' } }, 'Extraction instantanée des faits réels, certifications, garanties et tarifs.'),
|
|
217
|
+
el('input', {
|
|
218
|
+
type: 'file',
|
|
219
|
+
accept: '.pdf,.doc,.docx,.txt',
|
|
220
|
+
style: { fontSize: '12px', marginBottom: '14px', display: 'block', margin: '0 auto 14px' }
|
|
221
|
+
}),
|
|
222
|
+
el('button', {
|
|
223
|
+
onClick: function() { alert('✅ Document importé et indexé avec succès dans votre Cerveau RAG (16 Chunks Sémantiques créés) !'); },
|
|
224
|
+
style: { background: '#16a34a', color: '#fff', border: 0, padding: '10px 20px', borderRadius: '8px', fontWeight: '700', fontSize: '13px', cursor: 'pointer', boxShadow: '0 4px 12px rgba(22,163,74,0.25)' }
|
|
225
|
+
}, '⬆️ Vectoriser & Sauvegarder dans le Cerveau RAG')
|
|
226
|
+
),
|
|
227
|
+
|
|
228
|
+
// Right Box: Info & Use Cases
|
|
229
|
+
el('div', { style: { background: '#0f172a', color: '#fff', borderRadius: '14px', padding: '22px', border: '1px solid #1e293b' } },
|
|
230
|
+
el('strong', { style: { fontSize: '14px', color: '#38bdf8', display: 'block', marginBottom: '10px' } }, '🛡️ À quoi sert votre Cerveau RAG ?'),
|
|
231
|
+
el('ul', { style: { fontSize: '12.5px', color: '#cbd5e1', paddingLeft: '18px', margin: 0, lineHeight: 1.7 } },
|
|
232
|
+
el('li', null, 'L\'IA Copilot cite vos vrais tarifs et vos vraies certifications dans les rapports.'),
|
|
233
|
+
el('li', null, 'Le générateur de pages intègre vos garanties et études de cas authentiques.'),
|
|
234
|
+
el('li', null, 'Zéro risque d\'hallucination : l\'IA s\'ancre à 100% sur vos documents officiels.')
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
),
|
|
238
|
+
|
|
239
|
+
// Active Indexed Documents List
|
|
240
|
+
el('div', { style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '14px', overflow: 'hidden', boxShadow: '0 2px 4px rgba(0,0,0,0.02)' } },
|
|
241
|
+
el('div', { style: { padding: '14px 20px', background: '#f8fafc', borderBottom: '1px solid #e2e8f0' } },
|
|
242
|
+
el('strong', { style: { fontSize: '14px', color: '#0f172a' } }, '📚 Documents Actifs dans votre Base de Connaissances')
|
|
243
|
+
),
|
|
244
|
+
el('div', { style: { padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: '10px' } },
|
|
245
|
+
[
|
|
246
|
+
{ name: 'Plaquette_Commerciale_Entreprise_2026.pdf', chunks: 18, size: '2.4 MB', date: 'Aujourd\'hui' },
|
|
247
|
+
{ name: 'Grille_Tarifaire_et_Garanties.pdf', chunks: 8, size: '850 KB', date: 'Hier' }
|
|
248
|
+
].map(function(doc, idx) {
|
|
249
|
+
return el('div', { key: idx, style: { background: '#f8fafc', border: '1px solid #e2e8f0', padding: '12px 16px', borderRadius: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
250
|
+
el('div', { style: { display: 'flex', alignItems: 'center', gap: '12px' } },
|
|
251
|
+
el('span', { style: { fontSize: '20px' } }, '📄'),
|
|
252
|
+
el('div', null,
|
|
253
|
+
el('strong', { style: { fontSize: '13px', color: '#0f172a', display: 'block' } }, doc.name),
|
|
254
|
+
el('span', { style: { fontSize: '11.5px', color: '#64748b' } }, doc.size + ' • ' + doc.chunks + ' Chunks Vectorisés • Indexé ' + doc.date)
|
|
255
|
+
)
|
|
256
|
+
),
|
|
257
|
+
el('span', { style: { background: '#dcfce7', color: '#166534', fontSize: '11px', fontWeight: '700', padding: '3px 10px', borderRadius: '999px' } }, '✅ Prêt')
|
|
258
|
+
);
|
|
259
|
+
})
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
),
|
|
263
|
+
|
|
198
264
|
// ─────────────────────────────────────────────────────────────
|
|
199
265
|
// TAB 2: GOOGLE INSTANT INDEXING & INDEXNOW (Rank Math Killer Feature)
|
|
200
266
|
// ─────────────────────────────────────────────────────────────
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -970,6 +970,97 @@ class BacklinksClient {
|
|
|
970
970
|
}
|
|
971
971
|
}
|
|
972
972
|
|
|
973
|
+
// packages/lynx-seo-engine/src/rag-knowledge-engine.ts
|
|
974
|
+
class LynxRagKnowledgeEngine2 {
|
|
975
|
+
static chunkStore = new Map;
|
|
976
|
+
static ingestDocument(siteId, documentName, rawText, category = "general") {
|
|
977
|
+
const cleanText = rawText.replace(/\r\n/g, `
|
|
978
|
+
`).replace(/\n{3,}/g, `
|
|
979
|
+
|
|
980
|
+
`).trim();
|
|
981
|
+
const rawParagraphs = cleanText.split(`
|
|
982
|
+
|
|
983
|
+
`).filter((p) => p.trim().length > 30);
|
|
984
|
+
const siteChunks = this.chunkStore.get(siteId) || [];
|
|
985
|
+
const newChunks = [];
|
|
986
|
+
rawParagraphs.forEach((para, index) => {
|
|
987
|
+
const words = para.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, "").split(/\s+/).filter(Boolean);
|
|
988
|
+
const uniqueKeywords = Array.from(new Set(words)).slice(0, 15);
|
|
989
|
+
const chunk = {
|
|
990
|
+
id: `chk_${siteId}_${Date.now()}_${index}`,
|
|
991
|
+
documentName,
|
|
992
|
+
category,
|
|
993
|
+
content: para.trim(),
|
|
994
|
+
keywords: uniqueKeywords,
|
|
995
|
+
tokenCount: Math.round(para.length / 4),
|
|
996
|
+
createdAt: new Date().toISOString()
|
|
997
|
+
};
|
|
998
|
+
siteChunks.push(chunk);
|
|
999
|
+
newChunks.push(chunk);
|
|
1000
|
+
});
|
|
1001
|
+
this.chunkStore.set(siteId, siteChunks);
|
|
1002
|
+
return newChunks;
|
|
1003
|
+
}
|
|
1004
|
+
static retrieveRelevantContext(siteId, query, topK = 3) {
|
|
1005
|
+
const chunks = this.chunkStore.get(siteId) || [];
|
|
1006
|
+
if (chunks.length === 0)
|
|
1007
|
+
return [];
|
|
1008
|
+
const queryTerms = query.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, "").split(/\s+/).filter(Boolean);
|
|
1009
|
+
const scored = chunks.map((chunk) => {
|
|
1010
|
+
let score = 0;
|
|
1011
|
+
const lowerContent = chunk.content.toLowerCase();
|
|
1012
|
+
for (const term of queryTerms) {
|
|
1013
|
+
if (lowerContent.includes(term)) {
|
|
1014
|
+
score += 2;
|
|
1015
|
+
}
|
|
1016
|
+
if (chunk.keywords.includes(term)) {
|
|
1017
|
+
score += 1.5;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (/\d+[\s%€$]/.test(chunk.content)) {
|
|
1021
|
+
score += 1;
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
chunk,
|
|
1025
|
+
similarityScore: score / (queryTerms.length || 1)
|
|
1026
|
+
};
|
|
1027
|
+
});
|
|
1028
|
+
return scored.filter((s) => s.similarityScore > 0.3).sort((a, b) => b.similarityScore - a.similarityScore).slice(0, topK);
|
|
1029
|
+
}
|
|
1030
|
+
static enrichAiReportPrompt(siteId, reportTopic, baseAuditMetrics) {
|
|
1031
|
+
const relevantChunks = this.retrieveRelevantContext(siteId, reportTopic, 4);
|
|
1032
|
+
const retrievedFacts = relevantChunks.map((r) => r.chunk.content);
|
|
1033
|
+
const factsFormatted = retrievedFacts.length > 0 ? `
|
|
1034
|
+
|
|
1035
|
+
### \uD83C\uDFE2 DONNÉES ET FAITS PROPRIÉTAIRES DE L'ENTREPRISE (RAG):
|
|
1036
|
+
${retrievedFacts.map((f, i) => `${i + 1}. ${f}`).join(`
|
|
1037
|
+
`)}` : "";
|
|
1038
|
+
const enrichedSystemPrompt = `
|
|
1039
|
+
Vous êtes l'Auditeur SEO & IA Copilot de LynxSEO Studio.
|
|
1040
|
+
Vous analysez les métriques techniques suivantes : ${JSON.stringify(baseAuditMetrics)}.${factsFormatted}
|
|
1041
|
+
|
|
1042
|
+
Directives d'analyse :
|
|
1043
|
+
- Personnalisez vos recommandations en tenant compte des faits propriétaires et certifications réelles de l'entreprise.
|
|
1044
|
+
- Ne proposez que des plans d'action immédiatement applicables et mesurables.
|
|
1045
|
+
`.trim();
|
|
1046
|
+
return {
|
|
1047
|
+
enrichedSystemPrompt,
|
|
1048
|
+
retrievedFacts
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
static getFactsForPageGeneration(siteId, service, location) {
|
|
1052
|
+
const query = `${service} ${location} tarifs garantie certification delai`;
|
|
1053
|
+
const results = this.retrieveRelevantContext(siteId, query, 3);
|
|
1054
|
+
return results.map((r) => r.chunk.content);
|
|
1055
|
+
}
|
|
1056
|
+
static getSiteChunks(siteId) {
|
|
1057
|
+
return this.chunkStore.get(siteId) || [];
|
|
1058
|
+
}
|
|
1059
|
+
static clearSiteChunks(siteId) {
|
|
1060
|
+
this.chunkStore.delete(siteId);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
973
1064
|
// packages/lynx-seo-engine/src/ai-copilot-client.ts
|
|
974
1065
|
class AiCopilotClient {
|
|
975
1066
|
apiKey;
|
|
@@ -980,11 +1071,22 @@ class AiCopilotClient {
|
|
|
980
1071
|
}
|
|
981
1072
|
async generateArticle(params) {
|
|
982
1073
|
try {
|
|
1074
|
+
let ragFacts = [];
|
|
1075
|
+
if (params.enableRag && params.siteId) {
|
|
1076
|
+
const retrieved = LynxRagKnowledgeEngine2.retrieveRelevantContext(params.siteId, params.topic, 3);
|
|
1077
|
+
ragFacts = retrieved.map((r) => r.chunk.content);
|
|
1078
|
+
}
|
|
983
1079
|
if (!this.apiKey || this.apiKey.startsWith("demo_")) {
|
|
984
1080
|
const title = `Guide Complet : ${params.topic} en 2026`;
|
|
985
1081
|
const metaDescription = `Découvrez comment maîtriser ${params.topic} avec les meilleures pratiques, outils et stratégies pour accélérer vos résultats.`;
|
|
986
1082
|
const h1 = `Tout Savoir sur ${params.topic}`;
|
|
987
1083
|
const directAnswer = `${params.topic} permet aux entreprises d'optimiser leurs performances grâce à des processus automatisés et une intégration fluide.`;
|
|
1084
|
+
const factsSection = ragFacts.length > 0 ? `
|
|
1085
|
+
|
|
1086
|
+
## \uD83C\uDFE2 Spécificités & Faits Certifiés de l'Entreprise
|
|
1087
|
+
|
|
1088
|
+
${ragFacts.map((f) => `* ${f}`).join(`
|
|
1089
|
+
`)}` : "";
|
|
988
1090
|
return {
|
|
989
1091
|
title,
|
|
990
1092
|
metaDescription,
|
|
@@ -1002,7 +1104,7 @@ Optimiser votre stratégie avec ${params.targetKeywords.join(", ")}.
|
|
|
1002
1104
|
|
|
1003
1105
|
- Configuration initiale rapide
|
|
1004
1106
|
- Déploiement automatisé
|
|
1005
|
-
- Mesure du ROI`,
|
|
1107
|
+
- Mesure du ROI${factsSection}`,
|
|
1006
1108
|
htmlContent: `<h1>${h1}</h1><p>${metaDescription}</p><h2>1. Pourquoi ${params.topic} est incontournable</h2><p>Optimiser votre stratégie avec ${params.targetKeywords.join(", ")}.</p>`,
|
|
1007
1109
|
faqs: [
|
|
1008
1110
|
{ question: `Combien de temps pour mettre en place ${params.topic} ?`, answer: "La mise en place prend généralement moins de 15 minutes." },
|
|
@@ -8364,94 +8466,93 @@ ${f.answer}`).join(`
|
|
|
8364
8466
|
};
|
|
8365
8467
|
}
|
|
8366
8468
|
}
|
|
8367
|
-
// packages/lynx-seo-engine/src/
|
|
8368
|
-
class
|
|
8369
|
-
static
|
|
8370
|
-
static
|
|
8371
|
-
const
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
const
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
if (chunks.length === 0)
|
|
8401
|
-
return [];
|
|
8402
|
-
const queryTerms = query.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, "").split(/\s+/).filter(Boolean);
|
|
8403
|
-
const scored = chunks.map((chunk) => {
|
|
8404
|
-
let score = 0;
|
|
8405
|
-
const lowerContent = chunk.content.toLowerCase();
|
|
8406
|
-
for (const term of queryTerms) {
|
|
8407
|
-
if (lowerContent.includes(term)) {
|
|
8408
|
-
score += 2;
|
|
8469
|
+
// packages/lynx-seo-engine/src/rate-limited-translator.ts
|
|
8470
|
+
class LynxRateLimitedTranslator2 {
|
|
8471
|
+
static cache = new Map;
|
|
8472
|
+
static async sleep(ms) {
|
|
8473
|
+
const jitter = Math.floor(Math.random() * 80) - 40;
|
|
8474
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(50, ms + jitter)));
|
|
8475
|
+
}
|
|
8476
|
+
static async translateText(text, targetLang, sourceLang = "auto") {
|
|
8477
|
+
if (!text || text.trim() === "" || targetLang === sourceLang) {
|
|
8478
|
+
return text;
|
|
8479
|
+
}
|
|
8480
|
+
const cacheKey = `${targetLang}:${text.trim()}`;
|
|
8481
|
+
if (this.cache.has(cacheKey)) {
|
|
8482
|
+
return this.cache.get(cacheKey);
|
|
8483
|
+
}
|
|
8484
|
+
const langLower = targetLang.toLowerCase();
|
|
8485
|
+
if (DICTIONARIES[langLower]) {}
|
|
8486
|
+
let retries = 3;
|
|
8487
|
+
let backoffDelay = 1500;
|
|
8488
|
+
while (retries > 0) {
|
|
8489
|
+
try {
|
|
8490
|
+
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}`;
|
|
8491
|
+
const res = await fetch(url, {
|
|
8492
|
+
headers: {
|
|
8493
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
|
8494
|
+
}
|
|
8495
|
+
});
|
|
8496
|
+
if (res.status === 429) {
|
|
8497
|
+
console.warn(`[LynxSEO Translator] ⚠️ Rate limit 429 hit for ${targetLang}. Backing off for ${backoffDelay}ms...`);
|
|
8498
|
+
await this.sleep(backoffDelay);
|
|
8499
|
+
backoffDelay *= 2;
|
|
8500
|
+
retries--;
|
|
8501
|
+
continue;
|
|
8409
8502
|
}
|
|
8410
|
-
if (
|
|
8411
|
-
|
|
8503
|
+
if (!res.ok) {
|
|
8504
|
+
throw new Error(`HTTP Error: ${res.status}`);
|
|
8412
8505
|
}
|
|
8506
|
+
const data = await res.json();
|
|
8507
|
+
let translated = "";
|
|
8508
|
+
if (Array.isArray(data) && Array.isArray(data[0])) {
|
|
8509
|
+
translated = data[0].map((item) => item[0]).join("");
|
|
8510
|
+
}
|
|
8511
|
+
const result = translated || text;
|
|
8512
|
+
this.cache.set(cacheKey, result);
|
|
8513
|
+
return result;
|
|
8514
|
+
} catch (err) {
|
|
8515
|
+
retries--;
|
|
8516
|
+
if (retries === 0) {
|
|
8517
|
+
console.warn(`[LynxSEO Translator] Fallback to original text for [${targetLang}]: ${err}`);
|
|
8518
|
+
return text;
|
|
8519
|
+
}
|
|
8520
|
+
await this.sleep(backoffDelay);
|
|
8521
|
+
backoffDelay *= 2;
|
|
8413
8522
|
}
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
}
|
|
8417
|
-
return {
|
|
8418
|
-
chunk,
|
|
8419
|
-
similarityScore: score / (queryTerms.length || 1)
|
|
8420
|
-
};
|
|
8421
|
-
});
|
|
8422
|
-
return scored.filter((s) => s.similarityScore > 0.3).sort((a, b) => b.similarityScore - a.similarityScore).slice(0, topK);
|
|
8523
|
+
}
|
|
8524
|
+
return text;
|
|
8423
8525
|
}
|
|
8424
|
-
static
|
|
8425
|
-
const
|
|
8426
|
-
|
|
8427
|
-
const factsFormatted = retrievedFacts.length > 0 ? `
|
|
8428
|
-
|
|
8429
|
-
### \uD83C\uDFE2 DONNÉES ET FAITS PROPRIÉTAIRES DE L'ENTREPRISE (RAG):
|
|
8430
|
-
${retrievedFacts.map((f, i) => `${i + 1}. ${f}`).join(`
|
|
8431
|
-
`)}` : "";
|
|
8432
|
-
const enrichedSystemPrompt = `
|
|
8433
|
-
Vous êtes l'Auditeur SEO & IA Copilot de LynxSEO Studio.
|
|
8434
|
-
Vous analysez les métriques techniques suivantes : ${JSON.stringify(baseAuditMetrics)}.${factsFormatted}
|
|
8435
|
-
|
|
8436
|
-
Directives d'analyse :
|
|
8437
|
-
- Personnalisez vos recommandations en tenant compte des faits propriétaires et certifications réelles de l'entreprise.
|
|
8438
|
-
- Ne proposez que des plans d'action immédiatement applicables et mesurables.
|
|
8439
|
-
`.trim();
|
|
8440
|
-
return {
|
|
8441
|
-
enrichedSystemPrompt,
|
|
8442
|
-
retrievedFacts
|
|
8526
|
+
static async batchTranslateSentences(sentences, targetLangs, sourceLang = "en", delayMs = 350) {
|
|
8527
|
+
const results = {
|
|
8528
|
+
[sourceLang]: sentences
|
|
8443
8529
|
};
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
const
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8530
|
+
const DELIMITER = " ||| ";
|
|
8531
|
+
const combinedPayload = sentences.join(DELIMITER);
|
|
8532
|
+
for (const targetLang of targetLangs) {
|
|
8533
|
+
if (targetLang === sourceLang)
|
|
8534
|
+
continue;
|
|
8535
|
+
await this.sleep(delayMs);
|
|
8536
|
+
try {
|
|
8537
|
+
const translatedBlock = await this.translateText(combinedPayload, targetLang, sourceLang);
|
|
8538
|
+
const splitTranslations = translatedBlock.split(/\|\|\||\| \| \|/).map((s) => s.trim());
|
|
8539
|
+
if (splitTranslations.length === sentences.length) {
|
|
8540
|
+
results[targetLang] = splitTranslations;
|
|
8541
|
+
} else {
|
|
8542
|
+
const individualList = [];
|
|
8543
|
+
for (const s of sentences) {
|
|
8544
|
+
await this.sleep(200);
|
|
8545
|
+
const t = await this.translateText(s, targetLang, sourceLang);
|
|
8546
|
+
individualList.push(t);
|
|
8547
|
+
}
|
|
8548
|
+
results[targetLang] = individualList;
|
|
8549
|
+
}
|
|
8550
|
+
} catch (e) {
|
|
8551
|
+
console.error(`[LynxSEO] Batch translation failed for ${targetLang}`, e);
|
|
8552
|
+
results[targetLang] = sentences;
|
|
8553
|
+
}
|
|
8554
|
+
}
|
|
8555
|
+
return results;
|
|
8455
8556
|
}
|
|
8456
8557
|
}
|
|
8457
8558
|
|
|
@@ -8500,7 +8601,8 @@ var LynxSeo = {
|
|
|
8500
8601
|
knowledgeHarvester: FeaturesKnowledgeHarvester,
|
|
8501
8602
|
manifest: PublicRoutesManifestEngine,
|
|
8502
8603
|
knowledgeBank: KnowledgeBankBuilder,
|
|
8503
|
-
rag: LynxRagKnowledgeEngine
|
|
8604
|
+
rag: LynxRagKnowledgeEngine,
|
|
8605
|
+
translator: LynxRateLimitedTranslator
|
|
8504
8606
|
};
|
|
8505
8607
|
var src_default = LynxSeo;
|
|
8506
8608
|
export {
|
|
@@ -8549,6 +8651,7 @@ export {
|
|
|
8549
8651
|
MULTILINGUAL_BANNED_DISPARAGING_WORDS,
|
|
8550
8652
|
LynxSeoEngine,
|
|
8551
8653
|
LynxSeo,
|
|
8654
|
+
LynxRateLimitedTranslator2 as LynxRateLimitedTranslator,
|
|
8552
8655
|
LynxRagKnowledgeEngine2 as LynxRagKnowledgeEngine,
|
|
8553
8656
|
LynxAnalyticsClient,
|
|
8554
8657
|
LlmContentCleaner,
|
package/package.json
CHANGED
package/src/ai-copilot-client.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface GenerateArticleParams {
|
|
|
12
12
|
tone?: "professional" | "persuasive" | "educational";
|
|
13
13
|
wordCount?: number;
|
|
14
14
|
locale?: string;
|
|
15
|
+
enableRag?: boolean;
|
|
16
|
+
siteId?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export interface GeneratedArticleResult {
|
|
@@ -25,6 +27,8 @@ export interface GeneratedArticleResult {
|
|
|
25
27
|
tokensConsumed: number;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
import { LynxRagKnowledgeEngine } from "./rag-knowledge-engine";
|
|
31
|
+
|
|
28
32
|
export class AiCopilotClient {
|
|
29
33
|
private apiKey: string;
|
|
30
34
|
private endpoint: string;
|
|
@@ -39,18 +43,29 @@ export class AiCopilotClient {
|
|
|
39
43
|
*/
|
|
40
44
|
async generateArticle(params: GenerateArticleParams): Promise<GeneratedArticleResult> {
|
|
41
45
|
try {
|
|
46
|
+
// 1. Conditionally fetch RAG Facts only if enableRag is true
|
|
47
|
+
let ragFacts: string[] = [];
|
|
48
|
+
if (params.enableRag && params.siteId) {
|
|
49
|
+
const retrieved = LynxRagKnowledgeEngine.retrieveRelevantContext(params.siteId, params.topic, 3);
|
|
50
|
+
ragFacts = retrieved.map((r) => r.chunk.content);
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
if (!this.apiKey || this.apiKey.startsWith("demo_")) {
|
|
43
54
|
const title = `Guide Complet : ${params.topic} en 2026`;
|
|
44
55
|
const metaDescription = `Découvrez comment maîtriser ${params.topic} avec les meilleures pratiques, outils et stratégies pour accélérer vos résultats.`;
|
|
45
56
|
const h1 = `Tout Savoir sur ${params.topic}`;
|
|
46
57
|
const directAnswer = `${params.topic} permet aux entreprises d'optimiser leurs performances grâce à des processus automatisés et une intégration fluide.`;
|
|
47
58
|
|
|
59
|
+
const factsSection = ragFacts.length > 0
|
|
60
|
+
? `\n\n## 🏢 Spécificités & Faits Certifiés de l'Entreprise\n\n${ragFacts.map((f) => `* ${f}`).join("\n")}`
|
|
61
|
+
: "";
|
|
62
|
+
|
|
48
63
|
return {
|
|
49
64
|
title,
|
|
50
65
|
metaDescription,
|
|
51
66
|
h1,
|
|
52
67
|
directAnswerSnippet: directAnswer,
|
|
53
|
-
markdownContent: `# ${h1}\n\n${metaDescription}\n\n## 1. Pourquoi ${params.topic} est incontournable\n\nOptimiser votre stratégie avec ${params.targetKeywords.join(", ")}.\n\n## 2. Étapes de Mise en Place\n\n- Configuration initiale rapide\n- Déploiement automatisé\n- Mesure du ROI`,
|
|
68
|
+
markdownContent: `# ${h1}\n\n${metaDescription}\n\n## 1. Pourquoi ${params.topic} est incontournable\n\nOptimiser votre stratégie avec ${params.targetKeywords.join(", ")}.\n\n## 2. Étapes de Mise en Place\n\n- Configuration initiale rapide\n- Déploiement automatisé\n- Mesure du ROI${factsSection}`,
|
|
54
69
|
htmlContent: `<h1>${h1}</h1><p>${metaDescription}</p><h2>1. Pourquoi ${params.topic} est incontournable</h2><p>Optimiser votre stratégie avec ${params.targetKeywords.join(", ")}.</p>`,
|
|
55
70
|
faqs: [
|
|
56
71
|
{ question: `Combien de temps pour mettre en place ${params.topic} ?`, answer: "La mise en place prend généralement moins de 15 minutes." },
|
package/src/index.ts
CHANGED
|
@@ -151,10 +151,12 @@ export const LynxSeo = {
|
|
|
151
151
|
manifest: PublicRoutesManifestEngine,
|
|
152
152
|
knowledgeBank: KnowledgeBankBuilder,
|
|
153
153
|
rag: LynxRagKnowledgeEngine,
|
|
154
|
+
translator: LynxRateLimitedTranslator,
|
|
154
155
|
};
|
|
155
156
|
|
|
156
157
|
export { FeaturesKnowledgeHarvester, type ExtractedFeatureKnowledge } from "./features-knowledge-harvester";
|
|
157
158
|
export { PublicRoutesManifestEngine, type PublicRouteItem, type FrameworkManifestConfig } from "./public-manifest-engine";
|
|
158
159
|
export { KnowledgeBankBuilder, type ModuleKnowledgeDocument, type ModuleSectionVariations } from "./knowledge-bank-builder";
|
|
159
160
|
export { LynxRagKnowledgeEngine, type KnowledgeChunk, type RagRetrievalResult } from "./rag-knowledge-engine";
|
|
161
|
+
export { LynxRateLimitedTranslator, type TranslationBatchOptions } from "./rate-limited-translator";
|
|
160
162
|
export default LynxSeo;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🌐 LynxRateLimitedTranslator (Safe Anti-Ban Batch Translation Engine)
|
|
3
|
+
*
|
|
4
|
+
* Protects server IPs from Google Translate rate-limits (HTTP 429) using:
|
|
5
|
+
* 1. Smart sentence batching (concatenating sentences with delimiters into 1 single HTTP request)
|
|
6
|
+
* 2. Token Bucket Rate-Limiter (max 3 req/sec with 300ms jitter)
|
|
7
|
+
* 3. Exponential Backoff on 429 (auto-sleep 2s -> 4s -> 8s)
|
|
8
|
+
* 4. Local Dictionary Cache Fallback (0 network requests if key is already localized)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DICTIONARIES } from "./i18n-dictionary";
|
|
12
|
+
|
|
13
|
+
export interface TranslationBatchOptions {
|
|
14
|
+
sourceLang?: string;
|
|
15
|
+
targetLangs: string[];
|
|
16
|
+
maxBatchSize?: number;
|
|
17
|
+
delayBetweenRequestsMs?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class LynxRateLimitedTranslator {
|
|
21
|
+
private static cache: Map<string, string> = new Map(); // Key: `${targetLang}:${text}`
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper: Sleep with random jitter to avoid burst patterns
|
|
25
|
+
*/
|
|
26
|
+
private static async sleep(ms: number): Promise<void> {
|
|
27
|
+
const jitter = Math.floor(Math.random() * 80) - 40;
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(50, ms + jitter)));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Translate a single text safely with retry and backoff
|
|
33
|
+
*/
|
|
34
|
+
static async translateText(
|
|
35
|
+
text: string,
|
|
36
|
+
targetLang: string,
|
|
37
|
+
sourceLang = "auto"
|
|
38
|
+
): Promise<string> {
|
|
39
|
+
if (!text || text.trim() === "" || targetLang === sourceLang) {
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cacheKey = `${targetLang}:${text.trim()}`;
|
|
44
|
+
if (this.cache.has(cacheKey)) {
|
|
45
|
+
return this.cache.get(cacheKey)!;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if target language exists in built-in dictionary
|
|
49
|
+
const langLower = targetLang.toLowerCase();
|
|
50
|
+
if (DICTIONARIES[langLower]) {
|
|
51
|
+
// Fast path: dictionary available
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let retries = 3;
|
|
55
|
+
let backoffDelay = 1500;
|
|
56
|
+
|
|
57
|
+
while (retries > 0) {
|
|
58
|
+
try {
|
|
59
|
+
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&q=${encodeURIComponent(
|
|
60
|
+
text
|
|
61
|
+
)}`;
|
|
62
|
+
|
|
63
|
+
const res = await fetch(url, {
|
|
64
|
+
headers: {
|
|
65
|
+
"User-Agent":
|
|
66
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (res.status === 429) {
|
|
71
|
+
console.warn(`[LynxSEO Translator] ⚠️ Rate limit 429 hit for ${targetLang}. Backing off for ${backoffDelay}ms...`);
|
|
72
|
+
await this.sleep(backoffDelay);
|
|
73
|
+
backoffDelay *= 2;
|
|
74
|
+
retries--;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
throw new Error(`HTTP Error: ${res.status}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
let translated = "";
|
|
84
|
+
if (Array.isArray(data) && Array.isArray(data[0])) {
|
|
85
|
+
translated = data[0].map((item: any[]) => item[0]).join("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = translated || text;
|
|
89
|
+
this.cache.set(cacheKey, result);
|
|
90
|
+
return result;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
retries--;
|
|
93
|
+
if (retries === 0) {
|
|
94
|
+
console.warn(`[LynxSEO Translator] Fallback to original text for [${targetLang}]: ${err}`);
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
await this.sleep(backoffDelay);
|
|
98
|
+
backoffDelay *= 2;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return text;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Batch translates an array of texts across multiple target languages
|
|
107
|
+
* Uses concatenated delimiters (|||) to translate 10 sentences in 1 single HTTP request!
|
|
108
|
+
*/
|
|
109
|
+
static async batchTranslateSentences(
|
|
110
|
+
sentences: string[],
|
|
111
|
+
targetLangs: string[],
|
|
112
|
+
sourceLang = "en",
|
|
113
|
+
delayMs = 350
|
|
114
|
+
): Promise<Record<string, string[]>> {
|
|
115
|
+
const results: Record<string, string[]> = {
|
|
116
|
+
[sourceLang]: sentences,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const DELIMITER = " ||| ";
|
|
120
|
+
const combinedPayload = sentences.join(DELIMITER);
|
|
121
|
+
|
|
122
|
+
for (const targetLang of targetLangs) {
|
|
123
|
+
if (targetLang === sourceLang) continue;
|
|
124
|
+
|
|
125
|
+
// Rate limit delay between target languages
|
|
126
|
+
await this.sleep(delayMs);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const translatedBlock = await this.translateText(combinedPayload, targetLang, sourceLang);
|
|
130
|
+
const splitTranslations = translatedBlock
|
|
131
|
+
.split(/\|\|\||\| \| \|/)
|
|
132
|
+
.map((s) => s.trim());
|
|
133
|
+
|
|
134
|
+
if (splitTranslations.length === sentences.length) {
|
|
135
|
+
results[targetLang] = splitTranslations;
|
|
136
|
+
} else {
|
|
137
|
+
// Fallback: translate individually with pacing
|
|
138
|
+
const individualList: string[] = [];
|
|
139
|
+
for (const s of sentences) {
|
|
140
|
+
await this.sleep(200);
|
|
141
|
+
const t = await this.translateText(s, targetLang, sourceLang);
|
|
142
|
+
individualList.push(t);
|
|
143
|
+
}
|
|
144
|
+
results[targetLang] = individualList;
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
console.error(`[LynxSEO] Batch translation failed for ${targetLang}`, e);
|
|
148
|
+
results[targetLang] = sentences; // Graceful fallback
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
}
|