From c392916af42f14dbc6d6871abc2e2fd9facfd7be Mon Sep 17 00:00:00 2001 From: pandaa Date: Mon, 23 Mar 2026 08:14:23 +0100 Subject: [PATCH] Just a temp commit --- pom.xml | 2 + .../aitestsuit/ChatGPTApiWrapper.java | 71 ---------- .../{aitestsuit => plinfa}/AiWindow.java | 26 ++-- .../de/derpandaa/plinfa/ChatListWidget.java | 40 ++++++ .../{aitestsuit => plinfa}/ChatWindow.java | 132 +++++++++--------- .../java/de/derpandaa/plinfa/PlinfaMenu.java | 23 +++ .../de/derpandaa/plinfa/PlinfaWindow.java | 104 ++++++++++++++ .../plinfa/aiprovider/AbstractAiProvider.java | 69 +++++++++ .../plinfa/aiprovider/AiProviderManager.java | 34 +++++ .../de/derpandaa/plinfa/aiprovider/Model.java | 11 ++ .../plinfa/aiprovider/OpenAiProvider.java | 36 +++++ .../de/derpandaa/plinfa/dto/AiRequest.java | 51 +++++++ .../derpandaa/plinfa/dto/AiResponseDto.java | 23 +++ .../plinfa/dto/AnthropicResponseDto.java | 17 +++ .../java/de/derpandaa/plinfa/dto/ChatDto.java | 60 ++++++++ .../dto}/MessageDto.java | 11 +- .../{aitestsuit => plinfa/dto}/OpenAiDto.java | 38 ++--- .../plinfa/dto/OpenAiResponseDto.java | 17 +++ .../plinfa/projects/GenericProject.java | 46 ++++++ 19 files changed, 643 insertions(+), 168 deletions(-) delete mode 100644 src/main/java/de/derpandaa/aitestsuit/ChatGPTApiWrapper.java rename src/main/java/de/derpandaa/{aitestsuit => plinfa}/AiWindow.java (76%) create mode 100644 src/main/java/de/derpandaa/plinfa/ChatListWidget.java rename src/main/java/de/derpandaa/{aitestsuit => plinfa}/ChatWindow.java (58%) create mode 100644 src/main/java/de/derpandaa/plinfa/PlinfaMenu.java create mode 100644 src/main/java/de/derpandaa/plinfa/PlinfaWindow.java create mode 100644 src/main/java/de/derpandaa/plinfa/aiprovider/AbstractAiProvider.java create mode 100644 src/main/java/de/derpandaa/plinfa/aiprovider/AiProviderManager.java create mode 100644 src/main/java/de/derpandaa/plinfa/aiprovider/Model.java create mode 100644 src/main/java/de/derpandaa/plinfa/aiprovider/OpenAiProvider.java create mode 100644 src/main/java/de/derpandaa/plinfa/dto/AiRequest.java create mode 100644 src/main/java/de/derpandaa/plinfa/dto/AiResponseDto.java create mode 100644 src/main/java/de/derpandaa/plinfa/dto/AnthropicResponseDto.java create mode 100644 src/main/java/de/derpandaa/plinfa/dto/ChatDto.java rename src/main/java/de/derpandaa/{aitestsuit => plinfa/dto}/MessageDto.java (65%) rename src/main/java/de/derpandaa/{aitestsuit => plinfa/dto}/OpenAiDto.java (59%) create mode 100644 src/main/java/de/derpandaa/plinfa/dto/OpenAiResponseDto.java create mode 100644 src/main/java/de/derpandaa/plinfa/projects/GenericProject.java diff --git a/pom.xml b/pom.xml index c0c8178..5c81464 100644 --- a/pom.xml +++ b/pom.xml @@ -6,6 +6,8 @@ 6.10.0 3.0.3 + 24 + 24 diff --git a/src/main/java/de/derpandaa/aitestsuit/ChatGPTApiWrapper.java b/src/main/java/de/derpandaa/aitestsuit/ChatGPTApiWrapper.java deleted file mode 100644 index e0fee73..0000000 --- a/src/main/java/de/derpandaa/aitestsuit/ChatGPTApiWrapper.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.derpandaa.aitestsuit; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.function.Consumer; - -import de.derpandaa.aitestsuit.MessageDto.Role; -import de.derpandaa.aitestsuit.OpenAiDto.Model; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ObjectMapper; - -/* - * I dont know how to name it - */ -public class ChatGPTApiWrapper { - - public static String API_KEY; - - public static String run(String systemPrompt, String userPrompt) { - OpenAiDto dto = new OpenAiDto(Model.gpt5nano, new MessageDto(Role.system, systemPrompt), - new MessageDto(Role.user, userPrompt)); - return run(dto); - } - - public static String run(OpenAiDto dto) { - ObjectMapper mapper = new ObjectMapper(); - String jsonBody = mapper.writeValueAsString(dto); - - System.out.println(jsonBody); - HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://api.openai.com/v1/chat/completions")) - .header("Content-Type", "application/json").header("Authorization", "Bearer " + API_KEY) - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build(); - - HttpClient client = HttpClient.newHttpClient(); - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - JsonNode root = mapper.readTree(response.body()); - return root.path("choices").path(0).path("message").path("content").asString(); - } catch (IOException | InterruptedException e) { - // NOOP - } - return ""; - } - - public static void runAsync(OpenAiDto dto, Consumer onResult) { - ObjectMapper mapper = new ObjectMapper(); - String jsonBody = mapper.writeValueAsString(dto); - - HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://api.openai.com/v1/chat/completions")) - .header("Content-Type", "application/json").header("Authorization", "Bearer " + API_KEY) - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build(); - - HttpClient client = HttpClient.newHttpClient(); - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body) - .thenAccept(body -> { - try { - JsonNode root = mapper.readTree(body); - String content = root.path("choices").path(0).path("message").path("content").asString(); - onResult.accept(content); - } catch (Exception e) { - onResult.accept(""); - } - }).exceptionally(ex -> { - onResult.accept(""); - return null; - }); - } -} diff --git a/src/main/java/de/derpandaa/aitestsuit/AiWindow.java b/src/main/java/de/derpandaa/plinfa/AiWindow.java similarity index 76% rename from src/main/java/de/derpandaa/aitestsuit/AiWindow.java rename to src/main/java/de/derpandaa/plinfa/AiWindow.java index 20fc50c..807ac0c 100644 --- a/src/main/java/de/derpandaa/aitestsuit/AiWindow.java +++ b/src/main/java/de/derpandaa/plinfa/AiWindow.java @@ -1,4 +1,4 @@ -package de.derpandaa.aitestsuit; +package de.derpandaa.plinfa; import io.qt.widgets.QApplication; import io.qt.widgets.QBoxLayout; @@ -9,12 +9,9 @@ import io.qt.widgets.QTextEdit; import io.qt.widgets.QVBoxLayout; import io.qt.widgets.QWidget; -public class AiWindow { - - - public static void main(String[] args) { - QApplication.initialize(args); - QMainWindow window = new QMainWindow(); +public class AiWindow extends QMainWindow { + public AiWindow() { + super(); QWidget widget = new QWidget(); QBoxLayout layout = new QVBoxLayout(); @@ -39,16 +36,23 @@ public class AiWindow { QPushButton buttonRun = new QPushButton("Run"); buttonRun.clicked.connect(() -> { - String result = ChatGPTApiWrapper.run(editSystemPrompt.getPlainText(), editUserPrompt.getPlainText()); - editAiAnser.setText(result); +// String result = AiProviderManager.getAiProviderManager().run(editSystemPrompt.getPlainText(), +// editUserPrompt.getPlainText()); +// editAiAnser.setText(result); }); layout.addWidget(buttonRun); widget.setLayout(layout); - window.setCentralWidget(widget); - window.show(); + setCentralWidget(widget); + show(); + } + + public static void main(String[] args) { + QApplication.initialize(args); + AiWindow window = new AiWindow(); + QApplication.exec(); QApplication.shutdown(); } diff --git a/src/main/java/de/derpandaa/plinfa/ChatListWidget.java b/src/main/java/de/derpandaa/plinfa/ChatListWidget.java new file mode 100644 index 0000000..e7aceff --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/ChatListWidget.java @@ -0,0 +1,40 @@ +package de.derpandaa.plinfa; + +import java.io.File; +import java.util.function.Consumer; + +import de.derpandaa.plinfa.dto.ChatDto; +import de.derpandaa.plinfa.projects.GenericProject; +import io.qt.core.Qt; +import io.qt.widgets.QTreeWidget; +import io.qt.widgets.QTreeWidgetItem; + +public class ChatListWidget extends QTreeWidget { + + public ChatListWidget(Consumer clickedFunction) { + super(); + setHeaderHidden(true); + GenericProject root = new GenericProject(new File(ChatDto.PROJECT_DIR)); + loadProject(root, null); + itemClicked.connect((item, column) -> { + ChatDto chat = (ChatDto) item.data(0, Qt.ItemDataRole.UserRole); + if (chat != null) { + clickedFunction.accept(chat); + } + }); + } + + private void loadProject(GenericProject project, QTreeWidgetItem parent) { + for (GenericProject subProject : project.getSubProjects()) { + QTreeWidgetItem folderItem = parent == null ? new QTreeWidgetItem(this) : new QTreeWidgetItem(parent); + folderItem.setText(0, subProject.getName()); + loadProject(subProject, folderItem); + } + + for (ChatDto chat : project.getChats()) { + QTreeWidgetItem chatItem = parent == null ? new QTreeWidgetItem(this) : new QTreeWidgetItem(parent); + chatItem.setText(0, chat.getName()); + chatItem.setData(0, Qt.ItemDataRole.UserRole, chat); + } + } +} diff --git a/src/main/java/de/derpandaa/aitestsuit/ChatWindow.java b/src/main/java/de/derpandaa/plinfa/ChatWindow.java similarity index 58% rename from src/main/java/de/derpandaa/aitestsuit/ChatWindow.java rename to src/main/java/de/derpandaa/plinfa/ChatWindow.java index 2d70f49..c2c1920 100644 --- a/src/main/java/de/derpandaa/aitestsuit/ChatWindow.java +++ b/src/main/java/de/derpandaa/plinfa/ChatWindow.java @@ -1,27 +1,24 @@ -package de.derpandaa.aitestsuit; +package de.derpandaa.plinfa; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Properties; - -import de.derpandaa.aitestsuit.MessageDto.Role; -import de.derpandaa.aitestsuit.OpenAiDto.Model; -import io.qt.core.QMetaObject; +import de.derpandaa.plinfa.aiprovider.AiProviderManager; +import de.derpandaa.plinfa.aiprovider.Model; +import de.derpandaa.plinfa.dto.ChatDto; +import de.derpandaa.plinfa.dto.MessageDto; +import de.derpandaa.plinfa.dto.MessageDto.Role; +import de.derpandaa.plinfa.dto.OpenAiDto; import io.qt.core.QMimeData; import io.qt.core.Qt.AlignmentFlag; -import io.qt.core.Qt.ConnectionType; +import io.qt.core.Qt.TextFormat; import io.qt.gui.QColor; import io.qt.gui.QKeySequence; import io.qt.gui.QPalette; +import io.qt.gui.QPalette.ColorRole; import io.qt.gui.QShortcut; -import io.qt.widgets.QApplication; import io.qt.widgets.QBoxLayout; import io.qt.widgets.QComboBox; import io.qt.widgets.QFrame; import io.qt.widgets.QHBoxLayout; import io.qt.widgets.QLabel; -import io.qt.widgets.QMainWindow; import io.qt.widgets.QPushButton; import io.qt.widgets.QScrollArea; import io.qt.widgets.QSizePolicy; @@ -30,36 +27,43 @@ import io.qt.widgets.QTextEdit; import io.qt.widgets.QVBoxLayout; import io.qt.widgets.QWidget; -public class ChatWindow extends QMainWindow { +public class ChatWindow extends QWidget { private QBoxLayout layout; private QVBoxLayout chatLayout; private QWidget chatWidget; private QTextEdit textInput; - private OpenAiDto openAiDto; + private ChatDto chatDto; private QComboBox modelComboBox; + private QScrollArea scrollArea; public ChatWindow() { + this(new ChatDto()); + } + + public ChatWindow(ChatDto chatDto) { super(); - openAiDto = new OpenAiDto(Model.gpt5_2, new MessageDto(Role.system, "Antworte kurz und präzise")); + QPalette palette = getPalette(); + palette.setColor(ColorRole.Window, PlinfaWindow.colorBackgroundAlt); + setPalette(palette); + this.chatDto = chatDto; - QWidget widget = new QWidget(); - layout = new QVBoxLayout(); - layout.setContentsMargins(10, 10, 10, 10); + layout = new QVBoxLayout(this); + layout.setContentsMargins(10, 0, 10, 10); - QPushButton button = new QPushButton("Import Chat"); - layout.addWidget(button); + QLabel chatNameLabel = new QLabel(chatDto.getName()); + layout.addWidget(chatNameLabel); modelComboBox = new QComboBox(); - for (OpenAiDto.Model m : OpenAiDto.Model.values()) { + for (Model m : AiProviderManager.getAiProviderManager().getActiveModels()) { modelComboBox.addItem(m.toString(), m); } layout.addWidget(modelComboBox); - QScrollArea scrollArea = new QScrollArea(); + scrollArea = new QScrollArea(); scrollArea.setWidgetResizable(true); - chatWidget = new QWidget(); + chatWidget = new QWidget(this); chatLayout = new QVBoxLayout(chatWidget); chatLayout.setAlignment(AlignmentFlag.AlignTop); chatLayout.setSpacing(5); @@ -85,18 +89,21 @@ public class ChatWindow extends QMainWindow { inputLayout.addWidget(sendButton); layout.addLayout(inputLayout); - QShortcut shortcut = new QShortcut(new QKeySequence("Ctrl+Return"), widget); + QShortcut shortcut = new QShortcut(new QKeySequence("Ctrl+Return"), this); shortcut.activated.connect(() -> sendMessage()); - widget.setLayout(layout); - setCentralWidget(widget); resize(600, 500); - show(); + importChat(chatDto); } - private void importChat(OpenAiDto openAiDto) { - for (MessageDto messageDto : openAiDto.getMessages()) { -// chatLayout.addWidget(createMessage(messageDto.getContent(), true)); + private void importChat(ChatDto chatDto) { + if (chatDto.getOpenAiDto().getMessages() == null) { + return; + } + for (MessageDto messageDto : chatDto.getOpenAiDto().getMessages()) { + if (messageDto.getRole() != Role.system) { + chatLayout.addWidget(createMessage(messageDto.getContent(), messageDto.getRole())); + } } } @@ -105,13 +112,14 @@ public class ChatWindow extends QMainWindow { frame.setFrameShape(QFrame.Shape.Box); frame.setFrameShadow(QFrame.Shadow.Raised); -// frame.setMaximumWidth(Integer.MAX_VALUE); - frame.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred); + frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum); + frame.setMaximumWidth((int) (chatWidget.getMaximumWidth() + 0.7)); QVBoxLayout frameLayout = new QVBoxLayout(frame); frameLayout.setContentsMargins(10, 10, 10, 10); QLabel textLabel = new QLabel(message); + textLabel.setTextFormat(TextFormat.MarkdownText); textLabel.setWordWrap(true); frameLayout.addWidget(textLabel); @@ -152,12 +160,17 @@ public class ChatWindow extends QMainWindow { if (message.isEmpty()) { return; } - openAiDto.setModel((Model) modelComboBox.currentData()); + + OpenAiDto openAiDto = chatDto.getOpenAiDto(); + + openAiDto.setModel((de.derpandaa.plinfa.aiprovider.Model) modelComboBox.currentData()); MessageDto messageDto = new MessageDto(Role.user, message); openAiDto.addMessage(messageDto); - chatLayout.addWidget(createMessage(message, Role.user)); + QWidget userWidget = createMessage(message, Role.user); + chatLayout.addWidget(userWidget); + scrollArea.ensureWidgetVisible(userWidget); textInput.clear(); chatWidget.adjustSize(); @@ -166,41 +179,24 @@ public class ChatWindow extends QMainWindow { chatLayout.addWidget(loadingWidget); chatWidget.adjustSize(); - ChatGPTApiWrapper.runAsync(openAiDto, answer -> { - QMetaObject.invokeMethod(this, () -> { - chatLayout.removeWidget(loadingWidget); - loadingWidget.disposeLater(); - - openAiDto.addMessage(new MessageDto(Role.assistant, answer)); - chatLayout.addWidget(createMessage(answer, Role.assistant)); - chatWidget.adjustSize(); - }, ConnectionType.QueuedConnection); -// update(); - }); - - } - - public static void main(String[] args) { - QApplication.initialize(args); - QApplication.setStyle("Fusion"); - initProperties(); - ChatWindow window = new ChatWindow(); - - QApplication.exec(); - QApplication.shutdown(); - } - - private static void initProperties() { - Properties properties = new Properties(); try { - properties.load(new FileInputStream("ai.properties")); - } catch (FileNotFoundException e) { - System.out.println("[ERROR] No Properties File"); - System.exit(1); - } catch (IOException e) { - System.out.println("[ERROR] Cannot Access File"); - System.exit(1); +// .runAsync(openAiDto, answer -> { +// QMetaObject.invokeMethod(this, () -> { +// chatLayout.removeWidget(loadingWidget); +// loadingWidget.disposeLater(); +// +// openAiDto.addMessage(new MessageDto(Role.assistant, answer)); +// QWidget assistantWidget = createMessage(answer, Role.assistant); +// chatLayout.addWidget(assistantWidget); +// scrollArea.ensureWidgetVisible(assistantWidget); +// chatWidget.adjustSize(); +// chatDto.writeToFile(); +// +// }, ConnectionType.QueuedConnection); +// }); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); } - ChatGPTApiWrapper.API_KEY = properties.getProperty("OPENAI_KEY"); } } \ No newline at end of file diff --git a/src/main/java/de/derpandaa/plinfa/PlinfaMenu.java b/src/main/java/de/derpandaa/plinfa/PlinfaMenu.java new file mode 100644 index 0000000..7269832 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/PlinfaMenu.java @@ -0,0 +1,23 @@ +package de.derpandaa.plinfa; + +import java.util.function.Consumer; + +import io.qt.gui.QIcon; +import io.qt.widgets.QToolBar; +import io.qt.widgets.QToolButton; + +public class PlinfaMenu extends QToolBar { + + public PlinfaMenu() { + setMaximumHeight(50); + } + + public void addButton(QIcon icon, Consumer function, String toolTip) { + QToolButton button = new QToolButton(); + button.setIcon(icon); + button.clicked.connect(bool -> function.accept(bool)); + button.setToolTip(toolTip); + addWidget(button); + button.setDown(true); + } +} diff --git a/src/main/java/de/derpandaa/plinfa/PlinfaWindow.java b/src/main/java/de/derpandaa/plinfa/PlinfaWindow.java new file mode 100644 index 0000000..9ec15aa --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/PlinfaWindow.java @@ -0,0 +1,104 @@ +package de.derpandaa.plinfa; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import de.derpandaa.plinfa.aiprovider.OpenAiProvider; +import de.derpandaa.plinfa.dto.ChatDto; +import io.qt.gui.QColor; +import io.qt.gui.QIcon; +import io.qt.gui.QPalette; +import io.qt.gui.QPalette.ColorRole; +import io.qt.widgets.QApplication; +import io.qt.widgets.QHBoxLayout; +import io.qt.widgets.QMainWindow; +import io.qt.widgets.QVBoxLayout; +import io.qt.widgets.QWidget; + +public class PlinfaWindow extends QMainWindow { + + public final static QColor colorBackground = new QColor(40, 42, 46); + public final static QColor colorBackgroundAlt = new QColor(55, 59, 65); + public final static QColor colorForeground = new QColor(208, 208, 208); + + private ChatWindow chatWindow; + private QVBoxLayout vLayout; + private QHBoxLayout hLayout; + private PlinfaMenu menuBar; + private Map chatMap; + + public PlinfaWindow() { + super(); + QPalette palette = getPalette(); + palette.setColor(ColorRole.Window, colorBackground); + palette.setColor(ColorRole.Base, colorBackgroundAlt); + palette.setColor(ColorRole.Button, colorBackgroundAlt); + palette.setColor(ColorRole.Text, colorForeground); + palette.setColor(ColorRole.ButtonText, colorForeground); + palette.setColor(ColorRole.PlaceholderText, colorForeground); + setPalette(palette); + + QWidget mainWidget = new QWidget(); + QWidget widget = new QWidget(); + vLayout = new QVBoxLayout(mainWidget); + menuBar = new PlinfaMenu(); + menuBar.addButton(new QIcon("/usr/share/pandaaPop/img/brightness.svg"), null, "Test"); + vLayout.addWidget(menuBar); + + + hLayout = new QHBoxLayout(widget); + vLayout.addWidget(widget); + + ChatListWidget chatListWidget = new ChatListWidget(chat -> loadChat(chat)); + hLayout.addWidget(chatListWidget, 1); + chatWindow = new ChatWindow(); + hLayout.addWidget(chatWindow, 3); + + chatMap = new HashMap(); + + setCentralWidget(mainWidget); + show(); + } + + public void loadChat(ChatDto chatDto) { + hLayout.removeWidget(chatWindow); + chatWindow.hide(); + ChatWindow cachedChatWindow = chatMap.get(chatDto); + if (cachedChatWindow == null) { + cachedChatWindow = new ChatWindow(chatDto); + chatMap.put(chatDto, cachedChatWindow); + } + chatWindow = cachedChatWindow; + hLayout.addWidget(chatWindow, 3); + chatWindow.show(); + } + + public static void main(String[] args) { + QApplication.initialize(args); + QApplication.setStyle("Fusion"); + initProperties(); + PlinfaWindow window = new PlinfaWindow(); + + QApplication.exec(); + QApplication.shutdown(); + } + + private static void initProperties() { + Properties properties = new Properties(); + try { + properties.load(new FileInputStream("ai.properties")); + } catch (FileNotFoundException e) { + System.out.println("[ERROR] No Properties File"); + System.exit(1); + } catch (IOException e) { + System.out.println("[ERROR] Cannot Access File"); + System.exit(1); + } + OpenAiProvider.API_KEY = properties.getProperty("OPENAI_KEY"); + ChatDto.PROJECT_DIR = properties.getProperty("PROJECT_DIR"); + } +} diff --git a/src/main/java/de/derpandaa/plinfa/aiprovider/AbstractAiProvider.java b/src/main/java/de/derpandaa/plinfa/aiprovider/AbstractAiProvider.java new file mode 100644 index 0000000..cc77bf2 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/aiprovider/AbstractAiProvider.java @@ -0,0 +1,69 @@ +package de.derpandaa.plinfa.aiprovider; + +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.List; +import java.util.function.Consumer; + +import de.derpandaa.plinfa.dto.AiRequest; +import de.derpandaa.plinfa.dto.AiResponseDto; +import de.derpandaa.plinfa.dto.OpenAiDto; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; + +public abstract class AbstractAiProvider { + public enum Provider { + OPEN_AI + } + + protected static final ObjectMapper mapper = new ObjectMapper(); + + public static String OpenAiKey; + + +// GPT5_NANO("gpt-5-nano", Provider.OPENAI), GPT5_MINI("gpt-5-mini", Provider.OPENAI), +// GPT5_2("gpt-5.2", Provider.OPENAI), CLAUDE_OPUS("claude-opus-4-6", Provider.ANTHROPIC), +// CLAUDE_SONNET("claude-sonnet-4-6", Provider.ANTHROPIC), CLAUDE_HAIKU("claude-haiku-4-5", Provider.ANTHROPIC); + + public abstract List getModels(); + + public abstract HttpRequest buildRequest(String jsonBody); + + public abstract AiResponseDto parseResponse(String body); + + public AiResponseDto run(AiRequest request) { + String jsonBody = mapper.writeValueAsString(request); + + System.out.println(jsonBody); + HttpRequest httpRequest = buildRequest(jsonBody); + + HttpClient client = HttpClient.newHttpClient(); + try { + HttpResponse response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + return parseResponse(response.body()); + } catch (IOException | InterruptedException e) { + // NOOP + } + return null; + + } + + public void runAsync(OpenAiDto dto, Consumer onResult) { + String jsonBody = mapper.writeValueAsString(dto); + + HttpRequest request = buildRequest(jsonBody); + + HttpClient client = HttpClient.newHttpClient(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body) + .thenAccept(body -> { + JsonNode root = mapper.readTree(body); + String content = root.path("choices").path(0).path("message").path("content").asString(); + onResult.accept(content); + }).exceptionally(ex -> { + onResult.accept(""); + return null; + }); + } +} diff --git a/src/main/java/de/derpandaa/plinfa/aiprovider/AiProviderManager.java b/src/main/java/de/derpandaa/plinfa/aiprovider/AiProviderManager.java new file mode 100644 index 0000000..e34b1a2 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/aiprovider/AiProviderManager.java @@ -0,0 +1,34 @@ +package de.derpandaa.plinfa.aiprovider; + +import java.util.ArrayList; +import java.util.List; + +public class AiProviderManager { + private final List providers; + private static AiProviderManager aiProviderManager; + private List models = new ArrayList(); + + public AiProviderManager() { + providers = new ArrayList(); + if (OpenAiProvider.API_KEY != null) { + providers.add(new OpenAiProvider()); + } + } + + public static AiProviderManager getAiProviderManager() { + if (aiProviderManager == null) { + aiProviderManager = new AiProviderManager(); + } + return aiProviderManager; + } + + public List getActiveModels() { + if (models.isEmpty()) { + for (AbstractAiProvider aiProvider : providers) { + models.addAll(aiProvider.getModels()); + } + } + return models; + } + +} diff --git a/src/main/java/de/derpandaa/plinfa/aiprovider/Model.java b/src/main/java/de/derpandaa/plinfa/aiprovider/Model.java new file mode 100644 index 0000000..091f020 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/aiprovider/Model.java @@ -0,0 +1,11 @@ +package de.derpandaa.plinfa.aiprovider; + +import de.derpandaa.plinfa.aiprovider.AbstractAiProvider.Provider; + +public record Model(String modelName, Provider provider) { + @Override + public String toString() { + return modelName; + } + +} diff --git a/src/main/java/de/derpandaa/plinfa/aiprovider/OpenAiProvider.java b/src/main/java/de/derpandaa/plinfa/aiprovider/OpenAiProvider.java new file mode 100644 index 0000000..bae0abc --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/aiprovider/OpenAiProvider.java @@ -0,0 +1,36 @@ +package de.derpandaa.plinfa.aiprovider; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; + +import de.derpandaa.plinfa.dto.AiResponseDto; +import de.derpandaa.plinfa.dto.OpenAiResponseDto; + +public class OpenAiProvider extends AbstractAiProvider { + public static String API_KEY; + private static int maxTokens; + private static OpenAiProvider openAiProvider; + + public OpenAiProvider() { + } + + @Override + public HttpRequest buildRequest(String jsonBody) { + return HttpRequest.newBuilder().uri(URI.create("https://api.openai.com/v1/chat/completions")) + .header("Content-Type", "application/json").header("Authorization", "Bearer " + API_KEY) + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build(); + } + + @Override + public List getModels() { + + return List.of(new Model("gpt-5-nano", Provider.OPEN_AI)); + } + + @Override + public AiResponseDto parseResponse(String body) { + return new OpenAiResponseDto(body); + } + +} diff --git a/src/main/java/de/derpandaa/plinfa/dto/AiRequest.java b/src/main/java/de/derpandaa/plinfa/dto/AiRequest.java new file mode 100644 index 0000000..4671d43 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/dto/AiRequest.java @@ -0,0 +1,51 @@ +package de.derpandaa.plinfa.dto; + +import java.util.ArrayList; +import java.util.List; + +import de.derpandaa.plinfa.aiprovider.Model; + +public class AiRequest { + private Model model; + private int maxTokens; + private String systemPrompt; + private List messages; + + public AiRequest(Model model) { + this.model = model; + this.maxTokens = 4096; + this.messages = new ArrayList<>(); + } + + public Model getModel() { + return model; + } + + public void setModel(Model model) { + this.model = model; + } + + public int getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(int maxTokens) { + this.maxTokens = maxTokens; + } + + public String getSystemPrompt() { + return systemPrompt; + } + + public void setSystemPrompt(String systemPrompt) { + this.systemPrompt = systemPrompt; + } + + public List getMessages() { + return messages; + } + + public void addMessages(MessageDto message) { + messages.add(message); + } +} diff --git a/src/main/java/de/derpandaa/plinfa/dto/AiResponseDto.java b/src/main/java/de/derpandaa/plinfa/dto/AiResponseDto.java new file mode 100644 index 0000000..13b6ad6 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/dto/AiResponseDto.java @@ -0,0 +1,23 @@ +package de.derpandaa.plinfa.dto; + +import tools.jackson.databind.ObjectMapper; + +public abstract class AiResponseDto { + protected String response; + protected int tokensUsed; + protected ObjectMapper mapper = new ObjectMapper(); + + public AiResponseDto(String jsonBody) { + parseBody(jsonBody); + } + + protected abstract void parseBody(String jsonBody); + + public String getResponse() { + return response; + } + + public int getTokensUsed() { + return tokensUsed; + } +} diff --git a/src/main/java/de/derpandaa/plinfa/dto/AnthropicResponseDto.java b/src/main/java/de/derpandaa/plinfa/dto/AnthropicResponseDto.java new file mode 100644 index 0000000..07afb6b --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/dto/AnthropicResponseDto.java @@ -0,0 +1,17 @@ +package de.derpandaa.plinfa.dto; + +import tools.jackson.databind.JsonNode; + +public class AnthropicResponseDto extends AiResponseDto { + public AnthropicResponseDto(String jsonBody) { + super(jsonBody); + } + + @Override + protected void parseBody(String jsonBody) { + JsonNode root = mapper.readTree(jsonBody); + response = root.path("content").path(0).path("text").asString(); + tokensUsed = root.path("usage").path("input_tokens").asInt() + root.path("usage").path("output_tokens").asInt(); + } + +} diff --git a/src/main/java/de/derpandaa/plinfa/dto/ChatDto.java b/src/main/java/de/derpandaa/plinfa/dto/ChatDto.java new file mode 100644 index 0000000..576d364 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/dto/ChatDto.java @@ -0,0 +1,60 @@ +package de.derpandaa.plinfa.dto; + +import java.io.File; + +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; + +public class ChatDto { + + public static String PROJECT_DIR; + + private String name; + private OpenAiDto openAiDto; + + public ChatDto() { + this.openAiDto = new OpenAiDto(); + } + + public ChatDto(File f) { + loadFromFile(f); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OpenAiDto getOpenAiDto() { + return openAiDto; + } + + public void setOpenAiDto(OpenAiDto openAiDto) { + this.openAiDto = openAiDto; + } + + private void loadFromFile(File f) { + ObjectMapper mapper = new ObjectMapper(); + + setName(f.getName().substring(0, f.getName().indexOf(".json"))); + try { + openAiDto = mapper.readValue(f, OpenAiDto.class); + } catch (JacksonException e) { + e.printStackTrace(); + System.out.println("Deserialisierung fehlgeschlagen"); + } + } + + public void writeToFile() { + if (name == null) { + return; + } + ObjectMapper mapper = new ObjectMapper(); + File f = new File(String.format("%s/%s.json", PROJECT_DIR, name)); + mapper.writeValue(f.getAbsoluteFile(), openAiDto); + } + +} diff --git a/src/main/java/de/derpandaa/aitestsuit/MessageDto.java b/src/main/java/de/derpandaa/plinfa/dto/MessageDto.java similarity index 65% rename from src/main/java/de/derpandaa/aitestsuit/MessageDto.java rename to src/main/java/de/derpandaa/plinfa/dto/MessageDto.java index 7ba996a..fab661a 100644 --- a/src/main/java/de/derpandaa/aitestsuit/MessageDto.java +++ b/src/main/java/de/derpandaa/plinfa/dto/MessageDto.java @@ -1,7 +1,9 @@ -package de.derpandaa.aitestsuit; +package de.derpandaa.plinfa.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +@JsonPropertyOrder({ "role", "content" }) public class MessageDto { public enum Role { @@ -13,6 +15,9 @@ public class MessageDto { @JsonProperty("content") private String content; + public MessageDto() { + } + public MessageDto(Role role, String content) { this.role = role; this.content = content; @@ -22,4 +27,8 @@ public class MessageDto { return content; } + public Role getRole() { + return role; + } + } diff --git a/src/main/java/de/derpandaa/aitestsuit/OpenAiDto.java b/src/main/java/de/derpandaa/plinfa/dto/OpenAiDto.java similarity index 59% rename from src/main/java/de/derpandaa/aitestsuit/OpenAiDto.java rename to src/main/java/de/derpandaa/plinfa/dto/OpenAiDto.java index 2f4f14a..3b4a979 100644 --- a/src/main/java/de/derpandaa/aitestsuit/OpenAiDto.java +++ b/src/main/java/de/derpandaa/plinfa/dto/OpenAiDto.java @@ -1,29 +1,17 @@ -package de.derpandaa.aitestsuit; +package de.derpandaa.plinfa.dto; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import de.derpandaa.plinfa.aiprovider.Model; + +@JsonPropertyOrder({ "model", "messages" }) public class OpenAiDto { - public enum Model { - gpt5nano("gpt-5-nano"), - gpt5mini("gpt-5-mini"), - gpt5_2("gpt-5.2"); - - final String value; - - Model(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } @JsonProperty("model") private Model model; @@ -31,6 +19,22 @@ public class OpenAiDto { @JsonProperty("messages") private List messages; + @JsonProperty("max_tokens") + private int maxTokens; + + public OpenAiDto() { + this(8192); + } + + public OpenAiDto(int maxTokens) { + this.maxTokens = maxTokens; + } + + public OpenAiDto(Model model) { + this.model = model; + this.messages = new ArrayList(); + } + public OpenAiDto(Model model, MessageDto... messages) { this.model = model; this.messages = new ArrayList(); diff --git a/src/main/java/de/derpandaa/plinfa/dto/OpenAiResponseDto.java b/src/main/java/de/derpandaa/plinfa/dto/OpenAiResponseDto.java new file mode 100644 index 0000000..f6a2ad5 --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/dto/OpenAiResponseDto.java @@ -0,0 +1,17 @@ +package de.derpandaa.plinfa.dto; + +import tools.jackson.databind.JsonNode; + +public class OpenAiResponseDto extends AiResponseDto { + public OpenAiResponseDto(String jsonBody) { + super(jsonBody); + } + + @Override + protected void parseBody(String jsonBody) { + JsonNode root = mapper.readTree(jsonBody); + response = root.path("choices").path(0).path("message").path("content").asString(); + tokensUsed = root.path("usage").path("total_tokens").asInt(); + } + +} diff --git a/src/main/java/de/derpandaa/plinfa/projects/GenericProject.java b/src/main/java/de/derpandaa/plinfa/projects/GenericProject.java new file mode 100644 index 0000000..22b1a8c --- /dev/null +++ b/src/main/java/de/derpandaa/plinfa/projects/GenericProject.java @@ -0,0 +1,46 @@ +package de.derpandaa.plinfa.projects; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import de.derpandaa.plinfa.dto.ChatDto; + +public class GenericProject { + private String name; + private List subProjects; + private List chats; + + public GenericProject(File dir) { + this.name = dir.getName(); + this.subProjects = new ArrayList<>(); + this.chats = new ArrayList<>(); + load(dir); + } + + private void load(File dir) { + File[] files = dir.listFiles(); + if (files == null) + return; + + for (File f : files) { + if (f.isDirectory()) { + subProjects.add(new GenericProject(f)); + } else if (f.getName().endsWith(".json")) { + chats.add(new ChatDto(f)); + } + } + } + + public String getName() { + return name; + } + + public List getSubProjects() { + return subProjects; + } + + public List getChats() { + return chats; + } +}