Compare commits

...

8 Commits

Author SHA1 Message Date
Bane Stojkovic 725c06992b release 2025-04-21 20:44:08 +02:00
Bane Stojkovic ad81da8d54 chatbot enhancements 2025-04-21 20:09:40 +02:00
Bane Stojkovic 39d667f0c9 Update user_manual.sql 2025-04-20 22:11:42 +02:00
Bane Stojkovic 76934af206 charbot enhancements 2025-04-18 22:20:46 +02:00
Bane Stojkovic f7504172c6 chatbot improvements 2025-04-17 23:18:08 +02:00
Bane Stojkovic 0b7cb91048 chatbot enhancements 2025-04-17 23:13:27 +02:00
Bane Stojkovic 0d1ecf67ff structure improvement 2025-04-17 21:02:58 +02:00
Bane Stojkovic 610f2f71eb improve netlist 2025-04-16 23:01:59 +02:00
24 changed files with 623 additions and 191 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.diylc</groupId> <groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId> <artifactId>diylc-parent</artifactId>
<version>5.2.0</version> <version>5.3.0</version>
</parent> </parent>
<artifactId>diylc-core</artifactId> <artifactId>diylc-core</artifactId>
@@ -45,6 +45,10 @@ public enum EventType {
* has been either reloaded or loaded with undo/redo operations. * has been either reloaded or loaded with undo/redo operations.
*/ */
PROJECT_LOADED, 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 * 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. * 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.appframework.simplemq.MessageDispatcher;
import org.diylc.plugins.chatbot.service.ChatbotService; import org.diylc.plugins.chatbot.service.ChatbotService;
import org.diylc.plugins.cloud.service.CloudService; import org.diylc.plugins.cloud.service.CloudService;
import org.diylc.presenter.DrawingManager;
public interface IServiceManager { public interface IServiceManager {
@@ -11,4 +12,6 @@ public interface IServiceManager {
ChatbotService getChatbotService(); ChatbotService getChatbotService();
MessageDispatcher<EventType> getMessageDispatcher(); 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.PhpFlatProxy;
import com.diyfever.httpproxy.ProxyFactory; import com.diyfever.httpproxy.ProxyFactory;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.appframework.miscutils.ConfigurationManager;
@@ -34,13 +36,13 @@ import org.diylc.common.PropertyWrapper;
import org.diylc.core.IDIYComponent; import org.diylc.core.IDIYComponent;
import org.diylc.core.ISwitch; import org.diylc.core.ISwitch;
import org.diylc.netlist.*; import org.diylc.netlist.*;
import org.diylc.plugins.chatbot.model.ChatMessageEntity; import org.diylc.plugins.chatbot.model.*;
import org.diylc.plugins.chatbot.model.IChatbotAPI;
import org.diylc.plugins.chatbot.model.SubscriptionEntity;
import org.diylc.plugins.cloud.model.IServiceAPI; import org.diylc.plugins.cloud.model.IServiceAPI;
import org.diylc.plugins.cloud.service.NotLoggedInException; import org.diylc.plugins.cloud.service.NotLoggedInException;
import org.diylc.presenter.ComponentProcessor; import org.diylc.presenter.ComponentProcessor;
import org.diylc.presenter.ContinuityArea;
import org.diylc.utils.FileUtils; import org.diylc.utils.FileUtils;
import org.diylc.utils.ReflectionUtils;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
@@ -48,7 +50,6 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static org.diylc.utils.FileUtils.extractFileName; import static org.diylc.utils.FileUtils.extractFileName;
@@ -59,11 +60,11 @@ import static org.diylc.utils.FileUtils.extractFileName;
*/ */
public class ChatbotService { 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 final static Logger LOG = Logger.getLogger(ChatbotService.class);
private static final ObjectMapper MAPPER = new ObjectMapper(); 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!"; public static final String WELCOME_MESSAGE = "Welcome to the DIYLC AI Assistant!";
private final IPlugInPort plugInPort; private final IPlugInPort plugInPort;
@@ -79,14 +80,14 @@ public class ChatbotService {
@Override @Override
public EnumSet<EventType> getSubscribedEventTypes() { public EnumSet<EventType> getSubscribedEventTypes() {
return EnumSet.of(EventType.PROJECT_LOADED, EventType.FILE_STATUS_CHANGED); return EnumSet.of(EventType.PROJECT_LOADED, EventType.PROJECT_SAVED);
} }
@Override @Override
public void processMessage(EventType eventType, Object... objects) { public void processMessage(EventType eventType, Object... objects) {
if (eventType == EventType.PROJECT_LOADED) { if (eventType == EventType.PROJECT_LOADED) {
ChatbotService.this.currentProjectName = extractFileName((String)objects[2]); 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]); String newProjectName = extractFileName((String)objects[0]);
if (!Objects.equals(ChatbotService.this.currentProjectName, newProjectName)) { if (!Objects.equals(ChatbotService.this.currentProjectName, newProjectName)) {
if (plugInPort.getCloudService().isLoggedIn()) { if (plugInPort.getCloudService().isLoggedIn()) {
@@ -117,24 +118,68 @@ public class ChatbotService {
if (!plugInPort.getCloudService().isLoggedIn()) if (!plugInPort.getCloudService().isLoggedIn())
throw new NotLoggedInException(); throw new NotLoggedInException();
LOG.info("Prompting chatbot: " + prompt);
String currentFile = plugInPort.getCurrentFileName(); String currentFile = plugInPort.getCurrentFileName();
String fileName = extractFileName(currentFile); String fileName = extractFileName(currentFile);
String netlist; File aiProjectFile = null;
File netlistFile = null;
try { try {
netlist = extractNetlists(); AiProject aiProject = extractAiProject();
if (netlist != null) { String aiProjectStr = null;
netlistFile = File.createTempFile("diylc_netlist_", ".txt"); try {
try (FileWriter writer = new FileWriter(netlistFile)) { aiProjectStr = MAPPER.writeValueAsString(aiProject);
writer.write(netlist); } 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 // 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); LOG.error("Error extracting or saving netlist", e);
netlist = null;
} }
try { try {
@@ -143,117 +188,19 @@ public class ChatbotService {
plugInPort.getCloudService().getCurrentToken(), plugInPort.getCloudService().getCurrentToken(),
plugInPort.getCloudService().getMachineId(), plugInPort.getCloudService().getMachineId(),
fileName, fileName,
netlistFile, aiProjectFile,
prompt); prompt);
} finally { } finally {
// Clean up the temp file immediately after use // Clean up the temp file immediately after use
if (netlistFile != null && netlistFile.exists()) { if (aiProjectFile != null && aiProjectFile.exists()) {
netlistFile.delete(); aiProjectFile.delete();
} }
} }
} }
private static final Set<Class<?>> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class); private AiProject extractAiProject() throws NetlistException {
List<ContinuityArea> continuityAreas = plugInPort.getDrawingManager().getContinuityAreas();
private String extractNetlists() throws NetlistException { return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas);
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);
}
} }
public SubscriptionEntity getSubscriptionInfo() throws NotLoggedInException { public SubscriptionEntity getSubscriptionInfo() throws NotLoggedInException {
@@ -317,23 +264,23 @@ public class ChatbotService {
public static final String[] diylcQuestions = { public static final String[] diylcQuestions = {
"How do I add a component to my layout in DIYLC?", "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 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?", "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?", "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 save my DIYLC project and reopen it later on?",
"How do I export my DIYLC layout as an image or PDF file?", "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?", "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?", "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?", "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?", "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 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?", "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 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?", "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?", "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?" "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; return chatbotService;
} }
@Override
public MessageDispatcher<EventType> getMessageDispatcher() { public MessageDispatcher<EventType> getMessageDispatcher() {
return messageDispatcher; return messageDispatcher;
} }
@Override
public DrawingManager getDrawingManager() {
return drawingManager;
}
} }
@@ -177,6 +177,7 @@ public class ProjectFileManager {
if (!isBackup) { if (!isBackup) {
this.currentFileName = fileName; this.currentFileName = fileName;
this.modified = false; this.modified = false;
messageDispatcher.dispatchMessage(EventType.PROJECT_SAVED, getCurrentFileName());
fireFileStatusChanged(); fireFileStatusChanged();
} }
} }
@@ -31,6 +31,7 @@ import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.Utils; import org.diylc.appframework.miscutils.Utils;
import org.diylc.core.annotations.IAutoCreator; import org.diylc.core.annotations.IAutoCreator;
import org.diylc.netlist.IGuitarDiagramAnalyzer;
import org.diylc.netlist.INetlistParser; import org.diylc.netlist.INetlistParser;
import org.diylc.presenter.Presenter; import org.diylc.presenter.Presenter;
@@ -89,4 +90,25 @@ public class ReflectionUtils {
return null; 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> <minor>3</minor>
<build>0</build> <build>0</build>
</versionNumber> </versionNumber>
<releaseDate>2025-03-27 00:00:00.000 CET</releaseDate> <releaseDate>2025-04-21 00:00:00.000 CET</releaseDate>
<name></name> <name></name>
<changes> <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> <org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType> <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> <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>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType> <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>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType> <changeType>BUG_FIX</changeType>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.diylc</groupId> <groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId> <artifactId>diylc-parent</artifactId>
<version>5.2.0</version> <version>5.3.0</version>
</parent> </parent>
<artifactId>diylc-library</artifactId> <artifactId>diylc-library</artifactId>
@@ -57,7 +57,7 @@ import org.diylc.netlist.TreeException;
import org.diylc.netlist.TreeLeaf; import org.diylc.netlist.TreeLeaf;
import org.diylc.netlist.Tree.ITreeWalker; import org.diylc.netlist.Tree.ITreeWalker;
public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer { public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements INetlistAnalyzer, IGuitarDiagramAnalyzer {
private static Set<String> JACK_TYPES = new HashSet<String>(); private static Set<String> JACK_TYPES = new HashSet<String>();
private static Set<String> POT_TYPES = new HashSet<String>(); private static Set<String> POT_TYPES = new HashSet<String>();
@@ -119,6 +119,27 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN
return Set.of(NetlistSwitchPreference.WITH); return Set.of(NetlistSwitchPreference.WITH);
} }
public List<String> collectNotes(Netlist netlist) {
List<String> notes = new ArrayList<String>();
Tree tree = null;
try {
tree = constructTree(netlist);
} catch (TreeException e) {
throw new RuntimeException(e);
}
if (tree == null) {
notes.add("No pickups are connected to the output jack");
return notes;
}
processTree(tree, notes);
return notes;
}
protected Summary summarize(Netlist netlist) throws TreeException { protected Summary summarize(Netlist netlist) throws TreeException {
List<String> notes = new ArrayList<String>(); List<String> notes = new ArrayList<String>();
+1 -1
View File
@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.diylc</groupId> <groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId> <artifactId>diylc-parent</artifactId>
<version>5.2.0</version> <version>5.3.0</version>
</parent> </parent>
<artifactId>diylc-swing</artifactId> <artifactId>diylc-swing</artifactId>
@@ -16,6 +16,8 @@ import javax.swing.*;
import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener; import javax.swing.event.HyperlinkListener;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
@@ -34,6 +36,10 @@ public class ChatbotPane extends JPanel {
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);
private static final Color TERMINAL_FG = new Color(200, 200, 200); private static final Color TERMINAL_FG = new Color(200, 200, 200);
private static final Color BUTTON_BG = new Color(70, 70, 70); // Brighter background for buttons
private static final Color BUTTON_FG = new Color(240, 240, 240); // Bright text for contrast
private static final Color BUTTON_BORDER = new Color(100, 100, 100); // Subtle border
private static final Color BUTTON_HOVER = new Color(90, 90, 90); // Hover state color
public static final String HTML_STYLE = String.format( public static final String HTML_STYLE = String.format(
"<style>" + "body { font-family: %s; font-size: %dpt; color: rgb(%d,%d,%d); background-color: rgb(%d,%d,%d); padding: 0px; margin: 0; }" + "pre { white-space: pre-wrap; margin: 0; }" + ".user { color: #98C379; }" + // Softer green that's easier on the eyes "<style>" + "body { font-family: %s; font-size: %dpt; color: rgb(%d,%d,%d); background-color: rgb(%d,%d,%d); padding: 0px; margin: 0; }" + "pre { white-space: pre-wrap; margin: 0; }" + ".user { color: #98C379; }" + // Softer green that's easier on the eyes
".assistant { color: #61AFEF; }" + // Lighter, more vibrant blue ".assistant { color: #61AFEF; }" + // Lighter, more vibrant blue
@@ -64,9 +70,21 @@ public class ChatbotPane extends JPanel {
super(); super();
this.swingUI = swingUI; this.swingUI = swingUI;
this.plugInPort = plugInPort; this.plugInPort = plugInPort;
this.chatbotService = plugInPort.getChatbotService(); this.chatbotService = new ChatbotService(plugInPort);
setName("AI Assistant"); setName("AI Assistant");
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
e.consume();
}
@Override
public void mousePressed(MouseEvent e) {
e.consume();
}
});
// Set panel background color // Set panel background color
setBackground(TERMINAL_BG); setBackground(TERMINAL_BG);
@@ -99,12 +117,15 @@ public class ChatbotPane extends JPanel {
int lineHeight = getPromptArea().getFontMetrics(getPromptArea().getFont()).getHeight(); int lineHeight = getPromptArea().getFontMetrics(getPromptArea().getFont()).getHeight();
int desiredHeight = lineHeight * 3 + 10; // 3 lines + padding int desiredHeight = lineHeight * 3 + 10; // 3 lines + padding
// Set minimum and preferred size for both text area and scroll pane // Set minimum and preferred size for scroll pane
Dimension promptSize = new Dimension(200, desiredHeight); promptScrollPane.setMinimumSize(new Dimension(200, desiredHeight));
getPromptArea().setMinimumSize(promptSize); promptScrollPane.setPreferredSize(new Dimension(200, desiredHeight));
getPromptArea().setPreferredSize(promptSize);
promptScrollPane.setMinimumSize(new Dimension(200, desiredHeight + 10)); // Enable vertical scrolling
promptScrollPane.setPreferredSize(new Dimension(200, desiredHeight + 10)); getPromptArea().setLineWrap(true);
getPromptArea().setWrapStyleWord(true);
getPromptArea().setRows(3); // Set initial rows
promptScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
gbc.gridx = 0; gbc.gridx = 0;
gbc.gridy = 3; gbc.gridy = 3;
@@ -163,21 +184,20 @@ public class ChatbotPane extends JPanel {
promptArea.addKeyListener(new java.awt.event.KeyAdapter() { promptArea.addKeyListener(new java.awt.event.KeyAdapter() {
@Override @Override
public void keyPressed(java.awt.event.KeyEvent e) { public void keyPressed(java.awt.event.KeyEvent e) {
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER && !e.isShiftDown()) { if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) {
e.consume(); // Prevent the newline from being added if (e.isShiftDown()) {
// Insert newline for Shift+Enter
promptArea.insert("\n", promptArea.getCaretPosition());
} else {
e.consume(); // Always consume Enter to prevent multiple newlines
if (getAskButton().isEnabled()) { if (getAskButton().isEnabled()) {
getAskButton().doClick(); getAskButton().doClick();
} }
} }
} }
}
}); });
// Calculate height based on font metrics
int lineHeight = promptArea.getFontMetrics(promptArea.getFont()).getHeight();
int desiredHeight = lineHeight * 3 + 10; // 3 lines + padding
promptArea.setMinimumSize(new Dimension(200, desiredHeight));
promptArea.setPreferredSize(new Dimension(200, desiredHeight));
// Add placeholder text behavior // Add placeholder text behavior
final String placeholder = "Ask me anything"; final String placeholder = "Ask me anything";
promptArea.setText(placeholder); promptArea.setText(placeholder);
@@ -256,7 +276,16 @@ public class ChatbotPane extends JPanel {
fetchChatHistory(); fetchChatHistory();
if (this.subscriptionInfo != null) { if (this.subscriptionInfo != null) {
getPremiumButton().setVisible(FREE_TIER.equals(this.subscriptionInfo.getTier())); getPremiumButton().setVisible(FREE_TIER.equals(this.subscriptionInfo.getTier()));
String subscriptionInfoText = getSubscriptionInfoText(); String subscriptionInfoText = "Your are currently subscribed to the '" + this.subscriptionInfo.getTier() + "' tier, expiring on " + this.subscriptionInfo.getEndDate() + ", with " + this.subscriptionInfo.getRemainingCredits() + " credits remaining.";
if (FREE_TIER.equals(this.subscriptionInfo.getTier())) {
subscriptionInfoText +=
" To subscribe to one of the premium tiers, unlock advanced AI models and get more credits, visit <a href='http://" + GET_PREMIUM_URL + "'>" + GET_PREMIUM_URL + "</a> and become a Patreon supporter. " +
"All the details about the AI Assistant limits and other benefits will be specified there. " +
"If you are already a Patreon supporter, make sure to link your account with the DIYLC Cloud account from the 'Cloud' menu.";
} else {
subscriptionInfoText += " Thank you for supporting further development of DIYLC!";
}
appendSection(ChatbotService.SYSTEM, subscriptionInfoText); appendSection(ChatbotService.SYSTEM, subscriptionInfoText);
} }
getAskButton().setEnabled(true); getAskButton().setEnabled(true);
@@ -265,31 +294,15 @@ public class ChatbotPane extends JPanel {
// Scroll to the end of the chat pane // Scroll to the end of the chat pane
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (getChatEditorPane().getParent() == null) {
return;
}
JScrollPane scrollPane = (JScrollPane) getChatEditorPane().getParent().getParent(); JScrollPane scrollPane = (JScrollPane) getChatEditorPane().getParent().getParent();
JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar();
verticalScrollBar.setValue(verticalScrollBar.getMaximum()); verticalScrollBar.setValue(verticalScrollBar.getMaximum());
}); });
} }
private String getSubscriptionInfoText() {
String subscriptionInfoText = String.format(
"Your are currently subscribed to the '%s' tier, expiring on %s, with %d credits remaining.",
this.subscriptionInfo.getTier(),
this.subscriptionInfo.getEndDate(),
this.subscriptionInfo.getRemainingCredits());
if (FREE_TIER.equals(this.subscriptionInfo.getTier())) {
subscriptionInfoText += String.format(
" To subscribe to one of the premium tiers, unlock advanced AI models and get more credits, visit <a href='http://%s'>%s</a> and become a Patreon supporter. " +
"All the details about the AI Assistant limits and other benefits will be specified there. " +
"If you are already a Patreon supporter, make sure to link your account with the DIYLC Cloud account from the 'Cloud' menu.",
GET_PREMIUM_URL, GET_PREMIUM_URL);
} else {
subscriptionInfoText += " Thank you for supporting further development of DIYLC!";
}
return subscriptionInfoText;
}
private void fetchChatHistory() { private void fetchChatHistory() {
try { try {
List<ChatMessageEntity> chatHistory = plugInPort.getChatbotService().getChatHistory(); List<ChatMessageEntity> chatHistory = plugInPort.getChatbotService().getChatHistory();
@@ -364,11 +377,7 @@ public class ChatbotPane extends JPanel {
public JButton getAskButton() { public JButton getAskButton() {
if (askButton == null) { if (askButton == null) {
askButton = new JButton(LangUtil.translate("Send")); askButton = new JButton(LangUtil.translate("Send"));
askButton.setBackground(TERMINAL_BG); styleButton(askButton);
askButton.setForeground(TERMINAL_FG);
askButton.setFocusPainted(false);
askButton.setBorderPainted(true);
askButton.setEnabled(false);
askButton.addActionListener(e -> { askButton.addActionListener(e -> {
String prompt = getPromptArea().getText(); String prompt = getPromptArea().getText();
appendSection(ChatbotService.USER, ME + prompt); appendSection(ChatbotService.USER, ME + prompt);
@@ -426,11 +435,7 @@ public class ChatbotPane extends JPanel {
public JButton getClearButton() { public JButton getClearButton() {
if (clearButton == null) { if (clearButton == null) {
clearButton = new JButton(LangUtil.translate("Clear")); clearButton = new JButton(LangUtil.translate("Clear"));
clearButton.setBackground(TERMINAL_BG); styleButton(clearButton);
clearButton.setForeground(TERMINAL_FG);
clearButton.setFocusPainted(false);
clearButton.setBorderPainted(true);
clearButton.setEnabled(false);
clearButton.addActionListener(e -> { clearButton.addActionListener(e -> {
// Show confirmation dialog // Show confirmation dialog
int result = swingUI.showConfirmDialog( int result = swingUI.showConfirmDialog(
@@ -469,13 +474,9 @@ public class ChatbotPane extends JPanel {
public JButton getPremiumButton() { public JButton getPremiumButton() {
if (premiumButton == null) { if (premiumButton == null) {
premiumButton = new JButton(LangUtil.translate("Get Premium")); premiumButton = new JButton(LangUtil.translate("Get Premium"));
premiumButton.setBackground(TERMINAL_BG); styleButton(premiumButton);
premiumButton.setForeground(TERMINAL_FG);
premiumButton.setFocusPainted(false);
premiumButton.setBorderPainted(true);
premiumButton.setVisible(false); premiumButton.setVisible(false);
premiumButton.addActionListener(e -> { premiumButton.addActionListener(e -> {
try { try {
java.awt.Desktop.getDesktop().browse(new java.net.URI("http://diy-fever.com/get-premium")); java.awt.Desktop.getDesktop().browse(new java.net.URI("http://diy-fever.com/get-premium"));
} catch (Exception ex) { } catch (Exception ex) {
@@ -487,4 +488,45 @@ public class ChatbotPane extends JPanel {
} }
return premiumButton; return premiumButton;
} }
private void styleButton(JButton button) {
button.setBackground(BUTTON_BG);
button.setForeground(BUTTON_FG);
button.setEnabled(true); // Reset enabled state to ensure proper color
button.setFocusPainted(false);
button.setBorderPainted(true);
button.setContentAreaFilled(false);
button.setOpaque(true);
// Create a simple rounded border with padding
button.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(BUTTON_BORDER, 1),
BorderFactory.createEmptyBorder(4, 8, 4, 8)
));
// Add hover effect
button.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
if (button.isEnabled()) {
button.setBackground(BUTTON_HOVER);
}
}
public void mouseExited(java.awt.event.MouseEvent evt) {
if (button.isEnabled()) {
button.setBackground(BUTTON_BG);
}
}
});
// Set minimum and preferred size based on button type
if (button == premiumButton) {
// Wider size for Premium button
button.setMinimumSize(new Dimension(120, 25));
button.setPreferredSize(new Dimension(120, 25));
} else {
// Standard size for other buttons
button.setMinimumSize(new Dimension(80, 25));
button.setPreferredSize(new Dimension(80, 25));
}
}
} }
@@ -48,7 +48,7 @@ public class ChatbotPlugin implements IPlugIn {
@Override @Override
public EnumSet<EventType> getSubscribedEventTypes() { public EnumSet<EventType> getSubscribedEventTypes() {
return EnumSet.of(EventType.FILE_STATUS_CHANGED, EventType.PROJECT_LOADED, return EnumSet.of(EventType.PROJECT_SAVED, EventType.PROJECT_LOADED,
EventType.CLOUD_LOGGED_IN, EventType.CLOUD_LOGGED_OUT); EventType.CLOUD_LOGGED_IN, EventType.CLOUD_LOGGED_OUT);
} }
@@ -56,7 +56,7 @@ public class ChatbotPlugin implements IPlugIn {
@Override @Override
public void processMessage(EventType eventType, Object... params) { public void processMessage(EventType eventType, Object... params) {
switch (eventType) { switch (eventType) {
case FILE_STATUS_CHANGED: case PROJECT_SAVED:
case PROJECT_LOADED: case PROJECT_LOADED:
case CLOUD_LOGGED_IN: case CLOUD_LOGGED_IN:
case CLOUD_LOGGED_OUT: case CLOUD_LOGGED_OUT:
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.diylc</groupId> <groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId> <artifactId>diylc-parent</artifactId>
<version>5.2.0</version> <version>5.3.0</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>DIY Layout Creator</name> <name>DIY Layout Creator</name>
+29
View File
@@ -259,3 +259,32 @@ INSERT INTO diylc_knowledge_base(category, section, content) VALUES ('User Manua
- Ask follow-up questions if needed. - Ask follow-up questions if needed.
- Use the "Clear" button to reset the conversation. - Use the "Clear" button to reset the conversation.
- Each chat conversation is associated with the current project and is saved in the cloud and will be automatically downloaded when that project is opened.'); - Each chat conversation is associated with the current project and is saved in the cloud and will be automatically downloaded when that project is opened.');
DELETE FROM diylc_knowledge_base WHERE section = 'User Manual - Keyboard Shortcuts';
INSERT INTO diylc_knowledge_base(category, section, content) VALUES ('User Manual', 'User Manual - Keyboard Shortcuts','
- X key: quick jump to the search box in the Project Explorer pane (if visible)
- Q key: quick jump to the search box in the component toolbox tree
- ~ key: repeat previously added component
- Ctrl and drag to "unstick" a component
- Ctrl (Command on Mac) + Alt (Option on Mac) and drag to move a component with more precision, without snapping to the grid
- Ctrl (Command on Mac) + G to group components
- Ctrl (Command on Mac) + U to ungroup components
- Ctrl (Command on Mac) + H to hide the status bar
- Ctrl (Command on Mac) + L to lock the status bar
- Ctrl (Command on Mac) + P to print the current project
- Arrow keys to move components 1 grid spacing at a time
- Shift + Arrow keys to move components in 1 px increments
- Alt (Option on Mac) + Left or Right arrow key to rotate a component by 90 degrees counterclockwise or clockwise respectively
- Alt (Option on Mac) + Up or Down arrow key to move a component up or down in the Z-order
- Alt (Option on Mac) + H to mirror the selected components horizontally
- Alt (Option on Mac) + V to mirror the selected components vertically
- Ctrl (Command on Mac) + number keys 1 through 6 to toggle the visibility of layers from Chassis, Board, Trace, Component, Wiring, Text
- ');
DELETE FROM diylc_knowledge_base WHERE section = 'User Manual - Creating a New Component';
INSERT INTO diylc_knowledge_base(category, section, content) VALUES ('User Manual', 'User Manual - Creating a New Component','
There are several ways to create a new component:
- if it can be composed of existing components, you can combine multiple components together in a "building block" from the context menu and then easily recall it from the component tree
- for more complex components that require custom appearance and behavior, you can create a new component from scratch using the Component API, as described in the [Component API](https://github.com/bancika/diy-layout-creator/blob/wiki/ComponentAPI.md) documentation
- you can file a component request on the [DIYLC GitHub repository](https://github.com/bancika/diy-layout-creator/issues) and I will implement it if I think it would be useful for other users without guaranteeing if and when it will be implemented
- you can reach out to me at [bancika@gmail.com](mailto:bancika@gmail.com) to sponsor a custom component for your project and expedite its development');