Cloud login improvements

This commit is contained in:
Bane Stojkovic
2025-04-05 10:36:55 +02:00
parent d097bcd740
commit a5cc9c1374
5 changed files with 161 additions and 114 deletions
@@ -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 =
"<div class='system'>" + WELCOME_MESSAGE + "</div><br><br>\n" +
"<div class='assistant'>I'm here to help you design, build, and troubleshoot your electronics projects using DIY Layout Creator (DIYLC). You can ask me questions about:</div><br>\n" +
// "<ul>\n" +
"\n" +
"- <b>Electronics theory or concepts:</b> <i>e.g.,</i> '<span class='user'>" + electronicsQuestions[r.nextInt(
electronicsQuestions.length)] + "</span>'<br>\n" +
"- <b>DIYLC features and usage:</b> <i>e.g.,</i> '<span class='user'>" + diylcQuestions[r.nextInt(diylcQuestions.length)] + "</span>'<br>\n" +
"- <b>Your current circuit project:</b> <i>e.g.,</i> '<span class='user'>" + circuitQuestions[r.nextInt(circuitQuestions.length)] + "</span>'<br>\n" +
// "</ul><br>\n" +
"<br>\n" +
"<div class='assistant'>Feel free to ask your own question or use one of these examples to get started!</div><br>\n";
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?",
@@ -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<String> identifiers = new ArrayList<>();
machineId = sb.toString();
} catch (Exception e) {
// Get MAC addresses
Enumeration<NetworkInterface> 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";
}
}
@@ -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 + "<body>" + sampleChat + "</body>");
}
private String getInitialChatContents() {
Random r = new Random();
String chatHtml =
"<div class='system'>" + ChatbotService.WELCOME_MESSAGE + "</div><br><br>\n" +
"<div class='assistant'>I'm here to help you design, build, and troubleshoot your electronics projects using DIY Layout Creator (DIYLC). You can ask me questions about:</div><br>\n" +
// "<ul>\n" +
"\n" +
"- <b>Electronics theory or concepts:</b> <i>e.g.,</i> '<span class='user'>" +ChatbotService.electronicsQuestions[r.nextInt(
ChatbotService.electronicsQuestions.length)] + "</span>'<br>\n" +
"- <b>DIYLC features and usage:</b> <i>e.g.,</i> '<span class='user'>" + ChatbotService.diylcQuestions[r.nextInt(ChatbotService.diylcQuestions.length)] + "</span>'<br>\n" +
"- <b>Your current circuit project:</b> <i>e.g.,</i> '<span class='user'>" + ChatbotService.circuitQuestions[r.nextInt(ChatbotService.circuitQuestions.length)] + "</span>'<br>\n" +
// "</ul><br>\n" +
"<br>\n" +
"<div class='assistant'>Feel free to ask your own question or use one of these examples to get started!</div><br>\n";
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);
});
@@ -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<EventType> 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<IDIYComponent<?>>) params[0]);
break;
case FILE_STATUS_CHANGED:
case PROJECT_LOADED:
case CLOUD_LOGGED_IN:
@@ -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<Boolean>() {
@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<EventType> 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 {