feat(思维导图):AI 生成接入 lpt-ai 服务
RemoteAiMindMapClient 原为 TODO 空桩,依赖 lpt.ai.endpoint/api-key 配置,从未生效。现改为依赖 AiServiceClient(lpt.ai-service.url), 调用 lpt-ai 的 /ai/generate-mind-map 接口获取 AI 大纲。 变更: - AiServiceClient 新增 generateMindMap():调用 POST /ai/generate-mind-map - RemoteAiMindMapClient 重写:注入 AiServiceClient,isAvailable() 改为检查 AiServiceClient.isConfigured(),generate() 用 lpt-ai 返回的大纲文本解析为 MindMapNode 树 - StandardMindMapServiceImpl.doGenerate():优先选 AI 客户端 (非 BUILTIN),AI 失败时自动降级到内置规则引擎 - application.yml:移除不再使用的 lpt.ai.endpoint/api-key 配置
This commit is contained in:
+43
-35
@@ -6,55 +6,35 @@ import com.guo.learningprogresstracker.entity.TaskApplicationEntity;
|
||||
import com.guo.learningprogresstracker.entity.TaskEntity;
|
||||
import com.guo.learningprogresstracker.service.MindMapAiClient;
|
||||
import com.guo.learningprogresstracker.utils.MindMapNode;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Setter;
|
||||
import com.guo.learningprogresstracker.utils.MindMapTreeTool;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 远程 AI 思维导图生成客户端(预留实现)。
|
||||
* <p>通过 {@code lpt.ai.endpoint} / {@code lpt.ai.api-key} 配置;未配置时不可用,
|
||||
* 上层服务会回退到 {@link BuiltinMindMapGenerator}。</p>
|
||||
* 远程 AI 思维导图生成客户端。
|
||||
* <p>通过 lpt-ai 服务({@code lpt.ai-service.url})调用大语言模型生成思维导图。
|
||||
* 未配置 lpt-ai 时不可用,会回退到 {@link BuiltinMindMapGenerator}。</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "lpt.ai")
|
||||
@Setter
|
||||
@RequiredArgsConstructor
|
||||
public class RemoteAiMindMapClient implements MindMapAiClient {
|
||||
|
||||
/** AI 服务端点 */
|
||||
private String endpoint;
|
||||
/** API Key */
|
||||
private String apiKey;
|
||||
/** 模型名称 */
|
||||
private String model = "claude-fable-5";
|
||||
/** 超时秒数 */
|
||||
private int timeoutSeconds = 60;
|
||||
|
||||
private boolean enabled = false;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
enabled = apiKey != null && !apiKey.isBlank() && endpoint != null && !endpoint.isBlank();
|
||||
if (enabled) {
|
||||
log.info("RemoteAiMindMapClient 已启用: endpoint={}, model={}", endpoint, model);
|
||||
} else {
|
||||
log.info("RemoteAiMindMapClient 未配置,将使用内置生成器");
|
||||
}
|
||||
}
|
||||
private final AiServiceClient aiServiceClient;
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return enabled;
|
||||
return aiServiceClient.isConfigured();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generatorName() {
|
||||
return "AI-" + model;
|
||||
return "AI";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,12 +43,40 @@ public class RemoteAiMindMapClient implements MindMapAiClient {
|
||||
List<StudyReportFragmentsEntity> fragments,
|
||||
List<TaskApplicationEntity> applications,
|
||||
String clientHint) {
|
||||
if (!enabled) {
|
||||
if (!isAvailable()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// TODO: 调用远程 AI API,构造 prompt 发送 reports/fragments
|
||||
// RestClient.create().post().uri(endpoint).body(prompt).retrieve()
|
||||
log.warn("RemoteAiMindMapClient: 远程 AI 调用尚未实现,请配置 API Key 并实现请求逻辑");
|
||||
return Optional.empty();
|
||||
|
||||
// 提取报告和残片的内容文本
|
||||
List<String> reportTexts = reports.stream()
|
||||
.map(StudyReportsEntity::getContent)
|
||||
.filter(c -> c != null && !c.isBlank())
|
||||
.collect(Collectors.toList());
|
||||
List<String> fragmentTexts = fragments.stream()
|
||||
.map(StudyReportFragmentsEntity::getContent)
|
||||
.filter(c -> c != null && !c.isBlank())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (reportTexts.isEmpty() && fragmentTexts.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<String> optOutline = aiServiceClient.generateMindMap(
|
||||
task.getTaskName(),
|
||||
task.getTaskDescription(),
|
||||
reportTexts,
|
||||
fragmentTexts
|
||||
);
|
||||
|
||||
if (optOutline.isEmpty() || optOutline.get().isBlank()) {
|
||||
log.warn("RemoteAiMindMapClient: lpt-ai 返回空大纲,降级");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String outline = optOutline.get();
|
||||
MindMapNode root = MindMapTreeTool.parseOutline(outline);
|
||||
log.info("RemoteAiMindMapClient: 为任务[{}]生成 AI 导图,共 {} 个节点,{} 层",
|
||||
task.getTaskNum(), MindMapTreeTool.countNodes(root), MindMapTreeTool.maxDepth(root));
|
||||
return Optional.of(root);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user