| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- 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<String, Object> variables) {
- return !extractConfiguredOpeningScript(node, variables).isEmpty();
- }
- /**
- * 提取开场话术优先级:流程变量 > greetingConfig > messageTemplate。
- */
- public static String extractConfiguredOpeningScript(CompanyWorkflowLobsterNode node, Map<String, Object> 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<String, Object> enrichConversationVariables(Map<String, Object> variables, ContactInfo contactInfo) {
- Map<String, Object> out = variables != null ? new LinkedHashMap<>(variables) : new LinkedHashMap<>();
- String customerName = resolveCustomerName(out, contactInfo);
- out.putIfAbsent("customerName", customerName);
- out.putIfAbsent("customer_name", customerName);
- Map<String, String> 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<String, Object> variables, VariableSubstitutionEngine varEngine) {
- if (template == null || template.isEmpty()) {
- return "";
- }
- Map<String, String> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, String> brandMapFromVariables(Map<String, Object> variables) {
- Map<String, String> 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<String, Object> variables, Map<String, String> 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}", "您");
- }
- }
|