package com.guo.learningprogresstracker.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* lpt-ai 独立 AI 服务客户端。
*
通过 {@code lpt.ai-service.url} 配置服务地址;未配置或调用失败时返回 empty,由调用方降级。
*
* 内部使用异步任务模式:提交任务到 lpt-ai 后轮询等待结果,
* 避免同步等待 LLM 响应时阻塞 HTTP 连接。
*
*/
@Slf4j
@Component
@ConfigurationProperties(prefix = "lpt.ai-service")
@Setter
public class AiServiceClient {
/** lpt-ai 服务地址,如 http://localhost:5199 */
private String url;
/** 超时秒数(等待 AI 任务完成的最长时间) */
private int timeoutSeconds = 600;
private HttpClient httpClient;
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
void init() {
httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
if (isConfigured()) {
log.info("AiServiceClient 已配置: {} (超时 {}s, 异步任务模式)", url, timeoutSeconds);
} else {
log.info("AiServiceClient 未配置,AI 功能不可用(将降级为内置逻辑)");
}
}
public boolean isConfigured() {
return url != null && !url.isBlank();
}
// ============ 公开 API(签名不变,内部改为 submit + poll)============
/**
* 调用 lpt-ai 将残片聚合为学习报告草稿。
*/
public Optional aggregateReport(String taskName, List fragments, String expectation) {
if (!isConfigured() || fragments == null || fragments.isEmpty()) {
return Optional.empty();
}
try {
List