Configuration wizard usability improvements
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 8 Mar 2025 22:56:45 +0000 (00:56 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 8 Mar 2025 22:56:45 +0000 (00:56 +0200)
src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java

index 6835db9..9f00a24 100644 (file)
@@ -55,75 +55,87 @@ public class WizardCommand implements Command {
 
         // Mail Directory
         String mailDir = getInputWithDefault(
-                "Enter the mail directory for AI tasks. This is where your task files will be stored.",
+                "\nEnter the mail directory for AI tasks. This is where your task files will be stored.",
                 existingConfig != null ? existingConfig.getMailDirectory().getPath() : "~/.config/alyverkko-cli/mail"
         );
         config.setMailDirectory(new File(mailDir));
 
         // Models Directory
         String modelsDir = getInputWithDefault(
-                "Enter the directory for AI models. This should contain your GGUF model files.",
+                "\nEnter the directory for AI models. This should contain your GGUF model files.",
                 existingConfig != null ? existingConfig.getModelsDirectory().getPath() : "~/.config/alyverkko-cli/models"
         );
         config.setModelsDirectory(new File(modelsDir));
 
         // Prompts Directory
         String promptsDir = getInputWithDefault(
-                "Enter the directory for prompts. This should contain text files with your AI prompts.",
+                "\nEnter the directory for prompts. This should contain text files with your AI prompts.",
                 existingConfig != null ? existingConfig.getPromptsDirectory().getPath() : "~/.config/alyverkko-cli/prompts"
         );
         config.setPromptsDirectory(new File(promptsDir));
 
         // Llama CLI Path
         String llamaCliPath = getInputWithDefault(
-                "Enter the path to the llama-cli executable. This is the command-line interface for llama.cpp.",
+                "\nEnter the path to the llama-cli executable. This is the command-line interface for llama.cpp.",
                 existingConfig != null ? existingConfig.getLlamaCliPath().getPath() : "/usr/local/bin/llama-cli"
         );
         config.setLlamaCliPath(new File(llamaCliPath));
 
         // Default Temperature
         float defaultTemp = getFloatInputWithDefault(
-                "Enter default temperature (0-1). A lower value makes the AI more deterministic, while a higher value makes it more creative.",
+                "\nEnter default temperature (0-2). A lower value makes the AI more deterministic, while a higher value makes it more creative.",
                 existingConfig != null ? existingConfig.getDefaultTemperature() : 0.7f,
-                0f, 1f
+                0f, 2f
         );
         config.setDefaultTemperature(defaultTemp);
 
         // Thread Counts
         int threadCount = getIntInputWithDefault(
-                "Enter number of threads for AI processing. This should typically match the number of CPU cores available.",
+                "\nEnter number of CPU threads for AI response generation. " +
+                        "RAM data transfer speed is usually the bottleneck here. " +
+                        "On a typical PC, while you might have 12 CPU cores, " +
+                        "RAM bandwidth can get already saturated with only 6 CPU threads. " +
+                        "Once RAM bandwidth is saturated, using more CPU threads " +
+                        "will not improve performance while it will keep CPU cores needlessly busy.",
                 existingConfig != null ? existingConfig.getThreadCount() : 6,
-                1, 16
+                1, null
         );
         config.setThreadCount(threadCount);
 
         int batchThreadCount = getIntInputWithDefault(
-                "Enter number of batch threads. This controls how many prompts are processed in parallel.",
+                "\nEnter number of CPU threads for input prompt processing. " +
+                        "CPU computing power is usually the bottleneck here (not the RAM bandwidth). " +
+                        "So you can utilize all CPU cores that you have here.",
                 existingConfig != null ? existingConfig.getBatchThreadCount() : 10,
-                1, 32
+                1, null
         );
         config.setBatchThreadCount(batchThreadCount);
 
         // Models Setup
-        System.out.println("\nModel configuration:");
+        System.out.println("\nModel configuration.");
         List<ConfigurationModel> models = new ArrayList<>();
+        if (existingConfig != null) {
+            models.addAll(existingConfig.getModels());
+        }
 
-        // Suggest models based on models directory
-        suggestModels(config.getModelsDirectory(), models);
+        String choice = getInput("Would you like to try to autodiscover available models ?", "Y");
+        if (choice.equalsIgnoreCase("Y")) {
+            discoverAndSuggestNewModels(config.getModelsDirectory(), models);
+        }
 
+        System.out.println("\nYou can now add models manually.");
         while (true) {
-            System.out.println("\nEnter model details or leave alias empty to finish.");
-            String alias = getInput("Model alias: ");
+            String alias = getInput("Enter desired model alias (leave empty to finish adding models): ");
             if (StringUtils.isBlank(alias)) break;
 
             String filePath = getInputWithDefault(
-                    "Enter filesystem path relative to models directory. " +
+                    "\nEnter filesystem path relative to models directory. " +
                             "Press Tab for suggestions or Enter to accept default.",
                     null
             );
 
             int contextSize = getIntInputWithDefault(
-                    "Enter context size in tokens. A higher value allows the model to remember more context.",
+                    "\nEnter context size in tokens. A higher value allows the model to remember more context.",
                     64000,
                     1024,
                     128000
@@ -186,20 +198,30 @@ public class WizardCommand implements Command {
         }
     }
 
-    private int getIntInputWithDefault(String prompt, int defaultValue, int min, int max) {
+    private int getIntInputWithDefault(String prompt, int defaultValue, Integer min, Integer 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);
+
+                if (max != null && value > max) {
+                    System.out.println("Value must be less than or equal to " + max);
+                    continue;
+                }
+
+                if (min != null && value < min) {
+                    System.out.println("Value must be greater than or equal to " + min);
+                    continue;
+                }
+
+                return value;
             } catch (NumberFormatException e) {
                 System.out.println("Invalid number format. Try again.");
             }
         }
     }
 
-    private void suggestModels(File modelsDir, List<ConfigurationModel> existingModels) {
+    private void discoverAndSuggestNewModels(File modelsDir, List<ConfigurationModel> existingModels) {
         if (!modelsDir.exists()) {
             System.out.println("Models directory does not exist. Please create it first.");
             return;
@@ -218,35 +240,45 @@ public class WizardCommand implements Command {
 
             for (File modelFile : modelFiles) {
                 String relativePath = modelsDir.toPath().relativize(modelFile.toPath()).toString();
-                String alias = suggestAlias(relativePath);
-
-                if (!existingAliases.contains(alias)) {
-                    System.out.println("\nFound new model: " + relativePath);
-                    System.out.println("Suggested alias: " + alias);
-                    System.out.println("Would you like to add this model? (y/N)");
-                    String choice = getInput("Add model? ", "N");
-                    if (choice.equalsIgnoreCase("Y")) {
-                        ConfigurationModel model = new ConfigurationModel();
-                        model.setAlias(alias);
-                        model.setFilesystemPath(relativePath);
-                        model.setContextSizeTokens(64000); // Default value
-                        existingModels.add(model);
-                        existingAliases.add(alias);
-                    }
-                }
+
+                boolean alreadyExists = existingModels.stream()
+                        .anyMatch(m -> m.getFilesystemPath().equals(relativePath));
+
+                if (!alreadyExists) suggestAddingModel(existingModels, relativePath, existingAliases);
+
             }
         } catch (IOException e) {
             System.err.println("Error scanning models directory: " + e.getMessage());
         }
     }
 
+    private void suggestAddingModel(List<ConfigurationModel> existingModels, String relativePath, List<String> existingAliases) {
+        String alias = suggestAlias(relativePath);
+        System.out.println("\nFound new model: " + relativePath);
+        System.out.println("Suggested alias: " + alias);
+        String choice = getInput("Would you like to add this model? (y/N) ", "N");
+        if (choice.equalsIgnoreCase("Y")) {
+            ConfigurationModel model = new ConfigurationModel();
+            model.setAlias(alias);
+            model.setFilesystemPath(relativePath);
+            int contextSize = getIntInputWithDefault(
+                    "\nEnter context size in tokens. A higher value allows the model to remember more context.",
+                    64000,
+                    1024,
+                    null
+            );
+            model.setContextSizeTokens(contextSize);
+            existingModels.add(model);
+            existingAliases.add(alias);
+        }
+    }
+
     private String suggestAlias(String filePath) {
         String fileName = new File(filePath).getName();
         String alias = fileName.replaceAll("[^a-zA-Z0-9]", "-").toLowerCase();
         return alias.replaceAll("-+", "-").replaceAll("^-|-$", "");
     }
 
-
     private String getInput(String prompt, String defaultValue) {
         Scanner scanner = new Scanner(System.in);
         System.out.print(prompt + (defaultValue != null ? " [" + defaultValue + "]" : "") + ": ");