mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-07 17:23:11 +00:00
Add conversation history, fix reported bugs, visual improvements #9)
This commit is contained in:
parent
6246491668
commit
f4cd93f018
53 changed files with 906 additions and 252 deletions
|
|
@ -9,16 +9,16 @@ public enum BaseModel {
|
|||
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 code;
|
||||
private final String description;
|
||||
|
||||
BaseModel(String model, String description) {
|
||||
this.model = model;
|
||||
BaseModel(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package ee.carlrobert.chatgpt.client;
|
|||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
|
@ -21,21 +22,36 @@ import okhttp3.sse.EventSources;
|
|||
public abstract class Client {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ClientCode clientCode;
|
||||
private EventSource eventSource;
|
||||
protected String prompt = "";
|
||||
protected Conversation conversation;
|
||||
protected String prompt;
|
||||
protected OkHttpClient client;
|
||||
|
||||
protected Client(ClientCode clientCode) {
|
||||
this.clientCode = clientCode;
|
||||
}
|
||||
|
||||
protected abstract ApiRequestDetails getRequestDetails(String prompt);
|
||||
|
||||
public abstract void clearPreviousSession();
|
||||
protected abstract EventSourceListener getEventSourceListener(
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<Conversation> onComplete,
|
||||
Consumer<String> onFailure);
|
||||
|
||||
protected abstract EventSourceListener getEventSourceListener(Consumer<String> onMessageReceived, Runnable onComplete);
|
||||
|
||||
public void getCompletionsAsync(String prompt, Consumer<String> onMessageReceived, Runnable onComplete) {
|
||||
public void getCompletionsAsync(
|
||||
Conversation conversation,
|
||||
String prompt,
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<Conversation> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
this.conversation = conversation;
|
||||
this.prompt = prompt;
|
||||
this.client = buildClient();
|
||||
this.eventSource = EventSources.createFactory(client)
|
||||
.newEventSource(buildHttpRequest(prompt), getEventSourceListener(onMessageReceived, onComplete));
|
||||
.newEventSource(
|
||||
buildHttpRequest(prompt),
|
||||
getEventSourceListener(onMessageReceived, onComplete, onFailure));
|
||||
}
|
||||
|
||||
public OkHttpClient buildClient() {
|
||||
|
|
@ -77,4 +93,8 @@ public abstract class Client {
|
|||
throw new RuntimeException("Unable to serialize request payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
public ClientCode getCode() {
|
||||
return clientCode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package ee.carlrobert.chatgpt.client;
|
||||
|
||||
public enum ClientCode {
|
||||
|
||||
CHAT_COMPLETIONS,
|
||||
TEXT_COMPLETIONS,
|
||||
UNOFFICIAL_CHATGPT
|
||||
}
|
||||
|
|
@ -19,12 +19,18 @@ public abstract class CompletionClientEventListener extends EventSourceListener
|
|||
private final OkHttpClient client;
|
||||
private final Consumer<String> onMessageReceived;
|
||||
private final Consumer<String> onComplete;
|
||||
private final Consumer<String> onFailure;
|
||||
private final StringBuilder messageBuilder = new StringBuilder();
|
||||
|
||||
public CompletionClientEventListener(OkHttpClient client, Consumer<String> onMessageReceived, Consumer<String> onComplete) {
|
||||
public CompletionClientEventListener(
|
||||
OkHttpClient client,
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<String> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
this.client = client;
|
||||
this.onMessageReceived = onMessageReceived;
|
||||
this.onComplete = onComplete;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
protected abstract String getMessage(String data) throws JsonProcessingException;
|
||||
|
|
@ -47,8 +53,10 @@ public abstract class CompletionClientEventListener extends EventSourceListener
|
|||
}
|
||||
|
||||
var message = getMessage(data);
|
||||
messageBuilder.append(message);
|
||||
onMessageReceived.accept(message);
|
||||
if (message != null) {
|
||||
messageBuilder.append(message);
|
||||
onMessageReceived.accept(message);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Unable to deserialize payload.", e);
|
||||
}
|
||||
|
|
@ -65,19 +73,17 @@ public abstract class CompletionClientEventListener extends EventSourceListener
|
|||
|
||||
try {
|
||||
if (response == null) {
|
||||
onMessageReceived.accept(DEFAULT_ERROR_MSG);
|
||||
onFailure.accept(DEFAULT_ERROR_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
var body = response.body();
|
||||
if (body != null) {
|
||||
var error = new ObjectMapper().readValue(body.string(), ApiResponseError.class);
|
||||
onMessageReceived.accept(error.getError().getMessage());
|
||||
onFailure.accept(error.getError().getMessage());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
onMessageReceived.accept(DEFAULT_ERROR_MSG);
|
||||
} finally {
|
||||
onComplete.accept(messageBuilder.toString());
|
||||
onFailure.accept(DEFAULT_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,23 @@ package ee.carlrobert.chatgpt.client.official.chat;
|
|||
|
||||
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
|
||||
import ee.carlrobert.chatgpt.client.Client;
|
||||
import ee.carlrobert.chatgpt.client.ClientCode;
|
||||
import ee.carlrobert.chatgpt.client.official.chat.request.ApiRequest;
|
||||
import ee.carlrobert.chatgpt.client.official.chat.request.ApiRequestMessage;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.message.Message;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import okhttp3.sse.EventSourceListener;
|
||||
|
||||
public class ChatCompletionClient extends Client {
|
||||
|
||||
private static final List<Map.Entry<String, String>> queries = new ArrayList<>();
|
||||
private static ChatCompletionClient instance;
|
||||
|
||||
private ChatCompletionClient() {
|
||||
super(ClientCode.CHAT_COMPLETIONS);
|
||||
}
|
||||
|
||||
public static ChatCompletionClient getInstance() {
|
||||
|
|
@ -26,35 +28,38 @@ public class ChatCompletionClient extends Client {
|
|||
return instance;
|
||||
}
|
||||
|
||||
public void clearPreviousSession() {
|
||||
queries.clear();
|
||||
}
|
||||
|
||||
protected ApiRequestDetails getRequestDetails(String prompt) {
|
||||
var messages = new ArrayList<ApiRequestMessage>();
|
||||
messages.add(new ApiRequestMessage(
|
||||
"system",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."));
|
||||
queries.forEach(query -> {
|
||||
messages.add(new ApiRequestMessage("user", query.getKey()));
|
||||
messages.add(new ApiRequestMessage("assistant", query.getValue()));
|
||||
conversation.getMessages().forEach(message -> {
|
||||
messages.add(new ApiRequestMessage("user", message.getPrompt()));
|
||||
messages.add(new ApiRequestMessage("assistant", message.getResponse()));
|
||||
});
|
||||
messages.add(new ApiRequestMessage("user", prompt));
|
||||
|
||||
return new ApiRequestDetails(
|
||||
"https://api.openai.com/v1/chat/completions",
|
||||
new ApiRequest(
|
||||
SettingsState.getInstance().chatCompletionBaseModel.getModel(),
|
||||
SettingsState.getInstance().chatCompletionBaseModel.getCode(),
|
||||
true,
|
||||
messages
|
||||
),
|
||||
SettingsState.getInstance().apiKey);
|
||||
}
|
||||
|
||||
protected EventSourceListener getEventSourceListener(Consumer<String> onMessageReceived, Runnable onComplete) {
|
||||
protected EventSourceListener getEventSourceListener(
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<Conversation> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
return new ChatCompletionClientEventListener(client, onMessageReceived, finalMessage -> {
|
||||
queries.add(Map.entry(prompt, finalMessage));
|
||||
onComplete.run();
|
||||
});
|
||||
var message = new Message();
|
||||
message.setPrompt(prompt);
|
||||
message.setResponse(finalMessage);
|
||||
conversation.setUpdatedOn(LocalDateTime.now());
|
||||
conversation.addMessage(message);
|
||||
onComplete.accept(conversation);
|
||||
}, onFailure);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ import okhttp3.OkHttpClient;
|
|||
|
||||
public class ChatCompletionClientEventListener extends CompletionClientEventListener {
|
||||
|
||||
public ChatCompletionClientEventListener(OkHttpClient client, Consumer<String> onMessageReceived, Consumer<String> onComplete) {
|
||||
super(client, onMessageReceived, onComplete);
|
||||
public ChatCompletionClientEventListener(
|
||||
OkHttpClient client,
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<String> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
super(client, onMessageReceived, onComplete, onFailure);
|
||||
}
|
||||
|
||||
protected String getMessage(String data) throws JsonProcessingException {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ 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.client.ClientCode;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.message.Message;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.util.ArrayList;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
|
@ -14,10 +17,10 @@ import okhttp3.sse.EventSourceListener;
|
|||
|
||||
public class TextCompletionClient extends Client {
|
||||
|
||||
private static final List<Map.Entry<String, String>> queries = new ArrayList<>();
|
||||
private static TextCompletionClient instance;
|
||||
|
||||
private TextCompletionClient() {
|
||||
super(ClientCode.TEXT_COMPLETIONS);
|
||||
}
|
||||
|
||||
public static TextCompletionClient getInstance() {
|
||||
|
|
@ -27,14 +30,10 @@ public class TextCompletionClient extends Client {
|
|||
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().textCompletionBaseModel.getModel()),
|
||||
SettingsState.getInstance().textCompletionBaseModel.getCode()),
|
||||
Map.of(
|
||||
"stop", List.of(" Human:", " AI:"),
|
||||
"prompt", buildPrompt(prompt),
|
||||
|
|
@ -49,11 +48,18 @@ public class TextCompletionClient extends Client {
|
|||
SettingsState.getInstance().apiKey);
|
||||
}
|
||||
|
||||
protected EventSourceListener getEventSourceListener(Consumer<String> onMessageReceived, Runnable onComplete) {
|
||||
protected EventSourceListener getEventSourceListener(
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<Conversation> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
return new TextCompletionClientEventListener(client, onMessageReceived, (finalMessage) -> {
|
||||
queries.add(Map.entry(prompt, finalMessage));
|
||||
onComplete.run();
|
||||
});
|
||||
var message = new Message();
|
||||
message.setPrompt(prompt);
|
||||
message.setResponse(finalMessage);
|
||||
conversation.setUpdatedOn(LocalDateTime.now());
|
||||
conversation.addMessage(message);
|
||||
onComplete.accept(conversation);
|
||||
}, onFailure);
|
||||
}
|
||||
|
||||
private StringBuilder getBasePrompt() {
|
||||
|
|
@ -69,12 +75,12 @@ public class TextCompletionClient extends Client {
|
|||
|
||||
private String buildPrompt(String prompt) {
|
||||
var basePrompt = getBasePrompt();
|
||||
queries.forEach(query ->
|
||||
conversation.getMessages().forEach(message ->
|
||||
basePrompt.append("Human: ")
|
||||
.append(query.getKey())
|
||||
.append(message.getPrompt())
|
||||
.append("\n")
|
||||
.append("AI: ")
|
||||
.append(query.getValue())
|
||||
.append(message.getResponse())
|
||||
.append("\n"));
|
||||
basePrompt.append("Human: ")
|
||||
.append(prompt)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ import okhttp3.OkHttpClient;
|
|||
|
||||
public class TextCompletionClientEventListener extends CompletionClientEventListener {
|
||||
|
||||
public TextCompletionClientEventListener(OkHttpClient client, Consumer<String> onMessageReceived, Consumer<String> onComplete) {
|
||||
super(client, onMessageReceived, onComplete);
|
||||
public TextCompletionClientEventListener(
|
||||
OkHttpClient client,
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<String> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
super(client, onMessageReceived, onComplete, onFailure);
|
||||
}
|
||||
|
||||
protected String getMessage(String data) throws JsonProcessingException {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ package ee.carlrobert.chatgpt.client.unofficial;
|
|||
|
||||
import ee.carlrobert.chatgpt.client.ApiRequestDetails;
|
||||
import ee.carlrobert.chatgpt.client.Client;
|
||||
import ee.carlrobert.chatgpt.client.unofficial.response.ApiResponse;
|
||||
import ee.carlrobert.chatgpt.client.ClientCode;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.message.Message;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -14,9 +17,9 @@ import okhttp3.sse.EventSourceListener;
|
|||
public class UnofficialChatGPTClient extends Client {
|
||||
|
||||
private static UnofficialChatGPTClient instance;
|
||||
private static ApiResponse lastReceivedResponse;
|
||||
|
||||
private UnofficialChatGPTClient() {
|
||||
super(ClientCode.UNOFFICIAL_CHATGPT);
|
||||
}
|
||||
|
||||
public static UnofficialChatGPTClient getInstance() {
|
||||
|
|
@ -26,17 +29,21 @@ public class UnofficialChatGPTClient extends Client {
|
|||
return instance;
|
||||
}
|
||||
|
||||
public void clearPreviousSession() {
|
||||
lastReceivedResponse = null;
|
||||
}
|
||||
|
||||
protected EventSourceListener getEventSourceListener(Consumer<String> onMessageReceived, Runnable onComplete) {
|
||||
protected EventSourceListener getEventSourceListener(
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<Conversation> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
return new UnofficialClientEventListener(client, prompt, onMessageReceived, (response) -> {
|
||||
if (response != null) {
|
||||
lastReceivedResponse = response;
|
||||
}
|
||||
onComplete.run();
|
||||
});
|
||||
var message = new Message();
|
||||
message.setPrompt(prompt);
|
||||
message.setResponse(response.getFullMessage());
|
||||
|
||||
conversation.setUnofficialClientConversationId(response.getConversationId());
|
||||
conversation.setParentMessageId(response.getMessage().getId());
|
||||
conversation.setUpdatedOn(LocalDateTime.now());
|
||||
conversation.addMessage(message);
|
||||
onComplete.accept(conversation);
|
||||
}, onFailure);
|
||||
}
|
||||
|
||||
protected ApiRequestDetails getRequestDetails(String prompt) {
|
||||
|
|
@ -55,9 +62,12 @@ public class UnofficialChatGPTClient extends Client {
|
|||
"model", "text-davinci-002-render-sha"
|
||||
));
|
||||
|
||||
if (lastReceivedResponse != null) {
|
||||
payload.put("conversation_id", lastReceivedResponse.getConversationId());
|
||||
payload.put("parent_message_id", lastReceivedResponse.getMessage().getId());
|
||||
var conversationId = conversation.getUnofficialClientConversationId();
|
||||
var parentMessageId = conversation.getParentMessageId();
|
||||
if (conversationId != null && !conversationId.isEmpty() &&
|
||||
parentMessageId != null && !parentMessageId.isEmpty()) {
|
||||
payload.put("conversation_id", conversationId);
|
||||
payload.put("parent_message_id", parentMessageId);
|
||||
} else {
|
||||
payload.put("parent_message_id", UUID.randomUUID().toString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,23 @@ public class UnofficialClientEventListener extends EventSourceListener {
|
|||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final String prompt;
|
||||
private final Consumer<String> onMessageReceived;
|
||||
private final Consumer<ApiResponse> onComplete;
|
||||
private final Consumer<@NotNull ApiResponse> onComplete;
|
||||
private final Consumer<String> onFailure;
|
||||
private ApiResponse lastReceivedResponse;
|
||||
private boolean eventReceived = false;
|
||||
private final OkHttpClient client;
|
||||
|
||||
public UnofficialClientEventListener(OkHttpClient client, String prompt, Consumer<String> onMessageReceived, Consumer<ApiResponse> onComplete) {
|
||||
public UnofficialClientEventListener(
|
||||
OkHttpClient client,
|
||||
String prompt,
|
||||
Consumer<String> onMessageReceived,
|
||||
Consumer<ApiResponse> onComplete,
|
||||
Consumer<String> onFailure) {
|
||||
this.client = client;
|
||||
this.prompt = prompt;
|
||||
this.onMessageReceived = onMessageReceived;
|
||||
this.onComplete = onComplete;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
|
||||
|
|
@ -45,11 +52,7 @@ public class UnofficialClientEventListener extends EventSourceListener {
|
|||
var response = client.newCall(UnofficialChatGPTClient.getInstance().buildHttpRequest(prompt)).execute();
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
var message = tryExtractingErrorMessage(responseBody.string());
|
||||
message.ifPresent(msg -> {
|
||||
onMessageReceived.accept(msg);
|
||||
onComplete.accept(null);
|
||||
});
|
||||
tryExtractingErrorMessage(responseBody.string()).ifPresent(onFailure);
|
||||
responseBody.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
|
@ -93,9 +96,8 @@ public class UnofficialClientEventListener extends EventSourceListener {
|
|||
@Nullable Throwable ex,
|
||||
@Nullable Response response) {
|
||||
if (isRequestNotCancelled()) {
|
||||
onMessageReceived.accept("Something went wrong. Please try again later. ");
|
||||
onFailure.accept("Something went wrong. Please try again later.");
|
||||
}
|
||||
onComplete.accept(null);
|
||||
}
|
||||
|
||||
private boolean isRequestNotCancelled() {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import ee.carlrobert.chatgpt.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsState;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AskAction extends AnAction {
|
||||
|
|
@ -19,9 +21,20 @@ public class AskAction extends AnAction {
|
|||
if (project != null) {
|
||||
var toolWindowService = project.getService(ToolWindowService.class);
|
||||
var toolWindow = toolWindowService.getToolWindow(project);
|
||||
new ClientFactory().getClient().clearPreviousSession();
|
||||
var contentManager = toolWindow.getContentManager();
|
||||
|
||||
Arrays.stream(contentManager.getContents())
|
||||
.filter(it -> "Chat".equals(it.getTabName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
contentManager::setSelectedContent,
|
||||
() -> contentManager.setSelectedContent(Objects.requireNonNull(contentManager.getContent(0)))
|
||||
);
|
||||
|
||||
ConversationsState.getInstance().startConversation();
|
||||
|
||||
toolWindow.show();
|
||||
toolWindow.setTitle("");
|
||||
toolWindow.setTitle("Chat");
|
||||
toolWindowService.removeAll();
|
||||
toolWindowService.paintLandingView();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
|
|||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import ee.carlrobert.chatgpt.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsState;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class BaseAction extends AnAction {
|
||||
|
||||
protected abstract void initToolWindow(ToolWindow toolWindow);
|
||||
|
||||
protected abstract void actionPerformed(Project project, Editor editor, String selectedText);
|
||||
|
||||
public void actionPerformed(@NotNull AnActionEvent event) {
|
||||
|
|
@ -36,8 +35,20 @@ public abstract class BaseAction extends AnAction {
|
|||
|
||||
protected void sendMessage(Project project, String prompt) {
|
||||
var toolWindowService = project.getService(ToolWindowService.class);
|
||||
new ClientFactory().getClient().clearPreviousSession();
|
||||
initToolWindow(toolWindowService.getToolWindow(project));
|
||||
var toolWindow = toolWindowService.getToolWindow(project);
|
||||
var contentManager = toolWindow.getContentManager();
|
||||
|
||||
Arrays.stream(contentManager.getContents())
|
||||
.filter(it -> "Chat".equals(it.getTabName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
contentManager::setSelectedContent,
|
||||
() -> contentManager.setSelectedContent(Objects.requireNonNull(contentManager.getContent(0)))
|
||||
);
|
||||
|
||||
ConversationsState.getInstance().startConversation();
|
||||
|
||||
toolWindowService.getToolWindow(project).show();
|
||||
toolWindowService.removeAll();
|
||||
toolWindowService.paintUserMessage(prompt);
|
||||
toolWindowService.sendMessage(prompt, project, null);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
|
@ -12,11 +11,6 @@ public class CustomPromptAction extends BaseAction {
|
|||
|
||||
private static String previousUserPrompt = "";
|
||||
|
||||
protected void initToolWindow(ToolWindow toolWindow) {
|
||||
toolWindow.setTitle("Custom Prompt");
|
||||
toolWindow.show();
|
||||
}
|
||||
|
||||
protected void actionPerformed(Project project, Editor editor, String selectedText) {
|
||||
if (selectedText != null && !selectedText.isEmpty()) {
|
||||
var fileExtension = getFileExtension(((EditorImpl) editor).getVirtualFile().getName());
|
||||
|
|
|
|||
|
|
@ -2,15 +2,9 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
|
||||
public class ExplainAction extends BaseAction {
|
||||
|
||||
protected void initToolWindow(ToolWindow toolWindow) {
|
||||
toolWindow.setTitle("Explain Code");
|
||||
toolWindow.show();
|
||||
}
|
||||
|
||||
protected void actionPerformed(Project project, Editor editor, String selectedText) {
|
||||
sendMessage(project, "Explain the following code:\n\n" + selectedText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,9 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
|
||||
public class FindBugsAction extends BaseAction {
|
||||
|
||||
protected void initToolWindow(ToolWindow toolWindow) {
|
||||
toolWindow.setTitle("Find Bugs");
|
||||
toolWindow.show();
|
||||
}
|
||||
|
||||
protected void actionPerformed(Project project, Editor editor, String selectedText) {
|
||||
sendMessage(project, "Find bugs in the following code:\n\n" + selectedText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,9 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
|
||||
public class OptimizeAction extends BaseAction {
|
||||
|
||||
protected void initToolWindow(ToolWindow toolWindow) {
|
||||
toolWindow.setTitle("Optimize Code");
|
||||
toolWindow.show();
|
||||
}
|
||||
|
||||
protected void actionPerformed(Project project, Editor editor, String selectedText) {
|
||||
sendMessage(project, "Optimize the following code:\n\n" + selectedText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,9 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
|
||||
public class RefactorAction extends BaseAction {
|
||||
|
||||
protected void initToolWindow(ToolWindow toolWindow) {
|
||||
toolWindow.setTitle("Refactor Code");
|
||||
toolWindow.show();
|
||||
}
|
||||
|
||||
protected void actionPerformed(Project project, Editor editor, String selectedText) {
|
||||
sendMessage(project, "Refactor the following code:\n\n" + selectedText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,9 @@ package ee.carlrobert.chatgpt.ide.action;
|
|||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
|
||||
public class WriteTestsAction extends BaseAction {
|
||||
|
||||
protected void initToolWindow(ToolWindow toolWindow) {
|
||||
toolWindow.setTitle("Write Tests");
|
||||
toolWindow.show();
|
||||
}
|
||||
|
||||
protected void actionPerformed(Project project, Editor editor, String selectedText) {
|
||||
sendMessage(project, "Generate unit tests for the following code:\n\n" + selectedText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations;
|
||||
|
||||
import ee.carlrobert.chatgpt.client.BaseModel;
|
||||
import ee.carlrobert.chatgpt.client.ClientCode;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.message.Message;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Conversation {
|
||||
|
||||
private UUID id;
|
||||
private String parentMessageId;
|
||||
private String unofficialClientConversationId;
|
||||
private List<Message> messages = new ArrayList<>();
|
||||
private ClientCode clientCode;
|
||||
private BaseModel model;
|
||||
private LocalDateTime createdOn;
|
||||
private LocalDateTime updatedOn;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getParentMessageId() {
|
||||
return parentMessageId;
|
||||
}
|
||||
|
||||
public void setParentMessageId(String parentMessageId) {
|
||||
this.parentMessageId = parentMessageId;
|
||||
}
|
||||
|
||||
public String getUnofficialClientConversationId() {
|
||||
return unofficialClientConversationId;
|
||||
}
|
||||
|
||||
public void setUnofficialClientConversationId(String unofficialClientConversationId) {
|
||||
this.unofficialClientConversationId = unofficialClientConversationId;
|
||||
}
|
||||
|
||||
public List<Message> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setMessages(List<Message> messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public ClientCode getClientCode() {
|
||||
return clientCode;
|
||||
}
|
||||
|
||||
public void setClientCode(ClientCode clientCode) {
|
||||
this.clientCode = clientCode;
|
||||
}
|
||||
|
||||
public void addMessage(Message message) {
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
public BaseModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(BaseModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedOn() {
|
||||
return createdOn;
|
||||
}
|
||||
|
||||
public void setCreatedOn(LocalDateTime createdOn) {
|
||||
this.createdOn = createdOn;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedOn() {
|
||||
return updatedOn;
|
||||
}
|
||||
|
||||
public void setUpdatedOn(LocalDateTime updatedOn) {
|
||||
this.updatedOn = updatedOn;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations;
|
||||
|
||||
import ee.carlrobert.chatgpt.client.ClientCode;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ConversationsContainer {
|
||||
|
||||
private Map<ClientCode, List<Conversation>> conversationsMapping = new HashMap<>();
|
||||
|
||||
public Map<ClientCode, List<Conversation>> getConversationsMapping() {
|
||||
return conversationsMapping;
|
||||
}
|
||||
|
||||
public void setConversationsMapping(Map<ClientCode, List<Conversation>> conversationsMapping) {
|
||||
this.conversationsMapping = conversationsMapping;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil;
|
||||
import com.intellij.util.xmlb.annotations.OptionTag;
|
||||
import ee.carlrobert.chatgpt.client.ClientCode;
|
||||
import ee.carlrobert.chatgpt.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.converter.ConversationConverter;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.converter.ConversationsConverter;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@State(
|
||||
name = "ee.carlrobert.chatgpt.ide.conversations.ConversationsState",
|
||||
storages = @Storage("ChatGPTConversations.xml")
|
||||
)
|
||||
public class ConversationsState implements PersistentStateComponent<ConversationsState> {
|
||||
|
||||
@OptionTag(converter = ConversationsConverter.class)
|
||||
public ConversationsContainer conversationsContainer = new ConversationsContainer();
|
||||
|
||||
@OptionTag(converter = ConversationConverter.class)
|
||||
public Conversation currentConversation;
|
||||
|
||||
public static ConversationsState getInstance() {
|
||||
return ApplicationManager.getApplication().getService(ConversationsState.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ConversationsState getState() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull ConversationsState state) {
|
||||
XmlSerializerUtil.copyBean(state, this);
|
||||
}
|
||||
|
||||
public void setCurrentConversation(@Nullable Conversation conversation) {
|
||||
this.currentConversation = conversation;
|
||||
}
|
||||
|
||||
public static @Nullable Conversation getCurrentConversation() {
|
||||
return getInstance().currentConversation;
|
||||
}
|
||||
|
||||
public Conversation createConversation(ClientCode clientCode) {
|
||||
var settings = SettingsState.getInstance();
|
||||
var conversation = new Conversation();
|
||||
conversation.setId(UUID.randomUUID());
|
||||
conversation.setClientCode(clientCode);
|
||||
if (!settings.isChatGPTOptionSelected) {
|
||||
if (settings.isChatCompletionOptionSelected) {
|
||||
conversation.setModel(settings.chatCompletionBaseModel);
|
||||
} else {
|
||||
conversation.setModel(settings.textCompletionBaseModel);
|
||||
}
|
||||
}
|
||||
conversation.setCreatedOn(LocalDateTime.now());
|
||||
conversation.setUpdatedOn(LocalDateTime.now());
|
||||
return conversation;
|
||||
}
|
||||
|
||||
public void addConversation(Conversation conversation) {
|
||||
var conversationsMapping = conversationsContainer.getConversationsMapping();
|
||||
var conversations = conversationsMapping.get(conversation.getClientCode());
|
||||
if (conversations == null) {
|
||||
conversations = new ArrayList<>();
|
||||
}
|
||||
conversations.add(conversation);
|
||||
conversationsMapping.put(conversation.getClientCode(), conversations);
|
||||
}
|
||||
|
||||
public void saveConversation(Conversation conversation) {
|
||||
conversation.setUpdatedOn(LocalDateTime.now());
|
||||
var iterator = conversationsContainer.getConversationsMapping()
|
||||
.get(conversation.getClientCode())
|
||||
.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
var next = iterator.next();
|
||||
if (next.getId().equals(conversation.getId())) {
|
||||
iterator.set(conversation);
|
||||
}
|
||||
}
|
||||
setCurrentConversation(conversation);
|
||||
}
|
||||
|
||||
public Conversation startConversation() {
|
||||
var conversation = createConversation(new ClientFactory().getClient().getCode());
|
||||
setCurrentConversation(conversation);
|
||||
addConversation(conversation);
|
||||
return conversation;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations.converter;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.intellij.util.xmlb.Converter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
abstract class BaseConverter<T> extends Converter<T> {
|
||||
|
||||
private final Class<T> clazz;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.registerModule(new Jdk8Module())
|
||||
.registerModule(new JavaTimeModule());
|
||||
|
||||
BaseConverter(Class<T> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public @Nullable T fromString(@NotNull String value) {
|
||||
try {
|
||||
return objectMapper.readValue(value, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Unable to deserialize conversations", e);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String toString(@NotNull T value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Unable to serialize conversations", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations.converter;
|
||||
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
|
||||
public class ConversationConverter extends BaseConverter<Conversation> {
|
||||
|
||||
public ConversationConverter() {
|
||||
super(Conversation.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations.converter;
|
||||
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsContainer;
|
||||
|
||||
public class ConversationsConverter extends BaseConverter<ConversationsContainer> {
|
||||
|
||||
public ConversationsConverter() {
|
||||
super(ConversationsContainer.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package ee.carlrobert.chatgpt.ide.conversations.message;
|
||||
|
||||
public class Message {
|
||||
|
||||
private String prompt;
|
||||
private String response;
|
||||
|
||||
public String getPrompt() {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public String getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(String response) {
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package ee.carlrobert.chatgpt.ide.settings;
|
||||
|
||||
import com.intellij.openapi.options.Configurable;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsState;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
|
@ -36,18 +37,20 @@ public class SettingsConfigurable implements Configurable {
|
|||
!settingsComponent.getProxyHost().equals(settings.proxyHost) ||
|
||||
settingsComponent.getProxyPort() != settings.proxyPort ||
|
||||
!settingsComponent.getProxyType().equals(settings.proxyType) ||
|
||||
!settingsComponent.getReverseProxyUrl().equals(settings.reverseProxyUrl) ||
|
||||
!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;
|
||||
!settingsComponent.getReverseProxyUrl().equals(settings.reverseProxyUrl) ||
|
||||
isClientChanged(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
var settings = SettingsState.getInstance();
|
||||
|
||||
if (isClientChanged(settings)) {
|
||||
ConversationsState.getInstance().setCurrentConversation(null);
|
||||
}
|
||||
|
||||
settings.isGPTOptionSelected = settingsComponent.isGPTOptionSelected();
|
||||
settings.isChatGPTOptionSelected = settingsComponent.isChatGPTOptionSelected();
|
||||
settings.accessToken = settingsComponent.getAccessToken();
|
||||
|
|
@ -83,4 +86,11 @@ public class SettingsConfigurable implements Configurable {
|
|||
public void disposeUIResources() {
|
||||
settingsComponent = null;
|
||||
}
|
||||
|
||||
private boolean isClientChanged(SettingsState settings) {
|
||||
return settingsComponent.isGPTOptionSelected() != settings.isGPTOptionSelected ||
|
||||
settingsComponent.isChatCompletionOptionSelected() != settings.isChatCompletionOptionSelected ||
|
||||
settingsComponent.isTextCompletionOptionSelected() != settings.isTextCompletionOptionSelected ||
|
||||
settingsComponent.isChatGPTOptionSelected() != settings.isChatGPTOptionSelected;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="ee.carlrobert.chatgpt.ide.toolwindow.ChatGptToolWindow">
|
||||
<grid id="27dc6" binding="chatGptToolWindowContent" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="8" bottom="8" right="8"/>
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="530" height="400"/>
|
||||
</constraints>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package ee.carlrobert.chatgpt.ide.toolwindow;
|
|||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.JBScrollPane;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.components.GenerateButton;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.components.TextArea;
|
||||
import java.awt.Adjustable;
|
||||
|
|
@ -78,7 +80,7 @@ public class ChatGptToolWindow {
|
|||
textAreaScrollPane.setBorder(null);
|
||||
textAreaScrollPane.setViewportBorder(null);
|
||||
textAreaScrollPane.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createEmptyBorder(),
|
||||
BorderFactory.createMatteBorder(1, 0, 0, 0, JBColor.border()),
|
||||
BorderFactory.createEmptyBorder(0, 5, 0, 10)));
|
||||
textAreaScrollPane.setViewportView(textArea);
|
||||
|
||||
|
|
@ -90,13 +92,14 @@ public class ChatGptToolWindow {
|
|||
scrollPane = new JBScrollPane();
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.setViewportView(scrollablePanel);
|
||||
scrollPane.setBorder(null);
|
||||
scrollPane.setBorder(JBUI.Borders.empty(0, 8));
|
||||
scrollPane.setViewportBorder(null);
|
||||
|
||||
generateButton = new GenerateButton();
|
||||
|
||||
var toolWindowService = project.getService(ToolWindowService.class);
|
||||
toolWindowService.setGenerateButton((GenerateButton) generateButton); // TODO: Remove casting
|
||||
toolWindowService.setScrollPane(scrollPane);
|
||||
toolWindowService.setScrollablePanel(scrollablePanel);
|
||||
toolWindowService.paintLandingView();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,49 @@ import com.intellij.openapi.project.Project;
|
|||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import com.intellij.openapi.wm.ToolWindowFactory;
|
||||
import com.intellij.ui.content.ContentFactory;
|
||||
import com.intellij.ui.content.ContentManagerEvent;
|
||||
import com.intellij.ui.content.ContentManagerListener;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsState;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.conversations.ConversationsToolWindow;
|
||||
import java.util.Arrays;
|
||||
import javax.swing.JPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ChatGptToolWindowFactory implements ToolWindowFactory, DumbAware {
|
||||
|
||||
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
|
||||
var content = ApplicationManager.getApplication()
|
||||
addContent(toolWindow, new ChatGptToolWindow(project).getContent(), "Chat");
|
||||
|
||||
var conversationToolWidow = new ConversationsToolWindow(project, toolWindow);
|
||||
addContent(toolWindow, conversationToolWidow.getContent(), "Conversation History");
|
||||
toolWindow.addContentManagerListener(new ContentManagerListener() {
|
||||
public void selectionChanged(@NotNull ContentManagerEvent event) {
|
||||
if ("Conversation History".equals(event.getContent().getTabName()) && event.getContent().isSelected()) {
|
||||
conversationToolWidow.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var conversation = ConversationsState.getCurrentConversation();
|
||||
if (conversation != null) {
|
||||
var toolWindowService = project.getService(ToolWindowService.class);
|
||||
var contentManager = toolWindow.getContentManager();
|
||||
Arrays.stream(contentManager.getContents())
|
||||
.filter(content -> "Chat".equals(content.getTabName()))
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
content -> {
|
||||
if (contentManager.isSelected(content)) {
|
||||
toolWindowService.displayConversation(conversation);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void addContent(@NotNull ToolWindow toolWindow, JPanel content, String displayName) {
|
||||
toolWindow.getContentManager().addContent(ApplicationManager.getApplication()
|
||||
.getService(ContentFactory.class)
|
||||
.createContent(new ChatGptToolWindow(project).getContent(), "", false);
|
||||
toolWindow.getContentManager().addContent(content);
|
||||
.createContent(content, displayName, false));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.createTextArea
|
|||
import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.justifyLeft;
|
||||
import static java.lang.String.format;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.ui.LafManager;
|
||||
import com.intellij.ide.ui.LafManagerListener;
|
||||
import com.intellij.openapi.options.ShowSettingsUtil;
|
||||
|
|
@ -13,14 +14,20 @@ 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.client.ClientFactory;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsState;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.message.Message;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsConfigurable;
|
||||
import ee.carlrobert.chatgpt.ide.settings.SettingsState;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.components.GenerateButton;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.components.GenerateButton.Mode;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.components.SyntaxTextArea;
|
||||
import icons.Icons;
|
||||
import java.awt.Adjustable;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.AdjustmentEvent;
|
||||
import java.awt.event.AdjustmentListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
|
@ -28,9 +35,11 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollBar;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
|
|
@ -41,28 +50,40 @@ public class ToolWindowService implements LafManagerListener {
|
|||
private static final List<SyntaxTextArea> textAreas = new ArrayList<>();
|
||||
private boolean isLandingViewVisible;
|
||||
private ScrollablePanel scrollablePanel;
|
||||
private JScrollPane scrollPane;
|
||||
private GenerateButton generateButton;
|
||||
|
||||
public void setScrollablePanel(ScrollablePanel scrollablePanel) {
|
||||
this.scrollablePanel = scrollablePanel;
|
||||
}
|
||||
|
||||
public void setScrollPane(JScrollPane scrollPane) {
|
||||
this.scrollPane = scrollPane;
|
||||
}
|
||||
|
||||
public void setGenerateButton(GenerateButton generateButton) {
|
||||
this.generateButton = generateButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||
for (var textArea : textAreas) {
|
||||
textArea.changeStyleViaThemeXml();
|
||||
}
|
||||
}
|
||||
|
||||
public ToolWindow getToolWindow(@NotNull Project project) {
|
||||
return ToolWindowManager.getInstance(project).getToolWindow("CodeGPT");
|
||||
}
|
||||
|
||||
public void paintUserMessage(String userMessage) {
|
||||
if (isLandingViewVisible) {
|
||||
if (isLandingViewVisible || ConversationsState.getCurrentConversation() == null) {
|
||||
removeAll();
|
||||
}
|
||||
addSpacing(8);
|
||||
addIconLabel(Icons.UserImageIcon, "User:");
|
||||
addIconLabel(AllIcons.General.User, "User:");
|
||||
addSpacing(8);
|
||||
scrollablePanel.add(createTextArea(userMessage, true));
|
||||
scrollablePanel.add(createTextArea(userMessage));
|
||||
}
|
||||
|
||||
public void sendMessage(String prompt, Project project, @Nullable Runnable scrollToBottom) {
|
||||
|
|
@ -76,6 +97,12 @@ public class ToolWindowService implements LafManagerListener {
|
|||
} else if (settings.isChatGPTOptionSelected && settings.accessToken.isEmpty()) {
|
||||
notifyMissingCredential(project, "Access token not provided.");
|
||||
} else {
|
||||
var conversationsState = ConversationsState.getInstance();
|
||||
var conversation = ConversationsState.getCurrentConversation();
|
||||
if (conversation == null) {
|
||||
conversation = conversationsState.startConversation();
|
||||
}
|
||||
|
||||
var textArea = new SyntaxTextArea(true, true, SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
|
||||
scrollablePanel.add(textArea);
|
||||
textAreas.add(textArea);
|
||||
|
|
@ -83,35 +110,64 @@ public class ToolWindowService implements LafManagerListener {
|
|||
var client = new ClientFactory().getClient();
|
||||
generateButton.setVisible(true);
|
||||
generateButton.setMode(Mode.STOP, client::cancelRequest);
|
||||
client.getCompletionsAsync(prompt, message -> {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(
|
||||
() -> {
|
||||
textArea.append(message);
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.run();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (InterruptedException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, () -> {
|
||||
generateButton.setMode(Mode.REFRESH, () -> {
|
||||
sendMessage(prompt, project, scrollToBottom);
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.run();
|
||||
}
|
||||
});
|
||||
textArea.displayCopyButton();
|
||||
textArea.hideCaret();
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.run();
|
||||
}
|
||||
});
|
||||
|
||||
var conversationMessage = new Message();
|
||||
conversationMessage.setPrompt(prompt);
|
||||
client.getCompletionsAsync(
|
||||
conversation,
|
||||
prompt,
|
||||
message -> {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(
|
||||
() -> {
|
||||
textArea.append(message);
|
||||
// TODO: Should we set the text everytime?
|
||||
conversationMessage.setResponse(textArea.getText());
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.run();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (InterruptedException | InvocationTargetException e) {
|
||||
textArea.append("Something went wrong. Please try again later.");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
},
|
||||
(completedConversation) -> {
|
||||
ConversationsState.getInstance().saveConversation(completedConversation);
|
||||
stopGenerating(prompt, textArea, project, scrollToBottom);
|
||||
},
|
||||
(errorMessage) -> {
|
||||
var currentConversation = ConversationsState.getCurrentConversation();
|
||||
conversationMessage.setResponse(errorMessage);
|
||||
currentConversation.addMessage(conversationMessage);
|
||||
ConversationsState.getInstance().saveConversation(currentConversation);
|
||||
|
||||
textArea.append(errorMessage);
|
||||
stopGenerating(prompt, textArea, project, scrollToBottom);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void displayConversation(Conversation conversation) {
|
||||
removeAll();
|
||||
conversation.getMessages().forEach(message -> {
|
||||
paintUserMessage(message.getPrompt());
|
||||
|
||||
addSpacing(8);
|
||||
addIconLabel(Icons.DefaultImageIcon, "ChatGPT:");
|
||||
addSpacing(8);
|
||||
|
||||
var textArea = new SyntaxTextArea(true, true, SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
|
||||
textArea.setText(message.getResponse());
|
||||
textArea.displayCopyButton();
|
||||
textArea.hideCaret();
|
||||
scrollablePanel.add(textArea);
|
||||
textAreas.add(textArea);
|
||||
});
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
public void paintLandingView() {
|
||||
isLandingViewVisible = true;
|
||||
|
||||
|
|
@ -145,12 +201,26 @@ public class ToolWindowService implements LafManagerListener {
|
|||
scrollablePanel.removeAll();
|
||||
}
|
||||
|
||||
private void stopGenerating(String prompt, SyntaxTextArea textArea, Project project, @Nullable Runnable scrollToBottom) {
|
||||
generateButton.setMode(Mode.REFRESH, () -> {
|
||||
sendMessage(prompt, project, scrollToBottom);
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.run();
|
||||
}
|
||||
});
|
||||
textArea.displayCopyButton();
|
||||
textArea.hideCaret();
|
||||
if (scrollToBottom != null) {
|
||||
scrollToBottom.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void addSpacing(int height) {
|
||||
scrollablePanel.add(Box.createVerticalStrut(height));
|
||||
}
|
||||
|
||||
private void addIconLabel(ImageIcon imageIcon, String text) {
|
||||
scrollablePanel.add(justifyLeft(createIconLabel(imageIcon, text)));
|
||||
private void addIconLabel(Icon icon, String text) {
|
||||
scrollablePanel.add(justifyLeft(createIconLabel(icon, text)));
|
||||
}
|
||||
|
||||
private void notifyMissingCredential(Project project, String text) {
|
||||
|
|
@ -164,10 +234,14 @@ public class ToolWindowService implements LafManagerListener {
|
|||
scrollablePanel.add(justifyLeft(label));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||
for (var textArea : textAreas) {
|
||||
textArea.changeStyleViaThemeXml();
|
||||
}
|
||||
private void scrollToBottom() {
|
||||
JScrollBar verticalBar = scrollPane.getVerticalScrollBar();
|
||||
verticalBar.addAdjustmentListener(new AdjustmentListener() {
|
||||
public void adjustmentValueChanged(AdjustmentEvent e) {
|
||||
Adjustable adjustable = e.getAdjustable();
|
||||
adjustable.setValue(adjustable.getMaximum());
|
||||
verticalBar.removeAdjustmentListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import java.awt.event.ActionEvent;
|
|||
import javax.swing.AbstractAction;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JTextArea;
|
||||
|
|
@ -16,32 +16,30 @@ import javax.swing.KeyStroke;
|
|||
|
||||
public class ToolWindowUtil {
|
||||
|
||||
public static JTextArea createTextArea(String selectedText, boolean isItalicFont) {
|
||||
public static JTextArea createTextArea(String selectedText) {
|
||||
var textArea = new JTextArea();
|
||||
textArea.append(selectedText);
|
||||
textArea.setLineWrap(true);
|
||||
textArea.setEditable(false);
|
||||
var font = textArea.getFont();
|
||||
textArea.setFont(font.deriveFont(isItalicFont ? Font.ITALIC : Font.PLAIN));
|
||||
textArea.setFont(textArea.getFont().deriveFont(Font.ITALIC));
|
||||
textArea.setWrapStyleWord(true);
|
||||
textArea.setBackground(JBColor.PanelBackground);
|
||||
// textArea.setBorder(new MatteBorder(0, 2, 0, 0, JBColor.RED));
|
||||
return textArea;
|
||||
}
|
||||
|
||||
public static JLabel createIconLabel(ImageIcon imageIcon, String text) {
|
||||
var iconLabel = new JLabel(imageIcon);
|
||||
public static JLabel createIconLabel(Icon icon, String text) {
|
||||
var iconLabel = new JLabel(icon);
|
||||
iconLabel.setText(text);
|
||||
iconLabel.setFont(iconLabel.getFont().deriveFont(iconLabel.getFont().getStyle() | Font.BOLD));
|
||||
iconLabel.setIconTextGap(8);
|
||||
return iconLabel;
|
||||
}
|
||||
|
||||
public static JButton createIconButton(ImageIcon imageIcon) {
|
||||
var button = new JButton(imageIcon);
|
||||
public static JButton createIconButton(Icon icon) {
|
||||
var button = new JButton(icon);
|
||||
button.setBorder(BorderFactory.createEmptyBorder());
|
||||
button.setContentAreaFilled(false);
|
||||
button.setPreferredSize(new Dimension(imageIcon.getIconWidth(), imageIcon.getIconHeight()));
|
||||
button.setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));
|
||||
return button;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package ee.carlrobert.chatgpt.ide.toolwindow.components;
|
||||
|
||||
import icons.Icons;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ public class GenerateButton extends JButton {
|
|||
|
||||
public void setMode(Mode mode, Runnable onClick) {
|
||||
var isStopMode = mode == Mode.STOP;
|
||||
setIcon(isStopMode ? Icons.SquareIcon : Icons.RefreshIcon);
|
||||
setIcon(isStopMode ? AllIcons.Actions.Suspend : AllIcons.Actions.Refresh);
|
||||
setText(isStopMode ? "Stop generating" : "Regenerate response");
|
||||
for (var listener : getActionListeners()) {
|
||||
removeActionListener(listener);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package ee.carlrobert.chatgpt.ide.toolwindow.components;
|
|||
|
||||
import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.createIconButton;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import icons.Icons;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
|
|
@ -65,10 +65,10 @@ public class SyntaxTextArea extends RSyntaxTextArea {
|
|||
}
|
||||
|
||||
private JButton createCopyButton() {
|
||||
var button = createIconButton(Icons.CopyImageIcon);
|
||||
var button = createIconButton(AllIcons.General.InlineCopy);
|
||||
button.addActionListener(e -> {
|
||||
copyToClipboard();
|
||||
button.setIcon(Icons.DoubleTickImageIcon);
|
||||
button.setIcon(AllIcons.General.InspectionsOK);
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.addShiftEnterI
|
|||
import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.createIconButton;
|
||||
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.JBTextArea;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import icons.Icons;
|
||||
import java.awt.event.ActionListener;
|
||||
|
|
@ -11,9 +12,8 @@ import java.awt.event.FocusEvent;
|
|||
import java.awt.event.FocusListener;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
public class TextArea extends JTextArea {
|
||||
public class TextArea extends JBTextArea {
|
||||
|
||||
public TextArea(Runnable onSubmit, JScrollPane textAreaScrollPane) {
|
||||
super("Ask me anything...");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
package ee.carlrobert.chatgpt.ide.toolwindow.conversations;
|
||||
|
||||
import static ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowUtil.justifyLeft;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
class ConversationPanel extends JPanel {
|
||||
|
||||
private final Conversation conversation;
|
||||
|
||||
ConversationPanel(Conversation conversation, boolean isSelected) {
|
||||
this.conversation = conversation;
|
||||
addStyles(isSelected);
|
||||
|
||||
var constraints = new GridBagConstraints();
|
||||
constraints.insets = JBUI.insets(0, 10);
|
||||
addChatIcon(constraints);
|
||||
addTextPanel(constraints);
|
||||
}
|
||||
|
||||
private void addStyles(boolean isSelected) {
|
||||
setBackground(JBColor.background().darker());
|
||||
if (isSelected) {
|
||||
setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(4, 4, 4, 4, JBColor.green),
|
||||
JBUI.Borders.empty(10)));
|
||||
} else {
|
||||
setBorder(JBUI.Borders.empty(10));
|
||||
}
|
||||
setLayout(new GridBagLayout());
|
||||
setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
|
||||
private void addChatIcon(GridBagConstraints constraints) {
|
||||
constraints.gridx = 0;
|
||||
constraints.gridy = 0;
|
||||
constraints.weightx = 0.0;
|
||||
constraints.fill = GridBagConstraints.NONE;
|
||||
add(new JLabel(AllIcons.Actions.Annotate), constraints);
|
||||
}
|
||||
|
||||
private void addTextPanel(GridBagConstraints constraints) {
|
||||
constraints.gridx = 1;
|
||||
constraints.weightx = 1.0;
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(createTextPanel(), constraints);
|
||||
}
|
||||
|
||||
private JPanel createTextPanel() {
|
||||
var title = new JLabel(getFirstPrompt());
|
||||
title.setBorder(JBUI.Borders.emptyBottom(8));
|
||||
title.setFont(title.getFont().deriveFont(title.getFont().getStyle() | Font.BOLD));
|
||||
|
||||
var textPanel = new JPanel();
|
||||
textPanel.setBackground(getBackground());
|
||||
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.PAGE_AXIS));
|
||||
textPanel.add(justifyLeft(title));
|
||||
|
||||
var bottomPanel = new JPanel();
|
||||
bottomPanel.setBackground(getBackground());
|
||||
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
|
||||
bottomPanel.add(new JLabel(conversation.getUpdatedOn()
|
||||
.format(DateTimeFormatter.ofPattern("M/d/yyyy, h:mm:ss a"))));
|
||||
bottomPanel.add(Box.createHorizontalGlue());
|
||||
if (conversation.getModel() != null) {
|
||||
bottomPanel.add(new JLabel(conversation.getModel().getCode()));
|
||||
}
|
||||
textPanel.add(bottomPanel);
|
||||
return textPanel;
|
||||
}
|
||||
|
||||
private String getFirstPrompt() {
|
||||
var messages = conversation.getMessages();
|
||||
var prompt = "";
|
||||
if (!messages.isEmpty()) {
|
||||
prompt = conversation.getMessages().get(0).getPrompt();
|
||||
}
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="ee.carlrobert.chatgpt.ide.toolwindow.conversations.ConversationsToolWindow">
|
||||
<grid id="27dc6" binding="conversationsToolWindowContent" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<scrollpane id="77046" binding="scrollPane" custom-create="true">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</scrollpane>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package ee.carlrobert.chatgpt.ide.toolwindow.conversations;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import com.intellij.ui.components.JBScrollPane;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.Conversation;
|
||||
import ee.carlrobert.chatgpt.ide.conversations.ConversationsState;
|
||||
import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ConversationsToolWindow {
|
||||
|
||||
private final Project project;
|
||||
private final ToolWindow toolWindow;
|
||||
private JPanel conversationsToolWindowContent;
|
||||
private JScrollPane scrollPane;
|
||||
private ScrollablePanel scrollablePanel;
|
||||
|
||||
public ConversationsToolWindow(@NotNull Project project, @NotNull ToolWindow toolWindow) {
|
||||
this.project = project;
|
||||
this.toolWindow = toolWindow;
|
||||
refresh();
|
||||
}
|
||||
|
||||
public JPanel getContent() {
|
||||
return conversationsToolWindowContent;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
scrollablePanel.removeAll();
|
||||
ConversationsState.getInstance()
|
||||
.conversationsContainer
|
||||
.getConversationsMapping()
|
||||
.forEach((key, value) -> value.stream()
|
||||
.sorted(Comparator.comparing(Conversation::getUpdatedOn).reversed())
|
||||
.forEach(this::addContent));
|
||||
}
|
||||
|
||||
private void addContent(Conversation conversation) {
|
||||
var mainPanel = new RootConversationPanel(() -> {
|
||||
ConversationsState.getInstance().setCurrentConversation(conversation);
|
||||
|
||||
var toolWindowService = project.getService(ToolWindowService.class);
|
||||
var contentManager = toolWindow.getContentManager();
|
||||
Arrays.stream(contentManager.getContents())
|
||||
.filter(content -> "Chat".equals(content.getTabName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
contentManager::setSelectedContent,
|
||||
() -> contentManager.setSelectedContent(Objects.requireNonNull(contentManager.getContent(0)))
|
||||
);
|
||||
toolWindowService.displayConversation(conversation);
|
||||
});
|
||||
|
||||
var currentConversation = ConversationsState.getCurrentConversation();
|
||||
var isSelected = currentConversation != null && currentConversation.getId().equals(conversation.getId());
|
||||
mainPanel.setBackground(conversationsToolWindowContent.getBackground());
|
||||
mainPanel.add(new ConversationPanel(conversation, isSelected));
|
||||
scrollablePanel.add(mainPanel);
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
scrollablePanel = new ScrollablePanel();
|
||||
scrollablePanel.setLayout(new BoxLayout(scrollablePanel, BoxLayout.Y_AXIS));
|
||||
|
||||
scrollPane = new JBScrollPane();
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.setViewportView(scrollablePanel);
|
||||
scrollPane.setBorder(null);
|
||||
scrollPane.setViewportBorder(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package ee.carlrobert.chatgpt.ide.toolwindow.conversations;
|
||||
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
class RootConversationPanel extends JPanel {
|
||||
|
||||
RootConversationPanel(Runnable onClick) {
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
setBorder(JBUI.Borders.empty(10, 20));
|
||||
setBackground(JBColor.background());
|
||||
addMouseListener(getMouseListener(onClick));
|
||||
}
|
||||
|
||||
private MouseListener getMouseListener(Runnable onClick) {
|
||||
return new MouseListener() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent mouseEvent) {
|
||||
onClick.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent mouseEvent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -7,17 +7,11 @@ import javax.swing.ImageIcon;
|
|||
|
||||
public class Icons {
|
||||
|
||||
public static final Icon DefaultIcon = IconLoader.getIcon("/icons/chatgpt-icon.svg", Icons.class);
|
||||
public static final Icon SquareIcon = IconLoader.getIcon("/icons/square-icon.svg", Icons.class);
|
||||
public static final Icon RefreshIcon = IconLoader.getIcon("/icons/refresh-icon.svg", Icons.class);
|
||||
public static final Icon CommandIcon = IconLoader.getIcon("/icons/command-icon.svg", Icons.class);
|
||||
public static final Icon QuestionMarkIcon = IconLoader.getIcon("/icons/question-mark-icon.svg", Icons.class);
|
||||
public static final Icon DefaultIcon = IconLoader.getIcon("/icons/codegpt-icon.svg", Icons.class);
|
||||
public static final Icon ToolWindowIcon = IconLoader.getIcon("/icons/toolwindow-icon.svg", Icons.class);
|
||||
public static final ImageIcon DefaultImageIcon = getImageIcon("/icons/chatgpt-icon.png");
|
||||
public static final ImageIcon SendImageIcon = getImageIcon("/icons/send-icon.png");
|
||||
public static final ImageIcon SunImageIcon = getImageIcon("/icons/sun-icon.png");
|
||||
public static final ImageIcon UserImageIcon = getImageIcon("/icons/user-icon.png");
|
||||
public static final ImageIcon CopyImageIcon = getImageIcon("/icons/copy-icon.png");
|
||||
public static final ImageIcon DoubleTickImageIcon = getImageIcon("/icons/double-tick-icon.png");
|
||||
|
||||
private static ImageIcon getImageIcon(String path) {
|
||||
return new ImageIcon(Objects.requireNonNull(Icons.class.getResource(path)));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue