mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-05 23:42:18 +00:00
This commit is contained in:
parent
841950d153
commit
07c2f6a0d7
31 changed files with 812 additions and 285 deletions
|
|
@ -1,116 +0,0 @@
|
|||
package ee.carlrobert.chatgpt.client;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse.BodySubscriber;
|
||||
import java.net.http.HttpResponse.ResponseInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class ApiClient {
|
||||
|
||||
private static final List<Map.Entry<String, String>> queries = new ArrayList<>(); // TODO
|
||||
private static ApiClient instance;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
|
||||
|
||||
private ApiClient() {
|
||||
}
|
||||
|
||||
public static ApiClient getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ApiClient();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void getCompletionsAsync(String userPrompt, Consumer<String> onMessage, @Nullable Consumer<String> onComplete) {
|
||||
var prompt = buildCompletePrompt(userPrompt);
|
||||
this.client.sendAsync(buildHttpRequest(prompt), respInfo -> subscribe(respInfo, userPrompt, onMessage, onComplete));
|
||||
}
|
||||
|
||||
public void clearQueries() {
|
||||
queries.clear();
|
||||
}
|
||||
|
||||
private String buildCompletePrompt(String prompt) {
|
||||
var basePrompt = new StringBuilder("""
|
||||
You are ChatGPT, a large language model trained by OpenAI.
|
||||
One of your main goals is code generation but not only.
|
||||
Answer in a markdown language. Markdown code blocks should contain language whenever possible.
|
||||
""");
|
||||
queries.forEach(query ->
|
||||
basePrompt.append("User:\n")
|
||||
.append(query.getKey())
|
||||
.append("<|im_end|>\n")
|
||||
.append("\n")
|
||||
.append("ChatGPT:\n")
|
||||
.append(query.getValue())
|
||||
.append("<|im_end|>\n")
|
||||
.append("\n"));
|
||||
basePrompt.append("User:\n")
|
||||
.append(prompt)
|
||||
.append("<|im_end|>\n")
|
||||
.append("\n")
|
||||
.append("ChatGPT:\n");
|
||||
return basePrompt.toString();
|
||||
}
|
||||
|
||||
private HttpRequest buildHttpRequest(String prompt) {
|
||||
try {
|
||||
return HttpRequest.newBuilder()
|
||||
.uri(URI.create("https://api.openai.com/v1/completions"))
|
||||
.header("Accept", "text/event-stream")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + SettingsState.getInstance().secretKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(objectMapper
|
||||
.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(Map.of(
|
||||
"model", "text-davinci-003",
|
||||
"stop", List.of("<|im_end|>"),
|
||||
"prompt", prompt,
|
||||
"max_tokens", 1000,
|
||||
"temperature", 1.0,
|
||||
"stream", true
|
||||
))))
|
||||
.build();
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Unable to serialize request payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
private BodySubscriber<Void> subscribe(
|
||||
ResponseInfo responseInfo,
|
||||
String userPrompt,
|
||||
Consumer<String> onMessage,
|
||||
@Nullable Consumer<String> onComplete) {
|
||||
if (responseInfo.statusCode() == 200) {
|
||||
return new Subscriber((messageData ->
|
||||
onMessage.accept(messageData.choices().get(0).text())),
|
||||
(finalMsg) -> {
|
||||
queries.add(Map.entry(userPrompt, finalMsg));
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(finalMsg);
|
||||
}
|
||||
});
|
||||
} else if (responseInfo.statusCode() == 401) {
|
||||
onMessage.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) {
|
||||
onMessage.accept("You exceeded your current quota, please check your plan and billing details.");
|
||||
throw new RuntimeException("Insufficient quota");
|
||||
} else {
|
||||
onMessage.accept("Something went wrong. Please try again later.");
|
||||
clearQueries();
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package ee.carlrobert.chatgpt.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ApiRequestDetails {
|
||||
|
||||
private final String url;
|
||||
private final Map<String, Object> body;
|
||||
private final String token;
|
||||
|
||||
public ApiRequestDetails(String url, Map<String, Object> body, String token) {
|
||||
this.url = url;
|
||||
this.body = body;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Map<String, Object> getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package ee.carlrobert.chatgpt.client;
|
||||
|
||||
public interface ApiResponse {
|
||||
}
|
||||
55
src/main/java/ee/carlrobert/chatgpt/client/Client.java
Normal file
55
src/main/java/ee/carlrobert/chatgpt/client/Client.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
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;
|
||||
import java.net.http.HttpResponse.BodySubscriber;
|
||||
import java.net.http.HttpResponse.ResponseInfo;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class Client {
|
||||
|
||||
private final HttpClient client =
|
||||
HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
protected String userPrompt = "";
|
||||
|
||||
protected abstract ApiRequestDetails getRequestDetails(String prompt);
|
||||
|
||||
public abstract void clearPreviousSession();
|
||||
|
||||
protected abstract BodySubscriber<Void> subscribe(
|
||||
ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete);
|
||||
|
||||
public void getCompletionsAsync(
|
||||
String prompt,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete) {
|
||||
this.userPrompt = prompt;
|
||||
this.client.sendAsync(
|
||||
buildHttpRequest(prompt),
|
||||
responseInfo -> subscribe(responseInfo, onMessageReceived, onComplete));
|
||||
}
|
||||
|
||||
private HttpRequest buildHttpRequest(String prompt) {
|
||||
var requestDetails = getRequestDetails(prompt);
|
||||
try {
|
||||
return HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestDetails.getUrl()))
|
||||
.header("Accept", "text/event-stream")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + requestDetails.getToken())
|
||||
.POST(HttpRequest.BodyPublishers.ofString(objectMapper
|
||||
.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(requestDetails.getBody())))
|
||||
.build();
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Unable to serialize request payload", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ee.carlrobert.chatgpt.client;
|
||||
|
||||
import ee.carlrobert.chatgpt.client.chatgpt.ChatGPTClient;
|
||||
import ee.carlrobert.chatgpt.client.gpt.GPTClient;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
|
||||
public class ClientFactory {
|
||||
|
||||
public Client getClient() {
|
||||
if (SettingsState.getInstance().isGPTOptionSelected) {
|
||||
return GPTClient.getInstance();
|
||||
}
|
||||
return ChatGPTClient.getInstance();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,8 @@ package ee.carlrobert.chatgpt.client;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ee.carlrobert.chatgpt.client.response.ApiResponse;
|
||||
import java.net.http.HttpResponse;
|
||||
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;
|
||||
|
|
@ -13,41 +12,29 @@ import java.util.concurrent.Flow.Subscription;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Subscriber implements HttpResponse.BodySubscriber<Void> {
|
||||
public abstract class Subscriber<T extends ApiResponse> implements BodySubscriber<Void> {
|
||||
|
||||
private static final Pattern dataLinePattern = Pattern.compile("^data: ?(.*)$");
|
||||
protected final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
private final Consumer<T> responseConsumer;
|
||||
private volatile Subscription subscription;
|
||||
private volatile String deferredText;
|
||||
private final Consumer<? super ApiResponse> messageDataConsumer;
|
||||
private final CompletableFuture<Void> future;
|
||||
private final Consumer<String> onComplete;
|
||||
private final StringBuilder msgBuilder = new StringBuilder();
|
||||
|
||||
public Subscriber(Consumer<? super ApiResponse> messageDataConsumer, Consumer<String> onComplete) {
|
||||
this.messageDataConsumer = messageDataConsumer;
|
||||
this.future = new CompletableFuture<>();
|
||||
this.subscription = null;
|
||||
this.deferredText = null;
|
||||
this.onComplete = onComplete;
|
||||
protected abstract T deserializePayload(String jsonPayload) throws JsonProcessingException;
|
||||
|
||||
protected abstract void onRequestComplete();
|
||||
|
||||
// Overridden from concrete class
|
||||
protected void processRegularResponse(String response) {
|
||||
}
|
||||
|
||||
protected static ApiResponse extractMessageData(String[] messageLines) {
|
||||
var responseBuilder = new StringBuilder();
|
||||
for (var line : messageLines) {
|
||||
var matcher = dataLinePattern.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
responseBuilder.append(matcher.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return new ObjectMapper().readValue(responseBuilder.toString(), ApiResponse.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Couldn't read the payload", e);
|
||||
}
|
||||
public Subscriber(Consumer<T> responseConsumer) {
|
||||
this.responseConsumer = responseConsumer;
|
||||
}
|
||||
|
||||
public CompletionStage<Void> getBody() {
|
||||
return this.future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription subscription) {
|
||||
this.subscription = subscription;
|
||||
try {
|
||||
|
|
@ -59,24 +46,27 @@ public class Subscriber implements HttpResponse.BodySubscriber<Void> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(List<ByteBuffer> buffers) {
|
||||
try {
|
||||
var deferredText = this.deferredText;
|
||||
|
||||
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 response = extractMessageData(tokens[i].split("\n"));
|
||||
var choice = response.choices().get(0);
|
||||
if ("stop".equals(choice.finishReason())) {
|
||||
onComplete();
|
||||
var responsePayload = extractPayload(tokens[i].split("\n"));
|
||||
if ("[DONE]".equals(responsePayload)) {
|
||||
future.complete(null);
|
||||
} else {
|
||||
msgBuilder.append(choice.text());
|
||||
try {
|
||||
this.responseConsumer.accept(deserializePayload(responsePayload));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Couldn't deserialize the payload", e);
|
||||
}
|
||||
}
|
||||
this.messageDataConsumer.accept(response);
|
||||
}
|
||||
deferredText = tokens[tokens.length - 1];
|
||||
}
|
||||
|
|
@ -89,23 +79,28 @@ public class Subscriber implements HttpResponse.BodySubscriber<Void> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
this.future.completeExceptionally(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
try {
|
||||
this.future.complete(null);
|
||||
this.onComplete.accept(msgBuilder.toString());
|
||||
onRequestComplete();
|
||||
} catch (Exception e) {
|
||||
this.future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> getBody() {
|
||||
return this.future;
|
||||
private String extractPayload(String[] payload) {
|
||||
Pattern dataLinePattern = Pattern.compile("^data: ?(.*)$");
|
||||
var responseBuilder = new StringBuilder();
|
||||
for (var line : payload) {
|
||||
var matcher = dataLinePattern.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
responseBuilder.append(matcher.group(1));
|
||||
}
|
||||
}
|
||||
return responseBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt;
|
||||
|
||||
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.ChatGPTResponseError;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
|
||||
|
||||
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);
|
||||
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 processRegularResponse(String jsonPayload) {
|
||||
try {
|
||||
onErrorCallback.accept(objectMapper.readValue(jsonPayload, ChatGPTResponseError.class));
|
||||
future.complete(null);
|
||||
} catch (JsonProcessingException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
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.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ChatGPTClient extends Client {
|
||||
|
||||
private static ChatGPTClient instance;
|
||||
private static ChatGPTResponse lastReceivedResponse;
|
||||
|
||||
|
||||
private ChatGPTClient() {
|
||||
}
|
||||
|
||||
public static ChatGPTClient getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ChatGPTClient();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void clearPreviousSession() {
|
||||
lastReceivedResponse = null;
|
||||
}
|
||||
|
||||
protected ApiRequestDetails getRequestDetails(String prompt) {
|
||||
var settings = SettingsState.getInstance();
|
||||
var payload = new HashMap<>(Map.of(
|
||||
"action", "next",
|
||||
"messages", List.of(Map.of(
|
||||
"id", UUID.randomUUID().toString(),
|
||||
"role", "user",
|
||||
"author", Map.of("role", "user"),
|
||||
"content", Map.of(
|
||||
"content_type", "text",
|
||||
"parts", List.of(prompt)
|
||||
)
|
||||
)),
|
||||
"model", "text-davinci-002-render-sha"
|
||||
));
|
||||
|
||||
if (lastReceivedResponse != null) {
|
||||
payload.put("conversation_id", lastReceivedResponse.getConversationId());
|
||||
payload.put("parent_message_id", lastReceivedResponse.getMessage().getId());
|
||||
} else {
|
||||
payload.put("parent_message_id", UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
return new ApiRequestDetails(
|
||||
settings.reverseProxyUrl,
|
||||
payload,
|
||||
settings.accessToken);
|
||||
}
|
||||
|
||||
protected HttpResponse.BodySubscriber<Void> subscribe(
|
||||
HttpResponse.ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete) {
|
||||
if (responseInfo.statusCode() == 200) {
|
||||
return new ChatGPTBodySubscriber((
|
||||
response -> onMessageReceived.accept(String.join("", response.getMessage().getContent().getParts()))),
|
||||
response -> {
|
||||
lastReceivedResponse = response;
|
||||
onComplete.call();
|
||||
},
|
||||
error -> {
|
||||
if ("invalid_api_key".equals(error.getDetail().getCode())) {
|
||||
onMessageReceived.accept(error.getDetail().getMessage());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
onMessageReceived.accept("Something went wrong. Please try again later.");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import ee.carlrobert.chatgpt.client.ApiResponse;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponse implements ApiResponse {
|
||||
|
||||
private ChatGPTResponseMessage message;
|
||||
@JsonProperty("conversation_id")
|
||||
private String conversationId;
|
||||
|
||||
public ChatGPTResponseMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(ChatGPTResponseMessage message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getConversationId() {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
public void setConversationId(String conversationId) {
|
||||
this.conversationId = conversationId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponseError {
|
||||
|
||||
private ChatGPTResponseErrorDetails detail;
|
||||
|
||||
public ChatGPTResponseErrorDetails getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(ChatGPTResponseErrorDetails detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponseErrorDetails {
|
||||
|
||||
private String message;
|
||||
private String type;
|
||||
private String code;
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponseMessage {
|
||||
|
||||
private String id;
|
||||
private ChatGPTResponseMessageContent content;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ChatGPTResponseMessageContent getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(ChatGPTResponseMessageContent content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package ee.carlrobert.chatgpt.client.chatgpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ChatGPTResponseMessageContent {
|
||||
|
||||
@JsonProperty("content_type")
|
||||
private String contentType;
|
||||
private List<String> parts;
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public List<String> getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
public void setParts(List<String> parts) {
|
||||
this.parts = parts;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ee.carlrobert.chatgpt.client.gpt;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ee.carlrobert.chatgpt.client.Subscriber;
|
||||
import ee.carlrobert.chatgpt.client.gpt.response.GPTResponse;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class GPTBodySubscriber extends Subscriber<GPTResponse> {
|
||||
|
||||
private final Consumer<String> onCompleteCallback;
|
||||
private final StringBuilder messageBuilder = new StringBuilder();
|
||||
|
||||
public GPTBodySubscriber(
|
||||
Consumer<GPTResponse> responseConsumer,
|
||||
Consumer<String> onCompleteCallback) {
|
||||
super(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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package ee.carlrobert.chatgpt.client.gpt;
|
||||
|
||||
import ee.carlrobert.chatgpt.EmptyCallback;
|
||||
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class GPTClient extends Client {
|
||||
|
||||
private static final List<Map.Entry<String, String>> queries = new ArrayList<>();
|
||||
private static GPTClient instance;
|
||||
|
||||
private GPTClient() {
|
||||
}
|
||||
|
||||
public static GPTClient getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new GPTClient();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void clearPreviousSession() {
|
||||
queries.clear();
|
||||
}
|
||||
|
||||
protected ApiRequestDetails getRequestDetails(String prompt) {
|
||||
return new ApiRequestDetails(
|
||||
"https://api.openai.com/v1/completions",
|
||||
Map.of(
|
||||
"model", "text-davinci-003",
|
||||
"stop", List.of("<|im_end|>"),
|
||||
"prompt", buildPrompt(prompt),
|
||||
"max_tokens", 1000,
|
||||
"temperature", 1.0,
|
||||
"stream", true
|
||||
),
|
||||
SettingsState.getInstance().apiKey);
|
||||
}
|
||||
|
||||
protected BodySubscriber<Void> subscribe(
|
||||
HttpResponse.ResponseInfo responseInfo,
|
||||
Consumer<String> onMessageReceived,
|
||||
EmptyCallback onComplete) {
|
||||
if (responseInfo.statusCode() == 200) {
|
||||
return new GPTBodySubscriber(
|
||||
response -> onMessageReceived.accept(response.getChoices().get(0).getText()),
|
||||
finalMsg -> {
|
||||
queries.add(Map.entry(super.userPrompt, finalMsg));
|
||||
onComplete.call();
|
||||
});
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPrompt(String prompt) {
|
||||
var basePrompt = new StringBuilder("""
|
||||
You are ChatGPT, a large language model trained by OpenAI.
|
||||
One of your main goals is code generation but not only.
|
||||
Answer in a markdown language. Markdown code blocks should contain language whenever possible.
|
||||
""");
|
||||
queries.forEach(query ->
|
||||
basePrompt.append("User:\n")
|
||||
.append(query.getKey())
|
||||
.append("<|im_end|>\n")
|
||||
.append("\n")
|
||||
.append("ChatGPT:\n")
|
||||
.append(query.getValue())
|
||||
.append("<|im_end|>\n")
|
||||
.append("\n"));
|
||||
basePrompt.append("User:\n")
|
||||
.append(prompt)
|
||||
.append("<|im_end|>\n")
|
||||
.append("\n")
|
||||
.append("ChatGPT:\n");
|
||||
return basePrompt.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package ee.carlrobert.chatgpt.client.gpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import ee.carlrobert.chatgpt.client.ApiResponse;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GPTResponse implements ApiResponse {
|
||||
|
||||
private String id;
|
||||
private List<GPTResponseChoice> choices;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<GPTResponseChoice> getChoices() {
|
||||
return choices;
|
||||
}
|
||||
|
||||
public void setChoices(List<GPTResponseChoice> choices) {
|
||||
this.choices = choices;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package ee.carlrobert.chatgpt.client.gpt.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GPTResponseChoice {
|
||||
|
||||
private String text;
|
||||
@JsonProperty("finish_reason")
|
||||
private String finishReason;
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getFinishReason() {
|
||||
return finishReason;
|
||||
}
|
||||
|
||||
public void setFinishReason(String finishReason) {
|
||||
this.finishReason = finishReason;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package ee.carlrobert.chatgpt.client.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record ApiError(String message, String type) {}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package ee.carlrobert.chatgpt.client.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ApiResponse(
|
||||
String id,
|
||||
String object,
|
||||
long created,
|
||||
String model,
|
||||
List<ApiResponseChoice> choices,
|
||||
ApiResponseUsage usage,
|
||||
ApiError error) {}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package ee.carlrobert.chatgpt.client.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record ApiResponseChoice(
|
||||
String text,
|
||||
int index,
|
||||
Object logprobs,
|
||||
@JsonProperty("finish_reason") String finishReason) {}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package ee.carlrobert.chatgpt.client.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record ApiResponseUsage(
|
||||
@JsonProperty("prompt_tokens") int promptTokens,
|
||||
@JsonProperty("completion_tokens") int completionTokens,
|
||||
@JsonProperty("total_tokens") int totalTokens) {}
|
||||
|
|
@ -17,7 +17,7 @@ public class ActionGroup extends DefaultActionGroup {
|
|||
Project project = event.getProject();
|
||||
boolean menuAllowed = false;
|
||||
if (editor != null && project != null) {
|
||||
var secretKey = SettingsState.getInstance().secretKey;
|
||||
var secretKey = SettingsState.getInstance().apiKey;
|
||||
menuAllowed = secretKey != null && !secretKey.isEmpty() && editor.getSelectionModel().getSelectedText() != null;
|
||||
}
|
||||
event.getPresentation().setEnabled(menuAllowed);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import ee.carlrobert.chatgpt.client.ApiClient;
|
||||
import ee.carlrobert.chatgpt.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
|
@ -18,13 +18,13 @@ public class AskAction extends AnAction {
|
|||
public void actionPerformed(@NotNull AnActionEvent event) {
|
||||
var project = event.getProject();
|
||||
if (project != null) {
|
||||
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
|
||||
var toolWindow = toolWindowService.getToolWindow(project);
|
||||
ApiClient.getInstance().clearQueries();
|
||||
toolWindow.show();
|
||||
toolWindow.setTitle("");
|
||||
toolWindowService.removeAll();
|
||||
toolWindowService.paintLandingView();
|
||||
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
|
||||
var toolWindow = toolWindowService.getToolWindow(project);
|
||||
new ClientFactory().getClient().clearPreviousSession();
|
||||
toolWindow.show();
|
||||
toolWindow.setTitle("");
|
||||
toolWindowService.removeAll();
|
||||
toolWindowService.paintLandingView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
|
|||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import ee.carlrobert.chatgpt.client.ApiClient;
|
||||
import ee.carlrobert.chatgpt.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ public abstract class BaseAction extends AnAction {
|
|||
if (editor != null && project != null) {
|
||||
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
|
||||
var selectedText = editor.getSelectionModel().getSelectedText();
|
||||
ApiClient.getInstance().clearQueries();
|
||||
new ClientFactory().getClient().clearPreviousSession();
|
||||
initToolWindow(toolWindowService.getToolWindow(project));
|
||||
toolWindowService.removeAll();
|
||||
toolWindowService.paintUserMessage(selectedText);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ public class ShowNotificationActivity implements StartupActivity {
|
|||
@Override
|
||||
public void runActivity(@NotNull Project project) {
|
||||
var notificationService = ApplicationManager.getApplication().getService(NotificationService.class);
|
||||
var secretKey = SettingsState.getInstance().secretKey;
|
||||
if (secretKey == null || secretKey.isEmpty()) {
|
||||
var apiKey = SettingsState.getInstance().apiKey;
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
notificationService.createAndNotify(project);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,60 @@
|
|||
package ee.carlrobert.chatgpt.ide.settings;
|
||||
|
||||
import com.intellij.ui.TitledSeparator;
|
||||
import com.intellij.ui.components.JBRadioButton;
|
||||
import com.intellij.ui.components.JBTextField;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UI;
|
||||
import java.awt.Desktop;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import javax.swing.ButtonGroup;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SettingsComponent {
|
||||
|
||||
private final JPanel myMainPanel;
|
||||
private final JBTextField apiKeyField = new JBTextField();
|
||||
private final JPanel mainPanel;
|
||||
private final JBTextField apiKeyField;
|
||||
private final JComboBox<String> reverseProxyComboBox;
|
||||
private final JBTextField accessTokenField;
|
||||
private final JBRadioButton useGPTRadioButton;
|
||||
private final JBRadioButton useChatGPTRadioButton;
|
||||
|
||||
public SettingsComponent() {
|
||||
myMainPanel = FormBuilder.createFormBuilder()
|
||||
.addComponent(UI.PanelFactory.panel(apiKeyField)
|
||||
.withLabel("API key:")
|
||||
.withComment("You can find your Secret API key in your <a href=\"https://platform.openai.com/account/api-keys\">User settings</a>.")
|
||||
.withCommentHyperlinkListener(event -> {
|
||||
if (HyperlinkEvent.EventType.ACTIVATED.equals(event.getEventType())) {
|
||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(event.getURL().toURI());
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new RuntimeException("Couldn't open the browser.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.createPanel())
|
||||
public SettingsComponent(SettingsState settings) {
|
||||
apiKeyField = new JBTextField(settings.apiKey);
|
||||
reverseProxyComboBox = new JComboBox<>(new String[] {
|
||||
"https://chat.duti.tech/api/conversation",
|
||||
"https://gpt.pawan.krd/backend-api/conversation"
|
||||
});
|
||||
accessTokenField = new JBTextField(settings.accessToken, 1);
|
||||
useGPTRadioButton = new JBRadioButton("Use OpenAI's official GPT3 API", settings.isGPTOptionSelected);
|
||||
useChatGPTRadioButton = new JBRadioButton("Use ChatGPT's unofficial backend API", settings.isChatGPTOptionSelected);
|
||||
mainPanel = FormBuilder.createFormBuilder()
|
||||
.addComponent(new TitledSeparator("Integration Preference"))
|
||||
.addComponent(createMainSelectionForm())
|
||||
.addComponentFillVertically(new JPanel(), 0)
|
||||
.getPanel();
|
||||
|
||||
if (settings.isGPTOptionSelected) {
|
||||
reverseProxyComboBox.setEnabled(false);
|
||||
accessTokenField.setEnabled(false);
|
||||
} else {
|
||||
apiKeyField.setEnabled(false);
|
||||
reverseProxyComboBox.setEnabled(true);
|
||||
accessTokenField.setEnabled(true);
|
||||
}
|
||||
|
||||
registerButtons();
|
||||
}
|
||||
|
||||
public JPanel getPanel() {
|
||||
return myMainPanel;
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
|
|
@ -46,11 +62,126 @@ public class SettingsComponent {
|
|||
}
|
||||
|
||||
@NotNull
|
||||
public String getApiKeyField() {
|
||||
public String getApiKey() {
|
||||
return apiKeyField.getText();
|
||||
}
|
||||
|
||||
public void setApiKeyField(@NotNull String apiKey) {
|
||||
public void setApiKey(@NotNull String apiKey) {
|
||||
apiKeyField.setText(apiKey);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getAccessToken() {
|
||||
return accessTokenField.getText();
|
||||
}
|
||||
|
||||
public void setAccessToken(@NotNull String accessToken) {
|
||||
accessTokenField.setText(accessToken);
|
||||
}
|
||||
|
||||
public boolean isGPTOptionSelected() {
|
||||
return useGPTRadioButton.isSelected();
|
||||
}
|
||||
|
||||
public void setUseGPTOptionSelected(boolean isSelected) {
|
||||
useGPTRadioButton.setSelected(isSelected);
|
||||
}
|
||||
|
||||
public boolean isChatGPTOptionSelected() {
|
||||
return useChatGPTRadioButton.isSelected();
|
||||
}
|
||||
|
||||
public void setUseChatGPTOptionSelected(boolean isSelected) {
|
||||
useChatGPTRadioButton.setSelected(isSelected);
|
||||
}
|
||||
|
||||
public String getReverseProxyUrl() {
|
||||
return (String) reverseProxyComboBox.getSelectedItem();
|
||||
}
|
||||
|
||||
public void setReverseProxyUrl(String reverseProxyUrl) {
|
||||
reverseProxyComboBox.setSelectedItem(reverseProxyUrl);
|
||||
}
|
||||
|
||||
private JPanel createMainSelectionForm() {
|
||||
var panel = FormBuilder.createFormBuilder()
|
||||
.addVerticalGap(8)
|
||||
.addComponent(UI.PanelFactory.panel(useGPTRadioButton)
|
||||
.withComment("Fast and robust, requires API key")
|
||||
.createPanel())
|
||||
.addComponent(createFirstSelectionForm())
|
||||
.addVerticalGap(8)
|
||||
.addComponent(UI.PanelFactory.panel(useChatGPTRadioButton)
|
||||
.withComment("Slow and free, more suitable for conversational tasks")
|
||||
.createPanel())
|
||||
.addComponent(createSecondSelectionForm())
|
||||
.getPanel();
|
||||
panel.setBorder(JBUI.Borders.emptyLeft(16));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createFirstSelectionForm() {
|
||||
var panel = FormBuilder.createFormBuilder()
|
||||
.addComponent(UI.PanelFactory.panel(apiKeyField)
|
||||
.withLabel("API key:")
|
||||
.withComment("You can find your Secret API key in your <a href=\"https://platform.openai.com/account/api-keys\">User settings</a>.")
|
||||
.withCommentHyperlinkListener(this::handleHyperlinkClicked)
|
||||
.createPanel())
|
||||
.getPanel();
|
||||
panel.setBorder(JBUI.Borders.emptyLeft(24));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createSecondSelectionForm() {
|
||||
var reverseProxyUrlPanel = FormBuilder.createFormBuilder()
|
||||
.addLabeledComponent("Reverse proxy url:", reverseProxyComboBox)
|
||||
.getPanel();
|
||||
|
||||
var accessTokenPanel = UI.PanelFactory.panel(accessTokenField)
|
||||
.withLabel("Access token:")
|
||||
.withComment(
|
||||
"Access token can be obtained from <a href=\"https://chat.openai.com/api/auth/session\">https://chat.openai.com/api/auth/session</a> and is valid for ~8h.")
|
||||
.withCommentHyperlinkListener(this::handleHyperlinkClicked)
|
||||
.createPanel();
|
||||
|
||||
var accessTokenLabel = accessTokenPanel.getComponents()[0];
|
||||
var reverseProxyUrlLabel = reverseProxyUrlPanel.getComponents()[0];
|
||||
if (accessTokenLabel instanceof JLabel && reverseProxyUrlLabel instanceof JLabel) {
|
||||
accessTokenLabel.setPreferredSize(reverseProxyUrlLabel.getPreferredSize());
|
||||
}
|
||||
|
||||
var panel = FormBuilder.createFormBuilder()
|
||||
.addComponent(reverseProxyUrlPanel)
|
||||
.addVerticalGap(8)
|
||||
.addComponent(accessTokenPanel)
|
||||
.getPanel();
|
||||
panel.setBorder(JBUI.Borders.emptyLeft(24));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void registerButtons() {
|
||||
ButtonGroup myButtonGroup = new ButtonGroup();
|
||||
myButtonGroup.add(useGPTRadioButton);
|
||||
myButtonGroup.add(useChatGPTRadioButton);
|
||||
useGPTRadioButton.addActionListener(e -> handleRadioOptionChange(false));
|
||||
useChatGPTRadioButton.addActionListener(e -> handleRadioOptionChange(true));
|
||||
}
|
||||
|
||||
private void handleRadioOptionChange(boolean isUseChatGPTOption) {
|
||||
apiKeyField.setEnabled(!isUseChatGPTOption);
|
||||
accessTokenField.setEnabled(isUseChatGPTOption);
|
||||
reverseProxyComboBox.setEnabled(isUseChatGPTOption);
|
||||
}
|
||||
|
||||
private void handleHyperlinkClicked(HyperlinkEvent event) {
|
||||
if (HyperlinkEvent.EventType.ACTIVATED.equals(event.getEventType())) {
|
||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(event.getURL().toURI());
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new RuntimeException("Couldn't open the browser.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package ee.carlrobert.chatgpt.ide.settings;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.options.Configurable;
|
||||
import ee.carlrobert.chatgpt.ide.notification.NotificationService;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
|
@ -25,29 +23,39 @@ public class SettingsConfigurable implements Configurable {
|
|||
@Nullable
|
||||
@Override
|
||||
public JComponent createComponent() {
|
||||
settingsComponent = new SettingsComponent();
|
||||
var settings = SettingsState.getInstance();
|
||||
settingsComponent = new SettingsComponent(settings);
|
||||
return settingsComponent.getPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
var settings = SettingsState.getInstance();
|
||||
return !settingsComponent.getApiKeyField().equals(settings.secretKey);
|
||||
return !settingsComponent.getApiKey().equals(settings.apiKey) ||
|
||||
!settingsComponent.getAccessToken().equals(settings.accessToken) ||
|
||||
!settingsComponent.getReverseProxyUrl().equals(settings.reverseProxyUrl) ||
|
||||
settingsComponent.isGPTOptionSelected() != settings.isGPTOptionSelected ||
|
||||
settingsComponent.isChatGPTOptionSelected() != settings.isChatGPTOptionSelected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
var settings = SettingsState.getInstance();
|
||||
settings.secretKey = settingsComponent.getApiKeyField();
|
||||
if (!settings.secretKey.isEmpty()) {
|
||||
ApplicationManager.getApplication().getService(NotificationService.class).expire();
|
||||
}
|
||||
settings.isGPTOptionSelected = settingsComponent.isGPTOptionSelected();
|
||||
settings.isChatGPTOptionSelected = settingsComponent.isChatGPTOptionSelected();
|
||||
settings.accessToken = settingsComponent.getAccessToken();
|
||||
settings.apiKey = settingsComponent.getApiKey();
|
||||
settings.reverseProxyUrl = settingsComponent.getReverseProxyUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
var settings = SettingsState.getInstance();
|
||||
settingsComponent.setApiKeyField(settings.secretKey);
|
||||
settingsComponent.setUseGPTOptionSelected(settings.isGPTOptionSelected);
|
||||
settingsComponent.setUseChatGPTOptionSelected(settings.isChatGPTOptionSelected);
|
||||
settingsComponent.setAccessToken(settings.accessToken);
|
||||
settingsComponent.setApiKey(settings.apiKey);
|
||||
settingsComponent.setReverseProxyUrl(settings.reverseProxyUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ import org.jetbrains.annotations.Nullable;
|
|||
)
|
||||
public class SettingsState implements PersistentStateComponent<SettingsState> {
|
||||
|
||||
public String secretKey = "";
|
||||
public String apiKey = "";
|
||||
public String accessToken = "";
|
||||
public String reverseProxyUrl = "";
|
||||
public boolean isGPTOptionSelected = true;
|
||||
public boolean isChatGPTOptionSelected = false;
|
||||
|
||||
public static SettingsState getInstance() {
|
||||
return ApplicationManager.getApplication().getService(SettingsState.class);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ 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.ApiClient;
|
||||
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;
|
||||
|
|
@ -23,7 +24,6 @@ import java.awt.event.MouseAdapter;
|
|||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
|
|
@ -60,8 +60,8 @@ public class ToolWindowService implements LafManagerListener {
|
|||
addIconLabel(Icons.DefaultImageIcon, "ChatGPT:");
|
||||
addSpacing(8);
|
||||
|
||||
var secretKey = SettingsState.getInstance().secretKey;
|
||||
if (secretKey == null || secretKey.isEmpty()) {
|
||||
var apiKey = SettingsState.getInstance().apiKey;
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
var label = new JLabel("<html>API key not provided. <font color='#589df6'><u>Open Settings</u></font> to set one.</html>");
|
||||
label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
label.addMouseListener(new MouseAdapter() {
|
||||
|
|
@ -75,19 +75,21 @@ public class ToolWindowService implements LafManagerListener {
|
|||
scrollablePanel.add(textArea);
|
||||
textAreas.add(textArea);
|
||||
|
||||
var messageCounter = new AtomicInteger(0);
|
||||
ApiClient.getInstance().getCompletionsAsync(
|
||||
prompt,
|
||||
(message) -> {
|
||||
if (messageCounter.getAndIncrement() == 0) {
|
||||
message = message.replace("\n", "");
|
||||
}
|
||||
textArea.append(message);
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.call();
|
||||
}
|
||||
},
|
||||
(finalMessage) -> textArea.displayCopyButton());
|
||||
var client = new ClientFactory().getClient();
|
||||
client.getCompletionsAsync(prompt, message -> {
|
||||
if (client instanceof ChatGPTClient) {
|
||||
textArea.setText(message);
|
||||
} else {
|
||||
textArea.append(message);
|
||||
}
|
||||
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.call();
|
||||
}
|
||||
}, () -> {
|
||||
textArea.displayCopyButton();
|
||||
textArea.enableSelection();
|
||||
});
|
||||
}
|
||||
|
||||
addSpacing(16);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import java.awt.Toolkit;
|
|||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.JButton;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
|
|
@ -23,15 +21,25 @@ public class SyntaxTextArea extends RSyntaxTextArea {
|
|||
setStyles();
|
||||
}
|
||||
|
||||
public Matcher getMarkdownMatcher() {
|
||||
return Pattern.compile("`{3}([\\w]*)\\n([\\S\\s]+?)\\n`{3}").matcher(getText());
|
||||
public void displayCopyButton() {
|
||||
ComponentBorder cb = new ComponentBorder(createCopyButton());
|
||||
cb.setAlignment(TOP_ALIGNMENT);
|
||||
cb.install(this);
|
||||
}
|
||||
|
||||
public void displayCopyButton() {
|
||||
if (getMarkdownMatcher().matches()) {
|
||||
ComponentBorder cb = new ComponentBorder(createCopyButton());
|
||||
cb.setAlignment(TOP_ALIGNMENT);
|
||||
cb.install(this);
|
||||
public void enableSelection() {
|
||||
setEditable(false);
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
public void changeStyleViaThemeXml() {
|
||||
var baseThemePath = "/org/fife/ui/rsyntaxtextarea/themes/";
|
||||
try {
|
||||
Theme theme = Theme.load(getClass().getResourceAsStream(
|
||||
UIUtil.isUnderDarcula() ? baseThemePath + "dark.xml" : baseThemePath + "idea.xml"));
|
||||
theme.apply(this);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,28 +56,11 @@ public class SyntaxTextArea extends RSyntaxTextArea {
|
|||
}
|
||||
|
||||
private void copyToClipboard() {
|
||||
var text = getText();
|
||||
var matcher = getMarkdownMatcher();
|
||||
if (matcher.find()) {
|
||||
text = matcher.group(2);
|
||||
}
|
||||
|
||||
StringSelection stringSelection = new StringSelection(text);
|
||||
StringSelection stringSelection = new StringSelection(getText());
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(stringSelection, null);
|
||||
}
|
||||
|
||||
public void changeStyleViaThemeXml() {
|
||||
var baseThemePath = "/org/fife/ui/rsyntaxtextarea/themes/";
|
||||
try {
|
||||
Theme theme = Theme.load(getClass().getResourceAsStream(
|
||||
UIUtil.isUnderDarcula() ? baseThemePath + "dark.xml" : baseThemePath + "idea.xml"));
|
||||
theme.apply(this);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private JButton createCopyButton() {
|
||||
var button = createIconButton(Icons.CopyImageIcon);
|
||||
button.addActionListener(e -> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue