diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java index 06fa2ab7..85e8ba99 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/ChatbotService.java @@ -111,7 +111,7 @@ public class ChatbotService { if (netlists == null) { return null; } - + StringBuilder sb = new StringBuilder(); sb.append("Components: "); @@ -169,25 +169,6 @@ public class ChatbotService { return sb.toString(); } - public String getInitialChatContents() { - Random r = new Random(); - - String chatHtml = - "
" + WELCOME_MESSAGE + "


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

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

\n"; - - return chatHtml; - } - public SubscriptionEntity getSubscriptionInfo() throws NotLoggedInException { if (!plugInPort.getCloudService().isLoggedIn()) throw new NotLoggedInException(); @@ -219,7 +200,7 @@ public class ChatbotService { public static final String USER = "user"; public static final String TEMPORARY = "temporary"; - String[] electronicsQuestions = { + public static final String[] electronicsQuestions = { "What is Ohm's Law, and how do voltage, current, and resistance relate to each other?", "Why are resistors important in circuits, and what happens if the resistor value is incorrect?", "What is a capacitor, and how does it behave differently from a resistor?", @@ -247,7 +228,7 @@ public class ChatbotService { "How can you determine if components in your circuit are properly rated for the voltage and current they're experiencing?" }; - String[] diylcQuestions = { + 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 do I move or reposition components on the canvas in DIYLC?", @@ -270,7 +251,7 @@ public class ChatbotService { "What is the DIYLC cloud feature and how can I share or download projects using it?" }; - String[] circuitQuestions = { + public static final String[] circuitQuestions = { "What's the purpose of this specific resistor in my circuit?", "How do I correctly determine capacitor values for my power supply?", "How should I route wiring to reduce interference and noise?", diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/cloud/service/CloudService.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/cloud/service/CloudService.java index 739f2f95..24243c1b 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/cloud/service/CloudService.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/cloud/service/CloudService.java @@ -21,9 +21,8 @@ */ package org.diylc.plugins.cloud.service; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; +import java.io.*; +import java.lang.management.RuntimeMXBean; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; @@ -33,10 +32,12 @@ import java.util.Collections; import java.util.List; import org.apache.log4j.Logger; import org.diylc.appframework.miscutils.ConfigurationManager; +import java.lang.management.ManagementFactory; import com.diyfever.httpproxy.PhpFlatProxy; import com.diyfever.httpproxy.ProxyFactory; +import org.diylc.appframework.miscutils.Utils; import org.diylc.common.EventType; import org.diylc.common.IPlugInPort; import org.diylc.common.PropertyWrapper; @@ -47,6 +48,14 @@ import org.diylc.plugins.cloud.model.UserEntity; import org.diylc.presenter.ComparatorFactory; import org.diylc.presenter.ComponentProcessor; +import javax.swing.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Enumeration; + /** * Contains all the back-end logic for using the cloud and manipulating projects on the cloud. * @@ -54,17 +63,21 @@ import org.diylc.presenter.ComponentProcessor; */ public class CloudService { - private static String USERNAME_KEY = "cloud.Username"; - private static String TOKEN_KEY = "cloud.token"; - - private static String ERROR = "Error"; - - private final static Logger LOG = Logger.getLogger(CloudService.class); + private static final Logger LOG = Logger.getLogger(CloudService.class); + private static final String MACHINE_ID_FILE = "machine.id"; + private static final String MACHINE_ID_KEY = "cloud.machineId"; + private static final String USERNAME_KEY = "cloud.Username"; + private static final String TOKEN_KEY = "cloud.token"; + private static final String ERROR = "Error"; private static final Object SUCCESS = "Success"; + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + private final IPlugInPort plugInPort; private IServiceAPI service; private String serviceUrl; + private UserEntity currentUser; + private String currentToken; private String machineId; private String[] categories; @@ -72,6 +85,22 @@ public class CloudService { public CloudService(IPlugInPort plugInPort) { this.plugInPort = plugInPort; + + serviceUrl = ConfigurationManager.getInstance().readString(IServiceAPI.URL_KEY, + "http://www.diy-fever.com/diylc/api/v1"); + ProxyFactory factory = new ProxyFactory(new PhpFlatProxy()); + service = factory.createProxy(IServiceAPI.class, serviceUrl); + + // Delay a test of logged in state to give it time to complete + Timer timer = new Timer(1000, e -> { + try { + tryLogInWithToken(); + } catch (Exception ex) { + LOG.error("Background login attempt failed", ex); + } + }); + timer.setRepeats(false); // Make it one-shot + timer.start(); } private IServiceAPI getService() { @@ -107,7 +136,7 @@ public class CloudService { } } - public boolean tryLogInWithToken() throws CloudException { + private void tryLogInWithToken() throws CloudException { String username = ConfigurationManager.getInstance().readString(USERNAME_KEY, null); String token = ConfigurationManager.getInstance().readString(TOKEN_KEY, null); @@ -121,22 +150,19 @@ public class CloudService { } if (res == null || res.equals(ERROR)) { LOG.info("Login failed"); - return false; } else { LOG.info("Login success"); this.loggedIn = true; this.plugInPort.getMessageDispatcher().dispatchMessage(EventType.CLOUD_LOGGED_IN); - return true; } - } else - return false; + } } public void logOut() { LOG.info("Logged out"); ConfigurationManager.getInstance().writeValue(TOKEN_KEY, null); - this.plugInPort.getMessageDispatcher().dispatchMessage(EventType.CLOUD_LOGGED_OUT); this.loggedIn = false; + this.plugInPort.getMessageDispatcher().dispatchMessage(EventType.CLOUD_LOGGED_OUT); } public boolean isLoggedIn() { @@ -428,22 +454,67 @@ public class CloudService { public String getMachineId() { if (machineId == null) { - try { - InetAddress ip = InetAddress.getLocalHost(); - - NetworkInterface network = NetworkInterface.getByInetAddress(ip); - - byte[] mac = network.getHardwareAddress(); - - StringBuilder sb = new StringBuilder(18); - for (byte b : mac) { - if (sb.length() > 0) - sb.append(':'); - sb.append(String.format("%02x", b)); + if (Utils.isWindows()) { + machineId = System.getenv("COMPUTERNAME"); + } else { + try { + Runtime r = Runtime.getRuntime(); + Process p = r.exec("uname -a"); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + machineId = reader.readLine(); + } catch (Exception e) { + LOG.error("Error getting machineId from uname", e); } + } + if (machineId == null) { + try { + // Generate a unique identifier based on hardware information + List identifiers = new ArrayList<>(); - machineId = sb.toString(); - } catch (Exception e) { + // Get MAC addresses + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + byte[] mac = networkInterface.getHardwareAddress(); + if (mac != null) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X", mac[i])); + } + identifiers.add(sb.toString()); + } + } + + // Get hostname + String hostname = InetAddress.getLocalHost().getHostName(); + identifiers.add(hostname); + + // Sort to ensure consistent order + Collections.sort(identifiers); + + // Create a single string from all identifiers + StringBuilder combined = new StringBuilder(); + for (String id : identifiers) { + combined.append(id); + } + + // Create a hash of the combined string + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hash = md.digest(combined.toString().getBytes()); + + // Convert to hex string + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + hexString.append(String.format("%02x", b)); + } + + machineId = hexString.toString(); + + } catch (Exception e) { + LOG.error("Failed to generate machine ID", e); + } + } + if (machineId == null) { machineId = "Generic"; } } diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java index b60ece6f..4fba6bb2 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPane.java @@ -8,11 +8,13 @@ import org.diylc.plugins.chatbot.model.SubscriptionEntity; import org.diylc.plugins.chatbot.service.ChatbotService; import org.diylc.plugins.cloud.service.NotLoggedInException; import org.diylc.swing.ISwingUI; +import org.diylc.utils.FileUtils; import javax.swing.*; import java.awt.*; import java.util.List; import java.util.Objects; +import java.util.Random; public class ChatbotPane extends JPanel { @@ -43,7 +45,7 @@ public class ChatbotPane extends JPanel { private JButton clearButton; private boolean loggedIn = false; - private String projectFileName = null; + private String projectFileName = "initial"; public ChatbotPane(ISwingUI swingUI, IPlugInPort plugInPort) { super(); @@ -202,7 +204,7 @@ public class ChatbotPane extends JPanel { public void refreshChat() { boolean currentLoggedIn = plugInPort.getCloudService().isLoggedIn(); - String currentProjectFileName = plugInPort.getCurrentFileName(); + String currentProjectFileName = FileUtils.extractFileName(plugInPort.getCurrentFileName()); if (this.loggedIn == currentLoggedIn && Objects.equals(currentProjectFileName, this.projectFileName)) return; @@ -210,17 +212,26 @@ public class ChatbotPane extends JPanel { this.loggedIn = currentLoggedIn; this.projectFileName = currentProjectFileName; - resetChat(); - try { - SubscriptionEntity subscriptionInfo = plugInPort.getChatbotService().getSubscriptionInfo(); - appendSection(ChatbotService.SYSTEM, "Subscribed to " + subscriptionInfo.getTier() + - " tier with " + subscriptionInfo.getRemainingCredits() + " remaining credits."); - } catch (NotLoggedInException e) { - LOG.error("Error getting subscription info", e); - appendSection(ChatbotService.SYSTEM, "Error getting subscription info"); + clearChat(); + if (!currentLoggedIn) { + appendSection(ChatbotService.SYSTEM, "Please log into your cloud account to use the AI assistant"); + getAskButton().setEnabled(false); + getClearButton().setEnabled(false); + } else { + try { + SubscriptionEntity subscriptionInfo = plugInPort.getChatbotService().getSubscriptionInfo(); + appendSection(ChatbotService.SYSTEM, + "Your current subscription tier is " + subscriptionInfo.getTier() + + " expiring on " + subscriptionInfo.getEndDate() + " with " + + subscriptionInfo.getRemainingCredits() + " credits remaining."); + } catch (NotLoggedInException e) { + LOG.error("Error getting subscription info", e); + appendSection(ChatbotService.SYSTEM, "Error getting subscription info"); + } + fetchChatHistory(); + getAskButton().setEnabled(true); + getClearButton().setEnabled(true); } - fetchChatHistory(); - getAskButton().setEnabled(true); } private void fetchChatHistory() { @@ -250,13 +261,32 @@ public class ChatbotPane extends JPanel { return chatEditorPane; } - private void resetChat() { - String sampleChat = chatbotService.getInitialChatContents(); + private void clearChat() { + String sampleChat = getInitialChatContents(); chatEditorPane.setContentType("text/html"); chatEditorPane.setText(HTML_STYLE + "" + sampleChat + ""); } + private String getInitialChatContents() { + Random r = new Random(); + + String chatHtml = + "
" + ChatbotService.WELCOME_MESSAGE + "


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

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

\n"; + + return chatHtml; + } + public JButton getAskButton() { if (askButton == null) { askButton = new JButton("Ask"); @@ -337,7 +367,7 @@ public class ChatbotPane extends JPanel { @Override public void complete(Void result) { - resetChat(); + clearChat(); } }, false); }); diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPlugin.java index 81c1a30a..fbcef87a 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPlugin.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/chatbot/ChatbotPlugin.java @@ -43,35 +43,18 @@ public class ChatbotPlugin implements IPlugIn { public ChatbotPlugin(ISwingUI swingUI) { super(); this.swingUI = swingUI; - - // Delay a test of logged in state to give it time to complete - Timer timer = new Timer(2000, e -> { - if (!plugInPort.getCloudService().isLoggedIn()) { - getChatbotPane().refreshChat(); - } - }); - timer.setRepeats(false); // Make it one-shot - timer.start(); } @Override public EnumSet getSubscribedEventTypes() { return EnumSet.of(EventType.FILE_STATUS_CHANGED, EventType.PROJECT_LOADED, - EventType.PROJECT_MODIFIED, EventType.SELECTION_CHANGED, EventType.CLOUD_LOGGED_IN, - EventType.CLOUD_LOGGED_OUT); + EventType.CLOUD_LOGGED_IN, EventType.CLOUD_LOGGED_OUT); } @SuppressWarnings("unchecked") @Override public void processMessage(EventType eventType, Object... params) { switch (eventType) { - case PROJECT_MODIFIED: -// getChatbotPane().setComponents(((Project) params[1]).getComponents(), -// plugInPort.getSelectedComponents()); - break; - case SELECTION_CHANGED: -// getChatbotPane().setSelection((Set>) params[0]); - break; case FILE_STATUS_CHANGED: case PROJECT_LOADED: case CLOUD_LOGGED_IN: diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/cloud/CloudPlugIn.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/cloud/CloudPlugIn.java index 494e6b5e..d34c6c4f 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/cloud/CloudPlugIn.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/cloud/CloudPlugIn.java @@ -108,33 +108,6 @@ public class CloudPlugIn implements IPlugIn { public void connect(IPlugInPort plugInPort) { this.plugInPort = plugInPort; this.cloudService = plugInPort.getCloudService(); - - initialize(); - } - - private void initialize() { - this.swingUI.executeBackgroundTask(new ITask() { - - @Override - public Boolean doInBackground() throws Exception { - return cloudService.tryLogInWithToken(); - } - - @Override - public void failed(Exception e) { - LOG.error("Error while trying to login using token"); - } - - @Override - public void complete(Boolean result) { - try { - if (result) - loggedIn(); - } catch (Exception e) { - LOG.error("Error while trying to login with token", e); - } - } - }, false); } public CloudBrowserFrame getCloudBrowser() { @@ -206,11 +179,20 @@ public class CloudPlugIn implements IPlugIn { @Override public EnumSet getSubscribedEventTypes() { - return null; + return EnumSet.of(EventType.CLOUD_LOGGED_IN, EventType.CLOUD_LOGGED_OUT); } @Override - public void processMessage(EventType eventType, Object... params) {} + public void processMessage(EventType eventType, Object... params) { + switch (eventType) { + case CLOUD_LOGGED_IN: + loggedIn(); + break; + case CLOUD_LOGGED_OUT: + loggedOut(); + break; + } + } class LibraryAction extends AbstractAction {