合并远程master,解决ArchiveMessageController冲突,清理target编译文件
This commit is contained in:
commit
926c8a828f
1
backend/archive-service/.gitignore
vendored
1
backend/archive-service/.gitignore
vendored
@ -1,2 +1 @@
|
||||
target/
|
||||
target
|
||||
@ -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<String, Object> detail = weComApiClient.getExternalContact(uid);
|
||||
log.info("getExternalContact 返回: uid={}, detail={}", uid, detail);
|
||||
if (detail != null && !detail.isEmpty()) {
|
||||
Map<String, Object> contact = (Map<String, Object>) 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<Customer>()
|
||||
.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,11 +112,12 @@ public class GenerationController {
|
||||
public Result<Map<String, Object>> 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);
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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;
|
||||
Binary file not shown.
Binary file not shown.
@ -1,3 +0,0 @@
|
||||
artifactId=generation-service
|
||||
groupId=com.artedu
|
||||
version=1.0.0-SNAPSHOT
|
||||
@ -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
|
||||
@ -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
|
||||
338
docker-compose-server.yml
Normal file
338
docker-compose-server.yml
Normal file
@ -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
|
||||
@ -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<string[]>([])
|
||||
const [, setProfileTags] = useState<string[]>([])
|
||||
const [utterances, setUtterances] = useState<UtteranceItem[]>([])
|
||||
const [error, setError] = useState('')
|
||||
const sseRef = useRef<EventSource | null>(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 (
|
||||
<div style={{ padding: 12, background: '#f5f5f5', minHeight: '100vh' }}>
|
||||
{/* 学员/群聊消息 + 标签 */}
|
||||
<div style={{
|
||||
background: '#fff',
|
||||
borderRadius: 12,
|
||||
padding: '12px 16px',
|
||||
marginBottom: 12,
|
||||
boxShadow: '0 1px 4px rgba(0,0,0,0.06)'
|
||||
}}>
|
||||
<div style={{ fontSize: 13, color: '#666', marginBottom: 8 }}>
|
||||
💬 <span style={{ color: '#999' }}>
|
||||
{isGroupChat ? '群聊消息' : '学员消息'}:
|
||||
</span>
|
||||
<span style={{ color: '#333', fontWeight: 500 }}>{customerMsg || '暂无'}</span>
|
||||
</div>
|
||||
{profileTags.length > 0 && (
|
||||
<Space wrap>
|
||||
{profileTags.map(tag => (
|
||||
<Tag key={tag} color='primary' fill='outline' style={{ fontSize: 12 }}>{tag}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 生成中状态 */}
|
||||
{/* 生成中状态 -->
|
||||
{generating && utterances.every(u => !u.isComplete) && (
|
||||
<div style={{
|
||||
background: '#fff',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user