#178 - Add support for running local LLMs via LLaMA C/C++ port (#249)

* Initial implementation of integrating llama.cpp to run LLaMA models locally

* Move submodule

* Copy llama submodule to bundle

* Support for downloading models from IDE

* Code cleanup

* Store port field

* Replace service selection radio group with dropdown

* Add quantization support + other fixes

* Add option to override host

* Fix override host handler

* Disable port field when override host enabled

* Design updates

* Fix llama settings configuration, design changes, clean up code

* Improve You.com coupon design

* Add new Phind model and help tooltip

* Fetch you.com subscription

* Add CodeBooga model, fix downloadable model selection

* Chat history support

* Code refactoring, minor bug fixes

* UI updates, several bug fixes, removed code llama python model

* Code cleanup, enable llama port only on macOS

* Change downloaded gguf models path

* Move some of the labels to codegpt bundle

* Minor fixes

* Remove ToRA model, add help texts

* Fix test

* Modify description
This commit is contained in:
Carl-Robert 2023-11-03 12:00:24 +02:00 committed by GitHub
parent ca2eb9b6fa
commit 45908e69df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 2748 additions and 533 deletions

View file

@ -6,8 +6,10 @@ import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import ee.carlrobert.codegpt.telemetry.core.util.Directories;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.jetbrains.annotations.NotNull;
public final class CodeGPTPlugin {
@ -33,6 +35,14 @@ public final class CodeGPTPlugin {
return getPluginOptionsPath() + File.separator + "indexes";
}
public static @NotNull String getLlamaSourcePath() {
return getPluginBasePath() + File.separator + "llama.cpp";
}
public static @NotNull String getLlamaModelsPath() {
return Paths.get(System.getProperty("user.home"), ".codegpt/models/gguf").toString();
}
public static @NotNull String getProjectIndexStorePath(@NotNull Project project) {
return getIndexStorePath() + File.separator + project.getName();
}

View file

@ -11,4 +11,5 @@ public final class Icons {
public static final Icon OpenAIIcon = IconLoader.getIcon("/icons/openai.svg", Icons.class);
public static final Icon AzureIcon = IconLoader.getIcon("/icons/azure.svg", Icons.class);
public static final Icon YouIcon = IconLoader.getIcon("/icons/you.svg", Icons.class);
public static final Icon LlamaIcon = IconLoader.getIcon("/icons/llama.svg", Icons.class);
}

View file

@ -38,7 +38,7 @@ public class EditorActionsUtil {
}
public static void refreshActions() {
AnAction actionGroup = ActionManager.getInstance().getAction("action.editor.group.EditorActionGroup");
AnAction actionGroup = ActionManager.getInstance().getAction("project.label");
if (actionGroup instanceof DefaultActionGroup) {
DefaultActionGroup group = (DefaultActionGroup) actionGroup;
group.removeAll();

View file

@ -5,11 +5,14 @@ import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager;
import ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsState;
import ee.carlrobert.codegpt.settings.state.AzureSettingsState;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.YouSettingsState;
import ee.carlrobert.llm.client.Client;
import ee.carlrobert.llm.client.ProxyAuthenticator;
import ee.carlrobert.llm.client.azure.AzureClient;
import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams;
import ee.carlrobert.llm.client.llama.LlamaClient;
import ee.carlrobert.llm.client.openai.OpenAIClient;
import ee.carlrobert.llm.client.you.UTMParameters;
import ee.carlrobert.llm.client.you.YouClient;
@ -33,8 +36,16 @@ public class CompletionClientProvider {
utmParameters.setMedium("jetbrains");
utmParameters.setCampaign(CodeGPTPlugin.getVersion());
utmParameters.setContent("CodeGPT");
return new YouClient.Builder(sessionId, accessToken)
// FIXME
return (YouClient) new YouClient.Builder(sessionId, accessToken)
.setUTMParameters(utmParameters)
.setHost(YouSettingsState.getInstance().getBaseHost())
.build();
}
public static LlamaClient getLlamaClient() {
return new LlamaClient.Builder()
.setPort(LlamaSettingsState.getInstance().getServerPort())
.build();
}
@ -65,10 +76,9 @@ public class CompletionClientProvider {
builder.setProxy(
new Proxy(advancedSettings.getProxyType(), new InetSocketAddress(proxyHost, proxyPort)));
if (advancedSettings.isProxyAuthSelected()) {
builder.setProxyAuthenticator(
new ProxyAuthenticator(
advancedSettings.getProxyUsername(),
advancedSettings.getProxyPassword()));
builder.setProxyAuthenticator(new ProxyAuthenticator(
advancedSettings.getProxyUsername(),
advancedSettings.getProxyPassword()));
}
}

View file

@ -80,6 +80,11 @@ public class CompletionRequestHandler {
var requestProvider = new CompletionRequestProvider(conversation);
try {
if (settings.isUseLlamaService()) {
return CompletionClientProvider.getLlamaClient()
.getChatCompletion(requestProvider.buildLlamaCompletionRequest(message), eventListener);
}
if (settings.isUseYouService()) {
var sessionId = "";
var accessToken = "";

View file

@ -2,19 +2,23 @@ package ee.carlrobert.codegpt.completions;
import static java.util.stream.Collectors.toList;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.EncodingManager;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.configuration.ConfigurationState;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.settings.state.YouSettingsState;
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
import ee.carlrobert.codegpt.telemetry.core.service.TelemetryService;
import ee.carlrobert.codegpt.telemetry.core.service.UserId;
import ee.carlrobert.codegpt.util.ApplicationUtils;
import ee.carlrobert.embedding.EmbeddingsService;
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel;
import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionMessage;
import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionRequest;
@ -36,17 +40,22 @@ public class CompletionRequestProvider {
"Follow the user's requirements carefully & to the letter.\n" +
"Your responses should be informative and logical.\n" +
"You should always adhere to technical information.\n" +
"If the user asks for code or technical questions, you must provide code suggestions and adhere to technical information.\n" +
"If the question is related to a developer, CodeGPT must respond with content related to a developer.\n" +
"First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.\n" +
"If the user asks for code or technical questions, you must provide code suggestions and " +
"adhere to technical information.\n" +
"If the question is related to a developer, CodeGPT must respond with " +
"content related to a developer.\n" +
"First think step-by-step - describe your plan for what to build in pseudocode, " +
"written out in great detail.\n" +
"Then output the code in a single code block.\n" +
"Minimize any other prose.\n" +
"Keep your answers short and impersonal.\n" +
"Use Markdown formatting in your answers.\n" +
"Make sure to include the programming language name at the start of the Markdown code blocks.\n" +
"Make sure to include the programming language name at the start of the " +
"Markdown code blocks.\n" +
"Avoid wrapping the whole response in triple backticks.\n" +
"The user works in an IDE built by JetBrains which has a concept for editors with open files, integrated unit test support, " +
"and output pane that shows the output of running the code as well as an integrated terminal.\n" +
"The user works in an IDE built by JetBrains which has a concept for editors " +
"with open files, integrated unit test support, and output pane that shows " +
"the output of running the code as well as an integrated terminal.\n" +
"You can only give one reply for each conversation turn.";
private final EncodingManager encodingManager = EncodingManager.getInstance();
@ -60,6 +69,20 @@ public class CompletionRequestProvider {
this.conversation = conversation;
}
public LlamaCompletionRequest buildLlamaCompletionRequest(Message message) {
var settings = LlamaSettingsState.getInstance();
var promptTemplate = settings.isUseCustomModel() ?
settings.getPromptTemplate() :
LlamaModel.findByHuggingFaceModel(settings.getHuggingFaceModel()).getPromptTemplate();
var prompt = promptTemplate.buildPrompt(
COMPLETION_SYSTEM_PROMPT,
message.getPrompt(),
conversation.getMessages());
return new LlamaCompletionRequest.Builder(prompt)
.setN_predict(512)
.build();
}
public YouCompletionRequest buildYouCompletionRequest(Message message) {
var requestBuilder = new YouCompletionRequest.Builder(message.getPrompt())
.setUseGPT4Model(YouSettingsState.getInstance().isUseGPT4Model())
@ -68,7 +91,8 @@ public class CompletionRequestProvider {
prevMessage.getPrompt(),
prevMessage.getResponse()))
.collect(toList()));
if (TelemetryConfiguration.getInstance().isEnabled()) {
if (TelemetryConfiguration.getInstance().isEnabled() &&
!ApplicationManager.getApplication().isUnitTestMode()) {
requestBuilder.setUserId(UUID.fromString(UserId.INSTANCE.get()));
}
return requestBuilder.build();

View file

@ -0,0 +1,85 @@
package ee.carlrobert.codegpt.completions;
import static java.lang.String.format;
import java.net.MalformedURLException;
import java.net.URL;
public enum HuggingFaceModel {
CODE_LLAMA_7B_Q3(7, 3, "CodeLlama-7B-Instruct-GGUF"),
CODE_LLAMA_7B_Q4(7, 4, "CodeLlama-7B-Instruct-GGUF"),
CODE_LLAMA_7B_Q5(7, 5, "CodeLlama-7B-Instruct-GGUF"),
CODE_LLAMA_13B_Q3(13, 3, "CodeLlama-13B-Instruct-GGUF"),
CODE_LLAMA_13B_Q4(13, 4, "CodeLlama-13B-Instruct-GGUF"),
CODE_LLAMA_13B_Q5(13, 5, "CodeLlama-13B-Instruct-GGUF"),
CODE_LLAMA_34B_Q3(34, 3, "CodeLlama-34B-Instruct-GGUF"),
CODE_LLAMA_34B_Q4(34, 4, "CodeLlama-34B-Instruct-GGUF"),
CODE_LLAMA_34B_Q5(34, 5, "CodeLlama-34B-Instruct-GGUF"),
CODE_BOOGA_34B_Q3(34, 3, "CodeBooga-34B-v0.1-GGUF"),
CODE_BOOGA_34B_Q4(34, 4, "CodeBooga-34B-v0.1-GGUF"),
CODE_BOOGA_34B_Q5(34, 5, "CodeBooga-34B-v0.1-GGUF"),
PHIND_CODE_LLAMA_34B_Q3(34, 3, "Phind-CodeLlama-34B-v2-GGUF"),
PHIND_CODE_LLAMA_34B_Q4(34, 4, "Phind-CodeLlama-34B-v2-GGUF"),
PHIND_CODE_LLAMA_34B_Q5(34, 5, "Phind-CodeLlama-34B-v2-GGUF"),
WIZARD_CODER_PYTHON_7B_Q3(7, 3, "WizardCoder-Python-7B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_7B_Q4(7, 4, "WizardCoder-Python-7B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_7B_Q5(7, 5, "WizardCoder-Python-7B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_13B_Q3(13, 3, "WizardCoder-Python-13B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_13B_Q4(13, 4, "WizardCoder-Python-13B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_13B_Q5(13, 5, "WizardCoder-Python-13B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_34B_Q3(34, 3, "WizardCoder-Python-34B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_34B_Q4(34, 4, "WizardCoder-Python-34B-V1.0-GGUF"),
WIZARD_CODER_PYTHON_34B_Q5(34, 5, "WizardCoder-Python-34B-V1.0-GGUF");
private final int parameterSize;
private final int quantization;
private final String modelName;
HuggingFaceModel(int parameterSize, int quantization, String modelName) {
this.parameterSize = parameterSize;
this.quantization = quantization;
this.modelName = modelName;
}
public int getParameterSize() {
return parameterSize;
}
public int getQuantization() {
return quantization;
}
public String getCode() {
return name();
}
public String getFileName() {
return modelName.toLowerCase().replace("-gguf", format(".Q%d_K_M.gguf", quantization));
}
public URL getFileURL() {
try {
return new URL(
format("https://huggingface.co/TheBloke/%s/resolve/main/%s", modelName, getFileName()));
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
public URL getHuggingFaceURL() {
try {
return new URL("https://huggingface.co/TheBloke/" + modelName);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
@Override
public String toString() {
return format("%d-bit precision", quantization);
}
}

View file

@ -0,0 +1,124 @@
package ee.carlrobert.codegpt.completions.llama;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
public enum LlamaModel {
CODE_LLAMA(
"Code Llama",
"Code Llama is a family of large language models for code based on Llama 2 providing state-of-the-art performance among open models, infilling capabilities, support for large input contexts, and zero-shot instruction following ability for programming tasks.",
PromptTemplate.LLAMA,
List.of(
HuggingFaceModel.CODE_LLAMA_7B_Q3,
HuggingFaceModel.CODE_LLAMA_7B_Q4,
HuggingFaceModel.CODE_LLAMA_7B_Q5,
HuggingFaceModel.CODE_LLAMA_13B_Q3,
HuggingFaceModel.CODE_LLAMA_13B_Q4,
HuggingFaceModel.CODE_LLAMA_13B_Q5,
HuggingFaceModel.CODE_LLAMA_34B_Q3,
HuggingFaceModel.CODE_LLAMA_34B_Q4,
HuggingFaceModel.CODE_LLAMA_34B_Q5)
),
CODE_BOOGA(
"CodeBooga",
"CodeBooga is a high-performing code instruct model created by merging two existing code models: <ol><li>Phind-CodeLlama-34B-v2</li><li>WizardCoder-Python-34B-V1.0</li></ol>",
PromptTemplate.ALPACA,
List.of(
HuggingFaceModel.CODE_BOOGA_34B_Q3,
HuggingFaceModel.CODE_BOOGA_34B_Q4,
HuggingFaceModel.CODE_BOOGA_34B_Q5)),
PHIND_CODE_LLAMA(
"Phind Code Llama",
"This model is fine-tuned from Phind-CodeLlama-34B-v1 on an additional 1.5B tokens high-quality programming-related data, achieving 73.8% pass@1 on HumanEval. It's the current state-of-the-art amongst open-source models.",
PromptTemplate.ALPACA,
List.of(
HuggingFaceModel.PHIND_CODE_LLAMA_34B_Q3,
HuggingFaceModel.PHIND_CODE_LLAMA_34B_Q4,
HuggingFaceModel.PHIND_CODE_LLAMA_34B_Q5)),
WIZARD_CODER_PYTHON(
"WizardCoder - Python",
"WizardCoder, a Code Evol-Instruct fine-tuned Code LLM, which achieves the 73.2 pass@1 and surpasses GPT4 (2023/03/15), ChatGPT-3.5, and Claude2 on the HumanEval Benchmarks.",
PromptTemplate.ALPACA,
List.of(
HuggingFaceModel.WIZARD_CODER_PYTHON_7B_Q3,
HuggingFaceModel.WIZARD_CODER_PYTHON_7B_Q4,
HuggingFaceModel.WIZARD_CODER_PYTHON_7B_Q5,
HuggingFaceModel.WIZARD_CODER_PYTHON_13B_Q3,
HuggingFaceModel.WIZARD_CODER_PYTHON_13B_Q4,
HuggingFaceModel.WIZARD_CODER_PYTHON_13B_Q5,
HuggingFaceModel.WIZARD_CODER_PYTHON_34B_Q3,
HuggingFaceModel.WIZARD_CODER_PYTHON_34B_Q4,
HuggingFaceModel.WIZARD_CODER_PYTHON_34B_Q5));
private final String label;
private final String description;
private final PromptTemplate promptTemplate;
private final List<HuggingFaceModel> huggingFaceModels;
LlamaModel(
String label,
String description,
PromptTemplate promptTemplate,
List<HuggingFaceModel> huggingFaceModels) {
this.label = label;
this.description = description;
this.promptTemplate = promptTemplate;
this.huggingFaceModels = huggingFaceModels;
}
public static @NotNull LlamaModel findByHuggingFaceModel(HuggingFaceModel huggingFaceModel) {
for (var llamaModel : LlamaModel.values()) {
if (llamaModel.getHuggingFaceModels().contains(huggingFaceModel)) {
return llamaModel;
}
}
throw new RuntimeException("Unable to find correct LLM");
}
@Override
public String toString() {
return String.join(" ", label, getFormattedModelSizeRange());
}
public String getLabel() {
return label;
}
public String getDescription() {
return description;
}
public PromptTemplate getPromptTemplate() {
return promptTemplate;
}
public List<HuggingFaceModel> getHuggingFaceModels() {
return huggingFaceModels;
}
public String getFormattedModelSizeRange() {
var parameters = huggingFaceModels.stream()
.map(HuggingFaceModel::getParameterSize)
.collect(toSet());
if (parameters.size() == 1) {
return parameters.iterator().next() + "B";
}
return format("(%dB - %dB)", Collections.min(parameters), Collections.max(parameters));
}
public List<Integer> getSortedUniqueModelSizes() {
return huggingFaceModels.stream()
.map(HuggingFaceModel::getParameterSize)
.collect(toSet())
.stream()
.sorted()
.collect(toList());
}
}

View file

@ -0,0 +1,156 @@
package ee.carlrobert.codegpt.completions.llama;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessListener;
import com.intellij.execution.process.ProcessOutputType;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.ui.components.JBLabel;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.settings.service.ServerProgressPanel;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import java.nio.charset.StandardCharsets;
import javax.swing.SwingConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Service
public final class LlamaServerAgent implements Disposable {
private static final Logger LOG = Logger.getInstance(LlamaServerAgent.class);
private static @Nullable OSProcessHandler makeProcessHandler;
private static @Nullable OSProcessHandler startServerProcessHandler;
public void startAgent(
String modelPath,
int contextLength,
int port,
ServerProgressPanel serverProgressPanel,
Runnable onSuccess) {
ApplicationManager.getApplication().invokeLater(() -> {
try {
serverProgressPanel.updateText("Building llama.cpp...");
makeProcessHandler = new OSProcessHandler(getMakeCommandLinde());
makeProcessHandler.addProcessListener(
getMakeProcessListener(modelPath, contextLength, port, serverProgressPanel, onSuccess));
makeProcessHandler.startNotify();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
});
}
public void stopAgent() {
if (startServerProcessHandler != null) {
startServerProcessHandler.destroyProcess();
}
}
public boolean isServerRunning() {
return startServerProcessHandler != null &&
startServerProcessHandler.isStartNotified() &&
!startServerProcessHandler.isProcessTerminated();
}
private ProcessListener getMakeProcessListener(
String modelPath,
int contextLength,
int port,
ServerProgressPanel serverProgressPanel,
Runnable onSuccess) {
return new ProcessAdapter() {
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
LOG.info(event.getText());
}
@Override
public void processTerminated(@NotNull ProcessEvent event) {
try {
serverProgressPanel.updateText("Booting up server...");
startServerProcessHandler = new OSProcessHandler(
getServerCommandLine(modelPath, contextLength, port));
startServerProcessHandler.addProcessListener(
getProcessListener(port, serverProgressPanel, onSuccess));
startServerProcessHandler.startNotify();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
};
}
private ProcessListener getProcessListener(
int port,
ServerProgressPanel serverProgressPanel,
Runnable onSuccess) {
return new ProcessAdapter() {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void processTerminated(@NotNull ProcessEvent event) {
serverProgressPanel.displayComponent(new JBLabel(
"Server terminated",
Actions.Cancel,
SwingConstants.LEADING));
}
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
LOG.debug(event.getText());
if (outputType == ProcessOutputType.STDOUT) {
try {
var serverMessage = objectMapper.readValue(event.getText(), LlamaServerMessage.class);
if ("HTTP server listening".equals(serverMessage.getMessage())) {
LlamaSettingsState.getInstance().setServerPort(port);
onSuccess.run();
}
} catch (Exception ignore) {
}
}
}
};
}
private static GeneralCommandLine getMakeCommandLinde() {
GeneralCommandLine commandLine = new GeneralCommandLine().withCharset(StandardCharsets.UTF_8);
commandLine.setExePath("make");
commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath());
commandLine.addParameters("-j");
commandLine.setRedirectErrorStream(false);
return commandLine;
}
private GeneralCommandLine getServerCommandLine(String modelPath, int contextLength, int port) {
GeneralCommandLine commandLine = new GeneralCommandLine().withCharset(StandardCharsets.UTF_8);
commandLine.setExePath("./server");
commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath());
commandLine.addParameters(
"-m", modelPath,
"-c", String.valueOf(contextLength),
"--port", String.valueOf(port));
commandLine.setRedirectErrorStream(false);
return commandLine;
}
@Override
public void dispose() {
if (makeProcessHandler != null && !makeProcessHandler.isProcessTerminated()) {
makeProcessHandler.destroyProcess();
}
if (startServerProcessHandler != null && !startServerProcessHandler.isProcessTerminated()) {
startServerProcessHandler.destroyProcess();
}
}
}

View file

@ -0,0 +1,26 @@
package ee.carlrobert.codegpt.completions.llama;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class LlamaServerMessage {
private final String level;
private final String message;
public LlamaServerMessage(
@JsonProperty("level") String level,
@JsonProperty("message") String message) {
this.level = level;
this.message = message;
}
public String getLevel() {
return level;
}
public String getMessage() {
return message;
}
}

View file

@ -0,0 +1,113 @@
package ee.carlrobert.codegpt.completions.llama;
import ee.carlrobert.codegpt.conversations.message.Message;
import java.util.List;
public enum PromptTemplate {
CHAT_ML("Chat Markup Language (ChatML)") {
@Override
public String buildPrompt(String systemPrompt, String userPrompt, List<Message> history) {
StringBuilder prompt = new StringBuilder();
if (systemPrompt != null && !systemPrompt.isEmpty()) {
prompt.append("<|im_start|>system\n")
.append(systemPrompt)
.append("<|im_end|>\n");
}
for (Message message : history) {
prompt.append("<|im_start|>user\n")
.append(message.getPrompt())
.append("<|im_end|>\n")
.append("<|im_start|>assistant\n")
.append(message.getResponse())
.append("<|im_end|>\n");
}
return prompt.append("<|im_start|>user\n")
.append(userPrompt)
.append("<|im_end|>")
.toString();
}
},
LLAMA("Llama") {
@Override
public String buildPrompt(String systemPrompt, String userPrompt, List<Message> history) {
StringBuilder prompt = new StringBuilder();
if (systemPrompt != null && !systemPrompt.isEmpty()) {
prompt.append("<<SYS>>")
.append(systemPrompt)
.append("<</SYS>>\n");
}
for (Message message : history) {
prompt.append("[INST]")
.append(message.getPrompt())
.append("[/INST]\n")
.append(message.getResponse()).append("\n");
}
return prompt.append("[INST]")
.append(userPrompt)
.append("[/INST]")
.toString();
}
},
TORA("ToRA") {
@Override
public String buildPrompt(String systemPrompt, String userPrompt, List<Message> history) {
StringBuilder prompt = new StringBuilder();
for (Message message : history) {
prompt.append("<|user|>\n")
.append(message.getPrompt())
.append("\n<|assistant|>\n")
.append(message.getResponse()).append("\n");
}
return prompt.append("<|user|>\n")
.append(userPrompt)
.append("\n<|assistant|>")
.toString();
}
},
ALPACA("Alpaca/Vicuna") {
@Override
public String buildPrompt(String systemPrompt, String userPrompt, List<Message> history) {
StringBuilder prompt = new StringBuilder();
prompt.append(
"Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n");
for (Message message : history) {
prompt.append("### Instruction\n")
.append(message.getPrompt())
.append("\n\n")
.append("### Response:\n")
.append(message.getResponse())
.append("\n\n");
}
return prompt.append("### Instruction\n")
.append(userPrompt)
.append("\n\n")
.append("### Response:\n")
.toString();
}
};
private final String label;
PromptTemplate(String label) {
this.label = label;
}
public abstract String buildPrompt(String systemPrompt, String userPrompt, List<Message> history);
@Override
public String toString() {
return label;
}
}

View file

@ -1,51 +1,56 @@
package ee.carlrobert.codegpt.completions.you;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import okhttp3.Callback;
import ee.carlrobert.codegpt.completions.you.auth.response.YouAuthenticationResponse;
import java.io.IOException;
import java.util.List;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.jetbrains.annotations.Nullable;
@Service
public final class YouApiClient {
private static final String API_BASE_URL = "https://web.stytch.com/sdk";
private static final String publicToken = "public-token-live-507a52ad-7e69-496b-aee0-1c9863c7c819";
private static final String API_BASE_URL = "https://you.com/api";
public static YouApiClient getInstance() {
return ApplicationManager.getApplication().getService(YouApiClient.class);
}
public void authenticate(String email, String password, Callback callback) {
try {
new OkHttpClient()
.newCall(new Request.Builder()
.url(API_BASE_URL + "/v1/passwords/authenticate")
.headers(Headers.of(
"content-type", "application/json",
"authority", "web.stytch.com",
"authorization", "Basic " + Base64.getEncoder().encodeToString((publicToken + ":" + publicToken).getBytes()),
"x-sdk-client", "eyJldmVudF9pZCI6ImV2ZW50LWlkLWY5YmU4YWU5LWE3MjctNGFlYy1hNzY0LTk4NDg1NDFkZjcwYSIsImFwcF9zZXNzaW9uX2lkIjoiYXBwLXNlc3Npb24taWQtYjY1NzcwZjMtMWFkMy00YjlhLWFjYzctMzJjNWQyMGMxNGU0IiwicGVyc2lzdGVudF9pZCI6InBlcnNpc3RlbnQtaWQtYzY0M2M0YTMtZDg5MC00ZGJkLTk3YjQtMjY0MmFlODdkMTZhIiwiY2xpZW50X3NlbnRfYXQiOiIyMDIzLTA5LTAxVDIyOjMwOjU1LjIzNFoiLCJ0aW1lem9uZSI6IkV1cm9wZS9UYWxsaW5uIiwiYXBwIjp7ImlkZW50aWZpZXIiOiJ5b3UuY29tIn0sInNkayI6eyJpZGVudGlmaWVyIjoiU3R5dGNoLmpzIEphdmFzY3JpcHQgU0RLIC0gWU9VLkNPTSBERUJVRyBCVUlMRCIsInZlcnNpb24iOiI0LjAuMCJ9fQ==",
"x-sdk-parent-host", "https://you.com"
))
.post(RequestBody.create(new ObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(Map.of(
"email", email,
"password", password,
"session_duration_minutes", 129_600))
.getBytes(StandardCharsets.UTF_8)))
.build())
.enqueue(callback);
} catch (JsonProcessingException e) {
throw new RuntimeException("Could not process request", e);
public @Nullable YouSubscription getSubscription(YouAuthenticationResponse auth) {
var sessionId = auth.getData().getSession().getSessionId();
var sessionJwt = auth.getData().getSessionJwt();
var request = new Request.Builder()
.url(API_BASE_URL + "/payments/orders/subscriptions/current")
.header("Accept", "application/json")
.header("Cache-Control", "no-cache")
.header("User-Agent", "youide CodeGPT")
.header("Cookie", (
"stytch_session=" + sessionId + "; " +
"ydc_stytch_session=" + sessionId + "; " +
"stytch_session_jwt=" + sessionJwt + "; " +
"ydc_stytch_session_jwt=" + sessionJwt + "; "))
.get()
.build();
try (var response = new OkHttpClient().newCall(request).execute()) {
var body = response.body();
if (body == null || !response.isSuccessful()) {
return null;
}
List<YouSubscription> subscriptions =
new ObjectMapper().readValue(body.string(), new TypeReference<>() {
});
if (subscriptions == null || subscriptions.isEmpty()) {
return null;
}
return subscriptions.get(0);
} catch (IOException ex) {
throw new RuntimeException("Could not get You.com subscription", ex);
}
}
}

View file

@ -0,0 +1,34 @@
package ee.carlrobert.codegpt.completions.you;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class YouSubscription {
private final String service;
private final String tier;
private final String month;
public YouSubscription(
@JsonProperty("service") String service,
@JsonProperty("tier") String tier,
@JsonProperty("month") String month) {
this.service = service;
this.tier = tier;
this.month = month;
}
public String getService() {
return service;
}
public String getTier() {
return tier;
}
public String getMonth() {
return month;
}
}

View file

@ -0,0 +1,11 @@
package ee.carlrobert.codegpt.completions.you;
import com.intellij.util.messages.Topic;
public interface YouSubscriptionNotifier {
Topic<YouSubscriptionNotifier> SUBSCRIPTION_TOPIC =
Topic.create("subscriptionTopic", YouSubscriptionNotifier.class);
void subscribed();
}

View file

@ -9,6 +9,7 @@ import ee.carlrobert.codegpt.completions.you.auth.response.YouAuthenticationResp
public final class YouUserManager {
private YouAuthenticationResponse authenticationResponse;
private boolean subscribed;
private YouUserManager() {
}
@ -27,14 +28,19 @@ public final class YouUserManager {
public void clearSession() {
authenticationResponse = null;
subscribed = false;
ApplicationManager.getApplication().getMessageBus()
.syncPublisher(SignedOutNotifier.SIGNED_OUT_TOPIC)
.signedOut();
}
public void setSubscribed(boolean subscribed) {
this.subscribed = subscribed;
}
public boolean isSubscribed() {
return true; // TODO
return subscribed;
}
public boolean isAuthenticated() {

View file

@ -0,0 +1,51 @@
package ee.carlrobert.codegpt.completions.you.auth;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
@Service
public final class YouAuthClient {
private static final String API_BASE_URL = "https://web.stytch.com/sdk";
private static final String publicToken = "public-token-live-507a52ad-7e69-496b-aee0-1c9863c7c819";
public static YouAuthClient getInstance() {
return ApplicationManager.getApplication().getService(YouAuthClient.class);
}
public void authenticate(String email, String password, Callback callback) {
try {
new OkHttpClient()
.newCall(new Request.Builder()
.url(API_BASE_URL + "/v1/passwords/authenticate")
.headers(Headers.of(
"content-type", "application/json",
"authority", "web.stytch.com",
"authorization", "Basic " + Base64.getEncoder().encodeToString((publicToken + ":" + publicToken).getBytes()),
"x-sdk-client", "eyJldmVudF9pZCI6ImV2ZW50LWlkLWY5YmU4YWU5LWE3MjctNGFlYy1hNzY0LTk4NDg1NDFkZjcwYSIsImFwcF9zZXNzaW9uX2lkIjoiYXBwLXNlc3Npb24taWQtYjY1NzcwZjMtMWFkMy00YjlhLWFjYzctMzJjNWQyMGMxNGU0IiwicGVyc2lzdGVudF9pZCI6InBlcnNpc3RlbnQtaWQtYzY0M2M0YTMtZDg5MC00ZGJkLTk3YjQtMjY0MmFlODdkMTZhIiwiY2xpZW50X3NlbnRfYXQiOiIyMDIzLTA5LTAxVDIyOjMwOjU1LjIzNFoiLCJ0aW1lem9uZSI6IkV1cm9wZS9UYWxsaW5uIiwiYXBwIjp7ImlkZW50aWZpZXIiOiJ5b3UuY29tIn0sInNkayI6eyJpZGVudGlmaWVyIjoiU3R5dGNoLmpzIEphdmFzY3JpcHQgU0RLIC0gWU9VLkNPTSBERUJVRyBCVUlMRCIsInZlcnNpb24iOiI0LjAuMCJ9fQ==",
"x-sdk-parent-host", "https://you.com"
))
.post(RequestBody.create(new ObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(Map.of(
"email", email,
"password", password,
"session_duration_minutes", 129_600))
.getBytes(StandardCharsets.UTF_8)))
.build())
.enqueue(callback);
} catch (JsonProcessingException e) {
throw new RuntimeException("Could not process request", e);
}
}
}

View file

@ -6,6 +6,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.diagnostic.Logger;
import ee.carlrobert.codegpt.completions.you.YouApiClient;
import ee.carlrobert.codegpt.completions.you.YouSubscriptionNotifier;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.completions.you.auth.response.YouAuthenticationResponse;
import ee.carlrobert.codegpt.util.OverlayUtils;
@ -19,7 +20,7 @@ import org.jetbrains.annotations.NotNull;
public final class YouAuthenticationService {
private static final Logger LOG = Logger.getInstance(YouAuthenticationService.class);
private static final YouApiClient client = YouApiClient.getInstance();
private static final YouAuthClient authClient = YouAuthClient.getInstance();
private YouAuthenticationService() {
}
@ -28,8 +29,9 @@ public final class YouAuthenticationService {
return ApplicationManager.getApplication().getService(YouAuthenticationService.class);
}
public void signInAsync(String email, String password, AuthenticationHandler authenticationHandler) {
client.authenticate(email, password, new AuthenticationCallback(authenticationHandler));
public void signInAsync(String email, String password,
AuthenticationHandler authenticationHandler) {
authClient.authenticate(email, password, new AuthenticationCallback(authenticationHandler));
}
static class AuthenticationCallback implements Callback {
@ -56,11 +58,23 @@ public final class YouAuthenticationService {
if (response.code() == 200) {
try {
var authenticationResponse = new ObjectMapper().readValue(body.string(), YouAuthenticationResponse.class);
YouUserManager.getInstance().setAuthenticationResponse(authenticationResponse);
var messageBus = ApplicationManager.getApplication().getMessageBus();
var userManager = YouUserManager.getInstance();
var authenticationResponse =
new ObjectMapper().readValue(body.string(), YouAuthenticationResponse.class);
userManager.setAuthenticationResponse(authenticationResponse);
authenticationHandler.handleAuthenticated(authenticationResponse);
ApplicationManager.getApplication().getMessageBus()
var subscription =
YouApiClient.getInstance().getSubscription(authenticationResponse);
var subscribed = subscription != null && "youpro".equals(subscription.getService());
userManager.setSubscribed(subscribed);
if (subscribed) {
messageBus.syncPublisher(YouSubscriptionNotifier.SUBSCRIPTION_TOPIC).subscribed();
}
messageBus
.syncPublisher(AuthenticationNotifier.AUTHENTICATION_TOPIC)
.authenticationSuccessful();
return;
@ -70,7 +84,8 @@ public final class YouAuthenticationService {
}
try {
authenticationHandler.handleError(new ObjectMapper().readValue(body.string(), YouAuthenticationError.class));
authenticationHandler.handleError(
new ObjectMapper().readValue(body.string(), YouAuthenticationError.class));
} catch (Throwable ex) {
authenticationHandler.handleGenericError();
throw new RuntimeException(ex);

View file

@ -6,6 +6,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.state.AzureSettingsState;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import java.time.LocalDateTime;
@ -46,8 +47,10 @@ public final class ConversationService {
conversation.setModel("YouCode");
} else if (settings.isUseAzureService()) {
conversation.setModel(AzureSettingsState.getInstance().getModel());
} else {
} else if (settings.isUseOpenAIService()) {
conversation.setModel(OpenAISettingsState.getInstance().getModel());
} else {
conversation.setModel(LlamaSettingsState.getInstance().getHuggingFaceModel().getCode());
}
conversation.setCreatedOn(LocalDateTime.now());
conversation.setUpdatedOn(LocalDateTime.now());
@ -64,7 +67,11 @@ public final class ConversationService {
conversationsMapping.put(conversation.getClientCode(), conversations);
}
public void saveMessage(String response, Message message, Conversation conversation, boolean isRetry) {
public void saveMessage(
String response,
Message message,
Conversation conversation,
boolean isRetry) {
var conversationMessages = conversation.getMessages();
if (isRetry && !conversationMessages.isEmpty()) {
var messageToBeSaved = conversationMessages.stream()
@ -122,6 +129,9 @@ public final class ConversationService {
if (settings.isUseAzureService()) {
return "azure.chat.completion";
}
if (settings.isUseLlamaService()) {
return "llama.chat.completion";
}
return "you.chat.completion";
}

View file

@ -19,8 +19,8 @@ import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.embedding.CheckedFile;
import ee.carlrobert.codegpt.util.file.FileUtils;
import ee.carlrobert.embedding.CheckedFile;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
@ -123,7 +123,7 @@ public class FolderStructureTreePanel {
panel.add(loadingFilesSpinner);
} else {
panel.add(new JBLabel("Total size: " +
convertFileSize(totalSize) + " ~ " +
FileUtils.convertFileSize(totalSize) + " ~ " +
(convertLongValue(totalSize / 4)) + " tokens " + " ~ " +
new DecimalFormat("#.##").format(((double) (totalSize / 4) / 1000) * 0.0001) + " $"));
}
@ -137,7 +137,9 @@ public class FolderStructureTreePanel {
}
private List<VirtualFileImpl> getCheckedVirtualFiles() {
return Arrays.stream(checkboxTree.getCheckedNodes(VirtualFileSystemEntry.class, node -> node instanceof VirtualFileImpl))
return Arrays.stream(checkboxTree.getCheckedNodes(
VirtualFileSystemEntry.class,
node -> node instanceof VirtualFileImpl))
.map(entry -> (VirtualFileImpl) entry)
.collect(toList());
}
@ -160,12 +162,15 @@ public class FolderStructureTreePanel {
}
}
private void traverseDirectory(@NotNull CheckedTreeNode parentNode, @NotNull VirtualFile projectDirectory) {
private void traverseDirectory(@NotNull CheckedTreeNode parentNode,
@NotNull VirtualFile projectDirectory) {
for (VirtualFile childFile : projectDirectory.getChildren()) {
var node = new CheckedTreeNode(childFile);
parentNode.add(node);
if (!parentNode.isChecked() || ignoredFileDirectories.parallelStream().anyMatch(it -> it.equalsIgnoreCase(childFile.getName()))) {
var potentiallyIgnored = ignoredFileDirectories.parallelStream()
.anyMatch(it -> it.equalsIgnoreCase(childFile.getName()));
if (!parentNode.isChecked() || potentiallyIgnored) {
node.setChecked(false);
}
@ -180,7 +185,13 @@ public class FolderStructureTreePanel {
private @NotNull CheckboxTree.CheckboxTreeCellRenderer createFileTypesRenderer() {
return new CheckboxTree.CheckboxTreeCellRenderer() {
@Override
public void customizeRenderer(JTree t, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean focus) {
public void customizeRenderer(JTree t,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean focus) {
if (!(value instanceof CheckedTreeNode)) {
return;
}
@ -194,28 +205,18 @@ public class FolderStructureTreePanel {
if (userObject instanceof VirtualDirectoryImpl) {
getTextRenderer().setIcon(AllIcons.Nodes.Folder);
} else {
var fileType = FileTypeManager.getInstance().getFileTypeByFile((VirtualFileSystemEntry) userObject);
var fileType = FileTypeManager.getInstance()
.getFileTypeByFile((VirtualFileSystemEntry) userObject);
getTextRenderer().setIcon(fileType.getIcon());
getTextRenderer().append(" - " + convertFileSize(((VirtualFileSystemEntry) userObject).getLength()));
getTextRenderer().append(
" - " + FileUtils.convertFileSize(
((VirtualFileSystemEntry) userObject).getLength()));
}
}
}
};
}
private static String convertFileSize(long fileSizeInBytes) {
String[] units = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
double fileSize = fileSizeInBytes;
while (fileSize >= 1024 && unitIndex < units.length - 1) {
fileSize /= 1024;
unitIndex++;
}
return new DecimalFormat("#.##").format(fileSize) + " " + units[unitIndex];
}
private static String convertLongValue(long value) {
if (value >= 1_000_000) {
return value / 1_000_000 + "M";

View file

@ -1,30 +0,0 @@
package ee.carlrobert.codegpt.settings;
import com.intellij.openapi.ui.ComboBox;
import ee.carlrobert.llm.completion.CompletionModel;
import java.awt.Component;
import javax.swing.JList;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
public class ModelComboBox extends ComboBox<CompletionModel> {
public ModelComboBox(CompletionModel[] options, CompletionModel selectedModel) {
super(options);
setSelectedItem(selectedModel);
setRenderer(getBasicComboBoxRenderer());
}
private BasicComboBoxRenderer getBasicComboBoxRenderer() {
return new BasicComboBoxRenderer() {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value != null) {
CompletionModel model = (CompletionModel) value;
setText(model.getDescription());
}
return this;
}
};
}
}

View file

@ -1,2 +0,0 @@
package ee.carlrobert.codegpt.settings;public class ServiceChangeNotifier {
}

View file

@ -1,12 +1,22 @@
package ee.carlrobert.codegpt.settings;
import static java.util.stream.Collectors.toList;
import com.intellij.openapi.Disposable;
import com.intellij.ui.TitledSeparator;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.ComponentValidator;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.settings.service.ServiceSelectionForm;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import java.awt.CardLayout;
import java.util.Arrays;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComponent;
import javax.swing.JPanel;
@ -14,25 +24,53 @@ public class SettingsComponent {
private final JPanel mainPanel;
private final JBTextField displayNameField;
private final ComboBox<ServiceType> serviceComboBox;
private final ServiceSelectionForm serviceSelectionForm;
private final YouServiceSelectionPanel youServiceSelectionPanel;
public SettingsComponent(Disposable parentDisposable, SettingsState settings) {
serviceSelectionForm = new ServiceSelectionForm(parentDisposable, settings);
displayNameField = new JBTextField(settings.getDisplayName(), 20);
youServiceSelectionPanel = new YouServiceSelectionPanel(parentDisposable);
serviceSelectionForm = new ServiceSelectionForm(parentDisposable);
var cardLayout = new CardLayout();
var cards = new JPanel(cardLayout);
cards.add(serviceSelectionForm.getOpenAIServiceSectionPanel(), ServiceType.OPENAI.getCode());
cards.add(serviceSelectionForm.getAzureServiceSectionPanel(), ServiceType.AZURE.getCode());
cards.add(serviceSelectionForm.getYouServiceSectionPanel(), ServiceType.YOU.getCode());
cards.add(serviceSelectionForm.getLlamaServiceSectionPanel(), ServiceType.LLAMA_CPP.getCode());
var serviceComboBoxModel = new DefaultComboBoxModel<ServiceType>();
serviceComboBoxModel.addAll(Arrays.stream(ServiceType.values())
.filter(it -> !"LLAMA_CPP".equals(it.getCode()) || SystemInfoRt.isUnix)
.collect(toList()));
serviceComboBox = new ComboBox<>(serviceComboBoxModel);
serviceComboBox.setSelectedItem(ServiceType.OPENAI);
serviceComboBox.setPreferredSize(displayNameField.getPreferredSize());
var serviceInputValidator = createInputValidator(parentDisposable, serviceComboBox);
serviceInputValidator.revalidate();
serviceComboBox.addItemListener(e -> {
serviceInputValidator.revalidate();
cardLayout.show(cards, ((ServiceType) e.getItem()).getCode());
});
mainPanel = FormBuilder.createFormBuilder()
.addComponent(UI.PanelFactory.panel(displayNameField)
.withLabel(CodeGPTBundle.get("settingsConfigurable.section.integration.displayNameFieldLabel"))
.resizeX(false)
.createPanel())
.addComponent(new TitledSeparator(CodeGPTBundle.get("settingsConfigurable.section.service.title")))
.addComponent(serviceSelectionForm.getForm())
.addVerticalGap(8)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.displayName.label"),
displayNameField)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.label"),
serviceComboBox)
.addComponent(cards)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
public ServiceType getSelectedService() {
return serviceComboBox.getItem();
}
public void setSelectedService(ServiceType serviceType) {
serviceComboBox.setSelectedItem(serviceType);
}
public JPanel getPanel() {
return mainPanel;
}
@ -41,18 +79,6 @@ public class SettingsComponent {
return displayNameField;
}
public String getEmail() {
return youServiceSelectionPanel.getEmail();
}
public void setEmail(String email) {
youServiceSelectionPanel.setEmail(email);
}
public String getPassword() {
return youServiceSelectionPanel.getPassword();
}
public ServiceSelectionForm getServiceSelectionForm() {
return serviceSelectionForm;
}
@ -64,4 +90,27 @@ public class SettingsComponent {
public void setDisplayName(String displayName) {
displayNameField.setText(displayName);
}
private ComponentValidator createInputValidator(
Disposable parentDisposable,
JComponent component) {
var validator = new ComponentValidator(parentDisposable)
.withValidator(() -> {
if (component instanceof ComboBox) {
var selectedItem = ((ComboBox<?>) component).getSelectedItem();
if (selectedItem == ServiceType.OPENAI &&
OpenAISettingsState.getInstance().isOpenAIQuotaExceeded()) {
return new ValidationInfo(
CodeGPTBundle.get("settings.openaiQuotaExceeded"),
component);
}
}
return null;
})
.andStartOnFocusLost()
.installOn(component);
validator.enableValidation();
return validator;
}
}

View file

@ -1,5 +1,10 @@
package ee.carlrobert.codegpt.settings;
import static ee.carlrobert.codegpt.settings.service.ServiceType.AZURE;
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.util.Disposer;
@ -7,7 +12,9 @@ import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.state.AzureSettingsState;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.settings.state.YouSettingsState;
@ -49,14 +56,24 @@ public class SettingsConfigurable implements Configurable {
var settings = SettingsState.getInstance();
var openAISettings = OpenAISettingsState.getInstance();
var azureSettings = AzureSettingsState.getInstance();
var llamaSettings = LlamaSettingsState.getInstance();
var serviceSelectionForm = settingsComponent.getServiceSelectionForm();
var llamaModelPreferencesForm = serviceSelectionForm.getLlamaModelPreferencesForm();
return !settingsComponent.getDisplayName().equals(settings.getDisplayName()) ||
isServiceChanged(serviceSelectionForm, settings) ||
isServiceChanged(settings) ||
openAISettings.isModified(serviceSelectionForm) ||
azureSettings.isModified(serviceSelectionForm) ||
serviceSelectionForm.isDisplayWebSearchResults() !=
YouSettingsState.getInstance().isDisplayWebSearchResults();
YouSettingsState.getInstance().isDisplayWebSearchResults() ||
llamaSettings.isUseCustomModel() != llamaModelPreferencesForm.isUseCustomLlamaModel() ||
llamaSettings.getServerPort() != serviceSelectionForm.getLlamaServerPort() ||
llamaSettings.getContextSize() != serviceSelectionForm.getContextSize() ||
llamaSettings.getHuggingFaceModel() != llamaModelPreferencesForm.getSelectedModel() ||
!llamaSettings.getPromptTemplate().equals(llamaModelPreferencesForm.getPromptTemplate()) ||
!llamaSettings.getCustomLlamaModelPath()
.equals(llamaModelPreferencesForm.getCustomLlamaModelPath());
}
@Override
@ -65,7 +82,8 @@ public class SettingsConfigurable implements Configurable {
var settings = SettingsState.getInstance();
var openAISettings = OpenAISettingsState.getInstance();
var azureSettings = AzureSettingsState.getInstance();
var serviceChanged = isServiceChanged(serviceSelectionForm, settings);
var llamaSettings = LlamaSettingsState.getInstance();
var serviceChanged = isServiceChanged(settings);
var modelChanged = openAISettings.getModel().equals(serviceSelectionForm.getOpenAIModel()) ||
azureSettings.getModel().equals(serviceSelectionForm.getAzureModel());
@ -80,11 +98,21 @@ public class SettingsConfigurable implements Configurable {
.setAzureActiveDirectoryToken(serviceSelectionForm.getAzureActiveDirectoryToken());
settings.setDisplayName(settingsComponent.getDisplayName());
settings.setUseOpenAIService(serviceSelectionForm.isOpenAIServiceSelected());
settings.setUseAzureService(serviceSelectionForm.isAzureServiceSelected());
settings.setUseYouService(serviceSelectionForm.isYouServiceSelected());
// TODO: Store as single enum value
settings.setUseOpenAIService(settingsComponent.getSelectedService() == OPENAI);
settings.setUseAzureService(settingsComponent.getSelectedService() == ServiceType.AZURE);
settings.setUseYouService(settingsComponent.getSelectedService() == ServiceType.YOU);
YouSettingsState.getInstance()
.setDisplayWebSearchResults(serviceSelectionForm.isDisplayWebSearchResults());
settings.setUseLlamaService(settingsComponent.getSelectedService() == ServiceType.LLAMA_CPP);
var llamaModelPreferencesForm = serviceSelectionForm.getLlamaModelPreferencesForm();
llamaSettings.setCustomLlamaModelPath(llamaModelPreferencesForm.getCustomLlamaModelPath());
llamaSettings.setHuggingFaceModel(llamaModelPreferencesForm.getSelectedModel());
llamaSettings.setUseCustomModel(llamaModelPreferencesForm.isUseCustomLlamaModel());
llamaSettings.setPromptTemplate(llamaModelPreferencesForm.getPromptTemplate());
llamaSettings.setServerPort(serviceSelectionForm.getLlamaServerPort());
llamaSettings.setContextSize(serviceSelectionForm.getContextSize());
openAISettings.apply(serviceSelectionForm);
azureSettings.apply(serviceSelectionForm);
@ -93,7 +121,7 @@ public class SettingsConfigurable implements Configurable {
resetActiveTab();
if (serviceChanged) {
TelemetryAction.SETTINGS_CHANGED.createActionMessage()
.property("service", getServiceCode(serviceSelectionForm))
.property("service", getServiceCode())
.send();
}
}
@ -104,15 +132,32 @@ public class SettingsConfigurable implements Configurable {
var settings = SettingsState.getInstance();
var openAISettings = OpenAISettingsState.getInstance();
var azureSettings = AzureSettingsState.getInstance();
var llamaSettings = LlamaSettingsState.getInstance();
var serviceSelectionForm = settingsComponent.getServiceSelectionForm();
settingsComponent.setEmail(settings.getEmail());
// settingsComponent.setEmail(settings.getEmail());
settingsComponent.setDisplayName(settings.getDisplayName());
serviceSelectionForm.setOpenAIServiceSelected(settings.isUseOpenAIService());
serviceSelectionForm.setAzureServiceSelected(settings.isUseAzureService());
serviceSelectionForm.setYouServiceSelected(settings.isUseYouService());
// TODO
if (settings.isUseOpenAIService()) {
settingsComponent.setSelectedService(OPENAI);
}
if (settings.isUseAzureService()) {
settingsComponent.setSelectedService(ServiceType.AZURE);
}
if (settings.isUseYouService()) {
settingsComponent.setSelectedService(ServiceType.YOU);
}
if (settings.isUseLlamaService()) {
settingsComponent.setSelectedService(ServiceType.LLAMA_CPP);
}
var llamaModelPreferencesForm = serviceSelectionForm.getLlamaModelPreferencesForm();
llamaModelPreferencesForm.setSelectedModel(llamaSettings.getHuggingFaceModel());
llamaModelPreferencesForm.setCustomLlamaModelPath(llamaSettings.getCustomLlamaModelPath());
llamaModelPreferencesForm.setUseCustomLlamaModel(llamaSettings.isUseCustomModel());
llamaModelPreferencesForm.setPromptTemplate(llamaSettings.getPromptTemplate());
serviceSelectionForm.setLlamaServerPort(llamaSettings.getServerPort());
serviceSelectionForm.setContextSize(llamaSettings.getContextSize());
openAISettings.reset(serviceSelectionForm);
azureSettings.reset(serviceSelectionForm);
@ -128,12 +173,11 @@ public class SettingsConfigurable implements Configurable {
settingsComponent = null;
}
private boolean isServiceChanged(
ServiceSelectionForm serviceSelectionForm,
SettingsState settings) {
return serviceSelectionForm.isOpenAIServiceSelected() != settings.isUseOpenAIService() ||
serviceSelectionForm.isAzureServiceSelected() != settings.isUseAzureService() ||
serviceSelectionForm.isYouServiceSelected() != settings.isUseYouService();
private boolean isServiceChanged(SettingsState settings) {
return (settingsComponent.getSelectedService() == OPENAI) != settings.isUseOpenAIService() ||
(settingsComponent.getSelectedService() == AZURE) != settings.isUseAzureService() ||
(settingsComponent.getSelectedService() == YOU) != settings.isUseYouService() ||
(settingsComponent.getSelectedService() == LLAMA_CPP) != settings.isUseLlamaService();
}
private void resetActiveTab() {
@ -146,16 +190,19 @@ public class SettingsConfigurable implements Configurable {
project.getService(StandardChatToolWindowContentManager.class).resetActiveTab();
}
private String getServiceCode(ServiceSelectionForm serviceSelectionForm) {
if (serviceSelectionForm.isOpenAIServiceSelected()) {
private String getServiceCode() {
if (settingsComponent.getSelectedService() == OPENAI) {
return "openai";
}
if (serviceSelectionForm.isAzureServiceSelected()) {
if (settingsComponent.getSelectedService() == AZURE) {
return "azure";
}
if (serviceSelectionForm.isYouServiceSelected()) {
if (settingsComponent.getSelectedService() == YOU) {
return "you";
}
if (settingsComponent.getSelectedService() == LLAMA_CPP) {
return "llama.cpp";
}
return null;
}
}

View file

@ -38,7 +38,8 @@ public class AdvancedSettingsComponent {
proxyTypeComboBox.setSelectedItem(advancedSettings.getProxyType());
proxyHostField = new JBTextField(advancedSettings.getProxyHost(), 20);
proxyPortField = new PortField();
proxyAuthCheckbox = new JBCheckBox(CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.authCheckBoxField.label"));
proxyAuthCheckbox = new JBCheckBox(CodeGPTBundle.get(
"advancedSettingsConfigurable.proxy.authCheckBoxField.label"));
proxyAuthUsername = new JBTextField(20);
proxyAuthUsername.setEnabled(advancedSettings.isProxyAuthSelected());
proxyAuthPassword = new JBPasswordField();
@ -52,10 +53,11 @@ public class AdvancedSettingsComponent {
readTimeoutField = new PortField(advancedSettings.getReadTimeout());
mainPanel = FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.title")))
.addComponent(new TitledSeparator(CodeGPTBundle.get(
"advancedSettingsConfigurable.proxy.title")))
.addComponent(createProxySettingsForm())
.addVerticalGap(4)
.addComponent(new TitledSeparator("Connection Settings"))
.addComponent(new TitledSeparator(CodeGPTBundle.get("advancedSettingsConfigurable.connectionSettings.title")))
.addComponent(createConnectionSettingsForm())
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
@ -63,8 +65,8 @@ public class AdvancedSettingsComponent {
private JPanel createConnectionSettingsForm() {
var panel = FormBuilder.createFormBuilder()
.addLabeledComponent("Connection timeout (s):", connectionTimeoutField)
.addLabeledComponent("Read timeout (s):", readTimeoutField)
.addLabeledComponent(CodeGPTBundle.get("advancedSettingsConfigurable.connectionSettings.connectionTimeout.label"), connectionTimeoutField)
.addLabeledComponent(CodeGPTBundle.get("advancedSettingsConfigurable.connectionSettings.readTimeout.label"), readTimeoutField)
.getPanel();
panel.setBorder(JBUI.Borders.emptyLeft(16));
return panel;
@ -145,15 +147,15 @@ public class AdvancedSettingsComponent {
var proxyTypePanel = SwingUtils.createPanel(
proxyTypeComboBox,
CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.typeComboBoxField.label"),
CodeGPTBundle.get("advancedSettingsConfigurable.proxy.typeComboBoxField.label"),
false);
var proxyHostPanel = SwingUtils.createPanel(
proxyHostField,
CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.hostField.label"),
CodeGPTBundle.get("advancedSettingsConfigurable.proxy.hostField.label"),
false);
var proxyPortPanel = SwingUtils.createPanel(
proxyPortField,
CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.portField.label"),
CodeGPTBundle.get("advancedSettingsConfigurable.proxy.portField.label"),
false);
SwingUtils.setEqualLabelWidths(proxyTypePanel, proxyHostPanel);
SwingUtils.setEqualLabelWidths(proxyPortPanel, proxyHostPanel);
@ -166,10 +168,10 @@ public class AdvancedSettingsComponent {
.createPanel());
var proxyUsernamePanel = SwingUtils.createPanel(proxyAuthUsername,
CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.usernameField.label"),
CodeGPTBundle.get("advancedSettingsConfigurable.proxy.usernameField.label"),
false);
var proxyPasswordPanel = SwingUtils.createPanel(proxyAuthPassword,
CodeGPTBundle.get("advancedSettingsConfigurable.section.proxy.passwordField.label"),
CodeGPTBundle.get("advancedSettingsConfigurable.proxy.passwordField.label"),
false);
SwingUtils.setEqualLabelWidths(proxyPasswordPanel, proxyUsernamePanel);

View file

@ -180,10 +180,10 @@ public class ConfigurationComponent {
try {
var value = Double.parseDouble(valueText);
if (value > 1.0 || value < 0.0) {
return new ValidationInfo("Value must be between 0 and 1.", component);
return new ValidationInfo(CodeGPTBundle.get("validation.error.mustBeBetweenZeroAndOne"), component);
}
} catch (NumberFormatException e) {
return new ValidationInfo("Value must be number.", component);
return new ValidationInfo(CodeGPTBundle.get("validation.error.mustBeNumber"), component);
}
return null;

View file

@ -0,0 +1,98 @@
package ee.carlrobert.codegpt.settings.service;
import static java.lang.String.format;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.util.DownloadingUtils;
import ee.carlrobert.codegpt.util.file.FileUtils;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.swing.DefaultComboBoxModel;
import org.jetbrains.annotations.NotNull;
public class DownloadModelAction extends AnAction {
private static final Logger LOG = Logger.getInstance(DownloadModelAction.class);
private final Consumer<ProgressIndicator> onDownload;
private final Runnable onDownloaded;
private final Consumer<Exception> onFailed;
private final Consumer<String> onUpdateProgress;
private final DefaultComboBoxModel<HuggingFaceModel> comboBoxModel;
public DownloadModelAction(
Consumer<ProgressIndicator> onDownload,
Runnable onDownloaded,
Consumer<Exception> onFailed,
Consumer<String> onUpdateProgress,
DefaultComboBoxModel<HuggingFaceModel> comboBoxModel) {
this.onDownload = onDownload;
this.onDownloaded = onDownloaded;
this.onFailed = onFailed;
this.onUpdateProgress = onUpdateProgress;
this.comboBoxModel = comboBoxModel;
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
ProgressManager.getInstance().run(new DownloadBackgroundTask(e.getProject()));
}
class DownloadBackgroundTask extends Task.Backgroundable {
DownloadBackgroundTask(Project project) {
super(project, CodeGPTBundle.get("settingsConfigurable.service.llama.progress.downloadingModel.title"), true);
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
var model = (HuggingFaceModel) comboBoxModel.getSelectedItem();
URL url = model.getFileURL();
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> progressUpdateScheduler = null;
try {
onDownload.accept(indicator);
indicator.setIndeterminate(false);
indicator.setText(format(CodeGPTBundle.get("settingsConfigurable.service.llama.progress.downloadingModelIndicator.text"), model.getFileName()));
long fileSize = url.openConnection().getContentLengthLong();
long[] bytesRead = {0};
long startTime = System.currentTimeMillis();
progressUpdateScheduler = executorService.scheduleAtFixedRate(() ->
onUpdateProgress.accept(
DownloadingUtils.getFormattedDownloadProgress(startTime, fileSize, bytesRead[0])),
0, 1, TimeUnit.SECONDS);
FileUtils.copyFileWithProgress(model.getFileName(), url, bytesRead, fileSize, indicator);
} catch (IOException ex) {
LOG.error("Unable to open connection", ex);
onFailed.accept(ex);
} finally {
if (progressUpdateScheduler != null) {
progressUpdateScheduler.cancel(true);
}
executorService.shutdown();
}
}
@Override
public void onSuccess() {
onDownloaded.run();
}
}
}

View file

@ -0,0 +1,442 @@
package ee.carlrobert.codegpt.settings.service;
import static java.util.stream.Collectors.toList;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.icons.AllIcons.General;
import com.intellij.ide.HelpTooltip;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.panel.ComponentPanelBuilder;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.EnumComboBoxModel;
import com.intellij.ui.components.AnActionLink;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent;
import ee.carlrobert.codegpt.completions.llama.PromptTemplate;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.io.File;
import java.util.Map;
import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.NotNull;
public class LlamaModelPreferencesForm {
private static final Map<Integer, Map<Integer, ModelDetails>> modelDetailsMap = Map.of(
7, Map.of(
3, new ModelDetails(3.30, 5.80),
4, new ModelDetails(4.08, 6.58),
5, new ModelDetails(4.78, 7.28)),
13, Map.of(
3, new ModelDetails(6.34, 8.84),
4, new ModelDetails(7.87, 10.37),
5, new ModelDetails(9.23, 11.73)),
34, Map.of(
3, new ModelDetails(16.28, 18.78),
4, new ModelDetails(20.22, 22.72),
5, new ModelDetails(23.84, 26.34)));
private final TextFieldWithBrowseButton customModelPathBrowserButton;
private final ComboBox<LlamaModel> modelComboBox;
private final ComboBox<ModelSize> modelSizeComboBox;
private final ComboBox<HuggingFaceModel> huggingFaceModelComboBox;
private final ComboBox<PromptTemplate> promptTemplateComboBox;
private final JBLabel modelExistsIcon;
private final DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel;
private final JBCheckBox useCustomModelCheckBox;
private final JBLabel helpIcon;
private final JPanel downloadModelActionLinkWrapper;
private final JBLabel progressLabel;
private final JBLabel modelDetailsLabel;
public TextFieldWithBrowseButton getCustomModelPathBrowserButton() {
return customModelPathBrowserButton;
}
public ComboBox<HuggingFaceModel> getHuggingFaceModelComboBox() {
return huggingFaceModelComboBox;
}
public LlamaModelPreferencesForm() {
var llamaServerAgent = ApplicationManager.getApplication().getService(LlamaServerAgent.class);
var llamaSettings = LlamaSettingsState.getInstance();
customModelPathBrowserButton = createCustomModelPathBrowseButton(
llamaSettings.isUseCustomModel() && !llamaServerAgent.isServerRunning());
customModelPathBrowserButton.setText(llamaSettings.getCustomLlamaModelPath());
progressLabel = new JBLabel("");
progressLabel.setBorder(JBUI.Borders.emptyLeft(2));
progressLabel.setFont(JBUI.Fonts.smallFont());
modelExistsIcon = new JBLabel(Actions.Commit);
modelExistsIcon.setVisible(isModelExists(llamaSettings.getHuggingFaceModel()));
helpIcon = new JBLabel(General.ContextHelp);
huggingFaceComboBoxModel = new DefaultComboBoxModel<>();
var llm = llamaSettings.getHuggingFaceModel();
var llamaModel = LlamaModel.findByHuggingFaceModel(llm);
var selectableModels = llamaModel.getHuggingFaceModels().stream()
.filter(model -> model.getParameterSize() == llm.getParameterSize())
.collect(toList());
huggingFaceComboBoxModel.addAll(selectableModels);
huggingFaceComboBoxModel.setSelectedItem(selectableModels.get(0));
downloadModelActionLinkWrapper = new JPanel(new BorderLayout());
downloadModelActionLinkWrapper.setBorder(JBUI.Borders.emptyLeft(2));
downloadModelActionLinkWrapper.add(
createDownloadModelLink(
progressLabel,
downloadModelActionLinkWrapper,
huggingFaceComboBoxModel),
BorderLayout.WEST);
modelDetailsLabel = new JBLabel();
huggingFaceModelComboBox = createHuggingFaceComboBox(
huggingFaceComboBoxModel,
modelExistsIcon,
modelDetailsLabel,
downloadModelActionLinkWrapper);
huggingFaceModelComboBox.setEnabled(!llamaServerAgent.isServerRunning());
var modelSizeComboBoxModel = new DefaultComboBoxModel<ModelSize>();
var initialModelSizes = llamaModel.getSortedUniqueModelSizes().stream()
.map(ModelSize::new)
.collect(toList());
modelSizeComboBoxModel.addAll(initialModelSizes);
modelSizeComboBoxModel.setSelectedItem(initialModelSizes.get(0));
var modelComboBoxModel = new EnumComboBoxModel<>(LlamaModel.class);
modelComboBox = createModelComboBox(modelComboBoxModel, llamaModel, modelSizeComboBoxModel);
modelComboBox.setEnabled(!llamaServerAgent.isServerRunning());
modelSizeComboBox = createModelSizeComboBox(
modelComboBoxModel,
modelSizeComboBoxModel,
huggingFaceComboBoxModel);
modelSizeComboBox.setEnabled(initialModelSizes.size() > 1 && !llamaServerAgent.isServerRunning());
promptTemplateComboBox = new ComboBox<>(new EnumComboBoxModel<>(PromptTemplate.class));
promptTemplateComboBox.setSelectedItem(llamaSettings.getPromptTemplate());
promptTemplateComboBox.setEnabled(
llamaSettings.isUseCustomModel() && !llamaServerAgent.isServerRunning());
promptTemplateComboBox.setPreferredSize(modelComboBox.getPreferredSize());
useCustomModelCheckBox = new JBCheckBox(CodeGPTBundle.get(
"settingsConfigurable.service.llama.useCustomModel.label"), llamaSettings.isUseCustomModel());
useCustomModelCheckBox.setEnabled(!llamaServerAgent.isServerRunning());
useCustomModelCheckBox.addChangeListener(e -> {
var selected = ((JBCheckBox) e.getSource()).isSelected();
customModelPathBrowserButton.setEnabled(selected && !llamaServerAgent.isServerRunning());
promptTemplateComboBox.setEnabled(selected && !llamaServerAgent.isServerRunning());
modelComboBox.setEnabled(!selected);
modelSizeComboBox.setEnabled((!selected));
huggingFaceModelComboBox.setEnabled((!selected));
});
}
public JPanel getForm() {
var customModelHelpText = ComponentPanelBuilder.createCommentComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.customModelPath.comment"),
true);
customModelHelpText.setBorder(JBUI.Borders.empty(0, 4));
var quantizationHelpText = ComponentPanelBuilder.createCommentComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.quantization.comment"),
true);
quantizationHelpText.setBorder(JBUI.Borders.empty(0, 4));
var modelComboBoxWrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
modelComboBoxWrapper.add(modelComboBox);
modelComboBoxWrapper.add(Box.createHorizontalStrut(8));
modelComboBoxWrapper.add(helpIcon);
modelComboBoxWrapper.add(Box.createHorizontalStrut(4));
modelComboBoxWrapper.add(modelExistsIcon);
var huggingFaceModelComboBoxWrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
huggingFaceModelComboBoxWrapper.add(huggingFaceModelComboBox);
huggingFaceModelComboBoxWrapper.add(Box.createHorizontalStrut(8));
huggingFaceModelComboBoxWrapper.add(modelDetailsLabel);
return FormBuilder.createFormBuilder()
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.shared.model.label"), modelComboBoxWrapper)
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.llama.modelSize.label"), modelSizeComboBox)
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.llama.quantization.label"), huggingFaceModelComboBoxWrapper)
.addComponentToRightColumn(quantizationHelpText)
.addComponentToRightColumn(downloadModelActionLinkWrapper)
.addComponentToRightColumn(progressLabel)
.addVerticalGap(8)
.addComponent(useCustomModelCheckBox)
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.llama.promptTemplate.label"), promptTemplateComboBox)
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.llama.customModelPath.label"), customModelPathBrowserButton)
.addComponentToRightColumn(customModelHelpText)
.addVerticalGap(4)
.getPanel();
}
public void enableFields(boolean enabled) {
modelComboBox.setEnabled(enabled);
modelSizeComboBox.setEnabled(enabled);
huggingFaceModelComboBox.setEnabled(enabled);
useCustomModelCheckBox.setEnabled(enabled);
promptTemplateComboBox.setEnabled(enabled && useCustomModelCheckBox.isSelected());
customModelPathBrowserButton.setEnabled(enabled && useCustomModelCheckBox.isSelected());
}
private static class ModelDetails {
double fileSize;
double maxRAMRequired;
public ModelDetails(double fileSize, double maxRAMRequired) {
this.fileSize = fileSize;
this.maxRAMRequired = maxRAMRequired;
}
}
private String getHuggingFaceModelDetailsHtml(HuggingFaceModel model) {
int parameterSize = model.getParameterSize();
int quantization = model.getQuantization();
if (!modelDetailsMap.containsKey(parameterSize)) {
return "";
}
ModelDetails details = modelDetailsMap.get(parameterSize).get(quantization);
if (details == null) {
return "";
}
return String.format("<html>"
+ "<p style=\"margin: 0\"><small>File Size: <strong>%.2f GB</strong></small></p>"
+ "<p style=\"margin: 0\"><small>Max RAM Required: <strong>%.2f GB</strong></small></p>"
+ "</html>", details.fileSize, details.maxRAMRequired);
}
public void setSelectedModel(HuggingFaceModel model) {
huggingFaceComboBoxModel.setSelectedItem(model);
}
public HuggingFaceModel getSelectedModel() {
return (HuggingFaceModel) huggingFaceComboBoxModel.getSelectedItem();
}
public void setCustomLlamaModelPath(String modelPath) {
customModelPathBrowserButton.setText(modelPath);
}
public String getCustomLlamaModelPath() {
return customModelPathBrowserButton.getText();
}
public void setUseCustomLlamaModel(boolean useCustomLlamaModel) {
useCustomModelCheckBox.setSelected(useCustomLlamaModel);
}
public boolean isUseCustomLlamaModel() {
return useCustomModelCheckBox.isSelected();
}
public void setPromptTemplate(PromptTemplate promptTemplate) {
promptTemplateComboBox.setSelectedItem(promptTemplate);
}
public PromptTemplate getPromptTemplate() {
return promptTemplateComboBox.getItem();
}
private ComboBox<LlamaModel> createModelComboBox(
EnumComboBoxModel<LlamaModel> llamaModelEnumComboBoxModel,
LlamaModel llamaModel,
DefaultComboBoxModel<ModelSize> modelSizeComboBoxModel) {
var comboBox = new ComboBox<>(llamaModelEnumComboBoxModel);
comboBox.setPreferredSize(new Dimension(280, comboBox.getPreferredSize().height));
comboBox.setSelectedItem(llamaModel);
comboBox.addItemListener(e -> {
var selectedModel = (LlamaModel) e.getItem();
var modelSizes = selectedModel.getSortedUniqueModelSizes().stream()
.map(ModelSize::new)
.collect(toList());
modelSizeComboBoxModel.removeAllElements();
modelSizeComboBoxModel.addAll(modelSizes);
modelSizeComboBoxModel.setSelectedItem(modelSizes.get(0));
modelSizeComboBox.setEnabled(modelSizes.size() > 1);
var huggingFaceModels = selectedModel.getHuggingFaceModels().stream()
.filter(model -> {
var size = ((ModelSize) modelSizeComboBoxModel.getSelectedItem()).getSize();
return size == model.getParameterSize();
})
.collect(toList());
huggingFaceComboBoxModel.removeAllElements();
huggingFaceComboBoxModel.addAll(huggingFaceModels);
huggingFaceComboBoxModel.setSelectedItem(huggingFaceModels.get(0));
});
return comboBox;
}
private ComboBox<ModelSize> createModelSizeComboBox(
EnumComboBoxModel<LlamaModel> llamaModelComboBoxModel,
DefaultComboBoxModel<ModelSize> modelSizeComboBoxModel,
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel) {
var comboBox = new ComboBox<>(modelSizeComboBoxModel);
comboBox.setPreferredSize(modelComboBox.getPreferredSize());
comboBox.setSelectedItem(modelSizeComboBoxModel.getSelectedItem());
comboBox.addItemListener(e -> {
var selectedModel = llamaModelComboBoxModel.getSelectedItem();
var models = selectedModel.getHuggingFaceModels().stream()
.filter(model -> {
var selectedModelSize = (ModelSize) modelSizeComboBoxModel.getSelectedItem();
return selectedModelSize != null &&
selectedModelSize.getSize() == model.getParameterSize();
})
.collect(toList());
if (!models.isEmpty()) {
huggingFaceComboBoxModel.removeAllElements();
huggingFaceComboBoxModel.addAll(models);
huggingFaceComboBoxModel.setSelectedItem(models.get(0));
}
});
return comboBox;
}
private ComboBox<HuggingFaceModel> createHuggingFaceComboBox(
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel,
JBLabel modelExistsIcon,
JBLabel modelDetailsLabel,
JPanel downloadModelActionLinkWrapper) {
var comboBox = new ComboBox<>(huggingFaceComboBoxModel);
comboBox.addItemListener(e -> {
var selectedModel = (HuggingFaceModel) e.getItem();
var modelExists = isModelExists(selectedModel);
updateModelHelpTooltip(selectedModel);
modelDetailsLabel.setText(getHuggingFaceModelDetailsHtml(selectedModel));
modelExistsIcon.setVisible(modelExists);
downloadModelActionLinkWrapper.setVisible(!modelExists);
});
return comboBox;
}
private TextFieldWithBrowseButton createCustomModelPathBrowseButton(boolean enabled) {
var browseButton = new TextFieldWithBrowseButton();
browseButton.setEnabled(enabled);
var fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileDescriptor("gguf");
fileChooserDescriptor.setForcedToUseIdeaFileChooser(true);
fileChooserDescriptor.setHideIgnored(false);
browseButton.addBrowseFolderListener(new TextBrowseFolderListener(fileChooserDescriptor));
return browseButton;
}
private boolean isModelExists(HuggingFaceModel model) {
return FileUtil.exists(
CodeGPTPlugin.getLlamaModelsPath() + File.separator + model.getFileName());
}
private AnActionLink createCancelDownloadLink(
JBLabel progressLabel,
JPanel actionLinkWrapper,
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel,
ProgressIndicator progressIndicator) {
return new AnActionLink(CodeGPTBundle.get("settingsConfigurable.service.llama.cancelDownloadLink.label"), new AnAction() {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
SwingUtilities.invokeLater(() -> {
configureFieldsForDownloading(false);
updateActionLink(
actionLinkWrapper,
createDownloadModelLink(progressLabel, actionLinkWrapper, huggingFaceComboBoxModel));
progressIndicator.cancel();
});
}
});
}
private void updateActionLink(JPanel actionLinkWrapper, AnActionLink actionLink) {
actionLinkWrapper.removeAll();
actionLinkWrapper.add(actionLink, BorderLayout.WEST);
actionLinkWrapper.revalidate();
actionLinkWrapper.repaint();
}
void configureFieldsForDownloading(boolean downloading) {
progressLabel.setText("");
progressLabel.setVisible(downloading);
modelComboBox.setEnabled(!downloading);
modelSizeComboBox.setEnabled(!downloading);
huggingFaceModelComboBox.setEnabled(!downloading);
modelExistsIcon.setVisible(!downloading);
}
private AnActionLink createDownloadModelLink(
JBLabel progressLabel,
JPanel actionLinkWrapper,
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel) {
return new AnActionLink(CodeGPTBundle.get("settingsConfigurable.service.llama.downloadModelLink.label"), new DownloadModelAction(
progressIndicator -> {
SwingUtilities.invokeLater(() -> {
configureFieldsForDownloading(true);
updateActionLink(
actionLinkWrapper,
createCancelDownloadLink(
progressLabel,
actionLinkWrapper,
huggingFaceComboBoxModel,
progressIndicator));
});
},
() -> SwingUtilities.invokeLater(() -> {
configureFieldsForDownloading(false);
updateActionLink(
actionLinkWrapper,
createDownloadModelLink(progressLabel, actionLinkWrapper, huggingFaceComboBoxModel));
actionLinkWrapper.setVisible(false);
LlamaSettingsState.getInstance()
.setHuggingFaceModel((HuggingFaceModel) huggingFaceComboBoxModel.getSelectedItem());
}),
(error) -> {
throw new RuntimeException(error);
},
(text) -> SwingUtilities.invokeLater(() -> progressLabel.setText(text)),
huggingFaceComboBoxModel), "unknown");
}
private void updateModelHelpTooltip(HuggingFaceModel model) {
helpIcon.setToolTipText(null);
var llamaModel = LlamaModel.findByHuggingFaceModel(model);
new HelpTooltip()
.setTitle(llamaModel.getLabel())
.setDescription("<html><p>" + llamaModel.getDescription() + "</p></html>")
.setBrowserLink(CodeGPTBundle.get("settingsConfigurable.service.llama.linkToModel.label"), model.getHuggingFaceURL())
.installOn(helpIcon);
}
static class ModelSize {
private final int size;
ModelSize(int size) {
this.size = size;
}
int getSize() {
return size;
}
@Override
public String toString() {
return size + "B";
}
}
}

View file

@ -0,0 +1,163 @@
package ee.carlrobert.codegpt.settings.service;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.panel.ComponentPanelBuilder;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.PortField;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.fields.IntegerField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.util.OverlayUtils;
import java.awt.BorderLayout;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
public class LlamaServiceSelectionForm extends JPanel {
private final LlamaModelPreferencesForm llamaModelPreferencesForm;
private final PortField portField;
private final IntegerField maxTokensField;
public LlamaServiceSelectionForm() {
var llamaServerAgent = ApplicationManager.getApplication().getService(LlamaServerAgent.class);
var serverRunning = llamaServerAgent.isServerRunning();
portField = new PortField(LlamaSettingsState.getInstance().getServerPort());
portField.setEnabled(!serverRunning);
var serverProgressPanel = new ServerProgressPanel();
llamaModelPreferencesForm = new LlamaModelPreferencesForm();
maxTokensField = new IntegerField("max_tokens", 256, 4096);
maxTokensField.setColumns(12);
maxTokensField.setValue(2048);
maxTokensField.setEnabled(!serverRunning);
var serverButton = new JButton();
serverButton.setText(serverRunning ?
CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label") :
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(serverRunning ? Actions.Suspend : Actions.Execute);
serverButton.addActionListener(event -> {
if (llamaModelPreferencesForm.isUseCustomLlamaModel()) {
var customModelPath = llamaModelPreferencesForm.getCustomLlamaModelPath();
if (customModelPath == null || customModelPath.isEmpty()) {
OverlayUtils.showBalloon(
CodeGPTBundle.get("validation.error.fieldRequired"),
MessageType.ERROR,
llamaModelPreferencesForm.getCustomModelPathBrowserButton());
return;
}
} else {
if (!isModelExists(llamaModelPreferencesForm.getSelectedModel())) {
OverlayUtils.showBalloon(
CodeGPTBundle.get("settingsConfigurable.service.llama.overlay.modelNotDownloaded.text"),
MessageType.ERROR,
llamaModelPreferencesForm.getHuggingFaceModelComboBox());
return;
}
}
if (llamaServerAgent.isServerRunning()) {
setFormEnabled(true);
serverButton.setText(CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(Actions.Execute);
serverProgressPanel.updateText(CodeGPTBundle.get("settingsConfigurable.service.llama.progress.stoppingServer"));
llamaServerAgent.stopAgent();
} else {
setFormEnabled(false);
serverButton.setText(CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label"));
serverButton.setIcon(Actions.Suspend);
serverProgressPanel.startProgress(CodeGPTBundle.get("settingsConfigurable.service.llama.progress.startingServer"));
// TODO: Move to LlamaModelPreferencesForm
var modelPath = llamaModelPreferencesForm.isUseCustomLlamaModel() ?
llamaModelPreferencesForm.getCustomLlamaModelPath() :
CodeGPTPlugin.getLlamaModelsPath() +
File.separator +
llamaModelPreferencesForm.getSelectedModel().getFileName();
llamaServerAgent.startAgent(
modelPath,
maxTokensField.getValue(),
portField.getNumber(),
serverProgressPanel,
() -> {
setFormEnabled(false);
serverProgressPanel.displayComponent(new JBLabel(
"Server running",
Actions.Commit,
SwingConstants.LEADING));
});
}
});
var contextSizeHelpText = ComponentPanelBuilder.createCommentComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.contextSize.comment"),
true);
contextSizeHelpText.setBorder(JBUI.Borders.empty(0, 4));
setLayout(new BorderLayout());
add(FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(CodeGPTBundle.get("settingsConfigurable.service.llama.modelPreferences.title")))
.addComponent(withEmptyLeftBorder(llamaModelPreferencesForm.getForm()))
.addComponent(new TitledSeparator(CodeGPTBundle.get("settingsConfigurable.service.llama.serverPreferences.title")))
.addComponent(withEmptyLeftBorder(FormBuilder.createFormBuilder()
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.llama.contextSize.label"), maxTokensField)
.addComponentToRightColumn(contextSizeHelpText)
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.llama.port.label"), JBUI.Panels.simplePanel()
.addToLeft(portField)
.addToRight(serverButton))
.getPanel()))
.addVerticalGap(4)
.addComponent(withEmptyLeftBorder(serverProgressPanel))
.addComponentFillVertically(new JPanel(), 0)
.getPanel());
}
private boolean isModelExists(HuggingFaceModel model) {
return FileUtil.exists(
CodeGPTPlugin.getLlamaModelsPath() + File.separator + model.getFileName());
}
private void setFormEnabled(boolean enabled) {
llamaModelPreferencesForm.enableFields(enabled);
portField.setEnabled(enabled);
maxTokensField.setEnabled(enabled);
}
public void setServerPort(int serverPort) {
portField.setNumber(serverPort);
}
public int getServerPort() {
return portField.getNumber();
}
public LlamaModelPreferencesForm getLlamaModelPreferencesForm() {
return llamaModelPreferencesForm;
}
private JComponent withEmptyLeftBorder(JComponent component) {
component.setBorder(JBUI.Borders.emptyLeft(16));
return component;
}
public int getContextSize() {
return maxTokensField.getValue();
}
public void setContextSize(int contextSize) {
maxTokensField.setValue(contextSize);
}
}

View file

@ -0,0 +1,37 @@
package ee.carlrobert.codegpt.settings.service;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.AsyncProcessIcon;
import java.awt.FlowLayout;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
public class ServerProgressPanel extends JPanel {
private final JBLabel label = new JBLabel();
public ServerProgressPanel() {
super(new FlowLayout(FlowLayout.LEADING, 0, 0));
setVisible(false);
add(new AsyncProcessIcon("sign_in_spinner"));
add(Box.createHorizontalStrut(4));
add(label);
}
public void startProgress(String text) {
setVisible(true);
updateText(text);
}
public void updateText(String text) {
label.setText(text);
}
public void displayComponent(JComponent component) {
removeAll();
add(component);
revalidate();
repaint();
}
}

View file

@ -1,58 +1,46 @@
package ee.carlrobert.codegpt.settings;
package ee.carlrobert.codegpt.settings.service;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.JBColor;
import com.intellij.ui.EnumComboBoxModel;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.*;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBPasswordField;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UI;
import com.intellij.util.ui.UIUtil;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.completions.you.auth.AuthenticationNotifier;
import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager;
import ee.carlrobert.codegpt.settings.state.AzureSettingsState;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.completions.you.auth.AuthenticationNotifier;
import ee.carlrobert.codegpt.settings.state.YouSettingsState;
import ee.carlrobert.codegpt.telemetry.ui.utils.JBLabelUtils;
import ee.carlrobert.codegpt.util.SwingUtils;
import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel;
import ee.carlrobert.llm.completion.CompletionModel;
import java.awt.*;
import java.util.List;
import java.util.Map;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JPanel;
public class ServiceSelectionForm {
private static final Logger LOG = Logger.getInstance(ServiceSelectionForm.class);
private final Disposable parentDisposable;
private static final OpenAIChatCompletionModel[] DEFAULT_OPENAI_MODELS = new OpenAIChatCompletionModel[]{
OpenAIChatCompletionModel.GPT_3_5,
OpenAIChatCompletionModel.GPT_3_5_16k,
OpenAIChatCompletionModel.GPT_4,
OpenAIChatCompletionModel.GPT_4_32k
};
private final JBRadioButton useOpenAIServiceRadioButton;
private final JBRadioButton useAzureServiceRadioButton;
private final JBPasswordField openAIApiKeyField;
private final JBTextField openAIBaseHostField;
private final JBTextField openAIPathField;
private final JBTextField openAIOrganizationField;
private final JPanel openAIServiceSectionPanel;
private final ComboBox<CompletionModel> openAICompletionModelComboBox;
private final ComboBox<OpenAIChatCompletionModel> openAICompletionModelComboBox;
private final JBRadioButton useAzureApiKeyAuthenticationRadioButton;
private final JBPasswordField azureApiKeyField;
@ -66,13 +54,14 @@ public class ServiceSelectionForm {
private final JBTextField azureDeploymentIdField;
private final JBTextField azureApiVersionField;
private final JPanel azureServiceSectionPanel;
private final ComboBox<CompletionModel> azureCompletionModelComboBox;
private final ComboBox<OpenAIChatCompletionModel> azureCompletionModelComboBox;
private final JBRadioButton useYouServiceRadioButton;
private final JPanel youServiceSectionPanel;
private final JBCheckBox displayWebSearchResultsCheckBox;
public ServiceSelectionForm(Disposable parentDisposable, SettingsState settings) {
private final LlamaServiceSelectionForm llamaServiceSectionPanel;
public ServiceSelectionForm(Disposable parentDisposable) {
this.parentDisposable = parentDisposable;
var openAISettings = OpenAISettingsState.getInstance();
var azureSettings = AzureSettingsState.getInstance();
@ -83,68 +72,58 @@ public class ServiceSelectionForm {
azureApiKeyField = new JBPasswordField();
azureApiKeyField.setColumns(30);
azureApiKeyField.setText(AzureCredentialsManager.getInstance().getAzureOpenAIApiKey());
azureApiKeyFieldPanel = UI.PanelFactory.panel(azureApiKeyField)
.withLabel("API key:")
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"))
.resizeX(false)
.createPanel();
azureActiveDirectoryTokenField = new JBPasswordField();
azureActiveDirectoryTokenField.setColumns(30);
azureActiveDirectoryTokenField.setText(
AzureCredentialsManager.getInstance().getAzureActiveDirectoryToken());
azureActiveDirectoryTokenFieldPanel = UI.PanelFactory.panel(azureActiveDirectoryTokenField)
.withLabel("Bearer token:")
.withLabel(CodeGPTBundle.get("settingsConfigurable.service.azure.bearerToken.label"))
.resizeX(false)
.createPanel();
useAzureApiKeyAuthenticationRadioButton = new JBRadioButton(
"Use API Key authentication",
CodeGPTBundle.get("settingsConfigurable.service.azure.useApiKeyAuth.label"),
azureSettings.isUseAzureApiKeyAuthentication());
useAzureActiveDirectoryAuthenticationRadioButton = new JBRadioButton(
"Use Active Directory authentication",
CodeGPTBundle.get("settingsConfigurable.service.azure.useActiveDirectoryAuth.label"),
azureSettings.isUseAzureActiveDirectoryAuthentication());
useOpenAIServiceRadioButton = new JBRadioButton(
CodeGPTBundle.get("settingsConfigurable.section.service.useOpenAIServiceRadioButtonLabel"),
settings.isUseOpenAIService());
useAzureServiceRadioButton = new JBRadioButton(
CodeGPTBundle.get("settingsConfigurable.section.service.useAzureServiceRadioButtonLabel"),
settings.isUseAzureService());
useYouServiceRadioButton = new JBRadioButton(
CodeGPTBundle.get("settingsConfigurable.section.service.useYouServiceRadioButtonLabel"),
settings.isUseYouService());
openAIBaseHostField = new JBTextField(openAISettings.getBaseHost(), 30);
openAIPathField = new JBTextField(openAISettings.getPath(), 30);
openAIOrganizationField = new JBTextField(openAISettings.getOrganization(), 30);
openAICompletionModelComboBox = new ModelComboBox(
DEFAULT_OPENAI_MODELS,
OpenAIChatCompletionModel.findByCode(openAISettings.getModel()));
var selectedOpenAIModel = OpenAIChatCompletionModel.findByCode(openAISettings.getModel());
openAICompletionModelComboBox = new ComboBox<>(
new EnumComboBoxModel<>(OpenAIChatCompletionModel.class));
openAICompletionModelComboBox.setSelectedItem(selectedOpenAIModel);
azureBaseHostField = new JBTextField(azureSettings.getBaseHost(), 35);
azurePathField = new JBTextField(azureSettings.getPath(), 35);
azureResourceNameField = new JBTextField(azureSettings.getResourceName(), 35);
azureDeploymentIdField = new JBTextField(azureSettings.getDeploymentId(), 35);
azureApiVersionField = new JBTextField(azureSettings.getApiVersion(), 35);
azureCompletionModelComboBox = new ModelComboBox(
DEFAULT_OPENAI_MODELS,
OpenAIChatCompletionModel.findByCode(azureSettings.getModel()));
azureCompletionModelComboBox = new ComboBox<>(
new EnumComboBoxModel<>(OpenAIChatCompletionModel.class));
azureCompletionModelComboBox.setSelectedItem(selectedOpenAIModel);
azureCompletionModelComboBox.getEditor()
.getEditorComponent()
.setMaximumSize(azureBaseHostField.getPreferredSize());
displayWebSearchResultsCheckBox = new JBCheckBox(
"Display web search results",
CodeGPTBundle.get("settingsConfigurable.service.you.displayResults.label"),
YouSettingsState.getInstance().isDisplayWebSearchResults());
displayWebSearchResultsCheckBox.setEnabled(YouUserManager.getInstance().isAuthenticated());
openAIServiceSectionPanel = createOpenAIServiceSectionPanel();
azureServiceSectionPanel = createAzureServiceSectionPanel();
youServiceSectionPanel = createYouServiceSectionPanel();
llamaServiceSectionPanel = new LlamaServiceSelectionForm();
registerPanelsVisibility(settings, azureSettings);
registerPanelsVisibility(azureSettings);
registerRadioButtons();
ApplicationManager.getApplication()
@ -154,58 +133,42 @@ public class ServiceSelectionForm {
(AuthenticationNotifier) () -> displayWebSearchResultsCheckBox.setEnabled(true));
}
public JPanel getForm() {
var panel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
panel.add(useOpenAIServiceRadioButton);
if (OpenAISettingsState.getInstance().isOpenAIQuotaExceeded()) {
panel.add(Box.createHorizontalStrut(4));
panel.add(new JBLabel("<html><sup style=\"color: #cc3300;\">quota exceeded</sup></html>"));
}
// flow layout's horizontal gap adds annoying horizontal padding on each sides
panel.add(Box.createHorizontalStrut(16));
panel.add(useAzureServiceRadioButton);
panel.add(Box.createHorizontalStrut(16));
panel.add(useYouServiceRadioButton);
return FormBuilder.createFormBuilder()
.addComponent(withEmptyLeftBorder(panel))
.addComponent(openAIServiceSectionPanel)
.addComponent(azureServiceSectionPanel)
.addComponent(youServiceSectionPanel)
.getPanel();
}
private JPanel createOpenAIServiceSectionPanel() {
var requestConfigurationPanel = UI.PanelFactory.grid()
.add(UI.PanelFactory.panel(openAIOrganizationField)
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.section.service.openai.organizationField.label"))
"settingsConfigurable.service.openai.organization.label"))
.resizeX(false)
.withComment(CodeGPTBundle.get(
"settingsConfigurable.section.service.openai.organizationField.comment")))
"settingsConfigurable.section.openai.organization.comment")))
.add(UI.PanelFactory.panel(openAIBaseHostField)
.withLabel("Base host:")
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.shared.baseHost.label"))
.resizeX(false))
.add(UI.PanelFactory.panel(openAIPathField)
.withLabel("Path:")
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.shared.path.label"))
.resizeX(false))
.add(UI.PanelFactory.panel(openAICompletionModelComboBox)
.withLabel("Model:")
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.shared.model.label"))
.resizeX(false))
.createPanel();
var apiKeyFieldPanel = UI.PanelFactory.panel(openAIApiKeyField)
.withLabel(CodeGPTBundle.get("settingsConfigurable.section.integration.apiKeyField.label"))
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"))
.resizeX(false)
.withComment(
CodeGPTBundle.get("settingsConfigurable.section.integration.apiKeyField.comment"))
CodeGPTBundle.get("settingsConfigurable.service.openai.apiKey.comment"))
.withCommentHyperlinkListener(SwingUtils::handleHyperlinkClicked)
.createPanel();
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator("Authentication"))
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.shared.authentication.title")))
.addComponent(withEmptyLeftBorder(apiKeyFieldPanel))
.addComponent(new TitledSeparator("Request Configuration"))
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.shared.requestConfiguration.title")))
.addComponent(withEmptyLeftBorder(requestConfigurationPanel))
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
@ -228,46 +191,51 @@ public class ServiceSelectionForm {
var configPanel = withEmptyLeftBorder(UI.PanelFactory.grid()
.add(UI.PanelFactory.panel(azureResourceNameField)
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.section.service.azure.resourceNameField.label"))
"settingsConfigurable.service.azure.resourceName.label"))
.resizeX(false)
.withComment(CodeGPTBundle.get(
"settingsConfigurable.section.service.azure.resourceNameField.comment")))
"settingsConfigurable.service.azure.resourceName.comment")))
.add(UI.PanelFactory.panel(azureDeploymentIdField)
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.section.service.azure.deploymentIdField.label"))
"settingsConfigurable.service.azure.deploymentId.label"))
.resizeX(false)
.withComment(CodeGPTBundle.get(
"settingsConfigurable.section.service.azure.deploymentIdField.comment")))
"settingsConfigurable.service.azure.deploymentId.comment")))
.add(UI.PanelFactory.panel(azureApiVersionField)
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.section.service.azure.apiVersionField.label"))
"settingsConfigurable.service.azure.apiVersion.label"))
.resizeX(false)
.withComment(CodeGPTBundle.get(
"settingsConfigurable.section.service.azure.apiVersionField.comment")))
"settingsConfigurable.service.azure.apiVersion.comment")))
.add(UI.PanelFactory.panel(azureBaseHostField)
.withLabel("Base host:")
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.baseHost.label"))
.resizeX(false))
.add(UI.PanelFactory.panel(azurePathField)
.withLabel("Path:")
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.path.label"))
.resizeX(false))
.add(UI.PanelFactory.panel(azureCompletionModelComboBox)
.withLabel("Model:")
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.model.label"))
.resizeX(false))
.createPanel());
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator("Authentication"))
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.shared.authentication.title")))
.addComponent(authPanel)
.addComponent(new TitledSeparator("Request Configuration"))
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.shared.requestConfiguration.title")))
.addComponent(configPanel)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
private JPanel createYouServiceSectionPanel() {
return FormBuilder.createFormBuilder()
.addComponent(new YouServiceSelectionPanel(parentDisposable))
.addComponent(new TitledSeparator("Chat Preferences"))
.addComponent(new YouServiceSelectionForm(parentDisposable))
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.service.you.chatPreferences.title")))
.addComponent(withEmptyLeftBorder(displayWebSearchResultsCheckBox))
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
@ -276,32 +244,22 @@ public class ServiceSelectionForm {
return component;
}
private void registerPanelsVisibility(SettingsState settings, AzureSettingsState azureSettings) {
openAIServiceSectionPanel.setVisible(settings.isUseOpenAIService());
azureServiceSectionPanel.setVisible(settings.isUseAzureService());
private void registerPanelsVisibility(AzureSettingsState azureSettings) {
azureApiKeyFieldPanel.setVisible(azureSettings.isUseAzureApiKeyAuthentication());
azureActiveDirectoryTokenFieldPanel.setVisible(
azureSettings.isUseAzureActiveDirectoryAuthentication());
youServiceSectionPanel.setVisible(settings.isUseYouService());
}
private void registerRadioButtons() {
registerRadioButtons(
List.of(
Map.entry(useOpenAIServiceRadioButton, openAIServiceSectionPanel),
Map.entry(useAzureServiceRadioButton, azureServiceSectionPanel),
Map.entry(useYouServiceRadioButton, youServiceSectionPanel)));
registerRadioButtons(
List.of(
Map.entry(useAzureApiKeyAuthenticationRadioButton, azureApiKeyFieldPanel),
Map.entry(useAzureActiveDirectoryAuthenticationRadioButton,
azureActiveDirectoryTokenFieldPanel)));
registerRadioButtons(List.of(
Map.entry(useAzureApiKeyAuthenticationRadioButton, azureApiKeyFieldPanel),
Map.entry(useAzureActiveDirectoryAuthenticationRadioButton,
azureActiveDirectoryTokenFieldPanel)));
}
private void registerRadioButtons(List<Map.Entry<JBRadioButton, JPanel>> entries) {
var buttonGroup = new ButtonGroup();
entries.forEach(entry -> buttonGroup.add(entry.getKey()));
entries.forEach(entry -> entry.getKey().addActionListener((e) -> {
for (Map.Entry<JBRadioButton, JPanel> innerEntry : entries) {
innerEntry.getValue().setVisible(innerEntry.equals(entry));
@ -309,42 +267,6 @@ public class ServiceSelectionForm {
}));
}
public OpenAIChatCompletionModel getSelectedCompletionModel() {
return (OpenAIChatCompletionModel) (isOpenAIServiceSelected() ?
openAICompletionModelComboBox.getSelectedItem() :
azureCompletionModelComboBox.getSelectedItem());
}
public void setSelectedChatCompletionModel(OpenAIChatCompletionModel chatCompletionModel) {
if (isOpenAIServiceSelected()) {
openAICompletionModelComboBox.setSelectedItem(chatCompletionModel);
}
}
public void setOpenAIServiceSelected(boolean selected) {
useOpenAIServiceRadioButton.setSelected(selected);
}
public boolean isOpenAIServiceSelected() {
return useOpenAIServiceRadioButton.isSelected();
}
public void setAzureServiceSelected(boolean selected) {
useAzureServiceRadioButton.setSelected(selected);
}
public boolean isAzureServiceSelected() {
return useAzureServiceRadioButton.isSelected();
}
public boolean isYouServiceSelected() {
return useYouServiceRadioButton.isSelected();
}
public void setYouServiceSelected(boolean selected) {
useYouServiceRadioButton.setSelected(selected);
}
public void setOpenAIApiKey(String apiKey) {
openAIApiKeyField.setText(apiKey);
}
@ -461,6 +383,10 @@ public class ServiceSelectionForm {
return displayWebSearchResultsCheckBox.isSelected();
}
public LlamaModelPreferencesForm getLlamaModelPreferencesForm() {
return llamaServiceSectionPanel.getLlamaModelPreferencesForm();
}
public void setOpenAIPath(String path) {
openAIPathField.setText(path);
}
@ -476,4 +402,36 @@ public class ServiceSelectionForm {
public String getAzurePath() {
return azurePathField.getText();
}
public void setLlamaServerPort(int serverPort) {
llamaServiceSectionPanel.setServerPort(serverPort);
}
public int getLlamaServerPort() {
return llamaServiceSectionPanel.getServerPort();
}
public JPanel getOpenAIServiceSectionPanel() {
return openAIServiceSectionPanel;
}
public JPanel getAzureServiceSectionPanel() {
return azureServiceSectionPanel;
}
public JPanel getYouServiceSectionPanel() {
return youServiceSectionPanel;
}
public JPanel getLlamaServiceSectionPanel() {
return llamaServiceSectionPanel;
}
public int getContextSize() {
return llamaServiceSectionPanel.getContextSize();
}
public void setContextSize(int contextSize) {
llamaServiceSectionPanel.setContextSize(contextSize);
}
}

View file

@ -0,0 +1,31 @@
package ee.carlrobert.codegpt.settings.service;
import ee.carlrobert.codegpt.CodeGPTBundle;
public enum ServiceType {
OPENAI("OPENAI", CodeGPTBundle.get("service.openai.title")),
AZURE("AZURE", CodeGPTBundle.get("service.azure.title")),
YOU("YOU", CodeGPTBundle.get("service.you.title")),
LLAMA_CPP("LLAMA_CPP", CodeGPTBundle.get("service.llama.title"));
private final String code;
private final String label;
ServiceType(String code, String label) {
this.code = code;
this.label = label;
}
public String getCode() {
return code;
}
public String getLabel() {
return label;
}
@Override
public String toString() {
return label;
}
}

View file

@ -1,4 +1,4 @@
package ee.carlrobert.codegpt.settings;
package ee.carlrobert.codegpt.settings.service;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.ui.ComponentValidator;
@ -9,7 +9,6 @@ import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBPasswordField;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.components.OnOffButton;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBFont;
@ -35,7 +34,7 @@ import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.Nullable;
public class YouServiceSelectionPanel extends JPanel {
public class YouServiceSelectionForm extends JPanel {
private final JBTextField emailField;
private final JBPasswordField passwordField;
@ -43,7 +42,7 @@ public class YouServiceSelectionPanel extends JPanel {
private final JTextPane signUpTextPane;
private final AsyncProcessIcon loadingSpinner;
public YouServiceSelectionPanel(Disposable parentDisposable) {
public YouServiceSelectionForm(Disposable parentDisposable) {
super(new BorderLayout());
var settings = SettingsState.getInstance();
emailField = new JBTextField(settings.getEmail(), 25);
@ -52,8 +51,7 @@ public class YouServiceSelectionPanel extends JPanel {
if (!settings.getEmail().isEmpty()) {
passwordField.setText(YouCredentialsManager.getInstance().getAccountPassword());
}
signInButton = new JButton(
CodeGPTBundle.get("settingsConfigurable.section.userAuthentication.signIn.label"));
signInButton = new JButton(CodeGPTBundle.get("settingsConfigurable.service.you.signIn.label"));
signUpTextPane = createSignUpTextPane();
loadingSpinner = new AsyncProcessIcon("sign_in_spinner");
loadingSpinner.setBorder(JBUI.Borders.emptyLeft(8));
@ -106,7 +104,8 @@ public class YouServiceSelectionPanel extends JPanel {
if (component instanceof JBTextField) {
value = ((JBTextField) component).getText();
if (!isValidEmail(value)) {
return new ValidationInfo("The email you entered is invalid.", component)
return new ValidationInfo(
CodeGPTBundle.get("validation.error.invalidEmail"), component)
.withOKEnabled();
}
} else {
@ -114,7 +113,9 @@ public class YouServiceSelectionPanel extends JPanel {
}
if (StringUtil.isEmpty(value)) {
return new ValidationInfo("This field is required.", component).withOKEnabled();
return new ValidationInfo(
CodeGPTBundle.get("validation.error.fieldRequired"), component)
.withOKEnabled();
}
return null;
@ -134,18 +135,14 @@ public class YouServiceSelectionPanel extends JPanel {
private JTextPane createSignUpTextPane() {
var textPane = createTextPane(
"<html><a href=\"https://you.com/code\" style=\"padding:0;\">Don't have an account?<br/>Sign up with 'CodeGPT' coupon for free GPT-4</a></html>");
"<html><a href=\"https://you.com/code\">Don't have an account? Sign up</a></html>");
textPane.setBorder(JBUI.Borders.emptyLeft(4));
return textPane;
}
private JTextPane createTextPane(String htmlContent) {
var textPane = new JTextPane();
textPane.setContentType("text/html");
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
var textPane = SwingUtils.createTextPane(SwingUtils::handleHyperlinkClicked);
textPane.setText(htmlContent);
textPane.addHyperlinkListener(SwingUtils::handleHyperlinkClicked);
textPane.setEditable(false);
return textPane;
}
@ -162,9 +159,23 @@ public class YouServiceSelectionPanel extends JPanel {
JBTextField emailAddressField,
JBPasswordField passwordField,
@Nullable YouAuthenticationError error) {
var couponLabel = new JBLabel(
"<html>"
+ "<body>"
+ "<h1 style=\"text-align: center; padding: 0; margin: 0;\">Free GPT-4</h1>"
+ "<p style=\"text-align: center; margin-top: 8px; margin-bottom: 8px;\">Your coupon code</p>"
+ "<h1 style=\"text-align: center; border: 2px dotted #646464; padding: 4px 32px; margin: 0 0 12px 0; background-color: #45494a; cursor: pointer;\">CODEGPT</h1>"
+ "</body>"
+ "</html>")
.withBorder(JBUI.Borders.emptyLeft(45)) // TODO
.setCopyable(true);
var contentPanelBuilder = FormBuilder.createFormBuilder()
.addLabeledComponent("Email address:", emailAddressField)
.addLabeledComponent("Password:", passwordField)
.addComponentToRightColumn(JBUI.Panels.simplePanel().addToLeft(couponLabel))
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.you.email.label"),
emailAddressField)
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.you.password.label"),
passwordField)
.addVerticalGap(4)
.addComponentToRightColumn(createFooterPanel())
.addVerticalGap(4);
@ -176,22 +187,24 @@ public class YouServiceSelectionPanel extends JPanel {
contentPanelBuilder.addComponentToRightColumn(invalidCredentialsLabel);
}
var contentPanel = contentPanelBuilder.getPanel();
contentPanel.setBorder(JBUI.Borders.emptyLeft(16));
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.section.userAuthentication.title")))
.addComponent(JBUI.Panels
.simplePanel(contentPanelBuilder.getPanel())
.withBorder(JBUI.Borders.emptyLeft(16)))
CodeGPTBundle.get("settingsConfigurable.service.you.authentication.title")))
.addComponent(contentPanel)
.getPanel();
}
private JPanel createUserInformationPanel(YouUser user) {
var userManager = YouUserManager.getInstance();
var contentPanelBuilder = FormBuilder.createFormBuilder()
.addLabeledComponent("Email address:",
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.service.you.email.label"),
new JBLabel(user.getEmails().get(0).getEmail()).withFont(JBFont.label().asBold()));
var signOutButton = new JButton("Sign Out");
var signOutButton = new JButton(
CodeGPTBundle.get("settingsConfigurable.service.you.signOut.label"));
signOutButton.addActionListener(e -> {
userManager.clearSession();
refreshView(createUserAuthenticationPanel(emailField, passwordField, null));
@ -199,7 +212,7 @@ public class YouServiceSelectionPanel extends JPanel {
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.section.userInformation.title")))
CodeGPTBundle.get("settingsConfigurable.service.you.userInformation.title")))
.addVerticalGap(8)
.addComponent(JBUI.Panels
.simplePanel(contentPanelBuilder.addVerticalGap(4)

View file

@ -6,7 +6,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.settings.ServiceSelectionForm;
import ee.carlrobert.codegpt.settings.service.ServiceSelectionForm;
import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel;
import org.jetbrains.annotations.NotNull;

View file

@ -0,0 +1,96 @@
package ee.carlrobert.codegpt.settings.state;
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 ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.PromptTemplate;
import java.io.IOException;
import java.net.ServerSocket;
import org.jetbrains.annotations.NotNull;
@State(name = "CodeGPT_LlamaSettings", storages = @Storage("CodeGPT_CodeGPT_LlamaSettings.xml"))
public class LlamaSettingsState implements PersistentStateComponent<LlamaSettingsState> {
private boolean useCustomModel;
private String customLlamaModelPath = "";
private HuggingFaceModel huggingFaceModel = HuggingFaceModel.CODE_LLAMA_7B_Q4;
private PromptTemplate promptTemplate = PromptTemplate.LLAMA;
private int serverPort = getRandomAvailablePortOrDefault();
private int contextSize = 2048;
public LlamaSettingsState() {
}
public static LlamaSettingsState getInstance() {
return ApplicationManager.getApplication().getService(LlamaSettingsState.class);
}
@Override
public LlamaSettingsState getState() {
return this;
}
@Override
public void loadState(@NotNull LlamaSettingsState state) {
XmlSerializerUtil.copyBean(state, this);
}
public boolean isUseCustomModel() {
return useCustomModel;
}
public void setUseCustomModel(boolean useCustomModel) {
this.useCustomModel = useCustomModel;
}
public String getCustomLlamaModelPath() {
return customLlamaModelPath;
}
public void setCustomLlamaModelPath(String customLlamaModelPath) {
this.customLlamaModelPath = customLlamaModelPath;
}
public HuggingFaceModel getHuggingFaceModel() {
return huggingFaceModel;
}
public void setHuggingFaceModel(HuggingFaceModel huggingFaceModel) {
this.huggingFaceModel = huggingFaceModel;
}
public PromptTemplate getPromptTemplate() {
return promptTemplate;
}
public void setPromptTemplate(PromptTemplate promptTemplate) {
this.promptTemplate = promptTemplate;
}
public int getServerPort() {
return serverPort;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public int getContextSize() {
return contextSize;
}
public void setContextSize(int contextSize) {
this.contextSize = contextSize;
}
private static Integer getRandomAvailablePortOrDefault() {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
} catch (IOException e) {
return 8080;
}
}
}

View file

@ -6,7 +6,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager;
import ee.carlrobert.codegpt.settings.ServiceSelectionForm;
import ee.carlrobert.codegpt.settings.service.ServiceSelectionForm;
import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel;
import org.jetbrains.annotations.NotNull;

View file

@ -5,6 +5,7 @@ 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 ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.conversations.Conversation;
import org.jetbrains.annotations.NotNull;
@ -17,6 +18,7 @@ public class SettingsState implements PersistentStateComponent<SettingsState> {
private boolean useOpenAIService = true;
private boolean useAzureService;
private boolean useYouService;
private boolean useLlamaService;
public SettingsState() {
}
@ -43,10 +45,15 @@ public class SettingsState implements PersistentStateComponent<SettingsState> {
if ("azure.chat.completion".equals(clientCode)) {
AzureSettingsState.getInstance().setModel(conversation.getModel());
}
if ("llama.chat.completion".equals(clientCode)) {
LlamaSettingsState.getInstance().setHuggingFaceModel(
HuggingFaceModel.valueOf(conversation.getModel()));
}
setUseOpenAIService("chat.completion".equals(clientCode));
setUseAzureService("azure.chat.completion".equals(clientCode));
setUseYouService("you.chat.completion".equals(clientCode));
setUseLlamaService("llama.chat.completion".equals(clientCode));
}
public String getEmail() {
@ -103,4 +110,12 @@ public class SettingsState implements PersistentStateComponent<SettingsState> {
public void setUseYouService(boolean useYouService) {
this.useYouService = useYouService;
}
public boolean isUseLlamaService() {
return useLlamaService;
}
public void setUseLlamaService(boolean useLlamaService) {
this.useLlamaService = useLlamaService;
}
}

View file

@ -12,6 +12,7 @@ public class YouSettingsState implements PersistentStateComponent<YouSettingsSta
private boolean displayWebSearchResults = true;
private boolean useGPT4Model;
private String baseHost;
public static YouSettingsState getInstance() {
return ApplicationManager.getApplication().getService(YouSettingsState.class);
@ -42,4 +43,12 @@ public class YouSettingsState implements PersistentStateComponent<YouSettingsSta
public void setUseGPT4Model(boolean useGPT4Model) {
this.useGPT4Model = useGPT4Model;
}
public void setBaseHost(String baseHost) {
this.baseHost = baseHost;
}
public String getBaseHost() {
return baseHost;
}
}

View file

@ -21,6 +21,9 @@ public class ModelIconLabel extends JBLabel {
if ("azure.chat.completion".equals(clientCode)) {
setIcon(Icons.AzureIcon);
}
if ("llama.chat.completion".equals(clientCode)) {
setIcon(Icons.LlamaIcon);
}
setText(formatModelName(modelCode));
setFont(JBFont.small().asBold());
setHorizontalAlignment(SwingConstants.LEADING);

View file

@ -5,27 +5,32 @@ import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import com.intellij.ide.HelpTooltip;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.impl.EditorImpl;
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.JBCheckBox;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.Borders;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.completions.CompletionRequestHandler;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
import ee.carlrobert.codegpt.completions.you.YouSerpResult;
import ee.carlrobert.codegpt.completions.you.YouSubscriptionNotifier;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.completions.you.auth.AuthenticationNotifier;
import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager;
import ee.carlrobert.codegpt.settings.state.AzureSettingsState;
import ee.carlrobert.codegpt.settings.state.LlamaSettingsState;
import ee.carlrobert.codegpt.settings.state.OpenAISettingsState;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.settings.state.YouSettingsState;
@ -38,6 +43,7 @@ import ee.carlrobert.codegpt.toolwindow.chat.components.UserMessagePanel;
import ee.carlrobert.codegpt.toolwindow.chat.components.UserPromptTextArea;
import ee.carlrobert.codegpt.util.EditorUtils;
import ee.carlrobert.codegpt.util.OverlayUtils;
import ee.carlrobert.codegpt.util.SwingUtils;
import ee.carlrobert.codegpt.util.file.FileUtils;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
@ -50,6 +56,7 @@ import java.util.UUID;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.NotNull;
@ -57,6 +64,8 @@ import org.jetbrains.annotations.Nullable;
public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPanel {
private static final Logger LOG = Logger.getInstance(BaseChatToolWindowTabPanel.class);
private final boolean useContextualSearch;
private final JPanel rootPanel;
private final ScrollablePanel scrollablePanel;
@ -105,10 +114,31 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
public void displayLandingView() {
scrollablePanel.removeAll();
scrollablePanel.add(getLandingView());
var youUserManager = YouUserManager.getInstance();
if (SettingsState.getInstance().isUseYouService() &&
(!youUserManager.isAuthenticated() || !youUserManager.isSubscribed())) {
scrollablePanel.add(new ResponsePanel().addContent(createTextPane()));
}
scrollablePanel.repaint();
scrollablePanel.revalidate();
}
private JTextPane createTextPane() {
var textPane = SwingUtils.createTextPane(SwingUtils::handleHyperlinkClicked);
textPane.setBackground(getPanelBackgroundColor());
textPane.setFocusable(false);
textPane.setText(
"<html>\n"
+ "<body>\n"
+ " <p style=\"margin: 4px 0;\">Use CodeGPT coupon for free month of GPT-4.</p>\n"
+ " <p style=\"margin: 4px 0;\">\n"
+ " <a href=\"https://you.com/plans\">Sign up here</a>\n"
+ " </p>\n"
+ "</body>\n"
+ "</html>");
return textPane;
}
@Override
public void startNewConversation(Message message) {
conversation = conversationService.startConversation();
@ -165,7 +195,9 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
requestHandler.withContextualSearch(useContextualSearch);
requestHandler.addMessageListener(partialMessage -> {
try {
responseContainer.update(partialMessage);
LOG.debug(partialMessage);
ApplicationManager.getApplication()
.invokeLater(() -> responseContainer.update(partialMessage));
} catch (Exception e) {
responseContainer.displayDefaultError();
throw new RuntimeException("Error while updating the content", e);
@ -354,23 +386,33 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
JBUI.Borders.empty(8)));
wrapper.setBackground(getPanelBackgroundColor());
wrapper.add(userPromptTextArea, BorderLayout.SOUTH);
if (model != null) {
var header = new JPanel(new BorderLayout());
header.setBackground(getPanelBackgroundColor());
header.setBorder(JBUI.Borders.emptyBottom(8));
if ("YouCode".equals(model)) {
var messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
subscribeToYouModelChangeTopic();
subscribeToYouAuthTopic();
subscribeToYouSubscriptionTopic(messageBusConnection);
subscribeToSignedOutTopic(messageBusConnection);
header.add(gpt4CheckBox, BorderLayout.LINE_START);
}
header.add(modelIconWrapper, BorderLayout.LINE_END);
wrapper.add(header);
}
rootPanel.add(wrapper, gbc);
userPromptTextArea.requestFocusInWindow();
userPromptTextArea.requestFocus();
}
private void subscribeToSignedOutTopic(MessageBusConnection messageBusConnection) {
messageBusConnection.subscribe(
SignedOutNotifier.SIGNED_OUT_TOPIC,
(SignedOutNotifier) () -> gpt4CheckBox.setEnabled(false));
}
private void subscribeToYouModelChangeTopic() {
project.getMessageBus()
.connect()
@ -379,24 +421,26 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
(YouModelChangeNotifier) gpt4CheckBox::setSelected);
}
private void subscribeToYouAuthTopic() {
ApplicationManager.getApplication()
.getMessageBus()
.connect()
.subscribe(AuthenticationNotifier.AUTHENTICATION_TOPIC,
(AuthenticationNotifier) () -> gpt4CheckBox.setEnabled(true));
private void subscribeToYouSubscriptionTopic(MessageBusConnection messageBusConnection) {
messageBusConnection.subscribe(
YouSubscriptionNotifier.SUBSCRIPTION_TOPIC,
(YouSubscriptionNotifier) () -> {
displayLandingView();
gpt4CheckBox.setEnabled(true);
});
}
private JBCheckBox createGPT4ModelCheckBox() {
var gpt4CheckBox = new JBCheckBox("Use GPT-4 model");
var gpt4CheckBox = new JBCheckBox(CodeGPTBundle.get("toolwindow.chat.youProCheckBox.text"));
gpt4CheckBox.setOpaque(false);
gpt4CheckBox.setEnabled(YouUserManager.getInstance().isAuthenticated());
gpt4CheckBox.setEnabled(YouUserManager.getInstance().isSubscribed());
gpt4CheckBox.setSelected(YouSettingsState.getInstance().isUseGPT4Model());
gpt4CheckBox.setToolTipText(getTooltipText(gpt4CheckBox.isSelected()));
gpt4CheckBox.addChangeListener(e -> {
var selected = ((JBCheckBox) e.getSource()).isSelected();
var tooltipText = getTooltipText(selected);
gpt4CheckBox.setToolTipText(tooltipText);
// TODO: Remove
project.getMessageBus()
.syncPublisher(YouModelChangeNotifier.YOU_MODEL_CHANGE_NOTIFIER_TOPIC)
.modelChanged(selected);
@ -406,9 +450,12 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
}
private String getTooltipText(boolean selected) {
return selected ?
"Turn off for faster responses" :
"<html>Turn on for complex queries, enable by creating an account on you.com<br />and signing in from plugin settings.<br />Use CodeGPT coupon for free month of GPT-4.</html>";
if (YouUserManager.getInstance().isSubscribed()) {
return selected ?
CodeGPTBundle.get("toolwindow.chat.youProCheckBox.disable") :
CodeGPTBundle.get("toolwindow.chat.youProCheckBox.enable");
}
return CodeGPTBundle.get("toolwindow.chat.youProCheckBox.notAllowed");
}
private String getClientCode() {
@ -422,6 +469,9 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
if (settings.isUseYouService()) {
return "you.chat.completion";
}
if (settings.isUseLlamaService()) {
return "llama.chat.completion";
}
return null;
}
@ -436,7 +486,25 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan
if (settings.isUseYouService()) {
return "YouCode";
}
if (settings.isUseLlamaService()) {
var llamaSettings = LlamaSettingsState.getInstance();
if (llamaSettings.isUseCustomModel()) {
var filePath = llamaSettings.getCustomLlamaModelPath();
int lastSeparatorIndex = filePath.lastIndexOf('/');
if (lastSeparatorIndex == -1) {
return filePath;
}
return filePath.substring(lastSeparatorIndex + 1);
}
var huggingFaceModel = llamaSettings.getHuggingFaceModel();
var llamaModel = LlamaModel.findByHuggingFaceModel(huggingFaceModel);
return String.format(
"%s %dB (Q%d)",
llamaModel.getLabel(),
huggingFaceModel.getParameterSize(),
huggingFaceModel.getQuantization());
}
return null;
return "Unknown";
}
}

View file

@ -11,6 +11,7 @@ public class StreamParser {
private boolean isProcessingCode;
public List<StreamParseResponse> parse(String message) {
message = message.replace("\r", "");
messageBuilder.append(message);
Pattern pattern = Pattern.compile(CODE_BLOCK_STARTING_REGEX);

View file

@ -22,7 +22,6 @@ import com.vladsch.flexmark.util.data.MutableDataSet;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.completions.you.YouSerpResult;
import ee.carlrobert.codegpt.settings.SettingsConfigurable;
import ee.carlrobert.codegpt.settings.state.SettingsState;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer;
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
@ -231,13 +230,13 @@ public class ChatMessageResponseBody extends JPanel {
add(currentlyProcessedElement);
}
private void prepareProcessingCodeResponse(String code, String language) {
private void prepareProcessingCodeResponse(String code, String markdownLanguage) {
hideCarets();
currentlyProcessedTextPane = null;
currentlyProcessedEditor = new ResponseEditor(
project,
code,
language,
markdownLanguage,
parentDisposable);
currentlyProcessedElement = new ResponseWrapper();
@ -249,15 +248,13 @@ public class ChatMessageResponseBody extends JPanel {
var editor = currentlyProcessedEditor.getEditor();
var document = editor.getDocument();
var application = ApplicationManager.getApplication();
Runnable updateDocumentRunnable = () -> {
application.runWriteAction(() ->
WriteCommandAction.runWriteCommandAction(project, () -> {
document.replaceString(0, document.getTextLength(), code);
editor.getCaretModel().moveToOffset(code.length());
editor.getComponent().revalidate();
editor.getComponent().repaint();
}));
};
Runnable updateDocumentRunnable = () -> application.runWriteAction(() ->
WriteCommandAction.runWriteCommandAction(project, () -> {
document.replaceString(0, document.getTextLength(), code);
editor.getCaretModel().moveToOffset(code.length());
editor.getComponent().revalidate();
editor.getComponent().repaint();
}));
if (application.isUnitTestMode()) {
application.invokeAndWait(updateDocumentRunnable);
@ -267,8 +264,7 @@ public class ChatMessageResponseBody extends JPanel {
}
private JTextPane createTextPane() {
var textPane = new JTextPane();
textPane.addHyperlinkListener(event -> {
var textPane = SwingUtils.createTextPane(event -> {
if (FileUtil.exists(event.getDescription()) && ACTIVATED.equals(event.getEventType())) {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(event.getDescription());
FileEditorManager.getInstance(project).openFile(Objects.requireNonNull(file), true);
@ -277,14 +273,10 @@ public class ChatMessageResponseBody extends JPanel {
SwingUtils.handleHyperlinkClicked(event);
});
textPane.setContentType("text/html");
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
textPane.setCaretPosition(textPane.getDocument().getLength());
textPane.setBackground(getBackground());
textPane.setFocusable(true);
textPane.getCaret().setVisible(true);
textPane.setEditable(false);
textPane.setCaretPosition(textPane.getDocument().getLength());
textPane.setBorder(JBUI.Borders.empty());
textPane.setBackground(getBackground());
return textPane;
}

View file

@ -9,6 +9,7 @@ import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.JBFont;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.Icons;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
@ -82,7 +83,10 @@ public class ResponsePanel extends JPanel {
public void addReloadAction(Runnable onReload) {
addIconActionButton(new IconActionButton(
new AnAction("Reload Response", "Reload response description", Actions.Refresh) {
new AnAction(
CodeGPTBundle.get("toolwindow.chat.response.action.reloadResponse.text"),
CodeGPTBundle.get("toolwindow.chat.response.action.reloadResponse.description"),
Actions.Refresh) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
enableActions(false);
@ -93,7 +97,10 @@ public class ResponsePanel extends JPanel {
public void addDeleteAction(Runnable onDelete) {
addIconActionButton(new IconActionButton(
new AnAction("Delete Response", "Delete response description", Actions.GC) {
new AnAction(
CodeGPTBundle.get("toolwindow.chat.response.action.deleteResponse.text"),
CodeGPTBundle.get("toolwindow.chat.response.action.deleteResponse.description"),
Actions.GC) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
onDelete.run();
@ -109,7 +116,10 @@ public class ResponsePanel extends JPanel {
}
private JBLabel getIconLabel() {
return new JBLabel("CodeGPT", Icons.DefaultIcon, SwingConstants.LEADING)
return new JBLabel(
CodeGPTBundle.get("project.label"),
Icons.DefaultIcon,
SwingConstants.LEADING)
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold());
}

View file

@ -7,6 +7,7 @@ import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBTextArea;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.completions.CompletionRequestHandler;
import ee.carlrobert.codegpt.util.SwingUtils;
@ -55,7 +56,7 @@ public class UserPromptTextArea extends JPanel {
textArea.setBackground(BACKGROUND_COLOR);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.getEmptyText().setText("Ask me anything");
textArea.getEmptyText().setText(CodeGPTBundle.get("toolwindow.chat.textArea.emptyText"));
textArea.setBorder(JBUI.Borders.empty(8, 4));
var input = textArea.getInputMap();
input.put(KeyStroke.getKeyStroke("ENTER"), TEXT_SUBMIT);
@ -171,6 +172,7 @@ public class UserPromptTextArea extends JPanel {
}
}
// TODO: IconActionButton?
private JButton createIconButton(Icon icon, @Nullable Runnable submitListener) {
var button = SwingUtils.createIconButton(icon);
if (submitListener != null) {

View file

@ -4,7 +4,6 @@ import static com.intellij.openapi.ui.DialogWrapper.OK_EXIT_CODE;
import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
@ -14,9 +13,6 @@ import ee.carlrobert.codegpt.indexes.CodebaseIndexingTask;
import ee.carlrobert.codegpt.indexes.FolderStructureTreePanel;
import ee.carlrobert.codegpt.settings.SettingsConfigurable;
import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.completions.you.auth.AuthenticationNotifier;
import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier;
import ee.carlrobert.codegpt.util.OverlayUtils;
import ee.carlrobert.codegpt.util.SwingUtils;
import ee.carlrobert.vector.VectorStore;
@ -25,6 +21,7 @@ import javax.swing.event.HyperlinkEvent;
@FunctionalInterface
interface ActionEvent {
void handleAction(String prompt);
}
@ -43,43 +40,30 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel {
.connect()
.subscribe(CodebaseIndexingCompletedNotifier.INDEXING_COMPLETED_TOPIC,
(CodebaseIndexingCompletedNotifier) () -> updateContent(createContent()));
var messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
messageBusConnection.subscribe(AuthenticationNotifier.AUTHENTICATION_TOPIC, (AuthenticationNotifier) () -> updateContent(createContent()));
messageBusConnection.subscribe(SignedOutNotifier.SIGNED_OUT_TOPIC, (SignedOutNotifier) () -> updateContent(createContent()));
}
private JTextPane createContent() {
var description = createTextPane();
var userManager = YouUserManager.getInstance();
if (userManager.getAuthenticationResponse() == null) {
description.setText("<html>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">It looks like you haven't logged in. Please <a href=\"LOGIN\">log in</a> to use the feature.</p>" +
"</html>");
return description;
}
if (!userManager.isSubscribed()) {
description.setText("<html>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">You are not currently subscribed to any plan.</p>" +
"</html>");
return description;
}
if (VectorStore.getInstance(CodeGPTPlugin.getPluginBasePath()).isIndexExists()) {
description.setText("<html>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">Feel free to ask me anything about your codebase, and I'll be your helpful guide, dedicated to providing you with the best answers possible!</p>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">Here are a few examples of how I might be helpful:</p>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">Feel free to ask me anything about your codebase, and I'll be your helpful guide, dedicated to providing you with the best answers possible!</p>"
+
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">Here are a few examples of how I might be helpful:</p>"
+
"<ul>" +
"<li><a href=\"LIST_DEPENDENCIES\">List all the dependencies that the project uses</a></li" +
"<li><a href=\"SCHEDULED_TASKS\">Are there any scheduled tasks or background jobs running in our codebase, and if so, what are they responsible for?</a></li>" +
"<li><a href=\"AUTHENTICATION_MECHANISM\">Can you provide an overview of the authentication and authorization mechanism implemented in our application?</a></li>" +
"<li><a href=\"LIST_DEPENDENCIES\">List all the dependencies that the project uses</a></li"
+
"<li><a href=\"SCHEDULED_TASKS\">Are there any scheduled tasks or background jobs running in our codebase, and if so, what are they responsible for?</a></li>"
+
"<li><a href=\"AUTHENTICATION_MECHANISM\">Can you provide an overview of the authentication and authorization mechanism implemented in our application?</a></li>"
+
"</html>");
} else {
description.setText("<html>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">It looks like you haven't indexed your codebase yet.</p>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\"><a href=\"START_INDEXING\">Start indexing</a> your codebase to get access to contextual chat experience.</p>" +
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">It looks like you haven't indexed your codebase yet.</p>"
+
"<p style=\"margin-top: 4px; margin-bottom: 4px;\"><a href=\"START_INDEXING\">Start indexing</a> your codebase to get access to contextual chat experience.</p>"
+
"</html>");
}
@ -87,13 +71,8 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel {
}
private JTextPane createTextPane() {
var textPane = new JTextPane();
textPane.addHyperlinkListener(this::handleHyperlinkClicked);
var textPane = SwingUtils.createTextPane(this::handleHyperlinkClicked);
textPane.setBackground(getPanelBackgroundColor());
textPane.setContentType("text/html");
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
textPane.setFocusable(false);
textPane.setEditable(false);
return textPane;
}
@ -112,7 +91,8 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel {
"Are there any scheduled tasks or background jobs running in our codebase, and if so, what are they responsible for?");
break;
case "AUTHENTICATION_MECHANISM":
actionEvent.handleAction("Can you provide an overview of the authentication and authorization mechanism implemented in our application?");
actionEvent.handleAction(
"Can you provide an overview of the authentication and authorization mechanism implemented in our application?");
break;
case "START_INDEXING":
var folderStructureTreePanel = new FolderStructureTreePanel(project);

View file

@ -1,14 +1,9 @@
package ee.carlrobert.codegpt.toolwindow.chat.contextual;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.indexes.CodebaseIndexingCompletedNotifier;
import ee.carlrobert.codegpt.toolwindow.chat.BaseChatToolWindowTabPanel;
import ee.carlrobert.codegpt.completions.you.auth.AuthenticationNotifier;
import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier;
import javax.swing.JComponent;
import org.jetbrains.annotations.NotNull;
@ -17,19 +12,6 @@ public class ContextualChatToolWindowTabPanel extends BaseChatToolWindowTabPanel
public ContextualChatToolWindowTabPanel(@NotNull Project project) {
super(project, true);
displayLandingView();
userPromptTextArea.setTextAreaEnabled(YouUserManager.getInstance().isSubscribed());
project.getMessageBus()
.connect()
.subscribe(CodebaseIndexingCompletedNotifier.INDEXING_COMPLETED_TOPIC,
(CodebaseIndexingCompletedNotifier) () -> userPromptTextArea.setTextAreaEnabled(
YouUserManager.getInstance().isSubscribed()));
var messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
messageBusConnection.subscribe(AuthenticationNotifier.AUTHENTICATION_TOPIC,
(AuthenticationNotifier) () -> userPromptTextArea.setTextAreaEnabled(
YouUserManager.getInstance().isSubscribed()));
messageBusConnection.subscribe(SignedOutNotifier.SIGNED_OUT_TOPIC, (SignedOutNotifier) () -> userPromptTextArea.setTextAreaEnabled(false));
}
@Override

View file

@ -33,6 +33,8 @@ public class CopyAction extends TrackableAction {
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;
OverlayUtils.showInfoBalloon("Code copied!", locationOnScreen);
OverlayUtils.showInfoBalloon(
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.success"),
locationOnScreen);
}
}

View file

@ -34,7 +34,9 @@ public class EditAction extends TrackableAction {
settings.setCaretRowShown(!viewer);
event.getPresentation().setIcon(viewer ? Actions.EditSource : Actions.Show);
event.getPresentation().setText(viewer ? "Edit Source" : "Disable Editing");
event.getPresentation().setText(viewer ?
CodeGPTBundle.get("toolwindow.chat.editor.action.edit.title") :
CodeGPTBundle.get("toolwindow.chat.editor.action.disableEditing.title"));
var locationOnScreen = ((MouseEvent) event.getInputEvent()).getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;

View file

@ -41,13 +41,8 @@ class StandardChatToolWindowLandingPanel extends ResponsePanel {
}
private JTextPane createTextPane() {
var textPane = new JTextPane();
textPane.addHyperlinkListener(this::handleHyperlinkClicked);
var textPane = SwingUtils.createTextPane(this::handleHyperlinkClicked);
textPane.setBackground(getPanelBackgroundColor());
textPane.setContentType("text/html");
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
textPane.setFocusable(false);
textPane.setEditable(false);
return textPane;
}

View file

@ -3,8 +3,6 @@ package ee.carlrobert.codegpt.toolwindow.chat.standard;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.Constraints;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.DefaultCompactActionGroup;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.SimpleToolWindowPanel;

View file

@ -0,0 +1,37 @@
package ee.carlrobert.codegpt.util;
import static java.lang.String.format;
import ee.carlrobert.codegpt.util.file.FileUtils;
public class DownloadingUtils {
private static final int BYTES_IN_MB = 1024 * 1024;
public static String getFormattedDownloadProgress(long startTime, long fileSize, long bytesRead) {
long timeElapsed = System.currentTimeMillis() - startTime;
double speed = ((double) bytesRead / timeElapsed) * 1000 / BYTES_IN_MB;
double percent = (double) bytesRead / fileSize * 100;
double downloadedMB = (double) bytesRead / BYTES_IN_MB;
double totalMB = (double) fileSize / BYTES_IN_MB;
double remainingMB = totalMB - downloadedMB;
return format(
"%s of %s (%.2f%%), Speed: %.2f MB/sec, Time left: %s",
FileUtils.convertFileSize((long) downloadedMB * BYTES_IN_MB),
FileUtils.convertFileSize((long) totalMB * BYTES_IN_MB),
percent,
speed,
getTimeLeftFormattedString(speed, remainingMB));
}
private static String getTimeLeftFormattedString(double speed, double remainingMB) {
double timeLeftSec = speed > 0 ? remainingMB / speed : 0;
long hours = (long) (timeLeftSec / 3600);
long minutes = (long) ((timeLeftSec % 3600) / 60);
long seconds = (long) (timeLeftSec % 60);
return format("%02d:%02d:%02d", hours, minutes, seconds);
}
}

View file

@ -63,7 +63,11 @@ public final class EditorUtils {
var editor = getSelectedEditor(project);
if (editor != null) {
var selectionModel = editor.getSelectionModel();
editor.getDocument().replaceString(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), text);
editor.getDocument()
.replaceString(
selectionModel.getSelectionStart(),
selectionModel.getSelectionEnd(),
text);
editor.getContentComponent().requestFocus();
selectionModel.removeSelection();
}

View file

@ -17,6 +17,7 @@ import com.intellij.openapi.ui.MessageDialogBuilder;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.Balloon.Position;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBLabel;
@ -27,6 +28,7 @@ import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.indexes.FolderStructureTreePanel;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import org.jetbrains.annotations.NotNull;
public class OverlayUtils {
@ -117,4 +119,12 @@ public class OverlayUtils {
.createBalloon()
.show(RelativePoint.fromScreen(locationOnScreen), Balloon.Position.above);
}
public static void showBalloon(String content, MessageType messageType, JComponent component) {
JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(content, messageType, null)
.setFadeoutTime(2500)
.createBalloon()
.show(RelativePoint.getSouthOf(component), Position.below);
}
}

View file

@ -4,26 +4,33 @@ import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import com.intellij.ide.BrowserUtil;
import com.intellij.util.ui.UI;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
public class SwingUtils {
public static JTextPane createTextPane(HyperlinkListener listener) {
var textPane = new JTextPane();
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
textPane.addHyperlinkListener(listener);
textPane.setContentType("text/html");
textPane.setEditable(false);
return textPane;
}
public static JButton createIconButton(Icon icon) {
var button = new JButton(icon);
button.setBorder(BorderFactory.createEmptyBorder());
@ -32,13 +39,6 @@ public class SwingUtils {
return button;
}
public static Box justifyLeft(Component component) {
Box box = Box.createHorizontalBox();
box.add(component);
box.add(Box.createHorizontalGlue());
return box;
}
public static void setEqualLabelWidths(JPanel firstPanel, JPanel secondPanel) {
var firstLabel = firstPanel.getComponents()[0];
var secondLabel = secondPanel.getComponents()[0];
@ -47,10 +47,6 @@ public class SwingUtils {
}
}
public static JPanel createPanel(JComponent component, String label) {
return createPanel(component, label, false);
}
public static JPanel createPanel(JComponent component, String label, boolean resizeX) {
return UI.PanelFactory.panel(component)
.withLabel(label)

View file

@ -6,16 +6,23 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -40,6 +47,33 @@ public class FileUtils {
}
}
public static void copyFileWithProgress(
String fileName,
URL url,
long[] bytesRead,
long fileSize,
ProgressIndicator indicator) throws IOException {
FileUtils.tryCreateDirectory(CodeGPTPlugin.getLlamaModelsPath());
try (
var readableByteChannel = Channels.newChannel(url.openStream());
var fileOutputStream = new FileOutputStream(
CodeGPTPlugin.getLlamaModelsPath() + File.separator + fileName)) {
var buffer = ByteBuffer.allocateDirect(1024 * 10);
while (readableByteChannel.read(buffer) != -1) {
if (indicator.isCanceled()) {
readableByteChannel.close();
break;
}
buffer.flip();
bytesRead[0] += fileOutputStream.getChannel().write(buffer);
buffer.clear();
indicator.setFraction((double) bytesRead[0] / fileSize);
}
}
}
public static VirtualFile getEditorFile(@NotNull Editor editor) {
return FileDocumentManager.getInstance().getFile(editor.getDocument());
}
@ -115,6 +149,19 @@ public class FileUtils {
}
}
public static String convertFileSize(long fileSizeInBytes) {
String[] units = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
double fileSize = fileSizeInBytes;
while (fileSize >= 1024 && unitIndex < units.length - 1) {
fileSize /= 1024;
unitIndex++;
}
return new DecimalFormat("#.##").format(fileSize) + " " + units[unitIndex];
}
private static Optional<Map.Entry<String, String>> findFirstExtension(
List<LanguageFileExtensionDetails> languageFileExtensionMappings,
String language) {