Done some basic things
This commit is contained in:
29
pom.xml
29
pom.xml
@@ -1,13 +1,16 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
<modelVersion>4.0.0</modelVersion>
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
<groupId>de.derpandaa</groupId>
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<artifactId>plinfa</artifactId>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<groupId>de.derpandaa</groupId>
|
||||||
<properties>
|
<artifactId>plinfa</artifactId>
|
||||||
<qtjambi.version>6.10.0</qtjambi.version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<properties>
|
||||||
|
<qtjambi.version>6.11.0</qtjambi.version>
|
||||||
<jackson.version>3.0.3</jackson.version>
|
<jackson.version>3.0.3</jackson.version>
|
||||||
|
<maven.compiler.release>25</maven.compiler.release>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.qtjambi</groupId>
|
<groupId>io.qtjambi</groupId>
|
||||||
<artifactId>qtjambi</artifactId>
|
<artifactId>qtjambi</artifactId>
|
||||||
@@ -18,6 +21,16 @@
|
|||||||
<artifactId>qtjambi-native-linux-x64</artifactId>
|
<artifactId>qtjambi-native-linux-x64</artifactId>
|
||||||
<version>${qtjambi.version}</version>
|
<version>${qtjambi.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.formdev</groupId>
|
||||||
|
<artifactId>flatlaf</artifactId>
|
||||||
|
<version>3.4.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.formdev</groupId>
|
||||||
|
<artifactId>flatlaf-extras</artifactId>
|
||||||
|
<version>3.4.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>tools.jackson.core</groupId>
|
<groupId>tools.jackson.core</groupId>
|
||||||
<artifactId>jackson-core</artifactId>
|
<artifactId>jackson-core</artifactId>
|
||||||
|
|||||||
@@ -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<String> 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<String> 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
package de.derpandaa.aitestsuit;
|
|
||||||
|
|
||||||
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 io.qt.core.QMimeData;
|
|
||||||
import io.qt.core.Qt.AlignmentFlag;
|
|
||||||
import io.qt.core.Qt.ConnectionType;
|
|
||||||
import io.qt.gui.QColor;
|
|
||||||
import io.qt.gui.QKeySequence;
|
|
||||||
import io.qt.gui.QPalette;
|
|
||||||
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;
|
|
||||||
import io.qt.widgets.QSizePolicy.Policy;
|
|
||||||
import io.qt.widgets.QTextEdit;
|
|
||||||
import io.qt.widgets.QVBoxLayout;
|
|
||||||
import io.qt.widgets.QWidget;
|
|
||||||
|
|
||||||
public class ChatWindow extends QMainWindow {
|
|
||||||
|
|
||||||
private QBoxLayout layout;
|
|
||||||
private QVBoxLayout chatLayout;
|
|
||||||
private QWidget chatWidget;
|
|
||||||
private QTextEdit textInput;
|
|
||||||
private OpenAiDto openAiDto;
|
|
||||||
private QComboBox modelComboBox;
|
|
||||||
|
|
||||||
public ChatWindow() {
|
|
||||||
super();
|
|
||||||
openAiDto = new OpenAiDto(Model.gpt5_2, new MessageDto(Role.system, "Antworte kurz und präzise"));
|
|
||||||
|
|
||||||
QWidget widget = new QWidget();
|
|
||||||
layout = new QVBoxLayout();
|
|
||||||
layout.setContentsMargins(10, 10, 10, 10);
|
|
||||||
|
|
||||||
QPushButton button = new QPushButton("Import Chat");
|
|
||||||
layout.addWidget(button);
|
|
||||||
|
|
||||||
modelComboBox = new QComboBox();
|
|
||||||
for (OpenAiDto.Model m : OpenAiDto.Model.values()) {
|
|
||||||
modelComboBox.addItem(m.toString(), m);
|
|
||||||
}
|
|
||||||
layout.addWidget(modelComboBox);
|
|
||||||
|
|
||||||
QScrollArea scrollArea = new QScrollArea();
|
|
||||||
scrollArea.setWidgetResizable(true);
|
|
||||||
|
|
||||||
chatWidget = new QWidget();
|
|
||||||
chatLayout = new QVBoxLayout(chatWidget);
|
|
||||||
chatLayout.setAlignment(AlignmentFlag.AlignTop);
|
|
||||||
chatLayout.setSpacing(5);
|
|
||||||
|
|
||||||
scrollArea.setWidget(chatWidget);
|
|
||||||
layout.addWidget(scrollArea);
|
|
||||||
|
|
||||||
QHBoxLayout inputLayout = new QHBoxLayout();
|
|
||||||
textInput = new QTextEdit() {
|
|
||||||
@Override
|
|
||||||
protected void insertFromMimeData(QMimeData source) {
|
|
||||||
insertPlainText(source.text());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
textInput.setMaximumHeight(100);
|
|
||||||
textInput.setPlaceholderText("Nachricht eingeben...");
|
|
||||||
|
|
||||||
QPushButton sendButton = new QPushButton("Senden");
|
|
||||||
sendButton.clicked.connect(() -> sendMessage());
|
|
||||||
sendButton.setSizePolicy(new QSizePolicy(Policy.Minimum, Policy.Minimum));
|
|
||||||
|
|
||||||
inputLayout.addWidget(textInput);
|
|
||||||
inputLayout.addWidget(sendButton);
|
|
||||||
layout.addLayout(inputLayout);
|
|
||||||
|
|
||||||
QShortcut shortcut = new QShortcut(new QKeySequence("Ctrl+Return"), widget);
|
|
||||||
shortcut.activated.connect(() -> sendMessage());
|
|
||||||
|
|
||||||
widget.setLayout(layout);
|
|
||||||
setCentralWidget(widget);
|
|
||||||
resize(600, 500);
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void importChat(OpenAiDto openAiDto) {
|
|
||||||
for (MessageDto messageDto : openAiDto.getMessages()) {
|
|
||||||
// chatLayout.addWidget(createMessage(messageDto.getContent(), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private QWidget createMessage(String message, Role role) {
|
|
||||||
QFrame frame = new QFrame();
|
|
||||||
frame.setFrameShape(QFrame.Shape.Box);
|
|
||||||
frame.setFrameShadow(QFrame.Shadow.Raised);
|
|
||||||
|
|
||||||
// frame.setMaximumWidth(Integer.MAX_VALUE);
|
|
||||||
frame.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred);
|
|
||||||
|
|
||||||
QVBoxLayout frameLayout = new QVBoxLayout(frame);
|
|
||||||
|
|
||||||
frameLayout.setContentsMargins(10, 10, 10, 10);
|
|
||||||
QLabel textLabel = new QLabel(message);
|
|
||||||
textLabel.setWordWrap(true);
|
|
||||||
frameLayout.addWidget(textLabel);
|
|
||||||
|
|
||||||
QWidget container = new QWidget();
|
|
||||||
QHBoxLayout boxLayout = new QHBoxLayout(container);
|
|
||||||
boxLayout.setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
QPalette palette = frame.palette();
|
|
||||||
QColor messageColor;
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case user:
|
|
||||||
messageColor = new QColor(209, 114, 246);
|
|
||||||
boxLayout.addStretch(30);
|
|
||||||
boxLayout.addWidget(frame, 70);
|
|
||||||
break;
|
|
||||||
case assistant:
|
|
||||||
messageColor = new QColor(106, 91, 117);
|
|
||||||
boxLayout.addWidget(frame, 70);
|
|
||||||
boxLayout.addStretch(30);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
messageColor = new QColor(100, 100, 100);
|
|
||||||
boxLayout.addStretch(15);
|
|
||||||
boxLayout.addWidget(frame, 70);
|
|
||||||
boxLayout.addStretch(15);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
palette.setColor(QPalette.ColorRole.Window, messageColor);
|
|
||||||
frame.setAutoFillBackground(true);
|
|
||||||
frame.setPalette(palette);
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessage() {
|
|
||||||
String message = textInput.getPlainText().trim();
|
|
||||||
|
|
||||||
if (message.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
openAiDto.setModel((Model) modelComboBox.currentData());
|
|
||||||
|
|
||||||
MessageDto messageDto = new MessageDto(Role.user, message);
|
|
||||||
openAiDto.addMessage(messageDto);
|
|
||||||
|
|
||||||
chatLayout.addWidget(createMessage(message, Role.user));
|
|
||||||
|
|
||||||
textInput.clear();
|
|
||||||
chatWidget.adjustSize();
|
|
||||||
|
|
||||||
QWidget loadingWidget = createMessage("Loading...", Role.assistant);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
ChatGPTApiWrapper.API_KEY = properties.getProperty("OPENAI_KEY");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package de.derpandaa.aitestsuit;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
@JsonProperty("messages")
|
|
||||||
private List<MessageDto> messages;
|
|
||||||
|
|
||||||
public OpenAiDto(Model model, MessageDto... messages) {
|
|
||||||
this.model = model;
|
|
||||||
this.messages = new ArrayList<MessageDto>();
|
|
||||||
this.messages.addAll(Arrays.asList(messages));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addMessage(MessageDto messageDto) {
|
|
||||||
messages.add(messageDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MessageDto> getMessages() {
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setModel(Model model) {
|
|
||||||
this.model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.derpandaa.aitestsuit;
|
package de.derpandaa.plinfa;
|
||||||
|
|
||||||
import io.qt.widgets.QApplication;
|
import io.qt.widgets.QApplication;
|
||||||
import io.qt.widgets.QBoxLayout;
|
import io.qt.widgets.QBoxLayout;
|
||||||
@@ -9,12 +9,9 @@ import io.qt.widgets.QTextEdit;
|
|||||||
import io.qt.widgets.QVBoxLayout;
|
import io.qt.widgets.QVBoxLayout;
|
||||||
import io.qt.widgets.QWidget;
|
import io.qt.widgets.QWidget;
|
||||||
|
|
||||||
public class AiWindow {
|
public class AiWindow extends QMainWindow {
|
||||||
|
public AiWindow() {
|
||||||
|
super();
|
||||||
public static void main(String[] args) {
|
|
||||||
QApplication.initialize(args);
|
|
||||||
QMainWindow window = new QMainWindow();
|
|
||||||
QWidget widget = new QWidget();
|
QWidget widget = new QWidget();
|
||||||
QBoxLayout layout = new QVBoxLayout();
|
QBoxLayout layout = new QVBoxLayout();
|
||||||
|
|
||||||
@@ -39,16 +36,23 @@ public class AiWindow {
|
|||||||
|
|
||||||
QPushButton buttonRun = new QPushButton("Run");
|
QPushButton buttonRun = new QPushButton("Run");
|
||||||
buttonRun.clicked.connect(() -> {
|
buttonRun.clicked.connect(() -> {
|
||||||
String result = ChatGPTApiWrapper.run(editSystemPrompt.getPlainText(), editUserPrompt.getPlainText());
|
// String result = AiProviderManager.getAiProviderManager().run(editSystemPrompt.getPlainText(),
|
||||||
editAiAnser.setText(result);
|
// editUserPrompt.getPlainText());
|
||||||
|
// editAiAnser.setText(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
layout.addWidget(buttonRun);
|
layout.addWidget(buttonRun);
|
||||||
|
|
||||||
widget.setLayout(layout);
|
widget.setLayout(layout);
|
||||||
|
|
||||||
window.setCentralWidget(widget);
|
setCentralWidget(widget);
|
||||||
window.show();
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
QApplication.initialize(args);
|
||||||
|
AiWindow window = new AiWindow();
|
||||||
|
|
||||||
QApplication.exec();
|
QApplication.exec();
|
||||||
QApplication.shutdown();
|
QApplication.shutdown();
|
||||||
}
|
}
|
||||||
67
src/main/java/de/derpandaa/plinfa/ChatListWidget.java
Normal file
67
src/main/java/de/derpandaa/plinfa/ChatListWidget.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package de.derpandaa.plinfa;
|
||||||
|
|
||||||
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.dto.ChatDto;
|
||||||
|
import de.derpandaa.plinfa.projects.GenericProject;
|
||||||
|
|
||||||
|
public class ChatListWidget extends JTree {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 559712098635547536L;
|
||||||
|
private final Consumer<ChatDto> clickHandler;
|
||||||
|
|
||||||
|
public ChatListWidget(Consumer<ChatDto> clickedFunction) {
|
||||||
|
super();
|
||||||
|
this.clickHandler = clickedFunction;
|
||||||
|
setRootVisible(true);
|
||||||
|
GenericProject rootProject = new GenericProject(new File(ChatDto.PROJECT_DIR));
|
||||||
|
DefaultMutableTreeNode rootNode = buildTree(rootProject);
|
||||||
|
setModel(new DefaultTreeModel(rootNode));
|
||||||
|
addTreeSelectionListener(e -> {
|
||||||
|
if (getLastSelectedPathComponent() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Object node = ((DefaultMutableTreeNode) getLastSelectedPathComponent()).getUserObject();
|
||||||
|
|
||||||
|
if (node instanceof ChatDto chat) {
|
||||||
|
clickHandler.accept(chat);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultMutableTreeNode buildTree(GenericProject project) {
|
||||||
|
DefaultMutableTreeNode node = new DefaultMutableTreeNode(project.getName());
|
||||||
|
for (GenericProject sub : project.getSubProjects()) {
|
||||||
|
node.add(buildTree(sub));
|
||||||
|
}
|
||||||
|
for (ChatDto chat : project.getChats()) {
|
||||||
|
DefaultMutableTreeNode chatNode = new DefaultMutableTreeNode(chat);
|
||||||
|
node.add(chatNode);
|
||||||
|
System.out.println(chat.getName());
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChat(ChatDto chat) {
|
||||||
|
DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
|
||||||
|
DefaultMutableTreeNode chatNode = new DefaultMutableTreeNode(chat);
|
||||||
|
root.add(chatNode);
|
||||||
|
((DefaultTreeModel) getModel()).reload(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processFocusEvent(FocusEvent e) {
|
||||||
|
if (e.FOCUS_LOST == e.getID()) {
|
||||||
|
clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.processFocusEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
191
src/main/java/de/derpandaa/plinfa/ChatWindow.java
Normal file
191
src/main/java/de/derpandaa/plinfa/ChatWindow.java
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package de.derpandaa.plinfa;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollBar;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.aiprovider.AiProviderManager;
|
||||||
|
import de.derpandaa.plinfa.aiprovider.Model;
|
||||||
|
import de.derpandaa.plinfa.dto.AiRequest;
|
||||||
|
import de.derpandaa.plinfa.dto.ChatDto;
|
||||||
|
import de.derpandaa.plinfa.dto.MessageDto;
|
||||||
|
import de.derpandaa.plinfa.dto.MessageDto.Role;
|
||||||
|
|
||||||
|
public class ChatWindow extends JPanel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -1698148372733277109L;
|
||||||
|
private final JPanel messageContainer;
|
||||||
|
private final JScrollPane scrollPane;
|
||||||
|
private final JTextArea input;
|
||||||
|
private JComboBox<Model> modelComboBox;
|
||||||
|
private ChatDto chatDto;
|
||||||
|
|
||||||
|
private final Runnable onFirstSave;
|
||||||
|
|
||||||
|
public ChatWindow(Runnable onFirstSave) {
|
||||||
|
this.onFirstSave = onFirstSave;
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
modelComboBox = new JComboBox<>();
|
||||||
|
|
||||||
|
for (Model m : AiProviderManager.getAiProviderManager().getActiveModels()) {
|
||||||
|
modelComboBox.addItem(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(modelComboBox, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// ===== Message Area =====
|
||||||
|
messageContainer = new JPanel();
|
||||||
|
messageContainer.setLayout(new BoxLayout(messageContainer, BoxLayout.Y_AXIS));
|
||||||
|
messageContainer.setAlignmentY(Component.TOP_ALIGNMENT);
|
||||||
|
messageContainer.setOpaque(false);
|
||||||
|
|
||||||
|
JPanel northWrapper = new JPanel(new BorderLayout());
|
||||||
|
northWrapper.add(messageContainer, BorderLayout.NORTH);
|
||||||
|
scrollPane = new JScrollPane(northWrapper);
|
||||||
|
scrollPane.setBorder(null);
|
||||||
|
scrollPane.setBorder(BorderFactory.createLineBorder(PlinfaWindow.BG_ALT, 2));
|
||||||
|
scrollPane.getVerticalScrollBar().setUnitIncrement(12);
|
||||||
|
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// ===== Input Area =====
|
||||||
|
JPanel inputPanel = new JPanel(new BorderLayout());
|
||||||
|
input = new JTextArea(3, 20);
|
||||||
|
input.setLineWrap(true);
|
||||||
|
input.setWrapStyleWord(true);
|
||||||
|
|
||||||
|
JButton send = new JButton("Send");
|
||||||
|
|
||||||
|
send.addActionListener(e -> sendMessage());
|
||||||
|
|
||||||
|
inputPanel.add(new JScrollPane(input), BorderLayout.CENTER);
|
||||||
|
inputPanel.add(send, BorderLayout.EAST);
|
||||||
|
|
||||||
|
add(inputPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK),
|
||||||
|
"save");
|
||||||
|
|
||||||
|
getActionMap().put("save", new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
saveChat();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK),
|
||||||
|
"send");
|
||||||
|
|
||||||
|
getActionMap().put("send", new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chatDto = new ChatDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBubble addUserMessage(String text) {
|
||||||
|
return addMessage(new MessageDto(Role.user, text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBubble addAssistantMessage(String text) {
|
||||||
|
return addMessage(new MessageDto(Role.assistant, text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageBubble addMessage(MessageDto messageDto) {
|
||||||
|
MessageBubble bubble = new MessageBubble(messageDto);
|
||||||
|
|
||||||
|
messageContainer.add(bubble);
|
||||||
|
messageContainer.revalidate();
|
||||||
|
messageContainer.repaint();
|
||||||
|
|
||||||
|
scrollToBottom();
|
||||||
|
return bubble;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scrollToBottom() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
messageContainer.revalidate();
|
||||||
|
messageContainer.repaint();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
JScrollBar bar = scrollPane.getVerticalScrollBar();
|
||||||
|
bar.setValue(bar.getMaximum());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage() {
|
||||||
|
|
||||||
|
String text = input.getText().trim();
|
||||||
|
if (text.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
addUserMessage(text);
|
||||||
|
input.setText("");
|
||||||
|
|
||||||
|
AiRequest request = new AiRequest((Model) modelComboBox.getSelectedItem());
|
||||||
|
request.addMessages(new MessageDto(Role.user, text));
|
||||||
|
|
||||||
|
request.setSystemPrompt("Du bist ein einfacher Assistent der kurz antwortet");
|
||||||
|
|
||||||
|
MessageBubble bubble = addAssistantMessage("Loading...");
|
||||||
|
|
||||||
|
AiProviderManager.getAiProviderManager().runAsync(request, answer -> {
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
addAssistantMessage(answer);
|
||||||
|
chatDto.addMessage(new MessageDto(Role.user, text));
|
||||||
|
chatDto.addMessage(new MessageDto(Role.assistant, answer));
|
||||||
|
messageContainer.remove(bubble);
|
||||||
|
messageContainer.revalidate();
|
||||||
|
messageContainer.repaint();
|
||||||
|
chatDto.writeToFile();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadChat(ChatDto chatDto) {
|
||||||
|
this.chatDto = chatDto == null ? new ChatDto() : chatDto;
|
||||||
|
|
||||||
|
chatDto.getMessages().forEach(m -> {
|
||||||
|
if (m.getRole() == Role.user) {
|
||||||
|
addUserMessage(m.getContent());
|
||||||
|
} else if (m.getRole() == Role.assistant) {
|
||||||
|
addAssistantMessage(m.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveChat() {
|
||||||
|
if (chatDto.getName() == null) {
|
||||||
|
String name = JOptionPane.showInputDialog(this, "Chat Name:", "Speichern", JOptionPane.PLAIN_MESSAGE);
|
||||||
|
if (name == null || name.isBlank())
|
||||||
|
return;
|
||||||
|
chatDto.setName(name);
|
||||||
|
chatDto.writeToFile();
|
||||||
|
onFirstSave.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void focusInput() {
|
||||||
|
input.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/main/java/de/derpandaa/plinfa/MessageBubble.java
Normal file
59
src/main/java/de/derpandaa/plinfa/MessageBubble.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package de.derpandaa.plinfa;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.FlowLayout;
|
||||||
|
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.dto.MessageDto;
|
||||||
|
|
||||||
|
public class MessageBubble extends JPanel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -6278277156331505629L;
|
||||||
|
|
||||||
|
public MessageBubble(MessageDto messageDto) {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(new EmptyBorder(6, 10, 6, 10));
|
||||||
|
|
||||||
|
JTextArea label = new JTextArea(messageDto.getContent());
|
||||||
|
label.setLineWrap(true);
|
||||||
|
label.setWrapStyleWord(true);
|
||||||
|
label.setEditable(false);
|
||||||
|
label.setOpaque(false);
|
||||||
|
|
||||||
|
JPanel bubble = new JPanel(new BorderLayout());
|
||||||
|
bubble.setBorder(new EmptyBorder(10, 12, 10, 12));
|
||||||
|
bubble.add(label, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
Color bg;
|
||||||
|
FlowLayout wrapperLayout;
|
||||||
|
|
||||||
|
switch (messageDto.getRole()) {
|
||||||
|
case user -> {
|
||||||
|
bg = new Color(120, 80, 200);
|
||||||
|
wrapperLayout = new FlowLayout(FlowLayout.RIGHT);
|
||||||
|
}
|
||||||
|
case assistant -> {
|
||||||
|
bg = new Color(60, 60, 60);
|
||||||
|
wrapperLayout = new FlowLayout(FlowLayout.LEFT);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
bg = new Color(90, 90, 90);
|
||||||
|
wrapperLayout = new FlowLayout(FlowLayout.CENTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bubble.setBackground(bg);
|
||||||
|
bubble.setOpaque(true);
|
||||||
|
|
||||||
|
JPanel wrapper = new JPanel(wrapperLayout);
|
||||||
|
wrapper.setOpaque(false);
|
||||||
|
wrapper.add(bubble);
|
||||||
|
|
||||||
|
setOpaque(false);
|
||||||
|
add(wrapper, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/main/java/de/derpandaa/plinfa/PlinfaMenu.java
Normal file
47
src/main/java/de/derpandaa/plinfa/PlinfaMenu.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package de.derpandaa.plinfa;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JToggleButton;
|
||||||
|
import javax.swing.JToolBar;
|
||||||
|
|
||||||
|
public class PlinfaMenu extends JToolBar {
|
||||||
|
|
||||||
|
public PlinfaMenu() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
setFloatable(false);
|
||||||
|
setPreferredSize(new Dimension(0, 50));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addToggleButton(Icon icon, Consumer<Boolean> function, String toolTip) {
|
||||||
|
|
||||||
|
JToggleButton button = new JToggleButton();
|
||||||
|
|
||||||
|
button.setIcon(icon);
|
||||||
|
button.setToolTipText(toolTip);
|
||||||
|
button.setSelected(true);
|
||||||
|
|
||||||
|
button.addActionListener(e -> {
|
||||||
|
if (function != null) {
|
||||||
|
function.accept(button.isSelected());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addButton(Icon icon, Runnable function, String toolTip) {
|
||||||
|
JButton button = new JButton();
|
||||||
|
button.setIcon(icon);
|
||||||
|
button.setToolTipText(toolTip);
|
||||||
|
button.addActionListener(e -> {
|
||||||
|
if (function != null)
|
||||||
|
function.run();
|
||||||
|
});
|
||||||
|
add(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/main/java/de/derpandaa/plinfa/PlinfaWindow.java
Normal file
141
src/main/java/de/derpandaa/plinfa/PlinfaWindow.java
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package de.derpandaa.plinfa;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JSplitPane;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.FlatDarkLaf;
|
||||||
|
import com.formdev.flatlaf.extras.FlatSVGIcon;
|
||||||
|
import com.formdev.flatlaf.extras.FlatSVGIcon.ColorFilter;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.aiprovider.OpenAiProvider;
|
||||||
|
import de.derpandaa.plinfa.dto.ChatDto;
|
||||||
|
|
||||||
|
public class PlinfaWindow extends JFrame {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1340010605617450301L;
|
||||||
|
public static final Color BG = new Color(40, 42, 46);
|
||||||
|
public static final Color BG_ALT = new Color(55, 59, 65);
|
||||||
|
public static final Color FG = new Color(208, 208, 208);
|
||||||
|
public static final Color ACCENT = new Color(0x6A5B75);
|
||||||
|
|
||||||
|
private final Map<ChatDto, ChatWindow> chatCache = new HashMap<>();
|
||||||
|
|
||||||
|
private ChatWindow currentChatWindow;
|
||||||
|
|
||||||
|
private JPanel centerContainer;
|
||||||
|
private ChatListWidget chatListWidget;
|
||||||
|
|
||||||
|
public PlinfaWindow() {
|
||||||
|
super("Plinfa");
|
||||||
|
|
||||||
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
setSize(1200, 800);
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
applyTheme();
|
||||||
|
initUI();
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
PlinfaMenu menu = new PlinfaMenu();
|
||||||
|
FlatSVGIcon addIcon = new FlatSVGIcon("img/add-icon.svg");
|
||||||
|
addIcon.setColorFilter(new ColorFilter((color) -> PlinfaWindow.ACCENT));
|
||||||
|
menu.addButton(addIcon, () -> {
|
||||||
|
loadChat(new ChatDto());
|
||||||
|
}, "Neuer Chat");
|
||||||
|
add(menu, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
chatListWidget = new ChatListWidget(this::loadChat);
|
||||||
|
JScrollPane leftScroll = new JScrollPane(chatListWidget);
|
||||||
|
leftScroll.setFocusable(false);
|
||||||
|
leftScroll.setPreferredSize(new Dimension(260, 0));
|
||||||
|
|
||||||
|
centerContainer = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
loadChat(new ChatDto());
|
||||||
|
centerContainer.add(currentChatWindow, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroll, centerContainer);
|
||||||
|
|
||||||
|
splitPane.setDividerLocation(260);
|
||||||
|
splitPane.setResizeWeight(0.0);
|
||||||
|
splitPane.setBorder(new javax.swing.border.EmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
add(splitPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTheme() {
|
||||||
|
UIManager.put("Panel.background", BG);
|
||||||
|
UIManager.put("OptionPane.background", BG);
|
||||||
|
UIManager.put("Label.foreground", FG);
|
||||||
|
UIManager.put("TextField.background", BG_ALT);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadChat(ChatDto chatDto) {
|
||||||
|
centerContainer.removeAll();
|
||||||
|
|
||||||
|
ChatWindow window = chatCache.get(chatDto);
|
||||||
|
|
||||||
|
if (window == null) {
|
||||||
|
window = new ChatWindow(() -> {
|
||||||
|
chatListWidget.addChat(chatDto);
|
||||||
|
});
|
||||||
|
window.loadChat(chatDto);
|
||||||
|
chatCache.put(chatDto, window);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChatWindow = window;
|
||||||
|
centerContainer.add(currentChatWindow, BorderLayout.CENTER);
|
||||||
|
centerContainer.revalidate();
|
||||||
|
centerContainer.repaint();
|
||||||
|
currentChatWindow.focusInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
initProperties();
|
||||||
|
try {
|
||||||
|
FlatDarkLaf.setup();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
UIManager.put("Component.focusColor", new Color(40, 42, 46));
|
||||||
|
SwingUtilities.invokeLater(PlinfaWindow::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initProperties() {
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
props.load(new FileInputStream("ai.properties"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[ERROR] Cannot load config");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenAiProvider.API_KEY = props.getProperty("OPENAI_KEY");
|
||||||
|
ChatDto.PROJECT_DIR = props.getProperty("PROJECT_DIR");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void noOutline(JComponent c) {
|
||||||
|
c.putClientProperty("focusWidth", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 javax.swing.JOptionPane;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.dto.AiRequest;
|
||||||
|
import de.derpandaa.plinfa.dto.AiResponseDto;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
public abstract class AbstractAiProvider {
|
||||||
|
|
||||||
|
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<Model> 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<String> response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
||||||
|
return parseResponse(response.body());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAsync(AiRequest request, Consumer<String> onResult) {
|
||||||
|
try {
|
||||||
|
String jsonBody = buildRequestBody(request);
|
||||||
|
HttpRequest httpRequest = buildRequest(jsonBody);
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
client.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body)
|
||||||
|
.thenAccept(body -> {
|
||||||
|
AiResponseDto dto = parseResponse(body);
|
||||||
|
onResult.accept(dto != null ? dto.getResponse() : "");
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
JOptionPane.showMessageDialog(null, e.getMessage());
|
||||||
|
onResult.accept("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String buildRequestBody(AiRequest request) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package de.derpandaa.plinfa.aiprovider;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.dto.AiRequest;
|
||||||
|
|
||||||
|
public class AiProviderManager {
|
||||||
|
private final List<AbstractAiProvider> providers;
|
||||||
|
private static AiProviderManager aiProviderManager;
|
||||||
|
private List<Model> models = new ArrayList<Model>();
|
||||||
|
|
||||||
|
public AiProviderManager() {
|
||||||
|
providers = new ArrayList<AbstractAiProvider>();
|
||||||
|
if (OpenAiProvider.API_KEY != null) {
|
||||||
|
providers.add(new OpenAiProvider());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AiProviderManager getAiProviderManager() {
|
||||||
|
if (aiProviderManager == null) {
|
||||||
|
aiProviderManager = new AiProviderManager();
|
||||||
|
}
|
||||||
|
return aiProviderManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Model> getActiveModels() {
|
||||||
|
if (models.isEmpty()) {
|
||||||
|
for (AbstractAiProvider aiProvider : providers) {
|
||||||
|
models.addAll(aiProvider.getModels());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractAiProvider getProviderForModel(Model model) {
|
||||||
|
for (AbstractAiProvider aiProvider : providers) {
|
||||||
|
if (model.clazz().equals(aiProvider.getClass())) {
|
||||||
|
return aiProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAsync(AiRequest aiRequest, Consumer<String> onResult) {
|
||||||
|
getProviderForModel(aiRequest.getModel()).runAsync(aiRequest, onResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
src/main/java/de/derpandaa/plinfa/aiprovider/Model.java
Normal file
16
src/main/java/de/derpandaa/plinfa/aiprovider/Model.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package de.derpandaa.plinfa.aiprovider;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
public record Model(String modelName, Class<? extends AbstractAiProvider> clazz) {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public String modelName() {
|
||||||
|
return modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package de.derpandaa.plinfa.aiprovider;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.derpandaa.plinfa.dto.AiRequest;
|
||||||
|
import de.derpandaa.plinfa.dto.AiResponseDto;
|
||||||
|
import de.derpandaa.plinfa.dto.MessageDto;
|
||||||
|
import de.derpandaa.plinfa.dto.OpenAiResponseDto;
|
||||||
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
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<Model> getModels() {
|
||||||
|
|
||||||
|
return List.of(new Model("gpt-5-nano", this.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AiResponseDto parseResponse(String body) {
|
||||||
|
return new OpenAiResponseDto(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String buildRequestBody(AiRequest request) throws Exception {
|
||||||
|
|
||||||
|
ObjectNode root = mapper.createObjectNode();
|
||||||
|
|
||||||
|
root.put("model", request.getModel().modelName());
|
||||||
|
root.put("max_completion_tokens", request.getMaxTokens());
|
||||||
|
|
||||||
|
if (request.getSystemPrompt() != null) {
|
||||||
|
ArrayNode messages = root.putArray("messages");
|
||||||
|
|
||||||
|
ObjectNode system = mapper.createObjectNode();
|
||||||
|
system.put("role", "system");
|
||||||
|
system.put("content", request.getSystemPrompt());
|
||||||
|
messages.add(system);
|
||||||
|
|
||||||
|
for (MessageDto m : request.getMessages()) {
|
||||||
|
ObjectNode msg = mapper.createObjectNode();
|
||||||
|
msg.put("role", m.getRole().name());
|
||||||
|
msg.put("content", m.getContent());
|
||||||
|
messages.add(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.writeValueAsString(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
src/main/java/de/derpandaa/plinfa/dto/AiRequest.java
Normal file
51
src/main/java/de/derpandaa/plinfa/dto/AiRequest.java
Normal file
@@ -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<MessageDto> 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<MessageDto> getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMessages(MessageDto message) {
|
||||||
|
messages.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/java/de/derpandaa/plinfa/dto/AiResponseDto.java
Normal file
23
src/main/java/de/derpandaa/plinfa/dto/AiResponseDto.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
68
src/main/java/de/derpandaa/plinfa/dto/ChatDto.java
Normal file
68
src/main/java/de/derpandaa/plinfa/dto/ChatDto.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package de.derpandaa.plinfa.dto;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import tools.jackson.core.JacksonException;
|
||||||
|
import tools.jackson.core.type.TypeReference;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
public class ChatDto {
|
||||||
|
|
||||||
|
public static String PROJECT_DIR;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
List<MessageDto> messages;
|
||||||
|
|
||||||
|
public ChatDto() {
|
||||||
|
messages = new ArrayList<MessageDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatDto(File f) {
|
||||||
|
loadFromFile(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MessageDto> getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMessage(MessageDto messageDto) {
|
||||||
|
messages.add(messageDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromFile(File f) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
setName(f.getName().substring(0, f.getName().indexOf(".json")));
|
||||||
|
try {
|
||||||
|
messages = mapper.readValue(f, new TypeReference<List<MessageDto>>() {
|
||||||
|
});
|
||||||
|
} catch (JacksonException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.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(), messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package de.derpandaa.aitestsuit;
|
package de.derpandaa.plinfa.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||||
|
|
||||||
|
@JsonPropertyOrder({ "role", "content" })
|
||||||
public class MessageDto {
|
public class MessageDto {
|
||||||
|
|
||||||
public enum Role {
|
public enum Role {
|
||||||
@@ -13,6 +15,9 @@ public class MessageDto {
|
|||||||
@JsonProperty("content")
|
@JsonProperty("content")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
public MessageDto() {
|
||||||
|
}
|
||||||
|
|
||||||
public MessageDto(Role role, String content) {
|
public MessageDto(Role role, String content) {
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
@@ -22,4 +27,8 @@ public class MessageDto {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Role getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
17
src/main/java/de/derpandaa/plinfa/dto/OpenAiResponseDto.java
Normal file
17
src/main/java/de/derpandaa/plinfa/dto/OpenAiResponseDto.java
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<GenericProject> subProjects;
|
||||||
|
private List<ChatDto> 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<GenericProject> getSubProjects() {
|
||||||
|
return subProjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChatDto> getChats() {
|
||||||
|
return chats;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/main/resources/img/add-icon.svg
Normal file
3
src/main/resources/img/add-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" stroke="#6A5B75" stroke-linecap="round" stroke-width="1.5" d="M15 12h-3m0 0H9m3 0V9m0 3v3m10-3c0 4.714 0 7.071-1.465 8.535C19.072 22 16.714 22 12 22s-7.071 0-8.536-1.465C2 19.072 2 16.714 2 12s0-7.071 1.464-8.536C4.93 2 7.286 2 12 2s7.071 0 8.535 1.464c.974.974 1.3 2.343 1.41 4.536" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 396 B |
Reference in New Issue
Block a user