fix-java-encoding-unicode.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. const fs = require('fs');
  2. const path = require('path');
  3. const javaRoot = path.join(__dirname, '..', 'fs-service', 'src', 'main', 'java');
  4. function w(rel, content) {
  5. const full = path.join(javaRoot, rel);
  6. fs.mkdirSync(path.dirname(full), { recursive: true });
  7. fs.writeFileSync(full, content, 'utf8');
  8. console.log('Wrote', rel);
  9. }
  10. function u(str) {
  11. return str;
  12. }
  13. // ProfilePromptEnrichmentHelper - full rewrite
  14. w('com/fs/company/service/workflow/profile/panel/ProfilePromptEnrichmentHelper.java', `package com.fs.company.service.workflow.profile.panel;
  15. import com.alibaba.fastjson.JSON;
  16. import com.alibaba.fastjson.JSONObject;
  17. import com.fs.company.domain.LobsterConversationSummary;
  18. import com.fs.company.mapper.LobsterConversationSummaryMapper;
  19. import com.fs.company.service.workflow.profile.LobsterProfileFieldRegistryService;
  20. import org.springframework.beans.factory.annotation.Autowired;
  21. import org.springframework.stereotype.Component;
  22. import org.springframework.util.StringUtils;
  23. import java.util.ArrayList;
  24. import java.util.Collections;
  25. import java.util.HashSet;
  26. import java.util.LinkedHashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Set;
  30. /**
  31. * ${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')}
  32. */
  33. @Component
  34. public class ProfilePromptEnrichmentHelper {
  35. private static final Set<String> STABLE_FACT_PREFIXES = new HashSet<>(java.util.Arrays.asList(
  36. "customer_", "after_sales_", "upgrade_intention", "repurchase_intention",
  37. "course.", "live.", "call.", "voice.", "order.", "crm.", "sop.", "qw."
  38. ));
  39. private static final Set<String> PROCESS_FACT_KEYS = new HashSet<>(java.util.Arrays.asList(
  40. "destination", "budget", "appointedTime", "nextContactTime", "deliveryDeadline",
  41. "coreNeed", "requirement", "confirmedPlan", "dealProbability"
  42. ));
  43. @Autowired(required = false)
  44. private LobsterConversationSummaryMapper conversationSummaryMapper;
  45. @Autowired(required = false)
  46. private LobsterProfileFieldRegistryService profileFieldRegistryService;
  47. public Map<String, Object> loadLatestSummaryDetail(Long companyId, String externalUserId, String channelType) {
  48. if (conversationSummaryMapper == null || companyId == null || !StringUtils.hasText(externalUserId)) {
  49. return Collections.emptyMap();
  50. }
  51. try {
  52. String ch = StringUtils.hasText(channelType) ? channelType : "QW";
  53. List<LobsterConversationSummary> rows = conversationSummaryMapper.selectByExternalUserId(
  54. companyId, externalUserId, ch);
  55. if (rows == null || rows.isEmpty()) {
  56. return Collections.emptyMap();
  57. }
  58. return ConversationSummaryViewHelper.toView(rows.get(0));
  59. } catch (Exception ignored) {
  60. return Collections.emptyMap();
  61. }
  62. }
  63. public Map<String, String> extractStableProfileFacts(List<?> historicalFacts) {
  64. Map<String, String> result = new LinkedHashMap<>();
  65. if (historicalFacts == null || historicalFacts.isEmpty()) {
  66. return result;
  67. }
  68. Set<String> seenKeys = new HashSet<>();
  69. for (Object o : historicalFacts) {
  70. if (!(o instanceof Map)) {
  71. continue;
  72. }
  73. Map<?, ?> f = (Map<?, ?>) o;
  74. String key = stringVal(f.get("fact_key"));
  75. if (!StringUtils.hasText(key) || seenKeys.contains(key)) {
  76. continue;
  77. }
  78. if (!ProfileFactLabelHelper.isDisplayableKey(key)) {
  79. continue;
  80. }
  81. if (PROCESS_FACT_KEYS.contains(key)) {
  82. continue;
  83. }
  84. if (!isStableFactKey(key)) {
  85. continue;
  86. }
  87. String val = stringVal(f.get("fact_value"));
  88. if (!StringUtils.hasText(val) || "null".equalsIgnoreCase(val)) {
  89. continue;
  90. }
  91. seenKeys.add(key);
  92. result.put(ProfileFactLabelHelper.labelOf(key), val);
  93. }
  94. return result;
  95. }
  96. public Map<String, String> flattenHistoricalFacts(List<?> historicalFacts) {
  97. Map<String, String> flat = new LinkedHashMap<>();
  98. if (historicalFacts == null) {
  99. return flat;
  100. }
  101. Set<String> seen = new HashSet<>();
  102. for (Object o : historicalFacts) {
  103. if (!(o instanceof Map)) {
  104. continue;
  105. }
  106. Map<?, ?> f = (Map<?, ?>) o;
  107. String key = stringVal(f.get("fact_key"));
  108. if (!StringUtils.hasText(key) || seen.contains(key)) {
  109. continue;
  110. }
  111. if (!ProfileFactLabelHelper.isDisplayableKey(key)) {
  112. continue;
  113. }
  114. String val = stringVal(f.get("fact_value"));
  115. if (!StringUtils.hasText(val)) {
  116. continue;
  117. }
  118. seen.add(key);
  119. flat.put(key, val);
  120. }
  121. return flat;
  122. }
  123. public void appendStructuredSummary(StringBuilder body, Map<String, Object> summary) {
  124. if (summary == null || summary.isEmpty()) {
  125. return;
  126. }
  127. List<String> lines = new ArrayList<>();
  128. appendIfPresent(lines, "${u('\u9636\u6bb5')}", summary.get("stage"));
  129. appendIfPresent(lines, "${u('\u60c5\u7eea\u6001\u5ea6')}", attitudeLabel(summary.get("customerAttitude")));
  130. appendIfPresent(lines, "${u('\u610f\u5411\u7a0b\u5ea6')}", intentionLabel(summary.get("customerIntentionLevel")));
  131. appendIfPresent(lines, "${u('\u4e0b\u4e00\u6b65\u5efa\u8bae')}", summary.get("nextActionHint"));
  132. Object concerns = summary.get("customerConcerns");
  133. if (concerns instanceof List && !((List<?>) concerns).isEmpty()) {
  134. lines.add("${u('\u5ba2\u6237\u987e\u8651')}: " + joinList((List<?>) concerns));
  135. }
  136. appendExtendedBlocks(lines, summary.get("extendedSummary"));
  137. if (lines.isEmpty()) {
  138. return;
  139. }
  140. body.append("${u('\u3010\u7ed3\u6784\u5316\u4f1a\u8bdd\u6458\u8981\u3011')}\\n");
  141. for (String line : lines) {
  142. body.append("- ").append(line).append("\\n");
  143. }
  144. 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");
  145. }
  146. public void appendConfigFieldGroups(StringBuilder body, Long companyId, Map<String, String> factByKey) {
  147. if (profileFieldRegistryService == null || companyId == null || factByKey == null || factByKey.isEmpty()) {
  148. return;
  149. }
  150. List<Map<String, Object>> configFields = profileFieldRegistryService.listFields(companyId, true);
  151. Map<String, Object> factObj = new LinkedHashMap<>();
  152. factByKey.forEach(factObj::put);
  153. List<Map<String, Object>> values = ProfileListAggregateHelper.buildConfigFieldValues(factObj, configFields);
  154. if (values.isEmpty()) {
  155. return;
  156. }
  157. Map<String, List<String>> groups = new LinkedHashMap<>();
  158. groups.put("manual", new ArrayList<>());
  159. groups.put("workflow", new ArrayList<>());
  160. groups.put("ai", new ArrayList<>());
  161. for (Map<String, Object> item : values) {
  162. if (item == null || item.get("value") == null) {
  163. continue;
  164. }
  165. String snippet = item.get("label") + ": " + item.get("value");
  166. String source = stringVal(item.get("source")).toLowerCase();
  167. if ("workflow".equals(source)) {
  168. groups.get("workflow").add(snippet);
  169. } else if ("ai_inferred".equals(source)) {
  170. groups.get("ai").add(snippet);
  171. } else {
  172. groups.get("manual").add(snippet);
  173. }
  174. }
  175. body.append("${u('\u3010\u79df\u6237\u914d\u7f6e\u5b57\u6bb5\u3011')}\\n");
  176. appendGroup(body, "${u('\u7ba1\u7406\u5458\u914d\u7f6e')}", groups.get("manual"));
  177. appendGroup(body, "${u('\u5de5\u4f5c\u6d41\u5b57\u6bb5')}", groups.get("workflow"));
  178. appendGroup(body, "${u('AI\u53d1\u73b0')}", groups.get("ai"));
  179. body.append("\\n");
  180. }
  181. public void appendBehaviorSignals(StringBuilder body, Map<String, String> factByKey) {
  182. if (factByKey == null || factByKey.isEmpty()) {
  183. return;
  184. }
  185. List<String> signals = new ArrayList<>();
  186. addSignal(signals, factByKey, "course.watch_minutes", "${u('\u8bfe\u7a0b\u89c2\u770b\u5206\u949f')}");
  187. addSignal(signals, factByKey, "course.last_finish_time", "${u('\u6700\u8fd1\u5b8c\u8bfe')}");
  188. addSignal(signals, factByKey, "live.watch_count", "${u('\u76f4\u64ad\u89c2\u770b\u6b21\u6570')}");
  189. addSignal(signals, factByKey, "live.last_finish_time", "${u('\u6700\u8fd1\u5b8c\u64ad')}");
  190. addSignal(signals, factByKey, "call.last_duration_sec", "${u('\u6700\u8fd1\u901a\u8bdd\u65f6\u957f(\u79d2)')}");
  191. addSignal(signals, factByKey, "call.intention", "${u('\u901a\u8bdd\u610f\u5411')}");
  192. addSignal(signals, factByKey, "voice.last_duration_sec", "${u('\u6700\u8fd1\u8bed\u97f3\u65f6\u957f(\u79d2)')}");
  193. addSignal(signals, factByKey, "voice.call_count", "${u('\u8bed\u97f3\u901a\u8bdd\u6b21\u6570')}");
  194. addSignal(signals, factByKey, "order.order_count", "${u('\u8ba2\u5355\u6570')}");
  195. if (signals.isEmpty()) {
  196. return;
  197. }
  198. body.append("${u('\u3010\u8fd1\u671f\u884c\u4e3a\u4fe1\u53f7\u3011')}\\n");
  199. for (String s : signals) {
  200. body.append("- ").append(s).append("\\n");
  201. }
  202. body.append("\\n");
  203. }
  204. public void appendDynamicToneGuidance(StringBuilder body, Map<String, Object> context, Map<String, String> habits) {
  205. if (body == null) {
  206. return;
  207. }
  208. List<String> hints = new ArrayList<>();
  209. String sentiment = context != null && context.get("detectedSentiment") != null
  210. ? context.get("detectedSentiment").toString() : null;
  211. if (sentiment != null) {
  212. try {
  213. double v = Double.parseDouble(sentiment.trim());
  214. if (v <= -0.3) {
  215. 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')}");
  216. } else if (v >= 0.3) {
  217. 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')}");
  218. }
  219. } catch (NumberFormatException ignored) {
  220. String lower = sentiment.toLowerCase();
  221. if (lower.contains("negative") || lower.contains("angry") || lower.contains("upset")) {
  222. 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')}");
  223. }
  224. }
  225. }
  226. if (habits != null) {
  227. String replySpeed = firstNonEmpty(habits.get("reply_speed"), habits.get("avg_reply_speed"),
  228. habits.get("customer_reply_speed"));
  229. if (replySpeed != null && (replySpeed.contains("${u('\u6162')}") || replySpeed.toLowerCase().contains("slow"))) {
  230. 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')}");
  231. }
  232. String chatHabit = firstNonEmpty(habits.get("chat_habit"), habits.get("customer_chat_habit"));
  233. if (chatHabit != null && chatHabit.contains("${u('\u7b80')}")) {
  234. hints.add("${u('\u5ba2\u6237\u504f\u597d\u7b80\u77ed\u6c9f\u901a\uff0c\u63a7\u5236\u7bc7\u5e45\uff0c\u6700\u591a1\u4e2a\u95ee\u53f7')}");
  235. }
  236. }
  237. Object deEscalate = context != null ? context.get("_deEscalateMode") : null;
  238. if (Boolean.TRUE.equals(deEscalate)) {
  239. 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')}");
  240. }
  241. if (hints.isEmpty()) {
  242. return;
  243. }
  244. body.append("${u('\u3010\u52a8\u6001\u8bed\u6c14\u7b56\u7565\u3011')}\\n");
  245. for (String h : hints) {
  246. body.append("- ").append(h).append("\\n");
  247. }
  248. body.append("\\n");
  249. }
  250. @SuppressWarnings("unchecked")
  251. public void appendSemanticKeywordHints(StringBuilder body, Map<String, Object> context) {
  252. if (context == null) {
  253. return;
  254. }
  255. Map<String, Object> vars = context.get("variables") instanceof Map
  256. ? (Map<String, Object>) context.get("variables") : null;
  257. Object keywords = vars != null ? vars.get("customerKeywords") : null;
  258. String contextNote = context.get("contextNote") != null ? context.get("contextNote").toString() : null;
  259. String suggestedAction = context.get("suggestedAction") != null ? context.get("suggestedAction").toString() : null;
  260. if (keywords == null && !StringUtils.hasText(contextNote) && !StringUtils.hasText(suggestedAction)) {
  261. return;
  262. }
  263. body.append("${u('\u3010\u8bed\u4e49\u5173\u952e\u8bcd\u63d0\u793a\u3011')}\\n");
  264. if (keywords != null) {
  265. body.append("- ${u('\u5ba2\u6237\u5173\u952e\u8bcd')}: ").append(keywords).append("\\n");
  266. }
  267. if (StringUtils.hasText(contextNote)) {
  268. body.append("- ${u('\u4e0a\u4e0b\u6587')}: ").append(contextNote).append("\\n");
  269. }
  270. if (StringUtils.hasText(suggestedAction)) {
  271. body.append("- ${u('\u5efa\u8bae\u52a8\u4f5c')}: ").append(suggestedAction).append("\\n");
  272. }
  273. 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");
  274. }
  275. private static boolean isStableFactKey(String key) {
  276. if (key.startsWith("customer_") || key.startsWith("after_sales_")) {
  277. return true;
  278. }
  279. if ("upgrade_intention".equals(key) || "repurchase_intention".equals(key)) {
  280. return true;
  281. }
  282. for (String prefix : STABLE_FACT_PREFIXES) {
  283. if (prefix.endsWith(".") && key.startsWith(prefix)) {
  284. return true;
  285. }
  286. }
  287. return false;
  288. }
  289. @SuppressWarnings("unchecked")
  290. private static void appendExtendedBlocks(List<String> lines, Object extObj) {
  291. if (!(extObj instanceof Map) || ((Map<?, ?>) extObj).isEmpty()) {
  292. if (extObj instanceof String && StringUtils.hasText((String) extObj)) {
  293. try {
  294. JSONObject json = JSON.parseObject((String) extObj);
  295. appendExtendedBlocks(lines, json);
  296. } catch (Exception ignored) {
  297. }
  298. }
  299. return;
  300. }
  301. Map<String, Object> ext = (Map<String, Object>) extObj;
  302. appendExtBlock(lines, "${u('\u9700\u6c42')}", ext.get("requirement"),
  303. new String[][]{{"coreNeed", "${u('\u6838\u5fc3\u9700\u6c42')}"}, {"level", "${u('\u610f\u5411')}"}, {"budget", "${u('\u9884\u7b97')}"}});
  304. appendExtBlock(lines, "${u('\u987e\u8651')}", ext.get("concerns"),
  305. new String[][]{{"worries", "${u('\u987e\u8651')}"}, {"objections", "${u('\u5f02\u8bae')}"}});
  306. appendExtBlock(lines, "${u('\u8fdb\u5c55')}", ext.get("progress"),
  307. new String[][]{{"stage", "${u('\u9636\u6bb5')}"}, {"pending", "${u('\u5f85\u63a8\u8fdb')}"}, {"stuckReason", "${u('\u5361\u70b9')}"}});
  308. appendExtBlock(lines, "${u('\u5171\u8bc6')}", ext.get("consensus"),
  309. new String[][]{{"confirmedPlan", "${u('\u5df2\u5b9a\u65b9\u6848')}"}, {"price", "${u('\u4ef7\u683c')}"}});
  310. }
  311. private static void appendExtBlock(List<String> lines, String title, Object block, String[][] fields) {
  312. if (!(block instanceof Map)) {
  313. return;
  314. }
  315. Map<?, ?> map = (Map<?, ?>) block;
  316. List<String> parts = new ArrayList<>();
  317. for (String[] pair : fields) {
  318. Object v = map.get(pair[0]);
  319. if (v != null && StringUtils.hasText(v.toString())) {
  320. parts.add(pair[1] + "=" + truncate(v.toString(), 80));
  321. }
  322. }
  323. if (!parts.isEmpty()) {
  324. lines.add(title + ": " + String.join("${u('\uff0c')}", parts));
  325. }
  326. }
  327. private static void appendGroup(StringBuilder body, String title, List<String> items) {
  328. if (items == null || items.isEmpty()) {
  329. return;
  330. }
  331. body.append("- ").append(title).append(": ").append(String.join("${u('\uff1b')}", items)).append("\\n");
  332. }
  333. private static void addSignal(List<String> signals, Map<String, String> facts, String key, String label) {
  334. String v = facts.get(key);
  335. if (StringUtils.hasText(v)) {
  336. signals.add(label + " " + v);
  337. }
  338. }
  339. private static void appendIfPresent(List<String> lines, String label, Object value) {
  340. if (value != null && StringUtils.hasText(value.toString())) {
  341. lines.add(label + ": " + truncate(value.toString(), 120));
  342. }
  343. }
  344. private static String attitudeLabel(Object value) {
  345. if (value == null) {
  346. return null;
  347. }
  348. String v = value.toString().trim().toLowerCase();
  349. switch (v) {
  350. case "positive": return "${u('\u79ef\u6781')}";
  351. case "negative": return "${u('\u6d88\u6781')}";
  352. case "neutral": return "${u('\u4e2d\u6027')}";
  353. case "curious": return "${u('\u597d\u5947')}";
  354. case "resistant": return "${u('\u62b5\u89e6')}";
  355. default: return value.toString();
  356. }
  357. }
  358. private static String intentionLabel(Object value) {
  359. if (value == null) {
  360. return null;
  361. }
  362. String v = value.toString().trim().toLowerCase();
  363. switch (v) {
  364. case "none":
  365. case "no": return "${u('\u65e0\u610f\u5411')}";
  366. case "weak":
  367. case "low": return "${u('\u5f31\u610f\u5411')}";
  368. case "medium":
  369. case "mid": return "${u('\u4e2d\u610f\u5411')}";
  370. case "strong":
  371. case "high": return "${u('\u5f3a\u610f\u5411')}";
  372. default: return value.toString();
  373. }
  374. }
  375. private static String joinList(List<?> list) {
  376. List<String> parts = new ArrayList<>();
  377. for (Object o : list) {
  378. if (o != null && StringUtils.hasText(o.toString())) {
  379. parts.add(o.toString().trim());
  380. }
  381. }
  382. return String.join("${u('\u3001')}", parts);
  383. }
  384. private static String truncate(String text, int max) {
  385. if (text == null) {
  386. return "";
  387. }
  388. return text.length() > max ? text.substring(0, max) + "${u('\u2026')}" : text;
  389. }
  390. private static String stringVal(Object v) {
  391. return v != null ? v.toString().trim() : "";
  392. }
  393. private static String firstNonEmpty(String... values) {
  394. for (String v : values) {
  395. if (StringUtils.hasText(v)) {
  396. return v;
  397. }
  398. }
  399. return null;
  400. }
  401. }
  402. `);
  403. console.log('Done');