1.2.0 - 🔥 Add support for the official ChatGPT API (closes #10)

This commit is contained in:
Carl-Robert Linnupuu 2023-03-02 19:18:33 +00:00
parent fa1e2a486b
commit a2ea5a0333
40 changed files with 713 additions and 305 deletions

View file

@ -1,14 +1,12 @@
package ee.carlrobert.chatgpt.client;
import java.util.Map;
public class ApiRequestDetails {
private final String url;
private final Map<String, Object> body;
private final Object body;
private final String token;
public ApiRequestDetails(String url, Map<String, Object> body, String token) {
public ApiRequestDetails(String url, Object body, String token) {
this.url = url;
this.body = body;
this.token = token;
@ -18,7 +16,7 @@ public class ApiRequestDetails {
return url;
}
public Map<String, Object> getBody() {
public Object getBody() {
return body;
}

View file

@ -5,7 +5,9 @@ public enum BaseModel {
ADA("text-ada-001", "Ada - Fastest"),
BABBAGE("text-babbage-001", "Babbage - Powerful"),
CURIE("text-curie-001", "Curie - Fast and efficient"),
DAVINCI("text-davinci-003", "Davinci - Most powerful (Default)");
DAVINCI("text-davinci-003", "Davinci - Most powerful (Default)"),
CHATGPT("gpt-3.5-turbo", "ChatGPT - Most recent and capable model (Default)"),
CHATGPT_SNAPSHOT("gpt-3.5-turbo-0301", "ChatGPT - Snapshot of gpt-3.5-turbo from March 1st 2023");
private final String model;
private final String description;

View file

@ -1,15 +1,19 @@
package ee.carlrobert.chatgpt.client;
import ee.carlrobert.chatgpt.client.chatgpt.ChatGPTClient;
import ee.carlrobert.chatgpt.client.gpt.GPTClient;
import ee.carlrobert.chatgpt.client.unofficial.UnofficialChatGPTClient;
import ee.carlrobert.chatgpt.client.official.chat.ChatCompletionClient;
import ee.carlrobert.chatgpt.client.official.text.TextCompletionClient;
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
public class ClientFactory {
public Client getClient() {
if (SettingsState.getInstance().isGPTOptionSelected) {
return GPTClient.getInstance();
if (SettingsState.getInstance().isChatCompletionOptionSelected) {
return ChatCompletionClient.getInstance();
}
return TextCompletionClient.getInstance();
}
return ChatGPTClient.getInstance();
return UnofficialChatGPTClient.getInstance();
}
}

View file

@ -1,17 +0,0 @@
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;
}
}

View file

@ -1,35 +0,0 @@
package ee.carlrobert.chatgpt.client.chatgpt.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatGPTResponseMessage {
private String id;
private ChatGPTResponseMessageAuthor author;
private ChatGPTResponseMessageContent content;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ChatGPTResponseMessageAuthor getAuthor() {
return author;
}
public void setAuthor(ChatGPTResponseMessageAuthor author) {
this.author = author;
}
public ChatGPTResponseMessageContent getContent() {
return content;
}
public void setContent(ChatGPTResponseMessageContent content) {
this.content = content;
}
}

View file

@ -1,117 +0,0 @@
package ee.carlrobert.chatgpt.client.gpt;
import static java.lang.String.format;
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.BodySubscriber;
import java.net.http.HttpResponse.ResponseInfo;
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(
format("https://api.openai.com/v1/engines/%s/completions", SettingsState.getInstance().baseModel.getModel()),
Map.of(
"stop", List.of(" Human:", " AI:"),
"prompt", buildPrompt(prompt),
"max_tokens", 1000,
"temperature", 0.9,
"best_of", 1,
"frequency_penalty", 0,
"presence_penalty", 0.6,
"top_p", 1,
"stream", true
),
SettingsState.getInstance().apiKey);
}
protected BodySubscriber<Void> subscribe(
ResponseInfo responseInfo,
Consumer<String> onMessageReceived,
Runnable onComplete) {
if (responseInfo.statusCode() == 200) {
return new GPTBodySubscriber(
onMessageReceived,
finalMsg -> {
queries.add(Map.entry(super.userPrompt, finalMsg));
onComplete.run();
});
} else {
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();
}
}
private StringBuilder getBasePrompt() {
var isDavinciModel = SettingsState.getInstance().baseModel == BaseModel.DAVINCI;
if (isDavinciModel) {
return new StringBuilder(
"You are ChatGPT, a large language model trained by OpenAI.\n" +
"Answer in a markdown language, code blocks should contain language whenever possible.\n");
}
return new StringBuilder(
"The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.\n\n");
}
private String buildPrompt(String prompt) {
var basePrompt = getBasePrompt();
queries.forEach(query ->
basePrompt.append("Human: ")
.append(query.getKey())
.append("\n")
.append("AI: ")
.append(query.getValue())
.append("\n"));
basePrompt.append("Human: ")
.append(prompt)
.append("\n")
.append("AI: ")
.append("\n");
return basePrompt.toString();
}
}

View file

@ -0,0 +1,55 @@
package ee.carlrobert.chatgpt.client.official;
import ee.carlrobert.chatgpt.client.Client;
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;
public abstract class CompletionClient extends Client {
protected static final List<Map.Entry<String, String>> queries = new ArrayList<>();
protected abstract CompletionSubscriber createSubscriber(
Consumer<String> responseConsumer,
Consumer<String> onCompleteCallback);
protected BodySubscriber<Void> subscribe(
ResponseInfo responseInfo,
Consumer<String> onMessageReceived,
Runnable onComplete) {
if (responseInfo.statusCode() == 200) {
return createSubscriber(onMessageReceived, finalMsg -> {
queries.add(Map.entry(super.userPrompt, finalMsg));
onComplete.run();
});
} else {
try {
handleError(responseInfo, onMessageReceived);
return null;
} finally {
onComplete.run();
}
}
}
private void handleError(ResponseInfo responseInfo, Consumer<String> onMessageReceived) {
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();
}
}
public void clearPreviousSession() {
queries.clear();
}
}

View file

@ -1,18 +1,19 @@
package ee.carlrobert.chatgpt.client.gpt;
package ee.carlrobert.chatgpt.client.official;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ee.carlrobert.chatgpt.client.ApiResponse;
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> {
public abstract class CompletionSubscriber extends Subscriber<ApiResponse> {
private final Consumer<String> onCompleteCallback;
private final StringBuilder messageBuilder = new StringBuilder();
private final Consumer<String> onCompleteCallback;
private final Consumer<String> responseConsumer;
public GPTBodySubscriber(
protected abstract String getMessage(String responsePayload) throws JsonProcessingException;
public CompletionSubscriber(
Consumer<String> responseConsumer,
Consumer<String> onCompleteCallback) {
this.responseConsumer = responseConsumer;
@ -30,8 +31,7 @@ public class GPTBodySubscriber extends Subscriber<GPTResponse> {
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();
var message = getMessage(responsePayload);
messageBuilder.append(message);
this.responseConsumer.accept(message);
}

View file

@ -0,0 +1,52 @@
package ee.carlrobert.chatgpt.client.official.chat;
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
import ee.carlrobert.chatgpt.client.official.CompletionClient;
import ee.carlrobert.chatgpt.client.official.CompletionSubscriber;
import ee.carlrobert.chatgpt.client.official.chat.request.Request;
import ee.carlrobert.chatgpt.client.official.chat.request.RequestMessage;
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
import java.util.ArrayList;
import java.util.function.Consumer;
public class ChatCompletionClient extends CompletionClient {
private static ChatCompletionClient instance;
private ChatCompletionClient() {
}
public static ChatCompletionClient getInstance() {
if (instance == null) {
instance = new ChatCompletionClient();
}
return instance;
}
protected ApiRequestDetails getRequestDetails(String prompt) {
var messages = new ArrayList<RequestMessage>();
messages.add(new RequestMessage(
"system",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."));
queries.forEach(query -> {
messages.add(new RequestMessage("user", query.getKey()));
messages.add(new RequestMessage("assistant", query.getValue()));
});
messages.add(new RequestMessage("user", prompt));
return new ApiRequestDetails(
"https://api.openai.com/v1/chat/completions",
new Request(
SettingsState.getInstance().chatCompletionBaseModel.getModel(),
true,
messages
),
SettingsState.getInstance().apiKey);
}
protected CompletionSubscriber createSubscriber(
Consumer<String> responseConsumer,
Consumer<String> onCompleteCallback) {
return new ChatCompletionSubscriber(responseConsumer, onCompleteCallback);
}
}

View file

@ -0,0 +1,23 @@
package ee.carlrobert.chatgpt.client.official.chat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ee.carlrobert.chatgpt.client.official.CompletionSubscriber;
import ee.carlrobert.chatgpt.client.official.chat.response.Response;
import java.util.function.Consumer;
public class ChatCompletionSubscriber extends CompletionSubscriber {
public ChatCompletionSubscriber(Consumer<String> responseConsumer, Consumer<String> onCompleteCallback) {
super(responseConsumer, onCompleteCallback);
}
protected String getMessage(String responsePayload) throws JsonProcessingException {
return new ObjectMapper()
.readValue(responsePayload, Response.class)
.getChoices()
.get(0)
.getDelta()
.getContent();
}
}

View file

@ -0,0 +1,28 @@
package ee.carlrobert.chatgpt.client.official.chat.request;
import java.util.List;
public class Request {
private final String model;
private final boolean stream;
private final List<RequestMessage> messages;
public Request(String model, boolean stream, List<RequestMessage> messages) {
this.model = model;
this.stream = stream;
this.messages = messages;
}
public String getModel() {
return model;
}
public boolean isStream() {
return stream;
}
public List<RequestMessage> getMessages() {
return messages;
}
}

View file

@ -0,0 +1,20 @@
package ee.carlrobert.chatgpt.client.official.chat.request;
public class RequestMessage {
private final String role;
private final String content;
public RequestMessage(String role, String content) {
this.role = role;
this.content = content;
}
public String getRole() {
return role;
}
public String getContent() {
return content;
}
}

View file

@ -1,14 +1,14 @@
package ee.carlrobert.chatgpt.client.gpt.response;
package ee.carlrobert.chatgpt.client.official.chat.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 {
public class Response implements ApiResponse {
private String id;
private List<GPTResponseChoice> choices;
private List<ResponseChoice> choices;
public String getId() {
return id;
@ -18,11 +18,11 @@ public class GPTResponse implements ApiResponse {
this.id = id;
}
public List<GPTResponseChoice> getChoices() {
public List<ResponseChoice> getChoices() {
return choices;
}
public void setChoices(List<GPTResponseChoice> choices) {
public void setChoices(List<ResponseChoice> choices) {
this.choices = choices;
}
}

View file

@ -0,0 +1,17 @@
package ee.carlrobert.chatgpt.client.official.chat.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResponseChoice {
private ResponseChoiceDelta delta;
public ResponseChoiceDelta getDelta() {
return delta;
}
public void setDelta(ResponseChoiceDelta delta) {
this.delta = delta;
}
}

View file

@ -0,0 +1,26 @@
package ee.carlrobert.chatgpt.client.official.chat.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResponseChoiceDelta {
private String role;
private String content;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View file

@ -0,0 +1,79 @@
package ee.carlrobert.chatgpt.client.official.text;
import static java.lang.String.format;
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
import ee.carlrobert.chatgpt.client.BaseModel;
import ee.carlrobert.chatgpt.client.official.CompletionClient;
import ee.carlrobert.chatgpt.client.official.CompletionSubscriber;
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class TextCompletionClient extends CompletionClient {
private static TextCompletionClient instance;
private TextCompletionClient() {
}
public static TextCompletionClient getInstance() {
if (instance == null) {
instance = new TextCompletionClient();
}
return instance;
}
protected ApiRequestDetails getRequestDetails(String prompt) {
return new ApiRequestDetails(
format("https://api.openai.com/v1/engines/%s/completions",
SettingsState.getInstance().textCompletionBaseModel.getModel()),
Map.of(
"stop", List.of(" Human:", " AI:"),
"prompt", buildPrompt(prompt),
"max_tokens", 1000,
"temperature", 0.9,
"best_of", 1,
"frequency_penalty", 0,
"presence_penalty", 0.6,
"top_p", 1,
"stream", true
),
SettingsState.getInstance().apiKey);
}
protected CompletionSubscriber createSubscriber(
Consumer<String> responseConsumer,
Consumer<String> onCompleteCallback) {
return new TextCompletionSubscriber(responseConsumer, onCompleteCallback);
}
private StringBuilder getBasePrompt() {
var isDavinciModel = SettingsState.getInstance().textCompletionBaseModel == BaseModel.DAVINCI;
if (isDavinciModel) {
return new StringBuilder(
"You are ChatGPT, a large language model trained by OpenAI.\n" +
"Answer in a markdown language, code blocks should contain language whenever possible.\n");
}
return new StringBuilder(
"The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.\n\n");
}
private String buildPrompt(String prompt) {
var basePrompt = getBasePrompt();
queries.forEach(query ->
basePrompt.append("Human: ")
.append(query.getKey())
.append("\n")
.append("AI: ")
.append(query.getValue())
.append("\n"));
basePrompt.append("Human: ")
.append(prompt)
.append("\n")
.append("AI: ")
.append("\n");
return basePrompt.toString();
}
}

View file

@ -0,0 +1,22 @@
package ee.carlrobert.chatgpt.client.official.text;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ee.carlrobert.chatgpt.client.official.CompletionSubscriber;
import ee.carlrobert.chatgpt.client.official.text.response.Response;
import java.util.function.Consumer;
public class TextCompletionSubscriber extends CompletionSubscriber {
public TextCompletionSubscriber(Consumer<String> responseConsumer, Consumer<String> onCompleteCallback) {
super(responseConsumer, onCompleteCallback);
}
protected String getMessage(String responsePayload) throws JsonProcessingException {
return new ObjectMapper()
.readValue(responsePayload, Response.class)
.getChoices()
.get(0)
.getText();
}
}

View file

@ -0,0 +1,28 @@
package ee.carlrobert.chatgpt.client.official.text.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import ee.carlrobert.chatgpt.client.ApiResponse;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Response implements ApiResponse {
private String id;
private List<ResponseChoice> choices;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<ResponseChoice> getChoices() {
return choices;
}
public void setChoices(List<ResponseChoice> choices) {
this.choices = choices;
}
}

View file

@ -1,10 +1,10 @@
package ee.carlrobert.chatgpt.client.gpt.response;
package ee.carlrobert.chatgpt.client.official.text.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class GPTResponseChoice {
public class ResponseChoice {
private String text;
@JsonProperty("finish_reason")

View file

@ -1,8 +1,8 @@
package ee.carlrobert.chatgpt.client.chatgpt;
package ee.carlrobert.chatgpt.client.unofficial;
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
import ee.carlrobert.chatgpt.client.Client;
import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponse;
import ee.carlrobert.chatgpt.client.unofficial.response.Response;
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
import java.net.http.HttpResponse.BodySubscriber;
import java.net.http.HttpResponse.ResponseInfo;
@ -12,17 +12,17 @@ import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
public class ChatGPTClient extends Client {
public class UnofficialChatGPTClient extends Client {
private static ChatGPTClient instance;
private static ChatGPTResponse lastReceivedResponse;
private static UnofficialChatGPTClient instance;
private static Response lastReceivedResponse;
private ChatGPTClient() {
private UnofficialChatGPTClient() {
}
public static ChatGPTClient getInstance() {
public static UnofficialChatGPTClient getInstance() {
if (instance == null) {
instance = new ChatGPTClient();
instance = new UnofficialChatGPTClient();
}
return instance;
}
@ -65,7 +65,7 @@ public class ChatGPTClient extends Client {
Consumer<String> onMessageReceived,
Runnable onComplete) {
if (responseInfo.statusCode() == 200) {
return new ChatGPTBodySubscriber(
return new UnofficialChatGPTSubscriber(
onMessageReceived,
response -> {
lastReceivedResponse = response;

View file

@ -1,25 +1,25 @@
package ee.carlrobert.chatgpt.client.chatgpt;
package ee.carlrobert.chatgpt.client.unofficial;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
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 ee.carlrobert.chatgpt.client.unofficial.response.Response;
import ee.carlrobert.chatgpt.client.unofficial.response.ResponseDetail;
import ee.carlrobert.chatgpt.client.unofficial.response.ResponseError;
import java.util.function.Consumer;
public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
public class UnofficialChatGPTSubscriber extends Subscriber<Response> {
private final Consumer<String> responseConsumer;
private final Consumer<ChatGPTResponse> onCompleteCallback;
private final Consumer<Response> onCompleteCallback;
private final ObjectMapper objectMapper = new ObjectMapper();
private ChatGPTResponse lastReceivedResponse;
private Response lastReceivedResponse;
public ChatGPTBodySubscriber(
public UnofficialChatGPTSubscriber(
Consumer<String> responseConsumer,
Consumer<ChatGPTResponse> onCompleteCallback) {
Consumer<Response> onCompleteCallback) {
this.responseConsumer = responseConsumer;
this.onCompleteCallback = onCompleteCallback;
}
@ -35,7 +35,7 @@ public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
protected void send(String responsePayload, String token) {
if (!responsePayload.isEmpty() && isValidJson(responsePayload)) {
try {
var response = objectMapper.readValue(responsePayload, ChatGPTResponse.class);
var response = objectMapper.readValue(responsePayload, Response.class);
var author = response.getMessage().getAuthor();
if (author != null && "assistant".equals(author.getRole())) {
var message = response.getFullMessage();
@ -51,7 +51,7 @@ public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
} else {
if (token != null && !token.isEmpty() && isValidJson(token)) {
try {
var response = objectMapper.readValue(token, ChatGPTResponseDetail.class);
var response = objectMapper.readValue(token, ResponseDetail.class);
this.responseConsumer.accept(response.getDetail());
} catch (JsonProcessingException e) {
tryProcessingErrorResponse(token);
@ -62,7 +62,7 @@ public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
private void tryProcessingErrorResponse(String jsonPayload) {
try {
var error = objectMapper.readValue(jsonPayload, ChatGPTResponseError.class);
var error = objectMapper.readValue(jsonPayload, ResponseError.class);
if ("invalid_api_key".equals(error.getDetail().getCode())) {
responseConsumer.accept(error.getDetail().getMessage());
}

View file

@ -1,21 +1,21 @@
package ee.carlrobert.chatgpt.client.chatgpt.response;
package ee.carlrobert.chatgpt.client.unofficial.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 {
public class Response implements ApiResponse {
private ChatGPTResponseMessage message;
private ResponseMessage message;
@JsonProperty("conversation_id")
private String conversationId;
public ChatGPTResponseMessage getMessage() {
public ResponseMessage getMessage() {
return message;
}
public void setMessage(ChatGPTResponseMessage message) {
public void setMessage(ResponseMessage message) {
this.message = message;
}

View file

@ -1,9 +1,10 @@
package ee.carlrobert.chatgpt.client.chatgpt.response;
package ee.carlrobert.chatgpt.client.unofficial.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatGPTResponseDetail {
public class ResponseDetail {
private String detail;
public String getDetail() {

View file

@ -0,0 +1,17 @@
package ee.carlrobert.chatgpt.client.unofficial.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResponseError {
private ResponseErrorDetails detail;
public ResponseErrorDetails getDetail() {
return detail;
}
public void setDetail(ResponseErrorDetails detail) {
this.detail = detail;
}
}

View file

@ -1,9 +1,9 @@
package ee.carlrobert.chatgpt.client.chatgpt.response;
package ee.carlrobert.chatgpt.client.unofficial.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatGPTResponseErrorDetails {
public class ResponseErrorDetails {
private String message;
private String type;

View file

@ -0,0 +1,35 @@
package ee.carlrobert.chatgpt.client.unofficial.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResponseMessage {
private String id;
private ResponseMessageAuthor author;
private ResponseMessageContent content;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ResponseMessageAuthor getAuthor() {
return author;
}
public void setAuthor(ResponseMessageAuthor author) {
this.author = author;
}
public ResponseMessageContent getContent() {
return content;
}
public void setContent(ResponseMessageContent content) {
this.content = content;
}
}

View file

@ -1,9 +1,9 @@
package ee.carlrobert.chatgpt.client.chatgpt.response;
package ee.carlrobert.chatgpt.client.unofficial.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatGPTResponseMessageAuthor {
public class ResponseMessageAuthor {
private String role;

View file

@ -1,11 +1,11 @@
package ee.carlrobert.chatgpt.client.chatgpt.response;
package ee.carlrobert.chatgpt.client.unofficial.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatGPTResponseMessageContent {
public class ResponseMessageContent {
@JsonProperty("content_type")
private String contentType;

View file

@ -8,13 +8,8 @@ import javax.swing.plaf.basic.BasicComboBoxRenderer;
public class BaseModelComboBox extends JComboBox<BaseModel> {
public BaseModelComboBox(BaseModel selectedModel) {
super(new BaseModel[] {
BaseModel.DAVINCI,
BaseModel.CURIE,
BaseModel.BABBAGE,
BaseModel.ADA,
});
public BaseModelComboBox(BaseModel[] options, BaseModel selectedModel) {
super(options);
setSelectedItem(selectedModel);
setRenderer(getBasicComboBoxRenderer());
}

View file

@ -10,6 +10,7 @@ import ee.carlrobert.chatgpt.client.BaseModel;
import java.awt.Desktop;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JComboBox;
import javax.swing.JComponent;
@ -22,22 +23,40 @@ public class SettingsComponent {
private final JPanel mainPanel;
private final JBTextField apiKeyField;
private final JComboBox<BaseModel> baseModelComboBox;
private final JComboBox<BaseModel> chatCompletionBaseModelComboBox;
private final JComboBox<BaseModel> textCompletionBaseModelComboBox;
private final JComboBox<String> reverseProxyComboBox;
private final JBTextField accessTokenField;
private final JBRadioButton useGPTRadioButton;
private final JBRadioButton useChatCompletionRadioButton;
private final JBRadioButton useTextCompletionRadioButton;
private final JBRadioButton useChatGPTRadioButton;
public SettingsComponent(SettingsState settings) {
apiKeyField = new JBTextField(settings.apiKey);
baseModelComboBox = new BaseModelComboBox(settings.baseModel);
chatCompletionBaseModelComboBox = new BaseModelComboBox(
new BaseModel[] {
BaseModel.CHATGPT,
BaseModel.CHATGPT_SNAPSHOT,
},
settings.textCompletionBaseModel);
textCompletionBaseModelComboBox = new BaseModelComboBox(
new BaseModel[] {
BaseModel.DAVINCI,
BaseModel.CURIE,
BaseModel.BABBAGE,
BaseModel.ADA,
},
settings.textCompletionBaseModel);
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);
useGPTRadioButton = new JBRadioButton("Use OpenAI's official API", settings.isGPTOptionSelected);
useChatCompletionRadioButton = new JBRadioButton("Use chat completion", settings.isChatCompletionOptionSelected);
useTextCompletionRadioButton = new JBRadioButton("Use text completion", settings.isTextCompletionOptionSelected);
useChatGPTRadioButton = new JBRadioButton("Use ChatGPT's unofficial API", settings.isChatGPTOptionSelected);
mainPanel = FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator("Integration Preference"))
.addComponent(createMainSelectionForm())
@ -82,6 +101,22 @@ public class SettingsComponent {
useGPTRadioButton.setSelected(isSelected);
}
public boolean isChatCompletionOptionSelected() {
return useChatCompletionRadioButton.isSelected();
}
public void setUseChatCompletionSelected(boolean isSelected) {
useChatCompletionRadioButton.setSelected(isSelected);
}
public boolean isTextCompletionOptionSelected() {
return useTextCompletionRadioButton.isSelected();
}
public void setUseTextCompletionSelected(boolean isSelected) {
useTextCompletionRadioButton.setSelected(isSelected);
}
public boolean isChatGPTOptionSelected() {
return useChatGPTRadioButton.isSelected();
}
@ -98,21 +133,61 @@ public class SettingsComponent {
reverseProxyComboBox.setSelectedItem(reverseProxyUrl);
}
public BaseModel getBaseModel() {
return (BaseModel) baseModelComboBox.getSelectedItem();
public BaseModel getTextCompletionBaseModel() {
return (BaseModel) textCompletionBaseModelComboBox.getSelectedItem();
}
public void setBaseModel(BaseModel baseModel) {
baseModelComboBox.setSelectedItem(baseModel);
public void setTextCompletionBaseModel(BaseModel baseModel) {
textCompletionBaseModelComboBox.setSelectedItem(baseModel);
}
public BaseModel getChatCompletionBaseModel() {
return (BaseModel) chatCompletionBaseModelComboBox.getSelectedItem();
}
public void setChatCompletionBaseModel(BaseModel baseModel) {
chatCompletionBaseModelComboBox.setSelectedItem(baseModel);
}
private JPanel createMainSelectionForm() {
var apiKeyFieldPanel = 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();
apiKeyFieldPanel.setBorder(JBUI.Borders.emptyLeft(8));
var chatCompletionModelsPanel = UI.PanelFactory.panel(chatCompletionBaseModelComboBox)
.withLabel("Model:")
.createPanel();
chatCompletionModelsPanel.setBorder(JBUI.Borders.emptyLeft(24));
var textCompletionModelsPanel = UI.PanelFactory.panel(textCompletionBaseModelComboBox)
.withLabel("Model:")
.createPanel();
textCompletionModelsPanel.setBorder(JBUI.Borders.emptyLeft(24));
var gptRadioPanel = FormBuilder.createFormBuilder()
.addComponent(apiKeyFieldPanel)
.addComponent(UI.PanelFactory.panel(useChatCompletionRadioButton)
.withComment("OpenAIs most advanced language model")
.createPanel())
.addComponent(chatCompletionModelsPanel)
.addVerticalGap(8)
.addComponent(UI.PanelFactory.panel(useTextCompletionRadioButton)
.withComment("Best for high-quality texts")
.createPanel())
.addComponent(textCompletionModelsPanel)
.getPanel();
gptRadioPanel.setBorder(JBUI.Borders.emptyLeft(16));
var panel = FormBuilder.createFormBuilder()
.addVerticalGap(8)
.addComponent(UI.PanelFactory.panel(useGPTRadioButton)
.withComment("Fast and robust, requires API key")
.createPanel())
.addComponent(createFirstSelectionForm())
.addComponent(gptRadioPanel)
.addVerticalGap(8)
.addComponent(UI.PanelFactory.panel(useChatGPTRadioButton)
.withComment("Slow and free, more suitable for conversational tasks, rate-limited")
@ -123,27 +198,6 @@ public class SettingsComponent {
return panel;
}
private JPanel createFirstSelectionForm() {
var baseModelPanel = UI.PanelFactory.panel(baseModelComboBox)
.withLabel("Model:")
.createPanel();
var apiKeyFieldPanel = 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();
setEqualLabelWidths(baseModelPanel, apiKeyFieldPanel);
var panel = FormBuilder.createFormBuilder()
.addComponent(baseModelPanel)
.addVerticalGap(8)
.addComponent(apiKeyFieldPanel)
.getPanel();
panel.setBorder(JBUI.Borders.emptyLeft(24));
return panel;
}
private JPanel createSecondSelectionForm() {
var reverseProxyUrlPanel = UI.PanelFactory.panel(reverseProxyComboBox)
.withLabel("Reverse proxy url:")
@ -181,11 +235,35 @@ public class SettingsComponent {
myButtonGroup.add(useChatGPTRadioButton);
useGPTRadioButton.addActionListener(e -> registerFields(false));
useChatGPTRadioButton.addActionListener(e -> registerFields(true));
ButtonGroup completionButtonGroup = new ButtonGroup();
completionButtonGroup.add(useChatCompletionRadioButton);
completionButtonGroup.add(useTextCompletionRadioButton);
useChatCompletionRadioButton.addActionListener(e -> {
chatCompletionBaseModelComboBox.setEnabled(true);
textCompletionBaseModelComboBox.setEnabled(false);
});
useTextCompletionRadioButton.addActionListener(e -> {
chatCompletionBaseModelComboBox.setEnabled(false);
textCompletionBaseModelComboBox.setEnabled(true);
});
}
private void registerFields(boolean isUseChatGPTOption) {
apiKeyField.setEnabled(!isUseChatGPTOption);
baseModelComboBox.setEnabled(!isUseChatGPTOption);
if (isUseChatGPTOption) {
List.of(
useChatCompletionRadioButton,
useTextCompletionRadioButton,
chatCompletionBaseModelComboBox,
textCompletionBaseModelComboBox
).forEach(it -> it.setEnabled(false));
} else {
useChatCompletionRadioButton.setEnabled(true);
useTextCompletionRadioButton.setEnabled(true);
chatCompletionBaseModelComboBox.setEnabled(useChatCompletionRadioButton.isSelected());
textCompletionBaseModelComboBox.setEnabled(useTextCompletionRadioButton.isSelected());
}
accessTokenField.setEnabled(isUseChatGPTOption);
reverseProxyComboBox.setEnabled(isUseChatGPTOption);
}

View file

@ -12,7 +12,7 @@ public class SettingsConfigurable implements Configurable {
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "ChatGPT: Settings";
return "CodeGPT: Settings";
}
@Override
@ -34,8 +34,11 @@ public class SettingsConfigurable implements Configurable {
return !settingsComponent.getApiKey().equals(settings.apiKey) ||
!settingsComponent.getAccessToken().equals(settings.accessToken) ||
!settingsComponent.getReverseProxyUrl().equals(settings.reverseProxyUrl) ||
!settingsComponent.getBaseModel().equals(settings.baseModel) ||
!settingsComponent.getChatCompletionBaseModel().equals(settings.chatCompletionBaseModel) ||
!settingsComponent.getTextCompletionBaseModel().equals(settings.textCompletionBaseModel) ||
settingsComponent.isGPTOptionSelected() != settings.isGPTOptionSelected ||
settingsComponent.isChatCompletionOptionSelected() != settings.isChatCompletionOptionSelected ||
settingsComponent.isTextCompletionOptionSelected() != settings.isTextCompletionOptionSelected ||
settingsComponent.isChatGPTOptionSelected() != settings.isChatGPTOptionSelected;
}
@ -47,18 +50,24 @@ public class SettingsConfigurable implements Configurable {
settings.accessToken = settingsComponent.getAccessToken();
settings.apiKey = settingsComponent.getApiKey();
settings.reverseProxyUrl = settingsComponent.getReverseProxyUrl();
settings.baseModel = settingsComponent.getBaseModel();
settings.chatCompletionBaseModel = settingsComponent.getChatCompletionBaseModel();
settings.isChatCompletionOptionSelected = settingsComponent.isChatCompletionOptionSelected();
settings.isTextCompletionOptionSelected = settingsComponent.isTextCompletionOptionSelected();
settings.textCompletionBaseModel = settingsComponent.getTextCompletionBaseModel();
}
@Override
public void reset() {
var settings = SettingsState.getInstance();
settingsComponent.setUseGPTOptionSelected(settings.isGPTOptionSelected);
settingsComponent.setUseChatCompletionSelected(settings.isChatCompletionOptionSelected);
settingsComponent.setUseTextCompletionSelected(settings.isTextCompletionOptionSelected);
settingsComponent.setUseChatGPTOptionSelected(settings.isChatGPTOptionSelected);
settingsComponent.setAccessToken(settings.accessToken);
settingsComponent.setApiKey(settings.apiKey);
settingsComponent.setReverseProxyUrl(settings.reverseProxyUrl);
settingsComponent.setBaseModel(settings.baseModel);
settingsComponent.setChatCompletionBaseModel(settings.chatCompletionBaseModel);
settingsComponent.setTextCompletionBaseModel(settings.textCompletionBaseModel);
}
@Override

View file

@ -11,16 +11,19 @@ import org.jetbrains.annotations.Nullable;
@State(
name = "ee.carlrobert.chatgpt.ide.settings.SettingsState",
storages = @Storage("SdkSettingsPlugin.xml")
storages = @Storage("CodeGPTSettings.xml")
)
public class SettingsState implements PersistentStateComponent<SettingsState> {
public String apiKey = "";
public String accessToken = "";
public String reverseProxyUrl = "";
public BaseModel baseModel = BaseModel.DAVINCI;
public BaseModel textCompletionBaseModel = BaseModel.DAVINCI;
public BaseModel chatCompletionBaseModel = BaseModel.CHATGPT;
public boolean isGPTOptionSelected = true;
public boolean isChatGPTOptionSelected = false;
public boolean isChatCompletionOptionSelected = true;
public boolean isTextCompletionOptionSelected = false;
public static SettingsState getInstance() {
return ApplicationManager.getApplication().getService(SettingsState.class);

View file

@ -44,7 +44,7 @@ public class ToolWindowService implements LafManagerListener {
}
public ToolWindow getToolWindow(@NotNull Project project) {
return ToolWindowManager.getInstance(project).getToolWindow("ChatGPT");
return ToolWindowManager.getInstance(project).getToolWindow("CodeGPT");
}
public void paintUserMessage(String userMessage) {