.
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
package kz.konturai.parser.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class ChartData {
|
||||||
|
private String chartType;
|
||||||
|
private String title;
|
||||||
|
private java.util.List<String> labels;
|
||||||
|
private java.util.List<Number> data;
|
||||||
|
|
||||||
|
public String getChartType() {
|
||||||
|
return chartType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChartType(String chartType) {
|
||||||
|
this.chartType = chartType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.util.List<String> getLabels() {
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabels(java.util.List<String> labels) {
|
||||||
|
this.labels = labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.util.List<Number> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(java.util.List<Number> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
package kz.konturai.parser.service;
|
package kz.konturai.parser.service;
|
||||||
|
|
||||||
import kz.konturai.parser.dto.FinalReportPayload;
|
import kz.konturai.parser.dto.FinalReportPayload;
|
||||||
|
import kz.konturai.parser.dto.ChartData;
|
||||||
import kz.konturai.parser.util.SvgToPngConverter;
|
import kz.konturai.parser.util.SvgToPngConverter;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,6 +19,7 @@ import java.util.stream.Collectors;
|
|||||||
public class ReportSynthesisService {
|
public class ReportSynthesisService {
|
||||||
|
|
||||||
private final OllamaAnalyticsService ollamaAnalyticsService;
|
private final OllamaAnalyticsService ollamaAnalyticsService;
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
public ReportSynthesisService(OllamaAnalyticsService ollamaAnalyticsService) {
|
public ReportSynthesisService(OllamaAnalyticsService ollamaAnalyticsService) {
|
||||||
this.ollamaAnalyticsService = ollamaAnalyticsService;
|
this.ollamaAnalyticsService = ollamaAnalyticsService;
|
||||||
@@ -31,53 +37,41 @@ public class ReportSynthesisService {
|
|||||||
|
|
||||||
Mono<String> textMono = Mono.fromCallable(
|
Mono<String> textMono = Mono.fromCallable(
|
||||||
() -> ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
() -> ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
||||||
buildTextPrompt(originalQuery, aggregatedLearnings, lang)));
|
buildTextPrompt(originalQuery, aggregatedLearnings, lang)))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic());
|
||||||
|
|
||||||
Mono<List<byte[]>> chartsMono = Mono.fromCallable(() -> {
|
Mono<List<byte[]>> chartsMono = Mono.fromCallable(() -> {
|
||||||
String json = extractSvgCode(ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
String jsonArrayString = ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
||||||
buildChartDataPrompt(aggregatedLearnings)));
|
buildChartDataPrompt(aggregatedLearnings));
|
||||||
if (json == null || json.trim().isEmpty() || json.trim().equals("{}")) {
|
String cleanJsonArray = extractJsonArray(jsonArrayString);
|
||||||
|
if (cleanJsonArray == null || cleanJsonArray.isEmpty() || cleanJsonArray.equals("[]")) {
|
||||||
return new ArrayList<byte[]>();
|
return new ArrayList<byte[]>();
|
||||||
}
|
}
|
||||||
String svg = ollamaAnalyticsService.generateWithInstruction(json, buildSvgPrompt(json));
|
List<ChartData> chartDataList = objectMapper.readValue(cleanJsonArray,
|
||||||
byte[] png = SvgToPngConverter.convert(svg);
|
new TypeReference<List<ChartData>>() {
|
||||||
List<byte[]> list = new ArrayList<>();
|
});
|
||||||
if (png != null && png.length > 0) {
|
List<byte[]> chartImages = new ArrayList<>();
|
||||||
list.add(png);
|
for (ChartData chartData : chartDataList) {
|
||||||
|
try {
|
||||||
|
String chartDataJson = objectMapper.writeValueAsString(chartData);
|
||||||
|
String rawSvgResponse = ollamaAnalyticsService.generateWithInstruction(chartDataJson,
|
||||||
|
buildSvgPrompt(chartDataJson));
|
||||||
|
String cleanSvg = extractSvgCode(rawSvgResponse);
|
||||||
|
byte[] png = SvgToPngConverter.convert(cleanSvg);
|
||||||
|
if (png != null && png.length > 0) {
|
||||||
|
chartImages.add(png);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to generate a chart: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return chartImages;
|
||||||
});
|
}).subscribeOn(Schedulers.boundedElastic());
|
||||||
|
|
||||||
return Mono.zip(textMono, chartsMono)
|
return Mono.zip(textMono, chartsMono)
|
||||||
.map(tuple -> new FinalReportPayload(tuple.getT1(), tuple.getT2()));
|
.map(tuple -> new FinalReportPayload(tuple.getT1(), tuple.getT2()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractSvgCode(String ollamaResponse) {
|
|
||||||
if (ollamaResponse == null || ollamaResponse.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find content within XML markdown block first
|
|
||||||
int startXml = ollamaResponse.indexOf("```xml");
|
|
||||||
if (startXml != -1) {
|
|
||||||
int startSvg = ollamaResponse.indexOf("<svg", startXml);
|
|
||||||
int endSvg = ollamaResponse.lastIndexOf("</svg>");
|
|
||||||
if (startSvg != -1 && endSvg != -1 && endSvg > startSvg) {
|
|
||||||
return ollamaResponse.substring(startSvg, endSvg + 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: find the first <svg> and last </svg> tags in the whole string
|
|
||||||
int startSvg = ollamaResponse.indexOf("<svg");
|
|
||||||
int endSvg = ollamaResponse.lastIndexOf("</svg>");
|
|
||||||
|
|
||||||
if (startSvg != -1 && endSvg != -1 && endSvg > startSvg) {
|
|
||||||
return ollamaResponse.substring(startSvg, endSvg + 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null; // Return null if no SVG found
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatLearnings(List<String> learnings) {
|
private String formatLearnings(List<String> learnings) {
|
||||||
if (learnings == null || learnings.isEmpty()) {
|
if (learnings == null || learnings.isEmpty()) {
|
||||||
return "- (данные не были предоставлены)";
|
return "- (данные не были предоставлены)";
|
||||||
@@ -110,7 +104,7 @@ public class ReportSynthesisService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildChartDataPrompt(String learningsAsString) {
|
private String buildChartDataPrompt(String learningsAsString) {
|
||||||
return "Ты — AI-аналитик данных. Внимательно проанализируй следующий текст. Если найдёшь в нём числовые данные, которые можно представить в виде простой столбчатой или круговой диаграммы (например, рост по годам, доли рынка, сравнение показателей), верни ТОЛЬКО один JSON-объект со структурой: {\\\"chartType\\\": \\\"bar\\\" или \\\"pie\\\", \\\"title\\\": \\\"Название диаграммы\\\", \\\"labels\\\": [\\\"Метка 1\\\", \\\"Метка 2\\\"], \\\"data\\\": [число1, число2]}. Если подходящих данных нет, верни пустой JSON-объект {}. Текст для анализа:\n---\n"
|
return "Ты — AI-аналитик данных. Внимательно проанализируй следующий текст. Найди в нём все наборы числовых данных, которые можно представить в виде простых диаграмм (столбчатых или круговых). Для КАЖДОГО найденного набора данных создай JSON-объект. Верни ТОЛЬКО JSON-массив этих объектов. Структура каждого объекта: {\"chartType\": \"bar\" или \"pie\", \"title\": \"Название диаграммы\", \"labels\": [\"Метка 1\"], \"data\": [число1]}. Если подходящих данных нет, верни пустой массив []. Текст для анализа:\n---\n"
|
||||||
+ learningsAsString + "\n---";
|
+ learningsAsString + "\n---";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,4 +112,35 @@ public class ReportSynthesisService {
|
|||||||
return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\n"
|
return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\n"
|
||||||
+ json + "\n---";
|
+ json + "\n---";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String extractJsonArray(String ollamaResponse) {
|
||||||
|
if (ollamaResponse == null)
|
||||||
|
return null;
|
||||||
|
int startIndex = ollamaResponse.indexOf("[");
|
||||||
|
int endIndex = ollamaResponse.lastIndexOf("]");
|
||||||
|
if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
|
||||||
|
return ollamaResponse.substring(startIndex, endIndex + 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractSvgCode(String ollamaResponse) {
|
||||||
|
if (ollamaResponse == null || ollamaResponse.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int startXml = ollamaResponse.indexOf("```xml");
|
||||||
|
if (startXml != -1) {
|
||||||
|
int startSvg = ollamaResponse.indexOf("<svg", startXml);
|
||||||
|
int endSvg = ollamaResponse.lastIndexOf("</svg>");
|
||||||
|
if (startSvg != -1 && endSvg != -1 && endSvg > startSvg) {
|
||||||
|
return ollamaResponse.substring(startSvg, endSvg + 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int startSvg = ollamaResponse.indexOf("<svg");
|
||||||
|
int endSvg = ollamaResponse.lastIndexOf("</svg>");
|
||||||
|
if (startSvg != -1 && endSvg != -1 && endSvg > startSvg) {
|
||||||
|
return ollamaResponse.substring(startSvg, endSvg + 6);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user