From 8e44c1138565e091f2444beddbe351a14de0a856 Mon Sep 17 00:00:00 2001 From: Zacharias Date: Sat, 24 May 2025 18:11:58 +0200 Subject: [PATCH] feat(api): major refactor from TCP socket to HTTP REST via Spring Boot This commit introduces a large-scale refactor that replaces the existing TCP-based API with a Spring Boot-powered HTTP REST architecture. Due to the size and scope of this change, only essential structural notes are included below. High-level changes: - Replaced TCP-based communication with RESTful endpoints - Introduced Spring Boot for API handling and configuration - Refactored internal core logic to support REST architecture New/Updated API components: - `APIApplication.java`: Main Spring Boot entry point - `MessageController.java`: Handles LLM-related queries - `ToolController.java`: Handles adding/removing tools - `NewToolRequest.java` / `NewToolResponse.java`: Data models for tool addition - `NewQueryResponseHook.java`: Webhook handler for LLM query results - `WebhookError.java`: Model for reporting webhook errors - `EnableIfNotDisplay.java`: Conditional configuration for TTY context - Other supporting classes (e.g., `ToolArgument`, `ToolRequest`) Core changes: - `Core.java`: Removed deprecated `addFunctionTool`, added `removeTool` - `LaunchOptions.java`: Added `notDisplay` flag for headless operation - `OllamaObject.java`: Implements tool removal logic Launcher/display changes: - `Launcher.java`: Starts `APIApplication` if not in TTY mode - `Display.java`: Integrates REST API contextually with TTY display NOTE: Several classes are included but not yet fully utilized; these are placeholders for upcoming features (e.g., `MessageResponse`, `ToolRequest`). BREAKING CHANGE: This refactors removes all TCP-based API code and replaces it with HTTP REST using Spring Boot. Any clients or modules depending on the old TCP interface will need to be updated. --- .gitignore | 3 +- API/build.gradle | 16 +- .../me/zacharias/chat/api/APIApplication.java | 127 +++++++ .../java/me/zacharias/chat/api/APICodes.java | 8 - .../me/zacharias/chat/api/APIEndpoints.java | 33 -- .../me/zacharias/chat/api/APIErrorCodes.java | 19 - .../java/me/zacharias/chat/api/APITool.java | 92 +++++ .../chat/api/QuerryResponceEndpoint.java | 31 ++ .../zacharias/chat/api/client/APIClient.java | 4 - .../api/condations/EnableIfNotDisplay.java | 13 + .../chat/api/controllers/Message.java | 58 +++ .../zacharias/chat/api/controllers/Tool.java | 40 +++ .../chat/api/payload/MessageResponce.java | 19 + .../chat/api/payload/ToolArgument.java | 33 ++ .../chat/api/payload/ToolRequest.java | 19 + .../payload/request/NewQurryResponceHook.java | 45 +++ .../api/payload/request/NewToolRequest.java | 69 ++++ .../api/payload/response/NewToolResponse.java | 44 +++ .../api/payload/webhook/WebhookError.java | 31 ++ .../webhook/responce/APIToolResponse.java | 35 ++ .../zacharias/chat/api/server/APIServer.java | 329 ------------------ .../me/zacharias/chat/api/server/Client.java | 38 -- .../java/me/zacharias/chat/core/Core.java | 24 +- .../me/zacharias/chat/core/GlobalObjects.java | 29 ++ .../me/zacharias/chat/core/LaunchOptions.java | 17 + .../zacharias/chat/ollama/OllamaObject.java | 4 + Display/build.gradle | 1 + .../me/zacharias/chat/display/Display.java | 5 +- README.md | 6 +- launcher/build.gradle | 15 +- .../me/zacharias/chat/launcher/Launcher.java | 16 +- 31 files changed, 771 insertions(+), 452 deletions(-) create mode 100644 API/src/main/java/me/zacharias/chat/api/APIApplication.java delete mode 100644 API/src/main/java/me/zacharias/chat/api/APICodes.java delete mode 100644 API/src/main/java/me/zacharias/chat/api/APIEndpoints.java delete mode 100644 API/src/main/java/me/zacharias/chat/api/APIErrorCodes.java create mode 100644 API/src/main/java/me/zacharias/chat/api/APITool.java create mode 100644 API/src/main/java/me/zacharias/chat/api/QuerryResponceEndpoint.java delete mode 100644 API/src/main/java/me/zacharias/chat/api/client/APIClient.java create mode 100644 API/src/main/java/me/zacharias/chat/api/condations/EnableIfNotDisplay.java create mode 100644 API/src/main/java/me/zacharias/chat/api/controllers/Message.java create mode 100644 API/src/main/java/me/zacharias/chat/api/controllers/Tool.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/MessageResponce.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/ToolArgument.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/ToolRequest.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/request/NewQurryResponceHook.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/request/NewToolRequest.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/response/NewToolResponse.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/webhook/WebhookError.java create mode 100644 API/src/main/java/me/zacharias/chat/api/payload/webhook/responce/APIToolResponse.java delete mode 100644 API/src/main/java/me/zacharias/chat/api/server/APIServer.java delete mode 100644 API/src/main/java/me/zacharias/chat/api/server/Client.java create mode 100644 Core/src/main/java/me/zacharias/chat/core/GlobalObjects.java diff --git a/.gitignore b/.gitignore index 92acb10..55589c3 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ bin/ /messages/ /logs/ data -/cache/ \ No newline at end of file +/cache/ +/run/ diff --git a/API/build.gradle b/API/build.gradle index 01fb848..58028cf 100644 --- a/API/build.gradle +++ b/API/build.gradle @@ -1,5 +1,8 @@ plugins { id 'java' + + id 'org.springframework.boot' version '3.2.2' + id 'io.spring.dependency-management' version '1.1.4' } group = 'me.zacharias' @@ -7,6 +10,15 @@ version = '1.0-SNAPSHOT' dependencies { implementation project(":Core") + + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + //implementation 'org.springframework.boot:spring-boot-starter-actuator' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + + //runtimeOnly('org.springframework.boot:spring-boot-starter-web') } test { @@ -15,6 +27,6 @@ test { jar{ manifest { - attributes 'Main-Class': 'me.zacharias.char.api.APIServer' + attributes 'Main-Class': 'me.zacharias.chat.api.APIApplication' } -} \ No newline at end of file +} diff --git a/API/src/main/java/me/zacharias/chat/api/APIApplication.java b/API/src/main/java/me/zacharias/chat/api/APIApplication.java new file mode 100644 index 0000000..37a47d7 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/APIApplication.java @@ -0,0 +1,127 @@ +package me.zacharias.chat.api; + +import me.zacharias.chat.api.payload.request.NewQurryResponceHook; +import me.zacharias.chat.api.payload.request.NewToolRequest; +import me.zacharias.chat.core.Core; +import me.zacharias.chat.core.GlobalObjects; +import me.zacharias.chat.core.PrintMessageHandler; +import me.zacharias.chat.ollama.OllamaObject; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@SpringBootApplication +public class APIApplication { + public static void start(){ + ConfigurableApplicationContext ctx = SpringApplication.run(APIApplication.class); + + } + + public static void main(String[] args) { + SpringApplication.run(APIApplication.class, args); + } + + private static APIApplication instance; + private final Map tools = new HashMap<>(); + private final ArrayList querryResponceEndpoints = new ArrayList<>(); + private final Core core; + + WebClient webClient = WebClient.create(); + + public APIApplication() + { + if(instance != null) + throw new IllegalStateException("APIApplication is already running!"); + + instance = this; + + if(GlobalObjects.getObject("core") instanceof Core coreInstance) { + this.core = coreInstance; + } else { + this.core = new Core(new PrintMessageHandler() { + @Override + public void printMessage(String message) { + synchronized (querryResponceEndpoints) { + querryResponceEndpoints.forEach(endpoint -> { + webClient.post().uri(endpoint.getUrl()).bodyValue(message).retrieve() + .onStatus( + status -> status.is4xxClientError() || status.is5xxServerError() || status.isError(), + clientResponse -> { + return clientResponse.bodyToMono(String.class).flatMap(body -> { + endpoint.sendError("Failed to send message for qurry response"); + querryResponceEndpoints.remove(endpoint); + return Mono.error(new RuntimeException("Webhook call failed with status: " + clientResponse.statusCode())); + }); + }) + .toBodilessEntity() + .doOnError(e -> { + + }) + .subscribe(); + }); + } + } + + @Override + public boolean color() { + return false; + } + }); + + core.setOllamaObject(OllamaObject.builder() + .setModel("qwen3:8b") + .keep_alive(10) + .build()); + } + } + + public static APIApplication getInstance() { + return instance; + } + + public boolean addTool(String name, NewToolRequest request) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Tool name cannot be null or empty"); + } + if (tools.containsKey(name)) { + return false; // Tool with this name already exists + } + + APITool tool = new APITool(request); + tools.put(name, tool); + core.addTool(tool, Core.Source.API); + return true; + } + + public boolean removeTool(String name) { + if(name == null || name.isEmpty()) { + throw new IllegalArgumentException("Tool name cannot be null or empty"); + } + if (!tools.containsKey(name)) { + return false; // Tool with this name does not exist + } + APITool tool = tools.remove(name); + core.removeTool(tool.name()); + return true; + } + + public void addQurryResponseHook(NewQurryResponceHook request) { + if (request == null || request.getUrl() == null || request.getUrl().isEmpty()) { + throw new IllegalArgumentException("Request and URL cannot be null or empty"); + } + QuerryResponceEndpoint endpoint = new QuerryResponceEndpoint(request.getUrl(), request.getErrorUrl()); + synchronized (querryResponceEndpoints) { + querryResponceEndpoints.add(endpoint); + } + } + + public Core getCore() { + return core; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/APICodes.java b/API/src/main/java/me/zacharias/chat/api/APICodes.java deleted file mode 100644 index 08a452f..0000000 --- a/API/src/main/java/me/zacharias/chat/api/APICodes.java +++ /dev/null @@ -1,8 +0,0 @@ -package me.zacharias.chat.api; - -public enum APICodes { - CREDENTIALS, - ERROR, - API, - SUCCESS, -} diff --git a/API/src/main/java/me/zacharias/chat/api/APIEndpoints.java b/API/src/main/java/me/zacharias/chat/api/APIEndpoints.java deleted file mode 100644 index 45405c2..0000000 --- a/API/src/main/java/me/zacharias/chat/api/APIEndpoints.java +++ /dev/null @@ -1,33 +0,0 @@ -package me.zacharias.chat.api; - -/** - * API Endpoints - * see API Docs - */ -public enum APIEndpoints { - // API request endpoints - /** - * Requests a tool to be added to the environment - */ - ADD_TOOL, - /** - * Requests a query to be executed on the environment - */ - QUERY, - - // API response endpoints - /** - * Response to a tool request containing the Ollama response - */ - RESPONSE, - /** - * Response to use a tool defined by the API Client - */ - USE_TOOL, - - // API endpoints that doesn't require credentials even if the server has them required - /** - * Returns info about the server - */ - INFO -} diff --git a/API/src/main/java/me/zacharias/chat/api/APIErrorCodes.java b/API/src/main/java/me/zacharias/chat/api/APIErrorCodes.java deleted file mode 100644 index 04250db..0000000 --- a/API/src/main/java/me/zacharias/chat/api/APIErrorCodes.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.zacharias.chat.api; - -public enum APIErrorCodes { - /** - * If the format is not a JSON or not having the expected JSON keys present - */ - FORMAT_ERROR, - - /** - * An error with credentials
- * Can be that the credentials is not matching or other credentials related issue - */ - CREDENTIALS_ERROR, - - /** - * An error describing that the APIEndpoint is unavailable, invalid, or related issue - */ - API_ERROR, -} diff --git a/API/src/main/java/me/zacharias/chat/api/APITool.java b/API/src/main/java/me/zacharias/chat/api/APITool.java new file mode 100644 index 0000000..b7227c6 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/APITool.java @@ -0,0 +1,92 @@ +package me.zacharias.chat.api; + +import me.zacharias.chat.api.payload.ToolArgument; +import me.zacharias.chat.api.payload.request.NewToolRequest; +import me.zacharias.chat.api.payload.webhook.responce.APIToolResponse; +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaFunctionTool; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +public class APITool extends OllamaFunctionTool { + + /** + * The name of the tool. + */ + private final String name; + /** + * The description of the tool. + */ + private final String description; + /** + * The arguments this tool will take. + */ + private final ToolArgument[] arguments; + /** + * The URL this api will call when the tool is used. + */ + private final String requestUrl; + + RestTemplate restTemplate = new RestTemplate(); + + public APITool(NewToolRequest request) { + super(); + this.name = request.getName(); + this.description = request.getDescription(); + this.arguments = request.getArguments(); + this.requestUrl = request.getRequestUrl(); + if (this.name == null || this.name.isEmpty()) { + throw new IllegalArgumentException("Tool name cannot be null or empty"); + } + } + + @Override + public String name() { + return name; + } + + @Override + public String description() { + return description; + } + + @Override + public OllamaPerameter parameters() { + OllamaPerameter.OllamaPerameterBuilder parameter = OllamaPerameter.builder(); + for (ToolArgument argument : arguments) { + parameter.addProperty(argument.getName(), argument.getType(), argument.getDescription(), argument.isRequired()); + } + return parameter.build(); + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(requestUrl); + for (OllamaFunctionArgument arg : args) { + builder.queryParam(arg.argument(), arg.value()); + } + String url = builder.toUriString(); + + ResponseEntity response = restTemplate.getForEntity(url, APIToolResponse.class); + + if (response.getStatusCode().is2xxSuccessful()){ + if (response.getBody() == null) { + throw new OllamaToolErrorException(name, "Response body is null"); + } + if(response.getBody().getError() != null && !response.getBody().getError().isEmpty()) + throw new OllamaToolErrorException(name, response.getBody().getError()); + + return new OllamaToolRespnce(name, response.getBody().getResponse()); + } + else { + if(response.getBody() == null) { + throw new OllamaToolErrorException(name, "Failed to call API: " + response.getStatusCode() + " - Response body is null"); + } + throw new OllamaToolErrorException(name, "Failed to call API: " + response.getStatusCode() + " - " + response.getBody().getError()); + } + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/QuerryResponceEndpoint.java b/API/src/main/java/me/zacharias/chat/api/QuerryResponceEndpoint.java new file mode 100644 index 0000000..e08160f --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/QuerryResponceEndpoint.java @@ -0,0 +1,31 @@ +package me.zacharias.chat.api; + +import me.zacharias.chat.api.payload.webhook.WebhookError; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; + +public class QuerryResponceEndpoint { + String url; + String errorUrl; + + public QuerryResponceEndpoint(String url, String errorUrl) { + this.url = url; + this.errorUrl = errorUrl; + } + + public String getUrl() { + return url; + } + + public void sendError(String error){ + WebClient.create().post() + .uri(errorUrl) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(new WebhookError(url, error)) + .retrieve() + .toBodilessEntity() + .doOnError(iggnore -> {}) + .subscribe(); + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/client/APIClient.java b/API/src/main/java/me/zacharias/chat/api/client/APIClient.java deleted file mode 100644 index 69d3e83..0000000 --- a/API/src/main/java/me/zacharias/chat/api/client/APIClient.java +++ /dev/null @@ -1,4 +0,0 @@ -package me.zacharias.chat.api.client; - -public class APIClient { -} diff --git a/API/src/main/java/me/zacharias/chat/api/condations/EnableIfNotDisplay.java b/API/src/main/java/me/zacharias/chat/api/condations/EnableIfNotDisplay.java new file mode 100644 index 0000000..d4cbea1 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/condations/EnableIfNotDisplay.java @@ -0,0 +1,13 @@ +package me.zacharias.chat.api.condations; + +import me.zacharias.chat.core.LaunchOptions; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class EnableIfNotDisplay implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return LaunchOptions.getInstance().isNotDisplay(); + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/controllers/Message.java b/API/src/main/java/me/zacharias/chat/api/controllers/Message.java new file mode 100644 index 0000000..6fdecc9 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/controllers/Message.java @@ -0,0 +1,58 @@ +package me.zacharias.chat.api.controllers; + +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import me.zacharias.chat.api.APIApplication; +import me.zacharias.chat.api.condations.EnableIfNotDisplay; +import me.zacharias.chat.api.payload.request.NewQurryResponceHook; +import me.zacharias.chat.ollama.OllamaMessage; +import me.zacharias.chat.ollama.OllamaMessageRole; +import org.springframework.context.annotation.Conditional; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Conditional(EnableIfNotDisplay.class) +@RestController +@RequestMapping("/v1/message") +public class Message { + + private final APIApplication apiApplication; + + public Message(APIApplication apiApplication) { + this.apiApplication = apiApplication; + } + + @GetMapping("/query") + public ResponseEntity query(@RequestParam(name = "query", required = true) String query) { + if (query == null || query.isEmpty()) { + return ResponseEntity.badRequest().body("Query cannot be null or empty"); + } + Thread t = new Thread(() -> { + apiApplication.getCore().getOllamaObject().addMessage(new OllamaMessage(OllamaMessageRole.USER, query)); + apiApplication.getCore().handleResponce(apiApplication.getCore().qurryOllama()); + }); + t.start(); + return ResponseEntity.ok("Query received"); + } + + @PostMapping("/addResponseHook") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Response hook added successfully"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid request data"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "Internal server error") + }) + public ResponseEntity addResponseHook(@RequestBody NewQurryResponceHook request) { + if (request.getUrl() == null || request.getUrl().isEmpty()) { + return ResponseEntity.badRequest().body("URL cannot be null or empty"); + } + + try { + apiApplication.addQurryResponseHook(request); + + return ResponseEntity.ok("Response hook added successfully: " + request.getUrl()); + }catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body("Invalid request data: " + e.getMessage()); + } catch (Exception e) { + return ResponseEntity.status(500).body("Internal server error: " + e.getMessage()); + } + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java b/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java new file mode 100644 index 0000000..6a2a1a9 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java @@ -0,0 +1,40 @@ +package me.zacharias.chat.api.controllers; + +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import me.zacharias.chat.api.APIApplication; +import me.zacharias.chat.api.payload.request.NewToolRequest; +import me.zacharias.chat.api.payload.response.NewToolResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/v1/tool") +public class Tool { + APIApplication application; + + public Tool(APIApplication application) { + this.application = application; + } + + /** + * Adds a new tool to the application. + * + * @param request The request containing the tool details. + * @return A response entity containing the tool details or an error message. + */ + @PostMapping("addTool") + @ApiResponse(responseCode = "200", description = "Tool added successfully", content = @io.swagger.v3.oas.annotations.media.Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = NewToolResponse.class))) + @ApiResponse(responseCode = "400", description = "Tool already exists", content = @io.swagger.v3.oas.annotations.media.Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = String.class))) + public ResponseEntity addTool(@RequestBody NewToolRequest request) { + if(application.addTool(request.getName(), request)) { + String[] arguments = new String[request.getArguments().length]; + for(int i = 0; i < arguments.length; i++) { + arguments[i] = request.getArguments()[i].getName()+":("+request.getArguments()[i].getType()+")"; + } + return ResponseEntity.ok(new NewToolResponse(request.getName(), request.getDescription(), arguments)); + } else { + return new ResponseEntity<>("Tool already exists", null, 400); + } + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/MessageResponce.java b/API/src/main/java/me/zacharias/chat/api/payload/MessageResponce.java new file mode 100644 index 0000000..fa36d27 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/MessageResponce.java @@ -0,0 +1,19 @@ +package me.zacharias.chat.api.payload; + +public class MessageResponce { + private final String message; + private final ToolRequest[] tool; + + public MessageResponce(String message, ToolRequest[] tool) { + this.message = message; + this.tool = tool; + } + + public String getMessage() { + return message; + } + + public ToolRequest[] getTool() { + return tool; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/ToolArgument.java b/API/src/main/java/me/zacharias/chat/api/payload/ToolArgument.java new file mode 100644 index 0000000..cab2076 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/ToolArgument.java @@ -0,0 +1,33 @@ +package me.zacharias.chat.api.payload; + +import me.zacharias.chat.ollama.OllamaPerameter; + +public class ToolArgument { + String name; + String description; + OllamaPerameter.OllamaPerameterBuilder.Type type; + boolean required; + + public ToolArgument(String name, String description, OllamaPerameter.OllamaPerameterBuilder.Type type, boolean required) { + this.name = name; + this.description = description; + this.type = type; + this.required = required; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public OllamaPerameter.OllamaPerameterBuilder.Type getType() { + return type; + } + + public boolean isRequired() { + return required; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/ToolRequest.java b/API/src/main/java/me/zacharias/chat/api/payload/ToolRequest.java new file mode 100644 index 0000000..4b4f7b7 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/ToolRequest.java @@ -0,0 +1,19 @@ +package me.zacharias.chat.api.payload; + +public class ToolRequest { + private final String name; + private final String[] args; + + public ToolRequest(String name, String[] args) { + this.name = name; + this.args = args; + } + + public String getName() { + return name; + } + + public String[] getArgs() { + return args; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/request/NewQurryResponceHook.java b/API/src/main/java/me/zacharias/chat/api/payload/request/NewQurryResponceHook.java new file mode 100644 index 0000000..607e8db --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/request/NewQurryResponceHook.java @@ -0,0 +1,45 @@ +package me.zacharias.chat.api.payload.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class NewQurryResponceHook { + /** + * The URL to send the response to. + */ + @Schema(description = "The URL to send the response to.", example = "https://example.com/webhook") + private final String url; + /** + * The URL to send errors to. + */ + @Schema(description = "The URL to send errors to.", example = "https://example.com/error-webhook") + private final String errorUrl; + + /** + * Constructor for NewQurryResponceHook. + * + * @param url The URL to send the response to. + * @param errorUrl The URL to send errors to. + */ + public NewQurryResponceHook(String url, String errorUrl) { + this.url = url; + this.errorUrl = errorUrl; + } + + /** + * Gets the URL to send the response to. + * + * @return The URL for the response. + */ + public String getUrl() { + return url; + } + + /** + * Gets the URL to send errors to. + * + * @return The URL for errors. + */ + public String getErrorUrl() { + return errorUrl; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/request/NewToolRequest.java b/API/src/main/java/me/zacharias/chat/api/payload/request/NewToolRequest.java new file mode 100644 index 0000000..19d0b60 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/request/NewToolRequest.java @@ -0,0 +1,69 @@ +package me.zacharias.chat.api.payload.request; + +import me.zacharias.chat.api.payload.ToolArgument; + +public class NewToolRequest { + /** + * The name of the tool. + */ + private String name; + /** + * The description of the tool. + */ + private String description; + /** + * The arguments this tool will take. + */ + private ToolArgument[] arguments; + /** + * The URL this api will call when the tool is used. + */ + private String requestUrl; + + /** + * Constructor for NewToolRequest. + * + * @param name The name of the tool. + * @param description The description of the tool. + * @param arguments The arguments this tool will take. + * @param requestUrl The URL this api will call when the tool is used. + */ + public NewToolRequest(String name, String description, ToolArgument[] arguments, String requestUrl) { + this.name = name; + this.description = description; + this.arguments = arguments; + this.requestUrl = requestUrl; + } + + /** + * + * @return the name of the tool. + */ + public String getName() { + return name; + } + + /** + * + * @return the description of the tool. + */ + public String getDescription() { + return description; + } + + /** + * + * @return the arguments this tool will take. + */ + public ToolArgument[] getArguments() { + return arguments; + } + + /** + * + * @return the URL this api will call when the tool is used. + */ + public String getRequestUrl() { + return requestUrl; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/response/NewToolResponse.java b/API/src/main/java/me/zacharias/chat/api/payload/response/NewToolResponse.java new file mode 100644 index 0000000..0480ab0 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/response/NewToolResponse.java @@ -0,0 +1,44 @@ +package me.zacharias.chat.api.payload.response; + +public class NewToolResponse { + private String name; + private String description; + private String[] arguments; + + /** + * Constructor for NewToolResponse. + * + * @param name The name of the tool. + * @param description The description of the tool. + * @param arguments The arguments this tool will take. + */ + public NewToolResponse(String name, String description, String[] arguments) { + this.name = name; + this.description = description; + this.arguments = arguments; + } + + /** + * + * @return the name of the tool. + */ + public String getName() { + return name; + } + + /** + * + * @return the description of the tool. + */ + public String getDescription() { + return description; + } + + /** + * + * @return the arguments this tool will take. + */ + public String[] getArguments() { + return arguments; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/webhook/WebhookError.java b/API/src/main/java/me/zacharias/chat/api/payload/webhook/WebhookError.java new file mode 100644 index 0000000..234eeba --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/webhook/WebhookError.java @@ -0,0 +1,31 @@ +package me.zacharias.chat.api.payload.webhook; + +public class WebhookError { + private final String originalUrl; + private final String errorMessage; + private final int statusCode; + private final String timestamp; + + public WebhookError(String url, String error) { + this.originalUrl = url; + this.errorMessage = error; + this.statusCode = 500; // Default status code for errors + this.timestamp = java.time.Instant.now().toString(); // Current timestamp in ISO-8601 format + } + + public String getOriginalUrl() { + return originalUrl; + } + + public String getErrorMessage() { + return errorMessage; + } + + public int getStatusCode() { + return statusCode; + } + + public String getTimestamp() { + return timestamp; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/payload/webhook/responce/APIToolResponse.java b/API/src/main/java/me/zacharias/chat/api/payload/webhook/responce/APIToolResponse.java new file mode 100644 index 0000000..54b66e0 --- /dev/null +++ b/API/src/main/java/me/zacharias/chat/api/payload/webhook/responce/APIToolResponse.java @@ -0,0 +1,35 @@ +package me.zacharias.chat.api.payload.webhook.responce; + +public class APIToolResponse { + private final String response; + private final String error; + + /** + * Constructor for APIToolResponse. + * + * @param response The response from the API. + * @param error The error message if any. + */ + public APIToolResponse(String response, String error) { + this.response = response; + this.error = error; + } + + /** + * Gets the response from the API. + * + * @return The response from the API. + */ + public String getResponse() { + return response; + } + + /** + * Gets the error message if any. + * + * @return The error message. + */ + public String getError() { + return error; + } +} diff --git a/API/src/main/java/me/zacharias/chat/api/server/APIServer.java b/API/src/main/java/me/zacharias/chat/api/server/APIServer.java deleted file mode 100644 index ad1ac99..0000000 --- a/API/src/main/java/me/zacharias/chat/api/server/APIServer.java +++ /dev/null @@ -1,329 +0,0 @@ -package me.zacharias.chat.api.server; - -import me.zacharias.chat.api.APICodes; -import me.zacharias.chat.api.APIEndpoints; -import me.zacharias.chat.api.APIErrorCodes; -import me.zacharias.chat.core.Core; -import me.zacharias.chat.core.LaunchOptions; -import me.zacharias.chat.core.PrintMessageHandler; -import me.zacharias.chat.ollama.OllamaObject; -import org.json.JSONObject; - -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Date; -import java.util.Objects; -import java.util.Scanner; - -import static me.zacharias.chat.api.APIErrorCodes.*; - -/** - * The API server.
- * This class is responsible for handling the server socket and the clients for the API.
- * It also handles the output redirection and the message printing.
- * And the Registration of Tools on the {@link Core} object. - */ -public class APIServer { - /** - * The list of clients connected to the server. - */ - ArrayList clientsList = new ArrayList<>(); - /** - * The server socket. - */ - ServerSocket serverSocket; - - Thread clientAcceptThread = new Thread(() -> { - while (true) { - try{ - Socket s = serverSocket.accept(); - BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); - PrintWriter out = new PrintWriter(s.getOutputStream(), true); - - JSONObject outObj = new JSONObject(); - - if(in.ready()) { - String line = in.readLine(); - JSONObject obj; - try{ - obj = new JSONObject(line); - }catch(Exception e){ - errorOutClient(out,"Invalid JSON object", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(!obj.has("code")) - { - errorOutClient(out, "Missing APICode", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(obj.getEnum(APICodes.class, "code") != APICodes.API) - { - errorOutClient(out, "Invalid APICode", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(!obj.has("api")) - { - errorOutClient(out, "Missing API", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - JSONObject apiObj = obj.getJSONObject("api"); - - if(!apiObj.has("code")) - { - errorOutClient(out, "Missing APIEndpoint", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - APIEndpoints apiEndpoint; - - if((apiEndpoint = apiObj.getEnum(APIEndpoints.class, "code")) != APIEndpoints.INFO) - { - errorOutClient(out, "APIEndpoint \""+apiEndpoint+"\" requires setup", API_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - outObj.put("code", APICodes.API); - JSONObject endpointObj = new JSONObject(); - - JSONObject infoObj = new JSONObject(); - infoObj.put("requires_credentials", LaunchOptions.getInstance().isServerCredentialsEnabled()); - - endpointObj.put("message", infoObj); - - outObj.put("api", endpointObj); - - out.println(outObj); - s.shutdownOutput(); - s.close(); - continue; - } - - if(LaunchOptions.getInstance().isServerCredentialsEnabled()) - { - outObj.put("code", APICodes.CREDENTIALS); - out.println(outObj); - - String line = in.readLine(); - JSONObject lineObj; - try{ - lineObj = new JSONObject(line); - }catch(Exception e) - { - errorOutClient(out, "Missing credentials or Incorrect format", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(!lineObj.has("code")) - { - errorOutClient(out, "Missing APICode", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(!(lineObj.getEnum(APICodes.class, "code") == APICodes.CREDENTIALS)) - { - errorOutClient(out, "Missing Credentials", CREDENTIALS_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(!lineObj.has("credentials")) - { - errorOutClient(out, "Missing Credentials", FORMAT_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - if(!Objects.equals(lineObj.getString("credentials"), LaunchOptions.getInstance().getServerCredentials())) - { - errorOutClient(out, "Invalid Credentials", CREDENTIALS_ERROR); - s.shutdownOutput(); - s.close(); - continue; - } - - outObj = new JSONObject(); - outObj.put("code", APICodes.SUCCESS); - out.println(outObj); - clientsList.add(new Client(s)); - continue; - } - else - { - outObj.put("code", APICodes.SUCCESS); - out.println(outObj); - clientsList.add(new Client(s)); - continue; - } - }catch(Exception e) { - - } - } - }); - - /** - * The output stream for the server. - */ - PrintStream dataOut; - - /** - * The message handler for the server. - */ - PrintMessageHandler printMessageHandler = new PrintMessageHandler() { - @Override - public void printMessage(String message) { - - synchronized (clientsList) { - for (Client client : clientsList) { - boolean success = client.sendMessage(message); - if (!success) { - clientsList.remove(client); - continue; - } - } - } - } - - @Override - public boolean color() { - return false; - } - }; - - /** - * The core object for the server. - */ - Core core = new Core(printMessageHandler); - - /** - * Options for this is expected to be passed through the {@link LaunchOptions#instance} object.
- * Used objects:
- * - {@link LaunchOptions#autoAccept}
- * - {@link LaunchOptions#redirectOutput}
- * - {@link LaunchOptions#port}
- */ - public APIServer() { - LaunchOptions options = LaunchOptions.getInstance(); - String redirectedOutput = options.getRedirectOutput(); - int port = options.getPort(); - - core.setOllamaObject(OllamaObject.builder() - .setModel("llama3-AI") - .keep_alive(10) - //.stream(false) - .build()); - - if (redirectedOutput != null && !Paths.get(redirectedOutput).toFile().getParentFile().exists()) { - System.out.println("Failed to be able to open the redirected output file due to missing directory"); - } - else { - redirectedOutput = "./out.txt"; - } - - File f = new File(redirectedOutput); - - try { - if (f.exists()) { - System.out.println("Output already exists"); - System.out.print("Overwrite the existing output file? [y/N]: "); - Scanner sc = new Scanner(System.in); - char c; - - if (options.isAutoAccept()) { - c = 'y'; - } else { - String s = sc.nextLine(); - c = s.isBlank() ? 'n' : s.charAt(0); - } - - if (Character.toLowerCase(c) == 'y') { - f.delete(); - f.createNewFile(); - dataOut = new PrintStream(new FileOutputStream(f), true); - } else { - System.out.print("Rename existing output file? [y/N]: "); - String s = sc.nextLine(); - c = s.isBlank() ? 'n' : s.charAt(0); - if (c == 'y') { - System.out.println("New file name for [" + f.getName() + "]: "); - File newFile = new File(f.getParentFile(), sc.nextLine().trim()); - if (f.renameTo(newFile)) { - System.out.println("Old file placed in [" + newFile.getPath() + "]"); - f = new File(redirectedOutput); - } else { - System.out.println("Failed to rename file. Proceeding with appending data."); - } - if (f.exists()) { - f.delete(); - } - f.createNewFile(); - dataOut = new PrintStream(new FileOutputStream(f), true); - } else { - System.out.print("Appending new data to [" + f.getPath() + "]"); - dataOut = new PrintStream(new FileOutputStream(f, true), true); - Date date = new Date(); - dataOut.println("\n\nNew instance started at: " + date.toString() + "\n"); - } - } - sc.close(); - } else { - f.createNewFile(); - dataOut = new PrintStream(new FileOutputStream(f, true), true); - } - } - catch (Exception e) { - e.printStackTrace(); - System.exit(1); - return; - } - - System.out.println("Redirecting output to [" + redirectedOutput + "]"); - System.out.println("Starting server on port [" + port + "]"); - - try{ - serverSocket = new ServerSocket(port); - - } - catch(Exception e){ - System.out.println("Failed to start server on port [" + port + "]"); - System.exit(1); - return; - } - - //serverSocket = new ServerSocket(port); - } - - private void errorOutClient(PrintWriter writer, String errorMessage, APIErrorCodes apiErrorCodes) { - JSONObject outObj = new JSONObject(); - outObj.put("code", APICodes.ERROR); - JSONObject errObj = new JSONObject(); - errObj.put("code", apiErrorCodes); - errObj.put("message", errorMessage); - outObj.put("error", errObj); - writer.println(outObj); - } -} diff --git a/API/src/main/java/me/zacharias/chat/api/server/Client.java b/API/src/main/java/me/zacharias/chat/api/server/Client.java deleted file mode 100644 index 3e62a79..0000000 --- a/API/src/main/java/me/zacharias/chat/api/server/Client.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.zacharias.chat.api.server; - -import java.io.BufferedWriter; -import java.io.OutputStreamWriter; -import java.net.Socket; - -/** - * The client object for the API server. - */ -public class Client { - - /** - * The socket for the client. - */ - Socket socket; - - /** - * Creates a new client object. - * @param socket the socket for the client. - */ - public Client(Socket socket) { - this.socket = socket; - } - - /** - * Returnes true unless if the method failes - * @param message the message to send to the client. - * @return true if the message was sent successfully, false otherwise. - */ - public boolean sendMessage(String message) { - try(BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) { - out.write(message+"\r\n"); - return true; - }catch (Exception e) { - return false; - } - } -} 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 8109e82..548ab05 100644 --- a/Core/src/main/java/me/zacharias/chat/core/Core.java +++ b/Core/src/main/java/me/zacharias/chat/core/Core.java @@ -273,15 +273,17 @@ public class Core { public OllamaObject getOllamaObject() { return ollamaObject; } - - /** - * Adds a new tool to the System - * @deprecated Use {@link Core#addTool(OllamaFunctionTool, String)} instead - * @param funtionTool The tool to add - */ - @Deprecated - public void addFuntionTool(OllamaFunctionTool funtionTool) { - funtionTools.add(new Pair<>(funtionTool, "External")); + + public void removeTool(String name) { + Pair funtionTool = funtionTools.stream().filter(tool -> tool.getKey().name().equalsIgnoreCase(name)).findFirst().orElse(null); + funtionTools.remove(funtionTool); + if(funtionTool.getKey() == null) { + // This should never happens... So if it does, Shit hit the fan + Exception e = new IllegalArgumentException("Function tool with name '"+name+"' does not exist"); + e.printStackTrace(); + System.exit(1); + } + ollamaObject.removeTool(funtionTool.getKey()); } /** @@ -306,8 +308,10 @@ public class Core { connection.setDoOutput(true); connection.setConnectTimeout(80*1000); + String ollamaObjectString = ollamaObject.toString(); + try(DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { - wr.writeBytes(ollamaObject.toString()); + wr.writeBytes(ollamaObjectString); wr.flush(); } diff --git a/Core/src/main/java/me/zacharias/chat/core/GlobalObjects.java b/Core/src/main/java/me/zacharias/chat/core/GlobalObjects.java new file mode 100644 index 0000000..442ba39 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/core/GlobalObjects.java @@ -0,0 +1,29 @@ +package me.zacharias.chat.core; + +import java.util.HashMap; +import java.util.Map; + +public class GlobalObjects { + private static final Map objects = new HashMap<>(); + + public static void addObject(String name, Object object) { + if (name == null || object == null) { + throw new IllegalArgumentException("Name and object cannot be null"); + } + objects.put(name, object); + } + + public static Object getObject(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + return objects.get(name); + } + + public static boolean removeObject(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + return objects.remove(name) != null; + } +} diff --git a/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java b/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java index b54d612..2f20d1d 100644 --- a/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java +++ b/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java @@ -19,6 +19,7 @@ public class LaunchOptions { private boolean autoAccept; private boolean serverMode; private boolean serverCredentialsEnabled; + private boolean notDisplay = true; private int port = 39075; private String redirectOutput; private String serverCredentials; @@ -143,4 +144,20 @@ public class LaunchOptions { public void setServerCredentials(String serverCredentials) { this.serverCredentials = serverCredentials; } + + /** + * Gets if the program is running in display mode. + * @return a boolean indicating if the program is running in display mode. + */ + public boolean isNotDisplay() { + return notDisplay; + } + + /** + * Sets if the program is running in display mode. + * @param notDisplay a boolean indicating if the program is running in display mode. + */ + public void setNotDisplay(boolean notDisplay) { + this.notDisplay = notDisplay; + } } 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 9f1bb3e..34a527c 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java @@ -134,6 +134,10 @@ public class OllamaObject { public void addTool(OllamaTool tool) { tools.add(tool); } + + public void removeTool(OllamaTool tool) { + tools.remove(tool); + } /** * Gets the format of the Ollama Object. diff --git a/Display/build.gradle b/Display/build.gradle index 995f01c..0cc98fc 100644 --- a/Display/build.gradle +++ b/Display/build.gradle @@ -8,6 +8,7 @@ version = '1.0-SNAPSHOT' dependencies { implementation project(":Core") implementation project(":MALAPITool") + implementation project(":API") } 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 ff2ecc2..0444c53 100644 --- a/Display/src/main/java/me/zacharias/chat/display/Display.java +++ b/Display/src/main/java/me/zacharias/chat/display/Display.java @@ -1,5 +1,6 @@ package me.zacharias.chat.display; +import me.zacharias.chat.api.APIApplication; import me.zacharias.chat.core.Core; import me.zacharias.chat.core.Pair; import me.zacharias.chat.core.PrintMessageHandler; @@ -35,7 +36,7 @@ public class Display { return true; } }); - + /** * Creates a new instance of Display.
* This Creates the OllamaObject and adds the tools to it. as well as handles the display of the messages. and the input from the user. @@ -56,6 +57,8 @@ public class Display { //core.addTool(new PythonRunner(core), Core.Source.INTERNAL); core.addTools(new MALAPITool().getOllamaTools()); + APIApplication.start(); + //core.getOllamaObject().addMessage(new OllamaMessage(OllamaMessageRole.SYSTEM, "Have a nice tone and use formal wording")); writeLog("Creating base OllamaObject with model: "+core.getOllamaObject().getModel()); diff --git a/README.md b/README.md index 2bfaa9b..7ca7513 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ While the primary goal is providing a somewhat more modular frontend for Ollama, simple examples are in the Display module where i gave it the ability to access python through docker and get the current date and time both with the [OllamaFunctionTool](https://server.4zellen.se:3000/Zacharias/chat_thing/src/branch/master/Core/src/main/java/me/zacharias/chat/ollama/OllamaFuntionTool.java) thru my Ollama framework ## API -The documentation for the API is available at the gitea wiki under [API docs](https://server.4zellen.se:3000/Zacharias/chat_thing/wiki/API-Docs) +The documentation for the API is available at the gitea wiki under [API docs](https://server.4zellen.se:3000/Chat_things/NeuroDock/wiki/API-Docs) ## How to run? To run you need to build the `launcher` module
@@ -19,7 +19,7 @@ This will put the `launcher-1.0-all.jar`(or similar, based on the version) in `. `$ java -jar launcher-1.0-all.jar`
you can use the `-h` argument to get a help message. ``` -Launch options for AI_chat +Launch options for NeuroChat: -h --help Provides this help message -s --server Starts the application as API server -p --port Provides the port number that the API server should use, defaults to 39075 @@ -36,7 +36,7 @@ If you only want to build a specific module, you can use the same command as lis - Core: `$ ./gradlew :Core:shadowJar` - However, this one is kinda useless unless you want to directly implement the system into your application - MALAPITool: `$ ./gradlew :MALAPITool:shadowJar` - - Please read MALAPITool [README.md](https://server.4zellen.se:3000/Chat_things/chat_thing/src/branch/master/MALAPITool/README.md) + - Please read MALAPITool [README.md](https://server.4zellen.se:3000/Chat_things/NeuroDock/src/branch/master/MALAPITool/README.md) - Launcher: `$ ./gradlew :launcher:shadowJar` - Depends on `API`, `Display` and `Core` - This is the main module that runs the application and starts the API server \ No newline at end of file diff --git a/launcher/build.gradle b/launcher/build.gradle index caa27eb..6369ecf 100644 --- a/launcher/build.gradle +++ b/launcher/build.gradle @@ -1,5 +1,8 @@ plugins { id 'java' + + id 'org.springframework.boot' version '3.2.2' + id 'io.spring.dependency-management' version '1.1.4' } group = 'me.zacharias' @@ -9,10 +12,20 @@ dependencies { implementation project(":Display") implementation project(":API") implementation project(":Core") + + afterEvaluate { + boolean hasAPIDependency = configurations.implementation.dependencies.any { it.name.contains('API') } + + if (hasAPIDependency) { + dependencies.add("implementation", 'org.springframework.boot:spring-boot-starter-web') + dependencies.add("implementation", 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0') + dependencies.add("testImplementation", 'org.springframework.boot:spring-boot-starter-test') + } + } } jar{ manifest { attributes 'Main-Class': 'me.zacharias.chat.launcher.Launcher' } -} \ No newline at end of file +} diff --git a/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java b/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java index 7b7dce1..a9e4930 100644 --- a/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java +++ b/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java @@ -1,9 +1,12 @@ package me.zacharias.chat.launcher; -import me.zacharias.chat.api.server.APIServer; +//import me.zacharias.chat.api.APIApplication; +import me.zacharias.chat.api.APIApplication; import me.zacharias.chat.core.LaunchOptions; import me.zacharias.chat.display.Display; +import java.lang.reflect.Method; + /** * The launcher for AI_chat.
* This is the main class of the application and is responsible for handling the command line arguments. @@ -49,7 +52,7 @@ public class Launcher { } case "--help", "-h" -> { System.out.println(""" - Launch options for AI_chat + Launch options for NeuroChat: -h --help Provides this help message -s --server Starts the application as API server -p --port Provides the port number that the API server should use, defaults to 39075 @@ -84,9 +87,16 @@ public class Launcher { if (options.isServerMode()) { System.out.println("Starting in API mode..."); - new APIServer(); + try { + APIApplication.start(); + }catch (Exception e) + { + System.out.println("Failed to start API server: " + e.getMessage()); + e.printStackTrace(); + } } else { System.out.println("Starting in Display mode..."); + options.setNotDisplay(false); new Display(); } }