|
|
@@ -15,9 +15,12 @@ import com.etotem.cfc.service.UserService;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDate;
|
|
|
import java.util.*;
|
|
|
import java.util.regex.Matcher;
|
|
|
import java.util.regex.Pattern;
|
|
|
@@ -68,6 +71,9 @@ public class AIChatController {
|
|
|
@Resource
|
|
|
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private RedisTemplate<String, Object> redisTemplate;
|
|
|
+
|
|
|
@Operation(summary = "发送聊天消息(支持传入reportId以解读报告)")
|
|
|
@PostMapping("/chat/send")
|
|
|
public Result<Map<String, Object>> sendMessage(
|
|
|
@@ -75,14 +81,64 @@ public class AIChatController {
|
|
|
@RequestBody Map<String, String> params) {
|
|
|
String query = params.get("query");
|
|
|
String conversationId = params.get("conversationId");
|
|
|
- String reportIdStr = params.get("reportId");
|
|
|
- String surveyIdStr = params.get("surveyId");
|
|
|
- String selfCheckIdStr = params.get("selfCheckId");
|
|
|
|
|
|
if (query == null || query.trim().isEmpty()) {
|
|
|
return Result.error("消息不能为空");
|
|
|
}
|
|
|
|
|
|
+ // 复用上下文组装(mascot + 家庭/报告 + 画像 + 自检 + 记忆层)
|
|
|
+ Map<String, Object> inputs = buildChatInputs(userId, params);
|
|
|
+
|
|
|
+ Map<String, Object> difyResp = aiService.sendMessage(
|
|
|
+ query, String.valueOf(userId),
|
|
|
+ conversationId, inputs);
|
|
|
+
|
|
|
+ String answer = (String) difyResp.getOrDefault("answer", "");
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("answer", answer);
|
|
|
+ result.put("conversationId", difyResp.getOrDefault("conversationId", ""));
|
|
|
+
|
|
|
+ // Parse [TASK] markers and create task records
|
|
|
+ List<Map<String, Object>> tasks = new ArrayList<>();
|
|
|
+ List<TaskParseResult> parsedTasks = taskParseService.parseTasks(answer);
|
|
|
+ String cleanAnswer = taskParseService.stripTaskMarkers(answer);
|
|
|
+
|
|
|
+ for (TaskParseResult taskResult : parsedTasks) {
|
|
|
+ com.etotem.cfc.entity.GrowthTask task = growthTaskService.createDynamicTask(
|
|
|
+ userId,
|
|
|
+ taskResult.getTitle(),
|
|
|
+ taskResult.getDescription(),
|
|
|
+ taskResult.getDimension(),
|
|
|
+ taskResult.getRewardPoints(),
|
|
|
+ (String) difyResp.getOrDefault("conversationId", "")
|
|
|
+ );
|
|
|
+ Map<String, Object> taskInfo = new LinkedHashMap<>();
|
|
|
+ taskInfo.put("id", task.getId());
|
|
|
+ taskInfo.put("title", task.getTitle());
|
|
|
+ taskInfo.put("description", task.getDescription());
|
|
|
+ taskInfo.put("dimension", task.getDimension());
|
|
|
+ taskInfo.put("rewardPoints", task.getRewardPoints());
|
|
|
+ tasks.add(taskInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("answer", cleanAnswer);
|
|
|
+ result.put("tasks", tasks);
|
|
|
+
|
|
|
+ try { growthTaskService.updateProgress(userId, "DAILY_AI", 1); } catch (Exception e) { log.warn("成长任务AI对话进度更新失败: userId={}, error={}", userId, e.getMessage()); }
|
|
|
+
|
|
|
+ return Result.success(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装 AI 对话上下文(mascot + 家庭/报告 + 画像 + 自检 + 记忆层)
|
|
|
+ * 供 /chat/send 和 /report/analyze 共用
|
|
|
+ */
|
|
|
+ private Map<String, Object> buildChatInputs(Long userId, Map<String, String> params) {
|
|
|
+ String conversationId = params.get("conversationId");
|
|
|
+ String reportIdStr = params.get("reportId");
|
|
|
+ String surveyIdStr = params.get("surveyId");
|
|
|
+ String selfCheckIdStr = params.get("selfCheckId");
|
|
|
+
|
|
|
// 获取用户mascot设置
|
|
|
com.etotem.cfc.entity.User user = userService.getUserInfo(userId);
|
|
|
String mascotCode = user != null ? user.getMascot() : null;
|
|
|
@@ -147,44 +203,64 @@ public class AIChatController {
|
|
|
|
|
|
// AI记忆层注入(会话摘要+关键事实)
|
|
|
inputs = aiService.enrichInputsWithMemory(userId, conversationId, inputs);
|
|
|
+ return inputs;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Operation(summary = "报告快速分析(免费版每日1次,付费版多轮)")
|
|
|
+ @PostMapping("/report/analyze")
|
|
|
+ public Result<Map<String, Object>> reportAnalyze(
|
|
|
+ @RequestAttribute("userId") Long userId,
|
|
|
+ @RequestBody Map<String, String> params) {
|
|
|
+ String query = params.get("query");
|
|
|
+ String reportIdStr = params.get("reportId");
|
|
|
+ if (query == null || query.trim().isEmpty()) {
|
|
|
+ return Result.error("消息不能为空");
|
|
|
+ }
|
|
|
+ if (reportIdStr == null || reportIdStr.trim().isEmpty()) {
|
|
|
+ return Result.error("报告ID不能为空");
|
|
|
+ }
|
|
|
+ Long reportId = Long.valueOf(reportIdStr);
|
|
|
+
|
|
|
+ // 1. 会员级别检查
|
|
|
+ com.etotem.cfc.entity.User user = userService.getUserInfo(userId);
|
|
|
+ String memberLevel = user != null ? user.getMemberLevel() : "FREE";
|
|
|
+ boolean isFree = memberLevel == null || "FREE".equals(memberLevel);
|
|
|
+
|
|
|
+ // 2. 免费版 Redis 计数(每日 1 次;Redis 异常时 fail-open 放行)
|
|
|
+ if (isFree) {
|
|
|
+ try {
|
|
|
+ String key = "report:analyze:free:" + userId + ":" + LocalDate.now();
|
|
|
+ Long count = redisTemplate.opsForValue().increment(key);
|
|
|
+ if (count != null && count == 1L) {
|
|
|
+ redisTemplate.expire(key, Duration.ofDays(1));
|
|
|
+ }
|
|
|
+ if (count != null && count > 1L) {
|
|
|
+ return Result.error("免费版每日仅限 1 次快速分析,升级会员解锁更多");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("Redis 计数失败,放行: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 组装上下文(免费版不带 conversationId → LangGraph 每次新建会话,锁死多轮)
|
|
|
+ if (isFree) {
|
|
|
+ params.remove("conversationId");
|
|
|
+ }
|
|
|
+ Map<String, Object> inputs = buildChatInputs(userId, params);
|
|
|
|
|
|
+ // 4. 调 LangGraph(经 aiService.sendMessage 含记忆层+镜像)
|
|
|
Map<String, Object> difyResp = aiService.sendMessage(
|
|
|
query, String.valueOf(userId),
|
|
|
- conversationId, inputs);
|
|
|
+ params.get("conversationId"), inputs);
|
|
|
|
|
|
+ if (difyResp == null || difyResp.isEmpty()) {
|
|
|
+ return Result.error("AI 服务暂不可用,请稍后重试");
|
|
|
+ }
|
|
|
String answer = (String) difyResp.getOrDefault("answer", "");
|
|
|
- Map<String, Object> result = new LinkedHashMap<>();
|
|
|
- result.put("answer", answer);
|
|
|
- result.put("conversationId", difyResp.getOrDefault("conversationId", ""));
|
|
|
-
|
|
|
- // Parse [TASK] markers and create task records
|
|
|
- List<Map<String, Object>> tasks = new ArrayList<>();
|
|
|
- List<TaskParseResult> parsedTasks = taskParseService.parseTasks(answer);
|
|
|
String cleanAnswer = taskParseService.stripTaskMarkers(answer);
|
|
|
-
|
|
|
- for (TaskParseResult taskResult : parsedTasks) {
|
|
|
- com.etotem.cfc.entity.GrowthTask task = growthTaskService.createDynamicTask(
|
|
|
- userId,
|
|
|
- taskResult.getTitle(),
|
|
|
- taskResult.getDescription(),
|
|
|
- taskResult.getDimension(),
|
|
|
- taskResult.getRewardPoints(),
|
|
|
- (String) difyResp.getOrDefault("conversationId", "")
|
|
|
- );
|
|
|
- Map<String, Object> taskInfo = new LinkedHashMap<>();
|
|
|
- taskInfo.put("id", task.getId());
|
|
|
- taskInfo.put("title", task.getTitle());
|
|
|
- taskInfo.put("description", task.getDescription());
|
|
|
- taskInfo.put("dimension", task.getDimension());
|
|
|
- taskInfo.put("rewardPoints", task.getRewardPoints());
|
|
|
- tasks.add(taskInfo);
|
|
|
- }
|
|
|
-
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
result.put("answer", cleanAnswer);
|
|
|
- result.put("tasks", tasks);
|
|
|
-
|
|
|
- try { growthTaskService.updateProgress(userId, "DAILY_AI", 1); } catch (Exception e) { log.warn("成长任务AI对话进度更新失败: userId={}, error={}", userId, e.getMessage()); }
|
|
|
-
|
|
|
+ result.put("conversationId", difyResp.getOrDefault("conversationId", ""));
|
|
|
return Result.success(result);
|
|
|
}
|
|
|
|