From: Svjatoslav Agejenko Date: Mon, 24 Mar 2025 23:21:15 +0000 (+0200) Subject: Possibility to specify alternative configuration file for selftest command X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=96a069ad7b725b7974a059838772ec47049f2a30;p=alyverkko-cli.git Possibility to specify alternative configuration file for selftest command --- diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/AiTask.java b/src/main/java/eu/svjatoslav/alyverkko_cli/AiTask.java index 73c3ede..fa35763 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/AiTask.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/AiTask.java @@ -1,6 +1,6 @@ package eu.svjatoslav.alyverkko_cli; -import eu.svjatoslav.alyverkko_cli.commands.MailQuery; +import eu.svjatoslav.alyverkko_cli.commands.mail_correspondant.MailQuery; import java.io.*; import java.nio.file.Files; diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java index f79f341..25fa1fd 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java @@ -1,6 +1,7 @@ package eu.svjatoslav.alyverkko_cli; import eu.svjatoslav.alyverkko_cli.commands.*; +import eu.svjatoslav.alyverkko_cli.commands.mail_correspondant.MailCorrespondentCommand; import eu.svjatoslav.alyverkko_cli.configuration.Configuration; import java.io.IOException; diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailCorrespondentCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailCorrespondentCommand.java deleted file mode 100644 index 32299dd..0000000 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailCorrespondentCommand.java +++ /dev/null @@ -1,344 +0,0 @@ -package eu.svjatoslav.alyverkko_cli.commands; - -import eu.svjatoslav.alyverkko_cli.*; -import eu.svjatoslav.alyverkko_cli.model.Model; -import eu.svjatoslav.alyverkko_cli.model.ModelLibrary; -import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser; -import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.FileOption; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.*; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static eu.svjatoslav.alyverkko_cli.Main.configuration; -import static eu.svjatoslav.alyverkko_cli.configuration.Configuration.loadConfiguration; -import static eu.svjatoslav.commons.file.IOHelper.getFileContentsAsString; -import static eu.svjatoslav.commons.file.IOHelper.saveToFile; -import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; -import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; - -/** - * The MailCorrespondentCommand continuously monitors a specified mail - * directory for new or modified text files, checks if they have a - * "TOCOMPUTE:" marker, and if so, processes them with an AI model. - * Once processed, results are appended to the same file. - * - * Usage: - *
- *   alyverkko-cli mail
- * 
- */ -public class MailCorrespondentCommand implements Command { - - /** - * A command-line parser to handle "mail" command arguments. - */ - final Parser parser = new Parser(); - - /** - * Optional CLI argument for specifying a configuration file path. - */ - public FileOption configFileOption = parser.add(new FileOption("Configuration file path")) - .addAliases("--config", "-c") - .mustExist(); - - /** - * The library of available models, constructed from configuration. - */ - ModelLibrary modelLibrary; - - /** - * The WatchService instance for monitoring file system changes in - * the mail directory. - */ - private WatchService directoryWatcher; - - /** - * The directory that we continuously watch for new tasks. - */ - File mailDir; - - /** - * @return the name of this command, i.e., "mail". - */ - @Override - public String getName() { - return "mail"; - } - - /** - * Executes the "mail" command, loading configuration, starting a - * WatchService on the mail directory, and running an infinite loop - * that processes newly discovered tasks. - * - * @param cliArguments the command-line arguments following the "mail" subcommand. - * @throws IOException if reading/writing tasks fails. - * @throws InterruptedException if the WatchService is interrupted. - */ - @Override - public void execute(String[] cliArguments) throws IOException, InterruptedException { - if (!parser.parse(cliArguments)) { - System.out.println("Failed to parse commandline arguments"); - parser.showHelp(); - return; - } - - configuration = loadConfiguration(configFileOption.isPresent() ? configFileOption.getValue() : null); - if (configuration == null) { - System.out.println("Failed to load configuration file"); - return; - } - - modelLibrary = new ModelLibrary(configuration.getModelsDirectory(), configuration.getModels()); - mailDir = configuration.getMailDirectory(); - - // Set up directory watch service - initializeFileWatcher(); - - // Process any existing files that might already be in the directory - initialMailScanAndReply(); - - System.out.println("Mail correspondent running. Press CTRL+c to terminate."); - - // Main loop: watch for file events - while (true) { - WatchKey key; - try { - key = directoryWatcher.take(); - } catch (InterruptedException e) { - System.out.println("Interrupted while waiting for file system events. Exiting."); - break; - } - - System.out.println("Detected filesystem event."); - - // Sleep briefly to allow the file to be fully written - Thread.sleep(1000); - - processDetectedFilesystemEvents(key); - - if (!key.reset()) { - break; - } - } - - directoryWatcher.close(); - } - - /** - * Performs an initial scan of existing files in the mail directory, - * processing those that need AI inference (i.e., that start with "TOCOMPUTE:"). - * - * @throws IOException if reading files fails. - * @throws InterruptedException if the thread is interrupted. - */ - private void initialMailScanAndReply() throws IOException, InterruptedException { - File[] files = mailDir.listFiles(); - if (files == null) return; - - for (File file : files) { - processMailIfNeeded(file); - } - } - - /** - * Checks if a file needs to be processed by verifying that it: - * 1) is not hidden, - * 2) is a regular file, - * 3) starts with "TOCOMPUTE:" in the first line. - * - * @param file the file to inspect. - * @return true if the file meets the criteria for AI processing. - * @throws IOException if reading the file fails. - */ - private boolean isMailProcessingNeeded(File file) throws IOException { - // ignore hidden files - if (file.getName().startsWith(".")) { - return false; - } - - // Check if it's a regular file - if (!file.isFile()) { - return false; - } - - // Ensure the first line says "TOCOMPUTE:" - return fileHasToComputeMarker(file); - } - - /** - * Inspects the first line of the file to see if it starts with "TOCOMPUTE:". - * - * @param file the file to read. - * @return true if the file's first line starts with "TOCOMPUTE:". - * @throws IOException if file reading fails. - */ - private static boolean fileHasToComputeMarker(File file) throws IOException { - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String firstLine = reader.readLine(); - return firstLine != null && firstLine.startsWith("TOCOMPUTE:"); - } - } - - /** - * Processes a file if it has the "TOCOMPUTE:" marker, running an AI - * query and appending the result to the file. Otherwise logs that - * it's being ignored. - * - * @param file the file to possibly process. - * @throws IOException if reading/writing the file fails. - * @throws InterruptedException if the AI query is interrupted. - */ - private void processMailIfNeeded(File file) throws IOException, InterruptedException { - if (!isMailProcessingNeeded(file)) { - System.out.println("Ignoring file: " + file.getName() + " (does not need processing for now)"); - return; - } - - System.out.println("\nReplying to mail: " + file.getName()); - - // Read the mail content - String inputFileContent = getFileContentsAsString(file); - - // Parse the relevant data into a MailQuery object - MailQuery mailQuery = parseInputFileContent(inputFileContent); - - // Create an AiTask and run the query - AiTask aiTask = new AiTask(mailQuery); - String aiGeneratedResponse = aiTask.runAiQuery(); - - // Build new content - StringBuilder resultFileContent = new StringBuilder(); - - // Ensure the user prompt block is labeled if it isn't already - if (!mailQuery.userPrompt.startsWith("* USER:\n")) { - resultFileContent.append("* USER:\n"); - } - resultFileContent.append(mailQuery.userPrompt).append("\n"); - - // Append the AI response block - resultFileContent - .append("* ASSISTANT:\n") - .append(aiGeneratedResponse) - .append("\n"); - - // Write the combined result back to the same file - saveToFile(file, resultFileContent.toString()); - } - - /** - * Converts the raw file content (including the line beginning with "TOCOMPUTE:") - * into a {@link MailQuery} object that the AI can process. - * - * @param inputFileContent the raw contents of the mail file. - * @return a {@link MailQuery} containing the system prompt, user prompt, and the selected model. - * @throws IOException if reading prompt files fails. - */ - private MailQuery parseInputFileContent(String inputFileContent) throws IOException { - MailQuery mailQuery = new MailQuery(); - - // Find the newline that separates "TOCOMPUTE: ..." from the rest - int firstNewLineIndex = inputFileContent.indexOf('\n'); - if (firstNewLineIndex == -1) { - throw new IllegalArgumentException("Input file is only one line long. Content: " + inputFileContent); - } else { - // The user prompt is everything after the first line - mailQuery.userPrompt = inputFileContent.substring(firstNewLineIndex + 1); - } - - // The first line will look like "TOCOMPUTE: model=... prompt=... etc." - String firstLine = inputFileContent.substring(0, firstNewLineIndex); - - // Parse out the key/value pairs - Map settings = parseSettings(firstLine); - - // Look up system prompt from the "prompt" alias - String promptAlias = settings.getOrDefault("prompt", "default"); - mailQuery.systemPrompt = configuration.getPromptByAlias(promptAlias); - - // Resolve model from the "model" alias - String modelAlias = settings.getOrDefault("model", "default"); - Optional modelOptional = modelLibrary.findModelByAlias(modelAlias); - if (!modelOptional.isPresent()) { - throw new IllegalArgumentException("Model with alias '" + modelAlias + "' not found."); - } - mailQuery.model = modelOptional.get(); - - return mailQuery; - } - - /** - * Parses the "TOCOMPUTE:" line, which should look like: - *
TOCOMPUTE: key1=value1 key2=value2 ...
- * - * @param toComputeLine the line beginning with "TOCOMPUTE:". - * @return a map of settings derived from that line. - */ - private Map parseSettings(String toComputeLine) { - if (!toComputeLine.startsWith("TOCOMPUTE:")) { - throw new IllegalArgumentException("Invalid TOCOMPUTE line: " + toComputeLine); - } - - // If there's nothing beyond "TOCOMPUTE:", just return an empty map - if (toComputeLine.length() <= "TOCOMPUTE: ".length()) { - return new HashMap<>(); - } - - // Example format: "TOCOMPUTE: prompt=writer model=mistral" - String[] parts = toComputeLine.substring("TOCOMPUTE: ".length()).split("\\s+"); - Map settings = new HashMap<>(); - - for (String part : parts) { - String[] keyValue = part.split("="); - if (keyValue.length == 2) { - settings.put(keyValue[0], keyValue[1]); - } - } - return settings; - } - - /** - * Handles the filesystem events from the WatchService (e.g. file creation - * or modification), then processes those files if necessary. - * - * @param key the watch key containing the events. - * @throws IOException if file reading/writing fails. - * @throws InterruptedException if the AI process is interrupted. - */ - private void processDetectedFilesystemEvents(WatchKey key) throws IOException, InterruptedException { - for (WatchEvent event : key.pollEvents()) { - WatchEvent.Kind kind = event.kind(); - - // Skip OVERFLOW event - if (kind == StandardWatchEventKinds.OVERFLOW) { - continue; - } - - // The filename for the event - Path filename = ((WatchEvent) event).context(); - System.out.println("Event: " + kind + " for file: " + filename); - - // Process the file - if (kind == ENTRY_CREATE || kind == ENTRY_MODIFY) { - File file = mailDir.toPath().resolve(filename).toFile(); - processMailIfNeeded(file); - } - } - } - - /** - * Registers the mail directory with a WatchService for ENTRY_CREATE - * and ENTRY_MODIFY events. - * - * @throws IOException if registration fails. - */ - private void initializeFileWatcher() throws IOException { - this.directoryWatcher = FileSystems.getDefault().newWatchService(); - Paths.get(mailDir.getAbsolutePath()).register(directoryWatcher, ENTRY_CREATE, ENTRY_MODIFY); - } -} diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java deleted file mode 100644 index f1a5627..0000000 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java +++ /dev/null @@ -1,41 +0,0 @@ -package eu.svjatoslav.alyverkko_cli.commands; - -import eu.svjatoslav.alyverkko_cli.model.Model; - -/** - * Represents the data needed to perform a single mail-based AI query, - * containing prompts and the specific AI model to use. - */ -public class MailQuery { - - /** - * The system prompt text that sets the context or role for the AI. - * This is often used to establish rules or background instructions - * for how the assistant should behave. - */ - public String systemPrompt; - - /** - * The user's prompt text (the main request or query). - */ - public String userPrompt; - - /** - * The AI model to be used for processing this query. - */ - public Model model; - - /** - * Returns a string containing a summary of the {@link MailQuery} object. - * - * @return a string with the system prompt, user prompt, and model info. - */ - @Override - public String toString() { - return "MailQuery{" + - "systemPrompt='" + systemPrompt + '\'' + - ", userPrompt='" + userPrompt + '\'' + - ", model=" + model + - '}'; - } -} diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java index 3a7bd0f..cfe7a2e 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java @@ -3,6 +3,8 @@ package eu.svjatoslav.alyverkko_cli.commands; import eu.svjatoslav.alyverkko_cli.AiTask; import eu.svjatoslav.alyverkko_cli.Command; import eu.svjatoslav.alyverkko_cli.Main; +import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser; +import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.FileOption; import java.io.File; import java.io.IOException; @@ -25,6 +27,15 @@ public class SelftestCommand implements Command { return "selftest"; } + final Parser parser = new Parser(); + + /** + * Optional CLI argument for specifying a configuration file path. + */ + public FileOption configFileOption = parser.add(new FileOption("Configuration file path")) + .addAliases("--config", "-c") + .mustExist(); + /** * Executes the self-test, checking if various directories, files, and * configurations exist and are valid. @@ -37,7 +48,18 @@ public class SelftestCommand implements Command { // Perform selftest checks here System.out.println("Starting selftest..."); - configuration = loadConfiguration(); + if (!parser.parse(cliArguments)) { + System.out.println("Failed to parse commandline arguments"); + parser.showHelp(); + return; + } + + configuration = loadConfiguration(configFileOption.isPresent() ? configFileOption.getValue() : null); + if (configuration == null) { + System.out.println("Failed to load configuration file"); + return; + } + boolean didAllChecksPass = true; // Check if the configuration is loaded diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/mail_correspondant/MailCorrespondentCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/mail_correspondant/MailCorrespondentCommand.java new file mode 100644 index 0000000..7bb24ee --- /dev/null +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/mail_correspondant/MailCorrespondentCommand.java @@ -0,0 +1,344 @@ +package eu.svjatoslav.alyverkko_cli.commands.mail_correspondant; + +import eu.svjatoslav.alyverkko_cli.*; +import eu.svjatoslav.alyverkko_cli.model.Model; +import eu.svjatoslav.alyverkko_cli.model.ModelLibrary; +import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser; +import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.FileOption; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static eu.svjatoslav.alyverkko_cli.Main.configuration; +import static eu.svjatoslav.alyverkko_cli.configuration.Configuration.loadConfiguration; +import static eu.svjatoslav.commons.file.IOHelper.getFileContentsAsString; +import static eu.svjatoslav.commons.file.IOHelper.saveToFile; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +/** + * The MailCorrespondentCommand continuously monitors a specified mail + * directory for new or modified text files, checks if they have a + * "TOCOMPUTE:" marker, and if so, processes them with an AI model. + * Once processed, results are appended to the same file. + * + * Usage: + *
+ *   alyverkko-cli mail
+ * 
+ */ +public class MailCorrespondentCommand implements Command { + + /** + * A command-line parser to handle "mail" command arguments. + */ + final Parser parser = new Parser(); + + /** + * Optional CLI argument for specifying a configuration file path. + */ + public FileOption configFileOption = parser.add(new FileOption("Configuration file path")) + .addAliases("--config", "-c") + .mustExist(); + + /** + * The library of available models, constructed from configuration. + */ + ModelLibrary modelLibrary; + + /** + * The WatchService instance for monitoring file system changes in + * the mail directory. + */ + private WatchService directoryWatcher; + + /** + * The directory that we continuously watch for new tasks. + */ + File mailDir; + + /** + * @return the name of this command, i.e., "mail". + */ + @Override + public String getName() { + return "mail"; + } + + /** + * Executes the "mail" command, loading configuration, starting a + * WatchService on the mail directory, and running an infinite loop + * that processes newly discovered tasks. + * + * @param cliArguments the command-line arguments following the "mail" subcommand. + * @throws IOException if reading/writing tasks fails. + * @throws InterruptedException if the WatchService is interrupted. + */ + @Override + public void execute(String[] cliArguments) throws IOException, InterruptedException { + if (!parser.parse(cliArguments)) { + System.out.println("Failed to parse commandline arguments"); + parser.showHelp(); + return; + } + + configuration = loadConfiguration(configFileOption.isPresent() ? configFileOption.getValue() : null); + if (configuration == null) { + System.out.println("Failed to load configuration file"); + return; + } + + modelLibrary = new ModelLibrary(configuration.getModelsDirectory(), configuration.getModels()); + mailDir = configuration.getMailDirectory(); + + // Set up directory watch service + initializeFileWatcher(); + + // Process any existing files that might already be in the directory + initialMailScanAndReply(); + + System.out.println("Mail correspondent running. Press CTRL+c to terminate."); + + // Main loop: watch for file events + while (true) { + WatchKey key; + try { + key = directoryWatcher.take(); + } catch (InterruptedException e) { + System.out.println("Interrupted while waiting for file system events. Exiting."); + break; + } + + System.out.println("Detected filesystem event."); + + // Sleep briefly to allow the file to be fully written + Thread.sleep(1000); + + processDetectedFilesystemEvents(key); + + if (!key.reset()) { + break; + } + } + + directoryWatcher.close(); + } + + /** + * Performs an initial scan of existing files in the mail directory, + * processing those that need AI inference (i.e., that start with "TOCOMPUTE:"). + * + * @throws IOException if reading files fails. + * @throws InterruptedException if the thread is interrupted. + */ + private void initialMailScanAndReply() throws IOException, InterruptedException { + File[] files = mailDir.listFiles(); + if (files == null) return; + + for (File file : files) { + processMailIfNeeded(file); + } + } + + /** + * Checks if a file needs to be processed by verifying that it: + * 1) is not hidden, + * 2) is a regular file, + * 3) starts with "TOCOMPUTE:" in the first line. + * + * @param file the file to inspect. + * @return true if the file meets the criteria for AI processing. + * @throws IOException if reading the file fails. + */ + private boolean isMailProcessingNeeded(File file) throws IOException { + // ignore hidden files + if (file.getName().startsWith(".")) { + return false; + } + + // Check if it's a regular file + if (!file.isFile()) { + return false; + } + + // Ensure the first line says "TOCOMPUTE:" + return fileHasToComputeMarker(file); + } + + /** + * Inspects the first line of the file to see if it starts with "TOCOMPUTE:". + * + * @param file the file to read. + * @return true if the file's first line starts with "TOCOMPUTE:". + * @throws IOException if file reading fails. + */ + private static boolean fileHasToComputeMarker(File file) throws IOException { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String firstLine = reader.readLine(); + return firstLine != null && firstLine.startsWith("TOCOMPUTE:"); + } + } + + /** + * Processes a file if it has the "TOCOMPUTE:" marker, running an AI + * query and appending the result to the file. Otherwise logs that + * it's being ignored. + * + * @param file the file to possibly process. + * @throws IOException if reading/writing the file fails. + * @throws InterruptedException if the AI query is interrupted. + */ + private void processMailIfNeeded(File file) throws IOException, InterruptedException { + if (!isMailProcessingNeeded(file)) { + System.out.println("Ignoring file: " + file.getName() + " (does not need processing for now)"); + return; + } + + System.out.println("\nReplying to mail: " + file.getName()); + + // Read the mail content + String inputFileContent = getFileContentsAsString(file); + + // Parse the relevant data into a MailQuery object + MailQuery mailQuery = parseInputFileContent(inputFileContent); + + // Create an AiTask and run the query + AiTask aiTask = new AiTask(mailQuery); + String aiGeneratedResponse = aiTask.runAiQuery(); + + // Build new content + StringBuilder resultFileContent = new StringBuilder(); + + // Ensure the user prompt block is labeled if it isn't already + if (!mailQuery.userPrompt.startsWith("* USER:\n")) { + resultFileContent.append("* USER:\n"); + } + resultFileContent.append(mailQuery.userPrompt).append("\n"); + + // Append the AI response block + resultFileContent + .append("* ASSISTANT:\n") + .append(aiGeneratedResponse) + .append("\n"); + + // Write the combined result back to the same file + saveToFile(file, resultFileContent.toString()); + } + + /** + * Converts the raw file content (including the line beginning with "TOCOMPUTE:") + * into a {@link MailQuery} object that the AI can process. + * + * @param inputFileContent the raw contents of the mail file. + * @return a {@link MailQuery} containing the system prompt, user prompt, and the selected model. + * @throws IOException if reading prompt files fails. + */ + private MailQuery parseInputFileContent(String inputFileContent) throws IOException { + MailQuery mailQuery = new MailQuery(); + + // Find the newline that separates "TOCOMPUTE: ..." from the rest + int firstNewLineIndex = inputFileContent.indexOf('\n'); + if (firstNewLineIndex == -1) { + throw new IllegalArgumentException("Input file is only one line long. Content: " + inputFileContent); + } else { + // The user prompt is everything after the first line + mailQuery.userPrompt = inputFileContent.substring(firstNewLineIndex + 1); + } + + // The first line will look like "TOCOMPUTE: model=... prompt=... etc." + String firstLine = inputFileContent.substring(0, firstNewLineIndex); + + // Parse out the key/value pairs + Map settings = parseSettings(firstLine); + + // Look up system prompt from the "prompt" alias + String promptAlias = settings.getOrDefault("prompt", "default"); + mailQuery.systemPrompt = configuration.getPromptByAlias(promptAlias); + + // Resolve model from the "model" alias + String modelAlias = settings.getOrDefault("model", "default"); + Optional modelOptional = modelLibrary.findModelByAlias(modelAlias); + if (!modelOptional.isPresent()) { + throw new IllegalArgumentException("Model with alias '" + modelAlias + "' not found."); + } + mailQuery.model = modelOptional.get(); + + return mailQuery; + } + + /** + * Parses the "TOCOMPUTE:" line, which should look like: + *
TOCOMPUTE: key1=value1 key2=value2 ...
+ * + * @param toComputeLine the line beginning with "TOCOMPUTE:". + * @return a map of settings derived from that line. + */ + private Map parseSettings(String toComputeLine) { + if (!toComputeLine.startsWith("TOCOMPUTE:")) { + throw new IllegalArgumentException("Invalid TOCOMPUTE line: " + toComputeLine); + } + + // If there's nothing beyond "TOCOMPUTE:", just return an empty map + if (toComputeLine.length() <= "TOCOMPUTE: ".length()) { + return new HashMap<>(); + } + + // Example format: "TOCOMPUTE: prompt=writer model=mistral" + String[] parts = toComputeLine.substring("TOCOMPUTE: ".length()).split("\\s+"); + Map settings = new HashMap<>(); + + for (String part : parts) { + String[] keyValue = part.split("="); + if (keyValue.length == 2) { + settings.put(keyValue[0], keyValue[1]); + } + } + return settings; + } + + /** + * Handles the filesystem events from the WatchService (e.g. file creation + * or modification), then processes those files if necessary. + * + * @param key the watch key containing the events. + * @throws IOException if file reading/writing fails. + * @throws InterruptedException if the AI process is interrupted. + */ + private void processDetectedFilesystemEvents(WatchKey key) throws IOException, InterruptedException { + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + + // Skip OVERFLOW event + if (kind == StandardWatchEventKinds.OVERFLOW) { + continue; + } + + // The filename for the event + Path filename = ((WatchEvent) event).context(); + System.out.println("Event: " + kind + " for file: " + filename); + + // Process the file + if (kind == ENTRY_CREATE || kind == ENTRY_MODIFY) { + File file = mailDir.toPath().resolve(filename).toFile(); + processMailIfNeeded(file); + } + } + } + + /** + * Registers the mail directory with a WatchService for ENTRY_CREATE + * and ENTRY_MODIFY events. + * + * @throws IOException if registration fails. + */ + private void initializeFileWatcher() throws IOException { + this.directoryWatcher = FileSystems.getDefault().newWatchService(); + Paths.get(mailDir.getAbsolutePath()).register(directoryWatcher, ENTRY_CREATE, ENTRY_MODIFY); + } +} diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/mail_correspondant/MailQuery.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/mail_correspondant/MailQuery.java new file mode 100644 index 0000000..a33584f --- /dev/null +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/mail_correspondant/MailQuery.java @@ -0,0 +1,41 @@ +package eu.svjatoslav.alyverkko_cli.commands.mail_correspondant; + +import eu.svjatoslav.alyverkko_cli.model.Model; + +/** + * Represents the data needed to perform a single mail-based AI query, + * containing prompts and the specific AI model to use. + */ +public class MailQuery { + + /** + * The system prompt text that sets the context or role for the AI. + * This is often used to establish rules or background instructions + * for how the assistant should behave. + */ + public String systemPrompt; + + /** + * The user's prompt text (the main request or query). + */ + public String userPrompt; + + /** + * The AI model to be used for processing this query. + */ + public Model model; + + /** + * Returns a string containing a summary of the {@link MailQuery} object. + * + * @return a string with the system prompt, user prompt, and model info. + */ + @Override + public String toString() { + return "MailQuery{" + + "systemPrompt='" + systemPrompt + '\'' + + ", userPrompt='" + userPrompt + '\'' + + ", model=" + model + + '}'; + } +}