From f85624b72067b0be9b7472d66c4f88c541e42a1c Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Mon, 24 Feb 2025 17:32:41 +0200 Subject: [PATCH] Add configuration wizard --- .../eu/svjatoslav/alyverkko_cli/Main.java | 8 +- .../alyverkko_cli/commands/WizardCommand.java | 162 ++++++++++++++++++ .../configuration/Configuration.java | 2 +- 3 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java index 049f966..4bd8d57 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java @@ -1,10 +1,7 @@ package eu.svjatoslav.alyverkko_cli; -import eu.svjatoslav.alyverkko_cli.commands.JoinFilesCommand; -import eu.svjatoslav.alyverkko_cli.commands.ListModelsCommand; -import eu.svjatoslav.alyverkko_cli.commands.SelftestCommand; +import eu.svjatoslav.alyverkko_cli.commands.*; import eu.svjatoslav.alyverkko_cli.configuration.Configuration; -import eu.svjatoslav.alyverkko_cli.commands.MailCorrespondentCommand; import java.io.IOException; import java.util.Optional; @@ -17,7 +14,8 @@ public class Main { new ListModelsCommand(), new MailCorrespondentCommand(), new JoinFilesCommand(), - new SelftestCommand() + new SelftestCommand(), + new WizardCommand() ); public static void main(final String[] args) throws IOException, InterruptedException { diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java new file mode 100644 index 0000000..9ddf06e --- /dev/null +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java @@ -0,0 +1,162 @@ +package eu.svjatoslav.alyverkko_cli.commands; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import eu.svjatoslav.alyverkko_cli.Command; +import eu.svjatoslav.alyverkko_cli.configuration.Configuration; +import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationModel; +import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser; +import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.NullOption; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import static eu.svjatoslav.alyverkko_cli.configuration.Configuration.DEFAULT_CONFIG_FILE_PATH; + +public class WizardCommand implements Command { + + private final Parser parser = new Parser(); + private final NullOption forceOption = parser.add(new NullOption("Force overwrite existing configuration")) + .addAliases("--force", "-f"); + + @Override + public String getName() { + return "wizard"; + } + + @Override + public void execute(String[] cliArguments) throws IOException { + if (!parser.parse(cliArguments)) { + System.out.println("Failed to parse commandline arguments"); + parser.showHelp(); + return; + } + + Configuration config = new Configuration(); + Scanner scanner = new Scanner(System.in); + + System.out.println("\nStarting Älyverkko CLI configuration wizard.\n"); + + // Mail Directory + String mailDir = getInput("Enter the mail directory for AI tasks", + "~/.config/alyverkko-cli/mail"); + config.setMailDirectory(new File(mailDir)); + + // Models Directory + String modelsDir = getInput("Enter the directory for AI models", + "~/.config/alyverkko-cli/models"); + config.setModelsDirectory(new File(modelsDir)); + + // Prompts Directory + String promptsDir = getInput("Enter the directory for prompts", + "~/.config/alyverkko-cli/prompts"); + config.setPromptsDirectory(new File(promptsDir)); + + // Llama CLI Path + String llamaCliPath = getInput("Enter the path to llama-cli executable", + "/usr/local/bin/llama-cli"); + config.setLlamaCliPath(new File(llamaCliPath)); + + // Default Temperature + float defaultTemp = getFloatInput("Enter default temperature", 0.7f, 0f, 1f); + config.setDefaultTemperature(defaultTemp); + + // Thread Counts + int threadCount = getIntInput("Enter number of threads for AI processing", 6, 1, 16); + config.setThreadCount(threadCount); + + int batchThreadCount = getIntInput("Enter number of batch threads", 10, 1, 32); + config.setBatchThreadCount(batchThreadCount); + + // Models Setup + System.out.println("\nModel configuration:"); + List models = new ArrayList<>(); + while (true) { + System.out.println("\nEnter model details or leave alias empty to finish."); + String alias = getInput("Model alias: "); + if (StringUtils.isBlank(alias)) break; + + String filePath = getInput("Filesystem path: "); + int contextSize = getIntInput("Context size (tokens): ", 64000, 1024, 128000); + + ConfigurationModel model = new ConfigurationModel(); + model.setAlias(alias); + model.setFilesystemPath(filePath); + model.setContextSizeTokens(contextSize); + models.add(model); + } + config.setModels(models); + + // Write configuration + Path configPath = Path.of(DEFAULT_CONFIG_FILE_PATH); + if (Files.exists(configPath) && !forceOption.isPresent()) { + String confirm = getInput("Configuration file already exists. Overwrite? (y/N)", "N"); + if (!confirm.equalsIgnoreCase("Y")) { + System.out.println("Configuration not saved. Run with --force to overwrite."); + return; + } + } + + // Create parent directories + Files.createDirectories(configPath.getParent()); + + // Save configuration + try (var writer = Files.newBufferedWriter(configPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + new ObjectMapper(new YAMLFactory()).writeValue(writer, config); + } + + System.out.println("\nConfiguration saved to: " + configPath); + System.out.println("You can now run 'alyverkko-cli selftest' to verify everything is set up correctly."); + } + + private String getInput(String prompt, String defaultValue) { + Scanner scanner = new Scanner(System.in); + + if (defaultValue == null) { + System.out.print(prompt + ": "); + } else { + System.out.print(prompt + " [" + defaultValue + "]: "); + } + String input = scanner.nextLine().trim(); + + return input.isEmpty() ? defaultValue : input; + } + + private String getInput(String prompt) { + return getInput(prompt, null); + } + + private float getFloatInput(String prompt, float defaultValue, float min, float max) { + while (true) { + String input = getInput(prompt, String.valueOf(defaultValue)); + try { + float value = Float.parseFloat(input); + if (value >= min && value <= max) return value; + System.out.println("Value must be between " + min + " and " + max); + } catch (NumberFormatException e) { + System.out.println("Invalid number format. Try again."); + } + } + } + + private int getIntInput(String prompt, int defaultValue, int min, int max) { + while (true) { + String input = getInput(prompt, String.valueOf(defaultValue)); + try { + int value = Integer.parseInt(input); + if (value >= min && value <= max) return value; + System.out.println("Value must be between " + min + " and " + max); + } catch (NumberFormatException e) { + System.out.println("Invalid number format. Try again."); + } + } + } + +} diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java index b301325..63769f8 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/configuration/Configuration.java @@ -14,7 +14,7 @@ import static eu.svjatoslav.commons.file.IOHelper.getFileContentsAsString; @Data public class Configuration { - private static final String DEFAULT_CONFIG_FILE_PATH = "~/.config/alyverkko-cli/alyverkko-cli.yaml".replaceFirst("^~", System.getProperty("user.home")); + public static final String DEFAULT_CONFIG_FILE_PATH = "~/.config/alyverkko-cli/alyverkko-cli.yaml".replaceFirst("^~", System.getProperty("user.home")); @JsonProperty("mail_directory") private File mailDirectory; -- 2.20.1