73 lines
3.8 KiB
JavaScript
73 lines
3.8 KiB
JavaScript
'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 };
|