'use strict'; // 购买意向识别 + post-LLM 追问兜底(不依赖 LLM 自觉)。 const PRODUCT_RE = /(戒指|项链|耳环|耳钉|手链|脚链|珠宝|饰品|发饰|包|服装|连衣裙|ring|necklace|earring|bracelet|anklet|jewelry|jewellery|accessor|hair ?clip|bag|dress|clothing|pendant|charm)/i; const QUANTITY_RE = /(\d+\s*(pcs?|pieces?|units?|sets?|pairs?|kilos?|kg|thousand|k\b|件|条|只|个|套|对)|moq|bulk|wholesale|批发|采购)/i; const ASK = { zh: '\n\n顺便问一下,方便告诉我贵公司名称和联系邮箱吗?这样我们好为您建立批发账户并跟进需求。', fr: '\n\nAu fait, pourriez-vous me donner le nom de votre société et un email de contact ? Cela nous permettra de créer votre compte grossiste et de suivre votre demande.', de: '\n\nDürfte ich noch den Namen Ihrer Firma und eine Kontakt-E-Mail haben? So können wir Ihr Großhandelskonto anlegen und Ihre Anfrage verfolgen.', it: '\n\nA proposito, potrebbe darmi il nome della sua azienda e una email di contatto? Così possiamo creare il suo account all\'ingrosso e seguire la richiesta.', es: '\n\nPor cierto, ¿podría darme el nombre de su empresa y un email de contacto? Así podemos crear su cuenta mayorista y dar seguimiento.', nl: '\n\nTrouwens, mag ik de naam van uw bedrijf en een contact-e-mail hebben? Zo kunnen we uw groothandelsaccount aanmaken en uw aanvraag opvolgen.', en: '\n\nBy the way, could you share your company name and a contact email? That way we can set up your wholesale account and follow up on your request.', }; // 公司/联系人信号:客户主动报公司名(含法律后缀)、邮箱、电话、联系人等。 // 命中它但数量单位不规整(如 "500 stainless steel rings")时,也要触发 lead 抽取兜底, // 否则 LLM 漏调 collect_info 时槽位会为空,商机就建不出来。 const LEAD_SIGNAL_RE = /(@|\+?\d[\d\s().-]{6,}\d|gmbh|b\.?\s?v\.?\b|ltd\b|llc\b|inc\b|sarl|srl\b|limited\b|company|corporation|we are|we're|our company|contact\s*(?:name|person)?\s*[::]|\u516c\u53f8|\u8054\u7cfb\u4eba|\u91c7\u8d2d|wholesale|bulk)/i; function hasBuyingIntent(text) { const value = String(text || ''); return PRODUCT_RE.test(value) && QUANTITY_RE.test(value); } function looksLikeLead(text) { const value = String(text || ''); return PRODUCT_RE.test(value) && LEAD_SIGNAL_RE.test(value); } function missingLeadContacts(slots = {}, knownEmail = '') { const missing = []; if (!slots.company) missing.push('company'); if (!slots.email && !slots.phone && !slots.contactName && !knownEmail) missing.push('contact'); return missing; } function detectLanguage(text) { const q = String(text || ''); if (/[\u3400-\u9fff]/.test(q)) return 'zh'; if (/\b(und|nicht|für|mit|bitte|haben|preis|menge|lieferung)\b/i.test(q)) return 'de'; if (/\b(et|pour|avec|bonjour|prix|quantité|livraison|merci)\b/i.test(q)) return 'fr'; if (/\b(e|per|con|prezzo|quantità|spedizione|grazie)\b/i.test(q)) return 'it'; if (/\b(y|para|con|precio|cantidad|envío|gracias)\b/i.test(q)) return 'es'; if (/\b(en|voor|met|prijs|aantal|levering|bedankt)\b/i.test(q)) return 'nl'; return 'en'; } function buildLeadFollowup(lang) { return ASK[lang] || ASK.en; } function alreadyAsksForLead(reply) { return /(company|société|entreprise|firma|unternehmen|azienda|empresa|bedrijf|email|e-mail|mail|kontakt|contact|公司|邮箱|邮件)/i.test(String(reply || '')); } function appendLeadFollowup(reply, lang) { const text = String(reply || '').replace(/\s+$/, ''); if (!text || alreadyAsksForLead(text)) return text; return text + buildLeadFollowup(lang); } module.exports = { hasBuyingIntent, looksLikeLead, LEAD_SIGNAL_RE, missingLeadContacts, detectLanguage, buildLeadFollowup, alreadyAsksForLead, appendLeadFollowup };