diff --git a/.gitignore b/.gitignore index 4fd0163..92acb10 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,5 @@ bin/ /pythonFiles/ /messages/ /logs/ -/data/ +data /cache/ \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f449e21..88d702b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -12,6 +12,7 @@ diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/Core/src/main/java/me/zacharias/chat/core/Core.java b/Core/src/main/java/me/zacharias/chat/core/Core.java index a08a205..8109e82 100644 --- a/Core/src/main/java/me/zacharias/chat/core/Core.java +++ b/Core/src/main/java/me/zacharias/chat/core/Core.java @@ -160,7 +160,7 @@ public class Core { logWriter.close(); LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH-mm-ss"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd_HH-mm-ss"); File messagesFile = new File("./messages/"+now.format(formatter)+".json"); @@ -175,7 +175,7 @@ public class Core { messagesWriter.write(messages.toString()); messagesWriter.close(); - File f = new File("./cache/messages.json"); + File f = new File("./data/messages.json"); if(f.exists()) { f.delete(); @@ -236,14 +236,28 @@ public class Core { /** * Adds a new tool to the System - * @param funtionTool The tool to add + * @param functionTool The tool to add * @param source The source of the tool */ - public void addTool(OllamaFunctionTool funtionTool, @MagicConstant(valuesFromClass = Core.Source.class) String source) { - funtionTools.add(new Pair<>(funtionTool, source)); - ollamaObject.addTool(funtionTool); + public void addTool(OllamaFunctionTool functionTool, @MagicConstant(valuesFromClass = Source.class) String source) { + funtionTools.add(new Pair<>(functionTool, source)); + ollamaObject.addTool(functionTool); } - + + /** + * Adds a list of tools to the System + * @param tools The tools to add + */ + @SuppressWarnings("MagicConstant") + public void addTools(OllamaFunctionTools tools) + { + for(Pair tool : tools) + { + + addTool(tool.getKey(), tool.getValue()); + } + } + /** * Gets the list of tools added to the System * @return The list of tools added to the System compressed as Pairs of the tool and the source @@ -324,6 +338,7 @@ public class Core { throw new RuntimeException("HTTP Response code - " + responseCode); } } catch (IOException e) { +// System.err.println("Error: JSON: "+); throw new RuntimeException(e); } } diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTools.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTools.java new file mode 100644 index 0000000..b281a2e --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTools.java @@ -0,0 +1,111 @@ +package me.zacharias.chat.ollama; + +import me.zacharias.chat.core.Core; +import me.zacharias.chat.core.Pair; +import org.intellij.lang.annotations.MagicConstant; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class OllamaFunctionTools implements Iterable> { + /** + * A list of tools for the OllamaObject. + * OPS! Shuld only be ussed to add a set of tools to the OllamaObject, not to be used for storage of tools internaly or externaly + */ + private ArrayList tools; + /** + * A list of source for the tools. + * OPS! Shuld only be ussed to add a set of tools to the OllamaObject, not to be used for storage of tools internaly or externaly + * OPS! most be the same size as the tools list! since each tool matches to a source! + */ + private ArrayList source; + + /** + * Gets the tools of the {@link OllamaFunctionTools} + * @param tools A list of {@link OllamaFunctionTool} + * @param source The source of the tools + */ + private OllamaFunctionTools(ArrayList tools, ArrayList source) { + if(source == null || tools == null || source.size() != tools.size() || source.isEmpty()) + throw new IllegalArgumentException("The source and tools must be the same size! and not empty!"); + this.tools = tools; + this.source = source; + } + + /** + * Gets the tools of the {@link OllamaFunctionTools} + * @param tools A list of {@link OllamaFunctionTool} + * @param source The source of the tools + */ + private OllamaFunctionTools(OllamaFunctionTool[] tools, @MagicConstant(valuesFromClass = Core.Source.class) String[] source) { + if(source == null || tools == null || source.length != tools.length || source.length == 0) + throw new IllegalArgumentException("The source and tools must be the same size! and not empty!"); + this.tools = new ArrayList<>(); + this.source = new ArrayList<>(); + + for (OllamaFunctionTool tool : tools) { + this.tools.add(tool); + this.source.add(tool.name()); + } + + for (String s : source) { + if (s.equals(Core.Source.INTERNAL)) { + this.source.add(s); + } + } + } + + public static OllamaFunctionToolsBuilder builder() { + return new OllamaFunctionToolsBuilder(); + } + + @Override + public @NotNull Iterator> iterator() { + ArrayList> pairs = new ArrayList<>(); + for (int i = 0; i < tools.size(); i++) { + pairs.add(new Pair<>(tools.get(i), source.get(i))); + } + return pairs.iterator(); + } + + @Override + public void forEach(Consumer> action) { + for (Pair pair : this) { + action.accept(pair); + } + } + + @Override + public Spliterator> spliterator() { + // TODO: Implement this method + throw new UnsupportedOperationException("Not implemented yet"); + //return Iterable.super.spliterator(); + } + + public static class OllamaFunctionToolsBuilder { + private ArrayList tools = new ArrayList<>(); + private ArrayList source = new ArrayList<>(); + + public OllamaFunctionToolsBuilder addTool(OllamaFunctionTool tool, @MagicConstant(valuesFromClass = Core.Source.class) String source) { + this.tools.add(tool); + this.source.add(source); + return this; + } + + public OllamaFunctionToolsBuilder addTools(HashMap tools) { + for (OllamaFunctionTool tool : tools.keySet()) { + this.tools.add(tool); + this.source.add(tools.get(tool)); + } + return this; + } + + public OllamaFunctionTools build() { + return new OllamaFunctionTools(tools, source); + } + } +} diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java index cbd6bbb..9f1bb3e 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java @@ -75,7 +75,7 @@ public class OllamaObject { LaunchOptions launchOptions = new LaunchOptions(); if(launchOptions.isLoadOld()) { System.out.println("Loading old data..."); - File f = new File("./cache/messages.json"); + File f = new File(Core.DATA_DIR+"/messages.json"); if(f.exists()) { try { BufferedReader br = new BufferedReader(new FileReader(f)); @@ -375,7 +375,8 @@ public class OllamaObject { FileHandler fileHandler = new FileHandler(baseDirectory); if(false); - return this; + + throw new IllegalArgumentException("FileHandler is not supported yet!"); } /** diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaPerameter.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaPerameter.java index c560cf4..1f798ed 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaPerameter.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaPerameter.java @@ -192,7 +192,15 @@ public class OllamaPerameter { /** * Represents a boolean parameter. */ - BOOLEAN("boolean"); + BOOLEAN("boolean"), + /** + * Represents a enum parameter. + */ + ENUM("enum"), + /** + * Represents a array parameter. + */ + ARRAY("array"); /** * The type of the parameter. diff --git a/Display/build.gradle b/Display/build.gradle index a3a65f9..995f01c 100644 --- a/Display/build.gradle +++ b/Display/build.gradle @@ -7,6 +7,7 @@ version = '1.0-SNAPSHOT' dependencies { implementation project(":Core") + implementation project(":MALAPITool") } test { diff --git a/Display/src/main/java/me/zacharias/chat/display/Display.java b/Display/src/main/java/me/zacharias/chat/display/Display.java index c41c7b4..ff2ecc2 100644 --- a/Display/src/main/java/me/zacharias/chat/display/Display.java +++ b/Display/src/main/java/me/zacharias/chat/display/Display.java @@ -5,6 +5,7 @@ import me.zacharias.chat.core.Pair; import me.zacharias.chat.core.PrintMessageHandler; import me.zacharias.chat.core.files.FileHandlerLocation; import me.zacharias.chat.core.memory.CoreMemory; +import me.zacharias.chat.mal.api.MALAPITool; import me.zacharias.chat.ollama.*; import org.json.JSONObject; @@ -42,16 +43,18 @@ public class Display { public Display() { - core.setOllamaObject(OllamaObject.builder() - .setModel("llama3.2") - //.setModel("deepseek-r1") + core.setOllamaObject/*NoMemory*/(OllamaObject.builder() + //.setModel("llama3.2") + //.setModel("gemma3:12b") + .setModel("qwen3:8b") .keep_alive(10) //.stream(false) - .addFileTools(FileHandlerLocation.DATA_FILES) + //.addFileTools(FileHandlerLocation.DATA_FILES) .build()); - //core.addTool(new TimeTool(), Core.Source.INTERNAL); + core.addTool(new TimeTool(), Core.Source.INTERNAL); //core.addTool(new PythonRunner(core), Core.Source.INTERNAL); + core.addTools(new MALAPITool().getOllamaTools()); //core.getOllamaObject().addMessage(new OllamaMessage(OllamaMessageRole.SYSTEM, "Have a nice tone and use formal wording")); @@ -101,7 +104,7 @@ public class Display { System.exit(0); return; case "write": - core.flushLog(); + Core.flushLog(); break; case "peek": CoreMemory coreMemory = CoreMemory.getInstance(); diff --git a/MALAPITool/README.md b/MALAPITool/README.md new file mode 100644 index 0000000..1c8fa19 --- /dev/null +++ b/MALAPITool/README.md @@ -0,0 +1,17 @@ +# MAL API wrapper + +## Important notes +### Scope +This wrapper is not fully implemented, some endpoints are missing, as well as no user-specific endpoints are added. +### Rate limits +I'm yet to enforce the rate limit, due to in there the Ollama backend won't be fast enough, but this is subject to change +### API Key Required +You must provide your own API key + +Store the key in `${DATE_DIR}/malapi.json` using the following format: +```jsonc +{ + "client_id": "", + "client_secret": ""// This is for the moment not used but shuld till be added +} +``` \ No newline at end of file diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALAPITool.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALAPITool.java new file mode 100644 index 0000000..f87a414 --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALAPITool.java @@ -0,0 +1,108 @@ +package me.zacharias.chat.mal.api; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; +import me.zacharias.chat.core.Core; +import me.zacharias.chat.ollama.*; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; + +public class MALAPITool { + + public final String Client_ID; + public final String Client_Secret; + public final String BaseURL = "https://api.myanimelist.net/v2"; + public final OllamaFunctionTools MALAPITools; + + public MALAPITool() { + super(); + try { + JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/malapi.json"))); + this.Client_ID = obj.getString("client_id"); + this.Client_Secret = obj.getString("client_secret"); + } catch (IOException e) { + throw new RuntimeException(e); + } + + OllamaFunctionTools.OllamaFunctionToolsBuilder builder = new OllamaFunctionTools.OllamaFunctionToolsBuilder(); + + try(ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("me.zacharias.chat.mal.api.endpoints").scan()) { + ClassInfoList endpoints = scanResult.getClassesWithAnnotation(MALEndpoint.class.getName()); + for (ClassInfo classInfo : endpoints) { + Class clazz = classInfo.loadClass(); + if (OllamaFunctionTool.class.isAssignableFrom(clazz)) { + //System.out.println("Found endpoint: " + clazz.getName() + " With constructor: " + clazz.getDeclaredConstructors().f.getName() + " and arguments: " + Arrays.toString(clazz.getDeclaredConstructor().getParameterTypes())); + MALEndpointTool tool = (MALEndpointTool) clazz.getDeclaredConstructor(MALAPITool.class).newInstance(MALAPITool.this); + builder.addTool(tool, Core.Source.INTERNAL); + } + } + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + + this.MALAPITools = builder.build(); + } + + public OllamaFunctionTools getOllamaTools() { + return this.MALAPITools; + } + + public JSONObject APIRequest(String endpoint, HashMap params) { + try { + + String urlParams = ParameterStringBuilder.getParamsString(params); + + URL url = new URI(this.BaseURL + endpoint + "?" + urlParams).toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("X-MAL-CLIENT-ID", this.Client_ID); + connection.setDoOutput(true); + connection.setConnectTimeout(80 * 1000); + + int responseCode = connection.getResponseCode(); + + // HTTP_OK or 200 response code generally means that the server ran successfully without any errors + StringBuilder response = new StringBuilder(); + + // Read response content + // connection.getInputStream() purpose is to obtain an input stream for reading the server's response. + try ( + BufferedReader reader = new BufferedReader( new InputStreamReader( connection.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + response.append(line); // Adds every line to response till the end of file. + } + } + + if (responseCode == HttpURLConnection.HTTP_OK) { + + + connection.disconnect(); + return new JSONObject(response.toString()); + } + else { + connection.disconnect(); + System.err.println("Error: HTTP Response code - " + responseCode + "\n"+response.toString()); + throw new RuntimeException("HTTP Response code - " + responseCode); + } + }catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error: " + e.getMessage()); + } + } +} diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALEndpoint.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALEndpoint.java new file mode 100644 index 0000000..baba13d --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALEndpoint.java @@ -0,0 +1,11 @@ +package me.zacharias.chat.mal.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MALEndpoint { +} diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALEndpointTool.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALEndpointTool.java new file mode 100644 index 0000000..5b8eaaf --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/MALEndpointTool.java @@ -0,0 +1,11 @@ +package me.zacharias.chat.mal.api; + +import me.zacharias.chat.ollama.OllamaFunctionTool; + +public abstract class MALEndpointTool extends OllamaFunctionTool { + protected MALAPITool MALAPIToolInstance; + public MALEndpointTool(MALAPITool malAPITool) { + super(); + this.MALAPIToolInstance = malAPITool; + } +} diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/ParameterStringBuilder.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/ParameterStringBuilder.java new file mode 100644 index 0000000..acbcafc --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/ParameterStringBuilder.java @@ -0,0 +1,25 @@ +package me.zacharias.chat.mal.api; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class ParameterStringBuilder { + public static String getParamsString(Map params) + throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder(); + + for (Map.Entry entry : params.entrySet()) { + result.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8)); + result.append("="); + result.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)); + result.append("&"); + } + + String resultString = result.toString(); + return !resultString.isEmpty() + ? resultString.substring(0, resultString.length() - 1) + : resultString; + } +} \ No newline at end of file diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetAnimeDetails.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetAnimeDetails.java new file mode 100644 index 0000000..08c4383 --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetAnimeDetails.java @@ -0,0 +1,86 @@ +package me.zacharias.chat.mal.api.endpoints; + +import me.zacharias.chat.mal.api.MALAPITool; +import me.zacharias.chat.mal.api.MALEndpoint; +import me.zacharias.chat.mal.api.MALEndpointTool; +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; + +@MALEndpoint +public class GetAnimeDetails extends MALEndpointTool { + public GetAnimeDetails(MALAPITool malAPITool) { + super(malAPITool); + } + + @Override + public String name() { + return "get_anime_details"; + } + + @Override + public String description() { + return "Gets the details of an anime from the MAL API"; + } + + @Override + public OllamaPerameter parameters() { + return OllamaPerameter.builder() + .addProperty("id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The id of the anime", true) + .addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return, defaults to [\"id\", \"title\", \"synopsis\", \"genres\"]") + .build(); + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + int id = -1; + String[] fields = null; + + for (OllamaFunctionArgument arg : args) { + switch (arg.argument()) { + case "id": + id = (Integer) arg.value(); + break; + case "fields": + JSONArray jsonArray = (JSONArray) arg.value(); + for (int i = 0; i < jsonArray.length(); i++) { + if (jsonArray.get(i) instanceof String) { + if (fields == null) { + fields = new String[jsonArray.length()]; + } + fields[i] = jsonArray.getString(i); + } else { + throw new OllamaToolErrorException(this.name(), "The fields must be a string array"); + } + } + break; + } + } + + if (id == -1) { + throw new OllamaToolErrorException(this.name(), "The id must be specified"); + } + + if (fields == null) { + fields = new String[]{ + "id", + "title", + "synopsis", + "genres" + }; + } + + HashMap map = new HashMap<>(); + map.put("fields", String.join(",", fields)); + + JSONObject obj = MALAPIToolInstance.APIRequest("/anime/"+id, map); + + return new OllamaToolRespnce(this.name(), obj.toString()); + } +} diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetAnimeList.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetAnimeList.java new file mode 100644 index 0000000..128c635 --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetAnimeList.java @@ -0,0 +1,88 @@ +package me.zacharias.chat.mal.api.endpoints; + +import me.zacharias.chat.mal.api.MALAPITool; +import me.zacharias.chat.mal.api.MALEndpoint; +import me.zacharias.chat.mal.api.MALEndpointTool; +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +@MALEndpoint +public class GetAnimeList extends MALEndpointTool { + public GetAnimeList(MALAPITool malAPITool) { + super(malAPITool); + } + + @Override + public String name() { + return "get_anime_list"; + } + + @Override + public String description() { + return "Gets a list of anime from the MAL API"; + } + + @Override + public OllamaPerameter parameters() { + return OllamaPerameter.builder() + .addProperty("query", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The query to search for",true) + .addProperty("offset", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The offset to start from") + .addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return") + .build(); + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + String query = ""; + int offset = 0; + String[] fields = null; + + for (OllamaFunctionArgument arg : args) { + switch (arg.argument()) { + case "query": + query = (String)arg.value(); + break; + case "offset": + offset = (Integer) arg.value(); + break; + case "fields": + JSONArray jsonArray = (JSONArray) arg.value(); + if(!jsonArray.isEmpty()) { + fields = new String[jsonArray.length()]; + for (int i = 0; i < jsonArray.length(); i++) { + if (jsonArray.get(i) instanceof String) { + fields[i] = jsonArray.getString(i); + } else { + throw new OllamaToolErrorException(this.name(), "The fields must be a string array"); + } + } + } + break; + } + } + + if (query.isEmpty()) { + throw new OllamaToolErrorException(this.name(), "Query cannot be empty"); + } + + HashMap params = new HashMap<>(); + params.put("q", query); + if (offset > 0) { + params.put("offset", String.valueOf(offset)); + } + if (fields != null) { + params.put("fields", String.join(",", fields)); + } + + JSONObject response = MALAPIToolInstance.APIRequest("/anime", params); + + return new OllamaToolRespnce(this.name(), response.toString()); + } +} diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetManagDetails.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetManagDetails.java new file mode 100644 index 0000000..2992789 --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetManagDetails.java @@ -0,0 +1,85 @@ +package me.zacharias.chat.mal.api.endpoints; + +import me.zacharias.chat.mal.api.MALAPITool; +import me.zacharias.chat.mal.api.MALEndpoint; +import me.zacharias.chat.mal.api.MALEndpointTool; +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.HashMap; + +@MALEndpoint +public class GetManagDetails extends MALEndpointTool { + public GetManagDetails(MALAPITool malAPITool) { + super(malAPITool); + } + + @Override + public String name() { + return "get_manag_details"; + } + + @Override + public String description() { + return "Gets the details of an manga from the MAL API"; + } + + @Override + public OllamaPerameter parameters() { + return OllamaPerameter.builder() + .addProperty("id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The id of the manga", true) + .addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return, defaults to [\"id\", \"title\", \"synopsis\", \"genres\"]") + .build(); + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + int id = -1; + String[] fields = null; + + for (OllamaFunctionArgument arg : args) { + switch (arg.argument()) { + case "id": + id = (Integer) arg.value(); + break; + case "fields": + JSONArray jsonArray = (JSONArray) arg.value(); + for (int i = 0; i < jsonArray.length(); i++) { + if (jsonArray.get(i) instanceof String) { + if (fields == null) { + fields = new String[jsonArray.length()]; + } + fields[i] = jsonArray.getString(i); + } else { + throw new OllamaToolErrorException(this.name(), "The fields must be a string array"); + } + } + break; + } + } + + if (id == -1) { + throw new OllamaToolErrorException(this.name(), "The id must be specified"); + } + + if (fields == null) { + fields = new String[]{ + "id", + "title", + "synopsis", + "genres" + }; + } + + HashMap map = new HashMap<>(); + map.put("fields", String.join(",", fields)); + + JSONObject obj = MALAPIToolInstance.APIRequest("/manga/"+id, map); + + return new OllamaToolRespnce(this.name(), obj.toString()); + } +} diff --git a/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetMangaList.java b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetMangaList.java new file mode 100644 index 0000000..eba881d --- /dev/null +++ b/MALAPITool/src/main/java/me/zacharias/chat/mal/api/endpoints/GetMangaList.java @@ -0,0 +1,87 @@ +package me.zacharias.chat.mal.api.endpoints; + +import me.zacharias.chat.mal.api.MALAPITool; +import me.zacharias.chat.mal.api.MALEndpoint; +import me.zacharias.chat.mal.api.MALEndpointTool; +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.HashMap; + +@MALEndpoint +public class GetMangaList extends MALEndpointTool { + public GetMangaList(MALAPITool malAPITool) { + super(malAPITool); + } + + @Override + public String name() { + return "get_manga_list"; + } + + @Override + public String description() { + return "Gets a list of manga from the MAL API"; + } + + @Override + public OllamaPerameter parameters() { + return OllamaPerameter.builder() + .addProperty("query", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The query to search for",true) + .addProperty("offset", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The offset to start from") + .addProperty("fields", OllamaPerameter.OllamaPerameterBuilder.Type.ARRAY, "The fields to return") + .build(); + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + String query = ""; + int offset = 0; + String[] fields = null; + + for (OllamaFunctionArgument arg : args) { + switch (arg.argument()) { + case "query": + query = (String)arg.value(); + break; + case "offset": + offset = (Integer) arg.value(); + break; + case "fields": + JSONArray jsonArray = (JSONArray) arg.value(); + if(!jsonArray.isEmpty()) { + fields = new String[jsonArray.length()]; + for (int i = 0; i < jsonArray.length(); i++) { + if (jsonArray.get(i) instanceof String) { + fields[i] = jsonArray.getString(i); + } else { + throw new OllamaToolErrorException(this.name(), "The fields must be a string array"); + } + } + } + break; + } + } + + if (query.isEmpty()) { + throw new OllamaToolErrorException(this.name(), "Query cannot be empty"); + } + + HashMap params = new HashMap<>(); + params.put("q", query); + if (offset > 0) { + params.put("offset", String.valueOf(offset)); + } + if (fields != null) { + params.put("fields", String.join(",", fields)); + } + + JSONObject response = MALAPIToolInstance.APIRequest("/manga", params); + + return new OllamaToolRespnce(this.name(), response.toString()); + } +} diff --git a/settings.gradle b/settings.gradle index dcd3864..4d6d8b9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ rootProject.name = 'AI-test' include 'API', 'Core', 'Display', 'launcher' + +include 'MALAPITool' \ No newline at end of file