const fs = require('fs'); const path = require('path'); const javaRoot = path.join(__dirname, '..', 'fs-service', 'src', 'main', 'java'); function w(rel, content) { const full = path.join(javaRoot, rel); fs.mkdirSync(path.dirname(full), { recursive: true }); fs.writeFileSync(full, content, 'utf8'); console.log('Wrote', rel); } function u(str) { return str; } // ProfilePromptEnrichmentHelper - full rewrite w('com/fs/company/service/workflow/profile/panel/ProfilePromptEnrichmentHelper.java', `package com.fs.company.service.workflow.profile.panel; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fs.company.domain.LobsterConversationSummary; import com.fs.company.mapper.LobsterConversationSummaryMapper; import com.fs.company.service.workflow.profile.LobsterProfileFieldRegistryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * ${u('\u753b\u50cf Prompt \u5bcc\u5316\uff1a\u7ed3\u6784\u5316\u6458\u8981\u3001\u914d\u7f6e\u5b57\u6bb5\u3001\u884c\u4e3a\u4fe1\u53f7\u3001\u52a8\u6001\u8bed\u6c14\u7b49\uff0c\u4f9b buildAiPrompt \u6ce8\u5165\u3002')} */ @Component public class ProfilePromptEnrichmentHelper { private static final Set STABLE_FACT_PREFIXES = new HashSet<>(java.util.Arrays.asList( "customer_", "after_sales_", "upgrade_intention", "repurchase_intention", "course.", "live.", "call.", "voice.", "order.", "crm.", "sop.", "qw." )); private static final Set PROCESS_FACT_KEYS = new HashSet<>(java.util.Arrays.asList( "destination", "budget", "appointedTime", "nextContactTime", "deliveryDeadline", "coreNeed", "requirement", "confirmedPlan", "dealProbability" )); @Autowired(required = false) private LobsterConversationSummaryMapper conversationSummaryMapper; @Autowired(required = false) private LobsterProfileFieldRegistryService profileFieldRegistryService; public Map loadLatestSummaryDetail(Long companyId, String externalUserId, String channelType) { if (conversationSummaryMapper == null || companyId == null || !StringUtils.hasText(externalUserId)) { return Collections.emptyMap(); } try { String ch = StringUtils.hasText(channelType) ? channelType : "QW"; List rows = conversationSummaryMapper.selectByExternalUserId( companyId, externalUserId, ch); if (rows == null || rows.isEmpty()) { return Collections.emptyMap(); } return ConversationSummaryViewHelper.toView(rows.get(0)); } catch (Exception ignored) { return Collections.emptyMap(); } } public Map extractStableProfileFacts(List historicalFacts) { Map result = new LinkedHashMap<>(); if (historicalFacts == null || historicalFacts.isEmpty()) { return result; } Set seenKeys = new HashSet<>(); for (Object o : historicalFacts) { if (!(o instanceof Map)) { continue; } Map f = (Map) o; String key = stringVal(f.get("fact_key")); if (!StringUtils.hasText(key) || seenKeys.contains(key)) { continue; } if (!ProfileFactLabelHelper.isDisplayableKey(key)) { continue; } if (PROCESS_FACT_KEYS.contains(key)) { continue; } if (!isStableFactKey(key)) { continue; } String val = stringVal(f.get("fact_value")); if (!StringUtils.hasText(val) || "null".equalsIgnoreCase(val)) { continue; } seenKeys.add(key); result.put(ProfileFactLabelHelper.labelOf(key), val); } return result; } public Map flattenHistoricalFacts(List historicalFacts) { Map flat = new LinkedHashMap<>(); if (historicalFacts == null) { return flat; } Set seen = new HashSet<>(); for (Object o : historicalFacts) { if (!(o instanceof Map)) { continue; } Map f = (Map) o; String key = stringVal(f.get("fact_key")); if (!StringUtils.hasText(key) || seen.contains(key)) { continue; } if (!ProfileFactLabelHelper.isDisplayableKey(key)) { continue; } String val = stringVal(f.get("fact_value")); if (!StringUtils.hasText(val)) { continue; } seen.add(key); flat.put(key, val); } return flat; } public void appendStructuredSummary(StringBuilder body, Map summary) { if (summary == null || summary.isEmpty()) { return; } List lines = new ArrayList<>(); appendIfPresent(lines, "${u('\u9636\u6bb5')}", summary.get("stage")); appendIfPresent(lines, "${u('\u60c5\u7eea\u6001\u5ea6')}", attitudeLabel(summary.get("customerAttitude"))); appendIfPresent(lines, "${u('\u610f\u5411\u7a0b\u5ea6')}", intentionLabel(summary.get("customerIntentionLevel"))); appendIfPresent(lines, "${u('\u4e0b\u4e00\u6b65\u5efa\u8bae')}", summary.get("nextActionHint")); Object concerns = summary.get("customerConcerns"); if (concerns instanceof List && !((List) concerns).isEmpty()) { lines.add("${u('\u5ba2\u6237\u987e\u8651')}: " + joinList((List) concerns)); } appendExtendedBlocks(lines, summary.get("extendedSummary")); if (lines.isEmpty()) { return; } body.append("${u('\u3010\u7ed3\u6784\u5316\u4f1a\u8bdd\u6458\u8981\u3011')}\\n"); for (String line : lines) { body.append("- ").append(line).append("\\n"); } body.append("${u('\u751f\u6210\u56de\u590d\u65f6\u8bf7\u5bf9\u9f50\u4e0a\u8ff0\u9636\u6bb5/\u60c5\u7eea/\u610f\u5411/\u987e\u8651\uff0c\u52ff\u4e0e\u6458\u8981\u77db\u76fe\u3002')}\\n\\n"); } public void appendConfigFieldGroups(StringBuilder body, Long companyId, Map factByKey) { if (profileFieldRegistryService == null || companyId == null || factByKey == null || factByKey.isEmpty()) { return; } List> configFields = profileFieldRegistryService.listFields(companyId, true); Map factObj = new LinkedHashMap<>(); factByKey.forEach(factObj::put); List> values = ProfileListAggregateHelper.buildConfigFieldValues(factObj, configFields); if (values.isEmpty()) { return; } Map> groups = new LinkedHashMap<>(); groups.put("manual", new ArrayList<>()); groups.put("workflow", new ArrayList<>()); groups.put("ai", new ArrayList<>()); for (Map item : values) { if (item == null || item.get("value") == null) { continue; } String snippet = item.get("label") + ": " + item.get("value"); String source = stringVal(item.get("source")).toLowerCase(); if ("workflow".equals(source)) { groups.get("workflow").add(snippet); } else if ("ai_inferred".equals(source)) { groups.get("ai").add(snippet); } else { groups.get("manual").add(snippet); } } body.append("${u('\u3010\u79df\u6237\u914d\u7f6e\u5b57\u6bb5\u3011')}\\n"); appendGroup(body, "${u('\u7ba1\u7406\u5458\u914d\u7f6e')}", groups.get("manual")); appendGroup(body, "${u('\u5de5\u4f5c\u6d41\u5b57\u6bb5')}", groups.get("workflow")); appendGroup(body, "${u('AI\u53d1\u73b0')}", groups.get("ai")); body.append("\\n"); } public void appendBehaviorSignals(StringBuilder body, Map factByKey) { if (factByKey == null || factByKey.isEmpty()) { return; } List signals = new ArrayList<>(); addSignal(signals, factByKey, "course.watch_minutes", "${u('\u8bfe\u7a0b\u89c2\u770b\u5206\u949f')}"); addSignal(signals, factByKey, "course.last_finish_time", "${u('\u6700\u8fd1\u5b8c\u8bfe')}"); addSignal(signals, factByKey, "live.watch_count", "${u('\u76f4\u64ad\u89c2\u770b\u6b21\u6570')}"); addSignal(signals, factByKey, "live.last_finish_time", "${u('\u6700\u8fd1\u5b8c\u64ad')}"); addSignal(signals, factByKey, "call.last_duration_sec", "${u('\u6700\u8fd1\u901a\u8bdd\u65f6\u957f(\u79d2)')}"); addSignal(signals, factByKey, "call.intention", "${u('\u901a\u8bdd\u610f\u5411')}"); addSignal(signals, factByKey, "voice.last_duration_sec", "${u('\u6700\u8fd1\u8bed\u97f3\u65f6\u957f(\u79d2)')}"); addSignal(signals, factByKey, "voice.call_count", "${u('\u8bed\u97f3\u901a\u8bdd\u6b21\u6570')}"); addSignal(signals, factByKey, "order.order_count", "${u('\u8ba2\u5355\u6570')}"); if (signals.isEmpty()) { return; } body.append("${u('\u3010\u8fd1\u671f\u884c\u4e3a\u4fe1\u53f7\u3011')}\\n"); for (String s : signals) { body.append("- ").append(s).append("\\n"); } body.append("\\n"); } public void appendDynamicToneGuidance(StringBuilder body, Map context, Map habits) { if (body == null) { return; } List hints = new ArrayList<>(); String sentiment = context != null && context.get("detectedSentiment") != null ? context.get("detectedSentiment").toString() : null; if (sentiment != null) { try { double v = Double.parseDouble(sentiment.trim()); if (v <= -0.3) { hints.add("${u('\u68c0\u6d4b\u5230\u5ba2\u6237\u60c5\u7eea\u504f\u8d1f\u9762\uff0c\u8bed\u6c14\u5b9c\u5171\u60c5\u5b89\u629a\uff0c\u5355\u6761\u4e0d\u8d85\u8fc780\u5b57\uff0c\u907f\u514d\u63a8\u9500\uff0c\u6700\u591a1\u4e2a\u95ee\u53f7')}"); } else if (v >= 0.3) { hints.add("${u('\u5ba2\u6237\u60c5\u7eea\u504f\u79ef\u6781\uff0c\u53ef\u9002\u5f53\u63a8\u8fdb\u4e0b\u4e00\u6b65\uff0c\u5355\u6761\u53ef\u7565\u957f\u4ecd\u5b9c\u7b80\u6d01\uff0c\u6700\u591a2\u4e2a\u95ee\u53f7')}"); } } catch (NumberFormatException ignored) { String lower = sentiment.toLowerCase(); if (lower.contains("negative") || lower.contains("angry") || lower.contains("upset")) { hints.add("${u('\u68c0\u6d4b\u5230\u5ba2\u6237\u60c5\u7eea\u504f\u8d1f\u9762\uff0c\u8bed\u6c14\u5b9c\u5171\u60c5\u5b89\u629a\uff0c\u5355\u6761\u4e0d\u8d85\u8fc780\u5b57\uff0c\u907f\u514d\u63a8\u9500')}"); } } } if (habits != null) { String replySpeed = firstNonEmpty(habits.get("reply_speed"), habits.get("avg_reply_speed"), habits.get("customer_reply_speed")); if (replySpeed != null && (replySpeed.contains("${u('\u6162')}") || replySpeed.toLowerCase().contains("slow"))) { hints.add("${u('\u5ba2\u6237\u56de\u590d\u504f\u6162\uff0c\u8bed\u6c14\u5b9c\u8010\u5fc3\u7b80\u77ed\uff0c\u52ff\u8fde\u7eed\u8ffd\u95ee\uff0c\u5355\u6761\u5efa\u8bae\u4e0d\u8d85\u8fc7100\u5b57')}"); } String chatHabit = firstNonEmpty(habits.get("chat_habit"), habits.get("customer_chat_habit")); if (chatHabit != null && chatHabit.contains("${u('\u7b80')}")) { hints.add("${u('\u5ba2\u6237\u504f\u597d\u7b80\u77ed\u6c9f\u901a\uff0c\u63a7\u5236\u7bc7\u5e45\uff0c\u6700\u591a1\u4e2a\u95ee\u53f7')}"); } } Object deEscalate = context != null ? context.get("_deEscalateMode") : null; if (Boolean.TRUE.equals(deEscalate)) { hints.add("${u('\u5f53\u524d\u5904\u4e8e\u964d\u7ef4\u5b89\u629a\u6a21\u5f0f\uff0c\u4f18\u5148\u503e\u542c\u4e0e\u786e\u8ba4\uff0c\u6682\u4e0d\u63a8\u8fdb\u9500\u552e')}"); } if (hints.isEmpty()) { return; } body.append("${u('\u3010\u52a8\u6001\u8bed\u6c14\u7b56\u7565\u3011')}\\n"); for (String h : hints) { body.append("- ").append(h).append("\\n"); } body.append("\\n"); } @SuppressWarnings("unchecked") public void appendSemanticKeywordHints(StringBuilder body, Map context) { if (context == null) { return; } Map vars = context.get("variables") instanceof Map ? (Map) context.get("variables") : null; Object keywords = vars != null ? vars.get("customerKeywords") : null; String contextNote = context.get("contextNote") != null ? context.get("contextNote").toString() : null; String suggestedAction = context.get("suggestedAction") != null ? context.get("suggestedAction").toString() : null; if (keywords == null && !StringUtils.hasText(contextNote) && !StringUtils.hasText(suggestedAction)) { return; } body.append("${u('\u3010\u8bed\u4e49\u5173\u952e\u8bcd\u63d0\u793a\u3011')}\\n"); if (keywords != null) { body.append("- ${u('\u5ba2\u6237\u5173\u952e\u8bcd')}: ").append(keywords).append("\\n"); } if (StringUtils.hasText(contextNote)) { body.append("- ${u('\u4e0a\u4e0b\u6587')}: ").append(contextNote).append("\\n"); } if (StringUtils.hasText(suggestedAction)) { body.append("- ${u('\u5efa\u8bae\u52a8\u4f5c')}: ").append(suggestedAction).append("\\n"); } body.append("${u('\u53ef\u5728\u56de\u590d\u4e2d\u81ea\u7136\u547c\u5e94\u4e0a\u8ff0\u8bed\u4e49\uff0c\u52ff\u751f\u786c\u590d\u8bfb\u3002')}\\n\\n"); } private static boolean isStableFactKey(String key) { if (key.startsWith("customer_") || key.startsWith("after_sales_")) { return true; } if ("upgrade_intention".equals(key) || "repurchase_intention".equals(key)) { return true; } for (String prefix : STABLE_FACT_PREFIXES) { if (prefix.endsWith(".") && key.startsWith(prefix)) { return true; } } return false; } @SuppressWarnings("unchecked") private static void appendExtendedBlocks(List lines, Object extObj) { if (!(extObj instanceof Map) || ((Map) extObj).isEmpty()) { if (extObj instanceof String && StringUtils.hasText((String) extObj)) { try { JSONObject json = JSON.parseObject((String) extObj); appendExtendedBlocks(lines, json); } catch (Exception ignored) { } } return; } Map ext = (Map) extObj; appendExtBlock(lines, "${u('\u9700\u6c42')}", ext.get("requirement"), new String[][]{{"coreNeed", "${u('\u6838\u5fc3\u9700\u6c42')}"}, {"level", "${u('\u610f\u5411')}"}, {"budget", "${u('\u9884\u7b97')}"}}); appendExtBlock(lines, "${u('\u987e\u8651')}", ext.get("concerns"), new String[][]{{"worries", "${u('\u987e\u8651')}"}, {"objections", "${u('\u5f02\u8bae')}"}}); appendExtBlock(lines, "${u('\u8fdb\u5c55')}", ext.get("progress"), new String[][]{{"stage", "${u('\u9636\u6bb5')}"}, {"pending", "${u('\u5f85\u63a8\u8fdb')}"}, {"stuckReason", "${u('\u5361\u70b9')}"}}); appendExtBlock(lines, "${u('\u5171\u8bc6')}", ext.get("consensus"), new String[][]{{"confirmedPlan", "${u('\u5df2\u5b9a\u65b9\u6848')}"}, {"price", "${u('\u4ef7\u683c')}"}}); } private static void appendExtBlock(List lines, String title, Object block, String[][] fields) { if (!(block instanceof Map)) { return; } Map map = (Map) block; List parts = new ArrayList<>(); for (String[] pair : fields) { Object v = map.get(pair[0]); if (v != null && StringUtils.hasText(v.toString())) { parts.add(pair[1] + "=" + truncate(v.toString(), 80)); } } if (!parts.isEmpty()) { lines.add(title + ": " + String.join("${u('\uff0c')}", parts)); } } private static void appendGroup(StringBuilder body, String title, List items) { if (items == null || items.isEmpty()) { return; } body.append("- ").append(title).append(": ").append(String.join("${u('\uff1b')}", items)).append("\\n"); } private static void addSignal(List signals, Map facts, String key, String label) { String v = facts.get(key); if (StringUtils.hasText(v)) { signals.add(label + " " + v); } } private static void appendIfPresent(List lines, String label, Object value) { if (value != null && StringUtils.hasText(value.toString())) { lines.add(label + ": " + truncate(value.toString(), 120)); } } private static String attitudeLabel(Object value) { if (value == null) { return null; } String v = value.toString().trim().toLowerCase(); switch (v) { case "positive": return "${u('\u79ef\u6781')}"; case "negative": return "${u('\u6d88\u6781')}"; case "neutral": return "${u('\u4e2d\u6027')}"; case "curious": return "${u('\u597d\u5947')}"; case "resistant": return "${u('\u62b5\u89e6')}"; default: return value.toString(); } } private static String intentionLabel(Object value) { if (value == null) { return null; } String v = value.toString().trim().toLowerCase(); switch (v) { case "none": case "no": return "${u('\u65e0\u610f\u5411')}"; case "weak": case "low": return "${u('\u5f31\u610f\u5411')}"; case "medium": case "mid": return "${u('\u4e2d\u610f\u5411')}"; case "strong": case "high": return "${u('\u5f3a\u610f\u5411')}"; default: return value.toString(); } } private static String joinList(List list) { List parts = new ArrayList<>(); for (Object o : list) { if (o != null && StringUtils.hasText(o.toString())) { parts.add(o.toString().trim()); } } return String.join("${u('\u3001')}", parts); } private static String truncate(String text, int max) { if (text == null) { return ""; } return text.length() > max ? text.substring(0, max) + "${u('\u2026')}" : text; } private static String stringVal(Object v) { return v != null ? v.toString().trim() : ""; } private static String firstNonEmpty(String... values) { for (String v : values) { if (StringUtils.hasText(v)) { return v; } } return null; } } `); console.log('Done');