diff --git a/src/main/java/eu/m724/Main.java b/src/main/java/eu/m724/Main.java index 5f81b69..55cc1b3 100644 --- a/src/main/java/eu/m724/Main.java +++ b/src/main/java/eu/m724/Main.java @@ -7,9 +7,9 @@ import eu.m724.example.OaiSource; import eu.m724.source.ChatResponse; import eu.m724.source.ChatSource; import eu.m724.source.option.Option; +import eu.m724.source.option.Options; -import java.util.Arrays; -import java.util.Map; +import java.util.NoSuchElementException; import java.util.Scanner; public class Main { @@ -26,10 +26,17 @@ public class Main { System.out.printf("Source: \033[1m%s\033[0m %s (%d) by %s\n", source.info().name(), source.info().versionName(), source.info().version(), source.info().author()); Scanner scanner = new Scanner(System.in); + String prompt; while (true) { System.out.print("\n> "); - String prompt = scanner.nextLine(); + + try { + prompt = scanner.nextLine(); + } catch (NoSuchElementException e) { + System.out.println("Exiting"); + break; + } if (!prompt.startsWith(":")) { chat.messages.add(new ChatMessage(false, prompt)); @@ -40,7 +47,7 @@ public class Main { do { token = chatResponse.eventQueue().take(); - if (token.finishReason() != "error") { + if (!"error".equals(token.finishReason())) { // this looks bad but at least idea doesn't nag me if (token.text() != null) { System.out.print(i++ % 2 == 1 ? "\033[1m" : "\033[0m"); System.out.print(token.text()); @@ -86,18 +93,20 @@ public class Main { case "options": case "opt": case "o": + Options options = source.options(); + boolean shouldShowOptions = parts.length < 3; - boolean optionExists = parts.length > 1 && Arrays.asList(source.options().keys()).contains(parts[1]); + boolean optionExists = parts.length > 1 && source.options().getKeys().contains(parts[1]); boolean complainNoOption = parts.length > 1 && !optionExists; String chosenOption = parts.length > 1 ? parts[1] : null; if (!shouldShowOptions) { - Option option = source.options().getOptions().get(parts[1]); - if (option != null) { + try { + Option option = options.getOption(parts[1]); Object value = option.fromString(parts[2]); option.setValue(value); System.out.printf("Set %s to %s\n", option.label, option.getValue()); - } else { + } catch (NoSuchElementException e) { shouldShowOptions = true; System.out.printf("Unknown option \"%s\". ", parts[1]); } @@ -108,16 +117,16 @@ public class Main { System.out.printf("Unknown option \"%s\". ", chosenOption); System.out.println("Available options:"); - for (Map.Entry> entry : source.options().getOptions().entrySet()) { - String value = entry.getValue().getValue().toString() + " (" + entry.getValue().getType().getName() + ")"; + for (Option option : options.getOptions()) { + String value = option.toString() + " (" + option.getType().getName() + ")"; - if (entry.getKey().equals(chosenOption)) { - System.out.printf("\033[1m%s (%s) = %s\033[0m\n", entry.getValue().label, entry.getKey(), value); + if (option.id.equals(chosenOption)) { + System.out.printf("\033[1m%s (%s) = %s\033[0m\n", option.label, option.id, value); } else { - if (entry.getValue().label.toLowerCase().contains("key")) { + if (option.label.toLowerCase().contains("key")) { value = "(looks confidential, specify to see)"; } - System.out.printf("%s (%s) = %s\n", entry.getValue().label, entry.getKey(), value); + System.out.printf("%s (%s) = %s\n", option.label, option.id, value); } } } @@ -151,50 +160,5 @@ public class Main { } System.out.print("\033[0m"); } -/* - ChatResponse chatResponse = source.ask(chat); - - // I was thinking about integrating this into ChatMessage - List tokens = new ArrayList<>(); - List delays = new ArrayList<>(); - - System.out.printf("%s has %d options: %s\n\n", - source.getClass().getName(), - source.options().count(), - source.options().getOptions().values().stream().map(o -> "%s (%s)".formatted(o.label, o.getType().getName())).collect(Collectors.joining(", ")) - ); - - System.out.println("Streaming response now\n"); - ChatEvent token; - - // usually finish reason will be alongside a token but this is simpler - while ((token = chatResponse.eventQueue().take()).finishReason() == null) { - System.out.print(token.text()); - tokens.add(token.text()); - - long now = System.currentTimeMillis(); - delays.add(now); - } - - System.out.println("\n"); - System.out.printf("Tokens: %d\n", tokens.size()); - - long time = delays.getFirst(); - System.out.printf("\"%s\"", tokens.getFirst()); - - for (int i = 1; i < tokens.size(); i++) { - System.out.print(i % 2 == 1 ? "\033[1m" : "\033[0m"); - System.out.printf(" + %dms + \"%s\"", delays.get(i) - time, tokens.get(i).replace("\n", "\\n")); - time = delays.get(i); - - if (i % 5 == 0 && i != tokens.size() - 1) { - System.out.println(" +\033[0m"); - } - } - System.out.println("\033[0m\n"); - - System.out.printf("\033[5mTotal: \033[8m%dms\033[0m\n", delays.getLast() - delays.getFirst()); - - //System.out.printf("Text: %s\n", chatResponse.message().join().text());*/ } } \ No newline at end of file diff --git a/src/main/java/eu/m724/source/option/Option.java b/src/main/java/eu/m724/source/option/Option.java index 942894f..ab3e955 100644 --- a/src/main/java/eu/m724/source/option/Option.java +++ b/src/main/java/eu/m724/source/option/Option.java @@ -34,7 +34,7 @@ public abstract class Option { throw new ClassCastException("Invalid type %s, expected %s".formatted(valueObject.getClass().getName(), getType().getName())); } - T value = (T) valueObject; + T value = (T) valueObject; // :| if (!isValid(value)) throw new IllegalArgumentException("Value invalid"); @@ -46,20 +46,20 @@ public abstract class Option { return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } - // TODO I'm not a fan of that, probably should address the warnings - /** - * checks if the option is valid given current constraints + * checks if a value respects current constraints + * * @param value the checked value * @return is it valid */ abstract boolean isValid(T value); /** - * convert a string to the type of this option - * TODO fix english - * @param text a text representation of a value - * @return a value in an acceptable type + * parse a string as {@link T} + * + * @param text {@link T} but text + * @return a value as {@link T} + * @throws IllegalArgumentException if the text cannot be converted (to {@link T}) */ - public abstract T fromString(String text); + public abstract T fromString(String text) throws IllegalArgumentException; } diff --git a/src/main/java/eu/m724/source/option/Options.java b/src/main/java/eu/m724/source/option/Options.java index b7d9dc8..3b9fcef 100644 --- a/src/main/java/eu/m724/source/option/Options.java +++ b/src/main/java/eu/m724/source/option/Options.java @@ -1,7 +1,6 @@ package eu.m724.source.option; -import java.util.Arrays; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -14,39 +13,122 @@ public class Options { ); } - public int count() { + /** + * how many options + * @return the amount of options + */ + public int optionsCount() { return options.size(); } - public String[] keys() { - return options.keySet().toArray(String[]::new); // TODO I should think whether it's a good idea to cast when I don't have to + /** + * get all keys, also called "option ids" + * @return the keys + */ + public Set getKeys() { + return options.keySet(); } - public Map> getOptions() { - return options; - } // TODO remove that + public Collection> getOptions() { + return options.values(); + } + // + + /** + * get option by id + * @param id the option id + * @return the option + * @throws NoSuchElementException if no option with such id + */ public Option getOption(String id) { - return options.get(id); - } - - public Object getOptionValue(String id, T type) { - return (T) options.get(id).getValue(); - } + Option option = options.get(id); - public String getStringValue(String id) { - return (String) getOptionValue(id, String.class); + if (option == null) { + throw new NoSuchElementException("No option with ID: " + id); + } + + return option; } /** - * set a value of an option + * returns an option value as an {@link Object} + * + * @param id option id + * @return value as object + * @see #getOptionValue(String, Class) + */ + public Object getOptionValue(String id) { + return getOption(id).getValue(); + } + + /** + * return an option value as {@link T} + * you can achieve the same effect by casting {@link #getDoubleValue(String)} + * + * @param id option id + * @param type the type to cast to + * @return the option as {@link T} + * @param the same as {@link .type} + * @see #getOptionValue(String) + */ + public T getOptionValue(String id, Class type) { + Object value = getOptionValue(id); + + if (!type.isInstance(value)) { + throw new ClassCastException("Option \"%s\" is of type \"%s\". You wanted it as \"%s\".".formatted(id, value.getClass().getName(), type.getName())); + } + + return type.cast(value); + } + + // + + public String getStringValue(String id) { + return getOptionValue(id, String.class); + } + + public Integer getIntegerValue(String id) { + return getOptionValue(id, Integer.class); + } + + public Double getDoubleValue(String id) { + return getOptionValue(id, Double.class); + } + + // + + /** + * Set an option's value, hoping you passed the correct type + * * @param id the option id * @param value the value * @throws IllegalArgumentException if value doesn't fit constraints * @throws ClassCastException if type is wrong - * @throws NullPointerException if no such option + * @throws NoSuchElementException if no option with this id */ public void setValue(String id, Object value) { - options.get(id).setValue(value); + Option option = getOption(id); + Class type = option.getType(); + + if (!type.isInstance(value)) { + throw new ClassCastException("Option \"%s\" is of type \"%s\". You passed \"%s\".".formatted(id, value.getClass().getName(), type.getName())); + } + + option.setValue(value); + } + + /** + * Parse {@code text} to an option's type + * + * @param id the option id + * @param text the value as text + * @throws IllegalArgumentException if value doesn't fit constraints, or can't be parsed from {@code text} + * @throws ClassCastException if type is wrong + * @throws NoSuchElementException if no option with this id + */ + public void setStringValue(String id, String text) { + Option option = getOption(id); + option.setValue(option.fromString(text)); } }