From 914f8537d6772dc13f6da6e5c58893dbb521f5c4 Mon Sep 17 00:00:00 2001 From: Wanja Krah Date: Thu, 27 Nov 2025 18:11:07 +0100 Subject: [PATCH] initial commit --- storymaker/.gitignore | 39 +++ storymaker/.idea/.gitignore | 8 + storymaker/.idea/encodings.xml | 7 + storymaker/.idea/misc.xml | 14 + storymaker/.idea/palantir-java-format.xml | 6 + storymaker/.mvn/maven.config | 3 + storymaker/.mvn/settings-local.xml | 265 ++++++++++++++++++ storymaker/pom.xml | 81 ++++++ storymaker/src/main/java/nl/agilecc/Main.java | 14 + .../main/java/nl/agilecc/model/AiVraag.java | 12 + .../main/java/nl/agilecc/model/Persona.java | 4 + .../src/main/java/nl/agilecc/model/Plot.java | 36 +++ .../main/java/nl/agilecc/model/Section.java | 4 + .../src/main/java/nl/agilecc/model/Story.java | 4 + .../main/java/nl/agilecc/model/UserInput.java | 8 + .../nl/agilecc/service/OpenAiService.java | 182 ++++++++++++ 16 files changed, 687 insertions(+) create mode 100644 storymaker/.gitignore create mode 100644 storymaker/.idea/.gitignore create mode 100644 storymaker/.idea/encodings.xml create mode 100644 storymaker/.idea/misc.xml create mode 100644 storymaker/.idea/palantir-java-format.xml create mode 100644 storymaker/.mvn/maven.config create mode 100644 storymaker/.mvn/settings-local.xml create mode 100644 storymaker/pom.xml create mode 100644 storymaker/src/main/java/nl/agilecc/Main.java create mode 100644 storymaker/src/main/java/nl/agilecc/model/AiVraag.java create mode 100644 storymaker/src/main/java/nl/agilecc/model/Persona.java create mode 100644 storymaker/src/main/java/nl/agilecc/model/Plot.java create mode 100644 storymaker/src/main/java/nl/agilecc/model/Section.java create mode 100644 storymaker/src/main/java/nl/agilecc/model/Story.java create mode 100644 storymaker/src/main/java/nl/agilecc/model/UserInput.java create mode 100644 storymaker/src/main/java/nl/agilecc/service/OpenAiService.java diff --git a/storymaker/.gitignore b/storymaker/.gitignore new file mode 100644 index 0000000..480bdf5 --- /dev/null +++ b/storymaker/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/storymaker/.idea/.gitignore b/storymaker/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/storymaker/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/storymaker/.idea/encodings.xml b/storymaker/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/storymaker/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/storymaker/.idea/misc.xml b/storymaker/.idea/misc.xml new file mode 100644 index 0000000..f0d5141 --- /dev/null +++ b/storymaker/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/storymaker/.idea/palantir-java-format.xml b/storymaker/.idea/palantir-java-format.xml new file mode 100644 index 0000000..cbcc1cb --- /dev/null +++ b/storymaker/.idea/palantir-java-format.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/storymaker/.mvn/maven.config b/storymaker/.mvn/maven.config new file mode 100644 index 0000000..d267dac --- /dev/null +++ b/storymaker/.mvn/maven.config @@ -0,0 +1,3 @@ +-s.mvn\settings-local.xml + +-Dmaven.repo.local=D:\workspace\private\.m2repo \ No newline at end of file diff --git a/storymaker/.mvn/settings-local.xml b/storymaker/.mvn/settings-local.xml new file mode 100644 index 0000000..24c1925 --- /dev/null +++ b/storymaker/.mvn/settings-local.xml @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + maven-default-http-blocker + external:http:* + Pseudo repository to mirror external repositories initially using HTTP. + http://0.0.0.0/ + true + + + + + + + + + + + + diff --git a/storymaker/pom.xml b/storymaker/pom.xml new file mode 100644 index 0000000..9fee618 --- /dev/null +++ b/storymaker/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + nl.agilecc + storymaker + 1.0-SNAPSHOT + jar + + + 25 + 25 + UTF-8 + + 1.7.1 + 3.2.0 + 3.14.1 + 3.3.1 + 3.5.4 + + + + + dev.langchain4j + langchain4j-open-ai + ${langchain4j.version} + + + + dev.langchain4j + langchain4j + ${langchain4j.version} + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-toolchains-plugin + ${maven-toolchains-plugin.version} + + + + toolchain + + + + + + + 25 + openjdk + + + + + + + + + \ No newline at end of file diff --git a/storymaker/src/main/java/nl/agilecc/Main.java b/storymaker/src/main/java/nl/agilecc/Main.java new file mode 100644 index 0000000..ff4a875 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/Main.java @@ -0,0 +1,14 @@ +package nl.agilecc; + +import nl.agilecc.service.OpenAiService; + +//TIP To Run code, press or +// click the icon in the gutter. +public class Main { + static void main() { + //TIP Press with your caret at the highlighted text + // to see how IntelliJ IDEA suggests fixing it. +// new OpenAiService().hello(); + new OpenAiService().createStory(); + } +} diff --git a/storymaker/src/main/java/nl/agilecc/model/AiVraag.java b/storymaker/src/main/java/nl/agilecc/model/AiVraag.java new file mode 100644 index 0000000..a6e1964 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/model/AiVraag.java @@ -0,0 +1,12 @@ +package nl.agilecc.model; + +public record AiVraag(String rol, String context, String opdracht, String kaders) { + @Override + public String toString() { + return """ + Je rol is: %s + De context is: %s + Je opdracht is: %s + Je kaders zijn: %s""".formatted(rol, context, opdracht, kaders); + } +} diff --git a/storymaker/src/main/java/nl/agilecc/model/Persona.java b/storymaker/src/main/java/nl/agilecc/model/Persona.java new file mode 100644 index 0000000..1689cc1 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/model/Persona.java @@ -0,0 +1,4 @@ +package nl.agilecc.model; + +public record Persona() { +} diff --git a/storymaker/src/main/java/nl/agilecc/model/Plot.java b/storymaker/src/main/java/nl/agilecc/model/Plot.java new file mode 100644 index 0000000..8e5e066 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/model/Plot.java @@ -0,0 +1,36 @@ +package nl.agilecc.model; + +import dev.langchain4j.model.chat.request.json.JsonArraySchema; +import dev.langchain4j.model.chat.request.json.JsonObjectSchema; +import dev.langchain4j.model.chat.request.json.JsonSchema; +import dev.langchain4j.model.chat.request.json.JsonStringSchema; + +import java.util.List; + +public record Plot(String locatie, List personages, String relaties, String acties, String fases, String ontwikkeling, String aanpak) { + + public static String omschrijvingPlot() { + return """ + Als locatie willen we weten waar, of in welke setting het verhaal af gaat spelen. + We willen een lijst van personages, per personage een korte omschrijving. Later worden deze personages nog verder uitgewerkt. + We willen alvast een idee van de relaties die de personages hebben tot elkaar. + We willen alvast weten welke acties de personages zich grofweg mee gaan bezig houden. + We willen weten welke ontwikkeling we verwachten dat de personages, of in ieder geval de belangrijkste personages, door gaan maken. + Tot slot willen we weten wat de aanpak waarmee het centrale probleem wordt aangepakt."""; + } + + public static JsonSchema getJsonSchema() { + return JsonSchema.builder() + .name("Plot") + .rootElement(JsonObjectSchema.builder() + .addStringProperty("locatie") + .addProperty("personages", JsonArraySchema.builder().items(JsonStringSchema.builder().description("persona").build()).build()) + .addStringProperty("relaties") + .addStringProperty("acties") + .addStringProperty("fases") + .addStringProperty("ontwikkeling") + .addStringProperty("aanpak") + .build()) + .build(); + } +} diff --git a/storymaker/src/main/java/nl/agilecc/model/Section.java b/storymaker/src/main/java/nl/agilecc/model/Section.java new file mode 100644 index 0000000..4fb7652 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/model/Section.java @@ -0,0 +1,4 @@ +package nl.agilecc.model; + +public record Section() { +} diff --git a/storymaker/src/main/java/nl/agilecc/model/Story.java b/storymaker/src/main/java/nl/agilecc/model/Story.java new file mode 100644 index 0000000..0161c11 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/model/Story.java @@ -0,0 +1,4 @@ +package nl.agilecc.model; + +public record Story() { +} diff --git a/storymaker/src/main/java/nl/agilecc/model/UserInput.java b/storymaker/src/main/java/nl/agilecc/model/UserInput.java new file mode 100644 index 0000000..d63875b --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/model/UserInput.java @@ -0,0 +1,8 @@ +package nl.agilecc.model; + +public record UserInput(String doelgroep, String genre, String setting, String personages, String conflict) { + @Override + public String toString() { + return "Voor de doelgroep %s, met als genre %s, met ls setting %s, met als personages %s, en met als centraal conflict: %s".formatted(doelgroep, genre, setting, personages, conflict); + } +} diff --git a/storymaker/src/main/java/nl/agilecc/service/OpenAiService.java b/storymaker/src/main/java/nl/agilecc/service/OpenAiService.java new file mode 100644 index 0000000..43994c9 --- /dev/null +++ b/storymaker/src/main/java/nl/agilecc/service/OpenAiService.java @@ -0,0 +1,182 @@ +package nl.agilecc.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.memory.ChatMemory; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.ResponseFormat; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.service.AiServices; +import nl.agilecc.model.AiVraag; +import nl.agilecc.model.Persona; +import nl.agilecc.model.Plot; +import nl.agilecc.model.Section; +import nl.agilecc.model.Story; +import nl.agilecc.model.UserInput; + +import java.util.List; + +public class OpenAiService { + private final String apiKey = System.getenv("OPENAI_API_KEY"); + + private final OpenAiChatModel openAiChatModel; + +// private interface Assistant { +// String chat(String message); +// } + +// private final Assistant assistant; + + public OpenAiService() { + ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); + + openAiChatModel = OpenAiChatModel.builder() + .baseUrl("http://langchain4j.dev/demo/openai/v1") + .apiKey(apiKey) + .modelName("gpt-4o-mini") + .build(); + +// assistant = AiServices.builder(Assistant.class) +// .chatModel(openAiChatModel) +// .chatMemory(chatMemory) +// .build(); + } + + public void hello() { + String answer = openAiChatModel.chat("Say 'Hello World!'"); +// String answer = "dummy"; + System.out.println(answer); + } + + public void createStory() { + // TODO refine gather input + /* + - doelgroep: kleuters, kinderen, pubers, jong-volwassenen, volwassenen + - Genre, e.g. Actie, Avontuur, Grappig, Drama, Fantasy, Historische fictie, Mysterie, Romantisch, Science Fiction + - Setting / Waar vindt het verhaal plaats: (bijvb. een mistige heide, een bos vol mystieke ruines, een voetbalstadium, een planeet met futuristische steden, etc.) + - Wie komen er in voor? Bijvb. een avontuurlijke ridder, een jonge uitvinder, een knappe prins, een zelfstandige princes, een boze koning, etc.) + - Het centrale conflict? (een verloren schatkist, een op drift geraakt ruimteschip, een geliefde ontvoerd, enz.) + */ + String doelgroep = question("Wat is de doelgroep (e.g. kleuters, kinderen, pubers, jong-volwassenen, artsen, etc.)?", "kinderen"); + String genre = question("Welk genre moet het zijn (e.g. Actie, Avontuur, Grappig, Drama, Fantasy, Historische fictie, Myserie, Romantisch, of Science Fiction)?", "Science Fiction"); + String setting = question("Waar vindt het verhaal plaats (e.g. een mistige heide, een mos vol mystieke ruïnes, een voetbalsatium, een futuristische planeet)?", "Een buitenaardse savanne"); + String wie = question("Wie komen er in voor (e.g. een avontuurlijke ridder, een jonge uitvinder, een knappe prinse)?", "Een boze koning, en een verstrooide verzetsheld"); + String centraalConflict = question("Wat is het centrale conflict (e.g. een verloren schatkist, een op drift geraakt ruimteschipt, een ontvoerde geliefde)?", "Een verloren kist met buitenaardse spelcomputers"); + + UserInput userInput = new UserInput(doelgroep, genre, setting, wie, centraalConflict); + Plot plot = bepaalPlot(userInput); + + IO.println("het plot %s".formatted(plot)); +// List personas = bepaalPersonages(userInput, plot); +// Plot herzienPlot = verbeterPlot(plot, personas); +// List
blauwdruk = creeerBlauwdruk(herzienPlot, personas, userInput); +// List
sections = schrijfSecties(blauwdruk); +// Story verhaal = schrijfVerhaal(sections); +// Story verhaalVersie2 = bepaalTitels(verhaal); +// Story verhaalVersie3 = maakPageTurner(verhaalVersie2); +// Story verhaalVersie4 = maakConsistent(verhaalVersie3, personas); +// Story verhaalVersie5 = killDarlings(verhaalVersie4); +// +// drukAf(verhaalVersie5); + + + // TODO schrijf (in een paar stappen) een plot. Laat terug komen waar het verhaal plaats vindt, wie een rol spelen in het verhaal, wat ze met elkaar te maken hebben, wie wat gaat doen en waarom, welke fases ze daarbij doorlopen in het verhaal, hoe ze zichzelf ontwikkelen gedurende deze fases, hoe het centrale probleem wordt opgelost + // TODO create personas voor de personen uit het plot. Geef een omschrijving van hun uiterlijk, een MBTI profiel, waar ze zijn opgegroeid, welke gebeurtenissen uit het verleden hen heeft gevormd, waar ze aan het begin van het verhaal leven en wat hen dagelijks bezig houdt. + // TODO Herschrijf het plot op basis van de personas + // TODO Schrijf op basis van het plot 8 +- 2 secties voor het verhaal. Schrijf voor elke sectie in 3 regels wat er gebeurd. Schrijf wie er wel en niet in voor komen. Welke ontwikkeling ze door maken. Wat in max. 10 woorden het doel is van de sectie + // TODO schrijf tekst voor elke sectie + // TODO herschrijf elke sectie zodat ze goed overgaan in de volgende en op de vorige sectie. Zorg aan het eind van elke sectie voor een cliffhanger + // TODO voeg alle secties samen tot één verhaal en schrap onnodige tussenwoorden en zinnen die geschrapt kunnen worden zonder afbreuk te doen aan het verhaal + // TODO Bepaal titel(s) + // TODO controleer structuur: corrigeer de tekst als het ergens teveel van het verhaal afwijkt, onnodig detail, onnodig herhalen van genoemde feiten, + // TODO contorleer consistentie: controleer of acties en dialogen overeen komen met hun persona's, + // TODO verwijder onnodige scenes +// IO.println(""" +// Om een verhaal te maken stellen we je een aantal vragen. +// Het default antwoord staat er in haakjes achter +// Als je geen antwoord geeft, gebruikt hij het default antwoord. +// Als je niets wilt opvoeren, type dan 'GEEN'"""); +// +// String leeftijd = question("Hoe oud ben je?", "10"); +// String genre = question("Wat voor een type verhaal moet het worden?", "sprookje"); +// String voorwerpen = question("Welke voorwerpen moeten er in voorkomen?", null); +// String personen = question("Welke personen moeten er in voorkomen?", null); +// String overig = question("Welke overige zaken moeten er in voorkomen?", null); +// String spanning = question("Op een schaal van 1 tot 10, hoe spannend moet het zijn?", "4"); +// +// IO.println("Het verhaal wordt gegenereerd"); +// +// String vraag1 = """ +// Je rol is een schrijver. +// Schrijf een verhaal met minimaal 700 en maximaal 1000 worden. +// Het verhaal is voor iemand van %s jaar oud. +// Het moet een %s zijn. +// De personen die er in voor moeten komen zijn: %s +// De volgende voorwerpen moeten er in voorkomen: %s +// Verder moeten de volgende elementen terug komen: %s +// Op een schaal van 1 tot 10, is het spanningsniveau: %s""" +// .formatted(leeftijd, genre, personen, voorwerpen, overig, spanning); +// String resultaat = assistant.chat(vraag1); +// +// IO.println("Het verhaal wordt nog even geedit"); +// +// String vraag2 = """ +// Je rol is een editor. +// Je bekijkt het verhaal wat je als schrijver hebt gemaakt en herschrijft het. +// Hierbij let je op dat het taalgebruik klopt bij de doelgroep, dat korte en lange zinnen elkaar goed afwisselen, +// en dat er geen wollig taalgebruik in zit, +// en dat er geen onnodig moeilijke woorden in zit, +// en dat er een beetje tempo in het verhaal zit"""; +// +// String resultaat2 = assistant.chat(vraag2); +// +// IO.println(resultaat2); + + +// IO.println(""" +// We gaan een %s-verhaal schrijven voor iemand van %s jaar oud +// Het gaat over %s en er komen %s en %s in voor +// Het spanningsniveau is %s""".formatted(genre, leeftijd, personen, voorwerpen, overig, spanning)); + } + + private Plot bepaalPlot(UserInput userInput) { + ResponseFormat responseFormat = ResponseFormat.builder() + .type(ResponseFormat.JSON.type()) + .jsonSchema(Plot.getJsonSchema()) + .build(); + + AiVraag aiVraag = new AiVraag( + "Schrijver", + "Een startpunt voor een nieuw te schrijven verhaal", + Plot.omschrijvingPlot(), + userInput.toString() + ); + + ChatRequest chatRequest = ChatRequest.builder() + .responseFormat(responseFormat) + .messages(UserMessage.from(aiVraag.toString())) + .build(); + + ChatResponse chatResponse = openAiChatModel.chat(chatRequest); + try { + return new ObjectMapper().readValue(chatResponse.aiMessage().text(), Plot.class); + } catch (JsonProcessingException e) { + IO.println("Ik kon %s niet omzetten naar een Plot".formatted(chatResponse.aiMessage().text())); + return null; + } + } + + private String question(String question, String defaultAwnser) { + String fallback = defaultAwnser == null ? "GEEN" : defaultAwnser; + String result = IO.readln(question + " (" + fallback + ")"); + return isNotEmpty(result) ? result : defaultAwnser; + } + + private boolean isNotEmpty(String input) { + return input != null && !input.trim().isEmpty(); + } +}