chatbot improvements

This commit is contained in:
Bane Stojkovic
2025-04-04 21:49:52 +02:00
parent acc28c2e54
commit d097bcd740
23 changed files with 437 additions and 139 deletions
@@ -3,14 +3,16 @@ package org.diylc.swing.plugins.chatbot;
import org.apache.log4j.Logger;
import org.diylc.common.IPlugInPort;
import org.diylc.common.ITask;
import org.diylc.plugins.chatbot.presenter.ChatbotPresenter;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.plugins.chatbot.model.ChatMessageEntity;
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 javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.Random;
import java.util.List;
import java.util.Objects;
public class ChatbotPane extends JPanel {
@@ -22,22 +24,32 @@ 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);
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
".system { color: #E5C07B; }" + // Warmer, muted yellow
".temporary { color: #555555; }" + "</style>", MONOSPACED_FONT.getFamily(),
MONOSPACED_FONT.getSize(), TERMINAL_FG.getRed(), TERMINAL_FG.getGreen(),
TERMINAL_FG.getBlue(), TERMINAL_BG.getRed(), TERMINAL_BG.getGreen(), TERMINAL_BG.getBlue());
private static final Color PROMPT_BG = new Color(60, 60, 60); // Slightly brighter than TERMINAL_BG
private ISwingUI swingUI;
private IPlugInPort plugInPort;
private ChatbotPresenter chatbotPresenter;
private ChatbotService chatbotService;
private JTextArea promptArea;
private JEditorPane chatEditorPane;
private JButton askButton;
private JButton clearButton;
private boolean loggedIn = false;
private String projectFileName = null;
public ChatbotPane(ISwingUI swingUI, IPlugInPort plugInPort) {
super();
this.swingUI = swingUI;
this.plugInPort = plugInPort;
this.chatbotPresenter = new ChatbotPresenter(plugInPort);
this.chatbotService = new ChatbotService(plugInPort);
setName("AI Assistant");
// Set panel background color
@@ -134,7 +146,7 @@ public class ChatbotPane extends JPanel {
promptArea.setPreferredSize(new Dimension(200, desiredHeight));
// Add placeholder text behavior
final String placeholder = "Type your question here...";
final String placeholder = "Ask me anything";
promptArea.setText(placeholder);
promptArea.setForeground(new Color(128, 128, 128)); // Dimmer text for placeholder
@@ -188,6 +200,41 @@ public class ChatbotPane extends JPanel {
return promptArea;
}
public void refreshChat() {
boolean currentLoggedIn = plugInPort.getCloudService().isLoggedIn();
String currentProjectFileName = plugInPort.getCurrentFileName();
if (this.loggedIn == currentLoggedIn && Objects.equals(currentProjectFileName, this.projectFileName))
return;
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");
}
fetchChatHistory();
getAskButton().setEnabled(true);
}
private void fetchChatHistory() {
try {
List<ChatMessageEntity> chatHistory = plugInPort.getChatbotService().getChatHistory();
chatHistory.forEach(message -> {
appendSection(ChatbotService.USER, message.getPrompt());
appendSection(ChatbotService.ASSISTANT, message.getResponse());
});
} catch (NotLoggedInException e) {
appendSection(ChatbotService.SYSTEM, "Failed to retrieve chat history.");
}
}
public JEditorPane getChatEditorPane() {
if (chatEditorPane == null) {
chatEditorPane = new JEditorPane("text/html", "<html></html>");
@@ -196,30 +243,20 @@ public class ChatbotPane extends JPanel {
chatEditorPane.setBackground(TERMINAL_BG);
chatEditorPane.setForeground(TERMINAL_FG);
chatEditorPane.setCaretColor(TERMINAL_FG); // Make the cursor visible on dark background
// Set up HTML styling
String htmlStyle = 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
".system { color: #E5C07B; }" + // Warmer, muted yellow
".temporary { color: #555555; }" +
"</style>",
MONOSPACED_FONT.getFamily(),
MONOSPACED_FONT.getSize(),
TERMINAL_FG.getRed(), TERMINAL_FG.getGreen(), TERMINAL_FG.getBlue(),
TERMINAL_BG.getRed(), TERMINAL_BG.getGreen(), TERMINAL_BG.getBlue());
String sampleChat = chatbotPresenter.getInitialChatContents();
chatEditorPane.setContentType("text/html");
chatEditorPane.setText(htmlStyle + "<body><pre>" + sampleChat + "</pre></body>");
chatEditorPane.setText(HTML_STYLE + "<body></body>");
}
return chatEditorPane;
}
private void resetChat() {
String sampleChat = chatbotService.getInitialChatContents();
chatEditorPane.setContentType("text/html");
chatEditorPane.setText(HTML_STYLE + "<body>" + sampleChat + "</body>");
}
public JButton getAskButton() {
if (askButton == null) {
askButton = new JButton("Ask");
@@ -229,8 +266,8 @@ public class ChatbotPane extends JPanel {
askButton.setBorderPainted(true);
askButton.addActionListener(e -> {
final String prompt = getPromptArea().getText();
appendSection(ChatbotPresenter.USER, prompt);
appendSection(ChatbotPresenter.TEMPORARY, "Waiting for the response...");
appendSection(ChatbotService.USER, prompt);
appendSection(ChatbotService.TEMPORARY, "Waiting for the response...");
getPromptArea().setText(null);
@@ -240,19 +277,19 @@ public class ChatbotPane extends JPanel {
@Override
public String doInBackground() throws Exception {
return chatbotPresenter.promptChatbot(prompt);
return chatbotService.promptChatbot(prompt);
}
@Override
public void failed(Exception e) {
appendSection(ChatbotPresenter.SYSTEM, "Failed to retrieve the response from the server. Error: " + e.getMessage());
appendSection(ChatbotService.SYSTEM, "Failed to retrieve the response from the server.");
LOG.error("Failed to retrieve the response from the server", e);
getClearButton().setEnabled(true);
}
@Override
public void complete(String result) {
appendSection(ChatbotPresenter.ASSISTANT, result);
appendSection(ChatbotService.ASSISTANT, result);
getClearButton().setEnabled(true);
}
}, false);
@@ -261,7 +298,7 @@ public class ChatbotPane extends JPanel {
return askButton;
}
private void appendSection(String style, String insertText) {
void appendSection(String style, String insertText) {
String text = getChatEditorPane().getText();
// remove any temporary divs
String regex = "(?s)<div\\s+class=[\"']temporary[\"'][^>]*>.*?</div>\\s*<br>\\s*";
@@ -281,6 +318,29 @@ public class ChatbotPane extends JPanel {
clearButton.setForeground(TERMINAL_FG);
clearButton.setFocusPainted(false);
clearButton.setBorderPainted(true);
clearButton.addActionListener(e -> {
swingUI.executeBackgroundTask(new ITask<Void>() {
@Override
public Void doInBackground() throws Exception {
chatbotService.deleteChatHistory();
return null;
}
@Override
public void failed(Exception e) {
appendSection(ChatbotService.SYSTEM, "Failed to delete chat history from the server.");
LOG.error("Failed to delete chat history from the server", e);
getClearButton().setEnabled(true);
}
@Override
public void complete(Void result) {
resetChat();
}
}, false);
});
}
return clearButton;
}
@@ -26,15 +26,11 @@ import org.diylc.common.BadPositionException;
import org.diylc.common.EventType;
import org.diylc.common.IPlugIn;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.swing.ISwingUI;
import org.diylc.swing.plugins.config.ConfigPlugin;
import javax.swing.*;
import java.util.EnumSet;
import java.util.Set;
public class ChatbotPlugin implements IPlugIn {
@@ -47,31 +43,44 @@ 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.PROJECT_MODIFIED, EventType.SELECTION_CHANGED, EventType.CLOUD_LOGGED_IN,
EventType.CLOUD_LOGGED_OUT);
}
@SuppressWarnings("unchecked")
@Override
public void processMessage(EventType eventType, Object... params) {
// switch (eventType) {
// case PROJECT_MODIFIED:
switch (eventType) {
case PROJECT_MODIFIED:
// getChatbotPane().setComponents(((Project) params[1]).getComponents(),
// plugInPort.getSelectedComponents());
// break;
// case PROJECT_LOADED:
// getChatbotPane().setComponents(((Project) params[0]).getComponents(),
// plugInPort.getSelectedComponents());
// break;
// case SELECTION_CHANGED:
break;
case SELECTION_CHANGED:
// getChatbotPane().setSelection((Set<IDIYComponent<?>>) params[0]);
// default:
// break;
// }
break;
case FILE_STATUS_CHANGED:
case PROJECT_LOADED:
case CLOUD_LOGGED_IN:
case CLOUD_LOGGED_OUT:
getChatbotPane().refreshChat();
break;
default:
break;
}
}
@Override
@@ -31,6 +31,7 @@ import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
import org.diylc.plugins.cloud.service.CloudService;
import org.diylc.swingframework.ButtonDialog;
import org.diylc.common.EventType;
@@ -38,8 +39,7 @@ import org.diylc.common.IPlugIn;
import org.diylc.common.IPlugInPort;
import org.diylc.common.ITask;
import org.diylc.core.IView;
import org.diylc.plugins.cloud.presenter.CloudException;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.plugins.cloud.service.CloudException;
import org.diylc.presenter.Presenter;
import org.diylc.swing.ISwingUI;
import org.diylc.swing.gui.DialogFactory;
@@ -63,6 +63,7 @@ public class CloudPlugIn implements IPlugIn {
private IPlugInPort plugInPort;
private IPlugInPort thumbnailPresenter;
private ThumbnailGenerator thumbnailGenerator;
private CloudService cloudService;
private LibraryAction libraryAction;
@@ -106,6 +107,7 @@ public class CloudPlugIn implements IPlugIn {
@Override
public void connect(IPlugInPort plugInPort) {
this.plugInPort = plugInPort;
this.cloudService = plugInPort.getCloudService();
initialize();
}
@@ -115,7 +117,7 @@ public class CloudPlugIn implements IPlugIn {
@Override
public Boolean doInBackground() throws Exception {
return CloudPresenter.Instance.tryLogInWithToken();
return cloudService.tryLogInWithToken();
}
@Override
@@ -244,7 +246,7 @@ public class CloudPlugIn implements IPlugIn {
dialog.setVisible(true);
if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) {
try {
if (CloudPresenter.Instance.logIn(dialog.getUserName(), dialog.getPassword())) {
if (plugInPort.getCloudService().logIn(dialog.getUserName(), dialog.getPassword())) {
swingUI
.showMessage(
"You have successfully logged into the system. You will remain logged in from this machine until logged out.",
@@ -277,7 +279,7 @@ public class CloudPlugIn implements IPlugIn {
@Override
public void actionPerformed(ActionEvent e) {
CloudPresenter.Instance.logOut();
cloudService.logOut();
loggedOut();
}
}
@@ -301,7 +303,7 @@ public class CloudPlugIn implements IPlugIn {
@Override
public Void doInBackground() throws Exception {
CloudPresenter.Instance.createUserAccount(dialog.getUserName(), dialog.getPassword(), dialog.getEmail(),
cloudService.createUserAccount(dialog.getUserName(), dialog.getPassword(), dialog.getEmail(),
dialog.getWebsite(), dialog.getBio());
return null;
}
@@ -335,14 +337,14 @@ public class CloudPlugIn implements IPlugIn {
public void actionPerformed(ActionEvent e) {
try {
final UserEditDialog dialog =
DialogFactory.getInstance().createUserEditDialog(CloudPresenter.Instance.getUserDetails());
DialogFactory.getInstance().createUserEditDialog(plugInPort.getCloudService().getUserDetails());
dialog.setVisible(true);
if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) {
swingUI.executeBackgroundTask(new ITask<Void>() {
@Override
public Void doInBackground() throws Exception {
CloudPresenter.Instance.updateUserDetails(dialog.getEmail(), dialog.getWebsite(), dialog.getBio());
cloudService.updateUserDetails(dialog.getEmail(), dialog.getWebsite(), dialog.getBio());
return null;
}
@@ -384,7 +386,7 @@ public class CloudPlugIn implements IPlugIn {
@Override
public Void doInBackground() throws Exception {
CloudPresenter.Instance.updatePassword(dialog.getOldPassword(), dialog.getNewPassword());
cloudService.updatePassword(dialog.getOldPassword(), dialog.getNewPassword());
return null;
}
@@ -431,7 +433,7 @@ public class CloudPlugIn implements IPlugIn {
public String[] doInBackground() throws Exception {
LOG.debug("Uploading from " + file.getAbsolutePath());
thumbnailPresenter.loadProjectFromFile(file.getAbsolutePath());
return CloudPresenter.Instance.getCategories();
return cloudService.getCategories();
}
@Override
@@ -448,7 +450,7 @@ public class CloudPlugIn implements IPlugIn {
@Override
public Void doInBackground() throws Exception {
CloudPresenter.Instance.uploadProject(dialog.getName(), dialog.getCategory(), dialog
cloudService.uploadProject(dialog.getName(), dialog.getCategory(), dialog
.getDescription(), dialog.getKeywords(), plugInPort.getCurrentVersionNumber().toString(),
thumbnailFile, file, null);
return null;
@@ -37,11 +37,11 @@ import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import org.diylc.common.IPlugInPort;
import org.diylc.common.ITask;
import org.diylc.core.IView;
import org.diylc.plugins.cloud.model.CommentEntity;
import org.diylc.plugins.cloud.model.ProjectEntity;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.swing.ISimpleView;
import org.diylc.swing.gui.components.HTMLTextArea;
@@ -57,6 +57,7 @@ public class CommentDialog extends JDialog {
private static final long serialVersionUID = 1L;
private final IPlugInPort plugInPort;
private ProjectEntity project;
private List<CommentEntity> comments;
@@ -69,8 +70,9 @@ public class CommentDialog extends JDialog {
private ISimpleView cloudUI;
public CommentDialog(ISimpleView cloudUI, ProjectEntity project, List<CommentEntity> comments) {
public CommentDialog(ISimpleView cloudUI, IPlugInPort plugInPort, ProjectEntity project, List<CommentEntity> comments) {
super(cloudUI.getOwnerFrame(), "Comments on " + project.getName());
this.plugInPort = plugInPort;
this.project = project;
this.comments = comments;
this.cloudUI = cloudUI;
@@ -79,7 +81,7 @@ public class CommentDialog extends JDialog {
pack();
setLocationRelativeTo(cloudUI.getOwnerFrame());
setModal(true);
if (!CloudPresenter.Instance.isLoggedIn()) {
if (!plugInPort.getCloudService().isLoggedIn()) {
getReplyArea().setEnabled(false);
getReplyArea().setText("Must be logged in to reply.");
getSendButton().setEnabled(false);
@@ -162,7 +164,7 @@ public class CommentDialog extends JDialog {
@Override
public Void doInBackground() throws Exception {
CloudPresenter.Instance.postComment(project.getId(), comment);
plugInPort.getCloudService().postComment(project.getId(), comment);
return null;
}
@@ -176,7 +178,7 @@ public class CommentDialog extends JDialog {
getReplyArea().setText("");
CommentEntity newComment = new CommentEntity();
newComment.setPostedAt(JUST_NOW);
newComment.setUsername(CloudPresenter.Instance.getCurrentUsername());
newComment.setUsername(plugInPort.getCloudService().getCurrentUsername());
newComment.setComment(comment);
addComment(newComment).requestFocusInWindow();
getListPanel().revalidate();
@@ -45,8 +45,7 @@ import org.diylc.common.IPlugInPort;
import org.diylc.common.ITask;
import org.diylc.core.IView;
import org.diylc.plugins.cloud.model.ProjectEntity;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.plugins.cloud.presenter.SearchSession;
import org.diylc.plugins.cloud.service.SearchSession;
import org.diylc.swing.ISimpleView;
import org.diylc.swing.ISwingUI;
import org.diylc.utils.IconLoader;
@@ -84,7 +83,7 @@ public class CloudBrowserFrame extends JFrame implements ISimpleView {
this.setIconImage(IconLoader.Cloud.getImage());
this.setPreferredSize(new Dimension(700, 640));
this.plugInPort = plugInPort;
this.searchSession = new SearchSession();
this.searchSession = new SearchSession(plugInPort);
setContentPane(getSearchPanel());
this.pack();
@@ -103,7 +102,9 @@ public class CloudBrowserFrame extends JFrame implements ISimpleView {
@Override
public Pair<String[], String[]> doInBackground() throws Exception {
return new Pair<String[], String[]>(CloudPresenter.Instance.getCategories(), CloudPresenter.Instance.getSortings());
return new Pair<String[], String[]>(
plugInPort.getCloudService().getCategories(),
plugInPort.getCloudService().getSortings());
}
@Override
@@ -59,8 +59,7 @@ import org.diylc.common.PropertyWrapper;
import org.diylc.core.IView;
import org.diylc.plugins.cloud.model.CommentEntity;
import org.diylc.plugins.cloud.model.ProjectEntity;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.plugins.cloud.presenter.SearchSession;
import org.diylc.plugins.cloud.service.SearchSession;
import org.diylc.presenter.Presenter;
import org.diylc.swing.ISimpleView;
import org.diylc.swing.ISwingUI;
@@ -234,7 +233,7 @@ public class ResultsScrollPanel extends JScrollPane {
@Override
public List<CommentEntity> doInBackground() throws Exception {
return CloudPresenter.Instance.getComments(project.getId());
return plugInPort.getCloudService().getComments(project.getId());
}
@Override
@@ -245,7 +244,7 @@ public class ResultsScrollPanel extends JScrollPane {
@Override
public void complete(List<CommentEntity> result) {
CommentDialog dialog = new CommentDialog(cloudUI, project, result);
CommentDialog dialog = new CommentDialog(cloudUI, plugInPort, project, result);
dialog.setVisible(true);
}
@@ -343,7 +342,7 @@ public class ResultsScrollPanel extends JScrollPane {
editButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
List<PropertyWrapper> projectProperties = CloudPresenter.Instance.getProjectProperties(project);
List<PropertyWrapper> projectProperties = plugInPort.getCloudService().getProjectProperties(project);
PropertyEditorDialog editor =
DialogFactory.getInstance().createPropertyEditorDialog(cloudUI.getOwnerFrame(), projectProperties,
"Edit Published Project", true);
@@ -364,8 +363,8 @@ public class ResultsScrollPanel extends JScrollPane {
@Override
public ProjectEntity doInBackground() throws Exception {
CloudPresenter.Instance.updateProjectDetails(project, plugInPort.getCurrentVersionNumber().toString());
return CloudPresenter.Instance.fetchUserUploads(project.getId()).get(0);
plugInPort.getCloudService().updateProjectDetails(project, plugInPort.getCurrentVersionNumber().toString());
return plugInPort.getCloudService().fetchUserUploads(project.getId()).get(0);
}
@Override
@@ -418,11 +417,11 @@ public class ResultsScrollPanel extends JScrollPane {
thumbnailPresenter.loadProjectFromFile(file.getAbsolutePath());
final File thumbnailFile = File.createTempFile("upload-thumbnail", ".png");
if (ImageIO.write(thumbnailGenerator.getThumbnail(), "png", thumbnailFile)) {
CloudPresenter.Instance.replaceProjectFile(
plugInPort.getCloudService().replaceProjectFile(
plugInPort.getCurrentVersionNumber().toString(), thumbnailFile, file,
project.getId());
return new Pair<BufferedImage, ProjectEntity>(thumbnailGenerator.getThumbnail(),
CloudPresenter.Instance.fetchUserUploads(project.getId()).get(0));
plugInPort.getCloudService().fetchUserUploads(project.getId()).get(0));
} else {
cloudUI.showMessage(
"Could not prepare temporary files to be uploaded to the cloud.",
@@ -478,7 +477,7 @@ public class ResultsScrollPanel extends JScrollPane {
@Override
public Void doInBackground() throws Exception {
CloudPresenter.Instance.deleteProject(project.getId());
plugInPort.getCloudService().deleteProject(project.getId());
return null;
}
@@ -33,7 +33,6 @@ import org.diylc.common.IPlugInPort;
import org.diylc.common.ITask;
import org.diylc.core.IView;
import org.diylc.plugins.cloud.model.ProjectEntity;
import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.swing.ISimpleView;
import org.diylc.swing.ISwingUI;
import org.diylc.utils.IconLoader;
@@ -80,7 +79,7 @@ public class UploadManagerFrame extends JFrame implements ISimpleView {
@Override
public List<ProjectEntity> doInBackground() throws Exception {
return CloudPresenter.Instance.fetchUserUploads(null);
return plugInPort.getCloudService().fetchUserUploads(null);
}
@Override