Update toolwindow UI (#290)

This commit is contained in:
Carl-Robert 2023-11-26 10:52:47 +02:00 committed by GitHub
parent 3797126de4
commit 1df20ccb86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 538 additions and 356 deletions

View file

@ -5,13 +5,15 @@ import javax.swing.Icon;
public final class Icons {
public static final Icon DefaultIcon = IconLoader.getIcon("/icons/codegpt.svg", Icons.class);
public static final Icon DefaultSmallIcon =
public static final Icon Default = IconLoader.getIcon("/icons/codegpt.svg", Icons.class);
public static final Icon DefaultSmall =
IconLoader.getIcon("/icons/codegpt-small.svg", Icons.class);
public static final Icon AzureIcon = IconLoader.getIcon("/icons/azure.svg", Icons.class);
public static final Icon LlamaIcon = IconLoader.getIcon("/icons/llama.svg", Icons.class);
public static final Icon OpenAIIcon = IconLoader.getIcon("/icons/openai.svg", Icons.class);
public static final Icon SendIcon = IconLoader.getIcon("/icons/send.svg", Icons.class);
public static final Icon SparkleIcon = IconLoader.getIcon("/icons/sparkle.svg", Icons.class);
public static final Icon YouIcon = IconLoader.getIcon("/icons/you.svg", Icons.class);
public static final Icon Azure = IconLoader.getIcon("/icons/azure.svg", Icons.class);
public static final Icon Llama = IconLoader.getIcon("/icons/llama.svg", Icons.class);
public static final Icon OpenAI = IconLoader.getIcon("/icons/openai.svg", Icons.class);
public static final Icon Send = IconLoader.getIcon("/icons/send.svg", Icons.class);
public static final Icon Sparkle = IconLoader.getIcon("/icons/sparkle.svg", Icons.class);
public static final Icon You = IconLoader.getIcon("/icons/you.svg", Icons.class);
public static final Icon YouSmall = IconLoader.getIcon("/icons/you_small.png", Icons.class);
public static final Icon User = IconLoader.getIcon("/icons/user.svg", Icons.class);
}

View file

@ -44,7 +44,7 @@ public class GenerateGitCommitMessageAction extends AnAction {
super(
CodeGPTBundle.get("action.generateCommitMessage.title"),
CodeGPTBundle.get("action.generateCommitMessage.description"),
Icons.SparkleIcon);
Icons.Sparkle);
encodingManager = EncodingManager.getInstance();
}

View file

@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull;
public class AskAction extends AnAction {
public AskAction() {
super("New Chat", "Chat with CodeGPT", Icons.SparkleIcon);
super("New Chat", "Chat with CodeGPT", Icons.Sparkle);
EditorActionsUtil.registerOrReplaceAction(this);
}

View file

@ -1,6 +1,6 @@
package ee.carlrobert.codegpt.actions.toolwindow;
import static ee.carlrobert.codegpt.Icons.DefaultIcon;
import static ee.carlrobert.codegpt.Icons.Default;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
@ -37,7 +37,7 @@ public class DeleteAllConversationsAction extends AnAction {
int answer = Messages.showYesNoDialog(
"Are you sure you want to delete all conversations?",
"Clear History",
DefaultIcon);
Default);
if (answer == Messages.YES) {
var project = event.getProject();
if (project != null) {

View file

@ -100,8 +100,10 @@ public class CompletionRequestProvider {
COMPLETION_SYSTEM_PROMPT,
message.getPrompt(),
conversation.getMessages());
var configuration = ConfigurationState.getInstance();
return new LlamaCompletionRequest.Builder(prompt)
.setN_predict(512)
.setN_predict(configuration.getMaxTokens())
.setTemperature(configuration.getTemperature())
.build();
}

View file

@ -56,7 +56,7 @@ public class MethodNameLookupListener implements LookupManagerListener {
for (var value : response.split(",")) {
application.runReadAction(() -> {
lookup.addItem(
LookupElementBuilder.create(value.trim()).withIcon(Icons.SparkleIcon),
LookupElementBuilder.create(value.trim()).withIcon(Icons.Sparkle),
PrefixMatcher.ALWAYS_TRUE);
});
application.invokeLater(() -> lookup.refreshUi(true, true));

View file

@ -19,7 +19,7 @@ public class ConfigurationState implements PersistentStateComponent<Configuratio
private String systemPrompt = COMPLETION_SYSTEM_PROMPT;
private int maxTokens = 1000;
private double temperature = 0.2;
private double temperature = 0.1;
private boolean createNewChatOnEachAction;
private boolean ignoreGitCommitTokenLimit;
private boolean methodNameGenerationEnabled = true;

View file

@ -11,21 +11,21 @@ public class ModelIconLabel extends JBLabel {
public ModelIconLabel(String clientCode, String modelCode) {
if ("you.chat.completion".equals(clientCode)) {
setIcon(Icons.YouIcon);
setIcon(Icons.You);
return;
}
if ("chat.completion".equals(clientCode)) {
setIcon(Icons.OpenAIIcon);
setIcon(Icons.OpenAI);
}
if ("azure.chat.completion".equals(clientCode)) {
setIcon(Icons.AzureIcon);
setIcon(Icons.Azure);
}
if ("llama.chat.completion".equals(clientCode)) {
setIcon(Icons.LlamaIcon);
setIcon(Icons.Llama);
}
setText(formatModelName(modelCode));
setFont(JBFont.small().asBold());
setFont(JBFont.small());
setHorizontalAlignment(SwingConstants.LEADING);
}

View file

@ -1,7 +1,6 @@
package ee.carlrobert.codegpt.toolwindow.chat;
import static ee.carlrobert.codegpt.util.UIUtil.createScrollPaneWithSmartScroller;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import static java.lang.String.format;
import com.intellij.openapi.diagnostic.Logger;
@ -16,6 +15,7 @@ import ee.carlrobert.codegpt.completions.CompletionRequestService;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.components.ChatMessageResponseBody;
@ -24,6 +24,7 @@ import ee.carlrobert.codegpt.toolwindow.chat.components.TotalTokensPanel;
import ee.carlrobert.codegpt.toolwindow.chat.components.UserMessagePanel;
import ee.carlrobert.codegpt.toolwindow.chat.components.UserPromptTextArea;
import ee.carlrobert.codegpt.toolwindow.chat.components.UserPromptTextAreaHeader;
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
import ee.carlrobert.codegpt.util.EditorUtil;
import ee.carlrobert.codegpt.util.file.FileUtil;
import java.awt.BorderLayout;
@ -65,7 +66,7 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
EditorUtil.getSelectedEditorSelectedText(project),
this);
userPromptTextArea = new UserPromptTextArea(this::handleSubmit, totalTokensPanel);
rootPanel = createRootPanel(settings);
rootPanel = createRootPanel(settings.getSelectedService());
userPromptTextArea.requestFocusInWindow();
userPromptTextArea.requestFocus();
}
@ -201,18 +202,21 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
sendMessage(message);
}
private JPanel createUserPromptPanel(SettingsState settings) {
private JPanel createUserPromptPanel(ServiceType selectedService) {
var panel = new JPanel(new BorderLayout());
panel.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0),
JBUI.Borders.empty(8)));
panel.setBackground(getPanelBackgroundColor());
panel.add(new UserPromptTextAreaHeader(settings, totalTokensPanel), BorderLayout.NORTH);
panel.add(userPromptTextArea, BorderLayout.SOUTH);
var contentManager = project.getService(StandardChatToolWindowContentManager.class);
panel.add(JBUI.Panels.simplePanel(new UserPromptTextAreaHeader(
selectedService,
totalTokensPanel,
contentManager::createNewTabPanel)), BorderLayout.NORTH);
panel.add(JBUI.Panels.simplePanel(userPromptTextArea), BorderLayout.CENTER);
return panel;
}
private JPanel createRootPanel(SettingsState settings) {
private JPanel createRootPanel(ServiceType selectedService) {
var gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weighty = 1;
@ -226,7 +230,7 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
gbc.weighty = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridy = 1;
rootPanel.add(createUserPromptPanel(settings), gbc);
rootPanel.add(createUserPromptPanel(selectedService), gbc);
return rootPanel;
}
}

View file

@ -1,7 +1,5 @@
package ee.carlrobert.codegpt.toolwindow.chat;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel;
import com.intellij.openapi.roots.ui.componentsList.layout.VerticalStackLayout;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
@ -73,7 +71,7 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel {
// TODO: Move
private JTextPane createYouCouponTextPane() {
var textPane = UIUtil.createTextPane(
return UIUtil.createTextPane(
"<html>\n"
+ "<body>\n"
+ " <p style=\"margin: 4px 0;\">Use CodeGPT coupon for free month of GPT-4.</p>\n"
@ -81,10 +79,7 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel {
+ " <a href=\"https://you.com/plans\">Sign up here</a>\n"
+ " </p>\n"
+ "</body>\n"
+ "</html>"
);
textPane.setBackground(getPanelBackgroundColor());
textPane.setFocusable(false);
return textPane;
+ "</html>",
false);
}
}

View file

@ -4,6 +4,8 @@ import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.vladsch.flexmark.ast.BulletListItem;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.CodeBlock;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.OrderedListItem;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.html.HtmlWriter;
@ -21,12 +23,23 @@ public class ResponseNodeRenderer implements NodeRenderer {
public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
return Set.of(
new NodeRenderingHandler<>(Paragraph.class, this::renderParagraph),
new NodeRenderingHandler<>(Code.class, this::renderCode)
new NodeRenderingHandler<>(Code.class, this::renderCode),
new NodeRenderingHandler<>(CodeBlock.class, this::renderCodeBlock),
new NodeRenderingHandler<>(BulletListItem.class, this::renderBulletListItem),
new NodeRenderingHandler<>(Heading.class, this::renderHeading),
new NodeRenderingHandler<>(OrderedListItem.class, this::renderOrderedListItem)
);
}
private void renderCode(Code node, NodeRendererContext context, HtmlWriter html) {
html.attr("style", "color: " + ColorUtil.toHex(new JBColor(0x00627A, 0xCC7832)));
private void renderCodeBlock(CodeBlock node, NodeRendererContext context, HtmlWriter html) {
html.attr("style", "white-space: pre-wrap;");
context.delegateRender();
}
private void renderHeading(Heading node, NodeRendererContext context, HtmlWriter html) {
if (node.getLevel() == 3) {
html.attr("style", "margin-top: 4px; margin-bottom: 4px;");
}
context.delegateRender();
}
@ -39,6 +52,27 @@ public class ResponseNodeRenderer implements NodeRenderer {
context.delegateRender();
}
private void renderCode(Code node, NodeRendererContext context, HtmlWriter html) {
html.attr("style", "color: " + ColorUtil.toHex(new JBColor(0x00627A, 0xCC7832)));
context.delegateRender();
}
private void renderBulletListItem(
BulletListItem node,
NodeRendererContext context,
HtmlWriter html) {
html.attr("style", "margin-bottom: 4px;");
context.delegateRender();
}
private void renderOrderedListItem(
OrderedListItem node,
NodeRendererContext context,
HtmlWriter html) {
html.attr("style", "margin-bottom: 4px;");
context.delegateRender();
}
public static class Factory implements NodeRendererFactory {
@NotNull

View file

@ -1,6 +1,5 @@
package ee.carlrobert.codegpt.toolwindow.chat.components;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import static java.lang.String.format;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
@ -13,6 +12,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.JBUI;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.html.HtmlRenderer;
@ -36,7 +36,6 @@ import java.util.stream.Collectors;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.UIManager;
public class ChatMessageResponseBody extends JPanel {
@ -44,33 +43,23 @@ public class ChatMessageResponseBody extends JPanel {
private final Disposable parentDisposable;
private final StreamParser streamParser;
private final boolean readOnly;
private JPanel currentlyProcessedElement;
private ResponseEditorPanel currentlyProcessedEditor;
private JTextPane currentlyProcessedTextPane;
private boolean responseReceived;
public ChatMessageResponseBody(Project project, Disposable parentDisposable) {
this(project, getPanelBackgroundColor(), false, parentDisposable);
this(project, false, parentDisposable);
}
public ChatMessageResponseBody(
Project project,
boolean withGhostText,
Disposable parentDisposable) {
this(project, getPanelBackgroundColor(), withGhostText, parentDisposable);
this(project, withGhostText, false, parentDisposable);
}
public ChatMessageResponseBody(
Project project,
Color backgroundColor,
boolean withGhostText,
Disposable parentDisposable) {
this(project, backgroundColor, withGhostText, false, parentDisposable);
}
public ChatMessageResponseBody(
Project project,
Color backgroundColor,
boolean withGhostText,
boolean readOnly,
Disposable parentDisposable) {
@ -80,15 +69,13 @@ public class ChatMessageResponseBody extends JPanel {
this.streamParser = new StreamParser();
this.readOnly = readOnly;
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBackground(backgroundColor);
setOpaque(false);
if (withGhostText) {
prepareProcessingTextResponse(!readOnly);
currentlyProcessedTextPane.setText(
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">&#8205;</p></html>");
}
UIManager.addPropertyChangeListener(propertyChangeEvent -> setBackground(backgroundColor));
}
public ChatMessageResponseBody withResponse(String response) {
@ -146,7 +133,7 @@ public class ChatMessageResponseBody extends JPanel {
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">%s</p></html>",
message);
if (responseReceived) {
add(new ResponseWrapper().add(createTextPane(errorText, false)));
add(createTextPane(errorText, false));
} else {
currentlyProcessedTextPane.setText(errorText);
}
@ -159,24 +146,12 @@ public class ChatMessageResponseBody extends JPanel {
public void displaySerpResults(List<YouSerpResult> serpResults) {
var html = getSearchResultsHtml(serpResults);
if (responseReceived) {
add(new ResponseWrapper().add(createTextPane(html, false)));
add(createTextPane(html, false));
} else {
currentlyProcessedTextPane.setText(html);
}
}
private String getSearchResultsHtml(List<YouSerpResult> serpResults) {
var titles = serpResults.stream()
.map(result -> format("<li style=\"margin-bottom: 4px;\"><a href=\"%s\">%s</a></li>",
result.getUrl(), result.getName()))
.collect(Collectors.joining());
return format(
"<html>"
+ "<p><strong>Search results:</strong></p>"
+ "<ol>%s</ol>"
+ "</html>", titles);
}
public void clear() {
removeAll();
@ -190,6 +165,18 @@ public class ChatMessageResponseBody extends JPanel {
revalidate();
}
private String getSearchResultsHtml(List<YouSerpResult> serpResults) {
var titles = serpResults.stream()
.map(result -> format("<li style=\"margin-bottom: 4px;\"><a href=\"%s\">%s</a></li>",
result.getUrl(), result.getName()))
.collect(Collectors.joining());
return format(
"<html>"
+ "<p><strong>Search results:</strong></p>"
+ "<ol>%s</ol>"
+ "</html>", titles);
}
private void processResponse(String markdownInput, boolean codeResponse, boolean caretVisible) {
responseReceived = true;
@ -225,24 +212,17 @@ public class ChatMessageResponseBody extends JPanel {
private void prepareProcessingTextResponse(boolean caretVisible) {
currentlyProcessedEditor = null;
currentlyProcessedTextPane = createTextPane("", caretVisible);
currentlyProcessedElement = new ResponseWrapper();
currentlyProcessedElement.add(currentlyProcessedTextPane);
add(currentlyProcessedElement);
add(currentlyProcessedTextPane);
}
private void prepareProcessingCodeResponse(String code, String markdownLanguage) {
currentlyProcessedTextPane.getCaret().setVisible(false);
if (currentlyProcessedTextPane != null) {
currentlyProcessedTextPane.getCaret().setVisible(false);
}
currentlyProcessedTextPane = null;
currentlyProcessedEditor = new ResponseEditorPanel(
project,
code,
markdownLanguage,
readOnly,
getPanelBackgroundColor(),
parentDisposable);
currentlyProcessedElement = new ResponseWrapper();
currentlyProcessedElement.add(currentlyProcessedEditor);
add(currentlyProcessedElement);
currentlyProcessedEditor =
new ResponseEditorPanel(project, code, markdownLanguage, readOnly, parentDisposable);
add(currentlyProcessedEditor);
}
private void updateEditorDocument(String code) {
@ -250,8 +230,11 @@ public class ChatMessageResponseBody extends JPanel {
var document = editor.getDocument();
var application = ApplicationManager.getApplication();
Runnable updateDocumentRunnable = () -> application.runWriteAction(() ->
WriteCommandAction.runWriteCommandAction(project, () ->
document.replaceString(0, document.getTextLength(), code)));
WriteCommandAction.runWriteCommandAction(project, () -> {
document.replaceString(0, document.getTextLength(), code);
editor.getComponent().repaint();
editor.getComponent().revalidate();
}));
if (application.isUnitTestMode()) {
application.invokeAndWait(updateDocumentRunnable);
@ -261,7 +244,7 @@ public class ChatMessageResponseBody extends JPanel {
}
private JTextPane createTextPane(String text, boolean caretVisible) {
var textPane = UIUtil.createTextPane(text, event -> {
var textPane = UIUtil.createTextPane(text, false, event -> {
if (FileUtil.exists(event.getDescription()) && ACTIVATED.equals(event.getEventType())) {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(event.getDescription());
FileEditorManager.getInstance(project).openFile(Objects.requireNonNull(file), true);
@ -275,7 +258,6 @@ public class ChatMessageResponseBody extends JPanel {
textPane.setCaretPosition(textPane.getDocument().getLength());
}
textPane.setBorder(JBUI.Borders.empty());
textPane.setBackground(getBackground());
return textPane;
}
@ -287,15 +269,4 @@ public class ChatMessageResponseBody extends JPanel {
.build()
.render(document);
}
private static class ResponseWrapper extends JPanel {
ResponseWrapper() {
super(new BorderLayout());
setBorder(JBUI.Borders.empty());
setBackground(getBackground());
UIManager.addPropertyChangeListener(propertyChangeEvent -> setBackground(getBackground()));
}
}
}

View file

@ -1,11 +1,8 @@
package ee.carlrobert.codegpt.toolwindow.chat.components;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBFont;
import com.intellij.util.ui.JBUI;
@ -66,7 +63,6 @@ public class ResponsePanel extends JPanel {
Header() {
super(new BorderLayout());
setBackground(getPanelBackgroundColor());
setBorder(JBUI.Borders.empty(12, 8, 4, 8));
add(getIconLabel(), BorderLayout.LINE_START);
@ -118,7 +114,7 @@ public class ResponsePanel extends JPanel {
private JBLabel getIconLabel() {
return new JBLabel(
CodeGPTBundle.get("project.label"),
Icons.DefaultIcon,
Icons.Default,
SwingConstants.LEADING)
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold());
@ -131,10 +127,7 @@ public class ResponsePanel extends JPanel {
Body() {
super(new BorderLayout());
setBackground(getPanelBackgroundColor());
setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 0, 0, 1, 0),
JBUI.Borders.empty(4, 8, 8, 8)));
setBorder(JBUI.Borders.empty(4, 8, 8, 8));
}
public void addContent(JComponent content) {

View file

@ -10,6 +10,7 @@ import com.intellij.openapi.editor.event.EditorFactoryListener;
import com.intellij.openapi.editor.event.SelectionEvent;
import com.intellij.openapi.editor.event.SelectionListener;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.EncodingManager;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.toolwindow.chat.TokenDetails;
@ -35,6 +36,7 @@ public class TotalTokensPanel extends JPanel {
@Nullable String highlightedText,
Disposable parentDisposable) {
super(new FlowLayout(FlowLayout.LEADING, 0, 0));
setBorder(JBUI.Borders.empty(4));
this.encodingManager = EncodingManager.getInstance();
this.tokenDetails = createTokenDetails(conversation, highlightedText);
this.label = getLabel(tokenDetails);

View file

@ -2,38 +2,45 @@ package ee.carlrobert.codegpt.toolwindow.chat.components;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBFont;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import java.awt.BorderLayout;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
public class UserMessagePanel extends JPanel {
public UserMessagePanel(Project project, Message message, Disposable parentDisposable) {
super(new BorderLayout());
setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 0, 0, 1, 0),
JBUI.Borders.customLine(JBColor.border(), 1, 0, 1, 0),
JBUI.Borders.empty(12, 8, 8, 8)));
add(createDisplayNameWrapper(), BorderLayout.NORTH);
add(new ChatMessageResponseBody(
project,
UIUtil.getPanelBackground(),
false,
true,
parentDisposable).withResponse(message.getPrompt()),
BorderLayout.SOUTH);
setBackground(ColorUtil.brighter(getBackground(), 2));
add(createDisplayNameLabel(), BorderLayout.NORTH);
add(createResponseBody(project, message, parentDisposable), BorderLayout.SOUTH);
}
private JPanel createDisplayNameWrapper() {
return JBUI.Panels.simplePanel()
.withBorder(JBUI.Borders.emptyBottom(6))
.addToLeft(new JBLabel(SettingsState.getInstance().getDisplayName())
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold()));
private ChatMessageResponseBody createResponseBody(
Project project,
Message message,
Disposable parentDisposable) {
return new ChatMessageResponseBody(project, false, true, parentDisposable)
.withResponse(message.getPrompt());
}
private JBLabel createDisplayNameLabel() {
return new JBLabel(
SettingsState.getInstance().getDisplayName(),
Icons.User,
SwingConstants.LEADING)
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold())
.withBorder(JBUI.Borders.emptyBottom(6));
}
}

View file

@ -50,6 +50,7 @@ public class UserPromptTextArea extends JPanel {
private boolean submitEnabled = true;
public UserPromptTextArea(Consumer<String> onSubmit, TotalTokensPanel totalTokensPanel) {
super(new BorderLayout());
this.onSubmit = onSubmit;
textArea = new JBTextArea();
@ -160,7 +161,6 @@ public class UserPromptTextArea extends JPanel {
private void init() {
setOpaque(false);
setLayout(new BorderLayout());
add(textArea, BorderLayout.CENTER);
stopButton = createIconButton(AllIcons.Actions.Suspend, null);
@ -168,9 +168,9 @@ public class UserPromptTextArea extends JPanel {
var flowLayout = new FlowLayout(FlowLayout.RIGHT);
flowLayout.setHgap(8);
iconsPanel = new JPanel(flowLayout);
iconsPanel.add(createIconButton(Icons.SendIcon, this::handleSubmit));
iconsPanel.add(createIconButton(Icons.Send, this::handleSubmit));
iconsPanel.add(stopButton);
add(JBUI.Panels.simplePanel().addToBottom(iconsPanel), BorderLayout.EAST);
add(iconsPanel, BorderLayout.EAST);
}
private void updateFont() {

View file

@ -1,26 +1,27 @@
package ee.carlrobert.codegpt.toolwindow.chat.components;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.Borders;
import ee.carlrobert.codegpt.completions.you.YouSubscriptionNotifier;
import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.toolwindow.ModelIconLabel;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.toolwindow.chat.standard.ModelComboBoxAction;
import java.awt.BorderLayout;
import javax.swing.JPanel;
public class UserPromptTextAreaHeader extends JPanel {
public UserPromptTextAreaHeader(SettingsState settings, TotalTokensPanel totalTokensPanel) {
public UserPromptTextAreaHeader(
ServiceType selectedService,
TotalTokensPanel totalTokensPanel,
Runnable onAddNewTab) {
super(new BorderLayout());
setBackground(getPanelBackgroundColor());
setBorder(JBUI.Borders.emptyBottom(8));
switch (settings.getSelectedService()) {
setOpaque(false);
setBorder(JBUI.Borders.emptyBottom(4));
switch (selectedService) {
case OPENAI:
case AZURE:
add(totalTokensPanel, BorderLayout.LINE_START);
@ -32,12 +33,8 @@ public class UserPromptTextAreaHeader extends JPanel {
break;
default:
}
add(JBUI.Panels
.simplePanel(new ModelIconLabel(
settings.getSelectedService().getCompletionCode(),
settings.getModel()))
.withBorder(Borders.emptyRight(4))
.withBackground(getPanelBackgroundColor()), BorderLayout.LINE_END);
add(new ModelComboBoxAction(onAddNewTab, selectedService)
.createCustomComponent(ActionPlaces.UNKNOWN), BorderLayout.LINE_END);
}
private void subscribeToYouTopics(JBCheckBox gpt4CheckBox) {

View file

@ -1,7 +1,6 @@
package ee.carlrobert.codegpt.toolwindow.chat.contextual;
import static com.intellij.openapi.ui.DialogWrapper.OK_EXIT_CODE;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import com.intellij.openapi.diagnostic.Logger;
@ -37,7 +36,7 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel {
}
private JTextPane createContent() {
var description = createTextPane();
var description = UIUtil.createTextPane("", false, this::handleHyperlinkClicked);
if (VectorStore.getInstance(CodeGPTPlugin.getPluginBasePath()).isIndexExists()) {
description.setText("<html>"
+ "<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
@ -76,12 +75,6 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel {
return description;
}
private JTextPane createTextPane() {
var textPane = UIUtil.createTextPane("", this::handleHyperlinkClicked);
textPane.setBackground(getPanelBackgroundColor());
return textPane;
}
private void handleHyperlinkClicked(HyperlinkEvent event) {
if (ACTIVATED.equals(event.getEventType())) {
if (event.getURL() == null) {

View file

@ -7,18 +7,23 @@ import com.intellij.icons.AllIcons.General;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.DefaultCompactActionGroup;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.ContextMenuPopupHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.JBMenuItem;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.JBColor;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.components.ActionLink;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.toolwindow.ReplaceCodeInMainEditorAction;
@ -30,34 +35,32 @@ import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.NewFileAction;
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.ReplaceSelectionAction;
import ee.carlrobert.codegpt.util.EditorUtil;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import javax.swing.Box;
import javax.swing.JPanel;
import org.jetbrains.annotations.NotNull;
public class ResponseEditorPanel extends JPanel implements Disposable {
private final Editor editor;
private final String language;
private final String extension;
public ResponseEditorPanel(
Project project,
String code,
String markdownLanguage,
boolean readOnly,
Color backgroundColor,
Disposable disposableParent) {
super(new BorderLayout());
setBorder(JBUI.Borders.empty(8, 0));
setOpaque(false);
var extensionMapping = findLanguageExtensionMapping(markdownLanguage);
language = extensionMapping.getKey();
extension = extensionMapping.getValue();
editor = EditorUtil.createEditor(project, extension, code);
editor = EditorUtil.createEditor(
project,
findLanguageExtensionMapping(markdownLanguage).getValue(),
code);
DefaultActionGroup group = new DefaultActionGroup();
var group = new DefaultActionGroup();
group.add(new ReplaceCodeInMainEditorAction());
String originalGroupId = ((EditorEx) editor).getContextMenuGroupId();
if (originalGroupId != null) {
AnAction originalGroup = ActionManager.getInstance().getAction(originalGroupId);
@ -65,12 +68,12 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
group.addAll(((ActionGroup) originalGroup).getChildren(null));
}
}
configureEditor((EditorEx) editor, readOnly, new ContextMenuPopupHandler.Simple(group));
add(createHeaderComponent(readOnly), BorderLayout.NORTH);
configureEditor(
(EditorEx) editor,
readOnly,
new ContextMenuPopupHandler.Simple(group),
findLanguageExtensionMapping(markdownLanguage).getValue());
add(editor.getComponent(), BorderLayout.CENTER);
add(createFooterComponent(readOnly ? getBackground() : backgroundColor), BorderLayout.SOUTH);
Disposer.register(disposableParent, this);
}
@ -87,7 +90,8 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
private void configureEditor(
EditorEx editorEx,
boolean readOnly,
ContextMenuPopupHandler popupHandler) {
ContextMenuPopupHandler popupHandler,
String extension) {
if (readOnly) {
editorEx.setOneLineMode(true);
editorEx.setHorizontalScrollbarVisible(false);
@ -97,23 +101,32 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
var settings = editorEx.getSettings();
settings.setAdditionalColumnsCount(0);
settings.setAdditionalLinesCount(1);
settings.setAdditionalLinesCount(0);
settings.setAdditionalPageAtBottom(false);
settings.setVirtualSpace(false);
settings.setUseSoftWraps(false);
settings.setLineMarkerAreaShown(false);
settings.setGutterIconsShown(false);
settings.setLineNumbersShown(false);
editorEx.getGutterComponentEx().setVisible(true);
editorEx.getGutterComponentEx().getParent().setVisible(false);
editorEx.setVerticalScrollbarVisible(false);
editorEx.getContentComponent().setBorder(JBUI.Borders.emptyLeft(4));
editorEx.setBorder(IdeBorderFactory.createBorder(ColorUtil.fromHex("#48494b")));
editorEx.setPermanentHeaderComponent(createHeaderComponent(editorEx, extension));
editorEx.setHeaderComponent(null);
}
private JPanel createHeaderComponent(boolean readOnly) {
private JPanel createHeaderComponent(EditorEx editorEx, String extension) {
var headerComponent = new JPanel(new BorderLayout());
headerComponent.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBColor.border(), 1, 1, 1, 1),
JBUI.Borders.empty(4, 8)));
headerComponent.add(new JBLabel(language), BorderLayout.LINE_START);
if (!readOnly) {
headerComponent.add(createHeaderActions(), BorderLayout.LINE_END);
}
headerComponent.setBorder(
JBUI.Borders.compound(
JBUI.Borders.customLine(ColorUtil.fromHex("#48494b"), 1, 1, 0, 1),
JBUI.Borders.empty(4)));
headerComponent.add(createExpandLink(editorEx), BorderLayout.LINE_START);
headerComponent.add(createHeaderActions(extension, editorEx).getComponent(),
BorderLayout.LINE_END);
return headerComponent;
}
@ -125,8 +138,7 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
: CodeGPTBundle.get("toolwindow.chat.editor.action.collapse");
}
private JPanel createFooterComponent(Color backgroundColor) {
var editorEx = ((EditorEx) editor);
private ActionLink createExpandLink(EditorEx editorEx) {
var linkText = getLinkText(editorEx.isOneLineMode());
var expandLink = new ActionLink(
linkText,
@ -134,32 +146,44 @@ public class ResponseEditorPanel extends JPanel implements Disposable {
var oneLineMode = editorEx.isOneLineMode();
var source = (ActionLink) event.getSource();
source.setText(getLinkText(!oneLineMode));
source.setIcon(oneLineMode ? General.ArrowUp : General.ArrowDown, true);
source.setIcon(oneLineMode ? General.ArrowDown : General.ArrowRight);
editorEx.setOneLineMode(!oneLineMode);
editorEx.setHorizontalScrollbarVisible(oneLineMode);
editorEx.getContentComponent().revalidate();
editorEx.getContentComponent().repaint();
});
expandLink.setIcon(editorEx.isOneLineMode() ? General.ArrowDown : General.ArrowUp, true);
var panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
panel.setBackground(backgroundColor);
panel.add(expandLink);
return panel;
expandLink.setIcon(editorEx.isOneLineMode() ? General.ArrowRight : General.ArrowDown);
return expandLink;
}
private JPanel createHeaderActions() {
private ActionToolbar createHeaderActions(String extension, EditorEx editorEx) {
var actionGroup = new DefaultCompactActionGroup("EDITOR_TOOLBAR_ACTION_GROUP", false);
actionGroup.add(new CopyAction(editor));
actionGroup.add(new ReplaceSelectionAction(editor));
actionGroup.addSeparator();
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
wrapper.add(new IconActionButton(new DiffAction(editor)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new EditAction(editor)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new NewFileAction(editor, extension)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(new IconActionButton(new CopyAction(editor)));
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(Box.createHorizontalStrut(4));
wrapper.add(new IconActionButton(new ReplaceSelectionAction(editor)));
return wrapper;
var menu = new JBPopupMenu();
menu.add(new JBMenuItem(new DiffAction(editorEx, menu.getLocation())));
menu.add(new JBMenuItem(new EditAction(editorEx)));
menu.add(new JBMenuItem(new NewFileAction(editorEx, extension)));
var toolbar = ActionManager.getInstance()
.createActionToolbar("NAVIGATION_BAR_TOOLBAR", actionGroup, true);
actionGroup.add(new AnAction("Editor Actions", "Editor Actions", General.GearPlain) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
menu.show(e.getInputEvent().getComponent(), 0, 0);
}
});
toolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY);
toolbar.setTargetComponent(editorEx.getComponent());
toolbar.getComponent().setBorder(JBUI.Borders.empty());
return toolbar;
}
}

View file

@ -10,35 +10,35 @@ import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.Side;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.util.Pair;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.actions.TrackableAction;
import ee.carlrobert.codegpt.util.EditorUtil;
import ee.carlrobert.codegpt.util.OverlayUtil;
import ee.carlrobert.codegpt.util.file.FileUtil;
import java.awt.Point;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.jetbrains.annotations.NotNull;
public class DiffAction extends TrackableAction {
public class DiffAction extends AbstractAction {
public DiffAction(@NotNull Editor editor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.diff.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.diff.description"),
Actions.DiffWithClipboard,
ActionType.DIFF_CODE);
private final EditorEx editor;
private final Point locationOnScreen;
public DiffAction(@NotNull EditorEx editor, @NotNull Point locationOnScreen) {
super("Diff", Actions.DiffWithClipboard);
this.editor = editor;
this.locationOnScreen = locationOnScreen;
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
var project = requireNonNull(event.getProject());
public void actionPerformed(ActionEvent event) {
var project = requireNonNull(editor.getProject());
var selectedTextEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (!EditorUtil.hasSelection(selectedTextEditor)) {
OverlayUtil.showSelectedEditorSelectionWarning(event);
OverlayUtil.showSelectedEditorSelectionWarning(project, locationOnScreen);
return;
}

View file

@ -1,44 +1,38 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.icons.AllIcons.Diff;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.ui.JBMenuItem;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.actions.TrackableAction;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.jetbrains.annotations.NotNull;
public class EditAction extends TrackableAction {
public class EditAction extends AbstractAction {
public EditAction(@NotNull Editor editor) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.edit.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.edit.description"),
Actions.EditSource,
ActionType.EDIT_CODE);
private final EditorEx editor;
public EditAction(@NotNull EditorEx editor) {
super("Edit Source", Actions.EditSource);
this.editor = editor;
}
@Override
public void handleAction(@NotNull AnActionEvent event) {
var editorEx = ((EditorEx) editor);
editorEx.setViewer(!editorEx.isViewer());
public void actionPerformed(@NotNull ActionEvent event) {
editor.setViewer(!editor.isViewer());
var viewer = editorEx.isViewer();
editorEx.setCaretVisible(!viewer);
editorEx.setCaretEnabled(!viewer);
var viewer = editor.isViewer();
editor.setCaretVisible(!viewer);
editor.setCaretEnabled(!viewer);
var settings = editorEx.getSettings();
var settings = editor.getSettings();
settings.setCaretRowShown(!viewer);
event.getPresentation().setIcon(viewer ? Actions.EditSource : Actions.Show);
event.getPresentation().setText(viewer
var menuItem = (JBMenuItem) event.getSource();
menuItem.setText(viewer
? CodeGPTBundle.get("toolwindow.chat.editor.action.edit.title")
: CodeGPTBundle.get("toolwindow.chat.editor.action.disableEditing.title"));
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
menuItem.setIcon(viewer ? Actions.EditSource : Diff.Lock);
}
}

View file

@ -1,11 +1,11 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.actions;
import static com.intellij.openapi.ui.DialogWrapper.OK_EXIT_CODE;
import static java.util.Objects.requireNonNull;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.ide.util.EditorHelper;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
@ -15,30 +15,25 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.psi.PsiManager;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.actions.TrackableAction;
import ee.carlrobert.codegpt.util.file.FileUtil;
import java.util.Objects;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.jetbrains.annotations.NotNull;
public class NewFileAction extends TrackableAction {
public class NewFileAction extends AbstractAction {
private final String fileExtension;
private final EditorEx editor;
public NewFileAction(@NotNull Editor editor, String fileExtension) {
super(
editor,
CodeGPTBundle.get("toolwindow.chat.editor.action.newFile.title"),
CodeGPTBundle.get("toolwindow.chat.editor.action.newFile.description"),
Actions.AddFile,
ActionType.CREATE_NEW_FILE);
public NewFileAction(@NotNull EditorEx editor, String fileExtension) {
super("Create New File", Actions.AddFile);
this.fileExtension = fileExtension;
this.editor = editor;
}
@Override
public void handleAction(@NotNull AnActionEvent e) {
var project = Objects.requireNonNull(e.getProject());
public void actionPerformed(@NotNull ActionEvent event) {
var project = requireNonNull(editor.getProject());
var fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
fileChooserDescriptor.setForcedToUseIdeaFileChooser(true);
var textFieldWithBrowseButton = new TextFieldWithBrowseButton();

View file

@ -3,24 +3,24 @@ package ee.carlrobert.codegpt.toolwindow.chat.standard;
enum EditorAction {
FIND_BUGS(
"Find Bugs",
"Find bugs in the following code",
"Find bugs and output code with bugs fixed in the following code: {{selectedCode}}"),
"Find bugs in the selected code",
"Find bugs and output code with bugs fixed in the selected code: {{selectedCode}}"),
WRITE_TESTS(
"Write Tests",
"Write Tests for the following code",
"Write Tests for the following code: {{selectedCode}}"),
"Write unit tests for the selected code",
"Write unit tests for the selected code: {{selectedCode}}"),
EXPLAIN(
"Explain",
"Explain the following code",
"Explain the following code: {{selectedCode}}"),
"Explain the selected code",
"Explain the selected code: {{selectedCode}}"),
REFACTOR(
"Refactor",
"Refactor the following code",
"Refactor the following code: {{selectedCode}}"),
"Refactor the selected code",
"Refactor the selected code: {{selectedCode}}"),
OPTIMIZE(
"Optimize",
"Optimize the following code",
"Optimize the following code: {{selectedCode}}");
"Optimize the selected code",
"Optimize the selected code: {{selectedCode}}");
private final String label;
private final String userMessage;

View file

@ -0,0 +1,158 @@
package ee.carlrobert.codegpt.toolwindow.chat.standard;
import static java.lang.String.format;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import org.jetbrains.annotations.NotNull;
public class ModelComboBoxAction extends ComboBoxAction {
private final Runnable onAddNewTab;
private final SettingsState settings;
private final OpenAISettingsState openAISettings;
public ModelComboBoxAction(Runnable onAddNewTab, ServiceType selectedService) {
this.onAddNewTab = onAddNewTab;
settings = SettingsState.getInstance();
openAISettings = OpenAISettingsState.getInstance();
updateTemplatePresentation(selectedService);
}
public JComponent createCustomComponent(@NotNull String place) {
return createCustomComponent(getTemplatePresentation(), place);
}
@NotNull
@Override
public JComponent createCustomComponent(
@NotNull Presentation presentation,
@NotNull String place) {
ComboBoxButton button = createComboBoxButton(presentation);
button.setBorder(null);
return button;
}
@Override
protected @NotNull DefaultActionGroup createPopupActionGroup(JComponent button) {
var presentation = ((ComboBoxButton) button).getPresentation();
var actionGroup = new DefaultActionGroup();
actionGroup.addSeparator("OpenAI");
List.of(
OpenAIChatCompletionModel.GPT_4_1106_128k,
OpenAIChatCompletionModel.GPT_3_5_1106_16k,
OpenAIChatCompletionModel.GPT_4_32k,
OpenAIChatCompletionModel.GPT_4,
OpenAIChatCompletionModel.GPT_3_5)
.forEach(
model -> actionGroup.add(createOpenAIModelAction(model, presentation)));
actionGroup.addSeparator();
actionGroup.add(
createModelAction(ServiceType.AZURE, "Azure OpenAI", Icons.Azure, presentation));
actionGroup.addSeparator();
actionGroup.add(createModelAction(
ServiceType.LLAMA_CPP,
getSelectedHuggingFace(),
Icons.Llama,
presentation));
actionGroup.addSeparator();
actionGroup.add(createModelAction(ServiceType.YOU, "You.com", Icons.YouSmall, presentation));
return actionGroup;
}
@Override
protected boolean shouldShowDisabledActions() {
return true;
}
private void updateTemplatePresentation(ServiceType selectedService) {
var templatePresentation = getTemplatePresentation();
switch (selectedService) {
case OPENAI:
templatePresentation.setIcon(Icons.OpenAI);
templatePresentation.setText(
OpenAIChatCompletionModel.findByCode(openAISettings.getModel()).getDescription());
break;
case AZURE:
templatePresentation.setIcon(Icons.Azure);
templatePresentation.setText("Azure OpenAI");
break;
case YOU:
templatePresentation.setIcon(Icons.YouSmall);
templatePresentation.setText("You.com");
break;
case LLAMA_CPP:
templatePresentation.setText(getSelectedHuggingFace());
templatePresentation.setIcon(Icons.Llama);
break;
default:
}
}
private String getSelectedHuggingFace() {
var huggingFaceModel = LlamaSettingsState.getInstance().getHuggingFaceModel();
return format(
"%s %dB (Q%d)",
LlamaModel.findByHuggingFaceModel(huggingFaceModel).getLabel(),
huggingFaceModel.getParameterSize(),
huggingFaceModel.getQuantization());
}
private AnAction createModelAction(
ServiceType serviceType,
String label,
Icon icon,
Presentation comboBoxPresentation) {
return new AnAction(label, "", icon) {
@Override
public void update(@NotNull AnActionEvent event) {
var presentation = event.getPresentation();
presentation.setEnabled(!presentation.getText().equals(comboBoxPresentation.getText()));
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
settings.setSelectedService(serviceType);
comboBoxPresentation.setIcon(icon);
comboBoxPresentation.setText(label);
onAddNewTab.run();
}
};
}
private AnAction createOpenAIModelAction(
OpenAIChatCompletionModel model,
Presentation comboBoxPresentation) {
createModelAction(ServiceType.OPENAI, model.getDescription(), Icons.OpenAI,
comboBoxPresentation);
return new AnAction(model.getDescription(), "", Icons.OpenAI) {
@Override
public void update(@NotNull AnActionEvent event) {
var presentation = event.getPresentation();
presentation.setEnabled(!presentation.getText().equals(comboBoxPresentation.getText()));
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
settings.setSelectedService(ServiceType.OPENAI);
openAISettings.setModel(model.getCode());
comboBoxPresentation.setIcon(Icons.OpenAI);
comboBoxPresentation.setText(model.getDescription());
onAddNewTab.run();
}
};
}
}

View file

@ -1,86 +1,70 @@
package ee.carlrobert.codegpt.toolwindow.chat.standard;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import static java.lang.String.format;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.components.ActionLink;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel;
import ee.carlrobert.codegpt.util.UIUtil;
import java.awt.event.MouseEvent;
import javax.swing.JTextPane;
import javax.swing.event.HyperlinkEvent;
import java.awt.BorderLayout;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
class StandardChatToolWindowLandingPanel extends ResponsePanel {
private static final Logger LOG = Logger.getInstance(StandardChatToolWindowLandingPanel.class);
private final EditorActionEvent onAction;
StandardChatToolWindowLandingPanel(EditorActionEvent onAction) {
this.onAction = onAction;
addContent(createContent());
addContent(createContent(onAction));
}
private JTextPane createContent() {
var textPane = UIUtil.createTextPane(
private ActionLink createEditorActionLink(EditorAction action, EditorActionEvent onAction) {
var link = new ActionLink(action.getUserMessage(), event -> {
onAction.handleAction(action, ((ActionLink) event.getSource()).getLocationOnScreen());
});
link.setIcon(Icons.Sparkle);
return link;
}
private JPanel createContent(EditorActionEvent onAction) {
var panel = new JPanel(new BorderLayout());
panel.add(UIUtil.createTextPane(
"<html>"
+ format(
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
+ "Welcome <strong>%s</strong>, I'm your intelligent code companion, here to be"
+ " your partner-in-crime for getting things done in a flash. Together, we'll "
+ "tackle tasks swiftly and efficiently, making your coding experience a joy."
+ "</p>",
SettingsState.getInstance().getDisplayName())
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
+ "Welcome <strong>%s</strong>, I'm your intelligent code companion, here to be"
+ " your partner-in-crime for getting things done in a flash."
+ "</p>", SettingsState.getInstance().getDisplayName())
+ "<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
+ "Feel free to ask me anything you'd like, but my true superpower lies in assisting "
+ "you with your code! Here are a few examples of how I can assist you:"
+ "</p>"
+ "<ul style=\"margin-top: 4px; margin-bottom: 4px;\">"
+ "<li>"
+ "<a href=\"GENERATE_UNIT_TESTS\">Generate unit tests for the selected code</a>"
+ "</li>"
+ "<li>"
+ "<a href=\"EXPLAIN_CODE\">Explain the selected code</a>"
+ "</li>"
+ "<li>"
+ "<a href=\"FIND_BUGS\">Find bugs in the selected code</a>"
+ "</li>"
+ "</ul"
+ "</html>",
false), BorderLayout.NORTH);
panel.add(createEditorActionsListPanel(onAction), BorderLayout.CENTER);
panel.add(UIUtil.createTextPane(
"<html>"
+ "<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
+ "Being an AI-powered assistant, I may occasionally have surprises or make mistakes. "
+ "Therefore, it's wise to double-check any code or suggestions I provide."
+ "</p>"
+ "</html>",
this::handleHyperlinkClicked);
textPane.setBackground(getPanelBackgroundColor());
return textPane;
false), BorderLayout.SOUTH);
return panel;
}
private void handleHyperlinkClicked(HyperlinkEvent event) {
if (ACTIVATED.equals(event.getEventType())) {
if (event.getURL() == null) {
var mouseLocation = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
mouseLocation.y = mouseLocation.y - 10;
switch (event.getDescription()) {
case "GENERATE_UNIT_TESTS":
onAction.handleAction(EditorAction.WRITE_TESTS, mouseLocation);
break;
case "EXPLAIN_CODE":
onAction.handleAction(EditorAction.EXPLAIN, mouseLocation);
break;
case "FIND_BUGS":
onAction.handleAction(EditorAction.FIND_BUGS, mouseLocation);
break;
default:
LOG.error("Could not trigger action {}", event.getDescription());
}
} else {
UIUtil.handleHyperlinkClicked(event);
}
}
private JPanel createEditorActionsListPanel(EditorActionEvent onAction) {
var listPanel = new JPanel();
listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.PAGE_AXIS));
listPanel.setBorder(JBUI.Borders.emptyLeft(4));
listPanel.add(Box.createVerticalStrut(4));
listPanel.add(createEditorActionLink(EditorAction.WRITE_TESTS, onAction));
listPanel.add(Box.createVerticalStrut(4));
listPanel.add(createEditorActionLink(EditorAction.EXPLAIN, onAction));
listPanel.add(Box.createVerticalStrut(4));
listPanel.add(createEditorActionLink(EditorAction.FIND_BUGS, onAction));
listPanel.add(Box.createVerticalStrut(4));
return listPanel;
}
}

View file

@ -12,7 +12,8 @@ import ee.carlrobert.codegpt.actions.toolwindow.CreateNewConversationAction;
import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import javax.swing.JPanel;
import org.jetbrains.annotations.NotNull;
public class StandardChatToolWindowPanel extends SimpleToolWindowPanel {
@ -29,11 +30,22 @@ public class StandardChatToolWindowPanel extends SimpleToolWindowPanel {
if (conversation == null) {
conversation = ConversationService.getInstance().startConversation();
}
var tabPanel = new StandardChatToolWindowTabPanel(project, conversation);
var tabbedPane = createTabbedPane(tabPanel, parentDisposable);
var toolbarComponent = createActionToolbar(project, tabbedPane).getComponent();
toolbarComponent.setLayout(new FlowLayout());
setToolbar(toolbarComponent);
Runnable onAddNewTab = () -> {
tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(
project,
ConversationService.getInstance().startConversation()));
repaint();
revalidate();
};
var actionToolbarPanel = new JPanel(new BorderLayout());
actionToolbarPanel.add(
createActionToolbar(project, tabbedPane, onAddNewTab).getComponent(),
BorderLayout.LINE_START);
setToolbar(actionToolbarPanel);
setContent(tabbedPane);
Disposer.register(parentDisposable, tabPanel);
@ -41,15 +53,10 @@ public class StandardChatToolWindowPanel extends SimpleToolWindowPanel {
private ActionToolbar createActionToolbar(
Project project,
StandardChatToolWindowTabbedPane tabbedPane) {
StandardChatToolWindowTabbedPane tabbedPane,
Runnable onAddNewTab) {
var actionGroup = new DefaultCompactActionGroup("TOOLBAR_ACTION_GROUP", false);
actionGroup.add(new CreateNewConversationAction(() -> {
tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(
project,
ConversationService.getInstance().startConversation()));
repaint();
revalidate();
}));
actionGroup.add(new CreateNewConversationAction(onAddNewTab));
actionGroup.add(
new ClearChatWindowAction(() -> tabbedPane.resetCurrentlyActiveTabPanel(project)));
actionGroup.addSeparator();
@ -61,7 +68,8 @@ public class StandardChatToolWindowPanel extends SimpleToolWindowPanel {
return toolbar;
}
private StandardChatToolWindowTabbedPane createTabbedPane(StandardChatToolWindowTabPanel tabPanel,
private StandardChatToolWindowTabbedPane createTabbedPane(
StandardChatToolWindowTabPanel tabPanel,
Disposable parentDisposable) {
var tabbedPane = new StandardChatToolWindowTabbedPane(parentDisposable);
tabbedPane.addNewTab(tabPanel);

View file

@ -56,8 +56,8 @@ public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel {
private void displayConversation(@NotNull Conversation conversation) {
clearWindow();
conversation.getMessages().forEach(message -> {
var messageResponseBody = new ChatMessageResponseBody(project, this)
.withResponse(message.getResponse());
var messageResponseBody =
new ChatMessageResponseBody(project, this).withResponse(message.getResponse());
var serpResults = message.getSerpResults();
if (YouSettingsState.getInstance().isDisplayWebSearchResults()

View file

@ -134,11 +134,10 @@ public class StandardChatToolWindowTabbedPane extends JBTabbedPane {
button.setToolTipText("Close Chat");
button.setRolloverIcon(AllIcons.Actions.CloseHovered);
var panel = JBUI.Panels.simplePanel(4, 0)
return JBUI.Panels.simplePanel(4, 0)
.addToLeft(new JBLabel(title))
.addToRight(button);
panel.setOpaque(false);
return panel;
.addToRight(button)
.andTransparent();
}
class CloseActionListener implements ActionListener {

View file

@ -1,7 +1,5 @@
package ee.carlrobert.codegpt.toolwindow.conversations;
import static ee.carlrobert.codegpt.util.UIUtil.getPanelBackgroundColor;
import com.intellij.openapi.project.Project;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
@ -62,7 +60,6 @@ class ConversationPanel extends JPanel {
var border = isSelected
? JBUI.Borders.customLine(JBUI.CurrentTheme.ActionButton.focusedBorder(), 2, 2, 2, 2)
: JBUI.Borders.customLine(JBColor.border(), 1, 0, 1, 0);
setBackground(getPanelBackgroundColor());
setBorder(JBUI.Borders.compound(border, JBUI.Borders.empty(8)));
setLayout(new GridBagLayout());
setCursor(new Cursor(Cursor.HAND_CURSOR));
@ -85,7 +82,6 @@ class ConversationPanel extends JPanel {
gbc.weightx = 1.0;
gbc.gridx = 0;
headerPanel.setBackground(getPanelBackgroundColor());
headerPanel.add(new JBLabel(getFirstPrompt(conversation))
.withFont(JBFont.label().asBold()), gbc);
@ -94,7 +90,6 @@ class ConversationPanel extends JPanel {
headerPanel.add(new IconActionButton(new DeleteConversationAction(onDelete)), gbc);
var bottomPanel = new JPanel(new BorderLayout());
bottomPanel.setBackground(getPanelBackgroundColor());
bottomPanel.add(new JLabel(conversation.getUpdatedOn()
.format(DateTimeFormatter.ofPattern("M/d/yyyy, h:mm:ss a"))), BorderLayout.WEST);
if (conversation.getModel() != null) {
@ -104,7 +99,6 @@ class ConversationPanel extends JPanel {
}
var textPanel = new JPanel(new BorderLayout());
textPanel.setBackground(getPanelBackgroundColor());
textPanel.add(headerPanel, BorderLayout.NORTH);
textPanel.add(bottomPanel, BorderLayout.SOUTH);
return textPanel;

View file

@ -2,7 +2,7 @@ package ee.carlrobert.codegpt.util;
import static com.intellij.openapi.ui.Messages.CANCEL;
import static com.intellij.openapi.ui.Messages.OK;
import static ee.carlrobert.codegpt.Icons.DefaultIcon;
import static ee.carlrobert.codegpt.Icons.Default;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
@ -68,7 +68,7 @@ public class OverlayUtil {
return Messages.showYesNoDialog(
CodeGPTBundle.get("dialog.deleteConversation.description"),
CodeGPTBundle.get("dialog.deleteConversation.title"),
DefaultIcon);
Default);
}
public static int showTokenLimitExceededDialog() {
@ -77,7 +77,7 @@ public class OverlayUtil {
CodeGPTBundle.get("dialog.tokenLimitExceeded.description"))
.yesText(CodeGPTBundle.get("dialog.continue"))
.noText(CodeGPTBundle.get("dialog.cancel"))
.icon(DefaultIcon)
.icon(Default)
.doNotAsk(new DoNotAskOption.Adapter() {
@Override
public void rememberChoice(boolean isSelected, int exitCode) {
@ -106,7 +106,7 @@ public class OverlayUtil {
format(CodeGPTBundle.get("dialog.tokenSoftLimitExceeded.description"), tokenCount))
.yesText(CodeGPTBundle.get("dialog.continue"))
.noText(CodeGPTBundle.get("dialog.cancel"))
.icon(DefaultIcon)
.icon(Default)
.doNotAsk(new DoNotAskOption.Adapter() {
@Override
public void rememberChoice(boolean isSelected, int exitCode) {
@ -132,9 +132,14 @@ public class OverlayUtil {
public static void showSelectedEditorSelectionWarning(AnActionEvent event) {
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
showSelectedEditorSelectionWarning(requireNonNull(event.getProject()), locationOnScreen);
}
public static void showSelectedEditorSelectionWarning(
@NotNull Project project,
Point locationOnScreen) {
showWarningBalloon(
EditorUtil.getSelectedEditor(requireNonNull(event.getProject())) == null
EditorUtil.getSelectedEditor(project) == null
? "Unable to locate a selected editor"
: "Please select a target code before proceeding",
locationOnScreen);

View file

@ -31,16 +31,21 @@ import javax.swing.event.HyperlinkListener;
public class UIUtil {
public static JTextPane createTextPane(String text) {
return createTextPane(text, UIUtil::handleHyperlinkClicked);
return createTextPane(text, true);
}
public static JTextPane createTextPane(String text, HyperlinkListener listener) {
public static JTextPane createTextPane(String text, boolean opaque) {
return createTextPane(text, opaque, UIUtil::handleHyperlinkClicked);
}
public static JTextPane createTextPane(String text, boolean opaque, HyperlinkListener listener) {
var textPane = new JTextPane();
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
textPane.addHyperlinkListener(listener);
textPane.setContentType("text/html");
textPane.setEditable(false);
textPane.setText(text);
textPane.setOpaque(opaque);
return textPane;
}