Skip to content

Commit

Permalink
Feat #148: Allow creation of custom commands
Browse files Browse the repository at this point in the history
  • Loading branch information
stephanj committed Jul 4, 2024
1 parent 5bba3a2 commit 7bdb91b
Show file tree
Hide file tree
Showing 21 changed files with 417 additions and 73 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

group = "com.devoxx.genie"
version = "0.2.2"
version = "0.2.3"

repositories {
mavenCentral()
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/devoxx/genie/model/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ private Constant() {
/test: write unit tests on selected code
/explain: explain the selected code
/review: review selected code
/custom: set custom prompt in settings
The Devoxx Genie is open source and available at https://github.com/devoxx/DevoxxGenieIDEAPlugin.
Do not include any more info which might be incorrect, like discord, twitter, documentation or website info.
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/devoxx/genie/model/CustomPrompt.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.devoxx.genie.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class CustomPrompt {
private String name;
private String prompt;
}
17 changes: 12 additions & 5 deletions src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devoxx.genie.service;

import com.devoxx.genie.model.CustomPrompt;
import com.devoxx.genie.model.request.ChatMessageContext;
import com.devoxx.genie.ui.panel.PromptOutputPanel;
import com.devoxx.genie.ui.settings.DevoxxGenieStateService;
Expand Down Expand Up @@ -87,16 +88,22 @@ public void stopPromptExecution() {
private Optional<String> getCommandFromPrompt(@NotNull String prompt,
PromptOutputPanel promptOutputPanel) {
if (prompt.startsWith("/")) {
DevoxxGenieStateService settings = DevoxxGenieStateService.getInstance();

if (prompt.equalsIgnoreCase("/test")) {
prompt = DevoxxGenieStateService.getInstance().getTestPrompt();
prompt = settings.getTestPrompt();
} else if (prompt.equalsIgnoreCase("/review")) {
prompt = DevoxxGenieStateService.getInstance().getReviewPrompt();
prompt = settings.getReviewPrompt();
} else if (prompt.equalsIgnoreCase("/explain")) {
prompt = DevoxxGenieStateService.getInstance().getExplainPrompt();
} else if (prompt.equalsIgnoreCase("/custom")) {
prompt = DevoxxGenieStateService.getInstance().getCustomPrompt();
prompt = settings.getExplainPrompt();
} else {
// Check for custom prompts
for (CustomPrompt customPrompt : settings.getCustomPrompts()) {
if (prompt.equalsIgnoreCase("/" + customPrompt.getName())) {
prompt = customPrompt.getPrompt();
return Optional.of(prompt);
}
}
promptOutputPanel.showHelpText();
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.devoxx.genie.service.FileListManager;
import com.devoxx.genie.service.LLMProviderService;
import com.devoxx.genie.ui.component.PromptInputArea;
import com.devoxx.genie.ui.listener.CustomPromptChangeListener;
import com.devoxx.genie.ui.listener.LLMSettingsChangeListener;
import com.devoxx.genie.ui.listener.SettingsChangeListener;
import com.devoxx.genie.ui.panel.ActionButtonsPanel;
Expand Down Expand Up @@ -44,7 +45,10 @@
/**
* The Devoxx Genie Tool Window Content.
*/
public class DevoxxGenieToolWindowContent implements SettingsChangeListener, LLMSettingsChangeListener, ConversationStarter {
public class DevoxxGenieToolWindowContent implements SettingsChangeListener,
LLMSettingsChangeListener,
ConversationStarter,
CustomPromptChangeListener {

private static final float SPLITTER_PROPORTION = 0.8f;

Expand Down Expand Up @@ -89,6 +93,7 @@ public DevoxxGenieToolWindowContent(@NotNull ToolWindow toolWindow) {
private void setupMessageBusConnection(@NotNull ToolWindow toolWindow) {
MessageBusConnection messageBusConnection = project.getMessageBus().connect();
messageBusConnection.subscribe(AppTopics.LLM_SETTINGS_CHANGED_TOPIC, this);
messageBusConnection.subscribe(AppTopics.CUSTOM_PROMPT_CHANGED_TOPIC, this);
Disposer.register(toolWindow.getDisposable(), messageBusConnection);
}

Expand Down Expand Up @@ -383,4 +388,14 @@ private void hideModelNameComboBox() {
public void settingsChanged() {
updateModelNamesComboBox(DevoxxGenieStateService.getInstance().getSelectedProvider());
}

@Override
public void onCustomPromptsChanged() {
SwingUtilities.invokeLater(() -> {
// Update the help panel or any other UI components that display custom prompts
if (promptOutputPanel != null) {
promptOutputPanel.updateHelpText();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.devoxx.genie.ui.component;

import com.devoxx.genie.model.CustomPrompt;
import com.devoxx.genie.ui.listener.CustomPromptChangeListener;
import com.devoxx.genie.ui.settings.DevoxxGenieStateService;
import com.devoxx.genie.ui.topic.AppTopics;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.JBColor;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.text.*;
Expand All @@ -9,31 +16,44 @@
import java.util.ArrayList;
import java.util.List;

public class CommandAutoCompleteTextField extends PlaceholderTextArea {
public class CommandAutoCompleteTextField extends PlaceholderTextArea implements CustomPromptChangeListener {

private static final Logger LOG = Logger.getInstance(CommandAutoCompleteTextField.class);

private final List<String> commands = new ArrayList<>();
private boolean isAutoCompleting = false;

private String placeholder = "";

public CommandAutoCompleteTextField() {
super();
initializeCommands();

setDocument(new CommandDocument());
addKeyListener(new CommandKeyListener());

ApplicationManager.getApplication()
.getMessageBus()
.connect()
.subscribe(AppTopics.CUSTOM_PROMPT_CHANGED_TOPIC, this);

initializeCommands();
}

private void initializeCommands() {
commands.clear();
commands.add("/test");
commands.add("/explain");
commands.add("/review");
commands.add("/custom");
// Add more commands as needed

DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance();
for (CustomPrompt customPrompt : stateService.getCustomPrompts()) {
commands.add("/" + customPrompt.getName());
}
}

private class CommandKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
public void keyPressed(@NotNull KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE && e.isControlDown()) {
autoComplete();
e.consume();
Expand All @@ -54,7 +74,7 @@ private void autoComplete() {
getDocument().remove(start, currentLine.length());
getDocument().insertString(start, command, null);
} catch (BadLocationException ex) {
ex.printStackTrace();
LOG.error("Error while auto-completing command", ex);
}
setCaretPosition(text.length() - currentLine.length() + command.length());
isAutoCompleting = false;
Expand Down Expand Up @@ -83,7 +103,6 @@ public void insertString(int offs, String str, AttributeSet a) throws BadLocatio
if (currentLine.startsWith("/") && currentLine.length() > 1) {
for (String command : commands) {
if (command.startsWith(currentLine) && !command.equals(currentLine)) {
int start = text.lastIndexOf("\n") + 1;
String completion = command.substring(currentLine.length());
isAutoCompleting = true;
insertString(getLength(), completion, null);
Expand All @@ -96,25 +115,26 @@ public void insertString(int offs, String str, AttributeSet a) throws BadLocatio
}
}
} catch (BadLocationException e) {
e.printStackTrace();
LOG.error("Error while auto-completing command", e);
}
});
}
}

// Placeholder functionality
private String placeholder = "";

public void setPlaceholder(String placeholder) {
this.placeholder = placeholder;
repaint();
}

public void onCustomPromptsChanged() {
initializeCommands();
}

@Override
protected void paintComponent(java.awt.Graphics g) {
super.paintComponent(g);
if (getText().isEmpty() && !placeholder.isEmpty()) {
g.setColor(java.awt.Color.GRAY);
g.setColor(JBColor.GRAY);
g.drawString(placeholder, getInsets().left, g.getFontMetrics().getMaxAscent() + getInsets().top);
}
}
Expand Down
77 changes: 77 additions & 0 deletions src/main/java/com/devoxx/genie/ui/dialog/CustomPromptDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.devoxx.genie.ui.dialog;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextArea;
import com.intellij.ui.components.JBTextField;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;

public class CustomPromptDialog extends DialogWrapper {
private final JBTextField nameField;
private final JBTextArea promptArea;

public CustomPromptDialog(Project project) {
super(project);
setTitle("Add Custom Prompt");

nameField = new JBTextField(20);
promptArea = new JBTextArea(10, 40);
promptArea.setLineWrap(true);
promptArea.setWrapStyleWord(true);

init();
}

@Nullable
@Override
protected JComponent createCenterPanel() {
JPanel dialogPanel = new JPanel(new BorderLayout(10, 10));

// Name input
JPanel namePanel = new JPanel(new BorderLayout());
namePanel.add(new JLabel("Command Name:"), BorderLayout.WEST);
namePanel.add(nameField, BorderLayout.CENTER);

// Prompt input
JPanel promptPanel = new JPanel(new BorderLayout());
promptPanel.add(new JLabel("Prompt:"), BorderLayout.NORTH);
JBScrollPane scrollPane = new JBScrollPane(promptArea);
promptPanel.add(scrollPane, BorderLayout.CENTER);

dialogPanel.add(namePanel, BorderLayout.NORTH);
dialogPanel.add(promptPanel, BorderLayout.CENTER);

return dialogPanel;
}

@Override
protected void doOKAction() {
if (validateInput()) {
super.doOKAction();
}
}

private boolean validateInput() {
if (nameField.getText().trim().isEmpty()) {
Messages.showErrorDialog("The command name cannot be empty.", "Invalid Name");
return false;
}
if (promptArea.getText().trim().isEmpty()) {
Messages.showErrorDialog("The prompt cannot be empty.", "Invalid Prompt");
return false;
}
return true;
}

public String getCommandName() {
return nameField.getText().trim();
}

public String getPrompt() {
return promptArea.getText().trim();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.devoxx.genie.ui.listener;

public interface CustomPromptChangeListener {
void onCustomPromptsChanged();
}
47 changes: 37 additions & 10 deletions src/main/java/com/devoxx/genie/ui/panel/HelpPanel.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
package com.devoxx.genie.ui.panel;

import com.intellij.ui.components.JBLabel;

import java.awt.*;

import com.intellij.ui.components.JBScrollPane;
import javax.swing.*;

public class HelpPanel extends BackgroundPanel {
private final JEditorPane helpPane;

/**
* Create a help panel, listing the fixed prompt commands available
*
* @param helpMsg the help message
*/
public HelpPanel(String helpMsg) {
super("helpPanel");
setLayout(new BorderLayout());
withPreferredHeight(80);
withPreferredHeight(175);
add(new JBLabel(helpMsg), BorderLayout.CENTER);

helpPane = new JEditorPane("text/html", "");
helpPane.setEditable(false);
helpPane.setOpaque(false);
helpPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);

JBScrollPane scrollPane = new JBScrollPane(helpPane);
scrollPane.setBorder(null);
scrollPane.setOpaque(false);
scrollPane.getViewport().setOpaque(false);

add(scrollPane, BorderLayout.CENTER);
updateHelpText(helpMsg);
}

public void updateHelpText(String newHelpMsg) {
helpPane.setText(newHelpMsg);
updatePanelSize();
}

private void updatePanelSize() {
SwingUtilities.invokeLater(() -> {
int preferredHeight = calculatePreferredHeight();
setPreferredSize(new Dimension(getWidth(), preferredHeight));
revalidate();
repaint();
});
}

private int calculatePreferredHeight() {
int contentHeight = helpPane.getPreferredSize().height;
int maxHeight = 300; // Set a maximum height if needed
return Math.min(contentHeight + 20, maxHeight); // Add some padding
}
}
Loading

0 comments on commit 7bdb91b

Please sign in to comment.