diff --git a/backend/archive-service/.gitignore b/backend/archive-service/.gitignore index 59a54cd..2f7896d 100644 --- a/backend/archive-service/.gitignore +++ b/backend/archive-service/.gitignore @@ -1,2 +1 @@ target/ -target \ No newline at end of file diff --git a/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveMessageController.java b/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveMessageController.java index 0a19773..0bcdb76 100644 --- a/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveMessageController.java +++ b/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveMessageController.java @@ -270,32 +270,48 @@ public class ArchiveMessageController { nameMap.putAll(customerNameMap); // 对未找到昵称或昵称为ID的外部客户,实时调用企微API获取并更新数据库 + log.info("enrichSenderNames 开始处理, userIds={}, nameMap keys={}", userIds, nameMap.keySet()); for (String uid : userIds) { String currentName = nameMap.get(uid); - if (isExternalUserId(uid) && (currentName == null || currentName.equals(uid))) { + boolean isExternal = isExternalUserId(uid); + boolean needFetch = isExternal && (currentName == null || currentName.equals(uid)); + log.info("处理uid={}, currentName={}, isExternal={}, needFetch={}", uid, currentName, isExternal, needFetch); + if (needFetch) { try { Map detail = weComApiClient.getExternalContact(uid); + log.info("getExternalContact 返回: uid={}, detail={}", uid, detail); if (detail != null && !detail.isEmpty()) { Map contact = (Map) detail.get("external_contact"); if (contact != null) { String name = (String) contact.get("name"); + log.info("解析到name: uid={}, name={}", uid, name); if (name != null && !name.isEmpty()) { nameMap.put(uid, name); - // 更新数据库 + // 更新或创建数据库记录 Customer customer = customerMapper.selectOne( new LambdaQueryWrapper() .eq(Customer::getCustomerId, uid) - .eq(Customer::getCorpId, "wwd483c2fba24ae30a") + .eq(Customer::getCorpId, corpId) ); if (customer != null) { customer.setName(name); customerMapper.updateById(customer); + log.info("更新客户昵称成功: uid={}, name={}", uid, name); + } else { + Customer newCustomer = new Customer(); + newCustomer.setCustomerId(uid); + newCustomer.setCorpId(corpId); + newCustomer.setName(name); + newCustomer.setType("UNKNOWN"); + newCustomer.setStatus("ACTIVE"); + customerMapper.insert(newCustomer); + log.info("创建客户记录成功: uid={}, name={}", uid, name); } } } } } catch (Exception e) { - log.debug("实时获取客户昵称失败: {}", uid); + log.warn("实时获取客户昵称失败: uid={}, err={}", uid, e.getMessage()); } } } diff --git a/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java b/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java index b30f2e2..e28d3a3 100644 --- a/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java +++ b/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java @@ -112,11 +112,12 @@ public class GenerationController { public Result> generateUtterance( @RequestParam(value = "fromUser", required = false) String fromUser, @RequestParam(value = "toUser", required = false) String toUser, - @RequestParam(value = "roomid", required = false) String roomid) { + @RequestParam(value = "roomid", required = false) String roomid, + @RequestParam(value = "staffName", required = false) String staffName) { String chatContext = buildChatContext(fromUser, toUser, roomid); - String[] promptPair = promptEngine.buildUtterancePrompt(chatContext); + String[] promptPair = promptEngine.buildUtterancePrompt(chatContext, staffName); String systemPrompt = promptPair[0]; String userPrompt = promptPair[1]; @@ -187,7 +188,8 @@ public class GenerationController { public SseEmitter streamUtterance( @RequestParam("fromUser") String fromUser, @RequestParam("toUser") String toUser, - @RequestParam(value = "roomid", required = false) String roomid) { + @RequestParam(value = "roomid", required = false) String roomid, + @RequestParam(value = "staffName", required = false) String staffName) { SseEmitter emitter = new SseEmitter(120000L); @@ -208,8 +210,8 @@ public class GenerationController { return; } - String systemPrompt = promptEngine.buildSystemPrompt(chatContext); - String userPrompt = promptEngine.buildMultiStyleUserPrompt(); + String systemPrompt = promptEngine.buildSystemPrompt(chatContext, staffName); + String userPrompt = promptEngine.buildMultiStyleUserPrompt(staffName); long startTime = System.currentTimeMillis(); String generated = qianwenClient.generate(systemPrompt, userPrompt, 1200); diff --git a/backend/generation-service/src/main/java/com/artedu/generation/service/PromptEngine.java b/backend/generation-service/src/main/java/com/artedu/generation/service/PromptEngine.java index ecdb384..03dd6fd 100644 --- a/backend/generation-service/src/main/java/com/artedu/generation/service/PromptEngine.java +++ b/backend/generation-service/src/main/java/com/artedu/generation/service/PromptEngine.java @@ -10,9 +10,14 @@ public class PromptEngine { /** * 构建生成话术的系统提示词(角色设定) */ - public String buildSystemPrompt(String chatHistoryContext) { + public String buildSystemPrompt(String chatHistoryContext, String staffName) { StringBuilder sb = new StringBuilder(); sb.append("资深销售教练,仅输出符合规范的 3 条推荐回复话术,全程禁止输出任何对话分析、客户判断、前置解读、总结说明文字,任何分析类文字一律不写,正文只保留 3 条话术条目。\n"); + if (staffName != null && !staffName.isEmpty()) { + sb.append("## 重要约束\n"); + sb.append("你的名字是「").append(staffName).append("」,在回复中如果需要自称或介绍自己,必须使用这个名字,绝对禁止编造任何其他名字(如小王、小李、小张等)。\n"); + } + sb.append("## 对话历史\n"); sb.append(chatHistoryContext); sb.append("\n"); @@ -80,8 +85,8 @@ public class PromptEngine { * 构建完整话术 Prompt(system + user 分离) * 返回 String[]{systemPrompt, userPrompt} */ - public String[] buildUtterancePrompt(String chatHistoryContext) { - String system = buildSystemPrompt(chatHistoryContext); + public String[] buildUtterancePrompt(String chatHistoryContext, String staffName) { + String system = buildSystemPrompt(chatHistoryContext, staffName); // String user = buildUserPrompt(message, context, customerProfile, conversationContext, // referenceUtterances, intent, stage, stageLabel, intentLabel); String user = ""; @@ -133,8 +138,11 @@ public class PromptEngine { * 多风格话术生成的用户提示词 * 要求 LLM 输出 3 条不同风格话术,用 ||| 分隔 */ - public String buildMultiStyleUserPrompt() { + public String buildMultiStyleUserPrompt(String staffName) { StringBuilder sb = new StringBuilder(); + if (staffName != null && !staffName.isEmpty()) { + sb.append("你的名字是「").append(staffName).append("」,请用这个名字进行回复,不要编造其他名字。\n\n"); + } sb.append("请根据上述对话历史,生成 3 条不同风格的推荐回复话术。\n\n"); sb.append("风格要求(严格按顺序输出):\n"); sb.append("第1条 - 亲和风格:温暖亲切,像朋友聊天,拉近距离\n"); diff --git a/backend/generation-service/target/classes/application.yml b/backend/generation-service/target/classes/application.yml deleted file mode 100644 index 2c72491..0000000 --- a/backend/generation-service/target/classes/application.yml +++ /dev/null @@ -1,45 +0,0 @@ -server: - servlet: - encoding: - charset: UTF-8 - enabled: true - force: true - port: 8086 - -spring: - application: - name: generation-service - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false - username: ${MYSQL_USERNAME:root} - password: ${MYSQL_PASSWORD:root} - redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD:} - database: 0 - lettuce: - pool: - max-active: 8 - max-idle: 8 - min-idle: 0 - rabbitmq: - host: ${RABBITMQ_HOST:localhost} - port: ${RABBITMQ_PORT:5672} - username: ${RABBITMQ_USERNAME:admin} - password: ${RABBITMQ_PASSWORD:admin} - -llm: - qianwen: - api-key: sk-39ceb8d746014b349109e76f893acb18 - model: qwen-plus - -mybatis-plus: - configuration: - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 diff --git a/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class b/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class deleted file mode 100644 index 0877fb7..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/client/BailianClient.class b/backend/generation-service/target/classes/com/artedu/generation/client/BailianClient.class deleted file mode 100644 index 319082c..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/client/BailianClient.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/client/DoubaoClient.class b/backend/generation-service/target/classes/com/artedu/generation/client/DoubaoClient.class deleted file mode 100644 index 1156b84..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/client/DoubaoClient.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/client/HunyuanClient.class b/backend/generation-service/target/classes/com/artedu/generation/client/HunyuanClient.class deleted file mode 100644 index e8bb023..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/client/HunyuanClient.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/client/LLMClient.class b/backend/generation-service/target/classes/com/artedu/generation/client/LLMClient.class deleted file mode 100644 index e9d9c8d..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/client/LLMClient.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/client/LLMClientFactory.class b/backend/generation-service/target/classes/com/artedu/generation/client/LLMClientFactory.class deleted file mode 100644 index 0cc5f80..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/client/LLMClientFactory.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/controller/EmbedRequest.class b/backend/generation-service/target/classes/com/artedu/generation/controller/EmbedRequest.class deleted file mode 100644 index 3f768c9..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/controller/EmbedRequest.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/controller/GenerateRequest.class b/backend/generation-service/target/classes/com/artedu/generation/controller/GenerateRequest.class deleted file mode 100644 index be555bb..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/controller/GenerateRequest.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class b/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class deleted file mode 100644 index 23cd4cd..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/controller/LlmModelController.class b/backend/generation-service/target/classes/com/artedu/generation/controller/LlmModelController.class deleted file mode 100644 index 340c054..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/controller/LlmModelController.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/entity/LlmModel.class b/backend/generation-service/target/classes/com/artedu/generation/entity/LlmModel.class deleted file mode 100644 index feef090..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/entity/LlmModel.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/mapper/LlmModelMapper.class b/backend/generation-service/target/classes/com/artedu/generation/mapper/LlmModelMapper.class deleted file mode 100644 index 852b69f..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/mapper/LlmModelMapper.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/service/LlmModelService.class b/backend/generation-service/target/classes/com/artedu/generation/service/LlmModelService.class deleted file mode 100644 index 7b0fec2..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/service/LlmModelService.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class b/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class deleted file mode 100644 index 7e9d54c..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class and /dev/null differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class b/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class deleted file mode 100644 index 316a223..0000000 Binary files a/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class and /dev/null differ diff --git a/backend/generation-service/target/classes/db/migration/V20240603__add_llm_models.sql b/backend/generation-service/target/classes/db/migration/V20240603__add_llm_models.sql deleted file mode 100644 index 50e577f..0000000 --- a/backend/generation-service/target/classes/db/migration/V20240603__add_llm_models.sql +++ /dev/null @@ -1,24 +0,0 @@ --- 大模型配置表 -CREATE TABLE IF NOT EXISTS llm_models ( - id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', - provider VARCHAR(32) NOT NULL COMMENT '提供商: bailian(百炼), doubao(豆包), hunyuan(混元)', - model_name VARCHAR(64) NOT NULL COMMENT '模型显示名称', - model_code VARCHAR(64) NOT NULL COMMENT '模型代码(调用API时使用)', - api_key VARCHAR(255) COMMENT 'API密钥', - api_url VARCHAR(255) COMMENT 'API基础地址', - is_active TINYINT DEFAULT 1 COMMENT '是否启用: 1启用 0禁用', - is_default TINYINT DEFAULT 0 COMMENT '是否为默认模型: 1是 0否', - config_json TEXT COMMENT '额外配置JSON', - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY uk_provider_model (provider, model_code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='大模型配置表'; - --- 插入默认模型(百炼) -INSERT INTO llm_models (provider, model_name, model_code, api_key, api_url, is_active, is_default, config_json) VALUES -('bailian', '通义千问-Turbo', 'qwen-turbo', 'sk-39ceb8d746014b349109e76f893acb18', 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation', 1, 1, '{"maxTokens": 1500, "temperature": 0.7}'), -('bailian', '通义千问-Plus', 'qwen-plus', 'sk-39ceb8d746014b349109e76f893acb18', 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation', 1, 0, '{"maxTokens": 2000, "temperature": 0.7}'), -('doubao', '豆包-Pro', 'doubao-pro-32k', '', 'https://ark.cn-beijing.volces.com/api/v3/chat/completions', 1, 0, '{"maxTokens": 1500, "temperature": 0.7}'), -('hunyuan', '混元-Standard', 'hunyuan-standard', '', 'https://hunyuan.tencentcloudapi.com/', 0, 0, '{"maxTokens": 1500, "temperature": 0.7}') -ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP; diff --git a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar b/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar deleted file mode 100644 index 5e5987d..0000000 Binary files a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar and /dev/null differ diff --git a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar.original b/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar.original deleted file mode 100644 index 6857d42..0000000 Binary files a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar.original and /dev/null differ diff --git a/backend/generation-service/target/maven-archiver/pom.properties b/backend/generation-service/target/maven-archiver/pom.properties deleted file mode 100644 index b5f8c81..0000000 --- a/backend/generation-service/target/maven-archiver/pom.properties +++ /dev/null @@ -1,3 +0,0 @@ -artifactId=generation-service -groupId=com.artedu -version=1.0.0-SNAPSHOT diff --git a/backend/generation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend/generation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst deleted file mode 100644 index a245ba0..0000000 --- a/backend/generation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ /dev/null @@ -1,17 +0,0 @@ -com\artedu\generation\service\QianwenClient.class -com\artedu\generation\controller\GenerateRequest.class -com\artedu\generation\entity\ArchiveMessage.class -com\artedu\generation\service\LlmModelService.class -com\artedu\generation\client\DoubaoClient.class -com\artedu\generation\mapper\ArchiveMessageMapper.class -com\artedu\generation\client\LLMClientFactory.class -com\artedu\generation\controller\LlmModelController.class -com\artedu\generation\mapper\LlmModelMapper.class -com\artedu\generation\entity\LlmModel.class -com\artedu\generation\controller\GenerationController.class -com\artedu\generation\service\PromptEngine.class -com\artedu\generation\client\BailianClient.class -com\artedu\generation\client\LLMClient.class -com\artedu\generation\client\HunyuanClient.class -com\artedu\generation\GenerationServiceApplication.class -com\artedu\generation\controller\EmbedRequest.class diff --git a/backend/generation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend/generation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst deleted file mode 100644 index 40c5bbf..0000000 --- a/backend/generation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ /dev/null @@ -1,17 +0,0 @@ -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\client\BailianClient.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\client\DoubaoClient.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\client\HunyuanClient.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\client\LLMClient.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\client\LLMClientFactory.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\controller\EmbedRequest.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\controller\GenerateRequest.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\controller\GenerationController.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\controller\LlmModelController.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\entity\ArchiveMessage.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\entity\LlmModel.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\GenerationServiceApplication.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\mapper\ArchiveMessageMapper.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\mapper\LlmModelMapper.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\service\LlmModelService.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\service\PromptEngine.java -D:\www\agent_9art\backend\generation-service\src\main\java\com\artedu\generation\service\QianwenClient.java diff --git a/docker-compose-server.yml b/docker-compose-server.yml new file mode 100644 index 0000000..a0973af --- /dev/null +++ b/docker-compose-server.yml @@ -0,0 +1,338 @@ +version: '3.8' + +services: + # ---------------------- 基础设施 ---------------------- + mysql: + image: mysql:5.7 + container_name: ai-assistant-mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root} + MYSQL_DATABASE: ${MYSQL_DATABASE:-ai_assistant} + MYSQL_CHARACTER_SET_SERVER: utf8mb4 + MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + command: > + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_unicode_ci + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - ai-assistant-network + + redis: + image: redis:6-alpine + container_name: ai-assistant-redis + restart: always + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - ai-assistant-network + + rabbitmq: + image: rabbitmq:3-management-alpine + container_name: ai-assistant-rabbitmq + restart: always + ports: + - "5672:5672" + - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-admin} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-admin} + volumes: + - rabbitmq_data:/var/lib/rabbitmq + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - ai-assistant-network + + # ---------------------- 后端服务 ---------------------- + gateway: + build: ./backend/gateway + container_name: ai-assistant-gateway + restart: always + ports: + - "8080:8080" + volumes: + - ./gateway-application.yml:/app/config/application.yml + depends_on: + - auth-service + - archive-service + - conversation-service + - intent-service + - recommend-service + - generation-service + - kb-admin-service + - analytics-service + networks: + - ai-assistant-network + + auth-service: + build: ./backend/auth-service + container_name: ai-assistant-auth + restart: always + ports: + - "8081:8081" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - MYSQL_DATABASE=ai_assistant + - SPRING_DATASOURCE_USERNAME=assistant_prod + - SPRING_DATASOURCE_PASSWORD=U%$4Tu_C3+4 + - SPRING_DATASOURCE_URL=jdbc:mysql://sh-cdb-6fzlwnms.sql.tencentcdb.com:63912/ai_assistant?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false + - REDIS_HOST=redis + - JWT_SECRET=${JWT_SECRET:-your-256-bit-secret-key-here-at-least-32-characters} + depends_on: + - mysql + - redis + networks: + - ai-assistant-network + + archive-service: + build: ./backend/archive-service + container_name: ai-assistant-archive + restart: always + ports: + - "8082:8082" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - REDIS_HOST=redis + - RABBITMQ_HOST=rabbitmq + - WECOM_ARCHIVE_SECRET=${WECOM_ARCHIVE_SECRET:-} + - WECOM_ARCHIVE_CALLBACK_TOKEN=${WECOM_ARCHIVE_CALLBACK_TOKEN:-} + - WECOM_ARCHIVE_CALLBACK_AESKEY=${WECOM_ARCHIVE_CALLBACK_AESKEY:-} + - WECOM_ARCHIVE_SDK_PATH=${WECOM_ARCHIVE_SDK_PATH:-/app/sdk} + - ARCHIVE_REPAIR_ON_STARTUP=true + - WECOM_AGENT_SECRET=nU1xezyHyonS9rOJ9Kwo3i5OM9iuWc1TrjXf-rOYZx0 + - WECOM_CONTACT_SECRET=zrZkae10k1T7bqrt-jVYjj9LQ4GgMibj-wUMy48OsyY + volumes: + - ./keys:/app/keys + # 如需真实企微SDK,取消注释下行并将Linux .so文件放入 ./sdk/linux/ + - ./sdk/linux:/app/sdk + depends_on: + - mysql + - redis + - rabbitmq + networks: + - ai-assistant-network + + conversation-service: + build: ./backend/conversation-service + container_name: ai-assistant-conversation + restart: always + ports: + - "8083:8083" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - REDIS_HOST=redis + - RABBITMQ_HOST=rabbitmq + depends_on: + - mysql + - redis + - rabbitmq + networks: + - ai-assistant-network + + intent-service: + build: ./backend/intent-service + container_name: ai-assistant-intent + restart: always + ports: + - "8084:8084" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - REDIS_HOST=redis + - RABBITMQ_HOST=rabbitmq + depends_on: + - mysql + - redis + - rabbitmq + networks: + - ai-assistant-network + + recommend-service: + build: ./backend/recommendation-service + container_name: ai-assistant-recommend + restart: always + ports: + - "8085:8085" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - REDIS_HOST=redis + - RABBITMQ_HOST=rabbitmq + - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:-} + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_PORT=63912 + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_DATABASE=ai_assistant + depends_on: + - mysql + - redis + - rabbitmq + networks: + - ai-assistant-network + + generation-service: + build: ./backend/generation-service + container_name: ai-assistant-generation + restart: always + ports: + - "8086:8086" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - REDIS_HOST=redis + - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:-} + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_PORT=63912 + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_DATABASE=ai_assistant + depends_on: + - redis + networks: + - ai-assistant-network + + kb-admin-service: + build: ./backend/kb-admin-service + container_name: ai-assistant-kbadmin + restart: always + ports: + - "8087:8087" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - REDIS_HOST=redis + depends_on: + - mysql + - redis + networks: + - ai-assistant-network + + analytics-service: + build: ./backend/analytics-service + container_name: ai-assistant-analytics + restart: always + ports: + - "8088:8088" + environment: + - SERVER_SERVLET_ENCODING_CHARSET=UTF-8 + - SERVER_SERVLET_ENCODING_ENABLED=true + - SERVER_SERVLET_ENCODING_FORCE=true + - MYSQL_HOST=sh-cdb-6fzlwnms.sql.tencentcdb.com + - MYSQL_USERNAME=assistant_prod + - MYSQL_PASSWORD=U%$4Tu_C3+4 + - MYSQL_PORT=63912 + - REDIS_HOST=redis + depends_on: + - mysql + - redis + networks: + - ai-assistant-network + + # ---------------------- 前端服务 ---------------------- + admin: + build: ./frontend/admin + container_name: ai-assistant-admin + restart: always + ports: + - "5174:80" + depends_on: + - gateway + networks: + - ai-assistant-network + + sidebar: + build: ./frontend/sidebar + container_name: ai-assistant-sidebar + restart: always + ports: + - "5173:80" + depends_on: + - gateway + networks: + - ai-assistant-network + + # ---------------------- 统一入口 Nginx ---------------------- + nginx: + image: nginx:alpine + container_name: ai-assistant-nginx + restart: always + ports: + - "8089:80" + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf + depends_on: + - gateway + - admin + - sidebar + networks: + - ai-assistant-network + +# ---------------------- 数据卷 ---------------------- +volumes: + mysql_data: + redis_data: + rabbitmq_data: + +# ---------------------- 网络 ---------------------- +networks: + ai-assistant-network: + driver: bridge diff --git a/frontend/sidebar/src/components/ScriptRecommendStream.tsx b/frontend/sidebar/src/components/ScriptRecommendStream.tsx index 8a297af..02706fc 100644 --- a/frontend/sidebar/src/components/ScriptRecommendStream.tsx +++ b/frontend/sidebar/src/components/ScriptRecommendStream.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState, useCallback } from 'react' -import { Button, Toast, SpinLoading, Tag, Space } from 'antd-mobile' +import { Button, Toast, SpinLoading } from 'antd-mobile' interface StyleConfig { id: string @@ -31,8 +31,7 @@ const STYLES: StyleConfig[] = [ export default function ScriptRecommendStream({ userInfo, customerId: propCustomerId, chatContext }: Props) { const [loading, setLoading] = useState(true) const [generating, setGenerating] = useState(false) - const [customerMsg, setCustomerMsg] = useState('') - const [profileTags, setProfileTags] = useState([]) + const [, setProfileTags] = useState([]) const [utterances, setUtterances] = useState([]) const [error, setError] = useState('') const sseRef = useRef(null) @@ -123,7 +122,7 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom } } - // 构建 SSE URL:群聊传 roomid,单聊传 fromUser+toUser + // 构建 SSE URL:群聊传 roomid,单聊传 fromUser+toUser+staffName const params = new URLSearchParams() if (isGroupChat && chatId) { params.append('roomid', chatId) @@ -131,6 +130,9 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom params.append('fromUser', customerId) params.append('toUser', staffId) } + if (userInfo?.userName) { + params.append('staffName', userInfo.userName) + } const url = `/api/v1/generation/utterance-stream?${params.toString()}` console.log('SSE connect:', url) @@ -211,12 +213,20 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom const init = async () => { setLoading(true) try { - // 单聊获取最后一条消息用于展示(限定当前销售与学员的对话,避免串消息) - if (!isGroupChat && customerId && staffId) { - const res = await fetch(`/api/v1/conversations/last-message?customerId=${customerId}&staffId=${staffId}&corpId=${corpId}`) - const data = await res.json() - if (data.code === 0 && data.data?.content) { - setCustomerMsg(data.data.content) + // 单聊获取画像标签 + if (!isGroupChat && customerId) { + const profileRes = await fetch(`/api/v1/intent/profile/${encodeURIComponent(customerId)}?corpId=${corpId}`) + const profileData = await profileRes.json() + if (profileData.code === 0 && profileData.data) { + const p = profileData.data + const tags: string[] = [] + if (p.currentOccupation) tags.push(p.currentOccupation) + if (p.studentType) tags.push(p.studentType) + if (p.skillLevel) tags.push(p.skillLevel) + if (p.intentLevel) tags.push(p.intentLevel) + if (p.concernFocus) tags.push(p.concernFocus) + if (p.decisionStage) tags.push(p.decisionStage) + setProfileTags(tags) } } } catch (e) { @@ -247,30 +257,7 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom return (
- {/* 学员/群聊消息 + 标签 */} -
-
- 💬 - {isGroupChat ? '群聊消息' : '学员消息'}: - - {customerMsg || '暂无'} -
- {profileTags.length > 0 && ( - - {profileTags.map(tag => ( - {tag} - ))} - - )} -
- - {/* 生成中状态 */} + {/* 生成中状态 --> {generating && utterances.every(u => !u.isComplete) && (