report
This commit is contained in:
@@ -75,7 +75,7 @@ public class ReportController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<byte[]> generateResearchReport(
|
public ResponseEntity<ApiResponse<ReportGenerationResponse>> generateResearchReport(
|
||||||
@Validated @RequestBody ResearchReportRequest request) {
|
@Validated @RequestBody ResearchReportRequest request) {
|
||||||
// Валидация входных данных
|
// Валидация входных данных
|
||||||
if (request.getQuery() == null || request.getQuery().trim().isEmpty()) {
|
if (request.getQuery() == null || request.getQuery().trim().isEmpty()) {
|
||||||
@@ -90,33 +90,63 @@ public class ReportController {
|
|||||||
request.getBreadth(),
|
request.getBreadth(),
|
||||||
request.getReportType());
|
request.getReportType());
|
||||||
|
|
||||||
// Синхронно выполняем: deep-research -> synthesis via Ollama -> Markdown
|
// Генерация уникального ID задачи
|
||||||
DeepResearchResponse researchResponse = deepResearchService.generateReportAsync(deepRequest)
|
String taskId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
// Предполагаемое время завершения
|
||||||
|
LocalDateTime estimatedCompletion = LocalDateTime.now().plusMinutes(8);
|
||||||
|
|
||||||
|
// Асинхронный процесс: deep-research -> synthesis via Ollama -> Markdown ->
|
||||||
|
// сохранение
|
||||||
|
deepResearchService.generateReportAsync(deepRequest)
|
||||||
.publishOn(Schedulers.boundedElastic())
|
.publishOn(Schedulers.boundedElastic())
|
||||||
.block();
|
.flatMap(researchResponse -> {
|
||||||
if (researchResponse == null || researchResponse.getLearnings() == null
|
if (researchResponse == null
|
||||||
|| researchResponse.getLearnings().isEmpty()) {
|
|| researchResponse.getLearnings() == null
|
||||||
return ResponseEntity.internalServerError().build();
|
|| researchResponse.getLearnings().isEmpty()) {
|
||||||
}
|
return reactor.core.publisher.Mono.error(
|
||||||
|
new RuntimeException(
|
||||||
|
"Empty research response content"));
|
||||||
|
}
|
||||||
|
return reportSynthesisService
|
||||||
|
.synthesizeReport(request.getQuery(),
|
||||||
|
researchResponse.getLearnings(),
|
||||||
|
request.getLang())
|
||||||
|
.map(synthesizedMarkdown -> new Object[] { researchResponse,
|
||||||
|
synthesizedMarkdown });
|
||||||
|
})
|
||||||
|
.map(tuple -> {
|
||||||
|
DeepResearchResponse researchResponse = (DeepResearchResponse) tuple[0];
|
||||||
|
String synthesizedMarkdown = (String) tuple[1];
|
||||||
|
String finalMarkdown = buildMarkdownWithSources(synthesizedMarkdown,
|
||||||
|
researchResponse.getVisitedUrls());
|
||||||
|
byte[] mdBytes = finalMarkdown
|
||||||
|
.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
String filename = "research_report_" + System.currentTimeMillis() + ".md";
|
||||||
|
saveResearchMarkdownToHistory(request, filename, mdBytes, researchResponse);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.doOnError(e -> {
|
||||||
|
System.err.println("Error generating research report async: " + e.getMessage());
|
||||||
|
})
|
||||||
|
.subscribe(
|
||||||
|
ok -> {
|
||||||
|
},
|
||||||
|
err -> {
|
||||||
|
// Avoid onErrorDropped by consuming error here
|
||||||
|
System.err.println("Async pipeline error: " + err.getMessage());
|
||||||
|
});
|
||||||
|
|
||||||
String synthesizedMarkdown = reportSynthesisService
|
ReportGenerationResponse response = new ReportGenerationResponse(
|
||||||
.synthesizeReport(request.getQuery(), researchResponse.getLearnings(),
|
taskId,
|
||||||
request.getLang())
|
"Исследование запущено в фоновом режиме. Ожидаемое время завершения: "
|
||||||
.publishOn(Schedulers.boundedElastic())
|
+ estimatedCompletion
|
||||||
.block();
|
.format(java.time.format.DateTimeFormatter.ofPattern(
|
||||||
if (synthesizedMarkdown == null) {
|
"HH:mm")),
|
||||||
return ResponseEntity.internalServerError().build();
|
estimatedCompletion,
|
||||||
}
|
"PROCESSING");
|
||||||
|
|
||||||
String finalMarkdown = buildMarkdownWithSources(synthesizedMarkdown, researchResponse.getVisitedUrls());
|
return ResponseEntity.ok(ApiResponse.success("Исследовательский отчёт поставлен в очередь", response));
|
||||||
byte[] bytes = finalMarkdown.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
String filename = "research_report.md";
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
|
||||||
"attachment; filename=\"" + filename + "\"")
|
|
||||||
.contentType(MediaType.parseMediaType("text/markdown; charset=UTF-8"))
|
|
||||||
.body(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildMarkdownWithSources(String synthesizedMarkdown, java.util.List<String> visitedUrls) {
|
private String buildMarkdownWithSources(String synthesizedMarkdown, java.util.List<String> visitedUrls) {
|
||||||
@@ -164,6 +194,32 @@ public class ReportController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveResearchMarkdownToHistory(ResearchReportRequest request, String filename,
|
||||||
|
byte[] mdBytes, DeepResearchResponse researchResponse) {
|
||||||
|
try {
|
||||||
|
// Загрузка в MinIO
|
||||||
|
minIOService.uploadFile(filename, mdBytes,
|
||||||
|
MediaType.parseMediaType("text/markdown; charset=UTF-8").toString());
|
||||||
|
|
||||||
|
// Сохранение в базу данных
|
||||||
|
ReportHistory reportHistory = new ReportHistory();
|
||||||
|
reportHistory.setReportTitle(request.getQuery());
|
||||||
|
reportHistory.setAuthorName("AI Research Agent");
|
||||||
|
reportHistory.setCompanyName("Deep Research Service");
|
||||||
|
reportHistory.setStartDate(LocalDateTime.now());
|
||||||
|
reportHistory.setEndDate(LocalDateTime.now());
|
||||||
|
reportHistory.setFormat("MD");
|
||||||
|
reportHistory.setFilename(filename);
|
||||||
|
reportHistory.setFilePath(filename);
|
||||||
|
reportHistory.setFileSize(mdBytes.length);
|
||||||
|
reportHistory.setCreatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
reportHistoryRepository.save(reportHistory);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error saving research markdown to history: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/history")
|
@GetMapping("/history")
|
||||||
public ResponseEntity<PageResponse<ReportHistory>> history(
|
public ResponseEntity<PageResponse<ReportHistory>> history(
|
||||||
@RequestParam(name = "page", defaultValue = "0") int page,
|
@RequestParam(name = "page", defaultValue = "0") int page,
|
||||||
@@ -198,10 +254,17 @@ public class ReportController {
|
|||||||
byte[] bytes = inputStream.readAllBytes();
|
byte[] bytes = inputStream.readAllBytes();
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
|
|
||||||
MediaType mt = rh.getFormat() != null && rh.getFormat().equalsIgnoreCase("DOCX")
|
MediaType mt;
|
||||||
? MediaType
|
if (rh.getFormat() != null && rh.getFormat().equalsIgnoreCase("DOCX")) {
|
||||||
.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
|
mt = MediaType.parseMediaType(
|
||||||
: MediaType.APPLICATION_PDF;
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||||
|
} else if (rh.getFormat() != null && (rh.getFormat().equalsIgnoreCase("MD")
|
||||||
|
|| rh.getFilename() != null
|
||||||
|
&& rh.getFilename().toLowerCase().endsWith(".md"))) {
|
||||||
|
mt = MediaType.parseMediaType("text/markdown; charset=UTF-8");
|
||||||
|
} else {
|
||||||
|
mt = MediaType.APPLICATION_PDF;
|
||||||
|
}
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
"attachment; filename=\"" + rh.getFilename() + "\"")
|
"attachment; filename=\"" + rh.getFilename() + "\"")
|
||||||
|
|||||||
Reference in New Issue
Block a user