feat: Start/stop LLaMA Server from statusbar (#544)

This commit is contained in:
Rene Leonhardt 2024-05-13 18:02:22 +02:00 committed by GitHub
parent 91c7302008
commit 7c668ae143
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 248 additions and 84 deletions

View file

@ -8,7 +8,6 @@ import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.jetbrains.annotations.NotNull;
public final class CodeGPTPlugin {
@ -38,10 +37,6 @@ public final class CodeGPTPlugin {
return getPluginBasePath() + File.separator + "llama.cpp";
}
public static @NotNull String getLlamaModelsPath() {
return Paths.get(System.getProperty("user.home"), ".codegpt/models/gguf").toString();
}
public static @NotNull String getProjectIndexStorePath(@NotNull Project project) {
return getIndexStorePath() + File.separator + project.getName();
}

View file

@ -42,7 +42,7 @@ public final class LlamaServerAgent implements Disposable {
LlamaServerStartupParams params,
ServerProgressPanel serverProgressPanel,
Runnable onSuccess,
Consumer<ServerProgressPanel> onServerTerminated) {
Consumer<ServerProgressPanel> onServerStopped) {
this.activeServerProgressPanel = serverProgressPanel;
ApplicationManager.getApplication().invokeLater(() -> {
try {
@ -52,10 +52,10 @@ public final class LlamaServerAgent implements Disposable {
makeProcessHandler = new OSProcessHandler(
getMakeCommandLine(params.additionalBuildParameters()));
makeProcessHandler.addProcessListener(
getMakeProcessListener(params, onSuccess, onServerTerminated));
getMakeProcessListener(params, onSuccess, onServerStopped));
makeProcessHandler.startNotify();
} catch (ExecutionException e) {
showServerError(e.getMessage(), onServerTerminated);
showServerError(e.getMessage(), onServerStopped);
}
});
}
@ -82,7 +82,7 @@ public final class LlamaServerAgent implements Disposable {
private ProcessListener getMakeProcessListener(
LlamaServerStartupParams params,
Runnable onSuccess,
Consumer<ServerProgressPanel> onServerTerminated) {
Consumer<ServerProgressPanel> onServerStopped) {
LOG.info("Building llama project");
return new ProcessAdapter() {
@ -103,11 +103,11 @@ public final class LlamaServerAgent implements Disposable {
int exitCode = event.getExitCode();
LOG.info(format("Server build exited with code %d", exitCode));
if (stoppedByUser) {
onServerTerminated.accept(activeServerProgressPanel);
onServerStopped.accept(activeServerProgressPanel);
return;
}
if (exitCode != 0) {
showServerError(String.join(",", errorLines), onServerTerminated);
showServerError(String.join(",", errorLines), onServerStopped);
return;
}
@ -118,11 +118,10 @@ public final class LlamaServerAgent implements Disposable {
CodeGPTBundle.get("llamaServerAgent.serverBootup.description"));
startServerProcessHandler = new OSProcessHandler.Silent(getServerCommandLine(params));
startServerProcessHandler.addProcessListener(
getProcessListener(params.port(), onSuccess,
onServerTerminated));
getProcessListener(params.port(), onSuccess, onServerStopped));
startServerProcessHandler.startNotify();
} catch (ExecutionException ex) {
showServerError(ex.getMessage(), onServerTerminated);
showServerError(ex.getMessage(), onServerStopped);
}
}
};
@ -131,18 +130,18 @@ public final class LlamaServerAgent implements Disposable {
private ProcessListener getProcessListener(
int port,
Runnable onSuccess,
Consumer<ServerProgressPanel> onServerTerminated) {
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 terminated with code %d", event.getExitCode()));
LOG.info(format("Server stopped with code %d", event.getExitCode()));
if (stoppedByUser) {
onServerTerminated.accept(activeServerProgressPanel);
onServerStopped.accept(activeServerProgressPanel);
} else {
showServerError(String.join(",", errorLines), onServerTerminated);
showServerError(String.join(",", errorLines), onServerStopped);
}
}
@ -172,8 +171,8 @@ public final class LlamaServerAgent implements Disposable {
};
}
private void showServerError(String errorText, Consumer<ServerProgressPanel> onServerTerminated) {
onServerTerminated.accept(activeServerProgressPanel);
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);
}

View file

@ -1,6 +1,9 @@
package ee.carlrobert.codegpt.settings.service.llama;
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LLAMA_API_KEY;
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;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
@ -10,7 +13,13 @@ import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
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;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -64,4 +73,37 @@ public class LlamaSettings implements PersistentStateComponent<LlamaSettingsStat
form.getLlamaServerPreferencesForm().getApiKey(),
CredentialsStore.getCredential(LLAMA_API_KEY));
}
public static boolean isRunnable() {
return (IS_OS_MAC_OSX || IS_OS_LINUX)
&& GeneralSettings.getCurrentState().getSelectedService() == LLAMA_CPP;
}
public static boolean isRunnable(HuggingFaceModel model) {
return isRunnable() && isModelExists(model);
}
public static boolean isModelExists(HuggingFaceModel model) {
return getLlamaModelsPath().resolve(model.getFileName()).toFile().exists();
}
public static Path getLlamaModelsPath() {
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.getHuggingFaceModel().getFileName();
}
public static List<String> getAdditionalParametersList(String additionalParameters) {
return Arrays.stream(additionalParameters.split(","))
.map(String::trim)
.filter(s -> !s.isBlank())
.toList();
}
}

View file

@ -1,5 +1,7 @@
package ee.carlrobert.codegpt.settings.service.llama.form;
import static ee.carlrobert.codegpt.settings.service.llama.LlamaSettings.getLlamaModelsPath;
import static ee.carlrobert.codegpt.settings.service.llama.LlamaSettings.isModelExists;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
@ -15,7 +17,6 @@ import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.panel.ComponentPanelBuilder;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.EnumComboBoxModel;
import com.intellij.ui.components.AnActionLink;
import com.intellij.ui.components.JBLabel;
@ -23,7 +24,6 @@ import com.intellij.ui.components.JBRadioButton;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
@ -37,7 +37,6 @@ import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.io.File;
import java.util.Map;
import javax.swing.Box;
import javax.swing.BoxLayout;
@ -100,7 +99,7 @@ public class LlamaModelPreferencesForm {
.filter(model -> model.getParameterSize() == llm.getParameterSize())
.toList();
huggingFaceComboBoxModel.addAll(selectableModels);
huggingFaceComboBoxModel.setSelectedItem(selectableModels.get(0));
huggingFaceComboBoxModel.setSelectedItem(llm);
downloadModelActionLinkWrapper = new JPanel(new BorderLayout());
downloadModelActionLinkWrapper.setBorder(JBUI.Borders.emptyLeft(2));
downloadModelActionLinkWrapper.add(
@ -116,7 +115,10 @@ public class LlamaModelPreferencesForm {
var modelSizeComboBoxModel = new DefaultComboBoxModel<ModelSize>();
var initialModelSizes = llamaModel.getSortedUniqueModelSizes();
modelSizeComboBoxModel.addAll(initialModelSizes);
modelSizeComboBoxModel.setSelectedItem(initialModelSizes.get(0));
var selectedModelSize = initialModelSizes.stream()
.filter(ms -> ms.size() == llm.getParameterSize())
.findFirst().orElse(initialModelSizes.get(0));
modelSizeComboBoxModel.setSelectedItem(selectedModelSize);
var modelComboBoxModel = new EnumComboBoxModel<>(LlamaModel.class);
modelComboBox = createModelComboBox(modelComboBoxModel, llamaModel, modelSizeComboBoxModel);
modelComboBox.setEnabled(!llamaServerAgent.isServerRunning());
@ -194,7 +196,7 @@ public class LlamaModelPreferencesForm {
public String getActualModelPath() {
return isUseCustomLlamaModel()
? getCustomLlamaModelPath()
: CodeGPTPlugin.getLlamaModelsPath() + File.separator + getSelectedModel().getFileName();
: getLlamaModelsPath().resolve(getSelectedModel().getFileName()).toString();
}
private JPanel createFormPanelCards() {
@ -386,11 +388,6 @@ public class LlamaModelPreferencesForm {
return browseButton;
}
private boolean isModelExists(HuggingFaceModel model) {
return FileUtil.exists(
CodeGPTPlugin.getLlamaModelsPath() + File.separator + model.getFileName());
}
private AnActionLink createCancelDownloadLink(
JBLabel progressLabel,
JPanel actionLinkWrapper,

View file

@ -1,6 +1,7 @@
package ee.carlrobert.codegpt.settings.service.llama.form;
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LLAMA_API_KEY;
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;
@ -9,7 +10,6 @@ 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.openapi.util.io.FileUtil;
import com.intellij.ui.PortField;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.JBLabel;
@ -21,20 +21,17 @@ 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.CodeGPTPlugin;
import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
import ee.carlrobert.codegpt.completions.HuggingFaceModel;
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 java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
@ -254,7 +251,7 @@ public class LlamaServerPreferencesForm {
CodeGPTBundle.get("settingsConfigurable.service.llama.startServer.label"));
serverButton.setIcon(Actions.Execute);
activeServerProgressPanel.displayComponent(new JBLabel(
CodeGPTBundle.get("settingsConfigurable.service.llama.progress.serverTerminated"),
CodeGPTBundle.get("settingsConfigurable.service.llama.progress.serverStopped"),
Actions.Cancel,
SwingConstants.LEADING));
});
@ -293,11 +290,6 @@ public class LlamaServerPreferencesForm {
return true;
}
private boolean isModelExists(HuggingFaceModel model) {
return FileUtil.exists(
CodeGPTPlugin.getLlamaModelsPath() + File.separator + model.getFileName());
}
private void enableForm(JButton serverButton, ServerProgressPanel progressPanel) {
setFormEnabled(true);
serverButton.setText(
@ -358,10 +350,7 @@ public class LlamaServerPreferencesForm {
}
public List<String> getListOfAdditionalParameters() {
return Arrays.stream(additionalParametersField.getText().split(","))
.map(String::trim)
.filter(s -> !s.isBlank())
.toList();
return LlamaSettings.getAdditionalParametersList(additionalParametersField.getText());
}
public String getAdditionalBuildParameters() {
@ -369,10 +358,7 @@ public class LlamaServerPreferencesForm {
}
public List<String> getListOfAdditionalBuildParameters() {
return Arrays.stream(additionalBuildParametersField.getText().split(","))
.map(String::trim)
.filter(s -> !s.isBlank())
.toList();
return LlamaSettings.getAdditionalParametersList(additionalBuildParametersField.getText());
}
public PromptTemplate getPromptTemplate() {

View file

@ -31,15 +31,38 @@ import org.jetbrains.annotations.NotNull;
public class OverlayUtil {
public static final String NOTIFICATION_GROUP_ID = "CodeGPT Notification Group";
public static final String NOTIFICATION_GROUP_STICKY_ID = "CodeGPT Notification Group Sticky";
private OverlayUtil() {
}
public static Notification getDefaultNotification(String content, NotificationType type) {
return new Notification("CodeGPT Notification Group", "CodeGPT", content, type);
return new Notification(NOTIFICATION_GROUP_ID, "CodeGPT", content, type);
}
public static void showNotification(String content, NotificationType type) {
Notifications.Bus.notify(getDefaultNotification(content, type));
public static Notification getStickyNotification(String content, NotificationType type) {
return new Notification(NOTIFICATION_GROUP_STICKY_ID, "CodeGPT", content, type);
}
public static Notification showNotification(String content) {
return showNotification(content, NotificationType.INFORMATION);
}
public static Notification showNotification(String content, NotificationType type) {
var notification = getDefaultNotification(content, type);
Notifications.Bus.notify(notification);
return notification;
}
public static Notification stickyNotification(String content) {
return stickyNotification(content, NotificationType.INFORMATION);
}
public static Notification stickyNotification(String content, NotificationType type) {
var notification = getStickyNotification(content, type);
Notifications.Bus.notify(notification);
return notification;
}
public static int showDeleteConversationDialog() {