mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-06 16:22:19 +00:00
1.1.3 - Improve error handling, add block caret
This commit is contained in:
parent
f8fdb2cbab
commit
04c2ee1f09
17 changed files with 193 additions and 105 deletions
|
|
@ -4,7 +4,7 @@ plugins {
|
|||
}
|
||||
|
||||
group = "ee.carlrobert"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package ee.carlrobert.chatgpt;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EmptyCallback {
|
||||
void call();
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package ee.carlrobert.chatgpt.client;
|
|||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ee.carlrobert.chatgpt.EmptyCallback;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
|
|
@ -24,12 +23,9 @@ public abstract class Client {
|
|||
protected abstract BodySubscriber<Void> subscribe(
|
||||
ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete);
|
||||
Runnable onComplete);
|
||||
|
||||
public void getCompletionsAsync(
|
||||
String prompt,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete) {
|
||||
public void getCompletionsAsync(String prompt, Consumer<String> onMessageReceived, Runnable onComplete) {
|
||||
this.userPrompt = prompt;
|
||||
this.client.sendAsync(
|
||||
buildHttpRequest(prompt),
|
||||
|
|
|
|||
|
|
@ -2,34 +2,25 @@ package ee.carlrobert.chatgpt.client;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.net.http.HttpResponse.BodySubscriber;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Flow.Subscription;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class Subscriber<T extends ApiResponse> implements BodySubscriber<Void> {
|
||||
|
||||
protected final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
private final Consumer<T> responseConsumer;
|
||||
private volatile Subscription subscription;
|
||||
private volatile String deferredText;
|
||||
|
||||
protected abstract T deserializePayload(String jsonPayload) throws JsonProcessingException;
|
||||
|
||||
protected abstract void onRequestComplete();
|
||||
|
||||
// Overridden from concrete class
|
||||
protected void processRegularResponse(String response) {
|
||||
}
|
||||
protected abstract void onErrorOccurred();
|
||||
|
||||
public Subscriber(Consumer<T> responseConsumer) {
|
||||
this.responseConsumer = responseConsumer;
|
||||
}
|
||||
protected abstract void send(String jsonPayload, String token);
|
||||
|
||||
public CompletionStage<Void> getBody() {
|
||||
return this.future;
|
||||
|
|
@ -52,20 +43,12 @@ public abstract class Subscriber<T extends ApiResponse> implements BodySubscribe
|
|||
for (var buffer : buffers) {
|
||||
var decodedText = deferredText + UTF_8.decode(buffer);
|
||||
var tokens = decodedText.split("\n\n", -1);
|
||||
if (tokens.length == 1) {
|
||||
processRegularResponse(decodedText);
|
||||
}
|
||||
|
||||
for (var i = 0; i < tokens.length - 1; i++) {
|
||||
var responsePayload = extractPayload(tokens[i].split("\n"));
|
||||
for (String token : tokens) {
|
||||
var responsePayload = extractPayload(token.split("\n"));
|
||||
if ("[DONE]".equals(responsePayload)) {
|
||||
future.complete(null);
|
||||
} else {
|
||||
try {
|
||||
this.responseConsumer.accept(deserializePayload(responsePayload));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Couldn't deserialize the payload", e);
|
||||
}
|
||||
send(responsePayload, token);
|
||||
}
|
||||
}
|
||||
deferredText = tokens[tokens.length - 1];
|
||||
|
|
@ -80,7 +63,11 @@ public abstract class Subscriber<T extends ApiResponse> implements BodySubscribe
|
|||
}
|
||||
|
||||
public void onError(Throwable e) {
|
||||
this.future.completeExceptionally(e);
|
||||
try {
|
||||
onErrorOccurred();
|
||||
} finally {
|
||||
this.future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onComplete() {
|
||||
|
|
@ -93,7 +80,7 @@ public abstract class Subscriber<T extends ApiResponse> implements BodySubscribe
|
|||
}
|
||||
|
||||
private String extractPayload(String[] payload) {
|
||||
Pattern dataLinePattern = Pattern.compile("^data: ?(.*)$");
|
||||
Pattern dataLinePattern = Pattern.compile("^data:\\s*(\\{.*})\\s*$");
|
||||
var responseBuilder = new StringBuilder();
|
||||
for (var line : payload) {
|
||||
var matcher = dataLinePattern.matcher(line);
|
||||
|
|
|
|||
|
|
@ -4,39 +4,64 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ee.carlrobert.chatgpt.client.Subscriber;
|
||||
import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponse;
|
||||
import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponseDetail;
|
||||
import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponseError;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
|
||||
|
||||
private final Consumer<String> responseConsumer;
|
||||
private final Consumer<ChatGPTResponse> onCompleteCallback;
|
||||
private final Consumer<ChatGPTResponseError> onErrorCallback;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private ChatGPTResponse lastReceivedResponse;
|
||||
|
||||
public ChatGPTBodySubscriber(
|
||||
Consumer<ChatGPTResponse> responseConsumer,
|
||||
Consumer<ChatGPTResponse> onCompleteCallback,
|
||||
Consumer<ChatGPTResponseError> onErrorCallback) {
|
||||
super(responseConsumer);
|
||||
Consumer<String> responseConsumer,
|
||||
Consumer<ChatGPTResponse> onCompleteCallback) {
|
||||
this.responseConsumer = responseConsumer;
|
||||
this.onCompleteCallback = onCompleteCallback;
|
||||
this.onErrorCallback = onErrorCallback;
|
||||
}
|
||||
|
||||
protected ChatGPTResponse deserializePayload(String jsonPayload) throws JsonProcessingException {
|
||||
lastReceivedResponse = objectMapper.readValue(jsonPayload, ChatGPTResponse.class);
|
||||
return lastReceivedResponse;
|
||||
}
|
||||
|
||||
protected void onRequestComplete() {
|
||||
onCompleteCallback.accept(lastReceivedResponse);
|
||||
}
|
||||
|
||||
protected void onErrorOccurred() {
|
||||
responseConsumer.accept("Something went wrong. Please try again later.");
|
||||
}
|
||||
|
||||
protected void processRegularResponse(String jsonPayload) {
|
||||
protected void send(String responsePayload, String token) {
|
||||
if (!responsePayload.isEmpty()) {
|
||||
try {
|
||||
var response = objectMapper.readValue(responsePayload, ChatGPTResponse.class);
|
||||
var author = response.getMessage().getAuthor();
|
||||
if (author != null && "assistant".equals(author.getRole())) {
|
||||
var message = response.getFullMessage();
|
||||
if (lastReceivedResponse != null) {
|
||||
message = message.replace(lastReceivedResponse.getFullMessage(), "");
|
||||
}
|
||||
lastReceivedResponse = response;
|
||||
this.responseConsumer.accept(message);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Unable to deserialize the payload", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
var response = objectMapper.readValue(token, ChatGPTResponseDetail.class);
|
||||
this.responseConsumer.accept(response.getDetail());
|
||||
} catch (JsonProcessingException e) {
|
||||
tryProcessingErrorResponse(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryProcessingErrorResponse(String jsonPayload) {
|
||||
try {
|
||||
onErrorCallback.accept(objectMapper.readValue(jsonPayload, ChatGPTResponseError.class));
|
||||
var error = objectMapper.readValue(jsonPayload, ChatGPTResponseError.class);
|
||||
if ("invalid_api_key".equals(error.getDetail().getCode())) {
|
||||
responseConsumer.accept(error.getDetail().getMessage());
|
||||
}
|
||||
future.complete(null);
|
||||
} catch (JsonProcessingException e) {
|
||||
future.completeExceptionally(e);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt;
|
||||
|
||||
import ee.carlrobert.chatgpt.EmptyCallback;
|
||||
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
|
||||
import ee.carlrobert.chatgpt.client.Client;
|
||||
import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponse;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodySubscriber;
|
||||
import java.net.http.HttpResponse.ResponseInfo;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -17,7 +17,6 @@ public class ChatGPTClient extends Client {
|
|||
private static ChatGPTClient instance;
|
||||
private static ChatGPTResponse lastReceivedResponse;
|
||||
|
||||
|
||||
private ChatGPTClient() {
|
||||
}
|
||||
|
||||
|
|
@ -61,24 +60,20 @@ public class ChatGPTClient extends Client {
|
|||
settings.accessToken);
|
||||
}
|
||||
|
||||
protected HttpResponse.BodySubscriber<Void> subscribe(
|
||||
HttpResponse.ResponseInfo responseInfo,
|
||||
protected BodySubscriber<Void> subscribe(
|
||||
ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete) {
|
||||
Runnable onComplete) {
|
||||
if (responseInfo.statusCode() == 200) {
|
||||
return new ChatGPTBodySubscriber((
|
||||
response -> onMessageReceived.accept(String.join("", response.getMessage().getContent().getParts()))),
|
||||
return new ChatGPTBodySubscriber(
|
||||
onMessageReceived,
|
||||
response -> {
|
||||
lastReceivedResponse = response;
|
||||
onComplete.call();
|
||||
},
|
||||
error -> {
|
||||
if ("invalid_api_key".equals(error.getDetail().getCode())) {
|
||||
onMessageReceived.accept(error.getDetail().getMessage());
|
||||
}
|
||||
onComplete.run();
|
||||
});
|
||||
} else {
|
||||
onMessageReceived.accept("Something went wrong. Please try again later.");
|
||||
onComplete.run();
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ public class ChatGPTResponse implements ApiResponse {
|
|||
public void setConversationId(String conversationId) {
|
||||
this.conversationId = conversationId;
|
||||
}
|
||||
|
||||
public String getFullMessage() {
|
||||
return String.join("", message.getContent().getParts());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponseDetail {
|
||||
private String detail;
|
||||
|
||||
public String getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(String detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|||
public class ChatGPTResponseMessage {
|
||||
|
||||
private String id;
|
||||
private ChatGPTResponseMessageAuthor author;
|
||||
private ChatGPTResponseMessageContent content;
|
||||
|
||||
public String getId() {
|
||||
|
|
@ -16,6 +17,14 @@ public class ChatGPTResponseMessage {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public ChatGPTResponseMessageAuthor getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(ChatGPTResponseMessageAuthor author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public ChatGPTResponseMessageContent getContent() {
|
||||
return content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponseMessageAuthor {
|
||||
|
||||
private String role;
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,21 +10,33 @@ public class GPTBodySubscriber extends Subscriber<GPTResponse> {
|
|||
|
||||
private final Consumer<String> onCompleteCallback;
|
||||
private final StringBuilder messageBuilder = new StringBuilder();
|
||||
private final Consumer<String> responseConsumer;
|
||||
|
||||
public GPTBodySubscriber(
|
||||
Consumer<GPTResponse> responseConsumer,
|
||||
Consumer<String> responseConsumer,
|
||||
Consumer<String> onCompleteCallback) {
|
||||
super(responseConsumer);
|
||||
this.responseConsumer = responseConsumer;
|
||||
this.onCompleteCallback = onCompleteCallback;
|
||||
}
|
||||
|
||||
protected GPTResponse deserializePayload(String jsonPayload) throws JsonProcessingException {
|
||||
var response = new ObjectMapper().readValue(jsonPayload, GPTResponse.class);
|
||||
messageBuilder.append(response.getChoices().get(0).getText());
|
||||
return response;
|
||||
}
|
||||
|
||||
protected void onRequestComplete() {
|
||||
onCompleteCallback.accept(messageBuilder.toString());
|
||||
}
|
||||
|
||||
protected void onErrorOccurred() {
|
||||
responseConsumer.accept("Something went wrong. Please try again later.");
|
||||
}
|
||||
|
||||
protected void send(String responsePayload, String token) {
|
||||
try {
|
||||
if (!responsePayload.isEmpty()) {
|
||||
var response = new ObjectMapper().readValue(responsePayload, GPTResponse.class);
|
||||
var message = response.getChoices().get(0).getText();
|
||||
messageBuilder.append(message);
|
||||
this.responseConsumer.accept(message);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@ package ee.carlrobert.chatgpt.client.gpt;
|
|||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import ee.carlrobert.chatgpt.EmptyCallback;
|
||||
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
|
||||
import ee.carlrobert.chatgpt.client.BaseModel;
|
||||
import ee.carlrobert.chatgpt.client.Client;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodySubscriber;
|
||||
import java.net.http.HttpResponse.ResponseInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -51,26 +50,40 @@ public class GPTClient extends Client {
|
|||
}
|
||||
|
||||
protected BodySubscriber<Void> subscribe(
|
||||
HttpResponse.ResponseInfo responseInfo,
|
||||
ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete) {
|
||||
Runnable onComplete) {
|
||||
if (responseInfo.statusCode() == 200) {
|
||||
return new GPTBodySubscriber(
|
||||
response -> onMessageReceived.accept(response.getChoices().get(0).getText()),
|
||||
onMessageReceived,
|
||||
finalMsg -> {
|
||||
queries.add(Map.entry(super.userPrompt, finalMsg));
|
||||
onComplete.call();
|
||||
onComplete.run();
|
||||
});
|
||||
} else if (responseInfo.statusCode() == 401) {
|
||||
onMessageReceived.accept("Incorrect API key provided.\n" +
|
||||
"You can find your API key at https://platform.openai.com/account/api-keys.");
|
||||
throw new IllegalArgumentException();
|
||||
} else if (responseInfo.statusCode() == 429) {
|
||||
onMessageReceived.accept("You exceeded your current quota, please check your plan and billing details.");
|
||||
throw new RuntimeException("Insufficient quota");
|
||||
} else {
|
||||
onMessageReceived.accept("Something went wrong. Please try again later.");
|
||||
throw new RuntimeException();
|
||||
handleError(responseInfo, onMessageReceived, onComplete);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(
|
||||
ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
Runnable onComplete) {
|
||||
try {
|
||||
if (responseInfo.statusCode() == 401) {
|
||||
onMessageReceived.accept("Incorrect API key provided.\n" +
|
||||
"You can find your API key at https://platform.openai.com/account/api-keys.");
|
||||
throw new IllegalArgumentException();
|
||||
} else if (responseInfo.statusCode() == 429) {
|
||||
onMessageReceived.accept("You exceeded your current quota, please check your plan and billing details.");
|
||||
throw new RuntimeException("Insufficient quota");
|
||||
} else {
|
||||
onMessageReceived.accept("Something went wrong. Please try again later.");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
} finally {
|
||||
onComplete.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ public class SettingsComponent {
|
|||
.addComponent(createFirstSelectionForm())
|
||||
.addVerticalGap(8)
|
||||
.addComponent(UI.PanelFactory.panel(useChatGPTRadioButton)
|
||||
.withComment("Slow and free, more suitable for conversational tasks")
|
||||
.withComment("Slow and free, more suitable for conversational tasks, rate-limited")
|
||||
.createPanel())
|
||||
.addComponent(createSecondSelectionForm())
|
||||
.getPanel();
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ import com.intellij.openapi.project.Project;
|
|||
import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import com.intellij.openapi.wm.ToolWindowManager;
|
||||
import ee.carlrobert.chatgpt.EmptyCallback;
|
||||
import ee.carlrobert.chatgpt.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.client.chatgpt.ChatGPTClient;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsConfigurable;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.components.SyntaxTextArea;
|
||||
|
|
@ -35,9 +33,9 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
public class ToolWindowService implements LafManagerListener {
|
||||
|
||||
private ScrollablePanel scrollablePanel;
|
||||
private boolean isLandingViewVisible;
|
||||
private static final List<SyntaxTextArea> textAreas = new ArrayList<>();
|
||||
private boolean isLandingViewVisible;
|
||||
private ScrollablePanel scrollablePanel;
|
||||
|
||||
public void setScrollablePanel(ScrollablePanel scrollablePanel) {
|
||||
this.scrollablePanel = scrollablePanel;
|
||||
|
|
@ -56,7 +54,7 @@ public class ToolWindowService implements LafManagerListener {
|
|||
scrollablePanel.add(createTextArea(userMessage, true));
|
||||
}
|
||||
|
||||
public void sendMessage(String prompt, Project project, @Nullable EmptyCallback scrollToBottom) {
|
||||
public void sendMessage(String prompt, Project project, @Nullable Runnable scrollToBottom) {
|
||||
addSpacing(16);
|
||||
addIconLabel(Icons.DefaultImageIcon, "ChatGPT:");
|
||||
addSpacing(8);
|
||||
|
|
@ -73,16 +71,13 @@ public class ToolWindowService implements LafManagerListener {
|
|||
|
||||
var client = new ClientFactory().getClient();
|
||||
client.getCompletionsAsync(prompt, message -> {
|
||||
if (client instanceof ChatGPTClient) {
|
||||
textArea.setText(message);
|
||||
} else {
|
||||
textArea.append(message);
|
||||
}
|
||||
textArea.append(message);
|
||||
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.call();
|
||||
scrollToBottom.run();
|
||||
}
|
||||
}, () -> {
|
||||
textArea.getCaret().setVisible(false);
|
||||
textArea.displayCopyButton();
|
||||
textArea.enableSelection();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,15 +10,19 @@ import java.awt.datatransfer.Clipboard;
|
|||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.IOException;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.fife.ui.rsyntaxtextarea.Theme;
|
||||
import org.fife.ui.rtextarea.CaretStyle;
|
||||
|
||||
public class SyntaxTextArea extends RSyntaxTextArea {
|
||||
|
||||
public SyntaxTextArea() {
|
||||
super("");
|
||||
setStyles();
|
||||
addDocumentListener();
|
||||
}
|
||||
|
||||
public void displayCopyButton() {
|
||||
|
|
@ -50,6 +54,8 @@ public class SyntaxTextArea extends RSyntaxTextArea {
|
|||
setPaintTabLines(false);
|
||||
setHighlightCurrentLine(false);
|
||||
setLineWrap(true);
|
||||
setCaretStyle(0, CaretStyle.BLOCK_STYLE);
|
||||
getCaret().setVisible(true);
|
||||
setWrapStyleWord(true);
|
||||
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
|
||||
changeStyleViaThemeXml();
|
||||
|
|
@ -69,4 +75,23 @@ public class SyntaxTextArea extends RSyntaxTextArea {
|
|||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
private void addDocumentListener() {
|
||||
getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
setCaretPosition(getText().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
setCaretPosition(getText().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
setCaretPosition(getText().length());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.createIconButt
|
|||
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.chatgpt.EmptyCallback;
|
||||
import icons.Icons;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
|
@ -18,7 +17,7 @@ import javax.swing.KeyStroke;
|
|||
|
||||
public class TextArea extends JTextArea {
|
||||
|
||||
public TextArea(EmptyCallback onSubmit, JScrollPane textAreaScrollPane) {
|
||||
public TextArea(Runnable onSubmit, JScrollPane textAreaScrollPane) {
|
||||
setForeground(JBColor.GRAY);
|
||||
setMargin(JBUI.insets(5));
|
||||
addFocusListener(getFocusListener());
|
||||
|
|
@ -33,13 +32,13 @@ public class TextArea extends JTextArea {
|
|||
var actions = getActionMap();
|
||||
actions.put("text-submit", new AbstractAction() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
onSubmit.call();
|
||||
onSubmit.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addSubmitButton(EmptyCallback onSubmit, JScrollPane textAreaScrollPane) {
|
||||
var button = createSubmitButton(e -> onSubmit.call());
|
||||
private void addSubmitButton(Runnable onSubmit, JScrollPane textAreaScrollPane) {
|
||||
var button = createSubmitButton(e -> onSubmit.run());
|
||||
ComponentBorder cb = new ComponentBorder(button);
|
||||
cb.setAdjustInsets(true);
|
||||
cb.install(textAreaScrollPane);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
<change-notes>
|
||||
<![CDATA[
|
||||
<ul>
|
||||
<li><b>1.1.3</b> Improve error handling, add block caret</li>
|
||||
<li><b>1.1.2</b> Add option to select other language models</li>
|
||||
<li><b>1.1.1</b> Remove startup notification</li>
|
||||
<li><b>1.1.0</b> Add reverse proxy support, fix text selection and copy functionality</li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue