feat: display web docs progress

This commit is contained in:
Carl-Robert Linnupuu 2024-08-22 18:40:50 +03:00
parent ce0b90f232
commit 03feba0a4f
11 changed files with 195 additions and 32 deletions

View file

@ -25,4 +25,6 @@ public final class Icons {
public static final Icon Ollama = IconLoader.getIcon("/icons/ollama.svg", Icons.class);
public static final Icon User = IconLoader.getIcon("/icons/user.svg", Icons.class);
public static final Icon Upload = IconLoader.getIcon("/icons/upload.svg", Icons.class);
public static final Icon GreenCheckmark =
IconLoader.getIcon("/icons/greenCheckmark.svg", Icons.class);
}

View file

@ -32,6 +32,7 @@ import ee.carlrobert.codegpt.toolwindow.ui.ChatToolWindowLandingPanel;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.CreateDocumentationActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.DocumentationActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.PersonaActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.WebSearchActionItem;
@ -175,7 +176,8 @@ public class ChatToolWindowTabPanel implements Disposable {
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
.addContent(
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(), this));
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(),
message.getDocumentationDetails() != null, this));
}
private void reloadMessage(
@ -265,7 +267,8 @@ public class ChatToolWindowTabPanel implements Disposable {
var addedDocumentation = CodeGPTKeys.ADDED_DOCUMENTATION.get(project);
var appliedInlayExists = appliedInlayActions.stream()
.anyMatch(it -> it.getSuggestion() instanceof DocumentationActionItem);
.anyMatch(it -> it.getSuggestion() instanceof DocumentationActionItem
|| it.getSuggestion() instanceof CreateDocumentationActionItem);
if (addedDocumentation != null && appliedInlayExists) {
message.setDocumentationDetails(addedDocumentation);
CodeGPTKeys.ADDED_DOCUMENTATION.set(project, null);
@ -273,7 +276,7 @@ public class ChatToolWindowTabPanel implements Disposable {
var addedPersona = CodeGPTKeys.ADDED_PERSONA.get(project);
var personaInlayExists = appliedInlayActions.stream()
.anyMatch(it -> it.getSuggestion() instanceof PersonaActionItem);
.anyMatch(it -> it.getSuggestion() instanceof PersonaActionItem);
if (addedPersona != null && personaInlayExists) {
message.setPersonaDetails(addedPersona);
CodeGPTKeys.ADDED_PERSONA.set(project, null);

View file

@ -121,8 +121,7 @@ abstract class ToolWindowCompletionResponseEventListener implements
@Override
public void handleCodeGPTEvent(CodeGPTEvent event) {
ApplicationManager.getApplication().invokeLater(() ->
responseContainer.displayWebSearchItem(event.getEvent().getDetails()));
responseContainer.handleCodeGPTEvent(event);
}
private void stopStreaming(ChatMessageResponseBody responseContainer) {

View file

@ -5,6 +5,7 @@ import static ee.carlrobert.codegpt.util.MarkdownUtil.convertMdToHtml;
import static java.lang.String.format;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import com.intellij.icons.AllIcons.General;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
@ -14,12 +15,19 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.JBFont;
import com.intellij.util.ui.JBUI;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.parser.Parser;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.events.Details;
import ee.carlrobert.codegpt.events.AnalysisCompletedEventDetails;
import ee.carlrobert.codegpt.events.AnalysisFailedEventDetails;
import ee.carlrobert.codegpt.events.CodeGPTEvent;
import ee.carlrobert.codegpt.events.EventDetails;
import ee.carlrobert.codegpt.events.WebSearchEventDetails;
import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
@ -29,11 +37,16 @@ import ee.carlrobert.codegpt.ui.UIUtil;
import ee.carlrobert.codegpt.util.EditorUtil;
import ee.carlrobert.codegpt.util.MarkdownUtil;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.util.Objects;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingConstants;
public class ChatMessageResponseBody extends JPanel {
@ -41,8 +54,10 @@ public class ChatMessageResponseBody extends JPanel {
private final Disposable parentDisposable;
private final StreamParser streamParser;
private final boolean readOnly;
private final DefaultListModel<Details> webpageListModel = new DefaultListModel<>();
private final DefaultListModel<WebSearchEventDetails> webpageListModel = new DefaultListModel<>();
private final WebpageList webpageList = new WebpageList(webpageListModel);
private final JPanel webDocProgressContainer = new JPanel();
private final AsyncProcessIcon spinner = new AsyncProcessIcon("sign_in_spinner");
private ResponseEditorPanel currentlyProcessedEditorPanel;
private JTextPane currentlyProcessedTextPane;
private JPanel webpageListPanel;
@ -56,7 +71,7 @@ public class ChatMessageResponseBody extends JPanel {
Project project,
boolean withGhostText,
Disposable parentDisposable) {
this(project, withGhostText, false, false, parentDisposable);
this(project, withGhostText, false, false, false, parentDisposable);
}
public ChatMessageResponseBody(
@ -64,13 +79,14 @@ public class ChatMessageResponseBody extends JPanel {
boolean withGhostText,
boolean readOnly,
boolean webSearchIncluded,
boolean webDocIncluded,
Disposable parentDisposable) {
super(new BorderLayout());
this.project = project;
this.parentDisposable = parentDisposable;
this.streamParser = new StreamParser();
this.readOnly = readOnly;
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setOpaque(false);
if (webSearchIncluded) {
@ -78,6 +94,12 @@ public class ChatMessageResponseBody extends JPanel {
add(webpageListPanel);
}
if (webDocIncluded) {
webDocProgressContainer.setLayout(new BoxLayout(webDocProgressContainer, BoxLayout.Y_AXIS));
webDocProgressContainer.setBorder(JBUI.Borders.emptyBottom(8));
add(webDocProgressContainer);
}
if (withGhostText) {
prepareProcessingText(!readOnly);
currentlyProcessedTextPane.setText(
@ -161,10 +183,30 @@ public class ChatMessageResponseBody extends JPanel {
});
}
public void displayWebSearchItem(Details details) {
webpageListModel.addElement(details);
webpageList.revalidate();
webpageList.repaint();
public void handleCodeGPTEvent(CodeGPTEvent codegptEvent) {
ApplicationManager.getApplication()
.invokeLater(() -> {
var event = codegptEvent.getEvent();
if (event.getDetails() instanceof WebSearchEventDetails webSearchEventDetails) {
displayWebSearchItem(webSearchEventDetails);
return;
}
switch (event.getType()) {
case WEB_SEARCH_ITEM -> {
if (event.getDetails() != null
&& event.getDetails() instanceof WebSearchEventDetails eventDetails) {
displayWebSearchItem(eventDetails);
}
}
case ANALYZE_WEB_DOC_STARTED -> showWebDocsProgress();
case ANALYZE_WEB_DOC_COMPLETED -> completeWebDocsProgress(event.getDetails());
case ANALYZE_WEB_DOC_FAILED -> failWebDocsProgress(event.getDetails());
default -> {
}
}
});
}
public void hideCaret() {
@ -236,6 +278,45 @@ public class ChatMessageResponseBody extends JPanel {
add(currentlyProcessedEditorPanel);
}
private void displayWebSearchItem(WebSearchEventDetails details) {
webpageListModel.addElement(details);
webpageList.revalidate();
webpageList.repaint();
}
private void showWebDocsProgress() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
wrapper.add(spinner);
wrapper.add(Box.createHorizontalStrut(4));
wrapper.add(new JBLabel(
CodeGPTBundle.get("chatMessageResponseBody.webDocs.startProgress.label")).withFont(
JBFont.small()));
updateWebDocsProgress(wrapper);
}
private void completeWebDocsProgress(EventDetails eventDetails) {
if (eventDetails instanceof AnalysisCompletedEventDetails defaultEventDetails) {
updateWebDocsProgressLabel(defaultEventDetails.getDescription(), Icons.GreenCheckmark);
}
}
private void failWebDocsProgress(EventDetails eventDetails) {
if (eventDetails instanceof AnalysisFailedEventDetails failedEventDetails) {
updateWebDocsProgressLabel(failedEventDetails.getError(), General.Error);
}
}
private void updateWebDocsProgressLabel(String text, Icon icon) {
updateWebDocsProgress(new JBLabel(text, icon, SwingConstants.LEADING).withFont(JBFont.small()));
}
private void updateWebDocsProgress(Component content) {
webDocProgressContainer.removeAll();
webDocProgressContainer.add(JBUI.Panels.simplePanel(content));
webDocProgressContainer.revalidate();
webDocProgressContainer.repaint();
}
private JTextPane createTextPane(String text, boolean caretVisible) {
var textPane = UIUtil.createTextPane(text, false, event -> {
if (FileUtil.exists(event.getDescription()) && ACTIVATED.equals(event.getEventType())) {
@ -258,7 +339,7 @@ public class ChatMessageResponseBody extends JPanel {
var title = new JPanel(new BorderLayout());
title.setOpaque(false);
title.setBorder(JBUI.Borders.empty(8, 0));
title.add(new JBLabel(CodeGPTBundle.get("chatMessageResponseBody.webPagesTitle"))
title.add(new JBLabel(CodeGPTBundle.get("chatMessageResponseBody.webPages.title"))
.withFont(JBUI.Fonts.miniFont()), BorderLayout.LINE_START);
var listPanel = new JPanel(new BorderLayout());
listPanel.add(webpageList, BorderLayout.LINE_START);

View file

@ -12,7 +12,7 @@ import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.CodeGPTKeys;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.events.Details;
import ee.carlrobert.codegpt.events.WebSearchEventDetails;
import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.toolwindow.ui.WebpageList;
import java.awt.BorderLayout;
@ -66,9 +66,10 @@ public class UserMessagePanel extends JPanel {
var panel = new JPanel(new BorderLayout());
panel.setOpaque(false);
if (addedDocumentation != null) {
var listModel = new DefaultListModel<Details>();
listModel.addElement(new Details(UUID.randomUUID().toString(), addedDocumentation.getName(),
addedDocumentation.getUrl(), addedDocumentation.getUrl()));
var listModel = new DefaultListModel<WebSearchEventDetails>();
listModel.addElement(
new WebSearchEventDetails(UUID.randomUUID(), addedDocumentation.getName(),
addedDocumentation.getUrl(), addedDocumentation.getUrl()));
panel.add(createWebpageListPanel(new WebpageList(listModel)), BorderLayout.NORTH);
}
@ -99,6 +100,7 @@ public class UserMessagePanel extends JPanel {
false,
true,
false,
false,
parentDisposable)
.withResponse(prompt);
}