package com.fs.company.service.workflow.execution; import com.alibaba.fastjson.JSONObject; import com.fs.company.domain.CompanyWorkflowLobsterNode; import com.fs.company.service.workflow.VariableSubstitutionEngine; import com.fs.company.service.workflow.config.LobsterBrandContextHelper; import com.fs.company.service.workflow.config.LobsterIndustryCatalog; import com.fs.company.service.workflow.contact.ContactInfo; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Pattern; /** * 消息模板渲染:品牌占位 {advisorName} + 默认 ${customerName},过滤内部 instruction 文案。 */ public final class LobsterMessageTemplateRenderer { private static final Pattern INTERNAL_INSTRUCTION_PREFIX = Pattern.compile( "^(Evaluate customer intent|Reply professionally|Collect key|Create follow-up|" + "Send personalized care|Politely hand off)", Pattern.CASE_INSENSITIVE); private LobsterMessageTemplateRenderer() { } /** * 是否配置了可用的开场话术(非 system/instruction 占位)。 */ public static boolean hasConfiguredOpeningScript(CompanyWorkflowLobsterNode node, Map variables) { return !extractConfiguredOpeningScript(node, variables).isEmpty(); } /** * 提取开场话术优先级:流程变量 > greetingConfig > messageTemplate。 */ public static String extractConfiguredOpeningScript(CompanyWorkflowLobsterNode node, Map variables) { if (variables != null) { for (String key : new String[]{"greetingMessage", "welcomeMessage", "openingMessage", "bindingGreeting"}) { Object val = variables.get(key); if (val != null && !val.toString().isBlank() && !isInternalInstructionText(val.toString())) { return val.toString().trim(); } } } if (node == null) { return ""; } String fromGreeting = extractGreetingConfigMessage(node.getGreetingConfig()); if (!fromGreeting.isEmpty() && !isInternalInstructionText(fromGreeting)) { return fromGreeting; } String template = node.getMessageTemplate(); if (template != null && !template.isBlank() && !isInternalInstructionText(template.trim())) { return template.trim(); } return ""; } public static String extractGreetingConfigMessage(String greetingConfig) { if (greetingConfig == null || greetingConfig.isBlank()) { return ""; } String trimmed = greetingConfig.trim(); if (trimmed.startsWith("{")) { try { JSONObject obj = JSONObject.parseObject(trimmed); if (obj.containsKey("message")) { String msg = obj.getString("message"); return msg != null ? msg.trim() : ""; } if (obj.containsKey("text")) { String text = obj.getString("text"); return text != null ? text.trim() : ""; } } catch (Exception ignored) { } return ""; } return trimmed; } /** instruction / 内部占位话术识别(含英文 Evaluate... 及默认「请回复客户」等)。 */ public static boolean isInternalInstructionText(String text) { if (text == null || text.isBlank()) { return true; } String t = text.trim(); if ("请根据上下文回复客户,保持自然亲切的语气。".equals(t) || "请回复客户。".equals(t)) { return true; } return INTERNAL_INSTRUCTION_PREFIX.matcher(t).find(); } public static Map enrichConversationVariables(Map variables, ContactInfo contactInfo) { Map out = variables != null ? new LinkedHashMap<>(variables) : new LinkedHashMap<>(); String customerName = resolveCustomerName(out, contactInfo); out.putIfAbsent("customerName", customerName); out.putIfAbsent("customer_name", customerName); Map brands = brandMapFromVariables(out); out.putIfAbsent("companyShortName", brands.get("companyShortName")); out.putIfAbsent("advisorName", brands.get("advisorName")); out.putIfAbsent("mainProduct", brands.get("mainProduct")); out.putIfAbsent("company_short_name", brands.get("companyShortName")); out.putIfAbsent("advisor_name", brands.get("advisorName")); out.putIfAbsent("main_product", brands.get("mainProduct")); return out; } public static String render(String template, Map variables, VariableSubstitutionEngine varEngine) { if (template == null || template.isEmpty()) { return ""; } Map brands = brandMapFromVariables(variables); String withBrand = LobsterIndustryCatalog.applyBrandVars(template, brands); if (varEngine == null) { return stripUnresolvedCustomerName(withBrand); } String substituted = withBrand.contains("${") || withBrand.contains("{{") ? varEngine.substitute(withBrand, variables) : withBrand; return stripUnresolvedCustomerName(substituted); } public static boolean hasCustomerSpoken(Map variables) { if (variables == null) { return false; } Object inbound = variables.get("_customerInboundCount"); if (inbound instanceof Number && ((Number) inbound).intValue() > 0) { return true; } Object replyCount = variables.get("replyCount"); if (replyCount instanceof Number && ((Number) replyCount).intValue() > 0) { return true; } Object lastReply = variables.get("lastReply"); return lastReply != null && !lastReply.toString().isBlank(); } public static void markCustomerInbound(Map variables) { if (variables == null) { return; } int count = 0; Object raw = variables.get("_customerInboundCount"); if (raw instanceof Number) { count = ((Number) raw).intValue(); } variables.put("_customerInboundCount", count + 1); } private static String resolveCustomerName(Map variables, ContactInfo contactInfo) { if (variables != null) { for (String key : new String[]{"customerName", "customer_name", "contactName", "nickname"}) { Object val = variables.get(key); if (val != null && !val.toString().isBlank() && !val.toString().contains("${")) { return val.toString().trim(); } } } if (contactInfo != null && contactInfo.getName() != null && !contactInfo.getName().isBlank()) { return contactInfo.getName().trim(); } return "您"; } public static Map brandMapFromVariables(Map variables) { Map brands = LobsterBrandContextHelper.resolve(null, null, null, null, null); if (variables == null) { return brands; } copyBrandVar(variables, brands, "companyShortName", "company_short_name"); copyBrandVar(variables, brands, "advisorName", "advisor_name"); copyBrandVar(variables, brands, "mainProduct", "main_product"); return brands; } private static void copyBrandVar(Map variables, Map brands, String camelKey, String snakeKey) { Object camel = variables.get(camelKey); if (camel != null && !camel.toString().isBlank()) { brands.put(camelKey, camel.toString().trim()); return; } Object snake = variables.get(snakeKey); if (snake != null && !snake.toString().isBlank()) { brands.put(camelKey, snake.toString().trim()); } } private static String stripUnresolvedCustomerName(String text) { if (text == null) { return ""; } return text.replace("${customerName}", "您").replace("${customer_name}", "您"); } }