'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 };