feat: support high context limits (CodeGPT)

This commit is contained in:
Carl-Robert Linnupuu 2024-10-08 00:33:33 +03:00
parent d336b9ec8b
commit 30c255c5b5
20 changed files with 190 additions and 34 deletions

View file

@ -63,7 +63,7 @@ public final class EncodingManager {
public int countTokens(String text) {
try {
// #444: Cl100kParser.split() throws AssertionError "Input is not UTF-8: "
return encoding.countTokens(text);
return encoding.countTokens(text.replaceAll("<|", "").replaceAll("|>", ""));
} catch (Exception | Error ex) {
LOG.warn("Could not count tokens for: " + text, ex);
return 0;

View file

@ -1,7 +1,9 @@
package ee.carlrobert.codegpt.completions;
import ee.carlrobert.codegpt.ReferencedFile;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.message.Message;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public class CallParameters {
@ -11,8 +13,9 @@ public class CallParameters {
private final Message message;
private final boolean retry;
private final String highlightedText;
private @Nullable String imageMediaType;
private String imageMediaType;
private byte[] imageData;
private List<ReferencedFile> referencedFiles;
public CallParameters(Conversation conversation, Message message) {
this(conversation, ConversationType.DEFAULT, message, null, false);
@ -66,4 +69,12 @@ public class CallParameters {
public @Nullable String getHighlightedText() {
return highlightedText;
}
public @Nullable List<ReferencedFile> getReferencedFiles() {
return referencedFiles;
}
public void setReferencedFiles(List<ReferencedFile> referencedFiles) {
this.referencedFiles = referencedFiles;
}
}

View file

@ -16,7 +16,6 @@ import ee.carlrobert.codegpt.ReferencedFile;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.completions.CallParameters;
import ee.carlrobert.codegpt.completions.CompletionRequestService;
import ee.carlrobert.codegpt.completions.CompletionRequestUtil;
import ee.carlrobert.codegpt.completions.ConversationType;
import ee.carlrobert.codegpt.completions.ToolwindowChatCompletionRequestHandler;
import ee.carlrobert.codegpt.conversations.Conversation;
@ -134,8 +133,6 @@ public class ChatToolWindowTabPanel implements Disposable {
.toList();
message.setReferencedFilePaths(referencedFilePaths);
message.setUserMessage(message.getPrompt());
message.setPrompt(
CompletionRequestUtil.getPromptWithContext(referencedFiles, message.getPrompt()));
totalTokensPanel.updateReferencedFilesTokens(referencedFiles);
@ -147,6 +144,7 @@ public class ChatToolWindowTabPanel implements Disposable {
var attachedFilePath = CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH.get(project);
var callParameters =
getCallParameters(conversationType, message, highlightedText, attachedFilePath);
callParameters.setReferencedFiles(referencedFiles);
if (callParameters.getImageData() != null) {
message.setImageFilePath(attachedFilePath);
chatToolWindowPanel.ifPresent(panel -> panel.clearNotifications(project));
@ -180,10 +178,23 @@ public class ChatToolWindowTabPanel implements Disposable {
return callParameters;
}
private boolean hasReferencedFilePaths(Message message) {
return message.getReferencedFilePaths() != null && !message.getReferencedFilePaths().isEmpty();
}
private boolean hasReferencedFilePaths(Conversation conversation) {
return conversation.getMessages().stream()
.anyMatch(
it -> it.getReferencedFilePaths() != null && !it.getReferencedFilePaths().isEmpty());
}
private ResponsePanel createResponsePanel(
CallParameters callParameters,
ConversationType conversationType) {
var message = callParameters.getMessage();
var fileContextIncluded =
hasReferencedFilePaths(message) || hasReferencedFilePaths(conversation);
return new ResponsePanel()
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
@ -194,7 +205,9 @@ public class ChatToolWindowTabPanel implements Disposable {
true,
false,
message.isWebSearchIncluded(),
message.getDocumentationDetails() != null, this));
message.getDocumentationDetails() != null,
fileContextIncluded,
this));
}
private void reloadMessage(

View file

@ -27,6 +27,7 @@ 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.ProcessContextEventDetails;
import ee.carlrobert.codegpt.events.WebSearchEventDetails;
import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
@ -44,10 +45,10 @@ import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.Nullable;
public class ChatMessageResponseBody extends JPanel {
@ -59,7 +60,9 @@ public class ChatMessageResponseBody extends JPanel {
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 final JPanel progressContainer = new JPanel();
private final AsyncProcessIcon webDocsSpinner = new AsyncProcessIcon("web_docs_spinner");
private final AsyncProcessIcon processSpinner = new AsyncProcessIcon("process_spinner");
private final @Nullable String highlightedText;
private ResponseEditorPanel currentlyProcessedEditorPanel;
private JTextPane currentlyProcessedTextPane;
@ -67,7 +70,7 @@ public class ChatMessageResponseBody extends JPanel {
private boolean responseReceived;
public ChatMessageResponseBody(Project project, Disposable parentDisposable) {
this(project, null, false, false, false, false, parentDisposable);
this(project, null, false, false, false, false, false, parentDisposable);
}
public ChatMessageResponseBody(
@ -77,8 +80,8 @@ public class ChatMessageResponseBody extends JPanel {
boolean readOnly,
boolean webSearchIncluded,
boolean webDocIncluded,
boolean fileContextIncluded,
Disposable parentDisposable) {
super(new BorderLayout());
this.project = project;
this.highlightedText = highlightedText;
this.parentDisposable = parentDisposable;
@ -98,6 +101,12 @@ public class ChatMessageResponseBody extends JPanel {
add(webDocProgressContainer);
}
if (fileContextIncluded) {
progressContainer.setLayout(new BoxLayout(progressContainer, BoxLayout.Y_AXIS));
progressContainer.setBorder(JBUI.Borders.emptyBottom(8));
add(progressContainer);
}
if (withGhostText) {
prepareProcessingText(!readOnly);
currentlyProcessedTextPane.setText(
@ -209,6 +218,7 @@ public class ChatMessageResponseBody extends JPanel {
case ANALYZE_WEB_DOC_STARTED -> showWebDocsProgress();
case ANALYZE_WEB_DOC_COMPLETED -> completeWebDocsProgress(event.getDetails());
case ANALYZE_WEB_DOC_FAILED -> failWebDocsProgress(event.getDetails());
case PROCESS_CONTEXT -> showProcessContextEvent(event.getDetails());
default -> {
}
}
@ -305,7 +315,7 @@ public class ChatMessageResponseBody extends JPanel {
private void showWebDocsProgress() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
wrapper.add(spinner);
wrapper.add(webDocsSpinner);
wrapper.add(Box.createHorizontalStrut(4));
wrapper.add(new JBLabel(
CodeGPTBundle.get("chatMessageResponseBody.webDocs.startProgress.label")).withFont(
@ -325,10 +335,50 @@ public class ChatMessageResponseBody extends JPanel {
}
}
private void showProcessContextEvent(EventDetails eventDetails) {
if (eventDetails instanceof ProcessContextEventDetails details) {
switch (details.getStatus()) {
case "STARTED": {
updateProgressContainer(details.getDescription(), null);
break;
}
case "FAILED": {
updateProgressContainer(details.getDescription(), General.Error);
break;
}
case "COMPLETED": {
updateProgressContainer(details.getDescription(), Icons.GreenCheckmark);
break;
}
default:
break;
}
}
}
private void updateWebDocsProgressLabel(String text, Icon icon) {
updateWebDocsProgress(new JBLabel(text, icon, SwingConstants.LEADING).withFont(JBFont.small()));
}
private void updateProgressContainer(String text, @Nullable Icon icon) {
ApplicationManager.getApplication().invokeLater(() -> {
progressContainer.removeAll();
JComponent wrapper;
if (icon != null) {
wrapper = new JBLabel(text, icon, SwingConstants.LEADING);
((JBLabel) wrapper).setHorizontalTextPosition(SwingConstants.LEADING);
} else {
wrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
wrapper.add(new JBLabel(text));
wrapper.add(Box.createHorizontalStrut(4));
wrapper.add(processSpinner);
}
progressContainer.add(JBUI.Panels.simplePanel(wrapper));
progressContainer.revalidate();
progressContainer.repaint();
});
}
private void updateWebDocsProgress(Component content) {
webDocProgressContainer.removeAll();
webDocProgressContainer.add(JBUI.Panels.simplePanel(content));

View file

@ -43,7 +43,7 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel {
It looks like you haven't configured your API key yet. Visit <a href="#OPEN_SETTINGS">CodeGPT settings</a> to do so.
</p>
<p style="margin-top: 4px; margin-bottom: 4px;">
Don't have an account? <a href="https://codegpt.ee/signin">Sign up</a> for free access to all open-source models.
Don't have an account? <a href="https://codegpt.ee">Sign up</a> for free access to all models.
</p>
</html>""",
false,

View file

@ -102,6 +102,7 @@ public class UserMessagePanel extends JPanel {
true,
false,
false,
false,
parentDisposable)
.withResponse(prompt);
}

View file

@ -152,7 +152,7 @@ public class TotalTokensPanel extends JPanel {
"Referenced Files Tokens", totalTokensDetails.getReferencedFilesTokens()))
.entrySet().stream()
.map(entry -> format(
"<p style=\"margin: 0;\"><small>%s: <strong>%d</strong></small></p>",
"<p style=\"margin: 0; padding: 0;\"><small>%s: <strong>%d</strong></small></p>",
entry.getKey(),
entry.getValue()))
.collect(Collectors.joining());
@ -165,14 +165,16 @@ public class TotalTokensPanel extends JPanel {
private String getIconToolTipText(String html) {
if (!GeneralSettings.isSelected(ServiceType.OPENAI)) {
return """
<html
<p style="margin: 4px 0;">
<html>
<body style="margin: 0; padding: 0;">
%s
<p style="margin-top: 8px;">
<small>
<strong> Keep in mind that the output values might vary across different
large language models due to variations in their encoding methods.</strong>
<strong>Note:</strong> Output values might vary across different large language models
due to variations in their encoding methods.
</small>
</p>
%s
</body>
</html>""".formatted(html);
}
return "<html" + html + "</html>";

View file

@ -1,10 +1,12 @@
package ee.carlrobert.codegpt.ui.checkbox;
import com.intellij.icons.AllIcons;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.CheckedTreeNode;
import com.intellij.util.PlatformIcons;
import ee.carlrobert.codegpt.ReferencedFile;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@ -19,12 +21,20 @@ public class VirtualFileCheckboxTree extends FileCheckboxTree {
public List<ReferencedFile> getReferencedFiles() {
var checkedNodes = getCheckedNodes(VirtualFile.class, Objects::nonNull);
if (checkedNodes.length > 1000) {
if (checkedNodes.length > 1024) {
OverlayUtil.showNotification("Too many files selected.", NotificationType.ERROR);
throw new RuntimeException("Too many files selected");
}
return Arrays.stream(checkedNodes)
.map(item -> new ReferencedFile(new File(item.getPath())))
.map(item -> {
var file = new File(item.getPath());
if (file.isFile()) {
return new ReferencedFile(file);
}
return null;
})
.filter(Objects::nonNull)
.toList();
}