Ai improvements 1 (#914)

* improve netlist

* structure improvement

* chatbot enhancements

* chatbot improvements

* charbot enhancements

* Update user_manual.sql

* chatbot enhancements

* release
This commit is contained in:
Bane Stojkovic
2025-04-21 20:46:41 +02:00
committed by GitHub
parent b42fe2c34f
commit c914749413
24 changed files with 623 additions and 191 deletions
@@ -45,6 +45,10 @@ public enum EventType {
* has been either reloaded or loaded with undo/redo operations.
*/
PROJECT_LOADED,
/**
* Called when the current project is saved to a file. The first parameter is the current file name.
*/
PROJECT_SAVED,
/**
* Called when either current file name or modified flag are changed. The first parameter is the
* current file name, the second one is a boolean modified flag.
@@ -3,6 +3,7 @@ package org.diylc.common;
import org.diylc.appframework.simplemq.MessageDispatcher;
import org.diylc.plugins.chatbot.service.ChatbotService;
import org.diylc.plugins.cloud.service.CloudService;
import org.diylc.presenter.DrawingManager;
public interface IServiceManager {
@@ -11,4 +12,6 @@ public interface IServiceManager {
ChatbotService getChatbotService();
MessageDispatcher<EventType> getMessageDispatcher();
DrawingManager getDrawingManager();
}
@@ -0,0 +1,8 @@
package org.diylc.netlist;
import java.util.List;
public interface IGuitarDiagramAnalyzer {
List<String> collectNotes(Netlist netlist);
}
@@ -0,0 +1,6 @@
package org.diylc.plugins.chatbot.model;
import java.util.List;
public record AiComponent(String id, String type, String value, AiPoint fromPos, AiPoint toPos, AiPoint pos, List<AiTerminal> terminals) {
}
@@ -0,0 +1,4 @@
package org.diylc.plugins.chatbot.model;
public record AiPoint(int x, int y) {
}
@@ -0,0 +1,12 @@
package org.diylc.plugins.chatbot.model;
import java.util.List;
import java.util.Map;
import java.util.Set;
public record AiProject(Map<String, String> metadata,
List<String> tags,
List<AiComponent> components,
Map<String, Set<String>> nets,
List<AiSwitch> switches) {
}
@@ -0,0 +1,6 @@
package org.diylc.plugins.chatbot.model;
import java.util.List;
public record AiSwitch(String componentId, List<AiSwitchPosition> positions) {
}
@@ -0,0 +1,7 @@
package org.diylc.plugins.chatbot.model;
import java.util.List;
import java.util.Set;
public record AiSwitchPosition(String name, List<Set<Integer>> connectedTerminalGroups) {
}
@@ -0,0 +1,4 @@
package org.diylc.plugins.chatbot.model;
public record AiTerminal(int id, String label, AiPoint pos) {
}
@@ -0,0 +1,165 @@
package org.diylc.plugins.chatbot.service;
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.log4j.Logger;
import org.diylc.common.ComponentType;
import org.diylc.common.PropertyWrapper;
import org.diylc.core.IDIYComponent;
import org.diylc.core.ISwitch;
import org.diylc.core.Project;
import org.diylc.plugins.chatbot.model.*;
import org.diylc.presenter.ComponentProcessor;
import org.diylc.presenter.ContinuityArea;
public class AiProjectBuilder {
public static AiProject build(Project project, List<ContinuityArea> continuityAreas) {
Map<String, String> metadata = getMetadata(project);
List<String> tags = getTags(project);
List<AiComponent> components = project.getComponents().stream().map(AiProjectBuilder::mapComponent).toList();
List<Set<NetlistBuilder.TerminalRef>> sets =
NetlistBuilder.buildNets(project.getComponents(), continuityAreas);
AtomicInteger counter = new AtomicInteger();
Map<String, Set<String>> nets =
sets.stream().map(s -> s.stream()
.map(AiProjectBuilder::mapTerminal)
.collect(Collectors.toSet()))
.collect(Collectors.toMap(x -> "N" + counter.incrementAndGet(), x -> x));
List<AiSwitch> switches =
project.getComponents().stream()
.filter(x -> x instanceof ISwitch)
.map(x -> (ISwitch) x)
.map(AiProjectBuilder::mapSwitch).toList();
return new AiProject(metadata, tags, components, nets, switches);
}
private static Map<String, String> getMetadata(Project project) {
Map<String, String> metadata = new HashMap<>();
if (project.getTitle() != null) metadata.put("title", project.getTitle());
if (project.getAuthor() != null) metadata.put("author", project.getAuthor());
if (project.getDescription() != null) metadata.put("description", project.getDescription());
metadata.put("gridSpacing", project.getGridSpacing().toString());
metadata.put("width", project.getWidth().toString());
metadata.put("height", project.getHeight().toString());
return metadata;
}
private static List<String> getTags(Project project) {
List<String> tags = new ArrayList<>();
if (project.getComponents().stream().anyMatch(x -> x.getClass().getCanonicalName().toLowerCase().contains("guitar"))) {
tags.add("guitar");
}
if (project.getComponents().stream().anyMatch(x -> x.getClass().getName().toLowerCase().contains("symbol"))) {
tags.add("schematic");
}
if (project.getComponents().stream().anyMatch(x -> x.getClass().getName().toLowerCase().contains("board")) &&
project.getComponents().stream().anyMatch(x -> x.getClass().getName().toLowerCase().contains("trace")) &&
project.getComponents().stream().anyMatch(x -> x.getClass().getName().toLowerCase().contains("pad"))) {
tags.add("PCB");
}
if (project.getComponents().stream().anyMatch(x -> x.getClass().getName().toLowerCase().contains("tube"))) {
tags.add("tube");
}
if (project.getComponents().stream().anyMatch(x -> x.getClass().getName().toLowerCase().contains("vero"))) {
tags.add("vero/strip");
}
return tags;
}
private static final Set<Class<?>> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class);
private static final Set<String> PROPERTY_NAMES_TO_SKIP = Set.of("alpha");
static AiSwitch mapSwitch(ISwitch sw) {
List<AiSwitchPosition> positions = new ArrayList<>();
IDIYComponent<?> c = (IDIYComponent<?>) sw;
for (int p = 0; p < sw.getPositionCount(); p++) {
List<Set<Integer>> connections = new ArrayList<>();
for (int i = 0; i < c.getControlPointCount() - 1; i++) {
for (int j = i + 1; j < c.getControlPointCount(); j++) {
if (sw.arePointsConnected(i, j, p)) {
connections.add(Set.of(i, j));
}
}
}
positions.add(new AiSwitchPosition(sw.getPositionName(p), connections));
}
return new AiSwitch(c.getName(), positions);
}
static AiComponent mapComponent(IDIYComponent<?> component) {
ComponentType componentType = ComponentProcessor.getInstance()
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
List<PropertyWrapper> properties =
ComponentProcessor.getInstance().extractProperties(component.getClass());
AiPoint pos = null;
AiPoint fromPos = null;
AiPoint toPos = null;
// Map<String, Object> componentDescriptorMap = new HashMap<>();
// if (!component.isControlPointSticky(0)) {
// Point2D controlPoint1 = component.getControlPoint(0);
// if (component.getControlPointCount() > 1 && !component.isControlPointSticky(component.getControlPointCount() - 1)) {
// Point2D controlPoint2 = component.getControlPoint(component.getControlPointCount() - 1);
// fromPos = new AiPoint((int)Math.round(controlPoint1.getX()), (int)Math.round(controlPoint1.getY()));
// toPos = new AiPoint((int)Math.round(controlPoint2.getX()), (int)Math.round(controlPoint2.getY()));
// } else {
// pos = new AiPoint((int)Math.round(controlPoint1.getX()), (int)Math.round(controlPoint1.getY()));
// }
// }
//
// if (component.getValue() != null && !PROPERTY_TYPES_TO_SKIP.contains(component.getValue().getClass())) {
// componentDescriptorMap.put("value", component.getValue().toString());
// }
// properties.forEach(p -> {
// if (PROPERTY_TYPES_TO_SKIP.contains(p.getType()))
// return;
// if (PROPERTY_NAMES_TO_SKIP.contains(p.getName().toLowerCase()))
// return;
//
// try {
// p.readFrom(component);
// if (p.getValue() == null)
// return;
//
// componentDescriptorMap.put(p.getName(), p.getValue().toString());
// } catch (Exception e) {
// LOG.warn("Error extracting properties", e);
// }
// });
List<AiTerminal> terminals = new ArrayList<>();
for (int i = 0; i < component.getControlPointCount(); i++) {
if (component.isControlPointSticky(i)) {
Point2D controlPoint = component.getControlPoint(i);
String nodeName = component.getControlPointNodeName(i);
AiTerminal terminal = new AiTerminal(i, Integer.toString(i+1).equals(nodeName) ? null : nodeName,
new AiPoint((int)Math.round(controlPoint.getX()),
(int)Math.round(controlPoint.getY())));
terminals.add(terminal);
}
}
return new AiComponent(component.getName(), componentType.getName(),
component.getValue() == null ? null : component.getValue().toString(),
fromPos, toPos, pos, terminals.isEmpty() ? null : terminals);
}
static String mapTerminal(NetlistBuilder.TerminalRef terminalRef) {
return terminalRef.component.getName() + "." + terminalRef.terminalIndex;
}
}
@@ -23,6 +23,8 @@ package org.diylc.plugins.chatbot.service;
import com.diyfever.httpproxy.PhpFlatProxy;
import com.diyfever.httpproxy.ProxyFactory;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.ConfigurationManager;
@@ -34,13 +36,13 @@ import org.diylc.common.PropertyWrapper;
import org.diylc.core.IDIYComponent;
import org.diylc.core.ISwitch;
import org.diylc.netlist.*;
import org.diylc.plugins.chatbot.model.ChatMessageEntity;
import org.diylc.plugins.chatbot.model.IChatbotAPI;
import org.diylc.plugins.chatbot.model.SubscriptionEntity;
import org.diylc.plugins.chatbot.model.*;
import org.diylc.plugins.cloud.model.IServiceAPI;
import org.diylc.plugins.cloud.service.NotLoggedInException;
import org.diylc.presenter.ComponentProcessor;
import org.diylc.presenter.ContinuityArea;
import org.diylc.utils.FileUtils;
import org.diylc.utils.ReflectionUtils;
import java.awt.*;
import java.io.File;
@@ -48,7 +50,6 @@ import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import static org.diylc.utils.FileUtils.extractFileName;
@@ -59,11 +60,11 @@ import static org.diylc.utils.FileUtils.extractFileName;
*/
public class ChatbotService {
public static final String LOG_IN_MESSAGE =
"Please log into your DIYLC Cloud account in order to use the AI Assistant";
private final static Logger LOG = Logger.getLogger(ChatbotService.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
public static final String WELCOME_MESSAGE = "Welcome to the DIYLC AI Assistant!";
private final IPlugInPort plugInPort;
@@ -79,14 +80,14 @@ public class ChatbotService {
@Override
public EnumSet<EventType> getSubscribedEventTypes() {
return EnumSet.of(EventType.PROJECT_LOADED, EventType.FILE_STATUS_CHANGED);
return EnumSet.of(EventType.PROJECT_LOADED, EventType.PROJECT_SAVED);
}
@Override
public void processMessage(EventType eventType, Object... objects) {
if (eventType == EventType.PROJECT_LOADED) {
ChatbotService.this.currentProjectName = extractFileName((String)objects[2]);
} else if (eventType == EventType.FILE_STATUS_CHANGED) {
} else if (eventType == EventType.PROJECT_SAVED) {
String newProjectName = extractFileName((String)objects[0]);
if (!Objects.equals(ChatbotService.this.currentProjectName, newProjectName)) {
if (plugInPort.getCloudService().isLoggedIn()) {
@@ -117,24 +118,68 @@ public class ChatbotService {
if (!plugInPort.getCloudService().isLoggedIn())
throw new NotLoggedInException();
LOG.info("Prompting chatbot: " + prompt);
String currentFile = plugInPort.getCurrentFileName();
String fileName = extractFileName(currentFile);
String netlist;
File netlistFile = null;
File aiProjectFile = null;
try {
netlist = extractNetlists();
if (netlist != null) {
netlistFile = File.createTempFile("diylc_netlist_", ".txt");
try (FileWriter writer = new FileWriter(netlistFile)) {
writer.write(netlist);
AiProject aiProject = extractAiProject();
String aiProjectStr = null;
try {
aiProjectStr = MAPPER.writeValueAsString(aiProject);
} catch (JsonProcessingException e) {
LOG.error("Could not serialize aiProject to JSON", e);
}
if (aiProjectStr != null) {
aiProjectFile = File.createTempFile("diylc_netlist_", ".txt");
try (FileWriter writer = new FileWriter(aiProjectFile)) {
writer.write("""
Take these notes into account when analyzing the circuit
- Coordinates are represented in pixels assuming 200px/in resolution.
- Each component will either have a set of terminals that are connectable to other components or one or two points defining its position.
- Netlists of connected terminals are defined in the 'nets' structure.
- Analyze labels - they could provide clues about nearby terminals and what they represent.
""");
if (aiProject.switches() != null && !aiProject.switches().isEmpty()) {
writer.write("""
- Switches are defined in the 'switches' structure. Each switch will have a list of available position and the collection of internal terminals that are connected in each of the positions. Take the switching matrix into consideration when analyzing the circuit operation.
""");
}
if (aiProject.tags().contains("guitar")) {
IGuitarDiagramAnalyzer guitarDiagramAnalyzer =
ReflectionUtils.getGuitarDiagramAnalyzer();
if (guitarDiagramAnalyzer != null) {
try {
List<Netlist> netlists = plugInPort.extractNetlists(true);
writer.write("""
- The circuit represents a guitar wiring diagram and below are some of the known characteristics in each combination of switch positions:
""");
netlists.forEach(netlist -> {
List<String> notes = guitarDiagramAnalyzer.collectNotes(netlist);
try {
writer.write(netlist.getSwitchSetupString() + ": \n");
for (String note : notes) {
writer.write(" - " + note + "\n");
}
} catch (IOException ex) {
LOG.warn("Could not write guitar diagram notes", ex);
}
});
} catch (NetlistException | IOException ex) {
LOG.warn("Could not extract guitar diagram netlist", ex);
}
}
}
writer.write("\nBelow is the JSON describing the circuit in detail.\n");
writer.write(aiProjectStr);
}
// Make sure the temp file is deleted when the JVM exits
netlistFile.deleteOnExit();
aiProjectFile.deleteOnExit();
}
} catch (NetlistException | IOException e) {
} catch (Exception e) {
LOG.error("Error extracting or saving netlist", e);
netlist = null;
}
try {
@@ -143,117 +188,19 @@ public class ChatbotService {
plugInPort.getCloudService().getCurrentToken(),
plugInPort.getCloudService().getMachineId(),
fileName,
netlistFile,
aiProjectFile,
prompt);
} finally {
// Clean up the temp file immediately after use
if (netlistFile != null && netlistFile.exists()) {
netlistFile.delete();
if (aiProjectFile != null && aiProjectFile.exists()) {
aiProjectFile.delete();
}
}
}
private static final Set<Class<?>> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class);
private String extractNetlists() throws NetlistException {
List<Netlist> netlists = plugInPort.extractNetlists(true);
if (netlists == null || netlists.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
// Set<? extends IDIYComponent<?>> components = netlists.stream()
// .flatMap(n -> n.getGroups().stream()
// .flatMap(g -> g.getNodes().stream().
// map(Node::getComponent)))
// .collect(Collectors.toSet());
//
// if (!components.isEmpty()) {
// sb.append(
// "Components (each element of the JSON array represents one component with all the relevant properties): ");
// outputComponents(components, sb);
// }
Set<? extends ISwitch> switches = plugInPort.getCurrentProject().getComponents()
.stream().filter(x -> x instanceof ISwitch)
.map(x -> (ISwitch)x)
.collect(Collectors.toSet());;
if (!switches.isEmpty()) {
sb.append(
"Switches (each row represents one switch, starts with the switch name and lists key properties of a switch):\n\n");
outputSwitches(switches, sb);
}
for (Netlist netlist : netlists) {
sb.append("\n");
if (!netlist.getSwitchSetup().isEmpty()) {
sb.append("Switch configuration: ").append(netlist.getSwitchSetup()).append(". ");
}
sb.append("Circuit netlist")
.append(netlist.getSwitchSetup().isEmpty() ? "" : " in this switch configuration")
.append(":\n\n");
try {
sb.append(SpiceSumarizer.summarize(netlist, false));
} catch (TreeException e) {
LOG.error("Error exporting netlist to Spice", e);
}
}
return sb.toString();
}
private static void outputSwitches(Set<? extends ISwitch> switches, StringBuilder sb) {
for (ISwitch sw : switches) {
IDIYComponent<?> component = (IDIYComponent<?>) sw;
ComponentType componentType = ComponentProcessor.getInstance()
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
sb.append(component.getName())
.append(" ").append(component.getValueForDisplay()).append(" ")
.append(componentType.getName())
.append(" [").append(componentType.getCategory()).append("]\n");
}
}
private static void outputComponents(Set<? extends IDIYComponent<?>> components, StringBuilder sb) {
List<Map<String, Object>> componentDescriptors = new ArrayList<>();
components.forEach(c -> {
ComponentType componentType = ComponentProcessor.getInstance()
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) c.getClass());
List<PropertyWrapper> properties =
ComponentProcessor.getInstance().extractProperties(c.getClass());
Map<String, Object> componentDescriptorMap = new HashMap<>();
componentDescriptorMap.put("name", c.getName());
componentDescriptorMap.put("type", componentType.getName());
componentDescriptorMap.put("category", componentType.getCategory());
properties.forEach(p -> {
if (PROPERTY_TYPES_TO_SKIP.contains(p.getType()))
return;
try {
p.readFrom(c);
if (p.getValue() == null)
return;
componentDescriptorMap.put(p.getName(), p.getValue().toString());
} catch (Exception e) {
LOG.warn("Error extracting properties", e);
}
});
componentDescriptors.add(componentDescriptorMap);
});
try {
String json = MAPPER.writeValueAsString(componentDescriptors);
sb.append(json).append("\n\n");
} catch (Exception e) {
LOG.error("Failed to serialize component descriptors to JSON", e);
}
private AiProject extractAiProject() throws NetlistException {
List<ContinuityArea> continuityAreas = plugInPort.getDrawingManager().getContinuityAreas();
return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas);
}
public SubscriptionEntity getSubscriptionInfo() throws NotLoggedInException {
@@ -317,23 +264,23 @@ public class ChatbotService {
public static final String[] diylcQuestions = {
"How do I add a component to my layout in DIYLC?",
"How can I quickly find a specific component in DIYLC?s component toolbox?",
"How can I quickly find a specific component in DIYLC's component toolbox?",
"How do I move or reposition components on the canvas in DIYLC?",
"How do I edit or change a component?s properties (e.g. value or color) in DIYLC?",
"How do I edit or change a component's properties (e.g. value or color) in DIYLC?",
"Is there a shortcut to duplicate or repeat the last component I placed in DIYLC?",
"How can I group multiple components so I can move or edit them together in DIYLC?",
"What are ?building blocks? in DIYLC and how do I create or use them?",
"What are 'building blocks' in DIYLC and how do I create or use them?",
"How do I save my DIYLC project and reopen it later on?",
"How do I export my DIYLC layout as an image or PDF file?",
"Can DIYLC generate a bill of materials (BOM) for my project, and how would I do that?",
"How can I change or hide different layers in a DIYLC project to manage overlapping components?",
"Is it possible to view the underside of a board in DIYLC, and how can I enable that?",
"What does the ?Highlight Connected Areas? feature do in DIYLC and how do I use it for debugging?",
"How can I analyze a guitar wiring diagram using DIYLC?s built-in tools?",
"What does the 'Highlight Connected Areas' feature do in DIYLC and how do I use it for debugging?",
"How can I analyze a guitar wiring diagram using DIYLC's built-in tools?",
"How do I assign a custom keyboard shortcut to a component in DIYLC for quick access?",
"What are component variants in DIYLC and how can I create and apply them?",
"What is the continuous creation mode in DIYLC and how does it help when adding many components?",
"How can I create a custom component to add to DIYLC?s component library?",
"How can I create a custom component to add to DIYLC's component library?",
"Does DIYLC support plug-ins, and how can I add new functionality to the app with them?",
"What is the DIYLC cloud feature and how can I share or download projects using it?"
};
@@ -0,0 +1,137 @@
package org.diylc.plugins.chatbot.service;
import org.diylc.core.IContinuity;
import org.diylc.core.IDIYComponent;
import org.diylc.core.ISwitch;
import org.diylc.presenter.ContinuityArea;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.util.*;
public class NetlistBuilder {
public static class TerminalRef {
public final IDIYComponent<?> component;
public final int terminalIndex;
public TerminalRef(IDIYComponent<?> component, int terminalIndex) {
this.component = component;
this.terminalIndex = terminalIndex;
}
public Point2D getPosition() {
return component.getControlPoint(terminalIndex);
}
@Override
public String toString() {
return component.getName() + "." + terminalIndex;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof TerminalRef)) return false;
TerminalRef other = (TerminalRef) obj;
return Objects.equals(component, other.component) && terminalIndex == other.terminalIndex;
}
@Override
public int hashCode() {
return Objects.hash(component, terminalIndex);
}
}
public static List<Set<TerminalRef>> buildNets(List<IDIYComponent<?>> components, List<ContinuityArea> continuityAreas) {
Map<TerminalRef, Integer> terminalIndex = new HashMap<>();
List<TerminalRef> allTerminals = new ArrayList<>();
int idx = 0;
for (IDIYComponent<?> c : components) {
for (int i = 0; i < c.getControlPointCount(); i++) {
if (c.isControlPointSticky(i)) {
TerminalRef ref = new TerminalRef(c, i);
terminalIndex.put(ref, idx++);
allTerminals.add(ref);
}
}
}
UnionFind uf = new UnionFind(allTerminals.size());
// Step 1: connect conductive components
for (IDIYComponent<?> c : components) {
if (c instanceof IContinuity continuity && !(c instanceof ISwitch)) {
int count = c.getControlPointCount();
for (int i = 0; i < count; i++) {
for (int j = i + 1; j < count; j++) {
if (continuity.arePointsConnected(i, j)) {
TerminalRef a = new TerminalRef(c, i);
TerminalRef b = new TerminalRef(c, j);
uf.union(terminalIndex.get(a), terminalIndex.get(b));
}
}
}
}
}
// Step 2: connect terminals inside continuity areas
for (ContinuityArea continuity : continuityAreas) {
Area area = continuity.getSimplifiedArea();
List<TerminalRef> inside = new ArrayList<>();
for (TerminalRef t : allTerminals) {
Point2D p = t.getPosition();
if (area.contains(p)) {
inside.add(t);
}
}
for (int i = 0; i < inside.size(); i++) {
for (int j = i + 1; j < inside.size(); j++) {
uf.union(terminalIndex.get(inside.get(i)), terminalIndex.get(inside.get(j)));
}
}
}
Map<Integer, Set<TerminalRef>> netMap = new HashMap<>();
for (TerminalRef t : allTerminals) {
int root = uf.find(terminalIndex.get(t));
netMap.computeIfAbsent(root, k -> new HashSet<>()).add(t);
}
// Filter out nets with only one terminal and remove exact duplicates
List<Set<TerminalRef>> nets = new ArrayList<>();
Set<Set<TerminalRef>> seen = new HashSet<>();
for (Set<TerminalRef> net : netMap.values()) {
if (net.size() > 1 && seen.add(net)) {
nets.add(net);
}
}
return nets;
}
// Union-Find with path compression
static class UnionFind {
int[] parent;
UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
void union(int x, int y) {
parent[find(x)] = find(y);
}
}
}
@@ -2831,7 +2831,13 @@ public class Presenter implements IPlugInPort {
return chatbotService;
}
@Override
public MessageDispatcher<EventType> getMessageDispatcher() {
return messageDispatcher;
}
@Override
public DrawingManager getDrawingManager() {
return drawingManager;
}
}
@@ -177,6 +177,7 @@ public class ProjectFileManager {
if (!isBackup) {
this.currentFileName = fileName;
this.modified = false;
messageDispatcher.dispatchMessage(EventType.PROJECT_SAVED, getCurrentFileName());
fireFileStatusChanged();
}
}
@@ -31,6 +31,7 @@ import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.core.annotations.IAutoCreator;
import org.diylc.netlist.IGuitarDiagramAnalyzer;
import org.diylc.netlist.INetlistParser;
import org.diylc.presenter.Presenter;
@@ -89,4 +90,25 @@ public class ReflectionUtils {
return null;
}
}
public static IGuitarDiagramAnalyzer getGuitarDiagramAnalyzer() throws Exception {
Set<Class<?>> classes;
classes = Utils.getClasses("org.diylc.netlist");
List<IGuitarDiagramAnalyzer> result = new ArrayList<>();
for (Class<?> clazz : classes) {
if (!Modifier.isAbstract(clazz.getModifiers()) && IGuitarDiagramAnalyzer.class.isAssignableFrom(clazz)) {
result.add((IGuitarDiagramAnalyzer) clazz.getDeclaredConstructor().newInstance());
}
}
if (result.isEmpty()) {
return null;
}
if (result.size() > 1) {
throw new RuntimeException("Found more than one IGuitarDiagramAnalyzer");
}
return result.get(0);
}
}
+10 -2
View File
@@ -258,9 +258,13 @@
<minor>3</minor>
<build>0</build>
</versionNumber>
<releaseDate>2025-03-27 00:00:00.000 CET</releaseDate>
<releaseDate>2025-04-21 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improved AI Assistant quality of responses, especially when analyzing a circuit</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>When saving a project to the new file, retain all the AI Assistant chat history for the new project file</description>
@@ -275,7 +279,11 @@
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Output current subscription statistics at the end of the chat for transparency</description>
<description>Output current subscription statistics at the end of the chat for usage transparency</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improved styling and usability of chatbot tool</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>