Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8612efd
Removed unused chat view cache.
HackusatePvP Nov 7, 2025
6680658
Enabled window scaling.
HackusatePvP Nov 7, 2025
08781bd
Updated ren-engine -> 1.0.6
HackusatePvP Nov 11, 2025
d9160a0
Added window scaling setting.
HackusatePvP Nov 11, 2025
3a4e520
Fixed issues with chat switching.
HackusatePvP Nov 11, 2025
2193aff
Adjusted map import.
HackusatePvP Nov 11, 2025
6924a4e
Added window scaling.
HackusatePvP Nov 11, 2025
ba335d9
Addressed qodana issues.
HackusatePvP Nov 11, 2025
19f6adb
Added character card exporter.
HackusatePvP Nov 11, 2025
a05aaf1
Window scaling parameter will now write to file.
HackusatePvP Nov 11, 2025
6f97721
Added user exporter and refactored classes.
HackusatePvP Nov 12, 2025
16fb887
Devices will be outputted to logger.
HackusatePvP Nov 12, 2025
4b75e12
Added export button for user templates.
HackusatePvP Nov 12, 2025
a6da885
Fixed design layout with user templates.
HackusatePvP Nov 12, 2025
b6f6721
Fixed issues when changing text size.
HackusatePvP Nov 12, 2025
eee6f2f
Fixed issues for detecting running process.
HackusatePvP Nov 12, 2025
5d13af8
Updated last-chat field when switching between chats.
HackusatePvP Nov 12, 2025
5831d0d
Fixed incorrect output path for user templates.
HackusatePvP Nov 12, 2025
296d38f
Implemented user template lorebook.
HackusatePvP Nov 12, 2025
6885633
Fixed import button name and removed disabling of the button.
HackusatePvP Nov 12, 2025
209ef15
Fixed issue where character image would be deleted.
HackusatePvP Nov 13, 2025
0fe53eb
Moved export button under import button.
HackusatePvP Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<dependency>
<groupId>me.piitex.engine</groupId>
<artifactId>ren-engine</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>1.0.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
22 changes: 17 additions & 5 deletions src/main/java/me/piitex/app/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
private volatile boolean error = false;

// Used for testing with the IDE!
static void main() {

Check warning on line 65 in src/main/java/me/piitex/app/App.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Confusing 'main()' method

Method `main()` does not have signature 'public static void main(String\[\])'
new App();
Application.launch(App.class);
}
Expand All @@ -74,12 +74,13 @@
setupDirectories();

settings = new ServerSettings();
appSettings = new AppSettings();

long currentPid = ProcessHandle.current().pid();
if (settings.getInfoFile().hasKey("main-pid")) {
String pid = settings.getInfoFile().get("main-pid");
if (ProcessUtil.isProcessRunning(Long.parseLong(pid))) {
logger.error("Process already running!");
logger.error("Process already running! '{}'", pid);
error = true;
Platform.runLater(() -> {
buildErrorWindow("Process is already running!").render();
Expand All @@ -101,7 +102,6 @@
App.logger.info("Finished pre-initialization.");
loading = false;
});
appSettings = new AppSettings();
}

@Override
Expand Down Expand Up @@ -160,7 +160,7 @@
// This is because the pathing for the image doesn't change but the image gets replaced by the new image.
ImageLoader.useCache = false;

window = new WindowBuilder("Chat App").setIcon(new ImageLoader(new File(App.getAppDirectory(), "logo.png"))).setScale(false).setDimensions(setWidth, setHeight).build();
window = new WindowBuilder("Chat App").setIcon(new ImageLoader(new File(App.getAppDirectory(), "logo.png"))).setScale((appSettings.isWindowScaling()) && !mobile).setAntiAliasing(false).setDimensions(setWidth, setHeight).build();

// Initialize global positions. Needed for the rendering process.
Positions.initialize();
Expand Down Expand Up @@ -326,12 +326,22 @@
downloader.startDownload(dataFileUrl, currentData);
}

logger.info("Model list updated...");
logger.info("Model list updated.");
downloader.shutdown();
} catch (IOException e) {
App.logger.error("Failed to fetch download size.");
}

// TODO: Automatically update llamacpp
// try {
// GitHubUtil gitHubUtil = new GitHubUtil("https://api.github.com/repos/ggerganov/llama.cpp/");
// FileDownloader llamaDownloader = gitHubUtil.downloadAsset(gitHubUtil.getReleaseAsset(gitHubUtil.getLatestReleaseID(), "llama-[a-zA-Z0-9]+-bin-win-cuda-12\\.4-x64\\.zip").getInt("id"), new File("output/download.zip"));
//
// } catch (IOException e) {
// throw new RuntimeException(e);
// }


}

public AppSettings getAppSettings() {
Expand Down Expand Up @@ -373,7 +383,7 @@

public Window buildErrorWindow(String message) {
if (window != null) {
window.close();
window.close(true);
}

Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet());
Expand All @@ -400,6 +410,8 @@
App.logger.info("Forcefully killing old process.");
ProcessUtil.terminateProcess(Long.parseLong(settings.getInfoFile().get("main-pid")));
}

appSettings.getInfoFile().set("main-pid", "");
});

window.getStage().setOnCloseRequest(event -> {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/me/piitex/app/backend/Character.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import me.piitex.app.App;
import me.piitex.app.configuration.ModelSettings;
import me.piitex.app.views.chats.ChatView;
import me.piitex.engine.LimitedHashMap;
import me.piitex.engine.maps.LimitedHashMap;
import me.piitex.os.configurations.InfoFile;

import java.io.File;
Expand Down Expand Up @@ -122,7 +122,7 @@

private void loadChats() {
if (getChatDirectory() == null || !getChatDirectory().exists()) return;
for (File file : getChatDirectory().listFiles()) {

Check warning on line 125 in src/main/java/me/piitex/app/backend/Character.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Nullability and data flow problems

Dereference of `getChatDirectory().listFiles()` may produce `NullPointerException`
if (file.isDirectory()) continue;
Chat chat = new Chat(file);
chats.add(chat);
Expand Down Expand Up @@ -288,7 +288,7 @@
return infoFile;
}

public Map<Chat, ChatView> getChatViewCachedNodes() {

Check warning on line 291 in src/main/java/me/piitex/app/backend/Character.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Class is exposed outside of its visibility scope

Class `ChatView` is not exported from module 'me.piitex.app'
return chatViewCachedNodes;
}

Expand Down
10 changes: 0 additions & 10 deletions src/main/java/me/piitex/app/backend/Chat.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.piitex.app.backend;

import me.piitex.app.App;
import me.piitex.app.views.chats.ChatView;
import me.piitex.os.configurations.FileCrypter;

import javax.crypto.IllegalBlockSizeException;
Expand All @@ -17,7 +16,6 @@ public class Chat {
private Response response;
private final LinkedList<ChatMessage> messages = new LinkedList<>();
private final boolean dev = false;
private ChatView cachedView;


public Chat(File file) {
Expand Down Expand Up @@ -240,12 +238,4 @@ public Response getResponse() {
public void setResponse(Response response) {
this.response = response;
}

public ChatView getCachedView() {
return cachedView;
}

public void setCachedView(ChatView cachedView) {
this.cachedView = cachedView;
}
}
4 changes: 2 additions & 2 deletions src/main/java/me/piitex/app/backend/server/DeviceProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.LinkedList;
import java.util.List;

public class DeviceProcess {
private final Process process;
Expand Down Expand Up @@ -37,12 +38,11 @@ public DeviceProcess(String backend) throws IOException {
}

public void handleOutput() {
App.logger.info("Handling input...");
try {
LinkedList<String> lines = new LinkedList<>(Files.readAllLines(new File(App.getDataDirectory(), "devices.txt").toPath()));
lines.removeFirst();
App.getInstance().getSettings().setDevices(lines);

App.logger.info("Devices: {}", List.of(lines));
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
19 changes: 16 additions & 3 deletions src/main/java/me/piitex/app/configuration/AppSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class AppSettings {
private String textColor;
private String quoteColor;
private String astrixColor;
private boolean windowScaling = false;

private final InfoFile infoFile;

Expand Down Expand Up @@ -55,6 +56,9 @@ public AppSettings() {
} else {
this.astrixColor = Color.DODGERBLUE.toString();
}
if (infoFile.hasKey("window-scaling")) {
this.windowScaling = infoFile.getBoolean("window-scaling");
}
}

public int getWidth() {
Expand Down Expand Up @@ -142,10 +146,19 @@ public void setAstrixColor(String astrixColor) {
infoFile.set("astrix-color", astrixColor);
}

public boolean isWindowScaling() {
return windowScaling;
}

public void setWindowScaling(boolean windowScaling) {
this.windowScaling = windowScaling;
infoFile.set("window-scaling", windowScaling);
}

/*
Utility functions for getting theme coloring.
Needed for RichTextFX components and BBCode
*/
Utility functions for getting theme coloring.
Needed for RichTextFX components and BBCode
*/
public Theme getStyleTheme(String name) {
if (name.equalsIgnoreCase("primer light")) {
return new PrimerLight();
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/me/piitex/app/utils/CharacterCardImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,22 @@ public static String getChatScenario(JSONObject metaData) throws JSONException {
return "";
}

public static String getUserDisplay(JSONObject metaData) {
JSONObject characterJson = getCharacterJson(metaData);
if (characterJson.has("userDisplay")) {
return characterJson.getString("userDisplay");
}
return null;
}

public static String getUserPersona(JSONObject metaData) {
JSONObject characterJson = getCharacterJson(metaData);
if (characterJson.has("userPersona")) {
return characterJson.getString("userPersona");
}
return null;
}

public static Map<String, String> getLoreItems(JSONObject metaData) throws JSONException {
JSONObject characterJson = getCharacterJson(metaData);
Map<String, String> map = new HashMap<>();
Expand Down
183 changes: 183 additions & 0 deletions src/main/java/me/piitex/app/utils/ImageCardExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package me.piitex.app.utils;

import me.piitex.app.App;
import me.piitex.app.backend.Character;
import me.piitex.app.backend.User;
import org.json.JSONArray;
import org.json.JSONObject;

import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Base64;
import java.util.Iterator;

public class ImageCardExporter {

public static void exportCharacter(Character character, File output) throws IOException {

Files.copy(new File(character.getIconPath()).toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);

BufferedImage image = ImageIO.read(output);

Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("png");
if (!writers.hasNext()) {
App.logger.error("Could not find png writer for exporter.", new RuntimeException());
return;
}
ImageWriter writer = writers.next();

// Prepare ImageWriteParam and IIOMetadata
ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromRenderedImage(image);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
String nativeFormat = metadata.getNativeMetadataFormatName();

// Create the native metadata tree
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(nativeFormat);
IIOMetadataNode textNode = null;


// Check if a tEXt node already exists
for (int i = 0; i < root.getChildNodes().getLength(); i++) {
if (root.getChildNodes().item(i).getNodeName().equals("tEXt")) {
textNode = (IIOMetadataNode) root.getChildNodes().item(i);
break;
}
}

// If not, create a new one and append it to the root
if (textNode == null) {
textNode = new IIOMetadataNode("tEXt");
root.appendChild(textNode);
}

IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry");
textEntry.setAttribute("keyword", "chara");
textEntry.setAttribute("value", Base64.getEncoder().encodeToString(toJson(character).toString().getBytes(StandardCharsets.UTF_8)));
textNode.appendChild(textEntry);


metadata.setFromTree(nativeFormat, root);

try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
writer.setOutput(stream);
writer.write(new IIOImage(image, null, metadata));
} finally {
writer.dispose();
}
}

public static void exportUser(User user, File output) throws IOException {

Files.copy(new File(user.getIconPath()).toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);

BufferedImage image = ImageIO.read(output);

Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("png");
if (!writers.hasNext()) {
App.logger.error("Could not find png writer for exporter.", new RuntimeException());
return;
}
ImageWriter writer = writers.next();

// Prepare ImageWriteParam and IIOMetadata
ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromRenderedImage(image);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
String nativeFormat = metadata.getNativeMetadataFormatName();

// Create the native metadata tree
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(nativeFormat);
IIOMetadataNode textNode = null;


// Check if a tEXt node already exists
for (int i = 0; i < root.getChildNodes().getLength(); i++) {
if (root.getChildNodes().item(i).getNodeName().equals("tEXt")) {
textNode = (IIOMetadataNode) root.getChildNodes().item(i);
break;
}
}

// If not, create a new one and append it to the root
if (textNode == null) {
textNode = new IIOMetadataNode("tEXt");
root.appendChild(textNode);
}

IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry");
textEntry.setAttribute("keyword", "user");
textEntry.setAttribute("value", Base64.getEncoder().encodeToString(toJson(user).toString().getBytes(StandardCharsets.UTF_8)));
textNode.appendChild(textEntry);


metadata.setFromTree(nativeFormat, root);

try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
writer.setOutput(stream);
writer.write(new IIOImage(image, null, metadata));
} finally {
writer.dispose();
}
}

public static JSONObject toJson(Character character) {
JSONObject root = new JSONObject();

JSONObject characterRoot = new JSONObject();
characterRoot.put("basePrompt", App.getInstance().getSettings().getGlobalModel().getSettings().getModelInstructions());
characterRoot.put("customDialogue", "");
characterRoot.put("firstMessage", character.getFirstMessage());
characterRoot.put("scenario", character.getChatScenario());
characterRoot.put("aiDisplayName", character.getDisplayName());
characterRoot.put("aiName", character.getId());
characterRoot.put("aiPersona", character.getPersona());

JSONArray loreRoot = new JSONArray();

for (String loreId : character.getLorebook().keySet()) {
String loreValue = character.getLorebook().get(loreId);
JSONObject loreEntry = new JSONObject();
loreEntry.put("id", loreId);
loreEntry.put("key", loreId);
loreEntry.put("value", loreValue);
loreRoot.put(loreEntry);
}
characterRoot.put("loreItems", loreRoot);
root.put("character", characterRoot);

return root;
}

public static JSONObject toJson(User user) {
JSONObject root = new JSONObject();

JSONObject characterRoot = new JSONObject();
characterRoot.put("userDisplay", user.getDisplayName());
characterRoot.put("userPersona", user.getPersona());

JSONArray loreRoot = new JSONArray();

for (String loreId : user.getLorebook().keySet()) {
String loreValue = user.getLorebook().get(loreId);
JSONObject loreEntry = new JSONObject();
loreEntry.put("id", loreId);
loreEntry.put("key", loreId);
loreEntry.put("value", loreValue);
loreRoot.put(loreEntry);
}
characterRoot.put("loreItems", loreRoot);
root.put("user", characterRoot);

return root;
}

}
Loading
Loading