mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-17 03:57:27 +00:00
fix: defer toolwindow tab creation and allow closing first/last tab (relates #865)
This commit is contained in:
parent
00775d38da
commit
0fffa2eac2
15 changed files with 426 additions and 184 deletions
|
|
@ -9,7 +9,7 @@ public class RenameSessionAction {
|
|||
private static final int MAX_NAME_LENGTH = 50;
|
||||
|
||||
public static void renameSession(ChatToolWindowTabbedPane tabbedPane, int tabIndex) {
|
||||
if (tabIndex <= 0) {
|
||||
if (tabIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -44,4 +44,4 @@ public class RenameSessionAction {
|
|||
return checkInput(inputString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import ee.carlrobert.codegpt.CodeGPTBundle;
|
|||
import ee.carlrobert.codegpt.Icons;
|
||||
import ee.carlrobert.codegpt.completions.ConversationType;
|
||||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -48,13 +46,12 @@ public final class ChatToolWindowContentManager {
|
|||
.getState()
|
||||
.getChatActions()
|
||||
.getStartInNewWindow();
|
||||
if (startInNewWindow || ConversationsState.getCurrentConversation() == null) {
|
||||
if (startInNewWindow) {
|
||||
createNewTabPanel().sendMessage(message, conversationType);
|
||||
return;
|
||||
}
|
||||
|
||||
tryFindChatTabbedPane()
|
||||
.map(tabbedPane -> tabbedPane.tryFindActiveTabPanel().orElseGet(this::createNewTabPanel))
|
||||
tryFindActiveChatTabPanel()
|
||||
.orElseGet(this::createNewTabPanel)
|
||||
.sendMessage(message, conversationType);
|
||||
}
|
||||
|
|
@ -65,23 +62,18 @@ public final class ChatToolWindowContentManager {
|
|||
|
||||
public void displayConversation(@NotNull Conversation conversation) {
|
||||
displayChatTab();
|
||||
tryFindChatTabbedPane()
|
||||
.ifPresent(tabbedPane -> tabbedPane.tryFindTabTitle(conversation.getId())
|
||||
.ifPresentOrElse(
|
||||
title -> tabbedPane.setSelectedIndex(tabbedPane.indexOfTab(title)),
|
||||
() -> tabbedPane.addNewTab(new ChatToolWindowTabPanel(project, conversation))));
|
||||
tryFindChatToolWindowPanel().ifPresent(chatPanel -> chatPanel.getChatTabbedPane()
|
||||
.tryFindTabTitle(conversation.getId())
|
||||
.ifPresentOrElse(
|
||||
title -> chatPanel.getChatTabbedPane()
|
||||
.setSelectedIndex(chatPanel.getChatTabbedPane().indexOfTab(title)),
|
||||
() -> chatPanel.createAndSelectConversationTab(conversation)));
|
||||
}
|
||||
|
||||
public ChatToolWindowTabPanel createNewTabPanel() {
|
||||
displayChatTab();
|
||||
return tryFindChatTabbedPane()
|
||||
.map(item -> {
|
||||
var panel = new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation(project));
|
||||
item.addNewTab(panel);
|
||||
return panel;
|
||||
})
|
||||
return tryFindChatToolWindowPanel()
|
||||
.map(ChatToolWindowPanel::createAndSelectNewTabPanel)
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
|
|
@ -113,12 +105,7 @@ public final class ChatToolWindowContentManager {
|
|||
}
|
||||
|
||||
public void resetAll() {
|
||||
tryFindChatTabbedPane().ifPresent(tabbedPane -> {
|
||||
tabbedPane.clearAll();
|
||||
tabbedPane.addNewTab(new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation(project)));
|
||||
});
|
||||
tryFindChatTabbedPane().ifPresent(ChatToolWindowTabbedPane::clearAll);
|
||||
}
|
||||
|
||||
public @NotNull ToolWindow getToolWindow() {
|
||||
|
|
@ -142,4 +129,4 @@ public final class ChatToolWindowContentManager {
|
|||
public void clearAllTags() {
|
||||
tryFindActiveChatTabPanel().ifPresent(ChatToolWindowTabPanel::clearAllTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ import ee.carlrobert.codegpt.CodeGPTKeys;
|
|||
import ee.carlrobert.codegpt.actions.toolwindow.ClearChatWindowAction;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.CreateNewConversationAction;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction;
|
||||
import ee.carlrobert.codegpt.completions.ConversationType;
|
||||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.psistructure.models.ClassStructure;
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType;
|
||||
import ee.carlrobert.codegpt.settings.models.ModelSettings;
|
||||
import ee.carlrobert.codegpt.settings.prompts.PersonaPromptDetailsState;
|
||||
|
|
@ -35,16 +37,25 @@ import ee.carlrobert.codegpt.settings.service.ServiceType;
|
|||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTUserDetailsNotifier;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ToolWindowFooterNotification;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.AttachImageNotifier;
|
||||
import java.awt.CardLayout;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
||||
|
||||
private static final String LANDING_CARD = "LANDING";
|
||||
private static final String TABS_CARD = "TABS";
|
||||
|
||||
private final ToolWindowFooterNotification imageFileAttachmentNotification;
|
||||
private final ActionLink upgradePlanLink;
|
||||
private final ChatToolWindowTabbedPane tabbedPane;
|
||||
private final JPanel centerPanel;
|
||||
private final CardLayout centerLayout;
|
||||
private final Project project;
|
||||
private ChatToolWindowTabPanel landingPanel;
|
||||
|
||||
public ChatToolWindowPanel(
|
||||
@NotNull Project project,
|
||||
|
|
@ -60,22 +71,16 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
|||
upgradePlanLink.setExternalLinkIcon();
|
||||
upgradePlanLink.setVisible(false);
|
||||
|
||||
var tabPanel = new ChatToolWindowTabPanel(project, getConversation());
|
||||
tabbedPane = new ChatToolWindowTabbedPane(parentDisposable);
|
||||
tabbedPane.addNewTab(tabPanel);
|
||||
tabbedPane.setTabLifecycleCallbacks(this::showTabsView, this::showLandingView);
|
||||
centerLayout = new CardLayout();
|
||||
centerPanel = new JPanel(centerLayout);
|
||||
centerPanel.add(tabbedPane, TABS_CARD);
|
||||
|
||||
initToolWindowPanel(project);
|
||||
initializeEventListeners(project);
|
||||
|
||||
Disposer.register(parentDisposable, tabPanel);
|
||||
}
|
||||
|
||||
private Conversation getConversation() {
|
||||
var conversation = ConversationsState.getCurrentConversation();
|
||||
if (conversation == null) {
|
||||
return ConversationService.getInstance().startConversation(project);
|
||||
}
|
||||
return conversation;
|
||||
showLandingView();
|
||||
Disposer.register(parentDisposable, this::disposeLandingPanel);
|
||||
}
|
||||
|
||||
private void initializeEventListeners(Project project) {
|
||||
|
|
@ -107,6 +112,63 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
|||
return tabbedPane;
|
||||
}
|
||||
|
||||
public ChatToolWindowTabPanel createAndSelectNewTabPanel() {
|
||||
return createAndSelectConversationTab(ConversationService.getInstance().startConversation(project));
|
||||
}
|
||||
|
||||
public ChatToolWindowTabPanel createAndSelectConversationTab(Conversation conversation) {
|
||||
var panel = new ChatToolWindowTabPanel(project, conversation);
|
||||
tabbedPane.addNewTab(panel);
|
||||
showTabsView();
|
||||
return panel;
|
||||
}
|
||||
|
||||
public void showTabsView() {
|
||||
centerLayout.show(centerPanel, TABS_CARD);
|
||||
}
|
||||
|
||||
public void requestFocusForInput() {
|
||||
tabbedPane.tryFindActiveTabPanel()
|
||||
.ifPresentOrElse(
|
||||
ChatToolWindowTabPanel::requestFocusForTextArea,
|
||||
() -> {
|
||||
if (landingPanel != null) {
|
||||
landingPanel.requestFocusForTextArea();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showLandingView() {
|
||||
disposeLandingPanel();
|
||||
landingPanel = createLandingPanel();
|
||||
centerPanel.add(landingPanel.getContent(), LANDING_CARD);
|
||||
centerLayout.show(centerPanel, LANDING_CARD);
|
||||
landingPanel.requestFocusForTextArea();
|
||||
centerPanel.revalidate();
|
||||
centerPanel.repaint();
|
||||
}
|
||||
|
||||
private ChatToolWindowTabPanel createLandingPanel() {
|
||||
var conversation = ConversationService.getInstance().createConversation();
|
||||
conversation.setProjectPath(project.getBasePath());
|
||||
return new ChatToolWindowTabPanel(project, conversation, this::promoteLandingDraftToTab);
|
||||
}
|
||||
|
||||
private void promoteLandingDraftToTab(Message message, Set<ClassStructure> psiStructure) {
|
||||
var tabPanel = createAndSelectNewTabPanel();
|
||||
tabPanel.sendMessage(message, ConversationType.DEFAULT, psiStructure);
|
||||
}
|
||||
|
||||
private void disposeLandingPanel() {
|
||||
if (landingPanel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
centerPanel.remove(landingPanel.getContent());
|
||||
Disposer.dispose(landingPanel);
|
||||
landingPanel = null;
|
||||
}
|
||||
|
||||
public void clearImageNotifications(Project project) {
|
||||
imageFileAttachmentNotification.hideNotification();
|
||||
|
||||
|
|
@ -115,9 +177,7 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
|||
|
||||
private void initToolWindowPanel(Project project) {
|
||||
Runnable onAddNewTab = () -> {
|
||||
tabbedPane.addNewTab(new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation(project)));
|
||||
createAndSelectNewTabPanel();
|
||||
repaint();
|
||||
revalidate();
|
||||
};
|
||||
|
|
@ -127,7 +187,7 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
|||
.addToLeft(createActionToolbar(project, tabbedPane, onAddNewTab).getComponent())
|
||||
.addToRight(upgradePlanLink));
|
||||
setContent(new BorderLayoutPanel()
|
||||
.addToCenter(tabbedPane)
|
||||
.addToCenter(centerPanel)
|
||||
.addToBottom(imageFileAttachmentNotification));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,13 +99,23 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
private final PsiStructureRepository psiStructureRepository;
|
||||
private final TagManager tagManager;
|
||||
private final JPanel mcpApprovalContainer;
|
||||
private final DraftSubmitHandler draftSubmitHandler;
|
||||
private @Nullable ToolwindowChatCompletionRequestHandler requestHandler;
|
||||
private final JBLabel loadingLabel;
|
||||
private final JPanel queuedMessageContainer;
|
||||
|
||||
public ChatToolWindowTabPanel(@NotNull Project project, @NotNull Conversation conversation) {
|
||||
this(project, conversation, null);
|
||||
}
|
||||
|
||||
public ChatToolWindowTabPanel(
|
||||
@NotNull Project project,
|
||||
@NotNull Conversation conversation,
|
||||
@Nullable DraftSubmitHandler draftSubmitHandler
|
||||
) {
|
||||
this.project = project;
|
||||
this.conversation = conversation;
|
||||
this.draftSubmitHandler = draftSubmitHandler;
|
||||
this.chatSession = new ChatSession();
|
||||
conversationService = ConversationService.getInstance();
|
||||
toolWindowScrollablePanel = new ChatToolWindowScrollablePanel();
|
||||
|
|
@ -614,7 +624,12 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
}
|
||||
|
||||
application.invokeLater(() -> {
|
||||
sendMessage(messageBuilder.build(), ConversationType.DEFAULT, psiStructure);
|
||||
var message = messageBuilder.build();
|
||||
if (draftSubmitHandler != null) {
|
||||
draftSubmitHandler.onDraftSubmit(message, psiStructure);
|
||||
} else {
|
||||
sendMessage(message, ConversationType.DEFAULT, psiStructure);
|
||||
}
|
||||
});
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
|
|
@ -772,4 +787,10 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
rootPanel.add(createSouthPanel(createUserPromptPanel()), BorderLayout.SOUTH);
|
||||
return rootPanel;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DraftSubmitHandler {
|
||||
|
||||
void onDraftSubmit(Message message, Set<ClassStructure> psiStructure);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
return o1.compareToIgnoreCase(o2);
|
||||
});
|
||||
private final Disposable parentDisposable;
|
||||
private Runnable onTabsOpened = () -> {
|
||||
};
|
||||
private Runnable onAllTabsClosed = () -> {
|
||||
};
|
||||
|
||||
public ChatToolWindowTabbedPane(Disposable parentDisposable) {
|
||||
this.parentDisposable = parentDisposable;
|
||||
|
|
@ -59,7 +63,13 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
return activeTabMapping;
|
||||
}
|
||||
|
||||
public void setTabLifecycleCallbacks(Runnable onTabsOpened, Runnable onAllTabsClosed) {
|
||||
this.onTabsOpened = onTabsOpened;
|
||||
this.onAllTabsClosed = onAllTabsClosed;
|
||||
}
|
||||
|
||||
public void addNewTab(ChatToolWindowTabPanel toolWindowPanel) {
|
||||
var wasEmpty = activeTabMapping.isEmpty();
|
||||
var tabIndices = activeTabMapping.keySet().toArray(new String[0]);
|
||||
var nextIndex = 0;
|
||||
for (String title : tabIndices) {
|
||||
|
|
@ -79,10 +89,11 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
super.insertTab(title, null, toolWindowPanel.getContent(), null, nextIndex);
|
||||
activeTabMapping.put(title, toolWindowPanel);
|
||||
super.setSelectedIndex(nextIndex);
|
||||
setTabComponentAt(nextIndex, createCloseableTabButtonPanel(title));
|
||||
toolWindowPanel.requestFocusForTextArea();
|
||||
|
||||
if (nextIndex > 0) {
|
||||
setTabComponentAt(nextIndex, createCloseableTabButtonPanel(title));
|
||||
toolWindowPanel.requestFocusForTextArea();
|
||||
if (wasEmpty) {
|
||||
onTabsOpened.run();
|
||||
}
|
||||
|
||||
Disposer.register(parentDisposable, toolWindowPanel);
|
||||
|
|
@ -122,8 +133,14 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
}
|
||||
|
||||
public void clearAll() {
|
||||
if (activeTabMapping.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTabMapping.values().forEach(Disposer::dispose);
|
||||
removeAll();
|
||||
activeTabMapping.clear();
|
||||
onAllTabsClosed.run();
|
||||
}
|
||||
|
||||
public void renameTab(int tabIndex, String newName) {
|
||||
|
|
@ -142,9 +159,7 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
|
||||
setTitleAt(tabIndex, uniqueName);
|
||||
|
||||
if (tabIndex > 0) {
|
||||
setTabComponentAt(tabIndex, createCloseableTabButtonPanel(uniqueName));
|
||||
}
|
||||
setTabComponentAt(tabIndex, createCloseableTabButtonPanel(uniqueName));
|
||||
|
||||
activeTabMapping.remove(oldTitle);
|
||||
activeTabMapping.put(uniqueName, panel);
|
||||
|
|
@ -187,9 +202,7 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
|
||||
public void resetCurrentlyActiveTabPanel(Project project) {
|
||||
tryFindActiveTabPanel().ifPresent(tabPanel -> {
|
||||
Disposer.dispose(tabPanel);
|
||||
activeTabMapping.remove(getTitleAt(getSelectedIndex()));
|
||||
removeTabAt(getSelectedIndex());
|
||||
closeTabAt(getSelectedIndex());
|
||||
addNewTab(new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation(project)));
|
||||
|
|
@ -225,9 +238,7 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
public void actionPerformed(ActionEvent evt) {
|
||||
var tabIndex = indexOfTab(title);
|
||||
if (tabIndex >= 0) {
|
||||
Disposer.dispose(activeTabMapping.get(title));
|
||||
removeTabAt(tabIndex);
|
||||
activeTabMapping.remove(title);
|
||||
closeTabAt(tabIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -238,22 +249,34 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
|
||||
TabPopupMenu() {
|
||||
add(createPopupMenuItem("Rename Title", e -> {
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
if (selectedPopupTabIndex >= 0) {
|
||||
RenameSessionAction.renameSession(ChatToolWindowTabbedPane.this, selectedPopupTabIndex);
|
||||
}
|
||||
}));
|
||||
addSeparator();
|
||||
add(createPopupMenuItem("Close", e -> {
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
activeTabMapping.remove(getTitleAt(selectedPopupTabIndex));
|
||||
removeTabAt(selectedPopupTabIndex);
|
||||
if (selectedPopupTabIndex >= 0) {
|
||||
closeTabAt(selectedPopupTabIndex);
|
||||
}
|
||||
}));
|
||||
add(createPopupMenuItem("Close Other Tabs", e -> {
|
||||
if (selectedPopupTabIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedPopupTabTitle = getTitleAt(selectedPopupTabIndex);
|
||||
var tabPanel = activeTabMapping.get(selectedPopupTabTitle);
|
||||
if (tabPanel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearAll();
|
||||
activeTabMapping.entrySet().stream()
|
||||
.filter(entry -> !entry.getKey().equals(selectedPopupTabTitle))
|
||||
.map(Map.Entry::getValue)
|
||||
.forEach(Disposer::dispose);
|
||||
|
||||
removeAll();
|
||||
activeTabMapping.clear();
|
||||
addNewTab(tabPanel);
|
||||
}));
|
||||
}
|
||||
|
|
@ -262,7 +285,7 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
public void show(Component invoker, int x, int y) {
|
||||
selectedPopupTabIndex = ChatToolWindowTabbedPane.this.getUI()
|
||||
.tabForCoordinate(ChatToolWindowTabbedPane.this, x, y);
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
if (selectedPopupTabIndex >= 0) {
|
||||
super.show(invoker, x, y);
|
||||
}
|
||||
}
|
||||
|
|
@ -273,4 +296,22 @@ public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
return menuItem;
|
||||
}
|
||||
}
|
||||
|
||||
private void closeTabAt(int tabIndex) {
|
||||
if (tabIndex < 0 || tabIndex >= getTabCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var title = getTitleAt(tabIndex);
|
||||
var panel = activeTabMapping.remove(title);
|
||||
if (panel != null) {
|
||||
Disposer.dispose(panel);
|
||||
}
|
||||
|
||||
removeTabAt(tabIndex);
|
||||
|
||||
if (activeTabMapping.isEmpty()) {
|
||||
onAllTabsClosed.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,8 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui;
|
||||
|
||||
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
|
||||
|
||||
import com.intellij.openapi.options.ShowSettingsUtil;
|
||||
import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel;
|
||||
import com.intellij.openapi.roots.ui.componentsList.layout.VerticalStackLayout;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore;
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey;
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType;
|
||||
import ee.carlrobert.codegpt.settings.models.ModelSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceConfigurable;
|
||||
import ee.carlrobert.codegpt.toolwindow.ui.ResponseMessagePanel;
|
||||
import ee.carlrobert.codegpt.ui.UIUtil;
|
||||
import ee.carlrobert.codegpt.util.ApplicationUtil;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -38,34 +25,6 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel {
|
|||
clearAll();
|
||||
add(landingView);
|
||||
landingViewVisible = true;
|
||||
if (ModelSettings.getInstance().getServiceForFeature(FeatureType.CHAT)
|
||||
== ServiceType.PROXYAI
|
||||
&& !CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.CodeGptApiKey.INSTANCE)) {
|
||||
|
||||
var panel = new ResponseMessagePanel();
|
||||
panel.addContent(UIUtil.createTextPane("""
|
||||
<html>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
It looks like you haven't configured your API key yet. Visit <a href="#OPEN_SETTINGS">ProxyAI settings</a> to do so.
|
||||
</p>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
Don't have an account? <a href="https://tryproxy.io/signin">Sign up</a> to get started.
|
||||
</p>
|
||||
</html>""",
|
||||
false,
|
||||
event -> {
|
||||
if (ACTIVATED.equals(event.getEventType())
|
||||
&& "#OPEN_SETTINGS".equals(event.getDescription())) {
|
||||
ShowSettingsUtil.getInstance().showSettingsDialog(
|
||||
ApplicationUtil.findCurrentProject(),
|
||||
CodeGPTServiceConfigurable.class);
|
||||
} else {
|
||||
UIUtil.handleHyperlinkClicked(event);
|
||||
}
|
||||
}));
|
||||
panel.setBorder(JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0));
|
||||
add(panel);
|
||||
}
|
||||
}
|
||||
|
||||
public ResponseMessagePanel getResponseMessagePanel(UUID messageId) {
|
||||
|
|
|
|||
|
|
@ -260,7 +260,6 @@ val Opus_4_6: LLModel = LLModel(
|
|||
maxOutputTokens = 64_000,
|
||||
)
|
||||
|
||||
|
||||
private val GPT5_4: LLModel = LLModel(
|
||||
provider = LLMProvider.OpenAI,
|
||||
id = "gpt-5.4",
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ class AgentToolWindowContentManager(private val project: Project) : Disposable {
|
|||
}
|
||||
})
|
||||
|
||||
createNewAgentTab()
|
||||
|
||||
return tabbedPane
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,20 +7,38 @@ import com.intellij.openapi.components.service
|
|||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.SimpleToolWindowPanel
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import ee.carlrobert.codegpt.conversations.Conversation
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import ee.carlrobert.codegpt.toolwindow.agent.ui.AgentCreditsToolbarLabel
|
||||
import java.awt.CardLayout
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
class AgentToolWindowPanel(
|
||||
private val project: Project
|
||||
) : SimpleToolWindowPanel(true), Disposable {
|
||||
|
||||
companion object {
|
||||
private const val LANDING_CARD = "LANDING"
|
||||
private const val TABS_CARD = "TABS"
|
||||
}
|
||||
|
||||
private val contentManager = project.service<AgentToolWindowContentManager>()
|
||||
private val tabbedPane = contentManager.initializeTabbedPane()
|
||||
private val centerLayout = CardLayout()
|
||||
private val centerPanel = JPanel(centerLayout)
|
||||
private var landingPanel: AgentToolWindowTabPanel? = null
|
||||
|
||||
init {
|
||||
tabbedPane.setTabLifecycleCallbacks(
|
||||
onTabsOpened = { showTabsView() },
|
||||
onAllTabsClosed = { showLandingView() }
|
||||
)
|
||||
centerPanel.add(tabbedPane, TABS_CARD)
|
||||
toolbar = createToolbar()
|
||||
setContent(tabbedPane)
|
||||
setContent(centerPanel)
|
||||
showLandingView()
|
||||
}
|
||||
|
||||
private fun createToolbar(): JComponent {
|
||||
|
|
@ -66,7 +84,45 @@ class AgentToolWindowPanel(
|
|||
|
||||
fun getTabbedPane(): AgentToolWindowTabbedPane = tabbedPane
|
||||
|
||||
private fun showTabsView() {
|
||||
centerLayout.show(centerPanel, TABS_CARD)
|
||||
}
|
||||
|
||||
private fun showLandingView() {
|
||||
disposeLandingPanel()
|
||||
landingPanel = createLandingPanel()
|
||||
centerPanel.add(landingPanel, LANDING_CARD)
|
||||
centerLayout.show(centerPanel, LANDING_CARD)
|
||||
landingPanel?.requestFocusForTextArea()
|
||||
centerPanel.revalidate()
|
||||
centerPanel.repaint()
|
||||
}
|
||||
|
||||
private fun createLandingPanel(): AgentToolWindowTabPanel {
|
||||
val draftSession = AgentSession(
|
||||
sessionId = UUID.randomUUID().toString(),
|
||||
conversation = Conversation()
|
||||
)
|
||||
return AgentToolWindowTabPanel(
|
||||
project = project,
|
||||
agentSession = draftSession,
|
||||
draftSubmitHandler = { message ->
|
||||
val panel = contentManager.createNewAgentTab()
|
||||
panel.submitMessage(message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun disposeLandingPanel() {
|
||||
val current = landingPanel ?: return
|
||||
centerPanel.remove(current)
|
||||
Disposer.dispose(current)
|
||||
landingPanel = null
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
tabbedPane.setTabLifecycleCallbacks(onTabsOpened = {}, onAllTabsClosed = {})
|
||||
disposeLandingPanel()
|
||||
tabbedPane.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ import javax.swing.JPanel
|
|||
|
||||
class AgentToolWindowTabPanel(
|
||||
private val project: Project,
|
||||
private val agentSession: AgentSession
|
||||
private val agentSession: AgentSession,
|
||||
private val draftSubmitHandler: ((MessageWithContext) -> Unit)? = null
|
||||
) : BorderLayoutPanel(), Disposable {
|
||||
companion object {
|
||||
private const val RECOVERED_CONVERSATION_RENDER_BATCH_SIZE = 6
|
||||
|
|
@ -274,6 +275,16 @@ class AgentToolWindowTabPanel(
|
|||
|
||||
private fun handleSubmit(text: String) {
|
||||
if (text.isBlank()) return
|
||||
val message = MessageWithContext(text, userInputPanel.getSelectedTags())
|
||||
if (draftSubmitHandler != null) {
|
||||
draftSubmitHandler.invoke(message)
|
||||
return
|
||||
}
|
||||
submitMessage(message)
|
||||
}
|
||||
|
||||
fun submitMessage(message: MessageWithContext) {
|
||||
if (message.text.isBlank()) return
|
||||
disposeLandingPanelIfPresent()
|
||||
scrollablePanel.clearLandingViewIfVisible()
|
||||
agentSession.serviceType =
|
||||
|
|
@ -282,16 +293,12 @@ class AgentToolWindowTabPanel(
|
|||
val agentService = project.service<AgentService>()
|
||||
|
||||
if (agentService.isSessionRunning(sessionId)) {
|
||||
addQueuedMessage(text)
|
||||
addQueuedMessage(message.text)
|
||||
userInputPanel.clearText()
|
||||
userInputPanel.setSubmitEnabled(true)
|
||||
userInputPanel.setStopEnabled(true)
|
||||
|
||||
agentService.submitMessage(
|
||||
MessageWithContext(text, userInputPanel.getSelectedTags()),
|
||||
eventHandler,
|
||||
sessionId
|
||||
)
|
||||
agentService.submitMessage(message, eventHandler, sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -303,11 +310,10 @@ class AgentToolWindowTabPanel(
|
|||
val rollbackRunId = rollbackService.startSession(sessionId)
|
||||
rollbackPanel.refreshOperations()
|
||||
|
||||
val message = MessageWithContext(text, userInputPanel.getSelectedTags())
|
||||
val messagePanel = scrollablePanel.addMessage(message.id)
|
||||
val userPanel = UserMessagePanel(
|
||||
project,
|
||||
MessageBuilder(project, text).withTags(userInputPanel.getSelectedTags()).build(),
|
||||
MessageBuilder(project, message.text).withTags(message.tags).build(),
|
||||
this
|
||||
)
|
||||
val responsePanel = ResponseMessagePanel()
|
||||
|
|
@ -322,7 +328,7 @@ class AgentToolWindowTabPanel(
|
|||
)
|
||||
|
||||
responsePanel.setResponseContent(responseBody)
|
||||
userPanel.addCopyAction { CopyAction.copyToClipboard(text) }
|
||||
userPanel.addCopyAction { CopyAction.copyToClipboard(message.text) }
|
||||
messagePanel.add(userPanel)
|
||||
messagePanel.add(responsePanel)
|
||||
scrollablePanel.update()
|
||||
|
|
@ -331,7 +337,7 @@ class AgentToolWindowTabPanel(
|
|||
runMessageId = message.id,
|
||||
rollbackRunId = rollbackRunId,
|
||||
responsePanel = responsePanel,
|
||||
prompt = text
|
||||
prompt = message.text
|
||||
)
|
||||
|
||||
eventHandler.resetForNewSubmission()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
)
|
||||
|
||||
private var isTabActive = true
|
||||
private var onTabsOpened: () -> Unit = {}
|
||||
private var onAllTabsClosed: () -> Unit = {}
|
||||
|
||||
init {
|
||||
tabComponentInsets = null
|
||||
|
|
@ -63,6 +65,11 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
|
||||
private val sessionStates = mutableMapOf<String, TabState>()
|
||||
|
||||
fun setTabLifecycleCallbacks(onTabsOpened: () -> Unit, onAllTabsClosed: () -> Unit) {
|
||||
this.onTabsOpened = onTabsOpened
|
||||
this.onAllTabsClosed = onAllTabsClosed
|
||||
}
|
||||
|
||||
fun updateStatusForSession(sessionId: String, status: TabStatus) {
|
||||
val state = sessionStates.getOrPut(sessionId) { TabState() }
|
||||
state.status = status
|
||||
|
|
@ -129,6 +136,7 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
}
|
||||
|
||||
fun addNewTab(toolWindowPanel: AgentToolWindowTabPanel, select: Boolean) {
|
||||
val wasEmpty = activeTabMapping.isEmpty()
|
||||
val tabIndices = activeTabMapping.keys.toTypedArray()
|
||||
var nextIndex = 0
|
||||
|
||||
|
|
@ -156,9 +164,13 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
|
||||
sessionStates[sessionId] = TabState(status = TabStatus.STOPPED, unseen = false)
|
||||
|
||||
setTabComponentAt(nextIndex, createTabButtonPanel(title, nextIndex > 0, TabStatus.STOPPED))
|
||||
setTabComponentAt(nextIndex, createTabButtonPanel(title, TabStatus.STOPPED))
|
||||
toolWindowPanel.requestFocusForTextArea()
|
||||
|
||||
if (wasEmpty) {
|
||||
onTabsOpened()
|
||||
}
|
||||
|
||||
Disposer.register(this, toolWindowPanel)
|
||||
}
|
||||
|
||||
|
|
@ -190,8 +202,17 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
}
|
||||
|
||||
fun clearAll() {
|
||||
if (activeTabMapping.isEmpty()) {
|
||||
return
|
||||
}
|
||||
activeTabMapping.values.forEach {
|
||||
project.service<AgentToolWindowContentManager>().removeSession(it.getSessionId())
|
||||
Disposer.dispose(it)
|
||||
}
|
||||
sessionStates.clear()
|
||||
removeAll()
|
||||
activeTabMapping.clear()
|
||||
onAllTabsClosed()
|
||||
}
|
||||
|
||||
fun renameTab(tabIndex: Int, newName: String) {
|
||||
|
|
@ -213,7 +234,7 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
TabStatus.STOPPED
|
||||
}
|
||||
|
||||
setTabComponentAt(tabIndex, createTabButtonPanel(uniqueName, tabIndex > 0, currentStatus))
|
||||
setTabComponentAt(tabIndex, createTabButtonPanel(uniqueName, currentStatus))
|
||||
|
||||
activeTabMapping.remove(oldTitle)
|
||||
activeTabMapping[uniqueName] = panel
|
||||
|
|
@ -271,7 +292,7 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
}
|
||||
|
||||
private fun renameAgentSession(tabIndex: Int) {
|
||||
if (tabIndex <= 0) {
|
||||
if (tabIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -309,14 +330,8 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
|
||||
fun resetCurrentlyActiveTabPanel() {
|
||||
tryFindActiveTabPanel().ifPresent { tabPanel ->
|
||||
val oldSessionId = tabPanel.getSessionId()
|
||||
val oldDisplayName = tabPanel.getAgentSession().displayName
|
||||
Disposer.dispose(tabPanel)
|
||||
activeTabMapping.remove(getTitleAt(selectedIndex))
|
||||
removeTabAt(selectedIndex)
|
||||
sessionStates.remove(oldSessionId)
|
||||
|
||||
project.service<AgentToolWindowContentManager>().removeSession(oldSessionId)
|
||||
closeTabAt(selectedIndex)
|
||||
val newSession = AgentSession(
|
||||
UUID.randomUUID().toString(),
|
||||
Conversation(),
|
||||
|
|
@ -330,7 +345,6 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
|
||||
private fun createTabButtonPanel(
|
||||
title: String,
|
||||
closeable: Boolean,
|
||||
status: TabStatus = TabStatus.STOPPED
|
||||
): JPanel {
|
||||
val titleLabel = JBLabel(title).apply {
|
||||
|
|
@ -341,18 +355,16 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
val panel = JBUI.Panels.simplePanel(4, 0)
|
||||
.addToLeft(titleLabel)
|
||||
|
||||
if (closeable) {
|
||||
val closeIcon = AllIcons.Actions.Close
|
||||
val button = JButton(closeIcon).apply {
|
||||
addActionListener(CloseActionListener(title))
|
||||
preferredSize = Dimension(closeIcon.iconWidth, closeIcon.iconHeight)
|
||||
border = BorderFactory.createEmptyBorder()
|
||||
isContentAreaFilled = false
|
||||
toolTipText = "Close Agent"
|
||||
rolloverIcon = AllIcons.Actions.CloseHovered
|
||||
}
|
||||
panel.addToRight(button)
|
||||
val closeIcon = AllIcons.Actions.Close
|
||||
val button = JButton(closeIcon).apply {
|
||||
addActionListener(CloseActionListener(title))
|
||||
preferredSize = Dimension(closeIcon.iconWidth, closeIcon.iconHeight)
|
||||
border = BorderFactory.createEmptyBorder()
|
||||
isContentAreaFilled = false
|
||||
toolTipText = "Close Agent"
|
||||
rolloverIcon = AllIcons.Actions.CloseHovered
|
||||
}
|
||||
panel.addToRight(button)
|
||||
|
||||
return panel.andTransparent()
|
||||
}
|
||||
|
|
@ -375,13 +387,7 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
override fun actionPerformed(evt: ActionEvent) {
|
||||
val tabIndex = indexOfTab(title)
|
||||
if (tabIndex >= 0) {
|
||||
activeTabMapping[title]?.let { panel ->
|
||||
sessionStates.remove(panel.getSessionId())
|
||||
project.service<AgentToolWindowContentManager>().removeSession(panel.getSessionId())
|
||||
Disposer.dispose(panel)
|
||||
}
|
||||
removeTabAt(tabIndex)
|
||||
activeTabMapping.remove(title)
|
||||
closeTabAt(tabIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -391,44 +397,46 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
|
||||
init {
|
||||
add(createPopupMenuItem("Rename Title") {
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
if (selectedPopupTabIndex >= 0) {
|
||||
renameAgentSession(selectedPopupTabIndex)
|
||||
}
|
||||
})
|
||||
addSeparator()
|
||||
add(createPopupMenuItem("Close") {
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
val title = getTitleAt(selectedPopupTabIndex)
|
||||
activeTabMapping[title]?.let { panel ->
|
||||
sessionStates.remove(panel.getSessionId())
|
||||
project.service<AgentToolWindowContentManager>().removeSession(panel.getSessionId())
|
||||
Disposer.dispose(panel)
|
||||
}
|
||||
removeTabAt(selectedPopupTabIndex)
|
||||
activeTabMapping.remove(title)
|
||||
if (selectedPopupTabIndex >= 0) {
|
||||
closeTabAt(selectedPopupTabIndex)
|
||||
}
|
||||
})
|
||||
add(createPopupMenuItem("Close Other Tabs") {
|
||||
if (selectedPopupTabIndex < 0) {
|
||||
return@createPopupMenuItem
|
||||
}
|
||||
val selectedPopupTabTitle = getTitleAt(selectedPopupTabIndex)
|
||||
val tabPanel = activeTabMapping[selectedPopupTabTitle]
|
||||
val keepSessionId = tabPanel?.getSessionId()
|
||||
sessionStates.keys
|
||||
if (tabPanel == null) {
|
||||
return@createPopupMenuItem
|
||||
}
|
||||
val keepSessionId = tabPanel.getSessionId()
|
||||
sessionStates.keys.toList()
|
||||
.filter { it != keepSessionId }
|
||||
.forEach { sessionStates.remove(it) }
|
||||
activeTabMapping.values
|
||||
.map { it.getSessionId() }
|
||||
.filter { it != keepSessionId }
|
||||
.forEach { project.service<AgentToolWindowContentManager>().removeSession(it) }
|
||||
activeTabMapping.entries
|
||||
.filter { it.key != selectedPopupTabTitle }
|
||||
.forEach { entry ->
|
||||
project.service<AgentToolWindowContentManager>().removeSession(entry.value.getSessionId())
|
||||
Disposer.dispose(entry.value)
|
||||
}
|
||||
|
||||
clearAll()
|
||||
tabPanel?.let { addNewTab(it) }
|
||||
removeAll()
|
||||
activeTabMapping.clear()
|
||||
addNewTab(tabPanel)
|
||||
})
|
||||
}
|
||||
|
||||
override fun show(invoker: Component, x: Int, y: Int) {
|
||||
selectedPopupTabIndex = this@AgentToolWindowTabbedPane.getUI()
|
||||
.tabForCoordinate(this@AgentToolWindowTabbedPane, x, y)
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
if (selectedPopupTabIndex >= 0) {
|
||||
super.show(invoker, x, y)
|
||||
}
|
||||
}
|
||||
|
|
@ -443,4 +451,23 @@ class AgentToolWindowTabbedPane(private val project: Project) : JBTabbedPane(),
|
|||
override fun dispose() {
|
||||
clearAll()
|
||||
}
|
||||
|
||||
private fun closeTabAt(tabIndex: Int) {
|
||||
if (tabIndex !in 0 until tabCount) {
|
||||
return
|
||||
}
|
||||
|
||||
val title = getTitleAt(tabIndex)
|
||||
val panel = activeTabMapping.remove(title)
|
||||
if (panel != null) {
|
||||
sessionStates.remove(panel.getSessionId())
|
||||
project.service<AgentToolWindowContentManager>().removeSession(panel.getSessionId())
|
||||
Disposer.dispose(panel)
|
||||
}
|
||||
|
||||
removeTabAt(tabIndex)
|
||||
if (activeTabMapping.isEmpty()) {
|
||||
onAllTabsClosed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ import ee.carlrobert.codegpt.ui.UIUtil.createTextPane
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import ee.carlrobert.codegpt.util.coroutines.DisposableCoroutineScope
|
||||
import com.intellij.openapi.Disposable
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.awt.Desktop
|
||||
import java.net.URI
|
||||
|
|
@ -53,7 +52,7 @@ import javax.swing.Box
|
|||
import javax.swing.BoxLayout
|
||||
import javax.swing.JPanel
|
||||
|
||||
class AgentToolWindowLandingPanel(private val project: Project) : ResponseMessagePanel(), Disposable {
|
||||
class AgentToolWindowLandingPanel(private val project: Project) : BorderLayoutPanel(), Disposable {
|
||||
|
||||
companion object {
|
||||
private val logger = thisLogger()
|
||||
|
|
@ -76,37 +75,51 @@ class AgentToolWindowLandingPanel(private val project: Project) : ResponseMessag
|
|||
}
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
historyListPanel.onOpen = { thread -> openCheckpointThread(thread) }
|
||||
historyListPanel.onLoadPage = { query, offset, limit, onResult ->
|
||||
loadHistoryPage(query, offset, limit, onResult)
|
||||
}
|
||||
addContent(buildContent())
|
||||
addToCenter(buildContent())
|
||||
loadHistory()
|
||||
}
|
||||
|
||||
private fun buildContent(): JPanel {
|
||||
return BorderLayoutPanel().apply {
|
||||
border = JBUI.Borders.empty(0)
|
||||
add(topPanel(), BorderLayout.NORTH)
|
||||
add(centerPanel(), BorderLayout.CENTER)
|
||||
return JPanel().apply {
|
||||
layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
||||
isOpaque = false
|
||||
add(primaryMessagePanel())
|
||||
apiKeyPanel()?.let { add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun primaryMessagePanel(): ResponseMessagePanel {
|
||||
return ResponseMessagePanel().apply {
|
||||
addContent(
|
||||
BorderLayoutPanel().apply {
|
||||
border = JBUI.Borders.empty(0)
|
||||
addToTop(topPanel())
|
||||
addToCenter(centerPanel())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun topPanel(): JPanel {
|
||||
return BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
apiKeyPanel()?.let { addToTop(it) }
|
||||
addToCenter(createTextPane(welcomeMessage(), false))
|
||||
}
|
||||
}
|
||||
|
||||
private fun apiKeyPanel(): JPanel? {
|
||||
private fun apiKeyPanel(): ResponseMessagePanel? {
|
||||
val provider = ModelSettings.getInstance().getServiceForFeature(FeatureType.AGENT)
|
||||
if (provider != ServiceType.PROXYAI || CredentialsStore.isCredentialSet(CodeGptApiKey)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ResponseMessagePanel().apply {
|
||||
border = JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0)
|
||||
addContent(
|
||||
createTextPane(
|
||||
"""
|
||||
|
|
@ -133,7 +146,6 @@ class AgentToolWindowLandingPanel(private val project: Project) : ResponseMessag
|
|||
}
|
||||
}
|
||||
)
|
||||
border = JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -304,7 +316,7 @@ class AgentToolWindowLandingPanel(private val project: Project) : ResponseMessag
|
|||
|
||||
private fun refresh() {
|
||||
removeAll()
|
||||
addContent(buildContent())
|
||||
addToCenter(buildContent())
|
||||
loadHistory()
|
||||
revalidate()
|
||||
repaint()
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@ class ChatToolWindowListener : ToolWindowManagerListener {
|
|||
|
||||
private fun requestFocusForTextArea(project: Project) {
|
||||
val contentManager = project.getService(ChatToolWindowContentManager::class.java)
|
||||
contentManager.tryFindChatTabbedPane().ifPresent { tabbedPane ->
|
||||
tabbedPane.tryFindActiveTabPanel().ifPresent { tabPanel ->
|
||||
tabPanel.requestFocusForTextArea()
|
||||
}
|
||||
contentManager.tryFindChatToolWindowPanel().ifPresent { panel ->
|
||||
panel.requestFocusForInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.ui
|
||||
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import ee.carlrobert.codegpt.Icons
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState
|
||||
import ee.carlrobert.codegpt.settings.models.ModelSettings
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceConfigurable
|
||||
import ee.carlrobert.codegpt.ui.UIUtil
|
||||
import ee.carlrobert.codegpt.ui.UIUtil.createTextPane
|
||||
import ee.carlrobert.codegpt.util.ApplicationUtil
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Point
|
||||
import java.awt.event.ActionListener
|
||||
|
|
@ -22,12 +32,57 @@ class ChatToolWindowLandingPanel(onAction: (LandingPanelAction, Point) -> Unit)
|
|||
|
||||
private fun createContent(onAction: (LandingPanelAction, Point) -> Unit): JPanel {
|
||||
return BorderLayoutPanel().apply {
|
||||
add(createTextPane(getWelcomeMessage(), false), BorderLayout.NORTH)
|
||||
add(
|
||||
BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
apiKeyPanel()?.let { addToTop(it) }
|
||||
addToCenter(createTextPane(getWelcomeMessage(), false))
|
||||
},
|
||||
BorderLayout.NORTH
|
||||
)
|
||||
add(createActionsListPanel(onAction), BorderLayout.CENTER)
|
||||
add(createTextPane(getCautionMessage(), false), BorderLayout.SOUTH)
|
||||
}
|
||||
}
|
||||
|
||||
private fun apiKeyPanel(): JPanel? {
|
||||
val provider = ModelSettings.getInstance().getServiceForFeature(FeatureType.CHAT)
|
||||
if (provider != ServiceType.PROXYAI || CredentialsStore.isCredentialSet(CodeGptApiKey)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
addToCenter(
|
||||
createTextPane(
|
||||
"""
|
||||
<html>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
It looks like you haven't configured your API key yet. Visit <a href="#OPEN_SETTINGS">ProxyAI settings</a> to do so.
|
||||
</p>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
Don't have an account? <a href="https://tryproxy.io/signin">Sign up</a> to get started.
|
||||
</p>
|
||||
</html>
|
||||
""".trimIndent(),
|
||||
false
|
||||
) { event ->
|
||||
if (event.eventType == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED &&
|
||||
event.description == "#OPEN_SETTINGS"
|
||||
) {
|
||||
ShowSettingsUtil.getInstance().showSettingsDialog(
|
||||
ApplicationUtil.findCurrentProject(),
|
||||
CodeGPTServiceConfigurable::class.java
|
||||
)
|
||||
} else {
|
||||
UIUtil.handleHyperlinkClicked(event)
|
||||
}
|
||||
}
|
||||
)
|
||||
border = JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActionsListPanel(onAction: (LandingPanelAction, Point) -> Unit): JPanel {
|
||||
val listPanel = JPanel()
|
||||
listPanel.layout = BoxLayout(listPanel, BoxLayout.PAGE_AXIS)
|
||||
|
|
@ -95,4 +150,3 @@ enum class LandingPanelAction(
|
|||
ChatActionsState.DEFAULT_EXPLAIN_PROMPT
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
|||
import ee.carlrobert.codegpt.conversations.ConversationService
|
||||
import ee.carlrobert.codegpt.conversations.message.Message
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import java.awt.event.ActionEvent
|
||||
|
||||
class ChatToolWindowTabbedPaneTest : BasePlatformTestCase() {
|
||||
|
||||
|
|
@ -40,6 +41,29 @@ class ChatToolWindowTabbedPaneTest : BasePlatformTestCase() {
|
|||
assertThat(tabPanel!!.conversation.messages).isEmpty()
|
||||
}
|
||||
|
||||
fun testCanCloseFirstTabWhenMultipleTabsExist() {
|
||||
val tabbedPane = ChatToolWindowTabbedPane(Disposer.newDisposable())
|
||||
tabbedPane.addNewTab(createNewTabPanel())
|
||||
tabbedPane.addNewTab(createNewTabPanel())
|
||||
|
||||
tabbedPane.CloseActionListener("Chat 1")
|
||||
.actionPerformed(ActionEvent(tabbedPane, ActionEvent.ACTION_PERFORMED, "close"))
|
||||
|
||||
assertThat(tabbedPane.activeTabMapping.keys).containsExactly("Chat 2")
|
||||
assertThat(tabbedPane.tabCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
fun testCanCloseLastRemainingTab() {
|
||||
val tabbedPane = ChatToolWindowTabbedPane(Disposer.newDisposable())
|
||||
tabbedPane.addNewTab(createNewTabPanel())
|
||||
|
||||
tabbedPane.CloseActionListener("Chat 1")
|
||||
.actionPerformed(ActionEvent(tabbedPane, ActionEvent.ACTION_PERFORMED, "close"))
|
||||
|
||||
assertThat(tabbedPane.activeTabMapping).isEmpty()
|
||||
assertThat(tabbedPane.tabCount).isZero()
|
||||
}
|
||||
|
||||
private fun createNewTabPanel(): ChatToolWindowTabPanel {
|
||||
return ChatToolWindowTabPanel(
|
||||
project,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue