Add conversation history, fix reported bugs, visual improvements #9)
|
|
@ -20,6 +20,8 @@ dependencies {
|
|||
implementation("com.fifesoft:rsyntaxtextarea:3.3.2")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.10.0")
|
||||
implementation("com.squareup.okhttp3:okhttp-sse:4.10.0")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.2")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2")
|
||||
}
|
||||
|
||||
java {
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -110,12 +110,12 @@
|
|||
</projectListeners>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationConfigurable parentId="tools" instance="ee.carlrobert.chatgpt.ide.settings.SettingsConfigurable"
|
||||
id="org.intellij.sdk.settings.AppSettingsConfigurable"
|
||||
displayName="CodeGPT"/>
|
||||
<applicationConfigurable id="settings.codegpt" parentId="tools" displayName="CodeGPT"
|
||||
instance="ee.carlrobert.chatgpt.ide.settings.SettingsConfigurable"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.chatgpt.ide.settings.SettingsState"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.chatgpt.ide.conversations.ConversationsState"/>
|
||||
<projectService serviceImplementation="ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService"/>
|
||||
<toolWindow id="CodeGPT" icon="Icons.DefaultIcon" anchor="right"
|
||||
<toolWindow id="CodeGPT" icon="Icons.ToolWindowIcon" anchor="right"
|
||||
factoryClass="ee.carlrobert.chatgpt.ide.toolwindow.ChatGptToolWindowFactory"/>
|
||||
</extensions>
|
||||
|
||||
|
|
@ -123,28 +123,16 @@
|
|||
|
||||
<actions>
|
||||
<group id="CodeGPTEditorPopup">
|
||||
<group id="ActionGroup"
|
||||
class="ee.carlrobert.chatgpt.ide.action.ActionGroup"
|
||||
popup="true">
|
||||
<action id="AskAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.AskAction"
|
||||
icon="Icons.QuestionMarkIcon">
|
||||
</action>
|
||||
<group id="ActionGroup" class="ee.carlrobert.chatgpt.ide.action.ActionGroup" popup="true" icon="Icons.DefaultIcon">
|
||||
<action id="AskAction" class="ee.carlrobert.chatgpt.ide.action.AskAction" icon="AllIcons.Actions.Find"/>
|
||||
<separator/>
|
||||
<action id="CustomPromptAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.CustomPromptAction"
|
||||
icon="Icons.CommandIcon"/>
|
||||
<action id="CustomPromptAction" class="ee.carlrobert.chatgpt.ide.action.CustomPromptAction" icon="AllIcons.Actions.Run_anything"/>
|
||||
<separator/>
|
||||
<action id="WriteTestsAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.WriteTestsAction"/>
|
||||
<action id="FindBugsAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.FindBugsAction"/>
|
||||
<action id="RefactorAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.RefactorAction"/>
|
||||
<action id="OptimizeAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.OptimizeAction"/>
|
||||
<action id="ExplainAction"
|
||||
class="ee.carlrobert.chatgpt.ide.action.ExplainAction"/>
|
||||
<action id="WriteTestsAction" class="ee.carlrobert.chatgpt.ide.action.WriteTestsAction"/>
|
||||
<action id="FindBugsAction" class="ee.carlrobert.chatgpt.ide.action.FindBugsAction"/>
|
||||
<action id="RefactorAction" class="ee.carlrobert.chatgpt.ide.action.RefactorAction"/>
|
||||
<action id="OptimizeAction" class="ee.carlrobert.chatgpt.ide.action.OptimizeAction"/>
|
||||
<action id="ExplainAction" class="ee.carlrobert.chatgpt.ide.action.ExplainAction"/>
|
||||
</group>
|
||||
<add-to-group group-id="EditorPopupMenu1" anchor="first"/>
|
||||
<separator/>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality"
|
||||
fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 512">
|
||||
<rect fill="#000" width="512" height="512" rx="104.187" ry="105.042"/>
|
||||
<path fill="#fff" fill-rule="nonzero"
|
||||
d="M378.68 230.011a71.432 71.432 0 003.654-22.541 71.383 71.383 0 00-9.783-36.064c-12.871-22.404-36.747-36.236-62.587-36.236a72.31 72.31 0 00-15.145 1.604 71.362 71.362 0 00-53.37-23.991h-.453l-.17.001c-31.297 0-59.052 20.195-68.673 49.967a71.372 71.372 0 00-47.709 34.618 72.224 72.224 0 00-9.755 36.226 72.204 72.204 0 0018.628 48.395 71.395 71.395 0 00-3.655 22.541 71.388 71.388 0 009.783 36.064 72.187 72.187 0 0077.728 34.631 71.375 71.375 0 0053.374 23.992H271l.184-.001c31.314 0 59.06-20.196 68.681-49.995a71.384 71.384 0 0047.71-34.619 72.107 72.107 0 009.736-36.194 72.201 72.201 0 00-18.628-48.394l-.003-.004zM271.018 380.492h-.074a53.576 53.576 0 01-34.287-12.423 44.928 44.928 0 001.694-.96l57.032-32.943a9.278 9.278 0 004.688-8.06v-80.459l24.106 13.919a.859.859 0 01.469.661v66.586c-.033 29.604-24.022 53.619-53.628 53.679zm-115.329-49.257a53.563 53.563 0 01-7.196-26.798c0-3.069.268-6.146.79-9.17.424.254 1.164.706 1.695 1.011l57.032 32.943a9.289 9.289 0 009.37-.002l69.63-40.205v27.839l.001.048a.864.864 0 01-.345.691l-57.654 33.288a53.791 53.791 0 01-26.817 7.17 53.746 53.746 0 01-46.506-26.818v.003zm-15.004-124.506a53.5 53.5 0 0127.941-23.534c0 .491-.028 1.361-.028 1.965v65.887l-.001.054a9.27 9.27 0 004.681 8.053l69.63 40.199-24.105 13.919a.864.864 0 01-.813.074l-57.66-33.316a53.746 53.746 0 01-26.805-46.5 53.787 53.787 0 017.163-26.798l-.003-.003zm198.055 46.089l-69.63-40.204 24.106-13.914a.863.863 0 01.813-.074l57.659 33.288a53.71 53.71 0 0126.835 46.491c0 22.489-14.033 42.612-35.133 50.379v-67.857c.003-.025.003-.051.003-.076a9.265 9.265 0 00-4.653-8.033zm23.993-36.111a81.919 81.919 0 00-1.694-1.01l-57.032-32.944a9.31 9.31 0 00-4.684-1.266 9.31 9.31 0 00-4.684 1.266l-69.631 40.205v-27.839l-.001-.048c0-.272.129-.528.346-.691l57.654-33.26a53.696 53.696 0 0126.816-7.177c29.644 0 53.684 24.04 53.684 53.684a53.91 53.91 0 01-.774 9.077v.003zm-150.831 49.618l-24.111-13.919a.859.859 0 01-.469-.661v-66.587c.013-29.628 24.053-53.648 53.684-53.648a53.719 53.719 0 0134.349 12.426c-.434.237-1.191.655-1.694.96l-57.032 32.943a9.272 9.272 0 00-4.687 8.057v.053l-.04 80.376zm13.095-28.233l31.012-17.912 31.012 17.9v35.812l-31.012 17.901-31.012-17.901v-35.8z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
1
src/main/resources/icons/codegpt-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="16px" height="16px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#000000"></stop><stop offset="0.699" stop-color="#000000"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#ffffff"></stop><stop offset="1" stop-color="#ffffff"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
1
src/main/resources/icons/codegpt-icon_dark.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="16px" height="16px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#ffffff"></stop><stop offset="0.699" stop-color="#ffffff"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#000000"></stop><stop offset="1" stop-color="#000000"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#ffffff" fill-rule="nonzero" stroke="#ffffff" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;"
|
||||
d="M 14.222656 2.222656 L 1.777344 2.222656 C 1.285156 2.222656 0.890625 2.621094 0.890625 3.109375 L 0.890625 12.890625 C 0.890625 13.378906 1.285156 13.777344 1.777344 13.777344 L 14.222656 13.777344 C 14.714844 13.777344 15.109375 13.378906 15.109375 12.890625 L 15.109375 3.109375 C 15.109375 2.621094 14.714844 2.222656 14.222656 2.222656 Z M 3.023438 7.027344 L 3.023438 5.851562 L 7.464844 7.894531 L 7.464844 8.816406 L 3.023438 10.859375 L 3.023438 9.679688 L 5.914062 8.347656 Z M 10.398438 11.289062 L 7.554688 11.289062 L 7.554688 10.222656 L 10.398438 10.222656 Z M 1.777344 4.089844 L 1.777344 3.109375 L 14.222656 3.109375 L 14.222656 4.089844 Z M 1.777344 4.089844 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1,007 B |
|
Before Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 371 B |
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;"
|
||||
d="M 8 2 C 4.691406 2 2 4.691406 2 8 C 2 11.308594 4.691406 14 8 14 C 11.308594 14 14 11.308594 14 8 C 14 4.691406 11.308594 2 8 2 Z M 8 13 C 5.238281 13 3 10.761719 3 8 C 3 5.238281 5.238281 3 8 3 C 10.761719 3 13 5.238281 13 8 C 13 10.761719 10.761719 13 8 13 Z M 9.535156 5.132812 C 9.941406 5.539062 10.167969 6.085938 10.167969 6.667969 C 10.167969 7.246094 9.941406 7.785156 9.535156 8.199219 C 9.246094 8.488281 8.886719 8.6875 8.5 8.773438 L 8.5 9 C 8.5 9.273438 8.273438 9.5 8 9.5 C 7.726562 9.5 7.5 9.273438 7.5 9 L 7.5 8.332031 C 7.5 8.058594 7.726562 7.832031 8 7.832031 C 8.3125 7.832031 8.605469 7.714844 8.828125 7.492188 C 9.046875 7.273438 9.167969 6.980469 9.167969 6.667969 C 9.167969 6.351562 9.046875 6.058594 8.828125 5.839844 C 8.386719 5.398438 7.621094 5.398438 7.179688 5.839844 C 6.960938 6.058594 6.839844 6.351562 6.839844 6.667969 C 6.839844 6.941406 6.613281 7.167969 6.339844 7.167969 C 6.066406 7.167969 5.839844 6.941406 5.839844 6.667969 C 5.839844 6.085938 6.066406 5.546875 6.472656 5.132812 C 7.292969 4.3125 8.71875 4.3125 9.539062 5.132812 Z M 8.667969 10.832031 C 8.667969 11.199219 8.367188 11.5 8 11.5 C 7.632812 11.5 7.332031 11.199219 7.332031 10.832031 C 7.332031 10.464844 7.632812 10.167969 8 10.167969 C 8.367188 10.167969 8.667969 10.464844 8.667969 10.832031 Z M 8.667969 10.832031 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,10 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0,0,256,256" width="12px" height="12px"
|
||||
fill-rule="nonzero">
|
||||
<g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10"
|
||||
stroke-dasharray="" stroke-dashoffset="0">
|
||||
<g transform="scale(10.66667,10.66667)">
|
||||
<path
|
||||
d="M7.16016,3l1.60156,2h9.23828c0.551,0 1,0.448 1,1v9h-3l4,5l4,-5h-3v-9c0,-1.654 -1.346,-3 -3,-3zM4,4l-4,5h3v9c0,1.654 1.346,3 3,3h10.83984l-1.60156,-2h-9.23828c-0.551,0 -1,-0.448 -1,-1v-9h3z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 615 B |
|
|
@ -1,4 +0,0 @@
|
|||
<svg stroke="white" fill="none" stroke-width="1.5" viewBox="0 0 18 18" stroke-linecap="round" stroke-linejoin="round"
|
||||
height="12px" width="12px" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3" width="12" height="12" rx="2" ry="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 253 B |
1
src/main/resources/icons/toolwindow-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="13px" height="13px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#000000"></stop><stop offset="0.699" stop-color="#000000"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#ffffff"></stop><stop offset="1" stop-color="#ffffff"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
1
src/main/resources/icons/toolwindow-icon_dark.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="13px" height="13px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#ffffff"></stop><stop offset="0.699" stop-color="#ffffff"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#000000"></stop><stop offset="1" stop-color="#000000"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#ffffff" fill-rule="nonzero" stroke="#ffffff" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |