Add conversation history, fix reported bugs, visual improvements #9)

This commit is contained in:
Carl-Robert Linnupuu 2023-03-09 11:58:40 +00:00
parent 6246491668
commit f4cd93f018
53 changed files with 906 additions and 252 deletions

View file

@ -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 {

View file

@ -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() {

View file

@ -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;
}
}

View file

@ -0,0 +1,8 @@
package ee.carlrobert.chatgpt.client;
public enum ClientCode {
CHAT_COMPLETIONS,
TEXT_COMPLETIONS,
UNOFFICIAL_CHATGPT
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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)

View file

@ -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 {

View file

@ -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());
}

View file

@ -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() {

View file

@ -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();
}

View file

@ -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);

View file

@ -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());

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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();
}

View file

@ -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));
}
}

View file

@ -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);
}
});
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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...");

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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);
}
}

View file

@ -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) {
}
};
}
}

View file

@ -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)));

View file

@ -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/>

View file

@ -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

View 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

View 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

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB