From f7cd33c80f73ab51b2dedcbf9909413ebc6e6009 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 24 Apr 2025 21:38:04 +0200 Subject: [PATCH] analyzer added --- .../plugins/chatbot/model/IChatbotAPI.java | 14 +++ .../chatbot/service/AiProjectBuilder.java | 4 +- .../chatbot/service/ChatbotService.java | 73 +++++++++++---- .../chatbot/service/JsonToHtmlConverter.java | 92 +++++++++++++++++++ .../java/org/diylc/swing/ActionFactory.java | 5 + .../actions/analyze/AiAnalyzeAction.java | 60 ++++++++++++ .../plugins/analyze/AnalyzeMenuPlugin.java | 1 + 7 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/JsonToHtmlConverter.java create mode 100644 diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/AiAnalyzeAction.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/model/IChatbotAPI.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/model/IChatbotAPI.java index 255edc05..a2da1560 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/model/IChatbotAPI.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/model/IChatbotAPI.java @@ -43,6 +43,20 @@ public interface IChatbotAPI { @ParamName("machineId") String machineId, @ParamName("project") String project, @ParamName("netlist") File netlist, @ParamName("prompt") String prompt); + /** + * Analyzes the provided circuit + * + * @param username + * @param token + * @param machineId + * @param project + * @param netlist + * @return + */ + String analyzeCircuit(@ParamName("username") String username, @ParamName("token") String token, + @ParamName("machineId") String machineId, @ParamName("project") String project, + @ParamName("netlist") File netlist); + SubscriptionEntity getSubscriptionInfo(@ParamName("username") String username, @ParamName("token") String token, @ParamName("machineId") String machineId); diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java index e7388a5c..bbdc2c0f 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java @@ -5,6 +5,7 @@ import java.awt.geom.Point2D; import java.util.*; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.log4j.Logger; @@ -34,7 +35,8 @@ public class AiProjectBuilder { sets.stream().map(s -> s.stream() .map(AiProjectBuilder::mapTerminal) .collect(Collectors.toSet())) - .collect(Collectors.toMap(x -> "N" + counter.incrementAndGet(), x -> x)); + .collect(Collectors.toMap(x -> "N" + String.format("%03d", counter.incrementAndGet()), + Function.identity())); List switches = project.getComponents().stream() diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java index 78936d54..e302f8f0 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java @@ -123,9 +123,57 @@ public class ChatbotService { String currentFile = plugInPort.getCurrentFileName(); String fileName = extractFileName(currentFile); - File aiProjectFile = null; + File aiProjectFile = getAiProjectFile(); + + try { + return getService().promptChatbot( + plugInPort.getCloudService().getCurrentUsername(), + plugInPort.getCloudService().getCurrentToken(), + plugInPort.getCloudService().getMachineId(), + fileName, + aiProjectFile, + prompt); + } finally { + // Clean up the temp file immediately after use + if (aiProjectFile != null && aiProjectFile.exists()) { + aiProjectFile.delete(); + } + } + } + + public String analyzeCircuit() throws NotLoggedInException { + if (!plugInPort.getCloudService().isLoggedIn()) + throw new NotLoggedInException(); + + LOG.info("Analyzing circuit..."); + + String currentFile = plugInPort.getCurrentFileName(); + String fileName = extractFileName(currentFile); + + File aiProjectFile = getAiProjectFile(); + + try { + String jsonResult = getService().analyzeCircuit(plugInPort.getCloudService().getCurrentUsername(), + plugInPort.getCloudService().getCurrentToken(), + plugInPort.getCloudService().getMachineId(), fileName, aiProjectFile); + return JsonToHtmlConverter.convertToHtml(jsonResult); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // Clean up the temp file immediately after use + if (aiProjectFile != null && aiProjectFile.exists()) { + aiProjectFile.delete(); + } + } + } + + private File getAiProjectFile() { + File aiProjectFile = null; + AiProject aiProject = extractAiProject(); + if (aiProject == null) { + return null; + } try { - AiProject aiProject = extractAiProject(); String aiProjectStr = null; try { aiProjectStr = MAPPER.writeValueAsString(aiProject); @@ -181,24 +229,13 @@ public class ChatbotService { } catch (Exception e) { LOG.error("Error extracting or saving netlist", e); } - - try { - return getService().promptChatbot( - plugInPort.getCloudService().getCurrentUsername(), - plugInPort.getCloudService().getCurrentToken(), - plugInPort.getCloudService().getMachineId(), - fileName, - aiProjectFile, - prompt); - } finally { - // Clean up the temp file immediately after use - if (aiProjectFile != null && aiProjectFile.exists()) { - aiProjectFile.delete(); - } - } + return aiProjectFile; } - private AiProject extractAiProject() throws NetlistException { + private AiProject extractAiProject() { + if (plugInPort.getCurrentProject().getComponents().isEmpty()) { + return null; + } List continuityAreas = plugInPort.getDrawingManager().getContinuityAreas(); return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas); } diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/JsonToHtmlConverter.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/JsonToHtmlConverter.java new file mode 100644 index 00000000..ba5f3e4a --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/JsonToHtmlConverter.java @@ -0,0 +1,92 @@ +package org.diylc.plugins.chatbot.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +public class JsonToHtmlConverter { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Convert the given JSON string into HTML. + * + * @param json the input JSON + * @return an HTML document as a String + * @throws IOException if JSON parsing fails + */ + public static String convertToHtml(String json) throws IOException { + JsonNode root = MAPPER.readTree(json); + StringBuilder html = new StringBuilder(); + + html.append("\n\n\n") + .append(" \n") + .append(" Validation Report\n") + .append(" \n") + .append("\n\n"); + + // Process each section + processSection(root, "critical", html, "#c00"); + processSection(root, "warnings", html, "#e65c00"); + processSection(root, "optimizations", html, "#0066c0"); + + html.append("\n"); + return html.toString(); + } + + private static void processSection(JsonNode root, String field, StringBuilder html, String color) { + JsonNode array = root.path(field); + if (array.isArray() && array.size() > 0) { + html.append(String.format("

%s

\n", color, capitalize(field))); + html.append("
    \n"); + for (JsonNode item : array) { + String message = escapeHtml(item.path("message").asText("")); + if (item.hasNonNull("componentId") && !item.path("componentId").asText().isEmpty()) { + String id = escapeHtml(item.path("componentId").asText()); + html.append("
  • ") + .append(id) + .append(": ") + .append(message) + .append("
  • \n"); + } else { + html.append("
  • ") + .append(message) + .append("
  • \n"); + } + } + html.append("
\n"); + } + } + + private static String capitalize(String text) { + if (text == null || text.isEmpty()) return text; + return Character.toUpperCase(text.charAt(0)) + text.substring(1); + } + + /** + * Very basic HTML escapement; for more robust use Apache Commons StringEscapeUtils.escapeHtml4(). + */ + private static String escapeHtml(String s) { + return s.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """); + } + + // Example usage: + public static void main(String[] args) throws IOException { + String json = "{\n" + + " \"critical\": [{\"message\":\"Value too low\"}],\n" + + " \"warnings\": [{\"componentId\":\"C3\",\"message\":\"Consider higher tolerance\"}],\n" + + " \"optimizations\": [{\"componentId\":\"L2\",\"message\":\"Could use 0805 package\"}]\n" + + "}"; + String html = convertToHtml(json); + System.out.println(html); + } +} diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/ActionFactory.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/ActionFactory.java index a265e96f..481e4fea 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/ActionFactory.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/ActionFactory.java @@ -21,6 +21,7 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import javax.swing.Icon; import org.apache.log4j.Logger; +import org.diylc.swing.actions.analyze.AiAnalyzeAction; import org.diylc.swing.actions.analyze.HighlightConnectedAreasAction; import org.diylc.swingframework.IDrawingProvider; @@ -275,6 +276,10 @@ public class ActionFactory { return new GenerateNetlistAction(plugInPort, swingUI, includeSwitches); } + public AiAnalyzeAction createAiAnalyzeAction(IPlugInPort plugInPort, ISwingUI swingUI) { + return new AiAnalyzeAction(plugInPort, swingUI); + } + public SummarizeNetlistAction createSummarizeNetlistAction(IPlugInPort plugInPort, ISwingUI swingUI, INetlistAnalyzer summarizer, boolean includeSwitches) { return new SummarizeNetlistAction(plugInPort, swingUI, summarizer, includeSwitches); diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/AiAnalyzeAction.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/AiAnalyzeAction.java new file mode 100644 index 00000000..934f3b48 --- /dev/null +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/AiAnalyzeAction.java @@ -0,0 +1,60 @@ +package org.diylc.swing.actions.analyze; + +import org.diylc.common.INetlistAnalyzer; +import org.diylc.common.IPlugInPort; +import org.diylc.common.ITask; +import org.diylc.netlist.Netlist; +import org.diylc.netlist.Summary; +import org.diylc.swing.ISwingUI; +import org.diylc.swingframework.TextDialog; +import org.diylc.utils.IconLoader; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.List; + +public class AiAnalyzeAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + private IPlugInPort plugInPort; + private ISwingUI swingUI; + + public AiAnalyzeAction(IPlugInPort plugInPort, ISwingUI swingUI) { + super(); + this.plugInPort = plugInPort; + this.swingUI = swingUI; + putValue(AbstractAction.NAME, "AI Circuit Analyzer"); +// putValue(AbstractAction.SMALL_ICON, +// Enum.valueOf(IconLoader.class, summarizer.getIconName()).getIcon()); + } + + @Override + public void actionPerformed(ActionEvent e) { + + swingUI.executeBackgroundTask(new ITask() { + + @Override + public String doInBackground() throws Exception { + return plugInPort.getChatbotService().analyzeCircuit(); + } + + @Override + public void failed(Exception e) { + swingUI.showMessage(e.getMessage(), "AI Circuit Analyzer", ISwingUI.ERROR_MESSAGE); + } + + @Override + public void complete(String res) { + if (res == null || res.isEmpty()) { + swingUI.showMessage("The generated analysis is empty, nothing to show.", + "AI Circuit Analyzer", ISwingUI.INFORMATION_MESSAGE); + return; + } + new TextDialog(swingUI.getOwnerFrame().getRootPane(), res, "AI Circuit Analyzer", + new Dimension(800, 600)).setVisible(true); + } + }, true); + } +} diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/analyze/AnalyzeMenuPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/analyze/AnalyzeMenuPlugin.java index 9e0f6bc4..cdd4049b 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/analyze/AnalyzeMenuPlugin.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/analyze/AnalyzeMenuPlugin.java @@ -66,6 +66,7 @@ public class AnalyzeMenuPlugin implements IPlugIn { List menuActions = new ArrayList<>(); // Add all menu actions to the list + menuActions.add(actionFactory.createAiAnalyzeAction(plugInPort, swingUI)); menuActions.add(actionFactory.createCheckProximityAction(plugInPort, swingUI)); menuActions.add(actionFactory.createBomAction(plugInPort)); menuActions.add(actionFactory.createGenerateNetlistAction(plugInPort, swingUI, false));