chatbot enhancements

This commit is contained in:
Bane Stojkovic
2025-04-03 20:10:58 +02:00
parent aa3ae65a80
commit 1b82d8c7df
10 changed files with 425 additions and 303 deletions
+5
View File
@@ -57,6 +57,11 @@
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>
<version>0.10.2</version> <version>0.10.2</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
@@ -50,11 +50,10 @@ public interface INetlistAnalyzer {
* Summarizes all {@link Netlist}s provided. * Summarizes all {@link Netlist}s provided.
* *
* @param netlists * @param netlists
* @param preferredOutput optional, needed only where there's more than one possible output node.
* @return * @return
* @throws TreeException * @throws TreeException
*/ */
List<Summary> summarize(List<Netlist> netlists, Node preferredOutput) throws TreeException; List<Summary> summarize(List<Netlist> netlists) throws TreeException;
/** /**
* @return name of the font to be used for display. * @return name of the font to be used for display.
@@ -30,10 +30,10 @@ public abstract class AbstractNetlistAnalyzer {
public AbstractNetlistAnalyzer() {} public AbstractNetlistAnalyzer() {}
public List<Summary> summarize(List<Netlist> netlists, Node preferredOutput) throws TreeException { public List<Summary> summarize(List<Netlist> netlists) throws TreeException {
Map<String, Summary> summaries = new HashMap<String, Summary>(); Map<String, Summary> summaries = new HashMap<String, Summary>();
for (Netlist n : netlists) { for (Netlist n : netlists) {
Summary s = summarize(n, preferredOutput); Summary s = summarize(n);
if (summaries.containsKey(s.getSummary())) if (summaries.containsKey(s.getSummary()))
summaries.get(s.getSummary()).append(n); summaries.get(s.getSummary()).append(n);
else else
@@ -46,7 +46,7 @@ public abstract class AbstractNetlistAnalyzer {
return res; 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) { public Tree constructTreeBetween(Netlist netlist, Node nodeA, Node nodeB) {
Tree tree = new Tree(TreeConnectionType.Parallel); Tree tree = new Tree(TreeConnectionType.Parallel);
@@ -23,10 +23,19 @@ package org.diylc.plugins.chatbot.presenter;
import com.diyfever.httpproxy.PhpFlatProxy; import com.diyfever.httpproxy.PhpFlatProxy;
import com.diyfever.httpproxy.ProxyFactory; import com.diyfever.httpproxy.ProxyFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.checkerframework.checker.units.qual.C;
import org.checkerframework.checker.units.qual.N; import org.checkerframework.checker.units.qual.N;
import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.common.ComponentType;
import org.diylc.common.IPlugInPort;
import org.diylc.common.PropertyWrapper; 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.chatbot.model.IChatbotAPI;
import org.diylc.plugins.cloud.model.CommentEntity; import org.diylc.plugins.cloud.model.CommentEntity;
import org.diylc.plugins.cloud.model.IServiceAPI; 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.ComparatorFactory;
import org.diylc.presenter.ComponentProcessor; import org.diylc.presenter.ComponentProcessor;
import javax.swing.*;
import java.awt.*;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.URL; import java.net.URL;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.util.Collections; import java.util.*;
import java.util.List; 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. * 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 class ChatbotPresenter {
public static final ChatbotPresenter Instance = new ChatbotPresenter();
private static String ERROR = "Error"; private static String ERROR = "Error";
private final static Logger LOG = Logger.getLogger(ChatbotPresenter.class); private final static Logger LOG = Logger.getLogger(ChatbotPresenter.class);
private static final Object SUCCESS = "Success"; private static final Object SUCCESS = "Success";
private final IPlugInPort plugInPort;
private static final ObjectMapper MAPPER = new ObjectMapper();
private IChatbotAPI service; private IChatbotAPI service;
private String serviceUrl; private String serviceUrl;
private ChatbotPresenter() {} public ChatbotPresenter(IPlugInPort plugInPort) {
this.plugInPort = plugInPort;
}
private IChatbotAPI getService() { private IChatbotAPI getService() {
if (service == null) { if (service == null) {
@@ -78,10 +95,193 @@ public class ChatbotPresenter {
return service; return service;
} }
public String promptChatbot(String project, String netlist, String prompt) throws NotLoggedInException { public String promptChatbot(String prompt) throws NotLoggedInException {
if (!CloudPresenter.Instance.isLoggedIn()) if (!CloudPresenter.Instance.isLoggedIn())
throw new NotLoggedInException(); 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<Class<?>> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class);
private String extractNetlists() throws NetlistException {
List<Netlist> netlists = plugInPort.extractNetlists(true);
StringBuilder sb = new StringBuilder();
sb.append("Components: ");
Set<? extends IDIYComponent<?>> components = netlists.stream().flatMap(n -> n.getGroups().stream()
.flatMap(g -> g.getNodes().stream().map(node -> node.getComponent())))
.collect(Collectors.toSet());
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);
}
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 =
"<div class='system'>Welcome to the DIYLC AI Assistant!</div><br><br>\n" +
"<div class='assistant'>I'm here to help you design, build, and troubleshoot your electronics projects using DIY Layout Creator (DIYLC). You can ask me questions about:</div><br>\n" +
// "<ul>\n" +
"\n" +
"- <b>Electronics theory or concepts:</b> <i>e.g.,</i> '<span class='user'>" + electronicsQuestions[r.nextInt(
electronicsQuestions.length)] + "</span>'<br>\n" +
"- <b>DIYLC features and usage:</b> <i>e.g.,</i> '<span class='user'>" + diylcQuestions[r.nextInt(diylcQuestions.length)] + "</span>'<br>\n" +
"- <b>Your current circuit project:</b> <i>e.g.,</i> '<span class='user'>" + circuitQuestions[r.nextInt(circuitQuestions.length)] + "</span>'<br>\n" +
// "</ul><br>\n" +
"<br>\n" +
"<div class='assistant'>Feel free to ask your own question or use one of these examples to get started!</div><br>\n";
if (!CloudPresenter.Instance.isLoggedIn()) {
chatHtml = chatHtml + "<div class='system' id='login'>Please log into your DIYLC Cloud account in order to use the AI Assistant.</div><br>\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?"
};
} }
@@ -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;
}
}
@@ -119,7 +119,7 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN
return Set.of(NetlistSwitchPreference.WITH); return Set.of(NetlistSwitchPreference.WITH);
} }
protected Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException { protected Summary summarize(Netlist netlist) throws TreeException {
List<String> notes = new ArrayList<String>(); List<String> notes = new ArrayList<String>();
Tree tree = constructTree(netlist); 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"); 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("<br><br>")
.append("<font face='Courier New'>")
.append(asciiTree)
.append("</font>");
if (!notes.isEmpty()) {
Collections.sort(notes);
sb.append("<br><br>");
for (String v : notes) {
sb.append(v).append("<br>");
}
}
return new Summary(netlist, sb.toString());
}
private void processTree(Tree tree, List<String> notes) {
Tree pickupTree = tree.filter(PICKUP_TYPES); Tree pickupTree = tree.filter(PICKUP_TYPES);
if (pickupTree == null || pickupTree.getChildren().isEmpty()) { if (pickupTree == null || pickupTree.getChildren().isEmpty()) {
@@ -242,6 +264,112 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN
Set<IDIYComponent<?>> pots = tree.extractComponents(POT_TYPES); Set<IDIYComponent<?>> pots = tree.extractComponents(POT_TYPES);
for (IDIYComponent<?> pot : pots) { for (IDIYComponent<?> pot : pots) {
processPotentiometer(pot, tree, pickupRoots, tonePotPickupReverseMap, tonePotPickupMap,
volPotTrebleBleedMap, volPotPickupMap, volPotPickupReverseMap);
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : volPotPickupMap.entrySet()) {
outputVolumePotentiometer(e, notes, volPotTrebleBleedMap);
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : volPotPickupReverseMap
.entrySet()) {
outputVolumePotReversed(e, notes);
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : tonePotPickupMap.entrySet()) {
outputTonePotentiometer(e, notes);
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : tonePotPickupReverseMap
.entrySet()) {
outputTonePotentiometerReversed(e, notes);
}
}
}
private static void outputTonePotentiometerReversed(Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e,
List<String> notes) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a tone control for ");
boolean first = true;
List<String> 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<IDIYComponent<?>, List<IDIYComponent<?>>> e,
List<String> notes) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a tone control for ");
boolean first = true;
List<String> 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<IDIYComponent<?>, List<IDIYComponent<?>>> e,
List<String> notes) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a volume control for ");
boolean first = true;
List<String> 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<IDIYComponent<?>, List<IDIYComponent<?>>> e,
List<String> notes, Map<IDIYComponent<?>, Set<IDIYComponent<?>>> volPotTrebleBleedMap) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a volume control for ");
boolean first = true;
List<String> 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<IDIYComponent<?>> 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<IDIYComponent<?>, Tree> pickupRoots,
Map<IDIYComponent<?>, List<IDIYComponent<?>>> tonePotPickupReverseMap,
Map<IDIYComponent<?>, List<IDIYComponent<?>>> tonePotPickupMap,
Map<IDIYComponent<?>, Set<IDIYComponent<?>>> volPotTrebleBleedMap,
Map<IDIYComponent<?>, List<IDIYComponent<?>>> volPotPickupMap,
Map<IDIYComponent<?>, List<IDIYComponent<?>>> volPotPickupReverseMap) {
TreeLeaf leaf1 = new TreeLeaf(pot, 0, 1); TreeLeaf leaf1 = new TreeLeaf(pot, 0, 1);
TreeLeaf leaf2 = new TreeLeaf(pot, 1, 2); TreeLeaf leaf2 = new TreeLeaf(pot, 1, 2);
Tree tree1 = tree.locate(leaf1, false); Tree tree1 = tree.locate(leaf1, false);
@@ -281,7 +409,7 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN
// volume // volume
if (tree1 == null || tree2 == null) if (tree1 == null || tree2 == null)
continue; return;
for (Map.Entry<IDIYComponent<?>, Tree> r : pickupRoots.entrySet()) { for (Map.Entry<IDIYComponent<?>, Tree> r : pickupRoots.entrySet()) {
Tree commonParent1 = tree.findCommonParent(tree1, r.getValue()); Tree commonParent1 = tree.findCommonParent(tree1, r.getValue());
Tree commonParent2 = tree.findCommonParent(tree2, r.getValue()); Tree commonParent2 = tree.findCommonParent(tree2, r.getValue());
@@ -309,100 +437,6 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN
} }
} }
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : volPotPickupMap.entrySet()) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a volume control for ");
boolean first = true;
List<String> 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<IDIYComponent<?>> 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);
}
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : volPotPickupReverseMap
.entrySet()) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a volume control for ");
boolean first = true;
List<String> 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());
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : tonePotPickupMap.entrySet()) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a tone control for ");
boolean first = true;
List<String> 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());
}
for (Map.Entry<IDIYComponent<?>, List<IDIYComponent<?>>> e : tonePotPickupReverseMap
.entrySet()) {
StringBuilder sb = new StringBuilder(
"'" + e.getKey().getName() + "' potentiometer acts as a tone control for ");
boolean first = true;
List<String> 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());
}
}
StringBuilder sb = new StringBuilder();
String asciiTree = tree.toAsciiString();
sb.append("<br><br>")
.append("<font face='Courier New'>")
.append(asciiTree)
.append("</font>");
if (!notes.isEmpty()) {
Collections.sort(notes);
sb.append("<br><br>");
for (String v : notes) {
sb.append(v).append("<br>");
}
}
return new Summary(netlist, sb.toString());
}
private void locateTrebleBleed(Tree tree, private void locateTrebleBleed(Tree tree,
Map<IDIYComponent<?>, Set<IDIYComponent<?>>> volPotTrebleBleedMap, IDIYComponent<?> pot, Map<IDIYComponent<?>, Set<IDIYComponent<?>>> volPotTrebleBleedMap, IDIYComponent<?> pot,
Tree tree1) { Tree tree1) {
@@ -51,7 +51,7 @@ public class MultimeterAnalyzer extends AbstractNetlistAnalyzer implements INetl
return EnumSet.allOf(NetlistSwitchPreference.class); 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); Tree tree = constructTree(netlist);
if (tree == null) { if (tree == null) {
@@ -69,7 +69,7 @@ public class SpiceAnalyzer extends AbstractNetlistAnalyzer implements INetlistAn
return EnumSet.allOf(NetlistSwitchPreference.class); 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 // grab all components that are in the netlist
List<Group> groups = netlist.getSortedGroups(); List<Group> groups = netlist.getSortedGroups();
@@ -53,7 +53,7 @@ public class SummarizeNetlistAction extends AbstractAction {
throw new Exception("The generated netlist is empty, nothing to show."); throw new Exception("The generated netlist is empty, nothing to show.");
} }
return summarizer.summarize(netlists, null); return summarizer.summarize(netlists);
} }
@Override @Override
@@ -18,93 +18,6 @@ public class ChatbotPane extends JPanel {
private static final Logger LOG = Logger.getLogger(ChatbotPane.class); 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); public static Font DEFAULT_FONT = new Font("Square721 BT", Font.PLAIN, 12);
private static final Font MONOSPACED_FONT = new Font("Monospaced", Font.PLAIN, 12); private static final Font MONOSPACED_FONT = new Font("Monospaced", Font.PLAIN, 12);
private static final Color TERMINAL_BG = new Color(40, 40, 40); private static final Color TERMINAL_BG = new Color(40, 40, 40);
@@ -113,10 +26,9 @@ public class ChatbotPane extends JPanel {
private ISwingUI swingUI; private ISwingUI swingUI;
private IPlugInPort plugInPort; private IPlugInPort plugInPort;
private ChatbotPresenter chatbotPresenter;
private JLabel titleLabel;
private JTextArea promptArea; private JTextArea promptArea;
private JScrollPane chatScrollPane;
private JEditorPane chatEditorPane; private JEditorPane chatEditorPane;
private JButton askButton; private JButton askButton;
private JButton clearButton; private JButton clearButton;
@@ -125,6 +37,7 @@ public class ChatbotPane extends JPanel {
super(); super();
this.swingUI = swingUI; this.swingUI = swingUI;
this.plugInPort = plugInPort; this.plugInPort = plugInPort;
this.chatbotPresenter = new ChatbotPresenter(plugInPort);
setName("AI Assistant"); setName("AI Assistant");
// Set panel background color // Set panel background color
@@ -148,17 +61,6 @@ public class ChatbotPane extends JPanel {
gbc.gridwidth = 2; // Span both columns gbc.gridwidth = 2; // Span both columns
add(chatScrollPane, gbc); 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 // Prompt scroll pane
JScrollPane promptScrollPane = new JScrollPane(getPromptArea(), JScrollPane promptScrollPane = new JScrollPane(getPromptArea(),
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 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_FG.getRed(), TERMINAL_FG.getGreen(), TERMINAL_FG.getBlue(),
TERMINAL_BG.getRed(), TERMINAL_BG.getGreen(), TERMINAL_BG.getBlue()); TERMINAL_BG.getRed(), TERMINAL_BG.getGreen(), TERMINAL_BG.getBlue());
String sampleChat = getInitialChatContents(); String sampleChat = chatbotPresenter.getInitialChatContents();
chatEditorPane.setContentType("text/html"); chatEditorPane.setContentType("text/html");
chatEditorPane.setText(htmlStyle + "<body><pre>" + sampleChat + "</pre></body>"); chatEditorPane.setText(htmlStyle + "<body><pre>" + sampleChat + "</pre></body>");
@@ -318,29 +220,6 @@ public class ChatbotPane extends JPanel {
return chatEditorPane; return chatEditorPane;
} }
private String getInitialChatContents() {
Random r = new Random();
String chatHtml =
"<div class='system'>Welcome to the DIYLC AI Assistant!</div><br><br>\n" +
"<div class='assistant'>I'm here to help you design, build, and troubleshoot your electronics projects using DIY Layout Creator (DIYLC). You can ask me questions about:</div><br>\n" +
// "<ul>\n" +
"\n" +
"- <b>Electronics theory or concepts:</b> <i>e.g.,</i> '<span class='user'>" + electronicsQuestions[r.nextInt(
electronicsQuestions.length)] + "</span>'<br>\n" +
"- <b>DIYLC features and usage:</b> <i>e.g.,</i> '<span class='user'>" + diylcQuestions[r.nextInt(diylcQuestions.length)] + "</span>'<br>\n" +
"- <b>Your current circuit project:</b> <i>e.g.,</i> '<span class='user'>" + circuitQuestions[r.nextInt(circuitQuestions.length)] + "</span>'<br>\n" +
// "</ul><br>\n" +
"<br>\n" +
"<div class='assistant'>Feel free to ask your own question or use one of these examples to get started!</div><br>\n";
if (!CloudPresenter.Instance.isLoggedIn()) {
chatHtml = chatHtml + "<div class='system' id='login'>Please log into your DIYLC Cloud account in order to use the AI Assistant.</div><br>\n";
}
return chatHtml;
}
public JButton getAskButton() { public JButton getAskButton() {
if (askButton == null) { if (askButton == null) {
askButton = new JButton("Ask"); askButton = new JButton("Ask");
@@ -350,11 +229,10 @@ public class ChatbotPane extends JPanel {
askButton.setBorderPainted(true); askButton.setBorderPainted(true);
askButton.addActionListener(e -> { askButton.addActionListener(e -> {
final String prompt = getPromptArea().getText(); final String prompt = getPromptArea().getText();
appendSection(USER, prompt); appendSection(ChatbotPresenter.USER, prompt);
appendSection(TEMPORARY, "Waiting for the response..."); appendSection(ChatbotPresenter.TEMPORARY, "Waiting for the response...");
getPromptArea().setText(null); getPromptArea().setText(null);
String currentFile = plugInPort.getCurrentFileName();
String fileName = extractFileName(currentFile);
getAskButton().setEnabled(false); getAskButton().setEnabled(false);
getClearButton().setEnabled(false); getClearButton().setEnabled(false);
@@ -362,19 +240,19 @@ public class ChatbotPane extends JPanel {
@Override @Override
public String doInBackground() throws Exception { public String doInBackground() throws Exception {
return ChatbotPresenter.Instance.promptChatbot(fileName, "", prompt); return chatbotPresenter.promptChatbot(prompt);
} }
@Override @Override
public void failed(Exception e) { 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); LOG.error("Failed to retrieve the response from the server", e);
getClearButton().setEnabled(true); getClearButton().setEnabled(true);
} }
@Override @Override
public void complete(String result) { public void complete(String result) {
appendSection(ASSISTANT, result); appendSection(ChatbotPresenter.ASSISTANT, result);
getClearButton().setEnabled(true); getClearButton().setEnabled(true);
} }
}, false); }, false);
@@ -396,24 +274,6 @@ public class ChatbotPane extends JPanel {
getChatEditorPane().setText(text); 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() { public JButton getClearButton() {
if (clearButton == null) { if (clearButton == null) {
clearButton = new JButton("Clear"); clearButton = new JButton("Clear");