diff --git a/src/main/java/com/guo/learningprogresstracker/entity/ReviewStandardMindMapEntity.java b/src/main/java/com/guo/learningprogresstracker/entity/ReviewStandardMindMapEntity.java index 18c2702..d3ae711 100644 --- a/src/main/java/com/guo/learningprogresstracker/entity/ReviewStandardMindMapEntity.java +++ b/src/main/java/com/guo/learningprogresstracker/entity/ReviewStandardMindMapEntity.java @@ -10,7 +10,7 @@ import java.io.Serializable; import java.time.LocalDateTime; /** - * 任务标准思维导图:由内置规则或 AI 从学习报告/残片生成,用户可修改 + * 任务标准思维导图:由内置规则或 AI 从学习报告生成,用户可修改 */ @TableName(value = "review_standard_mind_maps") @Data diff --git a/src/main/java/com/guo/learningprogresstracker/service/MindMapAiClient.java b/src/main/java/com/guo/learningprogresstracker/service/MindMapAiClient.java index dd8c25e..032324c 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/MindMapAiClient.java +++ b/src/main/java/com/guo/learningprogresstracker/service/MindMapAiClient.java @@ -1,6 +1,5 @@ package com.guo.learningprogresstracker.service; -import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.TaskApplicationEntity; import com.guo.learningprogresstracker.entity.TaskEntity; @@ -25,15 +24,13 @@ public interface MindMapAiClient { * 根据学习数据生成标准思维导图根节点 * * @param task 学习任务 - * @param reports 该任务的全部学习报告 - * @param fragments 该任务的全部学习残片 + * @param reports 该任务的全部学习报告 * @param applications 该任务的应用场景(可选) * @param clientHint 前端已有的大纲文本(可选,用于 AI 续写而非全量生成) * @return 标准思维导图的根节点;若无可生成数据则返回 {@link Optional#empty()} */ Optional generate(TaskEntity task, List reports, - List fragments, List applications, String clientHint); diff --git a/src/main/java/com/guo/learningprogresstracker/service/impl/AiServiceClient.java b/src/main/java/com/guo/learningprogresstracker/service/impl/AiServiceClient.java index 7f2dd74..433fb90 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/impl/AiServiceClient.java +++ b/src/main/java/com/guo/learningprogresstracker/service/impl/AiServiceClient.java @@ -82,10 +82,10 @@ public class AiServiceClient { } /** - * 调用 lpt-ai 从学习数据生成思维导图大纲。 + * 调用 lpt-ai 从学习报告生成思维导图大纲。 */ public Optional generateMindMap(String taskName, String taskDescription, - List reports, List fragments) { + List reports) { if (!isConfigured()) { return Optional.empty(); } @@ -93,8 +93,7 @@ public class AiServiceClient { Map params = Map.of( "taskName", taskName, "taskDescription", taskDescription != null ? taskDescription : "", - "reports", reports != null ? reports : List.of(), - "fragments", fragments != null ? fragments : List.of() + "reports", reports != null ? reports : List.of() ); Optional result = submitAndWait("generate-mind-map", params); diff --git a/src/main/java/com/guo/learningprogresstracker/service/impl/BuiltinMindMapGenerator.java b/src/main/java/com/guo/learningprogresstracker/service/impl/BuiltinMindMapGenerator.java index e6be4a9..a0c030a 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/impl/BuiltinMindMapGenerator.java +++ b/src/main/java/com/guo/learningprogresstracker/service/impl/BuiltinMindMapGenerator.java @@ -1,6 +1,5 @@ package com.guo.learningprogresstracker.service.impl; -import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.TaskApplicationEntity; import com.guo.learningprogresstracker.entity.TaskEntity; @@ -16,12 +15,12 @@ import java.util.*; import java.util.stream.Collectors; /** - * 内置规则引擎:从学习报告和残片提取关键词并组织为树形思维导图。 + * 内置规则引擎:从学习报告提取内容并组织为树形思维导图。 *

规则策略:

*
    *
  1. 根节点 = 任务名称
  2. *
  3. 一级分支 = 按会话(日期+报告摘要)分组
  4. - *
  5. 二级分支 = 该会话下的残片标题
  6. + *
  7. 二级分支 = 该会话下的报告
  8. *
  9. 附加分支"应用场景" = 任务应用场景(如有)
  10. *
  11. 去重:标准化后标题对比,合并内容相似的节点
  12. *
  13. 引用追溯:每个节点携带 sourceType / sourceId
  14. @@ -46,10 +45,9 @@ public class BuiltinMindMapGenerator implements MindMapAiClient { @Override public Optional generate(TaskEntity task, List reports, - List fragments, List applications, String clientHint) { - if (reports.isEmpty() && fragments.isEmpty()) { + if (reports == null || reports.isEmpty()) { return Optional.empty(); } @@ -58,34 +56,21 @@ public class BuiltinMindMapGenerator implements MindMapAiClient { // 按 session 分组 Map> reportsBySession = reports.stream() + .filter(r -> r.getSessionNum() != null) .collect(Collectors.groupingBy(StudyReportsEntity::getSessionNum)); - Map> fragmentsBySession = fragments.stream() - .collect(Collectors.groupingBy(StudyReportFragmentsEntity::getSessionNum)); - // 合并所有 session - Set allSessions = new LinkedHashSet<>(); - allSessions.addAll(reportsBySession.keySet()); - allSessions.addAll(fragmentsBySession.keySet()); + for (Map.Entry> entry : reportsBySession.entrySet()) { + List sessReports = entry.getValue(); - for (String sessionNum : allSessions) { - List sessReports = reportsBySession.getOrDefault(sessionNum, List.of()); - List sessFragments = fragmentsBySession.getOrDefault(sessionNum, List.of()); - - // 会话分支标题:取第一条报告的前 60 字作为摘要,或直接写"学习记录" - String sessionTitle; - if (!sessReports.isEmpty()) { - String firstReport = sessReports.get(0).getContent(); - sessionTitle = truncate(firstReport, MAX_TITLE_LENGTH); - if (sessReports.get(0).getCreatedTime() != null) { - sessionTitle = formatDate(sessReports.get(0).getCreatedTime()) + " " + sessionTitle; - } - } else { - sessionTitle = "学习记录 " + (sessFragments.isEmpty() ? "" : formatDate(sessFragments.get(0).getCreatedTime())); + // 会话分支标题:取第一条报告的前 60 字作为摘要 + StudyReportsEntity firstReport = sessReports.get(0); + String sessionTitle = truncate(firstReport.getContent(), MAX_TITLE_LENGTH); + if (firstReport.getCreatedTime() != null) { + sessionTitle = formatDate(firstReport.getCreatedTime()) + " " + sessionTitle; } MindMapNode sessionNode = new MindMapNode(sessionTitle); - // 报告作为子节点 for (StudyReportsEntity report : sessReports) { String content = report.getContent(); if (content == null || content.isBlank()) continue; @@ -96,17 +81,6 @@ public class BuiltinMindMapGenerator implements MindMapAiClient { sessionNode.getChildren().add(reportNode); } - // 残片作为子节点 - for (StudyReportFragmentsEntity frag : sessFragments) { - String content = frag.getContent(); - if (content == null || content.isBlank()) continue; - MindMapNode fragNode = new MindMapNode(truncate(content, MAX_TITLE_LENGTH)); - fragNode.setNotes(content); - fragNode.setSourceType("FRAGMENT"); - fragNode.setSourceId(frag.getId()); - sessionNode.getChildren().add(fragNode); - } - root.getChildren().add(sessionNode); } diff --git a/src/main/java/com/guo/learningprogresstracker/service/impl/RemoteAiMindMapClient.java b/src/main/java/com/guo/learningprogresstracker/service/impl/RemoteAiMindMapClient.java index 9de5dc9..20b92a5 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/impl/RemoteAiMindMapClient.java +++ b/src/main/java/com/guo/learningprogresstracker/service/impl/RemoteAiMindMapClient.java @@ -1,6 +1,5 @@ package com.guo.learningprogresstracker.service.impl; -import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.TaskApplicationEntity; import com.guo.learningprogresstracker.entity.TaskEntity; @@ -40,32 +39,26 @@ public class RemoteAiMindMapClient implements MindMapAiClient { @Override public Optional generate(TaskEntity task, List reports, - List fragments, List applications, String clientHint) { if (!isAvailable()) { return Optional.empty(); } - // 提取报告和残片的内容文本 + // 提取学习报告的内容文本 List reportTexts = reports.stream() .map(StudyReportsEntity::getContent) .filter(c -> c != null && !c.isBlank()) .collect(Collectors.toList()); - List fragmentTexts = fragments.stream() - .map(StudyReportFragmentsEntity::getContent) - .filter(c -> c != null && !c.isBlank()) - .collect(Collectors.toList()); - if (reportTexts.isEmpty() && fragmentTexts.isEmpty()) { + if (reportTexts.isEmpty()) { return Optional.empty(); } Optional optOutline = aiServiceClient.generateMindMap( task.getTaskName(), task.getTaskDescription(), - reportTexts, - fragmentTexts + reportTexts ); if (optOutline.isEmpty() || optOutline.get().isBlank()) { diff --git a/src/main/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImpl.java b/src/main/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImpl.java index ae9fdc9..acf8157 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImpl.java +++ b/src/main/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImpl.java @@ -36,7 +36,6 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { private final ReviewRecallRecordMapper recallRecordMapper; private final TasksMapper tasksMapper; private final StudyReportsMapper studyReportsMapper; - private final StudyReportFragmentsMapper studyReportFragmentsMapper; private final TaskApplicationMapper taskApplicationMapper; private final StudySessionsMapper studySessionsMapper; @@ -86,7 +85,8 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { ensureTaskExists(taskNum); ReviewStandardMindMapEntity existing = queryByTaskNum(taskNum); if (existing == null) { - throw new NotFindEntitiesException("标准思维导图尚不存在,无法增量更新"); + log.warn("任务[{}]标准思维导图尚不存在,无法增量更新", taskNum); + throw new NotFindEntitiesException("还没有生成过标准思维导图,请先完整生成一次哦"); } // 防并发生成 AtomicBoolean lock = generatingTasks.computeIfAbsent(taskNum, k -> new AtomicBoolean(false)); @@ -201,7 +201,8 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { try { resultJson = objectMapper.writeValueAsString(result); } catch (Exception e) { - throw new OperationFailedException("对比结果序列化失败"); + log.error("任务[{}]对比结果序列化失败", taskNum, e); + throw new OperationFailedException("对比结果解析失败了,请稍后再试"); } // 5. 保存回忆记录 @@ -405,7 +406,10 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { @Override public ReviewRecallRecordEntity getRecallRecord(Integer recordId) throws NotFindEntitiesException { return Optional.ofNullable(recallRecordMapper.selectById(recordId)) - .orElseThrow(() -> new NotFindEntitiesException("回忆记录[" + recordId + "]不存在")); + .orElseThrow(() -> { + log.warn("回忆记录[{}]不存在", recordId); + return new NotFindEntitiesException("这条回忆记录不存在或已被删除"); + }); } // ============ 内部方法 ============ @@ -415,7 +419,8 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { Wrappers.lambdaQuery().eq(TaskEntity::getTaskNum, taskNum).last("LIMIT 1")); if (task == null) { - throw new OperationFailedException("任务[" + taskNum + "]不存在"); + log.warn("任务[{}]不存在,无法生成思维导图", taskNum); + throw new OperationFailedException("这个任务不存在或已被删除"); } // 收集学习数据 @@ -428,14 +433,12 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { List reports = sessionNums.isEmpty() ? List.of() : studyReportsMapper.selectList(Wrappers.lambdaQuery() .in(StudyReportsEntity::getSessionNum, sessionNums)); - List fragments = sessionNums.isEmpty() ? List.of() - : studyReportFragmentsMapper.selectList(Wrappers.lambdaQuery() - .in(StudyReportFragmentsEntity::getSessionNum, sessionNums)); List applications = taskApplicationMapper.selectList( Wrappers.lambdaQuery().eq(TaskApplicationEntity::getTaskNum, taskNum)); - if (reports.isEmpty() && fragments.isEmpty()) { - throw new OperationFailedException("任务[" + taskNum + "]没有学习报告或残片,无法生成思维导图"); + if (reports.isEmpty()) { + log.warn("任务[{}]没有学习报告,无法生成思维导图", taskNum); + throw new OperationFailedException("这个任务还没开始学习哦,学习后产生学习报告后再来吧"); } // 优先选 AI 客户端(非 BUILTIN),其次内置生成器 @@ -445,10 +448,11 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { .orElse(null); if (client == null) { - throw new OperationFailedException("没有可用的思维导图生成器"); + log.warn("任务[{}]没有可用的思维导图生成器", taskNum); + throw new OperationFailedException("思维导图暂时生成不了,请稍后再试"); } - Optional optRoot = client.generate(task, reports, fragments, applications, null); + Optional optRoot = client.generate(task, reports, applications, null); // AI 生成失败时尝试降级到内置生成器 if (optRoot.isEmpty() && !"BUILTIN".equals(client.generatorName())) { log.info("AI 思维导图生成失败,降级到内置生成器"); @@ -456,11 +460,12 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { .filter(c -> "BUILTIN".equals(c.generatorName()) && c.isAvailable()) .findFirst().orElse(null); if (fallback != null) { - optRoot = fallback.generate(task, reports, fragments, applications, null); + optRoot = fallback.generate(task, reports, applications, null); } } if (optRoot.isEmpty()) { - throw new OperationFailedException("思维导图生成失败"); + log.warn("任务[{}]思维导图生成失败,已尝试全部生成器", taskNum); + throw new OperationFailedException("思维导图生成失败了,请稍后再试"); } MindMapNode root = optRoot.get(); @@ -483,7 +488,7 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { entity.setGenerator(client.generatorName()); entity.setGeneratorVersion("1.0"); entity.setSourceReportCount(reports.size()); - entity.setSourceFragmentCount(fragments.size()); + entity.setSourceFragmentCount(0); entity.setGeneratedTime(LocalDateTime.now()); if (create) { @@ -497,7 +502,8 @@ public class StandardMindMapServiceImpl implements StandardMindMapService { private void ensureTaskExists(String taskNum) throws NotFindEntitiesException { if (!StringUtils.hasText(taskNum) || !tasksMapper.exists( Wrappers.lambdaQuery().eq(TaskEntity::getTaskNum, taskNum))) { - throw new NotFindEntitiesException("任务[" + taskNum + "]不存在"); + log.warn("任务[{}]不存在", taskNum); + throw new NotFindEntitiesException("这个任务不存在或已被删除"); } } diff --git a/src/test/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImplTest.java b/src/test/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImplTest.java index 58cbcaf..8548442 100644 --- a/src/test/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImplTest.java +++ b/src/test/java/com/guo/learningprogresstracker/service/impl/StandardMindMapServiceImplTest.java @@ -36,8 +36,6 @@ class StandardMindMapServiceImplTest { @Mock private StudyReportsMapper studyReportsMapper; @Mock - private StudyReportFragmentsMapper studyReportFragmentsMapper; - @Mock private TaskApplicationMapper taskApplicationMapper; @Mock private StudySessionsMapper studySessionsMapper; @@ -189,7 +187,7 @@ class StandardMindMapServiceImplTest { private StandardMindMapServiceImpl createService() { StandardMindMapServiceImpl s = new StandardMindMapServiceImpl( standardMindMapMapper, recallRecordMapper, - tasksMapper, studyReportsMapper, studyReportFragmentsMapper, + tasksMapper, studyReportsMapper, taskApplicationMapper, studySessionsMapper, List.of(mockAiClient), objectMapper, aiServiceClient); return s;