mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
Ai improvements 1 (#914)
* improve netlist * structure improvement * chatbot enhancements * chatbot improvements * charbot enhancements * Update user_manual.sql * chatbot enhancements * release
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.3.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-core</artifactId>
|
||||
|
||||
@@ -45,6 +45,10 @@ public enum EventType {
|
||||
* has been either reloaded or loaded with undo/redo operations.
|
||||
*/
|
||||
PROJECT_LOADED,
|
||||
/**
|
||||
* Called when the current project is saved to a file. The first parameter is the current file name.
|
||||
*/
|
||||
PROJECT_SAVED,
|
||||
/**
|
||||
* Called when either current file name or modified flag are changed. The first parameter is the
|
||||
* current file name, the second one is a boolean modified flag.
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.diylc.common;
|
||||
import org.diylc.appframework.simplemq.MessageDispatcher;
|
||||
import org.diylc.plugins.chatbot.service.ChatbotService;
|
||||
import org.diylc.plugins.cloud.service.CloudService;
|
||||
import org.diylc.presenter.DrawingManager;
|
||||
|
||||
public interface IServiceManager {
|
||||
|
||||
@@ -11,4 +12,6 @@ public interface IServiceManager {
|
||||
ChatbotService getChatbotService();
|
||||
|
||||
MessageDispatcher<EventType> getMessageDispatcher();
|
||||
|
||||
DrawingManager getDrawingManager();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.diylc.netlist;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IGuitarDiagramAnalyzer {
|
||||
|
||||
List<String> collectNotes(Netlist netlist);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.diylc.plugins.chatbot.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AiComponent(String id, String type, String value, AiPoint fromPos, AiPoint toPos, AiPoint pos, List<AiTerminal> terminals) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.diylc.plugins.chatbot.model;
|
||||
|
||||
public record AiPoint(int x, int y) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.diylc.plugins.chatbot.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public record AiProject(Map<String, String> metadata,
|
||||
List<String> tags,
|
||||
List<AiComponent> components,
|
||||
Map<String, Set<String>> nets,
|
||||
List<AiSwitch> switches) {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.diylc.plugins.chatbot.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AiSwitch(String componentId, List<AiSwitchPosition> positions) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.diylc.plugins.chatbot.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public record AiSwitchPosition(String name, List<Set<Integer>> connectedTerminalGroups) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.diylc.plugins.chatbot.model;
|
||||
|
||||
public record AiTerminal(int id, String label, AiPoint pos) {
|
||||
}
|
||||
+165
@@ -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;
|
||||
}
|
||||
}
|
||||
+76
-129
@@ -23,6 +23,8 @@ package org.diylc.plugins.chatbot.service;
|
||||
|
||||
import com.diyfever.httpproxy.PhpFlatProxy;
|
||||
import com.diyfever.httpproxy.ProxyFactory;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
@@ -34,13 +36,13 @@ import org.diylc.common.PropertyWrapper;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.ISwitch;
|
||||
import org.diylc.netlist.*;
|
||||
import org.diylc.plugins.chatbot.model.ChatMessageEntity;
|
||||
import org.diylc.plugins.chatbot.model.IChatbotAPI;
|
||||
import org.diylc.plugins.chatbot.model.SubscriptionEntity;
|
||||
import org.diylc.plugins.chatbot.model.*;
|
||||
import org.diylc.plugins.cloud.model.IServiceAPI;
|
||||
import org.diylc.plugins.cloud.service.NotLoggedInException;
|
||||
import org.diylc.presenter.ComponentProcessor;
|
||||
import org.diylc.presenter.ContinuityArea;
|
||||
import org.diylc.utils.FileUtils;
|
||||
import org.diylc.utils.ReflectionUtils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
@@ -48,7 +50,6 @@ import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.diylc.utils.FileUtils.extractFileName;
|
||||
|
||||
@@ -59,11 +60,11 @@ import static org.diylc.utils.FileUtils.extractFileName;
|
||||
*/
|
||||
public class ChatbotService {
|
||||
|
||||
public static final String LOG_IN_MESSAGE =
|
||||
"Please log into your DIYLC Cloud account in order to use the AI Assistant";
|
||||
|
||||
private final static Logger LOG = Logger.getLogger(ChatbotService.class);
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
static {
|
||||
MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
}
|
||||
public static final String WELCOME_MESSAGE = "Welcome to the DIYLC AI Assistant!";
|
||||
|
||||
private final IPlugInPort plugInPort;
|
||||
@@ -79,14 +80,14 @@ public class ChatbotService {
|
||||
|
||||
@Override
|
||||
public EnumSet<EventType> getSubscribedEventTypes() {
|
||||
return EnumSet.of(EventType.PROJECT_LOADED, EventType.FILE_STATUS_CHANGED);
|
||||
return EnumSet.of(EventType.PROJECT_LOADED, EventType.PROJECT_SAVED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMessage(EventType eventType, Object... objects) {
|
||||
if (eventType == EventType.PROJECT_LOADED) {
|
||||
ChatbotService.this.currentProjectName = extractFileName((String)objects[2]);
|
||||
} else if (eventType == EventType.FILE_STATUS_CHANGED) {
|
||||
} else if (eventType == EventType.PROJECT_SAVED) {
|
||||
String newProjectName = extractFileName((String)objects[0]);
|
||||
if (!Objects.equals(ChatbotService.this.currentProjectName, newProjectName)) {
|
||||
if (plugInPort.getCloudService().isLoggedIn()) {
|
||||
@@ -117,24 +118,68 @@ public class ChatbotService {
|
||||
if (!plugInPort.getCloudService().isLoggedIn())
|
||||
throw new NotLoggedInException();
|
||||
|
||||
LOG.info("Prompting chatbot: " + prompt);
|
||||
|
||||
String currentFile = plugInPort.getCurrentFileName();
|
||||
String fileName = extractFileName(currentFile);
|
||||
|
||||
String netlist;
|
||||
File netlistFile = null;
|
||||
File aiProjectFile = null;
|
||||
try {
|
||||
netlist = extractNetlists();
|
||||
if (netlist != null) {
|
||||
netlistFile = File.createTempFile("diylc_netlist_", ".txt");
|
||||
try (FileWriter writer = new FileWriter(netlistFile)) {
|
||||
writer.write(netlist);
|
||||
AiProject aiProject = extractAiProject();
|
||||
String aiProjectStr = null;
|
||||
try {
|
||||
aiProjectStr = MAPPER.writeValueAsString(aiProject);
|
||||
} catch (JsonProcessingException e) {
|
||||
LOG.error("Could not serialize aiProject to JSON", e);
|
||||
}
|
||||
if (aiProjectStr != null) {
|
||||
aiProjectFile = File.createTempFile("diylc_netlist_", ".txt");
|
||||
try (FileWriter writer = new FileWriter(aiProjectFile)) {
|
||||
writer.write("""
|
||||
Take these notes into account when analyzing the circuit
|
||||
- Coordinates are represented in pixels assuming 200px/in resolution.
|
||||
- Each component will either have a set of terminals that are connectable to other components or one or two points defining its position.
|
||||
- Netlists of connected terminals are defined in the 'nets' structure.
|
||||
- Analyze labels - they could provide clues about nearby terminals and what they represent.
|
||||
""");
|
||||
if (aiProject.switches() != null && !aiProject.switches().isEmpty()) {
|
||||
writer.write("""
|
||||
- Switches are defined in the 'switches' structure. Each switch will have a list of available position and the collection of internal terminals that are connected in each of the positions. Take the switching matrix into consideration when analyzing the circuit operation.
|
||||
""");
|
||||
}
|
||||
if (aiProject.tags().contains("guitar")) {
|
||||
IGuitarDiagramAnalyzer guitarDiagramAnalyzer =
|
||||
ReflectionUtils.getGuitarDiagramAnalyzer();
|
||||
if (guitarDiagramAnalyzer != null) {
|
||||
try {
|
||||
List<Netlist> netlists = plugInPort.extractNetlists(true);
|
||||
writer.write("""
|
||||
- The circuit represents a guitar wiring diagram and below are some of the known characteristics in each combination of switch positions:
|
||||
""");
|
||||
netlists.forEach(netlist -> {
|
||||
List<String> notes = guitarDiagramAnalyzer.collectNotes(netlist);
|
||||
try {
|
||||
writer.write(netlist.getSwitchSetupString() + ": \n");
|
||||
for (String note : notes) {
|
||||
writer.write(" - " + note + "\n");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("Could not write guitar diagram notes", ex);
|
||||
}
|
||||
});
|
||||
} catch (NetlistException | IOException ex) {
|
||||
LOG.warn("Could not extract guitar diagram netlist", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.write("\nBelow is the JSON describing the circuit in detail.\n");
|
||||
writer.write(aiProjectStr);
|
||||
}
|
||||
// Make sure the temp file is deleted when the JVM exits
|
||||
netlistFile.deleteOnExit();
|
||||
aiProjectFile.deleteOnExit();
|
||||
}
|
||||
} catch (NetlistException | IOException e) {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error extracting or saving netlist", e);
|
||||
netlist = null;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -143,117 +188,19 @@ public class ChatbotService {
|
||||
plugInPort.getCloudService().getCurrentToken(),
|
||||
plugInPort.getCloudService().getMachineId(),
|
||||
fileName,
|
||||
netlistFile,
|
||||
aiProjectFile,
|
||||
prompt);
|
||||
} finally {
|
||||
// Clean up the temp file immediately after use
|
||||
if (netlistFile != null && netlistFile.exists()) {
|
||||
netlistFile.delete();
|
||||
if (aiProjectFile != null && aiProjectFile.exists()) {
|
||||
aiProjectFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Set<Class<?>> PROPERTY_TYPES_TO_SKIP = Set.of(Font.class, Color.class);
|
||||
|
||||
private String extractNetlists() throws NetlistException {
|
||||
List<Netlist> netlists = plugInPort.extractNetlists(true);
|
||||
|
||||
if (netlists == null || netlists.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Set<? extends IDIYComponent<?>> components = netlists.stream()
|
||||
// .flatMap(n -> n.getGroups().stream()
|
||||
// .flatMap(g -> g.getNodes().stream().
|
||||
// map(Node::getComponent)))
|
||||
// .collect(Collectors.toSet());
|
||||
//
|
||||
// if (!components.isEmpty()) {
|
||||
// sb.append(
|
||||
// "Components (each element of the JSON array represents one component with all the relevant properties): ");
|
||||
// outputComponents(components, sb);
|
||||
// }
|
||||
|
||||
Set<? extends ISwitch> switches = plugInPort.getCurrentProject().getComponents()
|
||||
.stream().filter(x -> x instanceof ISwitch)
|
||||
.map(x -> (ISwitch)x)
|
||||
.collect(Collectors.toSet());;
|
||||
|
||||
if (!switches.isEmpty()) {
|
||||
sb.append(
|
||||
"Switches (each row represents one switch, starts with the switch name and lists key properties of a switch):\n\n");
|
||||
outputSwitches(switches, sb);
|
||||
}
|
||||
|
||||
for (Netlist netlist : netlists) {
|
||||
sb.append("\n");
|
||||
if (!netlist.getSwitchSetup().isEmpty()) {
|
||||
sb.append("Switch configuration: ").append(netlist.getSwitchSetup()).append(". ");
|
||||
}
|
||||
sb.append("Circuit netlist")
|
||||
.append(netlist.getSwitchSetup().isEmpty() ? "" : " in this switch configuration")
|
||||
.append(":\n\n");
|
||||
try {
|
||||
sb.append(SpiceSumarizer.summarize(netlist, false));
|
||||
} catch (TreeException e) {
|
||||
LOG.error("Error exporting netlist to Spice", e);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void outputSwitches(Set<? extends ISwitch> switches, StringBuilder sb) {
|
||||
for (ISwitch sw : switches) {
|
||||
IDIYComponent<?> component = (IDIYComponent<?>) sw;
|
||||
ComponentType componentType = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
|
||||
sb.append(component.getName())
|
||||
.append(" ").append(component.getValueForDisplay()).append(" ")
|
||||
.append(componentType.getName())
|
||||
.append(" [").append(componentType.getCategory()).append("]\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void outputComponents(Set<? extends IDIYComponent<?>> components, StringBuilder sb) {
|
||||
List<Map<String, Object>> componentDescriptors = new ArrayList<>();
|
||||
|
||||
components.forEach(c -> {
|
||||
ComponentType componentType = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) c.getClass());
|
||||
List<PropertyWrapper> properties =
|
||||
ComponentProcessor.getInstance().extractProperties(c.getClass());
|
||||
|
||||
Map<String, Object> componentDescriptorMap = new HashMap<>();
|
||||
componentDescriptorMap.put("name", c.getName());
|
||||
componentDescriptorMap.put("type", componentType.getName());
|
||||
componentDescriptorMap.put("category", componentType.getCategory());
|
||||
|
||||
properties.forEach(p -> {
|
||||
if (PROPERTY_TYPES_TO_SKIP.contains(p.getType()))
|
||||
return;
|
||||
|
||||
try {
|
||||
p.readFrom(c);
|
||||
if (p.getValue() == null)
|
||||
return;
|
||||
|
||||
componentDescriptorMap.put(p.getName(), p.getValue().toString());
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Error extracting properties", e);
|
||||
}
|
||||
});
|
||||
|
||||
componentDescriptors.add(componentDescriptorMap);
|
||||
});
|
||||
|
||||
try {
|
||||
String json = MAPPER.writeValueAsString(componentDescriptors);
|
||||
sb.append(json).append("\n\n");
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to serialize component descriptors to JSON", e);
|
||||
}
|
||||
private AiProject extractAiProject() throws NetlistException {
|
||||
List<ContinuityArea> continuityAreas = plugInPort.getDrawingManager().getContinuityAreas();
|
||||
return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas);
|
||||
}
|
||||
|
||||
public SubscriptionEntity getSubscriptionInfo() throws NotLoggedInException {
|
||||
@@ -317,23 +264,23 @@ public class ChatbotService {
|
||||
|
||||
public static final String[] diylcQuestions = {
|
||||
"How do I add a component to my layout in DIYLC?",
|
||||
"How can I quickly find a specific component in DIYLC?s component toolbox?",
|
||||
"How can I quickly find a specific component in DIYLC's component toolbox?",
|
||||
"How do I move or reposition components on the canvas in DIYLC?",
|
||||
"How do I edit or change a component?s properties (e.g. value or color) in DIYLC?",
|
||||
"How do I edit or change a component's properties (e.g. value or color) in DIYLC?",
|
||||
"Is there a shortcut to duplicate or repeat the last component I placed in DIYLC?",
|
||||
"How can I group multiple components so I can move or edit them together in DIYLC?",
|
||||
"What are ?building blocks? in DIYLC and how do I create or use them?",
|
||||
"What are 'building blocks' in DIYLC and how do I create or use them?",
|
||||
"How do I save my DIYLC project and reopen it later on?",
|
||||
"How do I export my DIYLC layout as an image or PDF file?",
|
||||
"Can DIYLC generate a bill of materials (BOM) for my project, and how would I do that?",
|
||||
"How can I change or hide different layers in a DIYLC project to manage overlapping components?",
|
||||
"Is it possible to view the underside of a board in DIYLC, and how can I enable that?",
|
||||
"What does the ?Highlight Connected Areas? feature do in DIYLC and how do I use it for debugging?",
|
||||
"How can I analyze a guitar wiring diagram using DIYLC?s built-in tools?",
|
||||
"What does the 'Highlight Connected Areas' feature do in DIYLC and how do I use it for debugging?",
|
||||
"How can I analyze a guitar wiring diagram using DIYLC's built-in tools?",
|
||||
"How do I assign a custom keyboard shortcut to a component in DIYLC for quick access?",
|
||||
"What are component variants in DIYLC and how can I create and apply them?",
|
||||
"What is the continuous creation mode in DIYLC and how does it help when adding many components?",
|
||||
"How can I create a custom component to add to DIYLC?s component library?",
|
||||
"How can I create a custom component to add to DIYLC's component library?",
|
||||
"Does DIYLC support plug-ins, and how can I add new functionality to the app with them?",
|
||||
"What is the DIYLC cloud feature and how can I share or download projects using it?"
|
||||
};
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.diylc.plugins.chatbot.service;
|
||||
|
||||
import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.ISwitch;
|
||||
import org.diylc.presenter.ContinuityArea;
|
||||
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
|
||||
public class NetlistBuilder {
|
||||
|
||||
public static class TerminalRef {
|
||||
public final IDIYComponent<?> component;
|
||||
public final int terminalIndex;
|
||||
|
||||
public TerminalRef(IDIYComponent<?> component, int terminalIndex) {
|
||||
this.component = component;
|
||||
this.terminalIndex = terminalIndex;
|
||||
}
|
||||
|
||||
public Point2D getPosition() {
|
||||
return component.getControlPoint(terminalIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return component.getName() + "." + terminalIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof TerminalRef)) return false;
|
||||
TerminalRef other = (TerminalRef) obj;
|
||||
return Objects.equals(component, other.component) && terminalIndex == other.terminalIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(component, terminalIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Set<TerminalRef>> buildNets(List<IDIYComponent<?>> components, List<ContinuityArea> continuityAreas) {
|
||||
Map<TerminalRef, Integer> terminalIndex = new HashMap<>();
|
||||
List<TerminalRef> allTerminals = new ArrayList<>();
|
||||
int idx = 0;
|
||||
|
||||
for (IDIYComponent<?> c : components) {
|
||||
for (int i = 0; i < c.getControlPointCount(); i++) {
|
||||
if (c.isControlPointSticky(i)) {
|
||||
TerminalRef ref = new TerminalRef(c, i);
|
||||
terminalIndex.put(ref, idx++);
|
||||
allTerminals.add(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnionFind uf = new UnionFind(allTerminals.size());
|
||||
|
||||
// Step 1: connect conductive components
|
||||
for (IDIYComponent<?> c : components) {
|
||||
if (c instanceof IContinuity continuity && !(c instanceof ISwitch)) {
|
||||
int count = c.getControlPointCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int j = i + 1; j < count; j++) {
|
||||
if (continuity.arePointsConnected(i, j)) {
|
||||
TerminalRef a = new TerminalRef(c, i);
|
||||
TerminalRef b = new TerminalRef(c, j);
|
||||
uf.union(terminalIndex.get(a), terminalIndex.get(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: connect terminals inside continuity areas
|
||||
for (ContinuityArea continuity : continuityAreas) {
|
||||
Area area = continuity.getSimplifiedArea();
|
||||
List<TerminalRef> inside = new ArrayList<>();
|
||||
|
||||
for (TerminalRef t : allTerminals) {
|
||||
Point2D p = t.getPosition();
|
||||
if (area.contains(p)) {
|
||||
inside.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < inside.size(); i++) {
|
||||
for (int j = i + 1; j < inside.size(); j++) {
|
||||
uf.union(terminalIndex.get(inside.get(i)), terminalIndex.get(inside.get(j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<Integer, Set<TerminalRef>> netMap = new HashMap<>();
|
||||
for (TerminalRef t : allTerminals) {
|
||||
int root = uf.find(terminalIndex.get(t));
|
||||
netMap.computeIfAbsent(root, k -> new HashSet<>()).add(t);
|
||||
}
|
||||
|
||||
// Filter out nets with only one terminal and remove exact duplicates
|
||||
List<Set<TerminalRef>> nets = new ArrayList<>();
|
||||
Set<Set<TerminalRef>> seen = new HashSet<>();
|
||||
|
||||
for (Set<TerminalRef> net : netMap.values()) {
|
||||
if (net.size() > 1 && seen.add(net)) {
|
||||
nets.add(net);
|
||||
}
|
||||
}
|
||||
return nets;
|
||||
|
||||
}
|
||||
|
||||
// Union-Find with path compression
|
||||
static class UnionFind {
|
||||
int[] parent;
|
||||
|
||||
UnionFind(int n) {
|
||||
parent = new int[n];
|
||||
for (int i = 0; i < n; i++) parent[i] = i;
|
||||
}
|
||||
|
||||
int find(int x) {
|
||||
if (parent[x] != x)
|
||||
parent[x] = find(parent[x]);
|
||||
return parent[x];
|
||||
}
|
||||
|
||||
void union(int x, int y) {
|
||||
parent[find(x)] = find(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2831,7 +2831,13 @@ public class Presenter implements IPlugInPort {
|
||||
return chatbotService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageDispatcher<EventType> getMessageDispatcher() {
|
||||
return messageDispatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DrawingManager getDrawingManager() {
|
||||
return drawingManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@ public class ProjectFileManager {
|
||||
if (!isBackup) {
|
||||
this.currentFileName = fileName;
|
||||
this.modified = false;
|
||||
messageDispatcher.dispatchMessage(EventType.PROJECT_SAVED, getCurrentFileName());
|
||||
fireFileStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.Utils;
|
||||
|
||||
import org.diylc.core.annotations.IAutoCreator;
|
||||
import org.diylc.netlist.IGuitarDiagramAnalyzer;
|
||||
import org.diylc.netlist.INetlistParser;
|
||||
import org.diylc.presenter.Presenter;
|
||||
|
||||
@@ -89,4 +90,25 @@ public class ReflectionUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IGuitarDiagramAnalyzer getGuitarDiagramAnalyzer() throws Exception {
|
||||
Set<Class<?>> classes;
|
||||
classes = Utils.getClasses("org.diylc.netlist");
|
||||
List<IGuitarDiagramAnalyzer> result = new ArrayList<>();
|
||||
|
||||
for (Class<?> clazz : classes) {
|
||||
if (!Modifier.isAbstract(clazz.getModifiers()) && IGuitarDiagramAnalyzer.class.isAssignableFrom(clazz)) {
|
||||
result.add((IGuitarDiagramAnalyzer) clazz.getDeclaredConstructor().newInstance());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (result.size() > 1) {
|
||||
throw new RuntimeException("Found more than one IGuitarDiagramAnalyzer");
|
||||
}
|
||||
return result.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,9 +258,13 @@
|
||||
<minor>3</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-27 00:00:00.000 CET</releaseDate>
|
||||
<releaseDate>2025-04-21 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved AI Assistant quality of responses, especially when analyzing a circuit</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>When saving a project to the new file, retain all the AI Assistant chat history for the new project file</description>
|
||||
@@ -275,7 +279,11 @@
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Output current subscription statistics at the end of the chat for transparency</description>
|
||||
<description>Output current subscription statistics at the end of the chat for usage transparency</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved styling and usability of chatbot tool</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.3.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-library</artifactId>
|
||||
|
||||
@@ -57,7 +57,7 @@ import org.diylc.netlist.TreeException;
|
||||
import org.diylc.netlist.TreeLeaf;
|
||||
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> POT_TYPES = new HashSet<String>();
|
||||
@@ -119,6 +119,27 @@ public class GuitarDiagramAnalyzer extends AbstractNetlistAnalyzer implements IN
|
||||
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 {
|
||||
List<String> notes = new ArrayList<String>();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.3.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-swing</artifactId>
|
||||
|
||||
@@ -16,6 +16,8 @@ import javax.swing.*;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import javax.swing.event.HyperlinkListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
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 Color TERMINAL_BG = new Color(40, 40, 40);
|
||||
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(
|
||||
"<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
|
||||
@@ -64,9 +70,21 @@ public class ChatbotPane extends JPanel {
|
||||
super();
|
||||
this.swingUI = swingUI;
|
||||
this.plugInPort = plugInPort;
|
||||
this.chatbotService = plugInPort.getChatbotService();
|
||||
this.chatbotService = new ChatbotService(plugInPort);
|
||||
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
|
||||
setBackground(TERMINAL_BG);
|
||||
|
||||
@@ -99,12 +117,15 @@ public class ChatbotPane extends JPanel {
|
||||
int lineHeight = getPromptArea().getFontMetrics(getPromptArea().getFont()).getHeight();
|
||||
int desiredHeight = lineHeight * 3 + 10; // 3 lines + padding
|
||||
|
||||
// Set minimum and preferred size for both text area and scroll pane
|
||||
Dimension promptSize = new Dimension(200, desiredHeight);
|
||||
getPromptArea().setMinimumSize(promptSize);
|
||||
getPromptArea().setPreferredSize(promptSize);
|
||||
promptScrollPane.setMinimumSize(new Dimension(200, desiredHeight + 10));
|
||||
promptScrollPane.setPreferredSize(new Dimension(200, desiredHeight + 10));
|
||||
// Set minimum and preferred size for scroll pane
|
||||
promptScrollPane.setMinimumSize(new Dimension(200, desiredHeight));
|
||||
promptScrollPane.setPreferredSize(new Dimension(200, desiredHeight));
|
||||
|
||||
// Enable vertical scrolling
|
||||
getPromptArea().setLineWrap(true);
|
||||
getPromptArea().setWrapStyleWord(true);
|
||||
getPromptArea().setRows(3); // Set initial rows
|
||||
promptScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
@@ -163,20 +184,19 @@ public class ChatbotPane extends JPanel {
|
||||
promptArea.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(java.awt.event.KeyEvent e) {
|
||||
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER && !e.isShiftDown()) {
|
||||
e.consume(); // Prevent the newline from being added
|
||||
if (getAskButton().isEnabled()) {
|
||||
getAskButton().doClick();
|
||||
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) {
|
||||
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()) {
|
||||
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
|
||||
final String placeholder = "Ask me anything";
|
||||
@@ -256,7 +276,16 @@ public class ChatbotPane extends JPanel {
|
||||
fetchChatHistory();
|
||||
if (this.subscriptionInfo != null) {
|
||||
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);
|
||||
}
|
||||
getAskButton().setEnabled(true);
|
||||
@@ -265,31 +294,15 @@ public class ChatbotPane extends JPanel {
|
||||
|
||||
// Scroll to the end of the chat pane
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (getChatEditorPane().getParent() == null) {
|
||||
return;
|
||||
}
|
||||
JScrollPane scrollPane = (JScrollPane) getChatEditorPane().getParent().getParent();
|
||||
JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar();
|
||||
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() {
|
||||
try {
|
||||
List<ChatMessageEntity> chatHistory = plugInPort.getChatbotService().getChatHistory();
|
||||
@@ -364,11 +377,7 @@ public class ChatbotPane extends JPanel {
|
||||
public JButton getAskButton() {
|
||||
if (askButton == null) {
|
||||
askButton = new JButton(LangUtil.translate("Send"));
|
||||
askButton.setBackground(TERMINAL_BG);
|
||||
askButton.setForeground(TERMINAL_FG);
|
||||
askButton.setFocusPainted(false);
|
||||
askButton.setBorderPainted(true);
|
||||
askButton.setEnabled(false);
|
||||
styleButton(askButton);
|
||||
askButton.addActionListener(e -> {
|
||||
String prompt = getPromptArea().getText();
|
||||
appendSection(ChatbotService.USER, ME + prompt);
|
||||
@@ -426,11 +435,7 @@ public class ChatbotPane extends JPanel {
|
||||
public JButton getClearButton() {
|
||||
if (clearButton == null) {
|
||||
clearButton = new JButton(LangUtil.translate("Clear"));
|
||||
clearButton.setBackground(TERMINAL_BG);
|
||||
clearButton.setForeground(TERMINAL_FG);
|
||||
clearButton.setFocusPainted(false);
|
||||
clearButton.setBorderPainted(true);
|
||||
clearButton.setEnabled(false);
|
||||
styleButton(clearButton);
|
||||
clearButton.addActionListener(e -> {
|
||||
// Show confirmation dialog
|
||||
int result = swingUI.showConfirmDialog(
|
||||
@@ -469,13 +474,9 @@ public class ChatbotPane extends JPanel {
|
||||
public JButton getPremiumButton() {
|
||||
if (premiumButton == null) {
|
||||
premiumButton = new JButton(LangUtil.translate("Get Premium"));
|
||||
premiumButton.setBackground(TERMINAL_BG);
|
||||
premiumButton.setForeground(TERMINAL_FG);
|
||||
premiumButton.setFocusPainted(false);
|
||||
premiumButton.setBorderPainted(true);
|
||||
styleButton(premiumButton);
|
||||
premiumButton.setVisible(false);
|
||||
premiumButton.addActionListener(e -> {
|
||||
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new java.net.URI("http://diy-fever.com/get-premium"));
|
||||
} catch (Exception ex) {
|
||||
@@ -487,4 +488,45 @@ public class ChatbotPane extends JPanel {
|
||||
}
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ChatbotPlugin implements IPlugIn {
|
||||
@Override
|
||||
public void processMessage(EventType eventType, Object... params) {
|
||||
switch (eventType) {
|
||||
case FILE_STATUS_CHANGED:
|
||||
case PROJECT_SAVED:
|
||||
case PROJECT_LOADED:
|
||||
case CLOUD_LOGGED_IN:
|
||||
case CLOUD_LOGGED_OUT:
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.3.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>DIY Layout Creator</name>
|
||||
|
||||
@@ -258,4 +258,33 @@ INSERT INTO diylc_knowledge_base(category, section, content) VALUES ('User Manua
|
||||
- Use simple language and avoid complex technical terms.
|
||||
- Ask follow-up questions if needed.
|
||||
- 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');
|
||||
|
||||
Reference in New Issue
Block a user