| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- 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<String> 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<String> 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<String, Object> 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<LobsterConversationSummary> 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<String, String> extractStableProfileFacts(List<?> historicalFacts) {
- Map<String, String> result = new LinkedHashMap<>();
- if (historicalFacts == null || historicalFacts.isEmpty()) {
- return result;
- }
- Set<String> 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<String, String> flattenHistoricalFacts(List<?> historicalFacts) {
- Map<String, String> flat = new LinkedHashMap<>();
- if (historicalFacts == null) {
- return flat;
- }
- Set<String> 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<String, Object> summary) {
- if (summary == null || summary.isEmpty()) {
- return;
- }
- List<String> 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<String, String> factByKey) {
- if (profileFieldRegistryService == null || companyId == null || factByKey == null || factByKey.isEmpty()) {
- return;
- }
- List<Map<String, Object>> configFields = profileFieldRegistryService.listFields(companyId, true);
- Map<String, Object> factObj = new LinkedHashMap<>();
- factByKey.forEach(factObj::put);
- List<Map<String, Object>> values = ProfileListAggregateHelper.buildConfigFieldValues(factObj, configFields);
- if (values.isEmpty()) {
- return;
- }
- Map<String, List<String>> groups = new LinkedHashMap<>();
- groups.put("manual", new ArrayList<>());
- groups.put("workflow", new ArrayList<>());
- groups.put("ai", new ArrayList<>());
- for (Map<String, Object> 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<String, String> factByKey) {
- if (factByKey == null || factByKey.isEmpty()) {
- return;
- }
- List<String> 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<String, Object> context, Map<String, String> habits) {
- if (body == null) {
- return;
- }
- List<String> 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<String, Object> context) {
- if (context == null) {
- return;
- }
- Map<String, Object> vars = context.get("variables") instanceof Map
- ? (Map<String, Object>) 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<String> 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<String, Object> ext = (Map<String, Object>) 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<String> lines, String title, Object block, String[][] fields) {
- if (!(block instanceof Map)) {
- return;
- }
- Map<?, ?> map = (Map<?, ?>) block;
- List<String> 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<String> 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<String> signals, Map<String, String> facts, String key, String label) {
- String v = facts.get(key);
- if (StringUtils.hasText(v)) {
- signals.add(label + " " + v);
- }
- }
- private static void appendIfPresent(List<String> 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<String> 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');
|