Files
crm-ai/bridge/src/sitemap.js
T
2026-10-23 10:35:28 +08:00

56 lines
2.0 KiB
JavaScript

'use strict';
const { Pool } = require('pg');
const STOPWORDS = new Set(['the', 'a', 'an', 'and', 'or', 'for', 'to', 'of', 'in', 'on', 'with', 'do', 'does', 'is', 'are', 'how', 'what', 'can', 'you', 'my', 'your', 'i', 'we', 'it', 'please', 'show', 'me']);
// 全量商品页站点地图(bridge_site_map_urls):服务端匹配,不进 LLM/向量库。
function createSiteMap(cfg) {
if (!cfg.pgUrl) return { enabled: false, search: async () => [] };
const pool = new Pool({ connectionString: cfg.pgUrl, max: 2, connectionTimeoutMillis: 5000 });
function tokensFor(query) {
return String(query || '')
.toLowerCase()
.replace(/[^\p{L}\p{N}\s-]+/gu, ' ')
.split(/\s+/)
.filter((w) => w && w.length > 1 && !STOPWORDS.has(w))
.slice(0, 8);
}
function nameFromUrl(url) {
const seg = String(url || '').split('/').filter(Boolean).pop() || '';
return seg
.replace(/\.html$/, '')
.replace(/-?\d+$/, '')
.split('-')
.filter((w) => w && w.length > 1)
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
}
async function search(query, site = 'www', limit = 3) {
const tokens = tokensFor(query);
if (tokens.length < 2) return [];
try {
const patterns = tokens.map((t) => `%${t}%`);
const r = await pool.query(
`SELECT path, url, is_product, level, slug_tokens
FROM bridge_site_map_urls
WHERE site = $1 AND slug_tokens ILIKE ALL($2::text[])
ORDER BY is_product DESC, char_length(slug_tokens) ASC, level ASC
LIMIT $3`,
[site, patterns, Math.min(Number(limit) || 3, 5)]
);
return r.rows.map((row) => ({ name: nameFromUrl(row.url), url: row.url, is_product: row.is_product, path: row.path }));
} catch (err) {
console.error(JSON.stringify({ ts: new Date().toISOString(), warn: `sitemap search failed: ${err.message}` }));
return [];
}
}
return { enabled: true, search, pool };
}
module.exports = { createSiteMap };