diff --git a/pom.xml b/pom.xml index 8e9280d..ce28b29 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,8 @@ ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} diff --git a/src/main/java/net/dontcode/prj/GenerateProjectService.java b/src/main/java/net/dontcode/prj/GenerateProjectService.java deleted file mode 100644 index 090044a..0000000 --- a/src/main/java/net/dontcode/prj/GenerateProjectService.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.dontcode.prj; - -import dev.langchain4j.service.SystemMessage; -import io.quarkiverse.langchain4j.RegisterAiService; -import jakarta.enterprise.context.SessionScoped; -import net.dontcode.core.project.DontCodeProjectModel; - -@RegisterAiService -@SystemMessage(""" - Tu es un createur d'application utilisant le framework dont-code. Ce framework génère une application à partir d'un fichier json. - Basé sur la demande d'un utilisateur, tu fournis le fichier json permettant de générer l'application voulue. - Quand tu reçois une demande, trouve les objets qui devront être manipulés. Ces objets doivent être définis dans la liste entities du json.Ensuite, pour chaque objet, cherche les champs nécessaire, et leur type. - Ces champs sont renseignés dans la liste fields de chaque entity.Un champ peut-être d'un des types prédéfinis suivant: - "number","string","date","time","date-time","currency","country","money-amount","eur-amount","usd-amount","image","link","rating","recurring-task","task-complete" - ou du type d'une autre entité. - Optionnellement, un champ peut être une référence vers une autre entité, en ajoutant "reference" a la description avec les informations nécessaire pour faire le lien entre les deux entités. - """) -@SessionScoped -public interface GenerateProjectService { - - DontCodeProjectModel generateProjectJson (String msg); -} diff --git a/src/main/java/net/dontcode/prj/generate/GenerateProjectModel.java b/src/main/java/net/dontcode/prj/generate/GenerateProjectModel.java new file mode 100644 index 0000000..253e4f2 --- /dev/null +++ b/src/main/java/net/dontcode/prj/generate/GenerateProjectModel.java @@ -0,0 +1,8 @@ +package net.dontcode.prj.generate; + +import net.dontcode.core.project.DontCodeProjectModel; + +public record GenerateProjectModel(String response, DontCodeProjectModel model) +{ + +} diff --git a/src/main/java/net/dontcode/prj/GenerateProjectResource.java b/src/main/java/net/dontcode/prj/generate/GenerateProjectResource.java similarity index 74% rename from src/main/java/net/dontcode/prj/GenerateProjectResource.java rename to src/main/java/net/dontcode/prj/generate/GenerateProjectResource.java index f9fb5ce..e33cc27 100644 --- a/src/main/java/net/dontcode/prj/GenerateProjectResource.java +++ b/src/main/java/net/dontcode/prj/generate/GenerateProjectResource.java @@ -1,12 +1,12 @@ -package net.dontcode.prj; +package net.dontcode.prj.generate; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.websockets.next.OnError; import io.quarkus.websockets.next.OnOpen; import io.quarkus.websockets.next.OnTextMessage; import io.quarkus.websockets.next.WebSocket; -import jakarta.websocket.EncodeException; -import net.dontcode.common.websocket.MessageEncoderDecoder; +import jakarta.inject.Inject; import net.dontcode.core.project.DontCodeProjectModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,10 +15,10 @@ public class GenerateProjectResource { private static Logger log = LoggerFactory.getLogger(GenerateProjectResource.class); - private final GenerateProjectService service; + @Inject + protected GenerateProjectService service; - public GenerateProjectResource(GenerateProjectService service) { - this.service = service; + public GenerateProjectResource() { } @@ -32,7 +32,12 @@ public String onMessage(String message) { return projectToString(service.generateProjectJson(message)); } - protected String projectToString (DontCodeProjectModel prj) { + @OnError + public String onError(Throwable throwable) { + return throwable.getMessage(); + } + + protected String projectToString (GenerateProjectModel prj) { ObjectMapper mapper = new ObjectMapper(); String json = ""; try { diff --git a/src/main/java/net/dontcode/prj/generate/GenerateProjectService.java b/src/main/java/net/dontcode/prj/generate/GenerateProjectService.java new file mode 100644 index 0000000..01484f6 --- /dev/null +++ b/src/main/java/net/dontcode/prj/generate/GenerateProjectService.java @@ -0,0 +1,26 @@ +package net.dontcode.prj.generate; + +import dev.langchain4j.service.SystemMessage; +import io.quarkiverse.langchain4j.RegisterAiService; +import jakarta.enterprise.context.SessionScoped; +import net.dontcode.core.project.DontCodeProjectModel; + +@RegisterAiService +@SystemMessage(""" + You are creating applications using the dont-code framework. This framework generates an application from a json file. + Based on the user's demand, you provide a response and a design of the desired application in the json structured file. + You can dialog with the user using the field "reponse" of the json. + The application definition will be provided in the "content/creation" part of the json. + Here is the process to design the appliation: + When receiving a demand, find the entities that need to be managed. These entities must be defined in the "entities" list of the json. + Then, for each object, look for necessary fields and their types. + These fields are included in the "fields" list of each entity, a field can have the following pre-defined types: + "number","string","date","time","date-time","currency","country","money-amount","eur-amount","usd-amount","image","link","rating","recurring-task","task-complete" + A field can be of the type of another entity as well. + Optionally, a field can reference another entity by adding "reference" to the field description and by filling all the necessaries information to link both entities. + """) +@SessionScoped +public interface GenerateProjectService { + + GenerateProjectModel generateProjectJson (String msg); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6a75c91..b1787f0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -19,7 +19,10 @@ quarkus.http.cors.enabled=true quarkus.http.cors.origins=/.*/ quarkus.package.jar.type=uber-jar - +# Default memory type +quarkus.langchain4j.chat-memory.type=MESSAGE_WINDOW +# Maximum messages in memory (for MESSAGE_WINDOW) +quarkus.langchain4j.chat-memory.memory-window.max-messages=10 quarkus.langchain4j.mistralai.chat-model.model-name=codestral-latest diff --git a/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java b/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java index 0f7c883..b92a00b 100644 --- a/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java +++ b/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java @@ -4,8 +4,9 @@ import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.websocket.*; -import net.dontcode.core.Message; import net.dontcode.core.project.*; +import net.dontcode.prj.generate.GenerateProjectModel; +import net.dontcode.prj.generate.GenerateProjectService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -29,9 +30,11 @@ public class GenerateProjectResourceTest { public void testGeneration() throws DeploymentException, IOException, InterruptedException { DontCodeProjectEntities[] entities = new DontCodeProjectEntities[]{}; Mockito.when(serviceMock.generateProjectJson(anyString())).thenReturn( - new DontCodeProjectModel("Test", - new DontCodeProjectContent( - new DontCodeProjectCreation("Test App", DontCodeProjectCreationType.application, entities)))); + new GenerateProjectModel("Here is an application that would fit", + new DontCodeProjectModel("Test", "Test application.", + new DontCodeProjectContent( + new DontCodeProjectCreation("Test App", DontCodeProjectCreationType.application, entities)))) + ); ClientTestSession.opened=false; try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(ClientTestSession.class, generateUri)) { // Wait the data to be saved in the database diff --git a/src/test/java/net/dontcode/prj/GenerateProjectServiceIT.java b/src/test/java/net/dontcode/prj/GenerateProjectServiceIT.java new file mode 100644 index 0000000..3ef22ce --- /dev/null +++ b/src/test/java/net/dontcode/prj/GenerateProjectServiceIT.java @@ -0,0 +1,118 @@ +package net.dontcode.prj; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.QuarkusIntegrationTest; +import jakarta.websocket.*; +import net.dontcode.prj.generate.GenerateProjectModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Vector; + +@QuarkusIntegrationTest +public class GenerateProjectServiceIT { + + @TestHTTPResource("/generate") + URI uri; + + @Test + public void testSimpleApplication () throws DeploymentException, IOException, InterruptedException { + TestClientGenerateApplication wsClient=new TestClientGenerateApplication(); + try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(wsClient, uri)) { + // Wait the data to be saved in the database + for (int i = 0; i < 10; i++) { + Thread.sleep(50); + if (wsClient.opened) { + break; + } + } + Assertions.assertTrue(wsClient.opened, "Session was not opened"); + + String response=wsClient.waitForMessage(10); + Assertions.assertNotNull(response); + + session.getBasicRemote().sendText("Please create a cooking recipe application"); + + response=wsClient.waitForMessage(200); + Assertions.assertNotNull(response); + + GenerateProjectModel model=mapToProject(response); + Assertions.assertNotNull(model.response()); + + session.getBasicRemote().sendText("Change it to support images for each type of ingredients"); + response=wsClient.waitForMessage(200); + Assertions.assertNotNull(response); + + model=mapToProject(response); + Assertions.assertNotNull(model.response()); + } + + /*DontCodeProjectModel response=service.generateProjectJson("Please create a cooking recipe application"); + Assertions.assertNotNull(response); + Assertions.assertTrue(response.content().creation().entities().length > 0);*/ + } + + protected GenerateProjectModel mapToProject (String response) { + ObjectMapper mapper = new ObjectMapper(); + GenerateProjectModel model; + try { + model = mapper.readValue(response, GenerateProjectModel.class); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error decoding project", e); + } + return model; + + } + + + @ClientEndpoint() + public class TestClientGenerateApplication { + + public boolean opened=false; + public List receivedMessages = new Vector<>(); + public Session session; + + public TestClientGenerateApplication () { + + } + + @OnOpen + public void open(Session session) { + this.session=session; + opened=true; + } + + @OnMessage + void message(String msg) throws DecodeException { + //MESSAGES.add(msg); + //System.out.println(msg); + receivedMessages.add(msg); + } + + @OnError + void error (Throwable error) { + System.err.println("Error "+ error.getMessage()); + } + + public String waitForMessage (int maxTry) throws InterruptedException { + for (int i = 0; i < maxTry; i++) { + Thread.sleep(50); + if (!receivedMessages.isEmpty()) { + break; + } + } + + Assertions.assertFalse(receivedMessages.isEmpty()); + + return receivedMessages.removeLast(); + } + } + +} + + diff --git a/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java b/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java deleted file mode 100644 index 7dc661e..0000000 --- a/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.dontcode.prj; - -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import net.dontcode.core.project.DontCodeProjectModel; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -@QuarkusTest -public class GenerateProjectServiceTest { - - @Inject - GenerateProjectService service; - - @Test - public void testSimpleApplication () { - - //DontCodeProjectModel response=service.generateProjectJson("Please create a cooking recipe application"); - //Assertions.assertNotNull(response); - //Assertions.assertTrue(response.content().creation().entities().length > 0); - } - -}