make it cool
This commit is contained in:
		
					parent
					
						
							
								1b1393da2c
							
						
					
				
			
			
				commit
				
					
						307e2f7c1a
					
				
			
		
					 8 changed files with 276 additions and 54 deletions
				
			
		
							
								
								
									
										10
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								pom.xml
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -15,15 +15,19 @@
 | 
			
		|||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <!-- https://mvnrepository.com/artifact/org.apache.groovy/groovy -->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.apache.groovy</groupId>
 | 
			
		||||
            <artifactId>groovy</artifactId>
 | 
			
		||||
            <version>4.0.22</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <!-- https://mvnrepository.com/artifact/org.json/json -->
 | 
			
		||||
        <!-- TODO stop using this -->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.google.code.gson</groupId>
 | 
			
		||||
            <artifactId>gson</artifactId>
 | 
			
		||||
            <version>2.11.0</version>
 | 
			
		||||
            <groupId>org.json</groupId>
 | 
			
		||||
            <artifactId>json</artifactId>
 | 
			
		||||
            <version>20240303</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());*/
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<ChatEvent> 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										88
									
								
								src/main/java/eu/m724/example/OaiSource.groovy
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/main/java/eu/m724/example/OaiSource.groovy
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<ChatEvent> eventQueue = new LinkedBlockingQueue<>()
 | 
			
		||||
        CompletableFuture<ChatMessage> 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								src/main/java/eu/m724/source/SimpleChatResponse.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/main/java/eu/m724/source/SimpleChatResponse.java
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<ChatEvent> eventQueue;
 | 
			
		||||
    public final CompletableFuture<ChatMessage> message;
 | 
			
		||||
 | 
			
		||||
    public SimpleChatResponse(boolean streaming, LinkedBlockingQueue<ChatEvent> eventQueue, CompletableFuture<ChatMessage> message) {
 | 
			
		||||
        this.streaming = streaming;
 | 
			
		||||
        this.eventQueue = eventQueue;
 | 
			
		||||
        this.message = message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isStreaming() {
 | 
			
		||||
        return streaming;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public LinkedBlockingQueue<ChatEvent> eventQueue() {
 | 
			
		||||
        return eventQueue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<ChatMessage> message() {
 | 
			
		||||
        return message;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +0,0 @@
 | 
			
		|||
package eu.m724.source.net;
 | 
			
		||||
 | 
			
		||||
public interface Requester {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue