Logic for closing active tabs

This commit is contained in:
Carl-Robert Linnupuu 2023-03-24 11:41:48 +00:00
parent 5a9897dbfa
commit fbabf96463
9 changed files with 127 additions and 30 deletions

View file

@ -1,5 +1,6 @@
package ee.carlrobert.codegpt.client;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.openai.client.completion.CompletionEventListener;
@ -7,12 +8,14 @@ import java.util.function.Consumer;
public class EventListener implements CompletionEventListener {
private final Conversation conversation;
private final Message message;
private final Consumer<String> onAppend;
private final Runnable onStopGenerating;
private final boolean isRetry;
public EventListener(Message message, Consumer<String> onAppend, Runnable onStopGenerating, boolean isRetry) {
public EventListener(Conversation conversation, Message message, Consumer<String> onAppend, Runnable onStopGenerating, boolean isRetry) {
this.conversation = conversation;
this.onStopGenerating = onStopGenerating;
this.onAppend = onAppend;
this.message = message;
@ -34,10 +37,9 @@ public class EventListener implements CompletionEventListener {
private void saveConversation(String response) {
var conversationsState = ConversationsState.getInstance();
var conversation = conversationsState.getOrStartNew();
var conversationMessages = conversation.getMessages();
if (isRetry) {
if (isRetry && !conversationMessages.isEmpty()) {
conversationMessages.remove(conversationMessages.size() - 1);
}

View file

@ -170,7 +170,7 @@ public class ConversationsState implements PersistentStateComponent<Conversation
.values()
.stream()
.flatMap(Collection::stream)
.filter(it -> conversationId.equals(it.getId()))
.filter(item -> item.getId().equals(conversationId))
.findFirst();
}
}

View file

@ -1,15 +1,14 @@
package ee.carlrobert.codegpt.toolwindow;
import com.intellij.openapi.project.Project;
import ee.carlrobert.codegpt.client.ClientProvider;
import ee.carlrobert.codegpt.client.CompletionRequestProvider;
import ee.carlrobert.codegpt.client.EventListener;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.SettingsState;
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowTabPanel;
import ee.carlrobert.codegpt.toolwindow.components.SyntaxTextArea;
import java.util.List;
import java.util.function.Consumer;
import javax.swing.SwingWorker;
import okhttp3.sse.EventSource;
@ -18,18 +17,20 @@ public class ToolWindowService {
public void startRequest(
String prompt,
SyntaxTextArea textArea,
Project project,
boolean isRetry,
ChatToolWindowTabPanel toolWindow,
Conversation conversation) {
Conversation conversation,
Runnable onStop,
Consumer<EventSource> onStart,
Runnable onScrollToBottom) {
var conversationMessage = new Message(prompt);
new SwingWorker<Void, String>() {
protected Void doInBackground() {
var eventListener = new EventListener(
conversation,
conversationMessage,
textArea::append,
() -> toolWindow.stopGenerating(prompt, textArea, project),
onStop,
isRetry) {
public void onMessage(String message) {
publish(message);
@ -46,7 +47,7 @@ public class ToolWindowService {
call = ClientProvider.getTextCompletionClient().stream(
requestProvider.buildTextCompletionRequest(settings.textCompletionBaseModel), eventListener);
}
toolWindow.displayGenerateButton(call::cancel);
onStart.accept(call);
return null;
}
@ -55,7 +56,7 @@ public class ToolWindowService {
try {
textArea.append(text);
conversationMessage.setResponse(textArea.getText());
toolWindow.scrollToBottom();
onScrollToBottom.run();
} catch (Exception e) {
textArea.append("Something went wrong. Please try again later.");
throw new RuntimeException(e);

View file

@ -44,7 +44,7 @@ public class ChatContentManagerService {
public void resetTabbedPane(@NotNull Project project) {
tryFindChatTabbedPane(project).ifPresent(tabbedPane -> {
tabbedPane.removeAll();
tabbedPane.clearAll();
var tabPanel = new ChatToolWindowTabPanel(project);
tabPanel.displayLandingView();
tabbedPane.addNewTab(tabPanel);

View file

@ -3,10 +3,13 @@ package ee.carlrobert.codegpt.toolwindow.chat;
import com.intellij.ui.components.JBTabbedPane;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.swing.JPanel;
public class ChatTabbedPane extends JBTabbedPane {
@ -19,10 +22,24 @@ public class ChatTabbedPane extends JBTabbedPane {
}
public void addNewTab(ChatToolWindowTabPanel toolWindowPanel) {
super.addTab("Chat " + (getTabCount() + 1), toolWindowPanel.getContent());
var tabIndex = getTabCount() - 1;
super.setSelectedIndex(tabIndex);
activeTabMapping.put(tabIndex, toolWindowPanel);
var tabIndices = activeTabMapping.keySet().toArray(new Integer[0]);
var nextIndex = 0;
for (Integer val : tabIndices) {
if (val == nextIndex) {
nextIndex++;
} else {
break;
}
}
var title = "Chat " + (nextIndex + 1);
super.insertTab(title, null, toolWindowPanel.getContent(), null, nextIndex);
activeTabMapping.put(nextIndex, toolWindowPanel);
super.setSelectedIndex(nextIndex);
if (nextIndex > 0) {
setTabComponentAt(nextIndex, createCloseableTabButtonPanel(title, nextIndex));
}
}
public Optional<Integer> tryFindActiveConversationIndex(UUID conversationId) {
@ -39,4 +56,34 @@ public class ChatTabbedPane extends JBTabbedPane {
}
return ConversationsState.getInstance().getConversation(toolWindowPanel.getConversationId());
}
private JPanel createCloseableTabButtonPanel(String title, int tabIndex) {
var button = new CloseableTabButton(title);
button.addActionListener(new CloseActionListener(title, tabIndex));
return button.getComponent();
}
class CloseActionListener implements ActionListener {
private final String title;
private final int tabMappingIndex;
public CloseActionListener(String title, int tabMappingIndex) {
this.title = title;
this.tabMappingIndex = tabMappingIndex;
}
public void actionPerformed(ActionEvent evt) {
var tabIndex = indexOfTab(title);
if (tabIndex >= 0) {
removeTabAt(tabIndex);
activeTabMapping.remove(tabMappingIndex);
}
}
}
public void clearAll() {
removeAll();
activeTabMapping.clear();
}
}

View file

@ -25,14 +25,11 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
setToolbar(createActionToolbar(project, tabbedPane).getComponent());
setContent(tabbedPane);
var contentManagerService = project.getService(ChatContentManagerService.class);
if (contentManagerService.isChatTabSelected(toolWindow.getContentManager())) {
var conversation = ConversationsState.getCurrentConversation();
if (conversation == null) {
tabPanel.displayLandingView();
} else {
tabPanel.displayConversation(conversation);
}
var conversation = ConversationsState.getCurrentConversation();
if (conversation == null) {
tabPanel.displayLandingView();
} else {
tabPanel.displayConversation(conversation);
}
}

View file

@ -29,6 +29,7 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.swing.BorderFactory;
import javax.swing.Box;
@ -45,7 +46,7 @@ import org.jetbrains.annotations.NotNull;
public class ChatToolWindowTabPanel {
private static final List<SyntaxTextArea> textAreas = new ArrayList<>();
private final List<SyntaxTextArea> textAreas = new ArrayList<>();
private final Project project;
private JPanel chatGptToolWindowContent;
private ScrollPane scrollPane;
@ -130,10 +131,15 @@ public class ChatToolWindowTabPanel {
addTextArea(textArea);
}
var conversation = ConversationsState.getInstance().getOrStartNew();
setConversationId(conversation.getId());
var conversation = ConversationsState.getInstance().getConversation(conversationId);
if (conversation.isEmpty()) {
conversation = Optional.of(ConversationsState.getInstance().startConversation());
}
project.getService(ToolWindowService.class)
.startRequest(prompt, textArea, project, isRetry, this, conversation);
.startRequest(prompt, textArea, isRetry, conversation.get(),
() -> stopGenerating(prompt, textArea, project),
(eventSource) -> displayGenerateButton(eventSource::cancel),
this::scrollToBottom);
}
}

View file

@ -0,0 +1,45 @@
package ee.carlrobert.codegpt.toolwindow.chat;
import com.intellij.icons.AllIcons;
import com.intellij.util.ui.JBUI;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class CloseableTabButton extends JButton {
private static final Icon closeIcon = AllIcons.Actions.Close;
private final String title;
public CloseableTabButton(String title) {
super(closeIcon);
this.title = title;
setBorder(BorderFactory.createEmptyBorder());
setContentAreaFilled(false);
setPreferredSize(new Dimension(closeIcon.getIconWidth(), closeIcon.getIconHeight()));
setToolTipText("Close");
setRolloverIcon(AllIcons.Actions.CloseHovered);
}
public JPanel getComponent() {
var panel = new JPanel(new GridBagLayout());
panel.setOpaque(false);
var constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.weightx = 1;
panel.add(new JLabel(title), constraints);
constraints.gridx++;
constraints.weightx = 0;
constraints.insets = JBUI.insetsLeft(8);
panel.add(this, constraints);
return panel;
}
}

View file

@ -74,7 +74,6 @@ public class ConversationsToolWindow {
private void addContent(Conversation conversation) {
var mainPanel = new RootConversationPanel(() -> {
ConversationsState.getInstance().setCurrentConversation(conversation);
changeSettings(conversation);
var contentManagerService = project.getService(ChatContentManagerService.class);