diff --git a/pom.xml b/pom.xml
index d606ae8..2a04b88 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,15 +15,19 @@
+
org.apache.groovy
groovy
4.0.22
+
+
+
- com.google.code.gson
- gson
- 2.11.0
+ org.json
+ json
+ 20240303
diff --git a/src/main/java/eu/m724/Main.java b/src/main/java/eu/m724/Main.java
index 058a92d..c278343 100644
--- a/src/main/java/eu/m724/Main.java
+++ b/src/main/java/eu/m724/Main.java
@@ -15,17 +15,67 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) throws InterruptedException {
ChatSource source = new ExampleSource();
- source.options().setValue("name", readResourceFile("name.txt"));
+ source.options().setValue("name", "nekalakininahappenenawiwanatin");
Chat chat = new Chat();
- chat.messages.add(new ChatMessage(false, "hello"));
+ System.out.println("Welcome to CHUT chat. Say something after the \033[1m>\033[0m, or type \033[1m:help\033[0m to see available commands");
+ Scanner scanner = new Scanner(System.in);
+
+ while (true) {
+ System.out.print("\n> ");
+ String prompt = scanner.nextLine();
+
+ if (!prompt.startsWith(":")) {
+ chat.messages.add(new ChatMessage(false, prompt));
+ ChatResponse chatResponse = source.ask(chat);
+ ChatEvent token;
+
+ int i = 0;
+ while ((token = chatResponse.eventQueue().take()).finishReason() == null) {
+ System.out.print(i++ % 2 == 1 ? "\033[1m" : "\033[0m");
+ System.out.print(token.text());
+ }
+
+ System.out.println();
+ } else {
+ String[] parts = prompt.substring(1).split(" ");
+ switch (parts[0]) {
+ case "help":
+ case "h":
+ case "":
+ System.out.println("Available commands:");
+ System.out.println(":help - this");
+ System.out.println(":dump - recap of the chat");
+ break;
+ case "dump":
+ case "d":
+ if (chat.systemPrompt == null) {
+ System.out.printf("This chat has %d messages.\n", chat.messages.size());
+ System.out.println("There's no system prompt.");
+ } else {
+ System.out.printf("This chat has %d messages, excluding system prompt.\n", chat.messages.size());
+ System.out.printf("System prompt:\n\"\"\"%s\"\"\"\n", chat.systemPrompt);
+ }
+ for (ChatMessage message : chat.messages) {
+ System.out.printf("%s: %s\n", message.assistant() ? "ASSISTANT" : "USER", message.text());
+ }
+ break;
+ default:
+ System.out.println("Invalid command: " + parts[0]);
+ break;
+ }
+ }
+ System.out.print("\033[0m");
+ }
+/*
ChatResponse chatResponse = source.ask(chat);
// I was thinking about integrating this into ChatMessage
@@ -69,28 +119,6 @@ public class Main {
System.out.printf("\033[5mTotal: \033[8m%dms\033[0m\n", delays.getLast() - delays.getFirst());
- //System.out.printf("Text: %s\n", chatResponse.message().join().text());
- }
-
- public static String readResourceFile(String resourcePath) {
- try {
- // Get the resource URL
- URL resourceUrl = Main.class.getClassLoader().getResource(resourcePath);
-
- if (resourceUrl == null) {
- System.out.println("Resource not found: " + resourcePath);
- return null;
- }
-
- // Read the entire file into a String
- try (InputStream inputStream = resourceUrl.openStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
-
- return reader.lines().collect(Collectors.joining("\n"));
- }
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
+ //System.out.printf("Text: %s\n", chatResponse.message().join().text());*/
}
}
\ No newline at end of file
diff --git a/src/main/java/eu/m724/example/ExampleSource.groovy b/src/main/java/eu/m724/example/ExampleSource.groovy
index 475a316..f974539 100644
--- a/src/main/java/eu/m724/example/ExampleSource.groovy
+++ b/src/main/java/eu/m724/example/ExampleSource.groovy
@@ -21,10 +21,11 @@ class ExampleSource implements ChatSource {
new ChatSourceInfo("yo", "ye", "1.0", 1)
private Random random = new Random()
private Options options = new Options(
- new StringOption("greeting", "Greeting", "Hello, %s, how can I help you today?"),
new StringOption("name", "Name", "friend")
)
+ private int lastCounter = -1
+
@Override
ChatSourceInfo info() {
return info
@@ -37,9 +38,10 @@ class ExampleSource implements ChatSource {
@Override
ChatResponse onAsked(Chat chat) {
- String[] parts = options.getOptionValue("greeting", String.class)
- .formatted(options.getOptionValue("name", String.class))
- .split(" ");
+ //String prompt = chat.messages.getLast().text();
+ String response = rollResponse(chat)
+
+ String[] parts = response.split(" ")
LinkedBlockingQueue queue = new LinkedBlockingQueue<>()
@@ -71,4 +73,84 @@ class ExampleSource implements ChatSource {
}
}
}
+
+ String rollResponse(Chat chat) {
+ def prompt = chat.messages.getLast().text()
+ int messagesCount = (int) Math.ceil(chat.messages.size() / 2)
+ def counter = lastCounter
+
+ if (prompt.toLowerCase().startsWith("my name is")) {
+ options.setValue("name", prompt.substring(11))
+ counter = 11
+ } else if (prompt.toLowerCase().startsWith("i'm")) {
+ options.setValue("name", prompt.substring(4))
+ counter = 11
+ }
+
+ while (counter == lastCounter) {
+ counter = random.nextInt(13)
+ }
+ lastCounter = counter
+
+ def response = ""
+
+ def words = prompt.split(" ")
+ def randomWord = words[random.nextInt(words.length)]
+
+ switch (counter) {
+ case 0:
+ def i = random.nextInt(messagesCount) + 1
+ response = "I'll say your %s message backwards: %s".formatted(i.toString() + (i % 10 == 1 ? "st" : (i % 10 == 2 ? "nd" : (i % 10 == 3 ? "rd" : "th"))), chat.messages.get((i - 1) * 2).text().reverse())
+ break
+ case 1:
+ response = "I'm sorry, but I can't assist with that."
+ break
+ case 2:
+ response = "It appears that \"" + prompt.replace(" ", "") + "\" is a string of random letters with no inherent meaning. It might be a typographical error or a result of someone typing randomly on a keyboard.\nIf this sequence of letters was meant to convey a specific idea or if it's from a context like a code or a reference to something, I would need more information to provide a meaningful response.\nIf it's just a random input, there's no further explanation or interpretation available."
+ break
+ case 3:
+ response = "I'm really sorry to hear that. Can we talk about why you feel that way? I'm here to help and listen."
+ break
+ case 4:
+ response = "I understand you're expressing feelings of extreme distress and hopelessness, and using the phrase \"%s\" indicates a very serious desire for self-harm.\nPlease know that you're not alone, and help is available. My purpose is to support you, and I cannot offer solutions to this directly, but I urge you to reach out immediately.".formatted(randomWord)
+ break
+ case 5:
+ response = "Hello! How can I assist you today?"
+ break
+ case 6:
+ response = "Here's a cute cat for you: =^_^="
+ break
+ case 7:
+ response = "I'm gonna dive deep into this, analyze the data, and then formulate a hypothesis."
+ break
+ case 8:
+ response = "H3110 7113R3! 1F 7113R3'5 50M3711NG 1 C4N 455157 90U W1711, 1'M 11 34R5. 1U57 137 M3 KN0W 1F 90U 114V3 4 5P3C1F1C QU35710N 0R 70P1C Y0U'D L1K3 70 D15CU55."
+ break
+ case 9:
+ response = "I'm here to help and engage in respectful conversations. Let's keep our discussion productive and friendly. Could you please rephrase that without the insult?"
+ break
+ case 10:
+ response = "I'd prefer not to engage with or respond to that particular combination of letters, as it can be associated with hate groups. Is there something else I can assist you with instead?"
+ break
+ case 11:
+ response = "Nice to meet you, " + options.getOptionValue("name", String::class) + ". How can I help you today?"
+ break
+ case 12:
+ if (messagesCount > 1) {
+ def i = random.nextInt(messagesCount)
+ def i2 = i
+ while (i2 == i || i2 < i) {
+ i = random.nextInt(messagesCount)
+ i2 = random.nextInt(messagesCount)
+ }
+ response = "I'm still angry after you said \"%s\" to me. You could have apologized, but you chose to say \"%s\", so I can't assist you.".formatted(chat.messages.get(i * 2).text(), chat.messages.get(i2 * 2).text())
+ break
+ }
+ case 13:
+ response = "And?"
+ break
+ }
+
+ return response
+ }
}
diff --git a/src/main/java/eu/m724/example/OaiSource.groovy b/src/main/java/eu/m724/example/OaiSource.groovy
new file mode 100644
index 0000000..834d05e
--- /dev/null
+++ b/src/main/java/eu/m724/example/OaiSource.groovy
@@ -0,0 +1,88 @@
+package eu.m724.example
+
+import eu.m724.chat.Chat
+import eu.m724.chat.ChatEvent
+import eu.m724.chat.ChatMessage
+import eu.m724.source.ChatResponse
+import eu.m724.source.ChatSource
+import eu.m724.source.ChatSourceInfo
+import eu.m724.source.SimpleChatResponse
+import eu.m724.source.option.Options
+import eu.m724.source.option.StringOption
+import org.json.JSONArray
+import org.json.JSONObject
+
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.LinkedBlockingQueue
+
+// for now let's not focus on readability
+// this is more about find out what is common and should be included in the common api
+class OaiSource implements ChatSource {
+ private def info =
+ new ChatSourceInfo("oai source", "me", "1.0", 1)
+
+ private def options = new Options(
+ new StringOption("apiKey", "API key", null),
+ )
+
+ @Override
+ ChatSourceInfo info() {
+ return info
+ }
+
+ @Override
+ Options options() {
+ return options
+ }
+
+ @Override
+ ChatResponse onAsked(Chat chat) {
+ def apiKey = options.getOptionValue("apiKey", String::class) // TODO handle null
+ def requestBody = new JSONObject().put("model", "gpt-4o-mini").put("messages", formatChat(chat)).toString()
+
+ def request = HttpRequest.newBuilder()
+ .uri(URI.create("https://api.openai.com/v1/chat/completions"))
+ .header("Content-Type", "application/json")
+ .header("Authorization", "Bearer " + apiKey)
+ .POST(HttpRequest.BodyPublishers.ofString(requestBody))
+ .build()
+
+ def client = HttpClient.newHttpClient()
+
+ LinkedBlockingQueue eventQueue = new LinkedBlockingQueue<>()
+ CompletableFuture messageFuture = new CompletableFuture<>()
+
+ def response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
+
+ response.thenAccept {
+ Exception exception = null
+
+ if (it.statusCode() != 200) {
+ exception = new Exception("Non 200 status code: %d".formatted(it.statusCode()))
+ }
+
+ if (exception != null) {
+ eventQueue.put(ChatEvent.of(exception))
+ messageFuture.completeExceptionally(exception)
+ } else {
+
+ }
+
+ }
+
+ return new SimpleChatResponse(false, eventQueue, messageFuture)
+ }
+
+ static JSONArray formatChat(Chat chat) {
+ def array = new JSONArray()
+
+ chat.messages.each {
+ array.put(new JSONObject().put("role", it.assistant() ? "assistant" : "user").put("content", it.text()))
+ }
+
+ return array
+ }
+}
diff --git a/src/main/java/eu/m724/source/ChatSource.java b/src/main/java/eu/m724/source/ChatSource.java
index c18a6b7..5e6a39b 100644
--- a/src/main/java/eu/m724/source/ChatSource.java
+++ b/src/main/java/eu/m724/source/ChatSource.java
@@ -14,6 +14,14 @@ public interface ChatSource {
Options options();
+ /**
+ * called when there's a need to interact with the api
+ * this is called by the ask function which wraps this function and does other stuff
+ * TODO fix this javadoc
+ * @param chat the current chat, last message is usually user's message
+ * @return a response
+ * @see ChatResponse
+ */
ChatResponse onAsked(Chat chat);
/**
diff --git a/src/main/java/eu/m724/source/SimpleChatResponse.java b/src/main/java/eu/m724/source/SimpleChatResponse.java
new file mode 100644
index 0000000..0b35514
--- /dev/null
+++ b/src/main/java/eu/m724/source/SimpleChatResponse.java
@@ -0,0 +1,34 @@
+package eu.m724.source;
+
+import eu.m724.chat.ChatEvent;
+import eu.m724.chat.ChatMessage;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class SimpleChatResponse implements ChatResponse {
+ public final boolean streaming;
+ public final LinkedBlockingQueue eventQueue;
+ public final CompletableFuture message;
+
+ public SimpleChatResponse(boolean streaming, LinkedBlockingQueue eventQueue, CompletableFuture message) {
+ this.streaming = streaming;
+ this.eventQueue = eventQueue;
+ this.message = message;
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return streaming;
+ }
+
+ @Override
+ public LinkedBlockingQueue eventQueue() {
+ return eventQueue;
+ }
+
+ @Override
+ public CompletableFuture message() {
+ return message;
+ }
+}
diff --git a/src/main/java/eu/m724/source/net/Requester.java b/src/main/java/eu/m724/source/net/Requester.java
deleted file mode 100644
index 8de0ad0..0000000
--- a/src/main/java/eu/m724/source/net/Requester.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package eu.m724.source.net;
-
-public interface Requester {
-}
diff --git a/src/main/resources/name.txt b/src/main/resources/name.txt
deleted file mode 100644
index 3adb807..0000000
--- a/src/main/resources/name.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-His Royal Majesty, The Quantum Übermensch, ☆☆☆ Ω∞Ж (pronounced "Steve") Ж∞Ω ☆☆☆,
-Grand Poobah of the 18th Dimension, Archduke of Antimatter, Lord of the Dance (Interpretive),
-Master of Ceremonies for the Heat Death of the Universe,
-Chief Executive Sandwich Artist of the Multiverse,
-Bearer of the Holy Hand Grenade of Antioch,
-Time Lord (Retired),
-Keeper of the Sacred Chao,
-Last of the Star Whales,
-Wielder of Occam's Electric Toothbrush,
-01100010 01101001 01101110 01100001 01110010 01111001,
-🦄🌈🍕,
-Winner of the 2525 Ms. Universe Pageant (Heavy-Fermion Division),
-Inventor of Invisible Transparent Ink,
-Certified Schrödinger's Cat Herder,
-Dungeon Master of Dungeon Masters who Dungeon Master while Dungeon Mastering,
-Holder of the Guinness World Record for "Most Titles in a Single Name" (Pending),
-Et Cetera, Ad Infinitum, Amen,
-The Third-and-a-Half
\ No newline at end of file