.
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;
|
||||
|
||||
import kz.konturai.parser.dto.FinalReportPayload;
|
||||
import kz.konturai.parser.dto.ChartData;
|
||||
import kz.konturai.parser.util.SvgToPngConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
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.List;
|
||||
@@ -14,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
public class ReportSynthesisService {
|
||||
|
||||
private final OllamaAnalyticsService ollamaAnalyticsService;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public ReportSynthesisService(OllamaAnalyticsService ollamaAnalyticsService) {
|
||||
this.ollamaAnalyticsService = ollamaAnalyticsService;
|
||||
@@ -31,53 +37,41 @@ public class ReportSynthesisService {
|
||||
|
||||
Mono<String> textMono = Mono.fromCallable(
|
||||
() -> ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
||||
buildTextPrompt(originalQuery, aggregatedLearnings, lang)));
|
||||
buildTextPrompt(originalQuery, aggregatedLearnings, lang)))
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
|
||||
Mono<List<byte[]>> chartsMono = Mono.fromCallable(() -> {
|
||||
String json = extractSvgCode(ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
||||
buildChartDataPrompt(aggregatedLearnings)));
|
||||
if (json == null || json.trim().isEmpty() || json.trim().equals("{}")) {
|
||||
String jsonArrayString = ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings,
|
||||
buildChartDataPrompt(aggregatedLearnings));
|
||||
String cleanJsonArray = extractJsonArray(jsonArrayString);
|
||||
if (cleanJsonArray == null || cleanJsonArray.isEmpty() || cleanJsonArray.equals("[]")) {
|
||||
return new ArrayList<byte[]>();
|
||||
}
|
||||
String svg = ollamaAnalyticsService.generateWithInstruction(json, buildSvgPrompt(json));
|
||||
byte[] png = SvgToPngConverter.convert(svg);
|
||||
List<byte[]> list = new ArrayList<>();
|
||||
if (png != null && png.length > 0) {
|
||||
list.add(png);
|
||||
List<ChartData> chartDataList = objectMapper.readValue(cleanJsonArray,
|
||||
new TypeReference<List<ChartData>>() {
|
||||
});
|
||||
List<byte[]> chartImages = new ArrayList<>();
|
||||
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)
|
||||
.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) {
|
||||
if (learnings == null || learnings.isEmpty()) {
|
||||
return "- (данные не были предоставлены)";
|
||||
@@ -110,7 +104,7 @@ public class ReportSynthesisService {
|
||||
}
|
||||
|
||||
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---";
|
||||
}
|
||||
|
||||
@@ -118,4 +112,35 @@ public class ReportSynthesisService {
|
||||
return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\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