@lynxflow/seo-engine 1.8.29 → 2.0.0
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.
- package/README.md +47 -93
- package/dist/ai-copilot-client.d.ts +2 -0
- package/dist/built-in-locations.d.ts +18 -0
- package/dist/content-ai-arsenal.d.ts +28 -0
- package/dist/existing-media-harvester.d.ts +33 -0
- package/dist/extended-schemas.d.ts +84 -0
- package/dist/features-knowledge-harvester.d.ts +56 -0
- package/dist/geo-citation-scorer.d.ts +34 -0
- package/dist/i18n-detector.d.ts +1 -1
- package/dist/index.d.ts +32 -7
- package/dist/index.js +538 -82
- package/dist/index.mjs +2028 -163
- package/dist/internal-pagerank-graph.d.ts +59 -0
- package/dist/knowledge-bank-builder.d.ts +91 -0
- package/dist/marketing-skills-engine.d.ts +74 -0
- package/dist/matrix-engine.d.ts +26 -0
- package/dist/power-words-psychology.d.ts +27 -15
- package/dist/public-manifest-engine.d.ts +58 -0
- package/dist/rag-knowledge-engine.d.ts +50 -0
- package/dist/rate-limited-translator.d.ts +19 -0
- package/dist/token-quota-manager.d.ts +4 -0
- package/package.json +11 -3
- package/TUTORIEL_INTEGRATION_SITE.md +0 -283
- package/connectors/cloudflare-worker/worker.js +0 -106
- package/connectors/laravel/LynxSeoController.php +0 -37
- package/connectors/wordpress/lynxseo-admin-app.js +0 -2618
- package/connectors/wordpress/lynxseo-connector.php +0 -1712
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/lynxflow-seo-engine-1.2.0.tgz +0 -0
- package/src/ad-intelligence-cro.ts +0 -140
- package/src/ai-bots-log-analyzer.ts +0 -127
- package/src/ai-copilot-client.ts +0 -99
- package/src/analytics-client.ts +0 -315
- package/src/auth-key.ts +0 -262
- package/src/backlinks-client.ts +0 -85
- package/src/brand-dna-calendar.ts +0 -120
- package/src/brand-icons.ts +0 -138
- package/src/built-in-locations.ts +0 -170
- package/src/content-ai-arsenal.ts +0 -268
- package/src/copy-frameworks-master.ts +0 -89
- package/src/cro-copywriting-engine.ts +0 -105
- package/src/crosslink-scorer.ts +0 -101
- package/src/embeddable-seo-widget.ts +0 -57
- package/src/engine.test.ts +0 -386
- package/src/engine.ts +0 -813
- package/src/existing-media-harvester.ts +0 -98
- package/src/extended-schemas.ts +0 -589
- package/src/features-knowledge-harvester.ts +0 -190
- package/src/free-public-tools.ts +0 -224
- package/src/geo-mesh-linking.ts +0 -93
- package/src/google-business-profile.ts +0 -280
- package/src/i18n-detector.ts +0 -98
- package/src/i18n-dictionary.ts +0 -422
- package/src/index.ts +0 -164
- package/src/indexnow-client.ts +0 -94
- package/src/instant-matrix-search.ts +0 -48
- package/src/isr-cache-manager.ts +0 -78
- package/src/keyword-permutator.ts +0 -118
- package/src/knowledge-bank-builder.ts +0 -299
- package/src/knowledge-graph-linker.ts +0 -81
- package/src/lago-token-meter.ts +0 -120
- package/src/legal-disclaimers.ts +0 -407
- package/src/llm-content-cleaner.ts +0 -64
- package/src/llm-prompt.ts +0 -323
- package/src/master-marketing-engine.ts +0 -314
- package/src/matrix-engine.ts +0 -1204
- package/src/mcp-seo-server.ts +0 -108
- package/src/ngram-density-analyzer.ts +0 -108
- package/src/og-image-generator.ts +0 -109
- package/src/power-words-psychology.ts +0 -369
- package/src/public-manifest-engine.ts +0 -109
- package/src/rag-knowledge-engine.ts +0 -163
- package/src/rank-math-parity.ts +0 -235
- package/src/rate-limited-translator.ts +0 -110
- package/src/real-reviews-sync.ts +0 -207
- package/src/rss-syndication-feed.ts +0 -56
- package/src/schema-builder.ts +0 -134
- package/src/seo-opportunities-decay.ts +0 -154
- package/src/serp-client.ts +0 -89
- package/src/serp-history-alerts.ts +0 -75
- package/src/site-auditor.ts +0 -83
- package/src/site-crawler.ts +0 -406
- package/src/slug-engine.ts +0 -183
- package/src/social-ads-seo.ts +0 -211
- package/src/social-growth-suite.ts +0 -310
- package/src/social-trend-seo.ts +0 -103
- package/src/social-video-seo.ts +0 -100
- package/src/team-rbac.ts +0 -61
- package/src/technical-rules-auditor.ts +0 -171
- package/src/token-quota-manager.ts +0 -91
- package/src/types.ts +0 -143
- package/src/ui-icons.ts +0 -134
- package/src/urlytics-engine.ts +0 -97
- package/src/video-youtube-analyzer.ts +0 -81
- package/src/yoast-parity.ts +0 -161
- package/tsconfig.json +0 -15
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🧠 Universal Open Multilingual Power Words & Click Psychology Engine
|
|
3
|
-
* Supports 50+ World Languages (FR, EN, ES, DE, IT, NL, PT, AR, RU, ZH, JA, HI, PL, SV, TR, KO, etc.)
|
|
4
|
-
* With Dynamic Runtime Dictionary Registration for Infinite Language Extensibility.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export type LanguageCode = string;
|
|
8
|
-
|
|
9
|
-
export interface CategoryWordBank {
|
|
10
|
-
authority: string[];
|
|
11
|
-
urgency: string[];
|
|
12
|
-
value: string[];
|
|
13
|
-
curiosity: string[];
|
|
14
|
-
sentimentPositive: string[];
|
|
15
|
-
sentimentNegative: string[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface PsychologyAnalysisResult {
|
|
19
|
-
score: number; // 0 to 100
|
|
20
|
-
powerWordsDetected: string[];
|
|
21
|
-
sentiment: "positif" | "negatif" | "neutre";
|
|
22
|
-
hasNumber: boolean;
|
|
23
|
-
hasYear: boolean;
|
|
24
|
-
hasCuriosityTrigger: boolean;
|
|
25
|
-
hasUrgencyTrigger: boolean;
|
|
26
|
-
hasAuthorityTrigger: boolean;
|
|
27
|
-
hasCurrencyOrDiscount: boolean;
|
|
28
|
-
hasBracketsOrParentheses: boolean;
|
|
29
|
-
recommendations: string[];
|
|
30
|
-
suggestedTitles: string[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Pre-loaded high-converting dictionaries across global languages
|
|
34
|
-
export const MULTILINGUAL_POWER_WORDS: Record<string, CategoryWordBank> = {
|
|
35
|
-
fr: {
|
|
36
|
-
authority: [
|
|
37
|
-
"officiel", "guide", "expert", "complet", "certifié", "ultime", "meilleur", "meilleurs", "comparatif",
|
|
38
|
-
"maîtriser", "spécialiste", "leader", "professionnel", "authentique", "infaillible", "reconnu", "démontré", "avis"
|
|
39
|
-
],
|
|
40
|
-
urgency: [
|
|
41
|
-
"rapide", "instantané", "maintenant", "immédiat", "express", "2026", "dernière minute", "urgent",
|
|
42
|
-
"dépêchez-vous", "en 5 min", "en 10 min", "aujourd'hui", "débloquer", "vite"
|
|
43
|
-
],
|
|
44
|
-
value: [
|
|
45
|
-
"gratuit", "économique", "rentable", "offert", "sans engagement", "garanti", "facile", "clé en main",
|
|
46
|
-
"rentabilité", "gain", "économisez", "inclus", "accessible", "inédit", "remise", "promo"
|
|
47
|
-
],
|
|
48
|
-
curiosity: [
|
|
49
|
-
"exclusif", "secret", "astuces", "révélé", "inédit", "top", "méthode", "pourquoi", "comment",
|
|
50
|
-
"découvrir", "choc", "incontournable", "mystère", "incroyable", "vérité"
|
|
51
|
-
],
|
|
52
|
-
sentimentPositive: [
|
|
53
|
-
"parfait", "incroyable", "magnifique", "puissant", "réussi", "génial", "exceptionnel", "facile",
|
|
54
|
-
"meilleur", "succès", "brillant", "splendide", "radieux", "magique", "sublime"
|
|
55
|
-
],
|
|
56
|
-
sentimentNegative: [
|
|
57
|
-
"erreur", "piège", "danger", "éviter", "pire", "arnaque", "catastrophe", "risqué", "menace", "perte", "alerte"
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
en: {
|
|
61
|
-
authority: [
|
|
62
|
-
"ultimate", "complete", "definitive", "guide", "expert", "official", "proven", "master", "certified",
|
|
63
|
-
"best", "top", "authority", "case study", "benchmark", "professional", "review", "formula"
|
|
64
|
-
],
|
|
65
|
-
urgency: [
|
|
66
|
-
"instant", "fast", "quick", "now", "today", "2026", "deadline", "last chance", "step-by-step",
|
|
67
|
-
"in 5 minutes", "immediate", "urgent", "unlock", "rapid"
|
|
68
|
-
],
|
|
69
|
-
value: [
|
|
70
|
-
"free", "affordable", "save", "guaranteed", "easy", "bonus", "discount", "effortless",
|
|
71
|
-
"lifetime", "roi", "value", "exclusive deal", "profitable"
|
|
72
|
-
],
|
|
73
|
-
curiosity: [
|
|
74
|
-
"secret", "revealed", "insider", "hacks", "why", "how to", "untold", "surprising",
|
|
75
|
-
"shocking", "hidden", "must-know", "unbelievable", "myth"
|
|
76
|
-
],
|
|
77
|
-
sentimentPositive: [
|
|
78
|
-
"amazing", "brilliant", "powerful", "epic", "perfect", "stunning", "thriving", "seamless",
|
|
79
|
-
"exceptional", "masterpiece", "unbeatable"
|
|
80
|
-
],
|
|
81
|
-
sentimentNegative: [
|
|
82
|
-
"mistake", "warning", "fail", "avoid", "risk", "trap", "worst", "scam", "danger", "flaw"
|
|
83
|
-
]
|
|
84
|
-
},
|
|
85
|
-
es: {
|
|
86
|
-
authority: [
|
|
87
|
-
"definitiva", "completa", "guía", "experto", "oficial", "certificado", "mejor", "mejores", "comparativa",
|
|
88
|
-
"profesional", "comprobado", "líder", "análisis"
|
|
89
|
-
],
|
|
90
|
-
urgency: [
|
|
91
|
-
"rápido", "ahora", "urgente", "hoy", "2026", "en 5 minutos", "inmediato", "última oportunidad", "fácil"
|
|
92
|
-
],
|
|
93
|
-
value: [
|
|
94
|
-
"gratis", "económico", "ahorra", "garantizado", "rentable", "oferta", "descuento", "exclusivo"
|
|
95
|
-
],
|
|
96
|
-
curiosity: [
|
|
97
|
-
"secreto", "revelado", "trucos", "por qué", "cómo", "impactante", "increíble", "descubre", "método"
|
|
98
|
-
],
|
|
99
|
-
sentimentPositive: [
|
|
100
|
-
"excelente", "increíble", "perfecto", "potente", "maravilloso", "éxito", "fantástico"
|
|
101
|
-
],
|
|
102
|
-
sentimentNegative: [
|
|
103
|
-
"error", "peligro", "evita", "peor", "estafa", "trampa", "fracaso", "riesgo"
|
|
104
|
-
]
|
|
105
|
-
},
|
|
106
|
-
de: {
|
|
107
|
-
authority: [
|
|
108
|
-
"ultimativ", "vollständig", "leitfaden", "experte", "offiziell", "zertifiziert", "beste", "bester", "vergleich", "profi"
|
|
109
|
-
],
|
|
110
|
-
urgency: [
|
|
111
|
-
"schnell", "jetzt", "sofort", "2026", "in 5 minuten", "heute", "dringend", "einfach"
|
|
112
|
-
],
|
|
113
|
-
value: [
|
|
114
|
-
"kostenlos", "gratis", "sparen", "garantiert", "rabatt", "günstig", "angebot", "profitabel"
|
|
115
|
-
],
|
|
116
|
-
curiosity: [
|
|
117
|
-
"geheimnis", "enthüllt", "tricks", "tipps", "warum", "wie man", "unglaublich", "entdecken"
|
|
118
|
-
],
|
|
119
|
-
sentimentPositive: [
|
|
120
|
-
"hervorragend", "perfekt", "genial", "erfolg", "fantastisch", "wunderbar", "stark"
|
|
121
|
-
],
|
|
122
|
-
sentimentNegative: [
|
|
123
|
-
"fehler", "gefahr", "vermeiden", "schlimmste", "betrug", "risiko", "warnung"
|
|
124
|
-
]
|
|
125
|
-
},
|
|
126
|
-
it: {
|
|
127
|
-
authority: [
|
|
128
|
-
"guida", "completa", "esperto", "ufficiale", "certificato", "migliore", "migliori", "confronto", "definitiva", "professionale"
|
|
129
|
-
],
|
|
130
|
-
urgency: [
|
|
131
|
-
"veloce", "ora", "subito", "immediato", "2026", "in 5 minuti", "oggi", "urgente"
|
|
132
|
-
],
|
|
133
|
-
value: [
|
|
134
|
-
"gratis", "gratuito", "risparmia", "garantito", "sconto", "facile", "offerta", "conveniente"
|
|
135
|
-
],
|
|
136
|
-
curiosity: [
|
|
137
|
-
"segreto", "svelato", "trucchi", "perché", "come fare", "incredibile", "scopri", "metodo"
|
|
138
|
-
],
|
|
139
|
-
sentimentPositive: [
|
|
140
|
-
"perfetto", "incredibile", "fantastico", "successo", "eccezionale", "meraviglioso"
|
|
141
|
-
],
|
|
142
|
-
sentimentNegative: [
|
|
143
|
-
"errore", "pericolo", "evitare", "peggiore", "truffa", "rischio", "trappola"
|
|
144
|
-
]
|
|
145
|
-
},
|
|
146
|
-
nl: {
|
|
147
|
-
authority: ["ultiem", "gids", "expert", "officieel", "beste", "vergelijk", "compleet", "bewezen"],
|
|
148
|
-
urgency: ["snel", "nu", "direct", "vandaag", "2026", "meteen", "eenvoudig"],
|
|
149
|
-
value: ["gratis", "bespaar", "gegarandeerd", "korting", "goedkoop", "voordeel"],
|
|
150
|
-
curiosity: ["geheim", "onthuld", "tips", "waarom", "hoe", "ontdek"],
|
|
151
|
-
sentimentPositive: ["geweldig", "perfect", "uitstekend", "succes", "fantastisch"],
|
|
152
|
-
sentimentNegative: ["fout", "gevaar", "vermijd", "slechtste", "oplichting", "risico"]
|
|
153
|
-
},
|
|
154
|
-
pt: {
|
|
155
|
-
authority: ["guia", "completo", "definitivo", "especialista", "oficial", "melhor", "melhores", "comparativo"],
|
|
156
|
-
urgency: ["rápido", "agora", "imediato", "hoje", "2026", "em 5 minutos", "urgente"],
|
|
157
|
-
value: ["grátis", "gratuito", "economize", "garantido", "desconto", "fácil", "oferta"],
|
|
158
|
-
curiosity: ["segredo", "revelado", "dicas", "por que", "como fazer", "incrível", "descubra"],
|
|
159
|
-
sentimentPositive: ["perfeito", "incrível", "fantástico", "sucesso", "excelente"],
|
|
160
|
-
sentimentNegative: ["erro", "perigo", "evite", "pior", "golpe", "risco", "cilada"]
|
|
161
|
-
},
|
|
162
|
-
ar: {
|
|
163
|
-
authority: ["دليل", "شامل", "رسمي", "خبير", "أفضل", "مقارنة", "معتمد", "نهائي"],
|
|
164
|
-
urgency: ["سريع", "الآن", "فوري", "اليوم", "2026", "في دقائق", "عاجل"],
|
|
165
|
-
value: ["مجانا", "وفر", "مضمون", "خصم", "سهل", "عرض", "مميز"],
|
|
166
|
-
curiosity: ["سر", "كشف", "أسرار", "لماذا", "كيف", "مذهل", "اكتشف"],
|
|
167
|
-
sentimentPositive: ["رائع", "ممتاز", "ناجح", "مبهر", "مثالي"],
|
|
168
|
-
sentimentNegative: ["خطأ", "تحذير", "تجنب", "فخ", "احتيال", "خطر"]
|
|
169
|
-
},
|
|
170
|
-
ru: {
|
|
171
|
-
authority: ["руководство", "полный", "эксперт", "официальный", "лучший", "лучшие", "сравнение", "проверенный"],
|
|
172
|
-
urgency: ["быстро", "сейчас", "срочно", "сегодня", "2026", "за 5 минут", "немедленно"],
|
|
173
|
-
value: ["бесплатно", "скидка", "гарантия", "выгодно", "легко", "акция"],
|
|
174
|
-
curiosity: ["секрет", "раскрыт", "лайфхаки", "почему", "как", "невероятный", "узнайте"],
|
|
175
|
-
sentimentPositive: ["отлично", "идеально", "успех", "потрясающий", "великолепный"],
|
|
176
|
-
sentimentNegative: ["ошибка", "опасность", "избегать", "худший", "обман", "риск"]
|
|
177
|
-
},
|
|
178
|
-
zh: {
|
|
179
|
-
authority: ["终极", "完整", "指南", "专家", "官方", "最佳", "对比", "评测", "权威"],
|
|
180
|
-
urgency: ["快速", "立即", "今天", "2026", "5分钟", "马上", "紧急"],
|
|
181
|
-
value: ["免费", "省钱", "保证", "优惠", "简单", "折扣", "高性价比"],
|
|
182
|
-
curiosity: ["秘密", "揭秘", "技巧", "为什么", "如何", "必看", "探索"],
|
|
183
|
-
sentimentPositive: ["完美", "惊人", "卓越", "成功", "极佳", "出色"],
|
|
184
|
-
sentimentNegative: ["错误", "陷阱", "避免", "危险", "骗局", "风险"]
|
|
185
|
-
},
|
|
186
|
-
ja: {
|
|
187
|
-
authority: ["完全ガイド", "究極", "公式", "専門家", "おすすめ", "徹底比較", "決定版", "プロ"],
|
|
188
|
-
urgency: ["今すぐ", "速報", "最短", "2026年", "今日", "5分で", "簡単"],
|
|
189
|
-
value: ["無料", "お得", "割引", "保証", "コスパ", "特典", "プレゼント"],
|
|
190
|
-
curiosity: ["秘密", "裏ワザ", "暴露", "なぜ", "方法", "必見", "真実"],
|
|
191
|
-
sentimentPositive: ["最高", "完璧", "圧倒的", "成功", "絶賛", "素晴らしい"],
|
|
192
|
-
sentimentNegative: ["失敗", "注意", "危険", "避けるべき", "詐欺", "罠"]
|
|
193
|
-
},
|
|
194
|
-
hi: {
|
|
195
|
-
authority: ["गाइड", "सम्पूर्ण", "विशेषज्ञ", "आधिकारिक", "सर्वश्रेष्ठ", "तुलना", "सटीक"],
|
|
196
|
-
urgency: ["तुरंत", "अभी", "आज", "2026", "5 मिनट में", "जल्दी"],
|
|
197
|
-
value: ["मुफ्त", "बचत", "गारंटी", "छूट", "आसान", "ऑफर"],
|
|
198
|
-
curiosity: ["रहस्य", "खुलासा", "टिप्स", "क्यों", "कैसे", "अनोखा"],
|
|
199
|
-
sentimentPositive: ["उत्कृष्ट", "शानदार", "सफल", "अद्भुत", "बेहतरीन"],
|
|
200
|
-
sentimentNegative: ["गलती", "खतरा", "सावधान", "धोखा", "नुकसान"]
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
export class PowerWordsPsychologyEngine {
|
|
205
|
-
/**
|
|
206
|
-
* Register a custom language dictionary at runtime for infinite language extensibility
|
|
207
|
-
*/
|
|
208
|
-
static registerLanguageWords(langCode: string, words: Partial<CategoryWordBank>): void {
|
|
209
|
-
const code = langCode.toLowerCase().trim();
|
|
210
|
-
if (!MULTILINGUAL_POWER_WORDS[code]) {
|
|
211
|
-
MULTILINGUAL_POWER_WORDS[code] = {
|
|
212
|
-
authority: [],
|
|
213
|
-
urgency: [],
|
|
214
|
-
value: [],
|
|
215
|
-
curiosity: [],
|
|
216
|
-
sentimentPositive: [],
|
|
217
|
-
sentimentNegative: [],
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
if (words.authority) MULTILINGUAL_POWER_WORDS[code].authority.push(...words.authority);
|
|
221
|
-
if (words.urgency) MULTILINGUAL_POWER_WORDS[code].urgency.push(...words.urgency);
|
|
222
|
-
if (words.value) MULTILINGUAL_POWER_WORDS[code].value.push(...words.value);
|
|
223
|
-
if (words.curiosity) MULTILINGUAL_POWER_WORDS[code].curiosity.push(...words.curiosity);
|
|
224
|
-
if (words.sentimentPositive) MULTILINGUAL_POWER_WORDS[code].sentimentPositive.push(...words.sentimentPositive);
|
|
225
|
-
if (words.sentimentNegative) MULTILINGUAL_POWER_WORDS[code].sentimentNegative.push(...words.sentimentNegative);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Get all registered language codes
|
|
230
|
-
*/
|
|
231
|
-
static getSupportedLanguages(): string[] {
|
|
232
|
-
return Object.keys(MULTILINGUAL_POWER_WORDS);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Analyze title psychology and CTR potential in ANY language
|
|
237
|
-
*/
|
|
238
|
-
static analyzeTitle(title: string, lang: LanguageCode = "fr"): PsychologyAnalysisResult {
|
|
239
|
-
const raw = (title || "").toLowerCase().trim();
|
|
240
|
-
const langKey = lang.toLowerCase().trim();
|
|
241
|
-
|
|
242
|
-
// Multi-bank search: search primary language, plus universal English / French fallback
|
|
243
|
-
const primaryBank = MULTILINGUAL_POWER_WORDS[langKey] || MULTILINGUAL_POWER_WORDS["en"] || MULTILINGUAL_POWER_WORDS["fr"];
|
|
244
|
-
const allBanks = [primaryBank];
|
|
245
|
-
if (langKey !== "en" && MULTILINGUAL_POWER_WORDS["en"]) allBanks.push(MULTILINGUAL_POWER_WORDS["en"]);
|
|
246
|
-
|
|
247
|
-
const detectedWords: string[] = [];
|
|
248
|
-
let authorityMatch = false;
|
|
249
|
-
let urgencyMatch = false;
|
|
250
|
-
let valueMatch = false;
|
|
251
|
-
let curiosityMatch = false;
|
|
252
|
-
let positiveCount = 0;
|
|
253
|
-
let negativeCount = 0;
|
|
254
|
-
|
|
255
|
-
for (const bank of allBanks) {
|
|
256
|
-
for (const word of bank.authority) {
|
|
257
|
-
if (raw.includes(word.toLowerCase())) {
|
|
258
|
-
authorityMatch = true;
|
|
259
|
-
if (!detectedWords.includes(word)) detectedWords.push(word);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
for (const word of bank.urgency) {
|
|
263
|
-
if (raw.includes(word.toLowerCase())) {
|
|
264
|
-
urgencyMatch = true;
|
|
265
|
-
if (!detectedWords.includes(word)) detectedWords.push(word);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
for (const word of bank.value) {
|
|
269
|
-
if (raw.includes(word.toLowerCase())) {
|
|
270
|
-
valueMatch = true;
|
|
271
|
-
if (!detectedWords.includes(word)) detectedWords.push(word);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
for (const word of bank.curiosity) {
|
|
275
|
-
if (raw.includes(word.toLowerCase())) {
|
|
276
|
-
curiosityMatch = true;
|
|
277
|
-
if (!detectedWords.includes(word)) detectedWords.push(word);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
for (const word of bank.sentimentPositive) {
|
|
281
|
-
if (raw.includes(word.toLowerCase())) {
|
|
282
|
-
positiveCount++;
|
|
283
|
-
if (!detectedWords.includes(word)) detectedWords.push(word);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
for (const word of bank.sentimentNegative) {
|
|
287
|
-
if (raw.includes(word.toLowerCase())) {
|
|
288
|
-
negativeCount++;
|
|
289
|
-
if (!detectedWords.includes(word)) detectedWords.push(word);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Universal Language-Agnostic Triggers
|
|
295
|
-
const hasNumber = /\b\d+\b/.test(raw);
|
|
296
|
-
const hasYear = /\b(202[4-9]|203[0-9])\b/.test(raw);
|
|
297
|
-
const hasCurrencyOrDiscount = /[\$€£¥₹%]|د\.إ|﷼|\b(promo|soldes|discount|deal|off)\b/i.test(raw);
|
|
298
|
-
const hasBracketsOrParentheses = /[\[\]\(\)\{\}【】《》「」]/.test(raw);
|
|
299
|
-
|
|
300
|
-
// Score Calculation (0 - 100)
|
|
301
|
-
let score = 30; // base score
|
|
302
|
-
if (detectedWords.length >= 1) score += 20;
|
|
303
|
-
if (detectedWords.length >= 2) score += 10;
|
|
304
|
-
if (detectedWords.length >= 3) score += 10;
|
|
305
|
-
if (hasNumber) score += 15;
|
|
306
|
-
if (hasYear) score += 10;
|
|
307
|
-
if (hasCurrencyOrDiscount) score += 5;
|
|
308
|
-
if (hasBracketsOrParentheses) score += 5;
|
|
309
|
-
if (score > 100) score = 100;
|
|
310
|
-
|
|
311
|
-
let sentiment: "positif" | "negatif" | "neutre" = "neutre";
|
|
312
|
-
if (positiveCount > negativeCount) sentiment = "positif";
|
|
313
|
-
else if (negativeCount > positiveCount) sentiment = "negatif";
|
|
314
|
-
|
|
315
|
-
const recommendations: string[] = [];
|
|
316
|
-
if (detectedWords.length === 0) {
|
|
317
|
-
recommendations.push("Ajoutez au moins 1 mot d'impact émotionnel ou d'autorité (ex: 'Guide', 'Ultime', 'Meilleur').");
|
|
318
|
-
}
|
|
319
|
-
if (!hasNumber) {
|
|
320
|
-
recommendations.push("Intégrez un chiffre (ex: 'Les 7 Meilleurs...') pour augmenter le taux de clic de +36%.");
|
|
321
|
-
}
|
|
322
|
-
if (!hasYear) {
|
|
323
|
-
recommendations.push("Indiquez l'année courante (2026) pour signaler la fraîcheur du contenu aux internautes.");
|
|
324
|
-
}
|
|
325
|
-
if (!hasBracketsOrParentheses) {
|
|
326
|
-
recommendations.push("Utilisez des crochets ou parenthèses [Guide 2026] pour structurer visuellement l'extrait.");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Suggested high CTR titles
|
|
330
|
-
const cleanSubject = title.replace(/\(.*?\)|\[.*?\]/g, "").trim();
|
|
331
|
-
const suggestedTitles = [
|
|
332
|
-
`Les 7 Meilleurs ${cleanSubject} [Guide Ultime 2026]`,
|
|
333
|
-
`Comment Maîtriser ${cleanSubject} en 5 Étapes (Méthode 2026)`,
|
|
334
|
-
`${cleanSubject} : Avis, Comparatif et Pièges à Éviter (2026)`
|
|
335
|
-
];
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
score,
|
|
339
|
-
powerWordsDetected: detectedWords,
|
|
340
|
-
sentiment,
|
|
341
|
-
hasNumber,
|
|
342
|
-
hasYear,
|
|
343
|
-
hasCuriosityTrigger: curiosityMatch,
|
|
344
|
-
hasUrgencyTrigger: urgencyMatch,
|
|
345
|
-
hasAuthorityTrigger: authorityMatch,
|
|
346
|
-
hasCurrencyOrDiscount,
|
|
347
|
-
hasBracketsOrParentheses,
|
|
348
|
-
recommendations,
|
|
349
|
-
suggestedTitles
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Automatically enriches a programmatic title in ANY language with optimal psychology triggers
|
|
355
|
-
*/
|
|
356
|
-
static enrichProgrammaticTitle(baseTitle: string, lang: LanguageCode = "fr"): string {
|
|
357
|
-
const analysis = this.analyzeTitle(baseTitle, lang);
|
|
358
|
-
if (analysis.score >= 80) return baseTitle;
|
|
359
|
-
|
|
360
|
-
let enriched = baseTitle.trim();
|
|
361
|
-
if (!analysis.hasNumber && !enriched.startsWith("Top ") && !enriched.startsWith("Les ")) {
|
|
362
|
-
enriched = `Les Meilleurs ${enriched}`;
|
|
363
|
-
}
|
|
364
|
-
if (!analysis.hasYear) {
|
|
365
|
-
enriched = `${enriched} (Guide 2026)`;
|
|
366
|
-
}
|
|
367
|
-
return enriched;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🗺️ Universal Public Routes & Navigation Manifest Engine
|
|
3
|
-
*
|
|
4
|
-
* Allows developers and LLM agents to connect their real public pages (10-50 seed pages)
|
|
5
|
-
* from ANY framework (Next.js, TanStack Router, Astro, Nuxt, WordPress, Laravel, Remix).
|
|
6
|
-
*
|
|
7
|
-
* Instead of scanning millions of generated URLs, it inspects ONLY the root product pages,
|
|
8
|
-
* navigation menus, or route trees to build the knowledge base for all programmatic pages.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export interface PublicRouteItem {
|
|
12
|
-
path: string;
|
|
13
|
-
title: string;
|
|
14
|
-
description?: string;
|
|
15
|
-
category?: string;
|
|
16
|
-
paragraphs?: string[];
|
|
17
|
-
keyFeatures?: string[];
|
|
18
|
-
faqs?: { question: string; answer: string }[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface FrameworkManifestConfig {
|
|
22
|
-
framework?: "nextjs" | "tanstack" | "astro" | "wordpress" | "nuxt" | "custom";
|
|
23
|
-
routes?: PublicRouteItem[];
|
|
24
|
-
navMenuItems?: Array<{ label: string; url: string; category?: string }>;
|
|
25
|
-
contentCollections?: Array<{ slug: string; title: string; content: string }>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class PublicRoutesManifestEngine {
|
|
29
|
-
private static registeredRoutes: Map<string, PublicRouteItem> = new Map();
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 1. Register Public Routes & Navigation Menu (Universal Entrypoint)
|
|
33
|
-
*/
|
|
34
|
-
static registerManifest(config: FrameworkManifestConfig): number {
|
|
35
|
-
let count = 0;
|
|
36
|
-
|
|
37
|
-
// A. Explicit Route Items
|
|
38
|
-
if (config.routes && Array.isArray(config.routes)) {
|
|
39
|
-
for (const r of config.routes) {
|
|
40
|
-
this.registeredRoutes.set(r.path, r);
|
|
41
|
-
count++;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// B. From Navigation Menu Items (WordPress Menus, Navbar JSON, etc.)
|
|
46
|
-
if (config.navMenuItems && Array.isArray(config.navMenuItems)) {
|
|
47
|
-
for (const nav of config.navMenuItems) {
|
|
48
|
-
if (!this.registeredRoutes.has(nav.url)) {
|
|
49
|
-
this.registeredRoutes.set(nav.url, {
|
|
50
|
-
path: nav.url,
|
|
51
|
-
title: nav.label,
|
|
52
|
-
category: nav.category || "Main Navigation",
|
|
53
|
-
description: `Page officielle ${nav.label} disponible sur le site.`,
|
|
54
|
-
paragraphs: [`Découvrez la solution ${nav.label} pour optimiser vos processus et vos résultats.`],
|
|
55
|
-
keyFeatures: [`Accès direct à ${nav.label}`, "Intégration transparente"],
|
|
56
|
-
});
|
|
57
|
-
count++;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// C. From Markdown / MDX Content Collections (Astro, Next.js Contentlayer)
|
|
63
|
-
if (config.contentCollections && Array.isArray(config.contentCollections)) {
|
|
64
|
-
for (const doc of config.contentCollections) {
|
|
65
|
-
const paragraphs = doc.content
|
|
66
|
-
.split("\n\n")
|
|
67
|
-
.map((p) => p.replace(/[#*`]/g, "").trim())
|
|
68
|
-
.filter((p) => p.length > 30)
|
|
69
|
-
.slice(0, 15);
|
|
70
|
-
|
|
71
|
-
this.registeredRoutes.set(doc.slug, {
|
|
72
|
-
path: doc.slug.startsWith("/") ? doc.slug : `/${doc.slug}`,
|
|
73
|
-
title: doc.title,
|
|
74
|
-
category: "Documentation & Features",
|
|
75
|
-
paragraphs,
|
|
76
|
-
description: paragraphs[0] || doc.title,
|
|
77
|
-
});
|
|
78
|
-
count++;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return count;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* 2. Auto-Adapter for WordPress Menu & Pages
|
|
87
|
-
* In WordPress: pass result of wp_get_nav_menu_items() or get_pages()
|
|
88
|
-
*/
|
|
89
|
-
static registerWordPressMenu(menuItems: Array<{ title: string; url: string }>): number {
|
|
90
|
-
return this.registerManifest({
|
|
91
|
-
framework: "wordpress",
|
|
92
|
-
navMenuItems: menuItems.map((m) => ({ label: m.title, url: m.url })),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 3. Get all discovered seed public pages
|
|
98
|
-
*/
|
|
99
|
-
static getSeedRoutes(): PublicRouteItem[] {
|
|
100
|
-
return Array.from(this.registeredRoutes.values());
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 4. Retrieve single route details
|
|
105
|
-
*/
|
|
106
|
-
static getRoute(pathOrSlug: string): PublicRouteItem | undefined {
|
|
107
|
-
return this.registeredRoutes.get(pathOrSlug);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🧠 LynxRAG Hybrid Knowledge Engine (Enterprise pgvector & Local In-Memory Semantic Index)
|
|
3
|
-
*
|
|
4
|
-
* Provides unified RAG (Retrieval-Augmented Generation) capabilities:
|
|
5
|
-
* 1. Ingests Company PDFs, Pitch Decks, Product Catalogs, and Brand Kits
|
|
6
|
-
* 2. Chunks & stores semantic embeddings (pgvector compatible / in-memory cosine fallback)
|
|
7
|
-
* 3. Enriches AI Copilot Audit Reports with genuine company facts & certifications
|
|
8
|
-
* 4. Enriches Programmatic Pages with authentic proprietary statistics & guarantees
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export interface KnowledgeChunk {
|
|
12
|
-
id: string;
|
|
13
|
-
documentName: string;
|
|
14
|
-
category: "pricing" | "technical" | "case_study" | "certification" | "general";
|
|
15
|
-
content: string;
|
|
16
|
-
embedding?: number[];
|
|
17
|
-
keywords: string[];
|
|
18
|
-
tokenCount: number;
|
|
19
|
-
createdAt: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface RagRetrievalResult {
|
|
23
|
-
chunk: KnowledgeChunk;
|
|
24
|
-
similarityScore: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class LynxRagKnowledgeEngine {
|
|
28
|
-
private static chunkStore: Map<string, KnowledgeChunk[]> = new Map(); // Keyed by siteId / domain
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 1. Ingest Raw Document Text (from PDF, Word doc, or manual upload)
|
|
32
|
-
*/
|
|
33
|
-
static ingestDocument(
|
|
34
|
-
siteId: string,
|
|
35
|
-
documentName: string,
|
|
36
|
-
rawText: string,
|
|
37
|
-
category: KnowledgeChunk["category"] = "general"
|
|
38
|
-
): KnowledgeChunk[] {
|
|
39
|
-
const cleanText = rawText.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
40
|
-
const rawParagraphs = cleanText.split("\n\n").filter((p) => p.trim().length > 30);
|
|
41
|
-
|
|
42
|
-
const siteChunks = this.chunkStore.get(siteId) || [];
|
|
43
|
-
const newChunks: KnowledgeChunk[] = [];
|
|
44
|
-
|
|
45
|
-
rawParagraphs.forEach((para, index) => {
|
|
46
|
-
const words = para.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, "").split(/\s+/).filter(Boolean);
|
|
47
|
-
const uniqueKeywords = Array.from(new Set(words)).slice(0, 15);
|
|
48
|
-
|
|
49
|
-
const chunk: KnowledgeChunk = {
|
|
50
|
-
id: `chk_${siteId}_${Date.now()}_${index}`,
|
|
51
|
-
documentName,
|
|
52
|
-
category,
|
|
53
|
-
content: para.trim(),
|
|
54
|
-
keywords: uniqueKeywords,
|
|
55
|
-
tokenCount: Math.round(para.length / 4),
|
|
56
|
-
createdAt: new Date().toISOString(),
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
siteChunks.push(chunk);
|
|
60
|
-
newChunks.push(chunk);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
this.chunkStore.set(siteId, siteChunks);
|
|
64
|
-
return newChunks;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 2. Semantic Search & Top-K Chunks Retrieval (Fast In-Memory Cosine / Keyword Scoring)
|
|
69
|
-
*/
|
|
70
|
-
static retrieveRelevantContext(
|
|
71
|
-
siteId: string,
|
|
72
|
-
query: string,
|
|
73
|
-
topK: number = 3
|
|
74
|
-
): RagRetrievalResult[] {
|
|
75
|
-
const chunks = this.chunkStore.get(siteId) || [];
|
|
76
|
-
if (chunks.length === 0) return [];
|
|
77
|
-
|
|
78
|
-
const queryTerms = query.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, "").split(/\s+/).filter(Boolean);
|
|
79
|
-
|
|
80
|
-
const scored = chunks.map((chunk) => {
|
|
81
|
-
let score = 0;
|
|
82
|
-
const lowerContent = chunk.content.toLowerCase();
|
|
83
|
-
|
|
84
|
-
for (const term of queryTerms) {
|
|
85
|
-
if (lowerContent.includes(term)) {
|
|
86
|
-
score += 2.0;
|
|
87
|
-
}
|
|
88
|
-
if (chunk.keywords.includes(term)) {
|
|
89
|
-
score += 1.5;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Bonus for rich numerical / factual data
|
|
94
|
-
if (/\d+[\s%€$]/.test(chunk.content)) {
|
|
95
|
-
score += 1.0;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
chunk,
|
|
100
|
-
similarityScore: score / (queryTerms.length || 1),
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return scored
|
|
105
|
-
.filter((s) => s.similarityScore > 0.3)
|
|
106
|
-
.sort((a, b) => b.similarityScore - a.similarityScore)
|
|
107
|
-
.slice(0, topK);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 3. AI Audit Report Enhancer (Injects Company Facts into AI Reports)
|
|
112
|
-
*/
|
|
113
|
-
static enrichAiReportPrompt(
|
|
114
|
-
siteId: string,
|
|
115
|
-
reportTopic: string,
|
|
116
|
-
baseAuditMetrics: Record<string, unknown>
|
|
117
|
-
): { enrichedSystemPrompt: string; retrievedFacts: string[] } {
|
|
118
|
-
const relevantChunks = this.retrieveRelevantContext(siteId, reportTopic, 4);
|
|
119
|
-
const retrievedFacts = relevantChunks.map((r) => r.chunk.content);
|
|
120
|
-
|
|
121
|
-
const factsFormatted = retrievedFacts.length > 0
|
|
122
|
-
? `\n\n### 🏢 DONNÉES ET FAITS PROPRIÉTAIRES DE L'ENTREPRISE (RAG):\n${retrievedFacts.map((f, i) => `${i + 1}. ${f}`).join("\n")}`
|
|
123
|
-
: "";
|
|
124
|
-
|
|
125
|
-
const enrichedSystemPrompt = `
|
|
126
|
-
Vous êtes l'Auditeur SEO & IA Copilot de LynxSEO Studio.
|
|
127
|
-
Vous analysez les métriques techniques suivantes : ${JSON.stringify(baseAuditMetrics)}.${factsFormatted}
|
|
128
|
-
|
|
129
|
-
Directives d'analyse :
|
|
130
|
-
- Personnalisez vos recommandations en tenant compte des faits propriétaires et certifications réelles de l'entreprise.
|
|
131
|
-
- Ne proposez que des plans d'action immédiatement applicables et mesurables.
|
|
132
|
-
`.trim();
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
enrichedSystemPrompt,
|
|
136
|
-
retrievedFacts,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 4. Programmatic Page Generator Enhancer (Injects Company Facts into Programmatic Pages)
|
|
142
|
-
*/
|
|
143
|
-
static getFactsForPageGeneration(
|
|
144
|
-
siteId: string,
|
|
145
|
-
service: string,
|
|
146
|
-
location: string
|
|
147
|
-
): string[] {
|
|
148
|
-
const query = `${service} ${location} tarifs garantie certification delai`;
|
|
149
|
-
const results = this.retrieveRelevantContext(siteId, query, 3);
|
|
150
|
-
return results.map((r) => r.chunk.content);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 5. Clear or Get All Ingested Chunks for Site
|
|
155
|
-
*/
|
|
156
|
-
static getSiteChunks(siteId: string): KnowledgeChunk[] {
|
|
157
|
-
return this.chunkStore.get(siteId) || [];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
static clearSiteChunks(siteId: string): void {
|
|
161
|
-
this.chunkStore.delete(siteId);
|
|
162
|
-
}
|
|
163
|
-
}
|