feat: add server and build logs for llama.cpp

This commit is contained in:
Carl-Robert Linnupuu 2025-07-12 18:38:37 +01:00
parent 768d410737
commit e11477ded3
30 changed files with 2231 additions and 1086 deletions

View file

@ -48,16 +48,9 @@ public class CompletionClientProvider {
public static LlamaClient getLlamaClient() {
var llamaSettings = LlamaSettings.getCurrentState();
var builder = new LlamaClient.Builder()
.setPort(llamaSettings.getServerPort());
if (!llamaSettings.isRunLocalServer()) {
builder.setHost(llamaSettings.getBaseHost());
String apiKey = getCredential(CredentialKey.LlamaApiKey.INSTANCE);
if (apiKey != null && !apiKey.isBlank()) {
builder.setApiKey(apiKey);
}
}
return builder.build(getDefaultClientBuilder());
return new LlamaClient.Builder()
.setPort(llamaSettings.getServerPort())
.build(getDefaultClientBuilder());
}
public static OllamaClient getOllamaClient() {

View file

@ -1,254 +0,0 @@
package ee.carlrobert.codegpt.completions.llama;
import static java.lang.String.format;
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.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Key;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.llama.form.ServerProgressPanel;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
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 @Nullable OSProcessHandler makeSetupProcessHandler;
private @Nullable OSProcessHandler makeBuildProcessHandler;
private @Nullable OSProcessHandler startServerProcessHandler;
private ServerProgressPanel activeServerProgressPanel;
private boolean stoppedByUser;
public void startAgent(
LlamaServerStartupParams params,
ServerProgressPanel serverProgressPanel,
Runnable onSuccess,
Consumer<ServerProgressPanel> onServerStopped) {
this.activeServerProgressPanel = serverProgressPanel;
ApplicationManager.getApplication().invokeLater(() -> {
try {
stoppedByUser = false;
serverProgressPanel.displayText(
CodeGPTBundle.get("llamaServerAgent.buildingProject.description"));
makeSetupProcessHandler = new OSProcessHandler(getCMakeSetupCommandLine(params));
makeSetupProcessHandler.addProcessListener(new ProcessAdapter() {
private final List<String> errorLines = new CopyOnWriteArrayList<>();
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
if (ProcessOutputType.isStderr(outputType)) {
errorLines.add(event.getText());
return;
}
LOG.info(event.getText());
}
@Override
public void processTerminated(@NotNull ProcessEvent event) {
int exitCode = event.getExitCode();
LOG.info(format("CMake setup exited with code %d", exitCode));
if (stoppedByUser) {
onServerStopped.accept(activeServerProgressPanel);
return;
}
if (exitCode != 0) {
showServerError(String.join(",", errorLines), onServerStopped);
return;
}
try {
makeBuildProcessHandler = new OSProcessHandler(getCMakeBuildCommandLine(params));
makeBuildProcessHandler.addProcessListener(
getMakeProcessListener(params, onSuccess, onServerStopped));
makeBuildProcessHandler.startNotify();
} catch (ExecutionException e) {
showServerError(e.getMessage(), onServerStopped);
}
}
});
makeSetupProcessHandler.startNotify();
} catch (ExecutionException e) {
showServerError(e.getMessage(), onServerStopped);
}
});
}
public void stopAgent() {
stoppedByUser = true;
if (makeSetupProcessHandler != null) {
makeSetupProcessHandler.destroyProcess();
}
if (startServerProcessHandler != null) {
startServerProcessHandler.destroyProcess();
}
}
public boolean isServerRunning() {
return (makeSetupProcessHandler != null
&& makeSetupProcessHandler.isStartNotified()
&& !makeSetupProcessHandler.isProcessTerminated())
|| (startServerProcessHandler != null
&& startServerProcessHandler.isStartNotified()
&& !startServerProcessHandler.isProcessTerminated());
}
private ProcessListener getMakeProcessListener(
LlamaServerStartupParams params,
Runnable onSuccess,
Consumer<ServerProgressPanel> onServerStopped) {
LOG.info("Building llama project");
return new ProcessAdapter() {
private final List<String> errorLines = new CopyOnWriteArrayList<>();
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
if (ProcessOutputType.isStderr(outputType)) {
errorLines.add(event.getText());
return;
}
LOG.info(event.getText());
}
@Override
public void processTerminated(@NotNull ProcessEvent event) {
int exitCode = event.getExitCode();
LOG.info(format("Server build exited with code %d", exitCode));
if (stoppedByUser) {
onServerStopped.accept(activeServerProgressPanel);
return;
}
if (exitCode != 0) {
showServerError(String.join(",", errorLines), onServerStopped);
return;
}
try {
LOG.info("Booting up llama server");
activeServerProgressPanel.displayText(
CodeGPTBundle.get("llamaServerAgent.serverBootup.description"));
startServerProcessHandler = new OSProcessHandler.Silent(getServerCommandLine(params));
startServerProcessHandler.addProcessListener(
getProcessListener(params.port(), onSuccess, onServerStopped));
startServerProcessHandler.startNotify();
} catch (ExecutionException ex) {
showServerError(ex.getMessage(), onServerStopped);
}
}
};
}
private ProcessListener getProcessListener(
int port,
Runnable onSuccess,
Consumer<ServerProgressPanel> onServerStopped) {
return new ProcessAdapter() {
private final ObjectMapper objectMapper = new ObjectMapper();
private final List<String> errorLines = new CopyOnWriteArrayList<>();
@Override
public void processTerminated(@NotNull ProcessEvent event) {
LOG.info(format("Server stopped with code %d", event.getExitCode()));
if (stoppedByUser) {
onServerStopped.accept(activeServerProgressPanel);
} else {
showServerError(String.join(",", errorLines), onServerStopped);
}
}
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
LOG.info(event.getText());
// TODO: Use proper successful boot up validation
if (event.getText().contains("server is listening")) {
LOG.info("Server up and running!");
LlamaSettings.getCurrentState().setServerPort(port);
onSuccess.run();
}
}
};
}
private void showServerError(String errorText, Consumer<ServerProgressPanel> onServerStopped) {
onServerStopped.accept(activeServerProgressPanel);
LOG.info("Unable to start llama server:\n" + errorText);
OverlayUtil.showClosableBalloon(errorText, MessageType.ERROR, activeServerProgressPanel);
}
private static GeneralCommandLine getCMakeSetupCommandLine(LlamaServerStartupParams params) {
GeneralCommandLine cmakeSetupCommand = new GeneralCommandLine().withCharset(
StandardCharsets.UTF_8);
cmakeSetupCommand.setExePath("cmake");
cmakeSetupCommand.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath());
cmakeSetupCommand.addParameters("-B", "build");
cmakeSetupCommand.withEnvironment(params.additionalEnvironmentVariables());
cmakeSetupCommand.setRedirectErrorStream(false);
return cmakeSetupCommand;
}
private static GeneralCommandLine getCMakeBuildCommandLine(LlamaServerStartupParams params) {
GeneralCommandLine cmakeBuildCommand = new GeneralCommandLine().withCharset(
StandardCharsets.UTF_8);
cmakeBuildCommand.setExePath("cmake");
cmakeBuildCommand.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath());
cmakeBuildCommand.addParameters("--build", "build", "--config", "Release", "-j", "4");
cmakeBuildCommand.withEnvironment(params.additionalEnvironmentVariables());
cmakeBuildCommand.setRedirectErrorStream(false);
return cmakeBuildCommand;
}
private GeneralCommandLine getServerCommandLine(LlamaServerStartupParams params) {
GeneralCommandLine commandLine = new GeneralCommandLine().withCharset(StandardCharsets.UTF_8);
commandLine.setExePath("./build/bin/llama-server");
commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath());
commandLine.addParameters(
"-m", params.modelPath(),
"-c", String.valueOf(params.contextLength()),
"--port", String.valueOf(params.port()),
"-t", String.valueOf(params.threads()));
commandLine.addParameters(params.additionalRunParameters());
commandLine.withEnvironment(params.additionalEnvironmentVariables());
commandLine.setRedirectErrorStream(false);
return commandLine;
}
public void setActiveServerProgressPanel(
ServerProgressPanel activeServerProgressPanel) {
this.activeServerProgressPanel = activeServerProgressPanel;
}
@Override
public void dispose() {
if (makeSetupProcessHandler != null && !makeSetupProcessHandler.isProcessTerminated()) {
makeSetupProcessHandler.destroyProcess();
}
if (startServerProcessHandler != null && !startServerProcessHandler.isProcessTerminated()) {
startServerProcessHandler.destroyProcess();
}
}
}

View file

@ -1,6 +1,5 @@
package ee.carlrobert.codegpt.settings.service.llama;
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LlamaApiKey;
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX;
import static org.apache.commons.lang3.SystemUtils.IS_OS_MAC_OSX;
@ -13,7 +12,6 @@ import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
import ee.carlrobert.codegpt.completions.llama.PromptTemplate;
import ee.carlrobert.codegpt.credentials.CredentialsStore;
import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm;
import java.io.File;
@ -23,7 +21,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@State(name = "CodeGPT", storages = @Storage("CodeGPT_LlamaSettings.xml"))
@ -40,17 +37,12 @@ public class LlamaSettings implements PersistentStateComponent<LlamaSettingsStat
@Override
public void loadState(@NotNull LlamaSettingsState state) {
this.state = state;
// Catch if model's name has changed which could lead to
// HuggingFaceModel or PromptTemplates not being found
if (this.state.getHuggingFaceModel() == null) {
this.state.setHuggingFaceModel(HuggingFaceModel.CODE_QWEN_2_5_1_5B_Q8_0);
}
if (this.state.getLocalModelPromptTemplate() == null) {
this.state.setLocalModelPromptTemplate(PromptTemplate.CODE_QWEN);
}
if (this.state.getRemoteModelInfillPromptTemplate() == null) {
this.state.setRemoteModelInfillPromptTemplate(InfillPromptTemplate.CODE_QWEN_2_5);
}
if (this.state.getLocalModelInfillPromptTemplate() == null) {
this.state.setLocalModelInfillPromptTemplate(InfillPromptTemplate.CODE_QWEN);
}
@ -60,13 +52,10 @@ public class LlamaSettings implements PersistentStateComponent<LlamaSettingsStat
return getInstance().getState();
}
/**
* Code Completions enabled in settings and a model with InfillPromptTemplate selected.
*/
public static boolean isCodeCompletionsPossible() {
return getInstance().getState().isCodeCompletionsEnabled()
&& LlamaModel.findByHuggingFaceModel(getInstance().getState().getHuggingFaceModel())
.getInfillPromptTemplate() != null;
&& LlamaModel.findByHuggingFaceModel(getInstance().getState().getHuggingFaceModel())
.getInfillPromptTemplate() != null;
}
public static LlamaSettings getInstance() {
@ -74,15 +63,12 @@ public class LlamaSettings implements PersistentStateComponent<LlamaSettingsStat
}
public boolean isModified(LlamaSettingsForm form) {
return !form.getCurrentState().equals(state)
|| !StringUtils.equals(
form.getLlamaServerPreferencesForm().getApiKey(),
CredentialsStore.getCredential(LlamaApiKey.INSTANCE));
return !form.getCurrentState().equals(state);
}
public static boolean isRunnable() {
return (IS_OS_MAC_OSX || IS_OS_LINUX)
&& GeneralSettings.getCurrentState().getSelectedService() == LLAMA_CPP;
&& GeneralSettings.getCurrentState().getSelectedService() == LLAMA_CPP;
}
public static boolean isRunnable(HuggingFaceModel model) {
@ -97,19 +83,18 @@ public class LlamaSettings implements PersistentStateComponent<LlamaSettingsStat
return Paths.get(System.getProperty("user.home"), ".codegpt/models/gguf");
}
// Copied from LlamaModelPreferencesForm
public String getActualModelPath() {
return state.isUseCustomModel()
? state.getCustomLlamaModelPath()
: getLlamaModelsPath() + File.separator
? state.getCustomLlamaModelPath()
: getLlamaModelsPath() + File.separator
+ state.getHuggingFaceModel().getFileName();
}
public static List<String> getAdditionalParametersList(String additionalParameters) {
return Arrays.stream(additionalParameters.split(","))
.map(String::trim)
.filter(s -> !s.isBlank())
.toList();
.map(String::trim)
.filter(s -> !s.isBlank())
.toList();
}
public static Map<String, String> getAdditionalEnvironmentVariablesMap(

View file

@ -1,6 +1,5 @@
package ee.carlrobert.codegpt.settings.service.llama;
import com.intellij.openapi.util.SystemInfoRt;
import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.PromptTemplate;
@ -10,15 +9,11 @@ import java.util.Objects;
public class LlamaSettingsState {
private boolean runLocalServer = SystemInfoRt.isUnix;
private boolean useCustomModel;
private String customLlamaModelPath = "";
private HuggingFaceModel huggingFaceModel = HuggingFaceModel.CODE_QWEN_2_5_1_5B_Q8_0;
private PromptTemplate localModelPromptTemplate = PromptTemplate.LLAMA;
private PromptTemplate remoteModelPromptTemplate = PromptTemplate.LLAMA;
private InfillPromptTemplate localModelInfillPromptTemplate = InfillPromptTemplate.CODE_LLAMA;
private InfillPromptTemplate remoteModelInfillPromptTemplate = InfillPromptTemplate.CODE_LLAMA;
private String baseHost = "http://localhost:8080";
private Integer serverPort = getRandomAvailablePortOrDefault();
private int contextSize = 2048;
private int threads = 8;
@ -73,39 +68,6 @@ public class LlamaSettingsState {
this.localModelInfillPromptTemplate = localModelInfillPromptTemplate;
}
public InfillPromptTemplate getRemoteModelInfillPromptTemplate() {
return remoteModelInfillPromptTemplate;
}
public void setRemoteModelInfillPromptTemplate(
InfillPromptTemplate remoteModelInfillPromptTemplate) {
this.remoteModelInfillPromptTemplate = remoteModelInfillPromptTemplate;
}
public boolean isRunLocalServer() {
return runLocalServer;
}
public void setRunLocalServer(boolean runLocalServer) {
this.runLocalServer = runLocalServer;
}
public String getBaseHost() {
return baseHost;
}
public void setBaseHost(String baseHost) {
this.baseHost = baseHost;
}
public PromptTemplate getRemoteModelPromptTemplate() {
return remoteModelPromptTemplate;
}
public void setRemoteModelPromptTemplate(
PromptTemplate remoteModelPromptTemplate) {
this.remoteModelPromptTemplate = remoteModelPromptTemplate;
}
public Integer getServerPort() {
return serverPort;
@ -212,8 +174,7 @@ public class LlamaSettingsState {
return false;
}
LlamaSettingsState that = (LlamaSettingsState) o;
return runLocalServer == that.runLocalServer
&& useCustomModel == that.useCustomModel
return useCustomModel == that.useCustomModel
&& contextSize == that.contextSize
&& threads == that.threads
&& topK == that.topK
@ -223,10 +184,7 @@ public class LlamaSettingsState {
&& Objects.equals(customLlamaModelPath, that.customLlamaModelPath)
&& huggingFaceModel == that.huggingFaceModel
&& localModelPromptTemplate == that.localModelPromptTemplate
&& remoteModelPromptTemplate == that.remoteModelPromptTemplate
&& localModelInfillPromptTemplate == that.localModelInfillPromptTemplate
&& remoteModelInfillPromptTemplate == that.remoteModelInfillPromptTemplate
&& Objects.equals(baseHost, that.baseHost)
&& Objects.equals(serverPort, that.serverPort)
&& Objects.equals(additionalParameters, that.additionalParameters)
&& Objects.equals(additionalBuildParameters, that.additionalBuildParameters)
@ -236,9 +194,9 @@ public class LlamaSettingsState {
@Override
public int hashCode() {
return Objects.hash(runLocalServer, useCustomModel, customLlamaModelPath, huggingFaceModel,
localModelPromptTemplate, remoteModelPromptTemplate, localModelInfillPromptTemplate,
remoteModelInfillPromptTemplate, baseHost, serverPort, contextSize, threads,
return Objects.hash(useCustomModel, customLlamaModelPath, huggingFaceModel,
localModelPromptTemplate, localModelInfillPromptTemplate,
serverPort, contextSize, threads,
additionalParameters, additionalBuildParameters, additionalEnvironmentVariables, topK, topP,
minP, repeatPenalty,
codeCompletionsEnabled);

View file

@ -5,7 +5,6 @@ import static ee.carlrobert.codegpt.settings.service.llama.LlamaSettings.isModel
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.icons.AllIcons.General;
import com.intellij.ide.HelpTooltip;
import com.intellij.openapi.actionSystem.AnAction;
@ -14,6 +13,7 @@ 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.Messages;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.panel.ComponentPanelBuilder;
@ -34,24 +34,34 @@ import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ItemEvent;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class LlamaModelPreferencesForm {
private static final String PREDEFINED_MODEL_FORM_CARD_CODE = "PredefinedModelSettings";
private static final String CUSTOM_MODEL_FORM_CARD_CODE = "CustomModelSettings";
private static final String GROUP_DOWNLOADED = "Downloaded";
private static final String GROUP_NOT_DOWNLOADED = "Not Downloaded";
private static final Map<Integer, Map<Integer, ModelDetails>> modelDetailsMap = Map.of(
7, Map.of(
3, new ModelDetails(3.30, 5.80),
@ -69,11 +79,11 @@ public class LlamaModelPreferencesForm {
private final TextFieldWithBrowseButton browsableCustomModelTextField;
private final ComboBox<LlamaModel> modelComboBox;
private final ComboBox<ModelSize> modelSizeComboBox;
private final ComboBox<HuggingFaceModel> huggingFaceModelComboBox;
private final JBLabel modelExistsIcon;
private final DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel;
private final ComboBox<Object> huggingFaceModelComboBox;
private final DefaultComboBoxModel<Object> huggingFaceComboBoxModel;
private final JBLabel helpIcon;
private final JPanel downloadModelActionLinkWrapper;
private final JPanel modelActionsWrapper;
private final JBLabel progressLabel;
private final JBLabel modelDetailsLabel;
private final CardLayout cardLayout;
@ -84,59 +94,55 @@ public class LlamaModelPreferencesForm {
public LlamaModelPreferencesForm() {
cardLayout = new CardLayout();
progressLabel = new JBLabel("");
progressLabel.setBorder(JBUI.Borders.emptyLeft(2));
progressLabel.setFont(JBUI.Fonts.smallFont());
modelExistsIcon = new JBLabel(Actions.Checked);
var llamaSettings = LlamaSettings.getCurrentState();
modelExistsIcon.setVisible(isModelExists(llamaSettings.getHuggingFaceModel()));
progressLabel = createProgressLabel();
helpIcon = new JBLabel(General.ContextHelp);
huggingFaceComboBoxModel = new DefaultComboBoxModel<>();
var llamaSettings = LlamaSettings.getCurrentState();
var llm = llamaSettings.getHuggingFaceModel();
var llamaModel = LlamaModel.findByHuggingFaceModel(llm);
var selectableModels = llamaModel.getHuggingFaceModels().stream()
.filter(model -> model.getParameterSize() == llm.getParameterSize())
.toList();
huggingFaceComboBoxModel.addAll(selectableModels);
huggingFaceComboBoxModel.setSelectedItem(llm);
downloadModelActionLinkWrapper = new JPanel(new BorderLayout());
downloadModelActionLinkWrapper.setBorder(JBUI.Borders.emptyLeft(2));
downloadModelActionLinkWrapper.add(
createDownloadModelLink(
progressLabel,
downloadModelActionLinkWrapper,
huggingFaceComboBoxModel),
BorderLayout.WEST);
initializeHuggingFaceModel(llamaModel, llm);
downloadModelActionLinkWrapper = createActionPanel();
modelActionsWrapper = createActionPanel();
updateModelActionsPanel(llm);
modelDetailsLabel = new JBLabel();
huggingFaceModelComboBox = createModelQuantizationComboBox(huggingFaceComboBoxModel);
huggingFaceModelComboBox = createModelQuantizationComboBox();
var llamaServerAgent = ApplicationManager.getApplication().getService(LlamaServerAgent.class);
huggingFaceModelComboBox.setEnabled(!llamaServerAgent.isServerRunning());
var serverRunning = llamaServerAgent.isServerRunning();
huggingFaceModelComboBox.setEnabled(!serverRunning);
var modelSizeComboBoxModel = new DefaultComboBoxModel<ModelSize>();
var modelComboBoxModel = new DefaultComboBoxModel<LlamaModel>();
modelComboBoxModel.addAll(LlamaModel.getSorted());
var modelComboBoxModel = createLlamaModelComboBoxModel();
modelComboBox = createModelComboBox(
modelComboBoxModel, llamaModel, llm, llamaServerAgent, modelSizeComboBoxModel);
modelComboBox.setEnabled(!llamaServerAgent.isServerRunning());
modelComboBox.setEnabled(!serverRunning);
modelSizeComboBox = createModelSizeComboBox(
modelComboBoxModel,
modelSizeComboBoxModel,
llamaServerAgent,
huggingFaceComboBoxModel);
browsableCustomModelTextField = createBrowsableCustomModelTextField(
!llamaServerAgent.isServerRunning());
llamaServerAgent);
browsableCustomModelTextField = createBrowsableCustomModelTextField(!serverRunning);
browsableCustomModelTextField.setText(llamaSettings.getCustomLlamaModelPath());
localPromptTemplatePanel = new ChatPromptTemplatePanel(
llamaSettings.getLocalModelPromptTemplate(),
!llamaServerAgent.isServerRunning());
!serverRunning);
predefinedModelRadioButton = new JBRadioButton("Use pre-defined model",
!llamaSettings.isUseCustomModel());
customModelRadioButton = new JBRadioButton("Use custom model",
llamaSettings.isUseCustomModel());
infillPromptTemplatePanel = new InfillPromptTemplatePanel(
llamaSettings.getLocalModelInfillPromptTemplate(),
!llamaServerAgent.isServerRunning()
);
!serverRunning);
}
public JPanel getForm() {
@ -164,12 +170,15 @@ public class LlamaModelPreferencesForm {
return browsableCustomModelTextField;
}
@SuppressWarnings("unchecked")
public ComboBox<HuggingFaceModel> getHuggingFaceModelComboBox() {
return huggingFaceModelComboBox;
return (ComboBox<HuggingFaceModel>) (ComboBox<?>) huggingFaceModelComboBox;
}
@Nullable
public HuggingFaceModel getSelectedModel() {
return (HuggingFaceModel) huggingFaceComboBoxModel.getSelectedItem();
var selected = huggingFaceComboBoxModel.getSelectedItem();
return selected instanceof HuggingFaceModel ? (HuggingFaceModel) selected : null;
}
public String getCustomLlamaModelPath() {
@ -189,9 +198,40 @@ public class LlamaModelPreferencesForm {
}
public String getActualModelPath() {
return isUseCustomLlamaModel()
? getCustomLlamaModelPath()
: getLlamaModelsPath().resolve(getSelectedModel().getFileName()).toString();
if (isUseCustomLlamaModel()) {
return getCustomLlamaModelPath();
}
var selectedModel = getSelectedModel();
return selectedModel != null
? getLlamaModelsPath().resolve(selectedModel.getFileName()).toString()
: "";
}
private JBLabel createProgressLabel() {
var label = new JBLabel("");
label.setBorder(JBUI.Borders.emptyLeft(4));
label.setFont(JBUI.Fonts.smallFont());
return label;
}
private JPanel createActionPanel() {
var panel = new JPanel(new BorderLayout());
panel.setBorder(JBUI.Borders.emptyLeft(4));
return panel;
}
private void initializeHuggingFaceModel(LlamaModel llamaModel, HuggingFaceModel llm) {
var selectableModels = llamaModel.getHuggingFaceModels().stream()
.filter(model -> model.getParameterSize() == llm.getParameterSize())
.toList();
populateModelComboBoxWithGroups(selectableModels);
huggingFaceComboBoxModel.setSelectedItem(llm);
}
private DefaultComboBoxModel<LlamaModel> createLlamaModelComboBoxModel() {
var model = new DefaultComboBoxModel<LlamaModel>();
model.addAll(LlamaModel.getSorted());
return model;
}
private JPanel createFormPanelCards() {
@ -199,6 +239,7 @@ public class LlamaModelPreferencesForm {
formPanelCards.setBorder(JBUI.Borders.emptyLeft(16));
formPanelCards.add(createPredefinedModelForm(), PREDEFINED_MODEL_FORM_CARD_CODE);
formPanelCards.add(createCustomModelForm(), CUSTOM_MODEL_FORM_CARD_CODE);
cardLayout.show(
formPanelCards,
predefinedModelRadioButton.isSelected()
@ -218,15 +259,27 @@ public class LlamaModelPreferencesForm {
buttonGroup.add(predefinedModelRadioButton);
buttonGroup.add(customModelRadioButton);
var predefinedModelHelpText = createHelpText(
"settingsConfigurable.service.llama.predefinedModel.comment");
var customModelHelpText = createHelpText(
"settingsConfigurable.service.llama.customModel.comment");
var radioPanel = new JPanel();
radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.PAGE_AXIS));
radioPanel.add(predefinedModelRadioButton);
radioPanel.add(Box.createVerticalStrut(4));
radioPanel.add(predefinedModelHelpText);
radioPanel.add(customModelRadioButton);
radioPanel.add(Box.createVerticalStrut(8));
radioPanel.add(customModelHelpText);
return radioPanel;
}
private Component createHelpText(String bundleKey) {
var helpText = ComponentPanelBuilder.createCommentComponent(
CodeGPTBundle.get(bundleKey), true);
helpText.setBorder(JBUI.Borders.empty(0, 28, 8, 0));
return helpText;
}
private JPanel createCustomModelForm() {
var customModelHelpText = ComponentPanelBuilder.createCommentComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.customModelPath.comment"),
@ -254,17 +307,8 @@ public class LlamaModelPreferencesForm {
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);
var modelComboBoxWrapper = createModelComboBoxWrapper();
var huggingFaceModelComboBoxWrapper = createHuggingFaceModelComboBoxWrapper();
return FormBuilder.createFormBuilder()
.addLabeledComponent(CodeGPTBundle.get("settingsConfigurable.shared.model.label"),
@ -278,11 +322,28 @@ public class LlamaModelPreferencesForm {
.addComponentToRightColumn(quantizationHelpText)
.addComponentToRightColumn(downloadModelActionLinkWrapper)
.addComponentToRightColumn(progressLabel)
.addComponentToRightColumn(modelActionsWrapper)
.addVerticalGap(4)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
private JPanel createModelComboBoxWrapper() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
wrapper.add(modelComboBox);
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(helpIcon);
return wrapper;
}
private JPanel createHuggingFaceModelComboBoxWrapper() {
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
wrapper.add(huggingFaceModelComboBox);
wrapper.add(Box.createHorizontalStrut(8));
wrapper.add(modelDetailsLabel);
return wrapper;
}
private String getHuggingFaceModelDetailsHtml(HuggingFaceModel model) {
int parameterSize = model.getParameterSize();
int quantization = model.getQuantization();
@ -312,20 +373,26 @@ public class LlamaModelPreferencesForm {
comboBox.setPreferredSize(new Dimension(280, comboBox.getPreferredSize().height));
comboBox.setSelectedItem(llamaModel);
initializeModelSizes(llamaModel, llm, modelSizeComboBoxModel);
comboBox.addItemListener(e -> {
var selectedModel = (LlamaModel) e.getItem();
var hfm = selectedModel.getLastExistingModelOrFirst();
var modelSize = initializeModelSizes(selectedModel, hfm, modelSizeComboBoxModel);
var huggingFaceModels = selectedModel.filterSelectedModelsBySize(modelSize);
huggingFaceComboBoxModel.removeAllElements();
huggingFaceComboBoxModel.addAll(huggingFaceModels);
huggingFaceComboBoxModel.setSelectedItem(hfm);
modelSizeComboBox.setEnabled(
modelSizeComboBox.getModel().getSize() > 1 && !llamaServerAgent.isServerRunning());
});
comboBox.addItemListener(
e -> handleModelSelection(e, modelSizeComboBoxModel, llamaServerAgent));
return comboBox;
}
private void handleModelSelection(
ItemEvent e,
DefaultComboBoxModel<ModelSize> modelSizeComboBoxModel,
LlamaServerAgent llamaServerAgent) {
var selectedModel = (LlamaModel) e.getItem();
var hfm = selectedModel.getLastExistingModelOrFirst();
var modelSize = initializeModelSizes(selectedModel, hfm, modelSizeComboBoxModel);
var huggingFaceModels = selectedModel.filterSelectedModelsBySize(modelSize);
populateModelComboBoxWithGroups(huggingFaceModels);
huggingFaceComboBoxModel.setSelectedItem(hfm);
modelSizeComboBox.setEnabled(
modelSizeComboBox.getModel().getSize() > 1 && !llamaServerAgent.isServerRunning());
}
private static ModelSize initializeModelSizes(
LlamaModel llamaModel,
HuggingFaceModel hfm,
@ -333,60 +400,98 @@ public class LlamaModelPreferencesForm {
var modelSizes = llamaModel.getSortedUniqueModelSizes();
modelSizeComboBoxModel.removeAllElements();
modelSizeComboBoxModel.addAll(modelSizes);
var selectedModelSize = modelSizes.stream()
.filter(ms -> ms.size() == hfm.getParameterSize())
.findFirst().orElse(modelSizes.get(0));
var selectedModelSize = findMatchingModelSize(modelSizes, hfm.getParameterSize())
.orElse(modelSizes.get(0));
modelSizeComboBoxModel.setSelectedItem(selectedModelSize);
return selectedModelSize;
}
private static Optional<ModelSize> findMatchingModelSize(List<ModelSize> modelSizes,
int parameterSize) {
return modelSizes.stream()
.filter(ms -> ms.size() == parameterSize)
.findFirst();
}
private ComboBox<ModelSize> createModelSizeComboBox(
ComboBoxModel<LlamaModel> llamaModelComboBoxModel,
DefaultComboBoxModel<ModelSize> modelSizeComboBoxModel,
LlamaServerAgent llamaServerAgent,
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel) {
LlamaServerAgent llamaServerAgent) {
var comboBox = new ComboBox<>(modelSizeComboBoxModel);
comboBox.setPreferredSize(modelComboBox.getPreferredSize());
comboBox.setSelectedItem(modelSizeComboBoxModel.getSelectedItem());
comboBox.setEnabled(
modelSizeComboBoxModel.getSize() > 1 && !llamaServerAgent.isServerRunning());
comboBox.addItemListener(e -> {
var selectedModel = (LlamaModel) llamaModelComboBoxModel.getSelectedItem();
var models = selectedModel.filterSelectedModelsBySize(
(ModelSize) modelSizeComboBoxModel.getSelectedItem());
comboBox.setEnabled(
modelSizeComboBoxModel.getSize() > 1 && !llamaServerAgent.isServerRunning());
if (!models.isEmpty()) {
huggingFaceComboBoxModel.removeAllElements();
huggingFaceComboBoxModel.addAll(models);
huggingFaceComboBoxModel.setSelectedItem(models.get(0));
}
});
comboBox.addItemListener(e -> handleModelSizeSelection(
e, llamaModelComboBoxModel, modelSizeComboBoxModel, llamaServerAgent));
return comboBox;
}
private ComboBox<HuggingFaceModel> createModelQuantizationComboBox(
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel) {
private void handleModelSizeSelection(
java.awt.event.ItemEvent e,
ComboBoxModel<LlamaModel> llamaModelComboBoxModel,
DefaultComboBoxModel<ModelSize> modelSizeComboBoxModel,
LlamaServerAgent llamaServerAgent) {
var selectedModel = (LlamaModel) llamaModelComboBoxModel.getSelectedItem();
if (selectedModel == null) {
return;
}
var selectedSize = (ModelSize) modelSizeComboBoxModel.getSelectedItem();
if (selectedSize == null) {
return;
}
var models = selectedModel.filterSelectedModelsBySize(selectedSize);
modelSizeComboBox.setEnabled(
modelSizeComboBoxModel.getSize() > 1 && !llamaServerAgent.isServerRunning());
if (!models.isEmpty()) {
populateModelComboBoxWithGroups(models);
huggingFaceComboBoxModel.setSelectedItem(models.get(0));
}
}
private ComboBox<Object> createModelQuantizationComboBox() {
var comboBox = new ComboBox<>(huggingFaceComboBoxModel);
updateFromModelState(comboBox.getItem());
comboBox.addItemListener(e -> updateFromModelState((HuggingFaceModel) e.getItem()));
comboBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
var item = value instanceof HuggingFaceModel hfm ? hfm.getQuantizationLabel() : value;
return super.getListCellRendererComponent(list, item, index, isSelected, cellHasFocus);
}
});
var selectedItem = comboBox.getSelectedItem();
if (selectedItem instanceof HuggingFaceModel) {
updateFromModelState((HuggingFaceModel) selectedItem);
}
comboBox.addItemListener(this::handleQuantizationSelection);
comboBox.setRenderer(new ModelQuantizationRenderer());
return comboBox;
}
private void handleQuantizationSelection(java.awt.event.ItemEvent e) {
var item = e.getItem();
if (item instanceof HuggingFaceModel hfm) {
updateFromModelState(hfm);
} else if (item instanceof String) {
SwingUtilities.invokeLater(this::selectFirstAvailableModel);
}
}
private void selectFirstAvailableModel() {
for (int i = 0; i < huggingFaceComboBoxModel.getSize(); i++) {
var item = huggingFaceComboBoxModel.getElementAt(i);
if (item instanceof HuggingFaceModel) {
huggingFaceModelComboBox.setSelectedItem(item);
break;
}
}
}
private void updateFromModelState(HuggingFaceModel selectedModel) {
var modelExists = isModelExists(selectedModel);
updateModelHelpTooltip(selectedModel);
modelDetailsLabel.setText(getHuggingFaceModelDetailsHtml(selectedModel));
modelExistsIcon.setVisible(modelExists);
downloadModelActionLinkWrapper.setVisible(!modelExists);
updateModelActionsPanel(selectedModel);
}
private TextFieldWithBrowseButton createBrowsableCustomModelTextField(boolean enabled) {
@ -403,7 +508,6 @@ public class LlamaModelPreferencesForm {
private AnActionLink createCancelDownloadLink(
JBLabel progressLabel,
JPanel actionLinkWrapper,
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel,
ProgressIndicator progressIndicator) {
return new AnActionLink(
CodeGPTBundle.get("settingsConfigurable.service.llama.cancelDownloadLink.label"),
@ -414,10 +518,7 @@ public class LlamaModelPreferencesForm {
configureFieldsForDownloading(false);
updateActionLink(
actionLinkWrapper,
createDownloadModelLink(
progressLabel,
actionLinkWrapper,
huggingFaceComboBoxModel));
createDownloadModelLink(progressLabel, actionLinkWrapper));
progressIndicator.cancel();
});
}
@ -426,7 +527,11 @@ public class LlamaModelPreferencesForm {
private void updateActionLink(JPanel actionLinkWrapper, AnActionLink actionLink) {
actionLinkWrapper.removeAll();
actionLinkWrapper.add(actionLink, BorderLayout.WEST);
var flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
flowPanel.add(actionLink);
flowPanel.add(Box.createHorizontalStrut(16));
flowPanel.add(createOpenFolderLink());
actionLinkWrapper.add(flowPanel, BorderLayout.WEST);
actionLinkWrapper.revalidate();
actionLinkWrapper.repaint();
}
@ -437,13 +542,11 @@ public class LlamaModelPreferencesForm {
modelComboBox.setEnabled(!downloading);
modelSizeComboBox.setEnabled(!downloading);
huggingFaceModelComboBox.setEnabled(!downloading);
modelExistsIcon.setVisible(!downloading);
}
private AnActionLink createDownloadModelLink(
JBLabel progressLabel,
JPanel actionLinkWrapper,
DefaultComboBoxModel<HuggingFaceModel> huggingFaceComboBoxModel) {
JPanel actionLinkWrapper) {
return new AnActionLink(
CodeGPTBundle.get("settingsConfigurable.service.llama.downloadModelLink.label"),
new DownloadModelAction(
@ -451,29 +554,26 @@ public class LlamaModelPreferencesForm {
configureFieldsForDownloading(true);
updateActionLink(
actionLinkWrapper,
createCancelDownloadLink(
progressLabel,
actionLinkWrapper,
huggingFaceComboBoxModel,
progressIndicator));
createCancelDownloadLink(progressLabel, actionLinkWrapper, progressIndicator));
}),
() -> SwingUtilities.invokeLater(() -> {
configureFieldsForDownloading(false);
updateActionLink(
actionLinkWrapper,
createDownloadModelLink(
progressLabel,
actionLinkWrapper,
huggingFaceComboBoxModel));
actionLinkWrapper.setVisible(false);
LlamaSettings.getCurrentState().setHuggingFaceModel(
(HuggingFaceModel) huggingFaceComboBoxModel.getSelectedItem());
var downloadedModel = getSelectedModel();
if (downloadedModel != null) {
LlamaSettings.getCurrentState().setHuggingFaceModel(downloadedModel);
updateFromModelState(downloadedModel);
}
}),
(error) -> {
throw new RuntimeException(error);
},
(text) -> SwingUtilities.invokeLater(() -> progressLabel.setText(text)),
huggingFaceComboBoxModel), "unknown");
getHuggingFaceComboBoxModel()), "unknown");
}
@SuppressWarnings("unchecked")
private DefaultComboBoxModel<HuggingFaceModel> getHuggingFaceComboBoxModel() {
return (DefaultComboBoxModel<HuggingFaceModel>) (DefaultComboBoxModel<?>) huggingFaceComboBoxModel;
}
private void updateModelHelpTooltip(HuggingFaceModel model) {
@ -488,6 +588,131 @@ public class LlamaModelPreferencesForm {
.installOn(helpIcon);
}
private record ModelDetails(double fileSize, double maxRAMRequired) {
private void populateModelComboBoxWithGroups(List<HuggingFaceModel> models) {
huggingFaceComboBoxModel.removeAllElements();
var downloadedModels = models.stream()
.filter(LlamaSettings::isModelExists)
.toList();
var notDownloadedModels = models.stream()
.filter(model -> !isModelExists(model))
.toList();
if (!downloadedModels.isEmpty()) {
huggingFaceComboBoxModel.addElement(GROUP_DOWNLOADED);
downloadedModels.forEach(huggingFaceComboBoxModel::addElement);
}
if (!notDownloadedModels.isEmpty()) {
huggingFaceComboBoxModel.addElement(GROUP_NOT_DOWNLOADED);
notDownloadedModels.forEach(huggingFaceComboBoxModel::addElement);
}
}
}
private void updateModelActionsPanel(HuggingFaceModel model) {
modelActionsWrapper.removeAll();
downloadModelActionLinkWrapper.removeAll();
var flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
if (isModelExists(model)) {
flowPanel.add(createDeleteModelLink(model));
} else {
flowPanel.add(createDownloadModelLink(progressLabel, downloadModelActionLinkWrapper));
}
flowPanel.add(Box.createHorizontalStrut(16));
flowPanel.add(createOpenFolderLink());
downloadModelActionLinkWrapper.setVisible(false);
modelActionsWrapper.setVisible(true);
modelActionsWrapper.add(flowPanel, BorderLayout.WEST);
modelActionsWrapper.revalidate();
modelActionsWrapper.repaint();
downloadModelActionLinkWrapper.revalidate();
downloadModelActionLinkWrapper.repaint();
}
private AnActionLink createDeleteModelLink(HuggingFaceModel model) {
return new AnActionLink("Delete Model", new AnAction() {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var modelName = model.getFileName();
var result = Messages.showYesNoDialog(
"Are you sure you want to delete the model '" + modelName + "'?",
"Delete Model",
Messages.getQuestionIcon()
);
if (result == Messages.YES) {
var modelFile = getLlamaModelsPath().resolve(modelName).toFile();
if (modelFile.exists() && modelFile.delete()) {
updateFromModelState(model);
Messages.showInfoMessage("Model '" + modelName + "' has been deleted.",
"Model Deleted");
} else {
Messages.showErrorDialog("Failed to delete the model file.", "Delete Failed");
}
}
}
});
}
private AnActionLink createOpenFolderLink() {
return new AnActionLink("Open in Folder", new AnAction() {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
try {
var modelsPath = getLlamaModelsPath().toFile();
if (!modelsPath.exists() && !modelsPath.mkdirs()) {
Messages.showErrorDialog("Failed to create models directory.", "Error");
return;
}
if (Desktop.isDesktopSupported()) {
Desktop.getDesktop().open(modelsPath);
} else {
Messages.showErrorDialog("Desktop operations are not supported on this system.",
"Error");
}
} catch (IOException ex) {
Messages.showErrorDialog("Failed to open the models folder: " + ex.getMessage(), "Error");
}
}
});
}
private static class ModelQuantizationRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
if (value instanceof String groupHeader) {
var label = (JLabel) super.getListCellRendererComponent(list, groupHeader, index, false,
false);
label.setFont(label.getFont().deriveFont(java.awt.Font.BOLD));
label.setEnabled(false);
if (index > 0) {
label.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBUI.CurrentTheme.Popup.separatorColor(), 1, 0, 0, 0),
JBUI.Borders.empty(4, 4)
));
} else {
label.setBorder(JBUI.Borders.empty(4, 4));
}
return label;
}
if (value instanceof HuggingFaceModel hfm) {
var item = hfm.getQuantizationLabel();
return super.getListCellRendererComponent(list, item, index, isSelected, cellHasFocus);
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
private record ModelDetails(double fileSize, double maxRAMRequired) {
}
}

View file

@ -1,87 +0,0 @@
package ee.carlrobert.codegpt.settings.service.llama.form;
import com.intellij.openapi.ui.panel.ComponentPanelBuilder;
import com.intellij.ui.components.JBTextField;
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.settings.service.llama.LlamaSettingsState;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class LlamaRequestPreferencesForm {
private final IntegerField topKField;
private final JBTextField topPField;
private final JBTextField minPField;
private final JBTextField repeatPenaltyField;
public LlamaRequestPreferencesForm(LlamaSettingsState llamaSettings) {
topKField = new IntegerField();
topKField.setColumns(12);
topKField.setValue(llamaSettings.getTopK());
topPField = new JBTextField(12);
topPField.setText(String.valueOf(llamaSettings.getTopP()));
minPField = new JBTextField(12);
minPField.setText(String.valueOf(llamaSettings.getMinP()));
repeatPenaltyField = new JBTextField(12);
repeatPenaltyField.setText(String.valueOf(llamaSettings.getRepeatPenalty()));
}
public JPanel getForm() {
return FormBuilder.createFormBuilder()
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.topK.label"),
topKField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.topK.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.topP.label"),
topPField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.topP.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.minP.label"),
minPField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.minP.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.repeatPenalty.label"),
repeatPenaltyField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.repeatPenalty.comment"))
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
public void resetForm(LlamaSettingsState state) {
topKField.setValue(state.getTopK());
topPField.setText(String.valueOf((state.getTopP())));
minPField.setText(String.valueOf((state.getMinP())));
repeatPenaltyField.setText(String.valueOf((state.getRepeatPenalty())));
}
public int getTopK() {
return topKField.getValue();
}
public double getTopP() {
return Double.parseDouble(topPField.getText());
}
public double getMinP() {
return Double.parseDouble(minPField.getText());
}
public double getRepeatPenalty() {
return Double.parseDouble(repeatPenaltyField.getText());
}
private JLabel createComment(String messageKey) {
var comment = ComponentPanelBuilder.createCommentComponent(
CodeGPTBundle.get(messageKey), true);
comment.setBorder(JBUI.Borders.empty(0, 4));
return comment;
}
}

View file

@ -1,404 +0,0 @@
package ee.carlrobert.codegpt.settings.service.llama.form;
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LlamaApiKey;
import static ee.carlrobert.codegpt.settings.service.llama.LlamaSettings.isModelExists;
import static ee.carlrobert.codegpt.ui.UIUtil.createComment;
import static ee.carlrobert.codegpt.ui.UIUtil.createForm;
import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.ui.PortField;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBPasswordField;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.components.fields.IntegerField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent;
import ee.carlrobert.codegpt.completions.llama.LlamaServerStartupParams;
import ee.carlrobert.codegpt.completions.llama.PromptTemplate;
import ee.carlrobert.codegpt.credentials.CredentialsStore;
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.ui.UIUtil;
import ee.carlrobert.codegpt.ui.UIUtil.RadioButtonWithLayout;
import ee.carlrobert.codegpt.ui.URLTextField;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.Nullable;
public class LlamaServerPreferencesForm {
private static final String RUN_LOCAL_SERVER_FORM_CARD_CODE = "RunLocalServerSettings";
private static final String USE_EXISTING_SERVER_FORM_CARD_CODE = "UseExistingServerSettings";
private final LlamaModelPreferencesForm llamaModelPreferencesForm;
private final JBRadioButton runLocalServerRadioButton;
private final JBRadioButton useExistingServerRadioButton;
private final JBTextField baseHostField;
private final JBPasswordField apiKeyField;
private final PortField portField;
private final IntegerField maxTokensField;
private final IntegerField threadsField;
private final JBTextField additionalParametersField;
private final JBTextField additionalBuildParametersField;
private final JBTextField additionalEnvironmentVariablesField;
private final ChatPromptTemplatePanel remotePromptTemplatePanel;
private final InfillPromptTemplatePanel infillPromptTemplatePanel;
public LlamaServerPreferencesForm(LlamaSettingsState settings) {
var llamaServerAgent = ApplicationManager.getApplication().getService(LlamaServerAgent.class);
var serverRunning = llamaServerAgent.isServerRunning();
portField = new PortField(settings.getServerPort());
portField.setEnabled(!serverRunning);
maxTokensField = new IntegerField("max_tokens", 256, 4096);
maxTokensField.setColumns(12);
maxTokensField.setValue(settings.getContextSize());
maxTokensField.setEnabled(!serverRunning);
threadsField = new IntegerField("threads", 1, 256);
threadsField.setColumns(12);
threadsField.setValue(settings.getThreads());
threadsField.setEnabled(!serverRunning);
additionalParametersField = new JBTextField(settings.getAdditionalParameters(), 30);
additionalParametersField.setEnabled(!serverRunning);
additionalBuildParametersField = new JBTextField(settings.getAdditionalBuildParameters(), 30);
additionalBuildParametersField.setEnabled(!serverRunning);
additionalEnvironmentVariablesField = new JBTextField(
settings.getAdditionalEnvironmentVariables(), 30);
additionalEnvironmentVariablesField.setEnabled(!serverRunning);
baseHostField = new URLTextField(settings.getBaseHost(), 30);
apiKeyField = new JBPasswordField();
apiKeyField.setColumns(30);
ApplicationManager.getApplication().executeOnPooledThread(() -> {
var apiKey = CredentialsStore.getCredential(CredentialKey.LlamaApiKey.INSTANCE);
SwingUtilities.invokeLater(() -> apiKeyField.setText(apiKey));
});
llamaModelPreferencesForm = new LlamaModelPreferencesForm();
runLocalServerRadioButton = new JBRadioButton("Run local server",
settings.isRunLocalServer());
useExistingServerRadioButton = new JBRadioButton("Use remote server",
!settings.isRunLocalServer());
runLocalServerRadioButton.setEnabled(SystemInfoRt.isUnix);
runLocalServerRadioButton.setVisible(SystemInfoRt.isUnix);
remotePromptTemplatePanel = new ChatPromptTemplatePanel(
settings.getRemoteModelPromptTemplate(), true);
infillPromptTemplatePanel = new InfillPromptTemplatePanel(
settings.getRemoteModelInfillPromptTemplate(), true
);
}
public JPanel getForm() {
var llamaServerAgent =
ApplicationManager.getApplication().getService(LlamaServerAgent.class);
return createForm(Map.of(
RUN_LOCAL_SERVER_FORM_CARD_CODE, new RadioButtonWithLayout(runLocalServerRadioButton,
createRunLocalServerForm(llamaServerAgent)),
USE_EXISTING_SERVER_FORM_CARD_CODE, new RadioButtonWithLayout(useExistingServerRadioButton,
createUseExistingServerForm())
), SystemInfoRt.isUnix ? (runLocalServerRadioButton.isSelected()
? RUN_LOCAL_SERVER_FORM_CARD_CODE
: USE_EXISTING_SERVER_FORM_CARD_CODE) : USE_EXISTING_SERVER_FORM_CARD_CODE);
}
public void resetForm(LlamaSettingsState state) {
llamaModelPreferencesForm.resetForm(state);
remotePromptTemplatePanel.setPromptTemplate(state.getLocalModelPromptTemplate()); // ?
infillPromptTemplatePanel.setPromptTemplate(state.getLocalModelInfillPromptTemplate());
runLocalServerRadioButton.setSelected(state.isRunLocalServer());
baseHostField.setText(state.getBaseHost());
portField.setNumber(state.getServerPort());
maxTokensField.setValue(state.getContextSize());
threadsField.setValue(state.getThreads());
additionalParametersField.setText(state.getAdditionalParameters());
additionalBuildParametersField.setText(state.getAdditionalBuildParameters());
additionalEnvironmentVariablesField.setText(state.getAdditionalEnvironmentVariables());
remotePromptTemplatePanel.setPromptTemplate(state.getRemoteModelPromptTemplate()); // ?
infillPromptTemplatePanel.setPromptTemplate(state.getRemoteModelInfillPromptTemplate());
apiKeyField.setText(CredentialsStore.getCredential(LlamaApiKey.INSTANCE));
}
public JComponent createUseExistingServerForm() {
var apiKeyFieldPanel = UI.PanelFactory.panel(apiKeyField)
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"))
.resizeX(false)
.withComment(
CodeGPTBundle.get("settingsConfigurable.shared.apiKey.comment"))
.withCommentHyperlinkListener(UIUtil::handleHyperlinkClicked)
.createPanel();
return withEmptyLeftBorder(FormBuilder.createFormBuilder()
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.baseHost.label"),
baseHostField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.baseHost.comment"))
.addLabeledComponent(CodeGPTBundle.get("shared.promptTemplate"),
remotePromptTemplatePanel)
.addComponentToRightColumn(remotePromptTemplatePanel.getPromptTemplateHelpText())
.addLabeledComponent(CodeGPTBundle.get("shared.infillPromptTemplate"),
infillPromptTemplatePanel)
.addComponentToRightColumn(infillPromptTemplatePanel.getPromptTemplateHelpText())
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.shared.authentication.title")))
.addComponent(withEmptyLeftBorder(apiKeyFieldPanel))
.getPanel());
}
public JComponent createRunLocalServerForm(LlamaServerAgent llamaServerAgent) {
var serverProgressPanel = new ServerProgressPanel();
serverProgressPanel.setBorder(JBUI.Borders.emptyRight(16));
return withEmptyLeftBorder(FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.service.llama.modelPreferences.title")))
.addComponent(withEmptyLeftBorder(llamaModelPreferencesForm.getForm()))
.addComponent(new TitledSeparator("Server Configuration"))
.addComponent(withEmptyLeftBorder(FormBuilder.createFormBuilder()
.addLabeledComponent(
CodeGPTBundle.get("shared.port"),
JBUI.Panels.simplePanel()
.addToLeft(portField)
.addToRight(JBUI.Panels.simplePanel()
.addToCenter(serverProgressPanel)
.addToRight(getServerButton(llamaServerAgent, serverProgressPanel))))
.addVerticalGap(4)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.contextSize.label"),
maxTokensField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.contextSize.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.threads.label"),
threadsField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.threads.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.additionalParameters.label"),
additionalParametersField)
.addComponentToRightColumn(
createComment(
"settingsConfigurable.service.llama.additionalParameters.comment"))
.addLabeledComponent(
CodeGPTBundle.get(
"settingsConfigurable.service.llama.additionalBuildParameters.label"),
additionalBuildParametersField)
.addComponentToRightColumn(
createComment(
"settingsConfigurable.service.llama.additionalBuildParameters.comment"))
.addVerticalGap(4)
.addLabeledComponent(
CodeGPTBundle.get(
"settingsConfigurable.service.llama.additionalEnvironmentVariables.label"),
additionalEnvironmentVariablesField)
.addComponentToRightColumn(
createComment(
"settingsConfigurable.service.llama.additionalEnvironmentVariables.comment"))
.addVerticalGap(4)
.addComponentFillVertically(new JPanel(), 0)
.getPanel()))
.getPanel());
}
private JButton getServerButton(
LlamaServerAgent llamaServerAgent,
ServerProgressPanel serverProgressPanel) {
llamaServerAgent.setActiveServerProgressPanel(serverProgressPanel);
var serverRunning = llamaServerAgent.isServerRunning();
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 (!validateModelConfiguration()) {
return;
}
if (llamaServerAgent.isServerRunning()) {
enableForm(serverButton, serverProgressPanel);
llamaServerAgent.stopAgent();
} else {
disableForm(serverButton, serverProgressPanel);
llamaServerAgent.startAgent(
new LlamaServerStartupParams(
llamaModelPreferencesForm.getActualModelPath(),
getContextSize(),
getThreads(),
getServerPort(),
getListOfAdditionalParameters(),
getListOfAdditionalBuildParameters(),
getMapOfAdditionalEnvironmentVariables()
),
serverProgressPanel,
() -> {
setFormEnabled(false);
serverProgressPanel.displayComponent(new JBLabel(
CodeGPTBundle.get("settingsConfigurable.service.llama.progress.serverRunning"),
Actions.Checked,
SwingConstants.LEADING));
},
(activeServerProgressPanel) -> {
setFormEnabled(true);
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(Actions.Execute);
activeServerProgressPanel.displayComponent(new JBLabel(
CodeGPTBundle.get("settingsConfigurable.service.llama.progress.serverStopped"),
Actions.Cancel,
SwingConstants.LEADING));
});
}
});
return serverButton;
}
private boolean validateModelConfiguration() {
return validateCustomModelPath() && validateSelectedModel();
}
private boolean validateCustomModelPath() {
if (llamaModelPreferencesForm.isUseCustomLlamaModel()) {
var customModelPath = llamaModelPreferencesForm.getCustomLlamaModelPath();
if (customModelPath == null || customModelPath.isEmpty()) {
OverlayUtil.showBalloon(
CodeGPTBundle.get("validation.error.fieldRequired"),
MessageType.ERROR,
llamaModelPreferencesForm.getBrowsableCustomModelTextField());
return false;
}
}
return true;
}
private boolean validateSelectedModel() {
if (!llamaModelPreferencesForm.isUseCustomLlamaModel()
&& !isModelExists(llamaModelPreferencesForm.getSelectedModel())) {
OverlayUtil.showBalloon(
CodeGPTBundle.get("settingsConfigurable.service.llama.overlay.modelNotDownloaded.text"),
MessageType.ERROR,
llamaModelPreferencesForm.getHuggingFaceModelComboBox());
return false;
}
return true;
}
private void enableForm(JButton serverButton, ServerProgressPanel progressPanel) {
setFormEnabled(true);
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(Actions.Execute);
progressPanel.displayText(
CodeGPTBundle.get("settingsConfigurable.service.llama.progress.stoppingServer"));
}
private void disableForm(JButton serverButton, ServerProgressPanel progressPanel) {
setFormEnabled(false);
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label"));
serverButton.setIcon(Actions.Suspend);
progressPanel.displayText(
CodeGPTBundle.get("settingsConfigurable.service.llama.progress.startingServer"));
}
private void setFormEnabled(boolean enabled) {
llamaModelPreferencesForm.enableFields(enabled);
portField.setEnabled(enabled);
maxTokensField.setEnabled(enabled);
threadsField.setEnabled(enabled);
additionalParametersField.setEnabled(enabled);
additionalBuildParametersField.setEnabled(enabled);
additionalEnvironmentVariablesField.setEnabled(enabled);
}
public boolean isRunLocalServer() {
return runLocalServerRadioButton.isSelected();
}
public String getBaseHost() {
return baseHostField.getText();
}
public int getServerPort() {
return portField.getNumber();
}
public LlamaModelPreferencesForm getLlamaModelPreferencesForm() {
return llamaModelPreferencesForm;
}
public int getContextSize() {
return maxTokensField.getValue();
}
public void setThreads(int threads) {
threadsField.setValue(threads);
}
public int getThreads() {
return threadsField.getValue();
}
public String getAdditionalParameters() {
return additionalParametersField.getText();
}
public List<String> getListOfAdditionalParameters() {
return LlamaSettings.getAdditionalParametersList(additionalParametersField.getText());
}
public String getAdditionalBuildParameters() {
return additionalBuildParametersField.getText();
}
public List<String> getListOfAdditionalBuildParameters() {
return LlamaSettings.getAdditionalParametersList(additionalBuildParametersField.getText());
}
public String getAdditionalEnvironmentVariables() {
return additionalEnvironmentVariablesField.getText();
}
public Map<String, String> getMapOfAdditionalEnvironmentVariables() {
return LlamaSettings.getAdditionalEnvironmentVariablesMap(
additionalEnvironmentVariablesField.getText());
}
public PromptTemplate getPromptTemplate() {
var template = isRunLocalServer() ? llamaModelPreferencesForm.getPromptTemplate()
: remotePromptTemplatePanel.getPromptTemplate();
return template == null ? PromptTemplate.CODE_QWEN : template;
}
public @Nullable String getApiKey() {
var apiKey = new String(apiKeyField.getPassword());
return apiKey.isEmpty() ? null : apiKey;
}
public InfillPromptTemplate getInfillPromptTemplate() {
return infillPromptTemplatePanel.getPromptTemplate();
}
}

View file

@ -1,83 +1,456 @@
package ee.carlrobert.codegpt.settings.service.llama.form;
import static ee.carlrobert.codegpt.ui.UIUtil.createComment;
import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder;
import com.intellij.ui.TitledSeparator;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.ui.JBColor;
import com.intellij.ui.PortField;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.components.fields.IntegerField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.components.BorderLayoutPanel;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.settings.service.CodeCompletionConfigurationForm;
import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent;
import ee.carlrobert.codegpt.completions.llama.LlamaServerStartupParams;
import ee.carlrobert.codegpt.completions.llama.SimpleConsolePanel;
import ee.carlrobert.codegpt.completions.llama.logging.SettingsFormLoggingStrategy;
import ee.carlrobert.codegpt.services.llama.ServerLogsManager;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import org.jetbrains.annotations.NotNull;
public class LlamaSettingsForm extends JPanel {
private final LlamaServerPreferencesForm llamaServerPreferencesForm;
private final LlamaRequestPreferencesForm llamaRequestPreferencesForm;
private final CodeCompletionConfigurationForm codeCompletionConfigurationForm;
private static final int SERVER_CONFIG_TAB = 0;
private static final int SERVER_LOGS_TAB = 1;
private static final int BUILD_OUTPUT_TAB = 2;
private static final String SERVER_LOGS_DISABLED_TOOLTIP = "Server must be running to view logs";
private static final String BUILD_OUTPUT_DISABLED_TOOLTIP = "Available during build process";
public LlamaSettingsForm(LlamaSettingsState settings) {
llamaServerPreferencesForm = new LlamaServerPreferencesForm(settings);
llamaRequestPreferencesForm = new LlamaRequestPreferencesForm(settings);
codeCompletionConfigurationForm = new CodeCompletionConfigurationForm(
settings.isCodeCompletionsEnabled(),
null,
null);
init();
private final LlamaServerAgent serverAgent;
private final LlamaSettingsState settingsState;
private final JBTabbedPane tabbedPane;
private final LlamaServerPreferencesForm serverPreferencesForm;
private final SimpleConsolePanel serverLogsConsole;
private final SimpleConsolePanel buildLogsConsole;
private final JPanel serverStatusPanel;
private final JBLabel serverStatusLabel;
private final AsyncProcessIcon serverStatusSpinner;
public LlamaSettingsForm(LlamaSettingsState settingsState) {
this.settingsState = settingsState;
serverAgent = ApplicationManager.getApplication().getService(LlamaServerAgent.class);
tabbedPane = new JBTabbedPane();
serverStatusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
serverStatusLabel = new JBLabel();
serverStatusSpinner = new AsyncProcessIcon("server_status_spinner");
serverStatusPanel.add(serverStatusSpinner);
serverStatusPanel.add(Box.createHorizontalStrut(4));
serverStatusPanel.add(serverStatusLabel);
serverStatusSpinner.setVisible(false);
serverPreferencesForm = new LlamaServerPreferencesForm(settingsState, this);
serverLogsConsole = new SimpleConsolePanel();
buildLogsConsole = new SimpleConsolePanel();
updateServerStatus();
loadExistingLogs();
setLayout(new BorderLayout());
add(createLocalServerPanel(), BorderLayout.CENTER);
serverAgent.setSettingsForm(this);
}
private JPanel createLocalServerPanel() {
var panel = new BorderLayoutPanel();
var modelPreferencesForm = serverPreferencesForm.getLlamaModelPreferencesForm();
var modelPanel = new BorderLayoutPanel();
modelPanel.add(modelPreferencesForm.getForm(), BorderLayout.CENTER);
modelPanel.setBorder(JBUI.Borders.emptyBottom(8));
panel.add(modelPanel, BorderLayout.NORTH);
var serverConfigTab = createServerConfigurationTab();
tabbedPane.addTab(CodeGPTBundle.get("llama.ui.tab.serverConfiguration"), AllIcons.General.Settings, serverConfigTab);
var serverLogsTab = createServerLogsTab();
tabbedPane.addTab(CodeGPTBundle.get("llama.ui.tab.serverLogs"), AllIcons.Debugger.Console, serverLogsTab);
var buildLogsTab = createBuildLogsTab();
tabbedPane.addTab(CodeGPTBundle.get("llama.ui.tab.buildOutput"), AllIcons.Actions.Compile, buildLogsTab);
tabbedPane.addChangeListener(e -> {
SwingUtilities.invokeLater(() -> {
int selectedIndex = tabbedPane.getSelectedIndex();
if (selectedIndex == 1) {
serverLogsConsole.revalidate();
serverLogsConsole.repaint();
} else if (selectedIndex == 2) {
buildLogsConsole.revalidate();
buildLogsConsole.repaint();
}
});
});
panel.add(tabbedPane, BorderLayout.CENTER);
setTabState(SERVER_LOGS_TAB, serverAgent.isServerRunning(), SERVER_LOGS_DISABLED_TOOLTIP);
setTabState(BUILD_OUTPUT_TAB, serverAgent.isBuildInProgress(), BUILD_OUTPUT_DISABLED_TOOLTIP);
return panel;
}
private JPanel createServerConfigurationTab() {
var serverRunning = serverAgent.isServerRunning();
var portField = new PortField(serverPreferencesForm.getServerPort());
portField.setEnabled(!serverRunning);
var contextSizeField = new IntegerField("context_size", 256, 4096);
contextSizeField.setColumns(12);
contextSizeField.setValue(serverPreferencesForm.getContextSize());
contextSizeField.setEnabled(!serverRunning);
var threadsField = new IntegerField("threads", 1, 256);
threadsField.setColumns(12);
threadsField.setValue(serverPreferencesForm.getThreads());
threadsField.setEnabled(!serverRunning);
var additionalParametersField = new JBTextField(serverPreferencesForm.getAdditionalParameters(),
30);
additionalParametersField.setEnabled(!serverRunning);
var additionalBuildParametersField = new JBTextField(
serverPreferencesForm.getAdditionalBuildParameters(), 30);
additionalBuildParametersField.setEnabled(!serverRunning);
var additionalEnvironmentVariablesField = new JBTextField(
serverPreferencesForm.getAdditionalEnvironmentVariables(), 30);
additionalEnvironmentVariablesField.setEnabled(!serverRunning);
var config = new ServerButtonConfig(
portField,
contextSizeField,
threadsField,
additionalParametersField,
additionalBuildParametersField,
additionalEnvironmentVariablesField
);
var serverButton = createServerButton(config);
var portPanel = new JPanel();
portPanel.setLayout(new BoxLayout(portPanel, BoxLayout.X_AXIS));
portPanel.add(portField);
portPanel.add(Box.createHorizontalGlue());
portPanel.add(serverButton);
var form = withEmptyLeftBorder(FormBuilder.createFormBuilder()
.addVerticalGap(8)
.addLabeledComponent(CodeGPTBundle.get("shared.port"), portPanel)
.addVerticalGap(8)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.contextSize.label"),
contextSizeField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.contextSize.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.threads.label"),
threadsField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.threads.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.additionalParameters.label"),
additionalParametersField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.additionalParameters.comment"))
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.additionalBuildParameters.label"),
additionalBuildParametersField)
.addComponentToRightColumn(
createComment("settingsConfigurable.service.llama.additionalBuildParameters.comment"))
.addLabeledComponent(
CodeGPTBundle.get(
"settingsConfigurable.service.llama.additionalEnvironmentVariables.label"),
additionalEnvironmentVariablesField)
.addComponentToRightColumn(
createComment(
"settingsConfigurable.service.llama.additionalEnvironmentVariables.comment"))
.addComponentFillVertically(new JPanel(), 0)
.getPanel());
var panel = new JPanel(new BorderLayout());
panel.add(form, BorderLayout.CENTER);
return panel;
}
private JButton createServerButton(ServerButtonConfig config) {
var serverRunning = serverAgent.isServerRunning();
var buildInProgress = serverAgent.isBuildInProgress();
var serverButton = new JButton();
if (serverRunning) {
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label"));
serverButton.setIcon(AllIcons.Actions.Suspend);
} else if (buildInProgress) {
serverButton.setText(CodeGPTBundle.get("llama.ui.button.stopBuild"));
serverButton.setIcon(AllIcons.Actions.Suspend);
} else {
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(AllIcons.Actions.Execute);
}
serverButton.addActionListener(e -> {
if (serverAgent.isServerRunning()) {
serverAgent.stopAgent();
setFieldsEnabled(true, config);
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(AllIcons.Actions.Execute);
} else if (serverAgent.isBuildInProgress()) {
serverAgent.stopAgent();
setFieldsEnabled(true, config);
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(AllIcons.Actions.Execute);
} else {
setTabState(BUILD_OUTPUT_TAB, true, BUILD_OUTPUT_DISABLED_TOOLTIP);
tabbedPane.setSelectedIndex(BUILD_OUTPUT_TAB);
serverButton.setText(CodeGPTBundle.get("llama.ui.button.stopBuild"));
serverButton.setIcon(AllIcons.Actions.Suspend);
setFieldsEnabled(false, config);
logToConsole(CodeGPTBundle.get("llama.process.startingBuild"), false, true);
var params = new LlamaServerStartupParams(
serverPreferencesForm.getLlamaModelPreferencesForm().getActualModelPath(),
config.getContextSizeField().getValue(),
config.getThreadsField().getValue(),
config.getPortField().getNumber(),
LlamaSettings.getAdditionalParametersList(
config.getAdditionalParametersField().getText()),
LlamaSettings.getAdditionalParametersList(
config.getAdditionalBuildParametersField().getText()),
LlamaSettings.getAdditionalEnvironmentVariablesMap(
config.getAdditionalEnvironmentVariablesField().getText())
);
serverAgent.startAgent(
params,
new SettingsFormLoggingStrategy(this),
() -> {
SwingUtilities.invokeLater(() -> {
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label"));
serverButton.setIcon(AllIcons.Actions.Suspend);
setTabState(SERVER_LOGS_TAB, true, SERVER_LOGS_DISABLED_TOOLTIP);
setTabState(BUILD_OUTPUT_TAB, false, BUILD_OUTPUT_DISABLED_TOOLTIP);
tabbedPane.setSelectedIndex(SERVER_LOGS_TAB);
});
},
() -> {
SwingUtilities.invokeLater(() -> {
setFieldsEnabled(true, config);
serverButton.setText(
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(AllIcons.Actions.Execute);
});
}
);
}
});
return serverButton;
}
private void setFieldsEnabled(boolean enabled, ServerButtonConfig config) {
config.getPortField().setEnabled(enabled);
config.getContextSizeField().setEnabled(enabled);
config.getThreadsField().setEnabled(enabled);
config.getAdditionalParametersField().setEnabled(enabled);
config.getAdditionalBuildParametersField().setEnabled(enabled);
config.getAdditionalEnvironmentVariablesField().setEnabled(enabled);
}
private JPanel createBuildLogsTab() {
return createLogsTab(buildLogsConsole, false);
}
private JPanel createServerLogsTab() {
return createLogsTab(serverLogsConsole, true);
}
private JPanel createLogsTab(Component view, boolean isServerLogs) {
var panel = new JPanel(new BorderLayout());
panel.add(createLogsToolbar(isServerLogs), BorderLayout.NORTH);
var scrollPane = new JScrollPane(view);
scrollPane.setPreferredSize(JBUI.size(600, 240));
panel.add(scrollPane, BorderLayout.CENTER);
panel.add(createStatusBar(), BorderLayout.SOUTH);
return panel;
}
private JComponent createLogsToolbar(boolean isServerLogs) {
var actionGroup = new DefaultActionGroup();
actionGroup.add(new DumbAwareAction(CodeGPTBundle.get("llama.ui.action.clear"), CodeGPTBundle.get("llama.ui.action.clear.description"), AllIcons.Actions.GC) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var consolePanel = isServerLogs ? serverLogsConsole : buildLogsConsole;
consolePanel.clearConsole();
}
});
actionGroup.add(new DumbAwareAction(CodeGPTBundle.get("llama.ui.action.scrollToEnd"), CodeGPTBundle.get("llama.ui.action.scrollToEnd.description"),
AllIcons.RunConfigurations.Scroll_down) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var consolePanel = isServerLogs ? serverLogsConsole : buildLogsConsole;
consolePanel.setCaretPosition(consolePanel.getDocument().getLength());
}
});
var toolbar = ActionManager.getInstance().createActionToolbar("LlamaLogs", actionGroup, true);
toolbar.setTargetComponent(this);
return toolbar.getComponent();
}
private JPanel createStatusBar() {
var statusBar = new JPanel(new BorderLayout());
statusBar.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBUI.CurrentTheme.ToolWindow.borderColor(), 1, 0, 0, 0),
JBUI.Borders.empty(4)
));
statusBar.add(serverStatusPanel, BorderLayout.WEST);
return statusBar;
}
private void loadExistingLogs() {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
var logsManager = ApplicationManager.getApplication().getService(ServerLogsManager.class);
var logs = logsManager.getSessionLogs(logsManager.getCurrentSession().getId());
SwingUtilities.invokeLater(() -> {
for (var log : logs) {
boolean isError = log.getMessage().toLowerCase().contains("error") ||
log.getMessage().toLowerCase().contains("exception") ||
log.getMessage().toLowerCase().contains("failed");
serverLogsConsole.appendText(log.getMessage(), isError);
}
});
});
}
private void updateServerStatus() {
SwingUtilities.invokeLater(() -> {
if (serverAgent.isServerRunning()) {
serverStatusLabel.setText(CodeGPTBundle.get("llama.ui.status.running"));
serverStatusLabel.setIcon(AllIcons.General.InspectionsOK);
serverStatusLabel.setForeground(JBColor.GREEN);
serverStatusSpinner.setVisible(false);
setTabState(SERVER_LOGS_TAB, true, SERVER_LOGS_DISABLED_TOOLTIP);
setTabState(BUILD_OUTPUT_TAB, false, BUILD_OUTPUT_DISABLED_TOOLTIP);
} else if (serverAgent.isBuildInProgress()) {
serverStatusLabel.setText(CodeGPTBundle.get("llama.ui.status.building"));
serverStatusLabel.setIcon(null);
serverStatusLabel.setForeground(JBColor.BLUE);
serverStatusSpinner.setVisible(true);
setTabState(SERVER_LOGS_TAB, false, SERVER_LOGS_DISABLED_TOOLTIP);
setTabState(BUILD_OUTPUT_TAB, true, BUILD_OUTPUT_DISABLED_TOOLTIP);
} else {
serverStatusLabel.setText(CodeGPTBundle.get("llama.ui.status.stopped"));
serverStatusLabel.setIcon(AllIcons.General.InspectionsEye);
serverStatusLabel.setForeground(JBColor.GRAY);
serverStatusSpinner.setVisible(false);
setTabState(SERVER_LOGS_TAB, false, SERVER_LOGS_DISABLED_TOOLTIP);
setTabState(BUILD_OUTPUT_TAB, false, BUILD_OUTPUT_DISABLED_TOOLTIP);
}
});
}
public void refreshServerStatus() {
updateServerStatus();
}
public void updateServerStatusWithPhase(String phase) {
SwingUtilities.invokeLater(() -> {
serverStatusLabel.setText(CodeGPTBundle.get("llama.ui.status.prefix").replace("{0}", phase));
serverStatusLabel.setIcon(null);
serverStatusLabel.setForeground(JBColor.BLUE);
serverStatusSpinner.setVisible(true);
});
}
public void logToConsole(String message, boolean isError, boolean isBuildLog) {
var consolePanel = isBuildLog ? buildLogsConsole : serverLogsConsole;
if (consolePanel != null) {
consolePanel.appendText(message, isError);
}
}
public LlamaSettingsState getCurrentState() {
var state = new LlamaSettingsState();
state.setTopK(llamaRequestPreferencesForm.getTopK());
state.setTopP(llamaRequestPreferencesForm.getTopP());
state.setMinP(llamaRequestPreferencesForm.getMinP());
state.setRepeatPenalty(llamaRequestPreferencesForm.getRepeatPenalty());
state.setRemoteModelPromptTemplate(llamaServerPreferencesForm.getPromptTemplate());
state.setRemoteModelInfillPromptTemplate(llamaServerPreferencesForm.getInfillPromptTemplate());
state.setRunLocalServer(llamaServerPreferencesForm.isRunLocalServer());
state.setBaseHost(llamaServerPreferencesForm.getBaseHost());
state.setServerPort(llamaServerPreferencesForm.getServerPort());
state.setContextSize(llamaServerPreferencesForm.getContextSize());
state.setThreads(llamaServerPreferencesForm.getThreads());
state.setAdditionalParameters(llamaServerPreferencesForm.getAdditionalParameters());
state.setAdditionalBuildParameters(llamaServerPreferencesForm.getAdditionalBuildParameters());
state.setServerPort(serverPreferencesForm.getServerPort());
state.setContextSize(serverPreferencesForm.getContextSize());
state.setThreads(serverPreferencesForm.getThreads());
state.setAdditionalParameters(serverPreferencesForm.getAdditionalParameters());
state.setAdditionalBuildParameters(serverPreferencesForm.getAdditionalBuildParameters());
state.setAdditionalEnvironmentVariables(
llamaServerPreferencesForm.getAdditionalEnvironmentVariables());
serverPreferencesForm.getAdditionalEnvironmentVariables());
var modelPreferencesForm = llamaServerPreferencesForm.getLlamaModelPreferencesForm();
state.setTopK(settingsState.getTopK());
state.setTopP(settingsState.getTopP());
state.setMinP(settingsState.getMinP());
state.setRepeatPenalty(settingsState.getRepeatPenalty());
var modelPreferencesForm = serverPreferencesForm.getLlamaModelPreferencesForm();
state.setCustomLlamaModelPath(modelPreferencesForm.getCustomLlamaModelPath());
state.setHuggingFaceModel(modelPreferencesForm.getSelectedModel());
state.setUseCustomModel(modelPreferencesForm.isUseCustomLlamaModel());
state.setHuggingFaceModel(modelPreferencesForm.getSelectedModel());
state.setLocalModelPromptTemplate(modelPreferencesForm.getPromptTemplate());
state.setLocalModelInfillPromptTemplate(modelPreferencesForm.getInfillPromptTemplate());
state.setCodeCompletionsEnabled(codeCompletionConfigurationForm.isCodeCompletionsEnabled());
state.setCodeCompletionsEnabled(settingsState.isCodeCompletionsEnabled());
return state;
}
public void resetForm() {
var state = LlamaSettings.getCurrentState();
llamaServerPreferencesForm.resetForm(state);
llamaRequestPreferencesForm.resetForm(state);
codeCompletionConfigurationForm.setCodeCompletionsEnabled(state.isCodeCompletionsEnabled());
serverPreferencesForm.resetForm(settingsState);
}
public LlamaServerPreferencesForm getLlamaServerPreferencesForm() {
return llamaServerPreferencesForm;
public void resetForm(LlamaSettingsState newState) {
serverPreferencesForm.resetForm(newState);
}
private void init() {
setLayout(new BorderLayout());
add(FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator("Code Completions"))
.addComponent(withEmptyLeftBorder(codeCompletionConfigurationForm.getForm()))
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.service.llama.serverPreferences.title")))
.addComponent(llamaServerPreferencesForm.getForm())
.addComponent(new TitledSeparator("Request Preferences"))
.addComponent(withEmptyLeftBorder(llamaRequestPreferencesForm.getForm()))
.addComponentFillVertically(new JPanel(), 0)
.getPanel());
private void setTabState(int tabIndex, boolean enabled, String disabledTooltip) {
tabbedPane.setEnabledAt(tabIndex, enabled);
tabbedPane.setToolTipTextAt(tabIndex, enabled ? null : disabledTooltip);
}
}
}

View file

@ -1,28 +0,0 @@
package ee.carlrobert.codegpt.settings.service.llama.form;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.AsyncProcessIcon;
import javax.swing.JComponent;
import javax.swing.JPanel;
public class ServerProgressPanel extends JPanel {
private final JBLabel label = new JBLabel();
private final AsyncProcessIcon loadingSpinner = new AsyncProcessIcon("sign_in_spinner");
public void displayText(String text) {
label.setText(text);
removeAll();
add(loadingSpinner);
add(label);
revalidate();
repaint();
}
public void displayComponent(JComponent component) {
removeAll();
add(component);
revalidate();
repaint();
}
}

View file

@ -286,10 +286,6 @@ public class ModelComboBoxAction extends ComboBoxAction {
}
private String getLlamaCppPresentationText() {
var llamaSettingState = LlamaSettings.getCurrentState();
if (!llamaSettingState.isRunLocalServer()) {
return format("Remote %s", llamaSettingState.getRemoteModelPromptTemplate());
}
return getSelectedHuggingFace();
}

View file

@ -14,11 +14,9 @@ import ee.carlrobert.codegpt.completions.llama.LlamaServerStartupParams
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings.*
import ee.carlrobert.codegpt.settings.service.llama.form.ServerProgressPanel
import ee.carlrobert.codegpt.completions.llama.logging.NoOpLoggingStrategy
import ee.carlrobert.codegpt.ui.OverlayUtil.showNotification
import ee.carlrobert.codegpt.ui.OverlayUtil.stickyNotification
import java.util.function.Consumer
private const val STARTING = "settingsConfigurable.service.llama.progress.startingServer"
private const val RUNNING = "settingsConfigurable.service.llama.progress.serverRunning"
@ -62,10 +60,10 @@ abstract class LlamaServerToggleActions(
}
private fun start(serverName: String, llamaServerAgent: LlamaServerAgent) {
notification = stickyNotification(formatMsg(STARTING, serverName),
notification = stickyNotification(
formatMsg(STARTING, serverName),
createSimpleExpiring(CodeGPTBundle.get(STOP)) { stop(serverName, llamaServerAgent) })
val serverProgressPanel = ServerProgressPanel()
llamaServerAgent.setActiveServerProgressPanel(serverProgressPanel)
val settings = LlamaSettings.getInstance().state
llamaServerAgent.startAgent(
LlamaServerStartupParams(
@ -73,20 +71,18 @@ abstract class LlamaServerToggleActions(
settings.contextSize,
settings.threads,
settings.serverPort,
getAdditionalParametersList(settings.additionalParameters),
getAdditionalParametersList(settings.additionalBuildParameters),
getAdditionalEnvironmentVariablesMap(settings.additionalEnvironmentVariables)
LlamaSettings.getAdditionalParametersList(settings.additionalParameters),
LlamaSettings.getAdditionalParametersList(settings.additionalBuildParameters),
LlamaSettings.getAdditionalEnvironmentVariablesMap(settings.additionalEnvironmentVariables)
),
serverProgressPanel,
NoOpLoggingStrategy,
{
notification?.expire()
notification = notification(RUNNING, false, serverName, llamaServerAgent)
},
{
Consumer<ServerProgressPanel> { _: ServerProgressPanel ->
notification?.expire()
notification = notification(STOPPED, true, serverName, llamaServerAgent)
}
notification?.expire()
notification = notification(STOPPED, true, serverName, llamaServerAgent)
})
}
@ -98,13 +94,12 @@ abstract class LlamaServerToggleActions(
}
private fun notification(id: String, nextStart: Boolean, serverName: String, llamaServerAgent: LlamaServerAgent) =
showNotification(formatMsg(id, serverName),
showNotification(
formatMsg(id, serverName),
createSimpleExpiring(CodeGPTBundle.get(if (nextStart) START else STOP)) {
if (nextStart) start(serverName, llamaServerAgent) else stop(serverName, llamaServerAgent)
})
// "Starting server..." -> "Starting server: CodeLlama 7B 4-bit ..."
// "Stopped server" -> "Stopped server: CodeLlama 7B 4-bit"
private fun formatMsg(id: String, serverName: String): String {
val msg = CodeGPTBundle.get(id)
val points = msg.endsWith("...")
@ -112,7 +107,8 @@ abstract class LlamaServerToggleActions(
}
override fun update(e: AnActionEvent) {
val llamaRunnable = isRunnable(LlamaSettings.getInstance().state.huggingFaceModel)
val llamaRunnable =
LlamaSettings.isRunnable(LlamaSettings.getInstance().state.huggingFaceModel)
val serverRunning = llamaRunnable && service<LlamaServerAgent>().isServerRunning
val toggle = llamaRunnable && serverRunning != startServer
e.presentation.isVisible = toggle

View file

@ -144,9 +144,6 @@ object CodeCompletionRequestFactory {
}
private fun getLlamaInfillPromptTemplate(settings: LlamaSettingsState): InfillPromptTemplate {
if (!settings.isRunLocalServer) {
return settings.remoteModelInfillPromptTemplate
}
if (settings.isUseCustomModel) {
return settings.localModelInfillPromptTemplate
}

View file

@ -50,14 +50,10 @@ class LlamaRequestFactory : BaseRequestFactory() {
private fun getPromptTemplate(): PromptTemplate {
val settings = service<LlamaSettings>().state
return if (settings.isRunLocalServer) {
if (settings.isUseCustomModel)
settings.localModelPromptTemplate
else
LlamaModel.findByHuggingFaceModel(settings.huggingFaceModel).promptTemplate
} else {
settings.remoteModelPromptTemplate
}
return if (settings.isUseCustomModel)
settings.localModelPromptTemplate
else
LlamaModel.findByHuggingFaceModel(settings.huggingFaceModel).promptTemplate
}
private fun buildLlamaRequest(

View file

@ -0,0 +1,212 @@
package ee.carlrobert.codegpt.completions.llama
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.ProcessOutputType
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.util.Key
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.CodeGPTPlugin
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.BUILD_CONFIGURATION
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.BUILD_DIRECTORY
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.BUILD_PARALLEL_JOBS
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.PROGRESS_CMAKE_BUILD
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.PROGRESS_CMAKE_SETUP
import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.CopyOnWriteArrayList
class BuildPhaseManager(
private val infoLogger: (String) -> Unit,
private val errorLogger: (String) -> Unit,
private val phaseUpdater: (String) -> Unit,
private val progressStopper: () -> Unit
) {
companion object {
private val logger = thisLogger()
private fun getAbsoluteBuildPath(): String {
return Paths.get(CodeGPTPlugin.getLlamaSourcePath(), BUILD_DIRECTORY).toAbsolutePath()
.toString()
}
private fun isCMakeCacheConflict(buildPath: String): Boolean {
val cacheFile = File(buildPath, "CMakeCache.txt")
if (!cacheFile.exists()) return false
try {
val cacheContent = cacheFile.readText()
val currentSourcePath = CodeGPTPlugin.getLlamaSourcePath()
val homeDirectoryRegex = """CMAKE_HOME_DIRECTORY:INTERNAL=(.+)""".toRegex()
val match = homeDirectoryRegex.find(cacheContent)
return match?.groupValues?.get(1)?.let { cachedPath ->
!Paths.get(cachedPath).toAbsolutePath()
.equals(Paths.get(currentSourcePath).toAbsolutePath())
} == true
} catch (e: Exception) {
logger.warn("Failed to read CMake cache file", e)
return false
}
}
private fun cleanupCMakeCache(buildPath: String) {
try {
val buildDir = File(buildPath)
if (buildDir.exists()) {
logger.info("Cleaning up CMake cache due to path mismatch: $buildPath")
Files.walk(buildDir.toPath())
.sorted(Comparator.reverseOrder())
.map { it.toFile() }
.forEach { it.delete() }
buildDir.mkdirs()
}
} catch (e: Exception) {
logger.warn("Failed to cleanup CMake cache", e)
}
}
}
@Throws(ExecutionException::class)
fun executeCMakeSetup(
params: LlamaServerStartupParams,
indicator: ProgressIndicator,
onSuccess: () -> Unit,
onError: (String) -> Unit
): OSProcessHandler {
indicator.text = CodeGPTBundle.get("llama.build.cmake.setup")
indicator.fraction = PROGRESS_CMAKE_SETUP
phaseUpdater(CodeGPTBundle.get("llama.build.cmake.setup"))
infoLogger("=== " + CodeGPTBundle.get("llama.build.startingBuild") + " ===")
infoLogger(CodeGPTBundle.get("llama.build.phase.setup"))
infoLogger(CodeGPTBundle.get("llamaServerAgent.buildingProject.description"))
val buildPath = getAbsoluteBuildPath()
if (isCMakeCacheConflict(buildPath)) {
infoLogger(CodeGPTBundle.get("llama.build.cache.cleanup"))
cleanupCMakeCache(buildPath)
}
val handler = OSProcessHandler(getCMakeSetupCommandLine(params))
handler.addProcessListener(object : ProcessAdapter() {
private val errorLines = CopyOnWriteArrayList<String>()
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
if (ProcessOutputType.isStderr(outputType)) {
errorLines.add(event.text)
errorLogger(event.text.trim())
return
}
logger.info(event.text)
infoLogger(event.text.trim())
}
override fun processTerminated(event: ProcessEvent) {
val exitCode = event.exitCode
val exitMessage = "CMake setup exited with code $exitCode"
logger.info(exitMessage)
if (exitCode != 0) {
errorLogger(exitMessage)
phaseUpdater(CodeGPTBundle.get("llama.build.phase.setupFailed"))
progressStopper()
onError(errorLines.joinToString(","))
} else {
infoLogger(exitMessage)
onSuccess()
}
}
})
return handler
}
@Throws(ExecutionException::class)
fun executeCMakeBuild(
params: LlamaServerStartupParams,
indicator: ProgressIndicator,
onSuccess: () -> Unit,
onError: (String) -> Unit
): OSProcessHandler {
indicator.text = CodeGPTBundle.get("llama.build.cmake.build")
indicator.fraction = PROGRESS_CMAKE_BUILD
phaseUpdater(CodeGPTBundle.get("llama.build.cmake.build"))
infoLogger(CodeGPTBundle.get("llama.build.phase.build"))
val handler = OSProcessHandler(getCMakeBuildCommandLine(params))
handler.addProcessListener(object : ProcessAdapter() {
private val errorLines = CopyOnWriteArrayList<String>()
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
if (ProcessOutputType.isStderr(outputType)) {
errorLines.add(event.text)
errorLogger(event.text.trim())
return
}
logger.info(event.text)
infoLogger(event.text.trim())
}
override fun processTerminated(event: ProcessEvent) {
val exitCode = event.exitCode
val exitMessage = "Server build exited with code $exitCode"
logger.info(exitMessage)
if (exitCode != 0) {
errorLogger(exitMessage)
phaseUpdater(CodeGPTBundle.get("llama.build.phase.buildFailed"))
progressStopper()
onError(errorLines.joinToString(","))
} else {
infoLogger(exitMessage)
onSuccess()
}
}
})
return handler
}
private fun getCMakeSetupCommandLine(params: LlamaServerStartupParams): GeneralCommandLine {
val absoluteBuildPath = getAbsoluteBuildPath()
return GeneralCommandLine().apply {
charset = StandardCharsets.UTF_8
exePath = "cmake"
withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath())
addParameters("-B", absoluteBuildPath)
withEnvironment(params.additionalEnvironmentVariables())
isRedirectErrorStream = false
}
}
private fun getCMakeBuildCommandLine(params: LlamaServerStartupParams): GeneralCommandLine {
val absoluteBuildPath = getAbsoluteBuildPath()
return GeneralCommandLine().apply {
charset = StandardCharsets.UTF_8
exePath = "cmake"
withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath())
addParameters(
"--build",
absoluteBuildPath,
"--config",
BUILD_CONFIGURATION,
"-j",
BUILD_PARALLEL_JOBS.toString()
)
withEnvironment(params.additionalEnvironmentVariables())
isRedirectErrorStream = false
}
}
}

View file

@ -0,0 +1,17 @@
package ee.carlrobert.codegpt.completions.llama
object LlamaConstants {
const val PROGRESS_CMAKE_SETUP = 0.0
const val PROGRESS_CMAKE_BUILD = 0.33
const val PROGRESS_SERVER_START = 0.66
const val BUILD_PARALLEL_JOBS = 4
const val BUILD_CONFIGURATION = "Release"
const val BUILD_DIRECTORY = "build"
const val SERVER_EXECUTABLE_PATH = "./build/bin/llama-server"
const val SERVER_LISTENING_MESSAGE = "server is listening"
const val MAX_LOG_ENTRIES = 10000
const val MAX_LOG_SESSIONS = 5
}

View file

@ -0,0 +1,119 @@
package ee.carlrobert.codegpt.completions.llama
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.openapi.diagnostic.Logger
import com.intellij.openapi.util.Key
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.CodeGPTPlugin
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.SERVER_EXECUTABLE_PATH
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.SERVER_LISTENING_MESSAGE
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
import java.nio.charset.StandardCharsets
import java.util.concurrent.CopyOnWriteArrayList
import com.intellij.openapi.application.runInEdt
class LlamaProcessManager(
private val infoLogger: (String) -> Unit,
private val errorLogger: (String) -> Unit
) {
companion object {
private val LOG = Logger.getInstance(LlamaProcessManager::class.java)
}
private var serverProcessHandler: OSProcessHandler? = null
@Throws(ExecutionException::class)
fun startServer(
params: LlamaServerStartupParams,
onSuccess: () -> Unit,
onError: (String) -> Unit
) {
LOG.info("Booting up llama server")
infoLogger("Phase 3: Starting Server")
infoLogger("=== Starting Llama Server ===")
infoLogger(CodeGPTBundle.get("llamaServerAgent.serverBootup.description"))
serverProcessHandler = OSProcessHandler.Silent(getServerCommandLine(params)).apply {
addProcessListener(createServerProcessListener(
params.port(),
onSuccess,
onError
))
startNotify()
}
}
fun stopServer() {
serverProcessHandler?.let {
if (!it.isProcessTerminated) {
it.destroyProcess()
}
}
}
fun isServerRunning(): Boolean {
return serverProcessHandler?.let {
it.isStartNotified && !it.isProcessTerminated
} == true
}
private fun createServerProcessListener(
port: Int,
onSuccess: () -> Unit,
onError: (String) -> Unit
): ProcessListener {
return object : ProcessAdapter() {
private val errorLines = CopyOnWriteArrayList<String>()
override fun processTerminated(event: ProcessEvent) {
val message = "Server stopped with code ${event.exitCode}"
LOG.info(message)
if (event.exitCode != 0) {
errorLogger(message)
onError(errorLines.joinToString(","))
} else {
infoLogger(message)
}
}
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
LOG.debug(event.text)
infoLogger(event.text.trim())
if (event.text.contains(SERVER_LISTENING_MESSAGE)) {
val successMessage = "Server up and running!"
LOG.info(successMessage)
infoLogger(successMessage)
LlamaSettings.getCurrentState().serverPort = port
runInEdt { onSuccess() }
}
}
}
}
private fun getServerCommandLine(params: LlamaServerStartupParams): GeneralCommandLine {
return GeneralCommandLine().apply {
charset = StandardCharsets.UTF_8
exePath = SERVER_EXECUTABLE_PATH
withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath())
addParameters(
"-m", params.modelPath(),
"-c", params.contextLength().toString(),
"--port", params.port().toString(),
"-t", params.threads().toString()
)
addParameters(params.additionalRunParameters())
withEnvironment(params.additionalEnvironmentVariables())
isRedirectErrorStream = false
}
}
}

View file

@ -0,0 +1,246 @@
package ee.carlrobert.codegpt.completions.llama
import com.intellij.execution.ExecutionException
import com.intellij.execution.process.OSProcessHandler
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.util.application
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.PROGRESS_SERVER_START
import ee.carlrobert.codegpt.completions.llama.logging.NoOpLoggingStrategy
import ee.carlrobert.codegpt.completions.llama.logging.ServerLoggingStrategy
import ee.carlrobert.codegpt.services.llama.ServerLogsManager
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm
@Service
class LlamaServerAgent : Disposable {
companion object {
private val logger = thisLogger()
}
private val buildPhaseManager: BuildPhaseManager = BuildPhaseManager(
infoLogger = { message -> logToConsole(message, false, true) },
errorLogger = { message -> logToConsole(message, true, true) },
phaseUpdater = { phase -> updatePhase(phase) },
progressStopper = { stopProgress() }
)
private val processManager: LlamaProcessManager = LlamaProcessManager(
infoLogger = { message -> logToConsole(message, false, false) },
errorLogger = { message -> logToConsole(message, true, false) }
)
@Volatile
private var stoppedByUser: Boolean = false
@Volatile
private var buildInProgress: Boolean = false
@Volatile
private var currentProgressIndicator: ProgressIndicator? = null
@Volatile
private var setupProcessHandler: OSProcessHandler? = null
@Volatile
private var buildProcessHandler: OSProcessHandler? = null
private var loggingStrategy: ServerLoggingStrategy = NoOpLoggingStrategy
private var settingsForm: LlamaSettingsForm? = null
fun startAgent(
params: LlamaServerStartupParams,
loggingStrategy: ServerLoggingStrategy = NoOpLoggingStrategy,
onSuccess: Runnable,
onServerStopped: Runnable
) {
this.loggingStrategy = loggingStrategy
application.service<ServerLogsManager>().startNewSession()
ProgressManager.getInstance()
.run(object : Task.Backgroundable(null, CodeGPTBundle.get("llama.build.startingBuild"), true) {
override fun run(indicator: ProgressIndicator) {
currentProgressIndicator = indicator
indicator.isIndeterminate = false
buildServer(params, indicator, onSuccess, onServerStopped)
}
})
}
private fun buildServer(
params: LlamaServerStartupParams,
indicator: ProgressIndicator,
onSuccess: Runnable,
onServerStopped: Runnable
) {
try {
stoppedByUser = false
buildInProgress = true
loggingStrategy.startProgress()
if (indicator.isCanceled) {
stoppedByUser = true
return
}
setupProcessHandler = buildPhaseManager.executeCMakeSetup(params, indicator, {
if (stoppedByUser) {
buildInProgress = false
clearProcessHandlers()
logToConsole(CodeGPTBundle.get("llama.server.buildStopped"), false, true)
onServerStopped.run()
return@executeCMakeSetup
}
if (indicator.isCanceled) {
stoppedByUser = true
return@executeCMakeSetup
}
try {
buildProcessHandler = buildPhaseManager.executeCMakeBuild(params, indicator, {
if (stoppedByUser) {
buildInProgress = false
logToConsole(CodeGPTBundle.get("llama.server.buildStopped"), false, true)
onServerStopped.run()
return@executeCMakeBuild
}
indicator.text = CodeGPTBundle.get("llama.server.starting")
indicator.fraction = PROGRESS_SERVER_START
loggingStrategy.setPhase(CodeGPTBundle.get("llama.server.starting"))
if (indicator.isCanceled) {
stoppedByUser = true
return@executeCMakeBuild
}
try {
processManager.startServer(params, {
loggingStrategy.apply {
setPhase(CodeGPTBundle.get("llama.server.running"))
indicator.text = CodeGPTBundle.get("llama.server.running")
indicator.fraction = 1.0
stopProgress()
}
settingsForm?.refreshServerStatus()
buildInProgress = false
clearProcessHandlers()
onSuccess.run()
}) { errorText ->
showServerError(errorText, onServerStopped)
}
} catch (e: ExecutionException) {
showServerError(e.message ?: "Unknown error", onServerStopped)
}
}) { errorText ->
showServerError(errorText, onServerStopped)
}
buildProcessHandler?.startNotify()
} catch (e: ExecutionException) {
showServerError(e.message ?: "Unknown error", onServerStopped)
}
}) { errorText ->
showServerError(errorText, onServerStopped)
}
setupProcessHandler?.startNotify()
} catch (e: ExecutionException) {
showServerError(e.message ?: "Unknown error", onServerStopped)
}
}
fun stopAgent() {
stoppedByUser = true
buildInProgress = false
currentProgressIndicator?.cancel()
setupProcessHandler?.let { handler ->
if (!handler.isProcessTerminated) {
handler.destroyProcess()
logToConsole(CodeGPTBundle.get("llama.server.stopping.cmake"), false, true)
}
}
buildProcessHandler?.let { handler ->
if (!handler.isProcessTerminated) {
handler.destroyProcess()
logToConsole(CodeGPTBundle.get("llama.server.stopping.build"), false, true)
}
}
processManager.stopServer()
currentProgressIndicator = null
setupProcessHandler = null
buildProcessHandler = null
}
val isServerRunning: Boolean
get() = processManager.isServerRunning()
val isBuildInProgress: Boolean
get() = buildInProgress
private fun showServerError(errorText: String, onServerStopped: Runnable) {
buildInProgress = false
clearProcessHandlers()
loggingStrategy.apply {
setPhase(CodeGPTBundle.get("llama.server.startupFailed"))
stopProgress()
}
logToConsole(CodeGPTBundle.get("llama.error.server.startupWithDetails", errorText), true, false)
onServerStopped.run()
val enhancedErrorMessage = CodeGPTBundle.get("llama.error.server.startup", errorText)
logger.info(enhancedErrorMessage)
logToConsole(enhancedErrorMessage, true, false)
}
private fun clearProcessHandlers() {
currentProgressIndicator = null
setupProcessHandler = null
buildProcessHandler = null
}
fun setSettingsForm(settingsForm: LlamaSettingsForm?) {
this.settingsForm = settingsForm
}
@Synchronized
private fun logToConsole(message: String, isError: Boolean, isBuildLog: Boolean = false) {
application.service<ServerLogsManager>().log(message, isError)
loggingStrategy.logMessage(message, isError, isBuildLog)
}
override fun dispose() {
try {
stopAgent()
} catch (e: Exception) {
logger.error("Error during disposal", e)
}
}
private fun updatePhase(phase: String) {
loggingStrategy.setPhase(phase)
}
private fun stopProgress() {
loggingStrategy.stopProgress()
}
}

View file

@ -0,0 +1,110 @@
package ee.carlrobert.codegpt.completions.llama
import com.intellij.openapi.application.runInEdt
import com.intellij.ui.JBColor
import com.intellij.util.ui.JBUI
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentLinkedDeque
import javax.swing.JTextPane
import javax.swing.text.BadLocationException
import javax.swing.text.SimpleAttributeSet
import javax.swing.text.StyleConstants
class SimpleConsolePanel : JTextPane() {
private val lineBuffer = ConcurrentLinkedDeque<String>()
private val backgroundColor = JBColor.namedColor("Console.background", Color(43, 43, 43))
private val normalTextColor = JBColor.namedColor("Console.foreground", Color(204, 204, 204))
private val errorTextColor = JBColor.namedColor("Console.errorForeground", Color(255, 102, 102))
private val timestampColor = JBColor.namedColor("Console.grayForeground", Color(153, 153, 153))
private val normalAttributes = SimpleAttributeSet()
private val errorAttributes = SimpleAttributeSet()
private val timestampAttributes = SimpleAttributeSet()
companion object {
private const val MAX_LINES = 1000
private val TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss")
}
init {
setupConsoleAppearance()
setupStyles()
}
private fun setupConsoleAppearance() {
isEditable = false
background = backgroundColor
foreground = normalTextColor
font = JBUI.Fonts.create(Font.MONOSPACED, JBUI.scaleFontSize(12.0f).toInt())
putClientProperty(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON
)
putClientProperty(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON
)
}
private fun setupStyles() {
val baseFont = JBUI.Fonts.create(Font.MONOSPACED, JBUI.scaleFontSize(12.0f).toInt())
val smallFont = JBUI.Fonts.create(Font.MONOSPACED, JBUI.scaleFontSize(11.0f).toInt())
StyleConstants.setForeground(normalAttributes, normalTextColor)
StyleConstants.setFontFamily(normalAttributes, baseFont.family)
StyleConstants.setFontSize(normalAttributes, baseFont.size)
StyleConstants.setForeground(errorAttributes, errorTextColor)
StyleConstants.setFontFamily(errorAttributes, baseFont.family)
StyleConstants.setFontSize(errorAttributes, baseFont.size)
StyleConstants.setBold(errorAttributes, true)
StyleConstants.setForeground(timestampAttributes, timestampColor)
StyleConstants.setFontFamily(timestampAttributes, smallFont.family)
StyleConstants.setFontSize(timestampAttributes, smallFont.size)
}
fun appendText(message: String, isError: Boolean = false) {
try {
val timestamp = LocalDateTime.now().format(TIMESTAMP_FORMAT)
val fullMessage = "[$timestamp] $message"
lineBuffer.add(fullMessage)
while (lineBuffer.size > MAX_LINES) {
lineBuffer.pollFirst()
removeFirstLine()
}
val doc = styledDocument
val startOffset = doc.length
doc.insertString(startOffset, "[$timestamp] ", timestampAttributes)
val messageStyle = if (isError) errorAttributes else normalAttributes
doc.insertString(doc.length, "$message\n", messageStyle)
caretPosition = doc.length
} catch (e: BadLocationException) {
val timestamp = LocalDateTime.now().format(TIMESTAMP_FORMAT)
text = "$text[$timestamp] $message\n"
caretPosition = text.length
}
}
private fun removeFirstLine() {
try {
val doc = styledDocument
val text = doc.getText(0, doc.length)
val firstNewline = text.indexOf('\n')
if (firstNewline >= 0) {
doc.remove(0, firstNewline + 1)
}
} catch (e: BadLocationException) {
// Ignore errors when removing lines
}
}
fun clearConsole() {
runInEdt {
lineBuffer.clear()
text = ""
}
}
}

View file

@ -0,0 +1,19 @@
package ee.carlrobert.codegpt.completions.llama.logging
object NoOpLoggingStrategy : ServerLoggingStrategy {
override fun logMessage(message: String, isError: Boolean, isBuildLog: Boolean) {
// No-op: silent logging for headless operations
}
override fun setPhase(phase: String) {
// No-op
}
override fun startProgress() {
// No-op
}
override fun stopProgress() {
// No-op
}
}

View file

@ -0,0 +1,8 @@
package ee.carlrobert.codegpt.completions.llama.logging
interface ServerLoggingStrategy {
fun logMessage(message: String, isError: Boolean, isBuildLog: Boolean = false)
fun setPhase(phase: String)
fun startProgress()
fun stopProgress()
}

View file

@ -0,0 +1,24 @@
package ee.carlrobert.codegpt.completions.llama.logging
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm
class SettingsFormLoggingStrategy(
private val settingsForm: LlamaSettingsForm
) : ServerLoggingStrategy {
override fun logMessage(message: String, isError: Boolean, isBuildLog: Boolean) {
settingsForm.logToConsole(message, isError, isBuildLog)
}
override fun setPhase(phase: String) {
settingsForm.updateServerStatusWithPhase(phase)
}
override fun startProgress() {
settingsForm.updateServerStatusWithPhase("Initializing...")
}
override fun stopProgress() {
settingsForm.refreshServerStatus()
}
}

View file

@ -61,10 +61,6 @@ object CredentialsStore {
override val value: String = "ANTHROPIC_API_KEY"
}
data object LlamaApiKey : CredentialKey() {
override val value: String = "LLAMA_API_KEY"
}
data object GoogleApiKey : CredentialKey() {
override val value: String = "GOOGLE_API_KEY"
}

View file

@ -0,0 +1,129 @@
package ee.carlrobert.codegpt.services
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import java.io.IOException
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import kotlin.Throws
@Service(Service.Level.PROJECT)
class ExecutableRunnerService {
private val executorService = Executors.newCachedThreadPool()
private val currentProcess = AtomicReference<Process?>()
private val isRunning = AtomicBoolean(false)
interface ProcessOutputHandler {
fun onStandardOutput(line: String)
fun onErrorOutput(line: String)
fun onProcessStarted()
fun onProcessFinished(exitCode: Int)
fun onProcessFailed(exception: Exception)
}
fun runExecutable(
executable: String,
arguments: List<String>,
workingDirectory: Path? = null,
outputHandler: ProcessOutputHandler
): CompletableFuture<Int> {
return if (isRunning.compareAndSet(false, true)) {
CompletableFuture.supplyAsync({
try {
outputHandler.onProcessStarted()
executeProcess(executable, arguments, workingDirectory, outputHandler)
} catch (e: Exception) {
LOG.error("Failed to execute process", e)
outputHandler.onProcessFailed(e)
-1
} finally {
isRunning.set(false)
currentProcess.set(null)
}
}, executorService)
} else {
CompletableFuture.completedFuture(-1)
}
}
@Throws(IOException::class, InterruptedException::class)
private fun executeProcess(
executable: String,
arguments: List<String>,
workingDirectory: Path?,
outputHandler: ProcessOutputHandler
): Int {
val command = mutableListOf(executable).apply { addAll(arguments) }
val processBuilder = ProcessBuilder(command).apply {
workingDirectory?.let { directory(it.toFile()) }
redirectErrorStream(false)
}
LOG.info("Executing command: ${command.joinToString(" ")}")
val process = processBuilder.start()
currentProcess.set(process)
val streamReadingFuture = ProcessStreamReader.readProcessStreams(process, outputHandler)
val exitCode = process.waitFor()
streamReadingFuture.join()
outputHandler.onProcessFinished(exitCode)
return exitCode
}
fun isRunning(): Boolean = isRunning.get()
fun stopCurrentProcess() {
currentProcess.get()?.let { process ->
if (process.isAlive) {
process.destroyForcibly()
isRunning.set(false)
}
}
}
fun runCommand(
command: String,
outputHandler: ProcessOutputHandler
): CompletableFuture<Int> {
return runCommand(command, null, outputHandler)
}
fun runCommand(
command: String,
workingDirectory: Path?,
outputHandler: ProcessOutputHandler
): CompletableFuture<Int> {
val parts = command.split("\\s+".toRegex())
if (parts.isEmpty()) {
return CompletableFuture.completedFuture(-1)
}
val executable = parts[0]
val arguments = parts.drop(1)
return runExecutable(executable, arguments, workingDirectory, outputHandler)
}
fun dispose() {
stopCurrentProcess()
executorService.shutdown()
}
companion object {
private val LOG = Logger.getInstance(ExecutableRunnerService::class.java)
@JvmStatic
fun getInstance(project: Project): ExecutableRunnerService = project.service()
}
}

View file

@ -0,0 +1,49 @@
package ee.carlrobert.codegpt.services
import com.intellij.openapi.diagnostic.thisLogger
import ee.carlrobert.codegpt.services.ExecutableRunnerService.ProcessOutputHandler
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.util.concurrent.CompletableFuture
object ProcessStreamReader {
private val logger = thisLogger()
@JvmStatic
fun readStreamAsync(
inputStream: InputStream,
isError: Boolean,
outputHandler: ProcessOutputHandler
): CompletableFuture<Void?> {
return CompletableFuture.runAsync {
try {
BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader.lineSequence().forEach { line ->
if (isError) {
outputHandler.onErrorOutput(line)
} else {
outputHandler.onStandardOutput(line)
}
}
}
} catch (e: IOException) {
val streamType = if (isError) "stderr" else "stdout"
logger.warn("Error reading $streamType", e)
}
}
}
@JvmStatic
fun readProcessStreams(
process: Process,
outputHandler: ProcessOutputHandler
): CompletableFuture<Void?> {
val stdoutFuture = readStreamAsync(process.inputStream, false, outputHandler)
val stderrFuture = readStreamAsync(process.errorStream, true, outputHandler)
return CompletableFuture.allOf(stdoutFuture, stderrFuture)
}
}

View file

@ -0,0 +1,103 @@
package ee.carlrobert.codegpt.services.llama
import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.MAX_LOG_ENTRIES
import ee.carlrobert.codegpt.completions.llama.LlamaConstants.MAX_LOG_SESSIONS
import java.time.LocalDateTime
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
@Service
class ServerLogsManager {
private val sessions = ConcurrentHashMap<String, LogSession>()
private val sessionOrder = CopyOnWriteArrayList<String>()
@Volatile
private var currentSessionId: String? = null
@Synchronized
fun startNewSession(): LogSession {
val sessionId = UUID.randomUUID().toString()
val session = LogSession(sessionId, LocalDateTime.now())
sessions[sessionId] = session
sessionOrder.add(0, sessionId)
currentSessionId = sessionId
while (sessionOrder.size > MAX_LOG_SESSIONS) {
val oldestId = sessionOrder.removeAt(sessionOrder.size - 1)
sessions.remove(oldestId)
}
return session
}
fun getCurrentSession(): LogSession {
val sessionId = currentSessionId
return if (sessionId == null || !sessions.containsKey(sessionId)) {
startNewSession()
} else {
sessions[sessionId]!!
}
}
fun endCurrentSession() {
currentSessionId?.let { id ->
sessions[id]?.endTime = LocalDateTime.now()
}
}
fun log(message: String, isError: Boolean) {
val session = getCurrentSession()
val contentType = if (isError) {
ConsoleViewContentType.ERROR_OUTPUT
} else {
ConsoleViewContentType.NORMAL_OUTPUT
}
val entry = LogEntry(LocalDateTime.now(), message, contentType)
session.entries.add(entry)
while (session.entries.size > MAX_LOG_ENTRIES) {
session.entries.removeAt(0)
}
}
fun getAllSessions(): List<LogSession> {
return sessionOrder.mapNotNull { id -> sessions[id] }
}
fun getSessionLogs(sessionId: String): List<LogEntry> {
return sessions[sessionId]?.entries?.toList() ?: emptyList()
}
@Synchronized
fun clearAll() {
sessions.clear()
sessionOrder.clear()
currentSessionId = null
}
data class LogSession(
val id: String,
val startTime: LocalDateTime,
var endTime: LocalDateTime? = null,
val entries: MutableList<LogEntry> = CopyOnWriteArrayList()
)
data class LogEntry(
val timestamp: LocalDateTime,
val message: String,
val contentType: ConsoleViewContentType
)
companion object {
@JvmStatic
fun getInstance(): ServerLogsManager = ApplicationManager.getApplication().service()
}
}

View file

@ -1,40 +1,46 @@
package ee.carlrobert.codegpt.settings.service
import com.intellij.openapi.components.service
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.options.Configurable
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LlamaApiKey
import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential
import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm
import javax.swing.JComponent
class LlamaServiceConfigurable : Configurable {
class LlamaServiceConfigurable : Configurable, Disposable {
private lateinit var component: LlamaSettingsForm
private var form: LlamaSettingsForm? = null
override fun getDisplayName(): String {
return "ProxyAI: Custom Service"
return "CodeGPT: Llama"
}
override fun createComponent(): JComponent {
component = LlamaSettingsForm(service<LlamaSettings>().state)
return component
override fun createComponent(): JComponent? {
if (form == null) {
form = LlamaSettingsForm(LlamaSettings.getCurrentState())
ApplicationManager.getApplication().getService(LlamaServerAgent::class.java)
.setSettingsForm(form)
}
return form
}
override fun isModified(): Boolean {
return component.getCurrentState() != service<LlamaSettings>().state
|| component.llamaServerPreferencesForm.getApiKey() != getCredential(LlamaApiKey)
val currentForm = form ?: return false
return LlamaSettings.getInstance().isModified(currentForm)
}
override fun apply() {
service<GeneralSettings>().state.selectedService = ServiceType.LLAMA_CPP
setCredential(LlamaApiKey, component.llamaServerPreferencesForm.getApiKey())
service<LlamaSettings>().loadState(component.currentState)
val currentForm = form ?: return
LlamaSettings.getInstance().loadState(currentForm.currentState)
}
override fun reset() {
component.resetForm()
form?.resetForm(LlamaSettings.getCurrentState())
}
override fun dispose() {
form = null
}
}

View file

@ -0,0 +1,321 @@
package ee.carlrobert.codegpt.settings.service.llama.form
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.ui.MessageType
import com.intellij.ui.PortField
import com.intellij.ui.TitledSeparator
import com.intellij.ui.components.JBTextField
import com.intellij.ui.components.fields.IntegerField
import com.intellij.util.ui.FormBuilder
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent
import ee.carlrobert.codegpt.completions.llama.LlamaServerStartupParams
import ee.carlrobert.codegpt.completions.llama.PromptTemplate
import ee.carlrobert.codegpt.completions.llama.logging.NoOpLoggingStrategy
import ee.carlrobert.codegpt.completions.llama.logging.SettingsFormLoggingStrategy
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm
import ee.carlrobert.codegpt.ui.OverlayUtil
import ee.carlrobert.codegpt.ui.UIUtil
import javax.swing.Box
import javax.swing.BoxLayout
import javax.swing.JButton
import javax.swing.JPanel
class LlamaServerPreferencesForm(settings: LlamaSettingsState, private val parentForm: LlamaSettingsForm? = null) {
val llamaModelPreferencesForm = LlamaModelPreferencesForm()
private val portField: PortField
private val maxTokensField: IntegerField
private val threadsField: IntegerField
private val additionalParametersField: JBTextField
private val additionalBuildParametersField: JBTextField
private val additionalEnvironmentVariablesField: JBTextField
init {
val llamaServerAgent = ApplicationManager.getApplication().getService(LlamaServerAgent::class.java)
val serverRunning = llamaServerAgent.isServerRunning
portField = PortField(settings.serverPort).apply {
isEnabled = !serverRunning
}
maxTokensField = IntegerField("max_tokens", 256, 4096).apply {
columns = 12
value = settings.contextSize
isEnabled = !serverRunning
}
threadsField = IntegerField("threads", 1, 256).apply {
columns = 12
value = settings.threads
isEnabled = !serverRunning
}
additionalParametersField = JBTextField(settings.additionalParameters, 30).apply {
isEnabled = !serverRunning
}
additionalBuildParametersField = JBTextField(settings.additionalBuildParameters, 30).apply {
isEnabled = !serverRunning
}
additionalEnvironmentVariablesField = JBTextField(settings.additionalEnvironmentVariables, 30).apply {
isEnabled = !serverRunning
}
}
fun getForm(): JPanel {
val llamaServerAgent = ApplicationManager.getApplication().getService(LlamaServerAgent::class.java)
return createRunLocalServerForm(llamaServerAgent)
}
fun resetForm(state: LlamaSettingsState) {
llamaModelPreferencesForm.resetForm(state)
portField.number = state.serverPort
maxTokensField.value = state.contextSize
threadsField.value = state.threads
additionalParametersField.text = state.additionalParameters
additionalBuildParametersField.text = state.additionalBuildParameters
additionalEnvironmentVariablesField.text = state.additionalEnvironmentVariables
}
fun createRunLocalServerForm(llamaServerAgent: LlamaServerAgent): JPanel {
return UIUtil.withEmptyLeftBorder(
FormBuilder.createFormBuilder()
.addComponent(
TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.service.llama.modelPreferences.title")
)
)
.addComponent(UIUtil.withEmptyLeftBorder(llamaModelPreferencesForm.form))
.addComponent(TitledSeparator(CodeGPTBundle.get("llama.ui.tab.serverConfiguration")))
.addComponent(
UIUtil.withEmptyLeftBorder(
FormBuilder.createFormBuilder()
.addLabeledComponent(
CodeGPTBundle.get("shared.port"),
createPortAndButtonsPanel(llamaServerAgent)
)
.addVerticalGap(4)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.contextSize.label"),
maxTokensField
)
.addComponentToRightColumn(
UIUtil.createComment("settingsConfigurable.service.llama.contextSize.comment")
)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.threads.label"),
threadsField
)
.addComponentToRightColumn(
UIUtil.createComment("settingsConfigurable.service.llama.threads.comment")
)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.additionalParameters.label"),
additionalParametersField
)
.addComponentToRightColumn(
UIUtil.createComment("settingsConfigurable.service.llama.additionalParameters.comment")
)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.additionalBuildParameters.label"),
additionalBuildParametersField
)
.addComponentToRightColumn(
UIUtil.createComment("settingsConfigurable.service.llama.additionalBuildParameters.comment")
)
.addLabeledComponent(
CodeGPTBundle.get("settingsConfigurable.service.llama.additionalEnvironmentVariables.label"),
additionalEnvironmentVariablesField
)
.addComponentToRightColumn(
UIUtil.createComment("settingsConfigurable.service.llama.additionalEnvironmentVariables.comment")
)
.addComponentFillVertically(JPanel(), 0)
.panel
)
)
.panel
) as JPanel
}
private fun createPortAndButtonsPanel(llamaServerAgent: LlamaServerAgent): JPanel {
return JPanel().apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
add(portField)
add(Box.createHorizontalStrut(4))
val serverButton = getServerButton(llamaServerAgent)
serverButton.maximumSize = serverButton.preferredSize
add(serverButton)
add(Box.createHorizontalGlue())
}
}
private fun getServerButton(llamaServerAgent: LlamaServerAgent): JButton {
val serverRunning = llamaServerAgent.isServerRunning
val buildInProgress = llamaServerAgent.isBuildInProgress
return JButton().apply {
when {
serverRunning -> {
text = CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label")
icon = AllIcons.Actions.Suspend
}
buildInProgress -> {
text = CodeGPTBundle.get("llama.ui.button.stopBuild")
icon = AllIcons.Actions.Suspend
}
else -> {
text = CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label")
icon = AllIcons.Actions.Execute
}
}
addActionListener {
if (!validateModelConfiguration()) {
return@addActionListener
}
when {
llamaServerAgent.isServerRunning -> {
enableForm(this)
llamaServerAgent.stopAgent()
}
llamaServerAgent.isBuildInProgress -> {
enableForm(this)
llamaServerAgent.stopAgent()
}
else -> {
text = CodeGPTBundle.get("llama.ui.button.stopBuild")
icon = AllIcons.Actions.Suspend
disableForm(this)
llamaServerAgent.startAgent(
LlamaServerStartupParams(
llamaModelPreferencesForm.actualModelPath,
contextSize,
threads,
serverPort,
listOfAdditionalParameters,
listOfAdditionalBuildParameters,
mapOfAdditionalEnvironmentVariables
),
parentForm?.let {
val strategy = SettingsFormLoggingStrategy(it)
strategy.logMessage(CodeGPTBundle.get("llama.debug.buildLoggingStrategy"), false, true)
strategy
} ?: run {
NoOpLoggingStrategy
},
{
setFormEnabled(false)
text = CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label")
icon = AllIcons.Actions.Suspend
},
{
setFormEnabled(true)
text = CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label")
icon = AllIcons.Actions.Execute
}
)
}
}
}
}
}
private fun validateModelConfiguration(): Boolean {
return validateCustomModelPath() && validateSelectedModel()
}
private fun validateCustomModelPath(): Boolean {
if (llamaModelPreferencesForm.isUseCustomLlamaModel) {
val customModelPath = llamaModelPreferencesForm.customLlamaModelPath
if (customModelPath.isNullOrEmpty()) {
OverlayUtil.showBalloon(
CodeGPTBundle.get("validation.error.fieldRequired"),
MessageType.ERROR,
llamaModelPreferencesForm.browsableCustomModelTextField
)
return false
}
}
return true
}
private fun validateSelectedModel(): Boolean {
if (!llamaModelPreferencesForm.isUseCustomLlamaModel &&
!LlamaSettings.isModelExists(llamaModelPreferencesForm.selectedModel)
) {
OverlayUtil.showBalloon(
CodeGPTBundle.get("settingsConfigurable.service.llama.overlay.modelNotDownloaded.text"),
MessageType.ERROR,
llamaModelPreferencesForm.huggingFaceModelComboBox
)
return false
}
return true
}
private fun enableForm(serverButton: JButton) {
setFormEnabled(true)
serverButton.text = CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label")
serverButton.icon = AllIcons.Actions.Execute
}
private fun disableForm(serverButton: JButton) {
setFormEnabled(false)
serverButton.text = CodeGPTBundle.get("settingsConfigurable.service.llama.stopServer.label")
serverButton.icon = AllIcons.Actions.Suspend
}
private fun setFormEnabled(enabled: Boolean) {
llamaModelPreferencesForm.enableFields(enabled)
portField.isEnabled = enabled
maxTokensField.isEnabled = enabled
threadsField.isEnabled = enabled
additionalParametersField.isEnabled = enabled
additionalBuildParametersField.isEnabled = enabled
additionalEnvironmentVariablesField.isEnabled = enabled
}
val serverPort: Int
get() = portField.number
val contextSize: Int
get() = maxTokensField.value
var threads: Int
get() = threadsField.value
set(value) {
threadsField.value = value
}
val additionalParameters: String
get() = additionalParametersField.text
val listOfAdditionalParameters: List<String>
get() = LlamaSettings.getAdditionalParametersList(additionalParametersField.text)
val additionalBuildParameters: String
get() = additionalBuildParametersField.text
val listOfAdditionalBuildParameters: List<String>
get() = LlamaSettings.getAdditionalParametersList(additionalBuildParametersField.text)
val additionalEnvironmentVariables: String
get() = additionalEnvironmentVariablesField.text
val mapOfAdditionalEnvironmentVariables: Map<String, String>
get() = LlamaSettings.getAdditionalEnvironmentVariablesMap(additionalEnvironmentVariablesField.text)
val promptTemplate: PromptTemplate
get() = llamaModelPreferencesForm.promptTemplate ?: PromptTemplate.CODE_QWEN
}

View file

@ -0,0 +1,14 @@
package ee.carlrobert.codegpt.settings.service.llama.form
import com.intellij.ui.PortField
import com.intellij.ui.components.JBTextField
import com.intellij.ui.components.fields.IntegerField
data class ServerButtonConfig(
val portField: PortField,
val contextSizeField: IntegerField,
val threadsField: IntegerField,
val additionalParametersField: JBTextField,
val additionalBuildParametersField: JBTextField,
val additionalEnvironmentVariablesField: JBTextField
)

View file

@ -72,6 +72,8 @@
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.advanced.AdvancedSettings"/>
<applicationService serviceImplementation="ee.carlrobert.codegpt.conversations.ConversationsState"/>
<applicationService serviceImplementation="ee.carlrobert.codegpt.codecompletions.psi.CompletionContextService"/>
<applicationService serviceImplementation="ee.carlrobert.codegpt.services.llama.ServerLogsManager"/>
<projectService serviceImplementation="ee.carlrobert.codegpt.services.ExecutableRunnerService"/>
<inline.completion.provider
id="CodeGPTInlineCompletionProvider"
implementation="ee.carlrobert.codegpt.codecompletions.DebouncedCodeCompletionProvider"/>

View file

@ -147,14 +147,8 @@ configurationConfigurable.section.chatCompletion.psiStructure.title=Enable depen
configurationConfigurable.section.chatCompletion.psiStructure.analyzeDepth.title=Code analyze depth:
configurationConfigurable.section.chatCompletion.psiStructure.analyzeDepth.comment=The parameter limits the depth of the PSI structure traversal. Currently, it is implemented only for the Kotlin language.
configurationConfigurable.section.chatCompletion.psiStructure.description=If enabled, the class structure that is present in the imports of the attached files will be added in the context of the dialog. A structure refers to the source code in files that include constructors, fields, and methods, with all modifiers, arguments, and return types, but without an implementation. The implementation of dependencies is intentionally excluded in order to find a balance between a high-quality chat context and saving tokens.
settingsConfigurable.service.llama.topK.label=Top K:
settingsConfigurable.service.llama.topK.comment=Limit the next token selection to the K most probable tokens (default: 40)
settingsConfigurable.service.llama.topP.label=Top P:
settingsConfigurable.service.llama.topP.comment=Limit the next token selection to a subset of tokens with a cumulative probability above a threshold P (default: 0.9)
settingsConfigurable.service.llama.minP.label=Min P:
settingsConfigurable.service.llama.minP.comment=Sets a minimum base probability threshold for token selection (default: 0.05)
settingsConfigurable.service.llama.repeatPenalty.label=Repeat penalty:
settingsConfigurable.service.llama.repeatPenalty.comment=Control the repetition of token sequences in the generated text (default: 1.1)
settingsConfigurable.service.llama.predefinedModel.comment=Download and use vetted models from HuggingFace.
settingsConfigurable.service.llama.customModel.comment=Use your own GGUF model file from a local path on your computer.
settingsConfigurable.service.custom.openai.testConnection.label=Test Connection
settingsConfigurable.service.custom.openai.presetTemplate.label=Preset template:
settingsConfigurable.service.custom.openai.url.label=URL:
@ -336,3 +330,33 @@ tagPopupMenuItem.closeTagsToLeft=Close Tags to the Left
tagPopupMenuItem.closeTagsToRight=Close Tags to the Right
toolwindow.chat.loading=Generating response...
headerPanel.error.searchBlockNotMapped.title=Failed to Locate Search Block
llama.build.cmake.setup=Setting up CMake...
llama.build.cmake.build=Building project...
llama.build.startingBuild=Starting Llama Server Build
llama.build.phase.setup=Phase 1: CMake Setup
llama.build.phase.build=Phase 2: Building Project
llama.build.phase.setupFailed=CMake setup failed
llama.build.phase.buildFailed=Build failed
llama.build.cache.cleanup=Detected CMake cache path mismatch, cleaning up build directory...
llama.server.buildStopped=Build stopped by user
llama.server.starting=Starting server...
llama.server.running=Server running successfully
llama.server.startupFailed=Server startup failed
llama.server.stopping.cmake=Stopping CMake setup process
llama.server.stopping.build=Stopping build process
llama.error.server.startup=Unable to start llama server:\n{0}
llama.error.server.startupWithDetails=Server startup failed: {0}
llama.ui.tab.serverConfiguration=Server Configuration
llama.ui.tab.serverLogs=Server Logs
llama.ui.tab.buildOutput=Build Output
llama.ui.button.stopBuild=Stop Build
llama.ui.status.running=Server status: Running
llama.ui.status.building=Server status: Building...
llama.ui.status.stopped=Server status: Stopped
llama.ui.status.prefix=Server status: {0}
llama.ui.action.clear=Clear
llama.ui.action.clear.description=Clear console
llama.ui.action.scrollToEnd=Scroll to End
llama.ui.action.scrollToEnd.description=Scroll to bottom
llama.process.startingBuild=Starting server build process...
llama.debug.buildLoggingStrategy=DEBUG: Build logging strategy initialized