'use strict'; // 三级升级策略(旧 bridge escalation/manager 的 LibreDesk 版): // Level 0 正常;Level 1 追加“是否转人工”;Level 2 私聊备注 + 分配人工。 function createEscalation({ client, handoffUserId = 1 }) { function shouldEscalate(state, intent, kbConfidence, consecutiveFails, kbHitCount = 0) { const processed = Number(state && state.messagesProcessed) || 0; if (consecutiveFails >= 3) return { level: 2, reason: 'consecutive_fails' }; if (processed > 8 && !(state && state.opportunityId)) return { level: 2, reason: 'long_no_lead' }; if (intent === 'policy_faq' && kbConfidence === 'low' && kbHitCount === 0) return { level: 1, reason: 'low_kb_confidence' }; if (kbConfidence === 'none' && !['product_search', 'sales_lead', 'order_tracking', 'inventory_check'].includes(intent)) return { level: 1, reason: 'no_kb_match' }; if (processed > 5 && consecutiveFails >= 2) return { level: 1, reason: 'repeated_low_confidence' }; return { level: 0, reason: null }; } function appendEscalationOption(reply, query) { const text = String(reply || '').replace(/\s+$/, ''); const hints = { zh: '您想转人工客服吗?', en: 'Would you like to speak to a human agent?', de: 'Möchten Sie mit einem Mitarbeiter sprechen?', fr: 'Souhaitez-vous parler à un conseiller ?', it: 'Vuole parlare con un operatore?', nl: 'Wilt u met een medewerker spreken?', es: '¿Desea hablar con un agente humano?', tr: 'Bir insan temsilcisiyle görüşmek ister misiniz?', }; // 只输出一种语言:按用户输入判断语言,识别不到时用英文;不再拼英文兜底。 let lang = null; if (/[\u4e00-\u9fff]/.test(query)) lang = 'zh'; else if (/[ğışç]/i.test(query) || /\b(merhaba|istiyorum|konuşmak|temsilci|fiyat|ürün|sipariş)\b/i.test(query)) lang = 'tr'; else if (/[äöüß]/i.test(query)) lang = 'de'; else if (/[ñ¿¡]/i.test(query) || /\b(hola|quiero|hablar|agente|puedo|gracias|precio|pedido)\b/i.test(query)) lang = 'es'; else if (/\b(vorrei|parlare|operatore|desidera|posso|prezzo|ordine)\b/i.test(query)) lang = 'it'; else if (/[éèêëàâùûîïôç]/i.test(query) || /\b(bonjour|merci|conseiller|commande|livraison|prix|voudrais)\b/i.test(query)) lang = 'fr'; else if (/\b(hoe|voor|wilt|bent|geen|verzending|levering|bestelling)\b/i.test(query)) lang = 'nl'; const option = `\n\n${hints[lang] || hints.en}`; return text + option; } async function escalateToHuman({ convUuid, trigger, history }) { const lines = [`Escalation trigger: ${trigger || 'unknown'}`]; if (history && history.length) { lines.push('Recent context:'); for (const m of history.slice(-4)) { const role = m.role === 'user' ? 'Customer' : 'AI'; lines.push(` [${role}] ${String(m.content || '').slice(0, 150)}`); } } try { await client.sendPrivateNote(convUuid, lines.join('\n')); await client.assignUser(convUuid, handoffUserId); return true; } catch (err) { console.error(JSON.stringify({ ts: new Date().toISOString(), warn: `escalate failed: ${err.message}` })); return false; } } function applyEmotionStrategy(emotion) { switch (emotion) { case 'angry': return { skipAI: false, escalate: true, maxToolTurns: 2, tone: 'empathetic' }; case 'frustrated': return { skipAI: false, escalate: false, maxToolTurns: 3, tone: 'patient' }; case 'urgent': return { skipAI: false, escalate: false, maxToolTurns: 3, tone: 'direct' }; default: return { skipAI: false, escalate: false, maxToolTurns: 5, tone: 'normal' }; } } return { shouldEscalate, appendEscalationOption, escalateToHuman, applyEmotionStrategy }; } module.exports = { createEscalation };