From 1b82d8c7df08b7c3bbbddb5aeb8000266047316d Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 3 Apr 2025 20:10:58 +0200 Subject: [PATCH] chatbot enhancements --- diylc/diylc-core/pom.xml | 5 + .../org/diylc/common/INetlistAnalyzer.java | 3 +- .../netlist/AbstractNetlistAnalyzer.java | 6 +- .../chatbot/presenter/ChatbotPresenter.java | 214 +++++++++++- .../main/java/org/diylc/utils/FileUtils.java | 24 ++ .../diylc/netlist/GuitarDiagramAnalyzer.java | 312 ++++++++++-------- .../org/diylc/netlist/MultimeterAnalyzer.java | 2 +- .../java/org/diylc/netlist/SpiceAnalyzer.java | 2 +- .../analyze/SummarizeNetlistAction.java | 2 +- .../swing/plugins/chatbot/ChatbotPane.java | 158 +-------- 10 files changed, 425 insertions(+), 303 deletions(-) create mode 100644 diylc/diylc-core/src/main/java/org/diylc/utils/FileUtils.java diff --git a/diylc/diylc-core/pom.xml b/diylc/diylc-core/pom.xml index 327663d7..4271f479 100644 --- a/diylc/diylc-core/pom.xml +++ b/diylc/diylc-core/pom.xml @@ -57,6 +57,11 @@ reflections 0.10.2 + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + junit junit diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/INetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/common/INetlistAnalyzer.java index 3b2fb13a..9c732a20 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/common/INetlistAnalyzer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/common/INetlistAnalyzer.java @@ -50,11 +50,10 @@ public interface INetlistAnalyzer { * Summarizes all {@link Netlist}s provided. * * @param netlists - * @param preferredOutput optional, needed only where there's more than one possible output node. * @return * @throws TreeException */ - List summarize(List netlists, Node preferredOutput) throws TreeException; + List summarize(List netlists) throws TreeException; /** * @return name of the font to be used for display. diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java index a56091b5..80b56218 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java @@ -30,10 +30,10 @@ public abstract class AbstractNetlistAnalyzer { public AbstractNetlistAnalyzer() {} - public List summarize(List netlists, Node preferredOutput) throws TreeException { + public List summarize(List netlists) throws TreeException { Map summaries = new HashMap(); for (Netlist n : netlists) { - Summary s = summarize(n, preferredOutput); + Summary s = summarize(n); if (summaries.containsKey(s.getSummary())) summaries.get(s.getSummary()).append(n); else @@ -46,7 +46,7 @@ public abstract class AbstractNetlistAnalyzer { return res; } - protected abstract Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException; + protected abstract Summary summarize(Netlist netlist) throws TreeException; public Tree constructTreeBetween(Netlist netlist, Node nodeA, Node nodeB) { Tree tree = new Tree(TreeConnectionType.Parallel); diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/presenter/ChatbotPresenter.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/presenter/ChatbotPresenter.java index 94696947..394206f2 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/presenter/ChatbotPresenter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/presenter/ChatbotPresenter.java @@ -23,10 +23,19 @@ package org.diylc.plugins.chatbot.presenter; import com.diyfever.httpproxy.PhpFlatProxy; import com.diyfever.httpproxy.ProxyFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import org.apache.log4j.Logger; +import org.checkerframework.checker.units.qual.C; import org.checkerframework.checker.units.qual.N; import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.common.ComponentType; +import org.diylc.common.IPlugInPort; import org.diylc.common.PropertyWrapper; +import org.diylc.core.IDIYComponent; +import org.diylc.netlist.Group; +import org.diylc.netlist.Netlist; +import org.diylc.netlist.NetlistException; import org.diylc.plugins.chatbot.model.IChatbotAPI; import org.diylc.plugins.cloud.model.CommentEntity; import org.diylc.plugins.cloud.model.IServiceAPI; @@ -38,16 +47,22 @@ import org.diylc.plugins.cloud.presenter.NotLoggedInException; import org.diylc.presenter.ComparatorFactory; import org.diylc.presenter.ComponentProcessor; +import javax.swing.*; +import java.awt.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; -import java.util.Collections; +import java.util.*; import java.util.List; +import java.util.stream.Collectors; + +import static org.diylc.utils.FileUtils.extractFileName; /** * Contains all the back-end logic for using the cloud and manipulating projects on the cloud. @@ -56,17 +71,19 @@ import java.util.List; */ public class ChatbotPresenter { - public static final ChatbotPresenter Instance = new ChatbotPresenter(); - private static String ERROR = "Error"; private final static Logger LOG = Logger.getLogger(ChatbotPresenter.class); private static final Object SUCCESS = "Success"; + private final IPlugInPort plugInPort; + private static final ObjectMapper MAPPER = new ObjectMapper(); private IChatbotAPI service; private String serviceUrl; - private ChatbotPresenter() {} + public ChatbotPresenter(IPlugInPort plugInPort) { + this.plugInPort = plugInPort; + } private IChatbotAPI getService() { if (service == null) { @@ -78,10 +95,193 @@ public class ChatbotPresenter { return service; } - public String promptChatbot(String project, String netlist, String prompt) throws NotLoggedInException { + public String promptChatbot(String prompt) throws NotLoggedInException { if (!CloudPresenter.Instance.isLoggedIn()) throw new NotLoggedInException(); - return getService().promptChatbot(CloudPresenter.Instance.getCurrentUsername(), CloudPresenter.Instance.getCurrentToken(), - CloudPresenter.Instance.getMachineId(), project, netlist, prompt); + + String currentFile = plugInPort.getCurrentFileName(); + String fileName = extractFileName(currentFile); + + String netlist; + try { + netlist = extractNetlists(); + } catch (NetlistException e) { + LOG.error("Error extracting netlist", e); + netlist = null; + } + + return getService().promptChatbot(CloudPresenter.Instance.getCurrentUsername(), + CloudPresenter.Instance.getCurrentToken(), + CloudPresenter.Instance.getMachineId(), fileName, netlist, prompt); } + + private static final Set> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class); + + private String extractNetlists() throws NetlistException { + List netlists = plugInPort.extractNetlists(true); + + StringBuilder sb = new StringBuilder(); + sb.append("Components: "); + + Set> components = netlists.stream().flatMap(n -> n.getGroups().stream() + .flatMap(g -> g.getNodes().stream().map(node -> node.getComponent()))) + .collect(Collectors.toSet()); + + List> componentDescriptors = new ArrayList<>(); + + components.forEach(c -> { + ComponentType componentType = ComponentProcessor.getInstance() + .extractComponentTypeFrom((Class>) c.getClass()); + List properties = + ComponentProcessor.getInstance().extractProperties(c.getClass()); + + Map 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); + } + + for (Netlist netlist : netlists) { + sb.append("Switch configuration: ").append(netlist.getSwitchSetup()).append("\n\n") + .append("Connected node groups:\n\n"); + for (Group v : netlist.getSortedGroups()) { + sb.append(v.getSortedNodes()).append("\n"); + } + } + return sb.toString(); + } + + public String getInitialChatContents() { + Random r = new Random(); + + String chatHtml = + "
Welcome to the DIYLC AI Assistant!


\n" + + "
I'm here to help you design, build, and troubleshoot your electronics projects using DIY Layout Creator (DIYLC). You can ask me questions about:

\n" + + // "
    \n" + + "\n" + + "- Electronics theory or concepts: e.g., '" + electronicsQuestions[r.nextInt( + electronicsQuestions.length)] + "'
    \n" + + "- DIYLC features and usage: e.g., '" + diylcQuestions[r.nextInt(diylcQuestions.length)] + "'
    \n" + + "- Your current circuit project: e.g., '" + circuitQuestions[r.nextInt(circuitQuestions.length)] + "'
    \n" + + // "

\n" + + "
\n" + + "
Feel free to ask your own question or use one of these examples to get started!

\n"; + + if (!CloudPresenter.Instance.isLoggedIn()) { + chatHtml = chatHtml + "
Please log into your DIYLC Cloud account in order to use the AI Assistant.

\n"; + } + + return chatHtml; + } + + public static final String ASSISTANT = "assistant"; + public static final String SYSTEM = "system"; + public static final String USER = "user"; + public static final String TEMPORARY = "temporary"; + + String[] electronicsQuestions = { + "What is Ohm's Law, and how do voltage, current, and resistance relate to each other?", + "Why are resistors important in circuits, and what happens if the resistor value is incorrect?", + "What is a capacitor, and how does it behave differently from a resistor?", + "How does an inductor work, and where might you find one in common DIY projects?", + "What's the difference between AC (Alternating Current) and DC (Direct Current), and where is each used?", + "How do diodes work, and why are they essential in electronics?", + "What's the difference between series and parallel circuits, and why does it matter?", + "How does a transistor function, and what role does it typically play in a circuit?", + "Why is grounding important in electronic circuits, and what exactly does it do?", + "What is impedance, and why is it important when connecting speakers or audio equipment?", + "What exactly is an operational amplifier (op amp), and what are its common uses in audio circuits?", + "How does a relay work, and when would you choose a relay over a transistor or MOSFET?", + "What's the purpose of using a fuse or circuit breaker in a DIY electronics project?", + "Why do certain electronic components need specific voltage ratings?", + "What is electromagnetic interference (EMI), and how can you minimize it in your projects?", + "What does polarity mean for components like electrolytic capacitors and diodes, and why is it important?", + "How does filtering work to remove unwanted noise from audio signals or power supplies?", + "What is signal clipping in audio circuits, and how can you prevent it?", + "Why do some circuits require a heatsink, and how do heatsinks help protect components?", + "What is frequency response, and why is it crucial for audio circuits like guitar pedals and amplifiers?", + "How do you calculate the correct resistor value for limiting current to an LED?", + "What is the role of a voltage regulator, and how does it keep voltage stable?", + "How can you measure and verify voltage and current safely using a multimeter?", + "What does it mean when a circuit is described as having a 'high input impedance' or 'low output impedance,' and why does it matter?", + "How can you determine if components in your circuit are properly rated for the voltage and current they're experiencing?" + }; + + 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 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?", + "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?", + "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?", + "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?", + "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?" + }; + + String[] circuitQuestions = { + "What's the purpose of this specific resistor in my circuit?", + "How do I correctly determine capacitor values for my power supply?", + "How should I route wiring to reduce interference and noise?", + "Which component should I choose for this switching application, a MOSFET or BJT?", + "How do I verify if my ground connections are optimal?", + "Why might this transistor get hot during operation?", + "What's the best approach to simplify this section of my circuit?", + "How can I ensure my circuit is safe against short circuits?", + "What could be causing voltage instability in this circuit?", + "How do I properly size resistors to protect LEDs?", + "What are the steps to debug unexpected oscillations in my circuit?", + "Should I add a bypass capacitor here, and what value should I use?", + "How can I check if my components are operating within their rated specifications?", + "Is this the right way to connect multiple power rails in my design?", + "How can I test my circuit before physically assembling it?", + "What are good layout practices to minimize electromagnetic interference?", + "How do I identify if my circuit has any redundant or unnecessary components?", + "Why might the voltage regulator not output the expected voltage?", + "What's the recommended method for isolating digital and analog grounds?", + "How do I safely integrate high-voltage components into my DIY circuit?", + "Why isn't my LED lighting up when powered?", + "How do I troubleshoot a circuit that's drawing more current than expected?", + "Why does my amplifier circuit produce a humming or buzzing sound?", + "How can I detect and fix intermittent connections in my circuit?", + "What should I check if my circuit isn't responding to input signals as expected?", + "How do I determine if my soldering joints are causing reliability issues?", + "What steps can I take if my microcontroller circuit isn't functioning as intended?", + "How do I troubleshoot a noisy or unstable sensor reading?" + }; } diff --git a/diylc/diylc-core/src/main/java/org/diylc/utils/FileUtils.java b/diylc/diylc-core/src/main/java/org/diylc/utils/FileUtils.java new file mode 100644 index 00000000..20e3689a --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/utils/FileUtils.java @@ -0,0 +1,24 @@ +package org.diylc.utils; + +import java.io.File; + +public class FileUtils { + + /** + * Extracts the base file name without extension from a full path. + * For example: "/path/to/myfile.diy" returns "myfile" + */ + public static String extractFileName(String path) { + if (path == null || path.isEmpty()) { + return "Untitled"; + } + // Get the last part after the last separator + String fileName = path.substring(path.lastIndexOf(File.separator) + 1); + // Remove the extension if present + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex > 0) { + fileName = fileName.substring(0, dotIndex); + } + return fileName; + } +} diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java index 1239c7e1..0398f0fb 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java +++ b/diylc/diylc-library/src/main/java/org/diylc/netlist/GuitarDiagramAnalyzer.java @@ -119,7 +119,7 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN return Set.of(NetlistSwitchPreference.WITH); } - protected Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException { + protected Summary summarize(Netlist netlist) throws TreeException { List notes = new ArrayList(); Tree tree = constructTree(netlist); @@ -128,6 +128,28 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN return new Summary(netlist, "No pickups are connected to the output jack"); } + processTree(tree, notes); + + StringBuilder sb = new StringBuilder(); + + String asciiTree = tree.toAsciiString(); + + sb.append("

") + .append("") + .append(asciiTree) + .append(""); + if (!notes.isEmpty()) { + Collections.sort(notes); + sb.append("

"); + for (String v : notes) { + sb.append(v).append("
"); + } + } + + return new Summary(netlist, sb.toString()); + } + + private void processTree(Tree tree, List notes) { Tree pickupTree = tree.filter(PICKUP_TYPES); if (pickupTree == null || pickupTree.getChildren().isEmpty()) { @@ -242,165 +264,177 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN Set> pots = tree.extractComponents(POT_TYPES); for (IDIYComponent pot : pots) { - TreeLeaf leaf1 = new TreeLeaf(pot, 0, 1); - TreeLeaf leaf2 = new TreeLeaf(pot, 1, 2); - Tree tree1 = tree.locate(leaf1, false); - Tree tree2 = tree.locate(leaf2, false); - - if ((tree1 == null && tree2 != null) || (tree1 != null && tree2 == null)) { - boolean tonePotReversed = tree1 != null; - Tree t = tree1 == null ? tree2 : tree1; - Tree p = tree.findParent(t); - if (p != null && p.getChildren().size() == 2) { - Set> caps = p.extractComponents(CAP_TYPES); - if (caps != null && caps.size() == 1) { - for (Map.Entry, Tree> r : pickupRoots.entrySet()) { - Tree toneParent = tree.findCommonParent(p, r.getValue()); - if (toneParent != null - && toneParent.getConnectionType() == TreeConnectionType.Parallel) { - if (tonePotReversed) { - List> pickupList = tonePotPickupReverseMap.get(pot); - if (pickupList == null) { - pickupList = new ArrayList>(); - tonePotPickupReverseMap.put(pot, pickupList); - } - pickupList.add(r.getKey()); - } else { - List> pickupList = tonePotPickupMap.get(pot); - if (pickupList == null) { - pickupList = new ArrayList>(); - tonePotPickupMap.put(pot, pickupList); - } - pickupList.add(r.getKey()); - } - } - } - } - } - } - - // volume - if (tree1 == null || tree2 == null) - continue; - for (Map.Entry, Tree> r : pickupRoots.entrySet()) { - Tree commonParent1 = tree.findCommonParent(tree1, r.getValue()); - Tree commonParent2 = tree.findCommonParent(tree2, r.getValue()); - locateTrebleBleed(tree, volPotTrebleBleedMap, pot, tree1); - - if (commonParent1 != null && commonParent2 != null - && commonParent1.getConnectionType() == TreeConnectionType.Series - && commonParent2.getConnectionType() == TreeConnectionType.Parallel) { - List> pickupList = volPotPickupMap.get(pot); - if (pickupList == null) { - pickupList = new ArrayList>(); - volPotPickupMap.put(pot, pickupList); - } - pickupList.add(r.getKey()); - } else if (commonParent1 != null && commonParent2 != null - && commonParent1.getConnectionType() == TreeConnectionType.Parallel - && commonParent2.getConnectionType() == TreeConnectionType.Series) { - List> pickupList = volPotPickupReverseMap.get(pot); - if (pickupList == null) { - pickupList = new ArrayList>(); - volPotPickupReverseMap.put(pot, pickupList); - } - pickupList.add(r.getKey()); - } - } + processPotentiometer(pot, tree, pickupRoots, tonePotPickupReverseMap, tonePotPickupMap, + volPotTrebleBleedMap, volPotPickupMap, volPotPickupReverseMap); } for (Map.Entry, List>> e : volPotPickupMap.entrySet()) { - StringBuilder sb = new StringBuilder( - "'" + e.getKey().getName() + "' potentiometer acts as a volume control for "); - boolean first = true; - List pickupNames = - e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); - for (String pickupName : pickupNames) { - if (!first) - sb.append(pickupNames.size() == 2 ? " and " : ", "); - first = false; - sb.append("'").append(pickupName).append("'"); - } - notes.add(sb.toString()); - - Set> trebleBleedComponents = volPotTrebleBleedMap.get(e.getKey()); - if (trebleBleedComponents != null && trebleBleedComponents.size() > 0) { - - String trebleBleadNote = trebleBleedComponents.stream() - .sorted(Comparator.comparing(c -> c.getName())) - .map(c -> "'" + c.getName() + "'") - .collect(Collectors.joining(", ")) + " form a treble bleed network on the '" + e.getKey().getName() + "' volume control"; - notes.add(trebleBleadNote); - } + outputVolumePotentiometer(e, notes, volPotTrebleBleedMap); } for (Map.Entry, List>> e : volPotPickupReverseMap .entrySet()) { - StringBuilder sb = new StringBuilder( - "'" + e.getKey().getName() + "' potentiometer acts as a volume control for "); - boolean first = true; - List pickupNames = - e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); - for (String pickupName : pickupNames) { - if (!first) - sb.append(pickupNames.size() == 2 ? " and " : ", "); - first = false; - sb.append("'").append(pickupName).append("'"); - } - sb.append(", but is wired in REVERSE!"); - notes.add(sb.toString()); + outputVolumePotReversed(e, notes); } for (Map.Entry, List>> e : tonePotPickupMap.entrySet()) { - StringBuilder sb = new StringBuilder( - "'" + e.getKey().getName() + "' potentiometer acts as a tone control for "); - boolean first = true; - List pickupNames = - e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); - for (String pickupName : pickupNames) { - if (!first) - sb.append(pickupNames.size() == 2 ? " and " : ", "); - first = false; - sb.append("'").append(pickupName).append("'"); - } - notes.add(sb.toString()); + outputTonePotentiometer(e, notes); } for (Map.Entry, List>> e : tonePotPickupReverseMap .entrySet()) { - StringBuilder sb = new StringBuilder( - "'" + e.getKey().getName() + "' potentiometer acts as a tone control for "); - boolean first = true; - List pickupNames = - e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); - for (String pickupName : pickupNames) { - if (!first) - sb.append(pickupNames.size() == 2 ? " and " : ", "); - first = false; - sb.append("'").append(pickupName).append("'"); + outputTonePotentiometerReversed(e, notes); + } + } + } + + private static void outputTonePotentiometerReversed(Map.Entry, List>> e, + List notes) { + StringBuilder sb = new StringBuilder( + "'" + e.getKey().getName() + "' potentiometer acts as a tone control for "); + boolean first = true; + List pickupNames = + e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); + for (String pickupName : pickupNames) { + if (!first) + sb.append(pickupNames.size() == 2 ? " and " : ", "); + first = false; + sb.append("'").append(pickupName).append("'"); + } + sb.append(", but is wired in REVERSE!"); + notes.add(sb.toString()); + } + + private static void outputTonePotentiometer(Map.Entry, List>> e, + List notes) { + StringBuilder sb = new StringBuilder( + "'" + e.getKey().getName() + "' potentiometer acts as a tone control for "); + boolean first = true; + List pickupNames = + e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); + for (String pickupName : pickupNames) { + if (!first) + sb.append(pickupNames.size() == 2 ? " and " : ", "); + first = false; + sb.append("'").append(pickupName).append("'"); + } + notes.add(sb.toString()); + } + + private static void outputVolumePotReversed(Map.Entry, List>> e, + List notes) { + StringBuilder sb = new StringBuilder( + "'" + e.getKey().getName() + "' potentiometer acts as a volume control for "); + boolean first = true; + List pickupNames = + e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); + for (String pickupName : pickupNames) { + if (!first) + sb.append(pickupNames.size() == 2 ? " and " : ", "); + first = false; + sb.append("'").append(pickupName).append("'"); + } + sb.append(", but is wired in REVERSE!"); + notes.add(sb.toString()); + } + + private static void outputVolumePotentiometer(Map.Entry, List>> e, + List notes, Map, Set>> volPotTrebleBleedMap) { + StringBuilder sb = new StringBuilder( + "'" + e.getKey().getName() + "' potentiometer acts as a volume control for "); + boolean first = true; + List pickupNames = + e.getValue().stream().map(x -> x.getName()).sorted().collect(Collectors.toList()); + for (String pickupName : pickupNames) { + if (!first) + sb.append(pickupNames.size() == 2 ? " and " : ", "); + first = false; + sb.append("'").append(pickupName).append("'"); + } + notes.add(sb.toString()); + + Set> trebleBleedComponents = volPotTrebleBleedMap.get(e.getKey()); + if (trebleBleedComponents != null && trebleBleedComponents.size() > 0) { + + String trebleBleadNote = trebleBleedComponents.stream() + .sorted(Comparator.comparing(c -> c.getName())) + .map(c -> "'" + c.getName() + "'") + .collect(Collectors.joining(", ")) + " form a treble bleed network on the '" + e.getKey().getName() + "' volume control"; + notes.add(trebleBleadNote); + } + } + + private void processPotentiometer(IDIYComponent pot, Tree tree, Map, Tree> pickupRoots, + Map, List>> tonePotPickupReverseMap, + Map, List>> tonePotPickupMap, + Map, Set>> volPotTrebleBleedMap, + Map, List>> volPotPickupMap, + Map, List>> volPotPickupReverseMap) { + TreeLeaf leaf1 = new TreeLeaf(pot, 0, 1); + TreeLeaf leaf2 = new TreeLeaf(pot, 1, 2); + Tree tree1 = tree.locate(leaf1, false); + Tree tree2 = tree.locate(leaf2, false); + + if ((tree1 == null && tree2 != null) || (tree1 != null && tree2 == null)) { + boolean tonePotReversed = tree1 != null; + Tree t = tree1 == null ? tree2 : tree1; + Tree p = tree.findParent(t); + if (p != null && p.getChildren().size() == 2) { + Set> caps = p.extractComponents(CAP_TYPES); + if (caps != null && caps.size() == 1) { + for (Map.Entry, Tree> r : pickupRoots.entrySet()) { + Tree toneParent = tree.findCommonParent(p, r.getValue()); + if (toneParent != null + && toneParent.getConnectionType() == TreeConnectionType.Parallel) { + if (tonePotReversed) { + List> pickupList = tonePotPickupReverseMap.get(pot); + if (pickupList == null) { + pickupList = new ArrayList>(); + tonePotPickupReverseMap.put(pot, pickupList); + } + pickupList.add(r.getKey()); + } else { + List> pickupList = tonePotPickupMap.get(pot); + if (pickupList == null) { + pickupList = new ArrayList>(); + tonePotPickupMap.put(pot, pickupList); + } + pickupList.add(r.getKey()); + } + } + } } - sb.append(", but is wired in REVERSE!"); - notes.add(sb.toString()); } } - StringBuilder sb = new StringBuilder(); - - String asciiTree = tree.toAsciiString(); + // volume + if (tree1 == null || tree2 == null) + return; + for (Map.Entry, Tree> r : pickupRoots.entrySet()) { + Tree commonParent1 = tree.findCommonParent(tree1, r.getValue()); + Tree commonParent2 = tree.findCommonParent(tree2, r.getValue()); + locateTrebleBleed(tree, volPotTrebleBleedMap, pot, tree1); - sb.append("

") - .append("") - .append(asciiTree) - .append(""); - if (!notes.isEmpty()) { - Collections.sort(notes); - sb.append("

"); - for (String v : notes) { - sb.append(v).append("
"); + if (commonParent1 != null && commonParent2 != null + && commonParent1.getConnectionType() == TreeConnectionType.Series + && commonParent2.getConnectionType() == TreeConnectionType.Parallel) { + List> pickupList = volPotPickupMap.get(pot); + if (pickupList == null) { + pickupList = new ArrayList>(); + volPotPickupMap.put(pot, pickupList); + } + pickupList.add(r.getKey()); + } else if (commonParent1 != null && commonParent2 != null + && commonParent1.getConnectionType() == TreeConnectionType.Parallel + && commonParent2.getConnectionType() == TreeConnectionType.Series) { + List> pickupList = volPotPickupReverseMap.get(pot); + if (pickupList == null) { + pickupList = new ArrayList>(); + volPotPickupReverseMap.put(pot, pickupList); + } + pickupList.add(r.getKey()); } } - - return new Summary(netlist, sb.toString()); } private void locateTrebleBleed(Tree tree, diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java index 445c2ab8..abf9ac62 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java +++ b/diylc/diylc-library/src/main/java/org/diylc/netlist/MultimeterAnalyzer.java @@ -51,7 +51,7 @@ public class MultimeterAnalyzer extends AbstractNetlistAnalyzer implements INetl return EnumSet.allOf(NetlistSwitchPreference.class); } - protected Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException { + protected Summary summarize(Netlist netlist) throws TreeException { Tree tree = constructTree(netlist); if (tree == null) { diff --git a/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java b/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java index 0db6676d..eda49bde 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java +++ b/diylc/diylc-library/src/main/java/org/diylc/netlist/SpiceAnalyzer.java @@ -69,7 +69,7 @@ public class SpiceAnalyzer extends AbstractNetlistAnalyzer implements INetlistAn return EnumSet.allOf(NetlistSwitchPreference.class); } - protected Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException { + protected Summary summarize(Netlist netlist) throws TreeException { // grab all components that are in the netlist List groups = netlist.getSortedGroups(); diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/SummarizeNetlistAction.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/SummarizeNetlistAction.java index 9454d663..3f405227 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/SummarizeNetlistAction.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/actions/analyze/SummarizeNetlistAction.java @@ -53,7 +53,7 @@ public class SummarizeNetlistAction extends AbstractAction { throw new Exception("The generated netlist is empty, nothing to show."); } - return summarizer.summarize(netlists, null); + return summarizer.summarize(netlists); } @Override diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java index 517c146a..e7516a73 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java @@ -17,93 +17,6 @@ public class ChatbotPane extends JPanel { private static final long serialVersionUID = 1L; private static final Logger LOG = Logger.getLogger(ChatbotPane.class); - - public static final String ASSISTANT = "assistant"; - public static final String SYSTEM = "system"; - public static final String USER = "user"; - public static final String TEMPORARY = "temporary"; - - String[] electronicsQuestions = { - "What is Ohm's Law, and how do voltage, current, and resistance relate to each other?", - "Why are resistors important in circuits, and what happens if the resistor value is incorrect?", - "What is a capacitor, and how does it behave differently from a resistor?", - "How does an inductor work, and where might you find one in common DIY projects?", - "What's the difference between AC (Alternating Current) and DC (Direct Current), and where is each used?", - "How do diodes work, and why are they essential in electronics?", - "What's the difference between series and parallel circuits, and why does it matter?", - "How does a transistor function, and what role does it typically play in a circuit?", - "Why is grounding important in electronic circuits, and what exactly does it do?", - "What is impedance, and why is it important when connecting speakers or audio equipment?", - "What exactly is an operational amplifier (op amp), and what are its common uses in audio circuits?", - "How does a relay work, and when would you choose a relay over a transistor or MOSFET?", - "What's the purpose of using a fuse or circuit breaker in a DIY electronics project?", - "Why do certain electronic components need specific voltage ratings?", - "What is electromagnetic interference (EMI), and how can you minimize it in your projects?", - "What does polarity mean for components like electrolytic capacitors and diodes, and why is it important?", - "How does filtering work to remove unwanted noise from audio signals or power supplies?", - "What is signal clipping in audio circuits, and how can you prevent it?", - "Why do some circuits require a heatsink, and how do heatsinks help protect components?", - "What is frequency response, and why is it crucial for audio circuits like guitar pedals and amplifiers?", - "How do you calculate the correct resistor value for limiting current to an LED?", - "What is the role of a voltage regulator, and how does it keep voltage stable?", - "How can you measure and verify voltage and current safely using a multimeter?", - "What does it mean when a circuit is described as having a 'high input impedance' or 'low output impedance,' and why does it matter?", - "How can you determine if components in your circuit are properly rated for the voltage and current they're experiencing?" - }; - - 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 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?", - "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?", - "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?", - "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?", - "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?" - }; - - String[] circuitQuestions = { - "What's the purpose of this specific resistor in my circuit?", - "How do I correctly determine capacitor values for my power supply?", - "How should I route wiring to reduce interference and noise?", - "Which component should I choose for this switching application, a MOSFET or BJT?", - "How do I verify if my ground connections are optimal?", - "Why might this transistor get hot during operation?", - "What's the best approach to simplify this section of my circuit?", - "How can I ensure my circuit is safe against short circuits?", - "What could be causing voltage instability in this circuit?", - "How do I properly size resistors to protect LEDs?", - "What are the steps to debug unexpected oscillations in my circuit?", - "Should I add a bypass capacitor here, and what value should I use?", - "How can I check if my components are operating within their rated specifications?", - "Is this the right way to connect multiple power rails in my design?", - "How can I test my circuit before physically assembling it?", - "What are good layout practices to minimize electromagnetic interference?", - "How do I identify if my circuit has any redundant or unnecessary components?", - "Why might the voltage regulator not output the expected voltage?", - "What's the recommended method for isolating digital and analog grounds?", - "How do I safely integrate high-voltage components into my DIY circuit?", - "Why isn't my LED lighting up when powered?", - "How do I troubleshoot a circuit that's drawing more current than expected?", - "Why does my amplifier circuit produce a humming or buzzing sound?", - "How can I detect and fix intermittent connections in my circuit?", - "What should I check if my circuit isn't responding to input signals as expected?", - "How do I determine if my soldering joints are causing reliability issues?", - "What steps can I take if my microcontroller circuit isn't functioning as intended?", - "How do I troubleshoot a noisy or unstable sensor reading?" - }; public static Font DEFAULT_FONT = new Font("Square721 BT", Font.PLAIN, 12); private static final Font MONOSPACED_FONT = new Font("Monospaced", Font.PLAIN, 12); @@ -113,10 +26,9 @@ public class ChatbotPane extends JPanel { private ISwingUI swingUI; private IPlugInPort plugInPort; + private ChatbotPresenter chatbotPresenter; - private JLabel titleLabel; private JTextArea promptArea; - private JScrollPane chatScrollPane; private JEditorPane chatEditorPane; private JButton askButton; private JButton clearButton; @@ -125,6 +37,7 @@ public class ChatbotPane extends JPanel { super(); this.swingUI = swingUI; this.plugInPort = plugInPort; + this.chatbotPresenter = new ChatbotPresenter(plugInPort); setName("AI Assistant"); // Set panel background color @@ -148,17 +61,6 @@ public class ChatbotPane extends JPanel { gbc.gridwidth = 2; // Span both columns add(chatScrollPane, gbc); - // Prompt label - JLabel promptLabel = new JLabel("Prompt:"); - promptLabel.setForeground(TERMINAL_FG); - promptLabel.setFont(DEFAULT_FONT); - gbc.gridx = 0; - gbc.gridy = 2; - gbc.weightx = 0.0; - gbc.weighty = 0.0; - gbc.gridwidth = 1; -// add(promptLabel, gbc); - // Prompt scroll pane JScrollPane promptScrollPane = new JScrollPane(getPromptArea(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); @@ -310,7 +212,7 @@ public class ChatbotPane extends JPanel { TERMINAL_FG.getRed(), TERMINAL_FG.getGreen(), TERMINAL_FG.getBlue(), TERMINAL_BG.getRed(), TERMINAL_BG.getGreen(), TERMINAL_BG.getBlue()); - String sampleChat = getInitialChatContents(); + String sampleChat = chatbotPresenter.getInitialChatContents(); chatEditorPane.setContentType("text/html"); chatEditorPane.setText(htmlStyle + "
" + sampleChat + "
"); @@ -318,29 +220,6 @@ public class ChatbotPane extends JPanel { return chatEditorPane; } - private String getInitialChatContents() { - Random r = new Random(); - - String chatHtml = - "
Welcome to the DIYLC AI Assistant!


\n" + - "
I'm here to help you design, build, and troubleshoot your electronics projects using DIY Layout Creator (DIYLC). You can ask me questions about:

\n" + -// "
    \n" + - "\n" + - "- Electronics theory or concepts: e.g., '" + electronicsQuestions[r.nextInt( - electronicsQuestions.length)] + "'
    \n" + - "- DIYLC features and usage: e.g., '" + diylcQuestions[r.nextInt(diylcQuestions.length)] + "'
    \n" + - "- Your current circuit project: e.g., '" + circuitQuestions[r.nextInt(circuitQuestions.length)] + "'
    \n" + -// "

\n" + - "
\n" + - "
Feel free to ask your own question or use one of these examples to get started!

\n"; - - if (!CloudPresenter.Instance.isLoggedIn()) { - chatHtml = chatHtml + "
Please log into your DIYLC Cloud account in order to use the AI Assistant.

\n"; - } - - return chatHtml; - } - public JButton getAskButton() { if (askButton == null) { askButton = new JButton("Ask"); @@ -350,11 +229,10 @@ public class ChatbotPane extends JPanel { askButton.setBorderPainted(true); askButton.addActionListener(e -> { final String prompt = getPromptArea().getText(); - appendSection(USER, prompt); - appendSection(TEMPORARY, "Waiting for the response..."); + appendSection(ChatbotPresenter.USER, prompt); + appendSection(ChatbotPresenter.TEMPORARY, "Waiting for the response..."); getPromptArea().setText(null); - String currentFile = plugInPort.getCurrentFileName(); - String fileName = extractFileName(currentFile); + getAskButton().setEnabled(false); getClearButton().setEnabled(false); @@ -362,19 +240,19 @@ public class ChatbotPane extends JPanel { @Override public String doInBackground() throws Exception { - return ChatbotPresenter.Instance.promptChatbot(fileName, "", prompt); + return chatbotPresenter.promptChatbot(prompt); } @Override public void failed(Exception e) { - appendSection(SYSTEM, "Failed to retrieve the response from the server. Error: " + e.getMessage()); + appendSection(ChatbotPresenter.SYSTEM, "Failed to retrieve the response from the server. Error: " + e.getMessage()); LOG.error("Failed to retrieve the response from the server", e); getClearButton().setEnabled(true); } @Override public void complete(String result) { - appendSection(ASSISTANT, result); + appendSection(ChatbotPresenter.ASSISTANT, result); getClearButton().setEnabled(true); } }, false); @@ -396,24 +274,6 @@ public class ChatbotPane extends JPanel { getChatEditorPane().setText(text); } - /** - * Extracts the base file name without extension from a full path. - * For example: "/path/to/myfile.diy" returns "myfile" - */ - private String extractFileName(String path) { - if (path == null || path.isEmpty()) { - return "Untitled"; - } - // Get the last part after the last separator - String fileName = path.substring(path.lastIndexOf(File.separator) + 1); - // Remove the extension if present - int dotIndex = fileName.lastIndexOf('.'); - if (dotIndex > 0) { - fileName = fileName.substring(0, dotIndex); - } - return fileName; - } - public JButton getClearButton() { if (clearButton == null) { clearButton = new JButton("Clear");