From 0c75c2be64ea3fbfa71b94f89ca39d1b9b259bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rard=20Collin?= Date: Sat, 7 Mar 2026 15:03:07 +0100 Subject: [PATCH 1/2] feat: WebSocket based app generation --- .../dontcode/prj/GenerateProjectResource.java | 15 ++-- .../dontcode/prj/GenerateProjectService.java | 1 + src/main/resources/application.properties | 5 +- .../prj/GenerateProjectResourceTest.java | 2 +- .../prj/GenerateProjectServiceTest.java | 87 +++++++++++++++++-- 5 files changed, 96 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/dontcode/prj/GenerateProjectResource.java b/src/main/java/net/dontcode/prj/GenerateProjectResource.java index f9fb5ce..407fed9 100644 --- a/src/main/java/net/dontcode/prj/GenerateProjectResource.java +++ b/src/main/java/net/dontcode/prj/GenerateProjectResource.java @@ -2,11 +2,11 @@ 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,6 +32,11 @@ public String onMessage(String message) { return projectToString(service.generateProjectJson(message)); } + @OnError + public String onError(Throwable throwable) { + return throwable.getMessage(); + } + protected String projectToString (DontCodeProjectModel prj) { ObjectMapper mapper = new ObjectMapper(); String json = ""; diff --git a/src/main/java/net/dontcode/prj/GenerateProjectService.java b/src/main/java/net/dontcode/prj/GenerateProjectService.java index 090044a..2a414b0 100644 --- a/src/main/java/net/dontcode/prj/GenerateProjectService.java +++ b/src/main/java/net/dontcode/prj/GenerateProjectService.java @@ -14,6 +14,7 @@ "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. + Tu peux mettre des commentaires ou ta réponse dans le champ "comment" à la racine du json. Elle doit être dans la même languqe que la question de l'utilisateur. """) @SessionScoped public interface GenerateProjectService { 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..b4ae9f2 100644 --- a/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java +++ b/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java @@ -29,7 +29,7 @@ public class GenerateProjectResourceTest { public void testGeneration() throws DeploymentException, IOException, InterruptedException { DontCodeProjectEntities[] entities = new DontCodeProjectEntities[]{}; Mockito.when(serviceMock.generateProjectJson(anyString())).thenReturn( - new DontCodeProjectModel("Test", + new DontCodeProjectModel("Test", "Here is the content asked.", new DontCodeProjectContent( new DontCodeProjectCreation("Test App", DontCodeProjectCreationType.application, entities)))); ClientTestSession.opened=false; diff --git a/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java b/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java index 7dc661e..1ebebdc 100644 --- a/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java +++ b/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java @@ -1,23 +1,96 @@ package net.dontcode.prj; +import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; -import net.dontcode.core.project.DontCodeProjectModel; +import jakarta.websocket.*; +import net.dontcode.common.websocket.MessageEncoderDecoder; +import net.dontcode.core.Message; 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; + @QuarkusTest public class GenerateProjectServiceTest { - @Inject - GenerateProjectService service; + @TestHTTPResource("/generate") + URI uri; @Test - public void testSimpleApplication () { + 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); + } + + /*DontCodeProjectModel response=service.generateProjectJson("Please create a cooking recipe application"); + Assertions.assertNotNull(response); + Assertions.assertTrue(response.content().creation().entities().length > 0);*/ + } + + + @ClientEndpoint() + public class TestClientGenerateApplication { + + public boolean opened=false; + public List receivedMessages = new Vector<>(); + public Session session; - //DontCodeProjectModel response=service.generateProjectJson("Please create a cooking recipe application"); - //Assertions.assertNotNull(response); - //Assertions.assertTrue(response.content().creation().entities().length > 0); + 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(); + } } } + + From e25d9ff95866393de8d683d50ce506d6157a778a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rard=20Collin?= Date: Sun, 8 Mar 2026 10:47:57 +0100 Subject: [PATCH 2/2] feat: Enable integration tests with Mistral --- pom.xml | 2 ++ .../dontcode/prj/GenerateProjectService.java | 23 ------------- .../prj/generate/GenerateProjectModel.java | 8 +++++ .../GenerateProjectResource.java | 4 +-- .../prj/generate/GenerateProjectService.java | 26 ++++++++++++++ .../prj/GenerateProjectResourceTest.java | 11 +++--- ...est.java => GenerateProjectServiceIT.java} | 34 +++++++++++++++---- 7 files changed, 73 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/net/dontcode/prj/GenerateProjectService.java create mode 100644 src/main/java/net/dontcode/prj/generate/GenerateProjectModel.java rename src/main/java/net/dontcode/prj/{ => generate}/GenerateProjectResource.java (93%) create mode 100644 src/main/java/net/dontcode/prj/generate/GenerateProjectService.java rename src/test/java/net/dontcode/prj/{GenerateProjectServiceTest.java => GenerateProjectServiceIT.java} (70%) 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 2a414b0..0000000 --- a/src/main/java/net/dontcode/prj/GenerateProjectService.java +++ /dev/null @@ -1,23 +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. - Tu peux mettre des commentaires ou ta réponse dans le champ "comment" à la racine du json. Elle doit être dans la même languqe que la question de l'utilisateur. - """) -@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 93% rename from src/main/java/net/dontcode/prj/GenerateProjectResource.java rename to src/main/java/net/dontcode/prj/generate/GenerateProjectResource.java index 407fed9..e33cc27 100644 --- a/src/main/java/net/dontcode/prj/GenerateProjectResource.java +++ b/src/main/java/net/dontcode/prj/generate/GenerateProjectResource.java @@ -1,4 +1,4 @@ -package net.dontcode.prj; +package net.dontcode.prj.generate; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -37,7 +37,7 @@ public String onError(Throwable throwable) { return throwable.getMessage(); } - protected String projectToString (DontCodeProjectModel prj) { + 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/test/java/net/dontcode/prj/GenerateProjectResourceTest.java b/src/test/java/net/dontcode/prj/GenerateProjectResourceTest.java index b4ae9f2..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", "Here is the content asked.", - 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/GenerateProjectServiceTest.java b/src/test/java/net/dontcode/prj/GenerateProjectServiceIT.java similarity index 70% rename from src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java rename to src/test/java/net/dontcode/prj/GenerateProjectServiceIT.java index 1ebebdc..3ef22ce 100644 --- a/src/test/java/net/dontcode/prj/GenerateProjectServiceTest.java +++ b/src/test/java/net/dontcode/prj/GenerateProjectServiceIT.java @@ -1,11 +1,11 @@ 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.QuarkusTest; -import jakarta.inject.Inject; +import io.quarkus.test.junit.QuarkusIntegrationTest; import jakarta.websocket.*; -import net.dontcode.common.websocket.MessageEncoderDecoder; -import net.dontcode.core.Message; +import net.dontcode.prj.generate.GenerateProjectModel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -14,8 +14,8 @@ import java.util.List; import java.util.Vector; -@QuarkusTest -public class GenerateProjectServiceTest { +@QuarkusIntegrationTest +public class GenerateProjectServiceIT { @TestHTTPResource("/generate") URI uri; @@ -40,6 +40,16 @@ public void testSimpleApplication () throws DeploymentException, IOException, In 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"); @@ -47,6 +57,18 @@ public void testSimpleApplication () throws DeploymentException, IOException, In 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 {