diff --git a/pom.xml b/pom.xml
index af690af3..193012dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,7 @@
me.piitex.engine
ren-engine
- 1.0.4-SNAPSHOT
+ 1.0.6-SNAPSHOT
org.junit.jupiter
diff --git a/src/main/java/me/piitex/app/App.java b/src/main/java/me/piitex/app/App.java
index 3cc4b58d..73cad8fa 100644
--- a/src/main/java/me/piitex/app/App.java
+++ b/src/main/java/me/piitex/app/App.java
@@ -74,12 +74,13 @@ public void preInitialization() {
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();
@@ -101,7 +102,6 @@ public void preInitialization() {
App.logger.info("Finished pre-initialization.");
loading = false;
});
- appSettings = new AppSettings();
}
@Override
@@ -160,7 +160,7 @@ public void initialization(Stage initialStage) {
// 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();
@@ -326,12 +326,22 @@ public void performUpdates() {
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() {
@@ -373,7 +383,7 @@ public Map getUserTemplates() {
public Window buildErrorWindow(String message) {
if (window != null) {
- window.close();
+ window.close(true);
}
Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet());
@@ -400,6 +410,8 @@ public Window buildErrorWindow(String message) {
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 -> {
diff --git a/src/main/java/me/piitex/app/backend/Character.java b/src/main/java/me/piitex/app/backend/Character.java
index 6cec8579..a861a437 100644
--- a/src/main/java/me/piitex/app/backend/Character.java
+++ b/src/main/java/me/piitex/app/backend/Character.java
@@ -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;
diff --git a/src/main/java/me/piitex/app/backend/Chat.java b/src/main/java/me/piitex/app/backend/Chat.java
index 976c0f8e..65390df8 100644
--- a/src/main/java/me/piitex/app/backend/Chat.java
+++ b/src/main/java/me/piitex/app/backend/Chat.java
@@ -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;
@@ -17,7 +16,6 @@ public class Chat {
private Response response;
private final LinkedList messages = new LinkedList<>();
private final boolean dev = false;
- private ChatView cachedView;
public Chat(File file) {
@@ -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;
- }
}
diff --git a/src/main/java/me/piitex/app/backend/server/DeviceProcess.java b/src/main/java/me/piitex/app/backend/server/DeviceProcess.java
index 1ee3d5c4..b156b3c4 100644
--- a/src/main/java/me/piitex/app/backend/server/DeviceProcess.java
+++ b/src/main/java/me/piitex/app/backend/server/DeviceProcess.java
@@ -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;
@@ -37,12 +38,11 @@ public DeviceProcess(String backend) throws IOException {
}
public void handleOutput() {
- App.logger.info("Handling input...");
try {
LinkedList 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);
}
diff --git a/src/main/java/me/piitex/app/configuration/AppSettings.java b/src/main/java/me/piitex/app/configuration/AppSettings.java
index 32025325..51779792 100644
--- a/src/main/java/me/piitex/app/configuration/AppSettings.java
+++ b/src/main/java/me/piitex/app/configuration/AppSettings.java
@@ -16,6 +16,7 @@ public class AppSettings {
private String textColor;
private String quoteColor;
private String astrixColor;
+ private boolean windowScaling = false;
private final InfoFile infoFile;
@@ -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() {
@@ -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();
diff --git a/src/main/java/me/piitex/app/utils/CharacterCardImporter.java b/src/main/java/me/piitex/app/utils/CharacterCardImporter.java
index 909f4303..ae859108 100644
--- a/src/main/java/me/piitex/app/utils/CharacterCardImporter.java
+++ b/src/main/java/me/piitex/app/utils/CharacterCardImporter.java
@@ -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 getLoreItems(JSONObject metaData) throws JSONException {
JSONObject characterJson = getCharacterJson(metaData);
Map map = new HashMap<>();
diff --git a/src/main/java/me/piitex/app/utils/ImageCardExporter.java b/src/main/java/me/piitex/app/utils/ImageCardExporter.java
new file mode 100644
index 00000000..224fa06e
--- /dev/null
+++ b/src/main/java/me/piitex/app/utils/ImageCardExporter.java
@@ -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 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 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;
+ }
+
+}
diff --git a/src/main/java/me/piitex/app/utils/UserCardImporter.java b/src/main/java/me/piitex/app/utils/UserCardImporter.java
new file mode 100644
index 00000000..1f06ee36
--- /dev/null
+++ b/src/main/java/me/piitex/app/utils/UserCardImporter.java
@@ -0,0 +1,94 @@
+package me.piitex.app.utils;
+
+import com.drew.imaging.ImageMetadataReader;
+import com.drew.imaging.ImageProcessingException;
+import com.drew.metadata.Directory;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.Tag;
+import me.piitex.app.App;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+public class UserCardImporter {
+ public static JSONObject getImageMetaData(File file) throws ImageProcessingException, IOException {
+ App.logger.info("Gathering card data...");
+ Metadata metadata = ImageMetadataReader.readMetadata(file);
+ JSONObject toReturn = null;
+
+
+ for (Directory directory : metadata.getDirectories()) {
+ for (Tag tag : directory.getTags()) {
+ // Slightly easier with Silly Tavern. They all start with Chara: for the metadata.
+ if (tag.getDescription().startsWith("user")) {
+ // Remove the beginning "chara: " so it follows the json scheme.
+ String data = tag.getDescription().replace("user: ", "");
+ try {
+ data = new String(Base64.getDecoder().decode(data));
+ } catch (IllegalArgumentException ignored) {
+ // Thrown if the user entry cannot be decoded into base64.
+ // This happens if there is multiple user entries which is possible.
+ // Go to next entry to loop.
+ continue;
+ }
+ try {
+ toReturn = new JSONObject(data);
+ return toReturn;
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static JSONObject getUserJson(JSONObject metaData) throws JSONException {
+ if (metaData.has("user")) {
+ return metaData.getJSONObject("user");
+ }
+ // Can process other structures
+
+ return metaData;
+ }
+
+ public static String getUserDisplay(JSONObject metaData) {
+ JSONObject userJson = getUserJson(metaData);
+ if (userJson.has("userDisplay")) {
+ return userJson.getString("userDisplay");
+ }
+ return null;
+ }
+
+ public static String getUserPersona(JSONObject metaData) {
+ JSONObject userJson = getUserJson(metaData);
+ if (userJson.has("userPersona")) {
+ return userJson.getString("userPersona");
+ }
+ return null;
+ }
+
+ public static Map getLoreItems(JSONObject metaData) throws JSONException {
+ JSONObject userJson = getUserJson(metaData);
+ Map map = new HashMap<>();
+ if (userJson.has("loreItems")) {
+
+ JSONArray array = userJson.getJSONArray("loreItems");
+ for (int i = 0; i < array.length(); i++) {
+ JSONObject object = array.getJSONObject(i);
+ String key = object.getString("key");
+ String value = object.getString("value");
+ map.put(key, value);
+ }
+
+ return map;
+ }
+ return map;
+ }
+}
diff --git a/src/main/java/me/piitex/app/views/characters/CharacterEditView.java b/src/main/java/me/piitex/app/views/characters/CharacterEditView.java
index 5544bd78..7b24d46e 100644
--- a/src/main/java/me/piitex/app/views/characters/CharacterEditView.java
+++ b/src/main/java/me/piitex/app/views/characters/CharacterEditView.java
@@ -340,9 +340,11 @@ public HorizontalLayout buildSubmitBox() {
characterSpecificUser.setPersona(userPersona);
if (userIconPath != null && userIconPath.exists()) {
- File output = new File(currentCharacterInstance.getUserDirectory(), userIconPath.getName());
- Files.copy(userIconPath.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
- characterSpecificUser.setIconPath(output.getAbsolutePath());
+ File output = new File(character.getUserDirectory(), userIconPath.getName());
+ if (!userIconPath.toPath().equals(output.toPath())) {
+ Files.copy(userIconPath.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ characterSpecificUser.setIconPath(output.getAbsolutePath());
+ }
}
character.setUser(characterSpecificUser);
diff --git a/src/main/java/me/piitex/app/views/characters/CharactersView.java b/src/main/java/me/piitex/app/views/characters/CharactersView.java
index c8635f93..951c5764 100644
--- a/src/main/java/me/piitex/app/views/characters/CharactersView.java
+++ b/src/main/java/me/piitex/app/views/characters/CharactersView.java
@@ -19,6 +19,7 @@
import me.piitex.engine.layouts.FlowLayout;
import me.piitex.engine.layouts.HorizontalLayout;
import me.piitex.engine.layouts.VerticalLayout;
+import me.piitex.engine.loaders.ImageLoader;
import me.piitex.engine.overlays.*;
import org.apache.commons.io.FileUtils;
import org.kordamp.ikonli.javafx.FontIcon;
@@ -255,7 +256,9 @@ private void deleteCharacter(FlowLayout base, CardContainer card, Character char
// Add a buffer to ensure image resources are disposed.
App.getThreadPoolManager().submitSchedule(() -> {
try {
- App.logger.info("Deleting: {}", character.getId());
+ App.logger.info("Removing image from cache '{}'", character.getIconPath());
+ ImageLoader.imageCache.remove(character.getIconPath()); // Clear image from cache.
+ App.logger.info("Deleting Character: {}", character.getId());
FileUtils.deleteDirectory(character.getCharacterDirectory());
} catch (IOException e) {
App.logger.error("Could not delete directory!", e);
diff --git a/src/main/java/me/piitex/app/views/characters/tabs/CharacterTab.java b/src/main/java/me/piitex/app/views/characters/tabs/CharacterTab.java
index 30f49c99..71dd3744 100644
--- a/src/main/java/me/piitex/app/views/characters/tabs/CharacterTab.java
+++ b/src/main/java/me/piitex/app/views/characters/tabs/CharacterTab.java
@@ -12,6 +12,7 @@
import me.piitex.app.App;
import me.piitex.app.backend.User;
import me.piitex.app.configuration.AppSettings;
+import me.piitex.app.utils.ImageCardExporter;
import me.piitex.app.utils.CharacterCardImporter;
import me.piitex.app.views.characters.CharacterEditView;
import me.piitex.engine.containers.CardContainer;
@@ -85,7 +86,6 @@ private void buildCharacterTabContent(@Nullable Character character, boolean dup
charDescription.addStyle(Styles.TEXT_ON_EMPHASIS);
rootLayout.addElement(charDescription);
-
//rootLayout.addElement(buildExampleDialogue());
this.addElement(parentView.buildSubmitBox());
@@ -177,9 +177,11 @@ private VerticalLayout buildCharacterInput(@Nullable Character character, boolea
importCard.setWidth(200);
importCard.setHeight(50);
- FileChooserOverlay fileSelector = new FileChooserOverlay(App.window, importCard);
- root.addElement(fileSelector);
- fileSelector.onFileSelect(event -> {
+ FileChooserOverlay importSelector = new FileChooserOverlay(App.window, importCard);
+ importSelector.setText("Import character card.");
+ importSelector.setFileExtensions(new String[]{"*.png"});
+ root.addElement(importSelector);
+ importSelector.onFileSelect(event -> {
File file = event.getDirectory();
try {
JSONObject metadata = CharacterCardImporter.getImageMetaData(file);
@@ -194,6 +196,15 @@ private VerticalLayout buildCharacterInput(@Nullable Character character, boolea
parentView.getChatTabInstance().getFirstMessageInput().setCurrentText(CharacterCardImporter.getFirstMessage(metadata));
parentView.getChatTabInstance().getChatScenarioInput().setCurrentText(CharacterCardImporter.getChatScenario(metadata));
+ String userDisplay = CharacterCardImporter.getUserDisplay(metadata);
+ if (userDisplay != null) {
+ parentView.getUserTabInstance().getUserDisplayNameInput().setCurrentText(userDisplay);
+ }
+ String userPersona = CharacterCardImporter.getUserPersona(metadata);
+ if (userPersona != null) {
+ parentView.getUserTabInstance().getUserDescription().setCurrentText(userPersona);
+ }
+
parentView.setCharacterIconPath(file);
parentView.getInfoFile().set("icon-path", file.getAbsolutePath());
image.setImage(new ImageLoader(file));
@@ -211,6 +222,34 @@ private VerticalLayout buildCharacterInput(@Nullable Character character, boolea
}
});
+ ButtonOverlay exportCard = new ButtonBuilder("import").setText("Export Character Card").build();
+ if (character == null) {
+ exportCard.setEnabled(false);
+ }
+ exportCard.addStyle(Styles.ACCENT);
+ exportCard.addStyle(Styles.BUTTON_OUTLINED);
+ exportCard.setWidth(200);
+ exportCard.setHeight(50);
+
+ FileChooserOverlay exportSelector = new FileChooserOverlay(App.window, exportCard);
+ exportSelector.setText("Export character card as.");
+ exportSelector.setFileExtensions(new String[]{"*.png"});
+ root.addElement(exportSelector);
+ exportSelector.onFileSelect(event -> {
+ File file = event.getDirectory();
+ try {
+ // Character cannot be null as the button is disabled if it is.
+ ImageCardExporter.exportCharacter(character, file);
+ } catch (IOException e) {
+ App.logger.error("Error importing character card: ", e);
+ Platform.runLater(() -> {
+ MessageOverlay errorOverlay = new MessageOverlay(0, 0, 500, 50, "Import Failed", "Could not import character card: " + e.getMessage());
+ errorOverlay.addStyle(Styles.DANGER);
+ errorOverlay.addStyle(Styles.BG_DEFAULT);
+ App.window.renderPopup(errorOverlay, 650, 870, 500, 50, false, null);
+ });
+ }
+ });
return root;
}
diff --git a/src/main/java/me/piitex/app/views/characters/tabs/UserTab.java b/src/main/java/me/piitex/app/views/characters/tabs/UserTab.java
index 669b8e2c..880eff1e 100644
--- a/src/main/java/me/piitex/app/views/characters/tabs/UserTab.java
+++ b/src/main/java/me/piitex/app/views/characters/tabs/UserTab.java
@@ -1,11 +1,15 @@
package me.piitex.app.views.characters.tabs;
import atlantafx.base.theme.Styles;
+import com.drew.imaging.ImageProcessingException;
+import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.stage.FileChooser;
import me.piitex.app.App;
import me.piitex.app.backend.User;
import me.piitex.app.configuration.AppSettings;
+import me.piitex.app.utils.ImageCardExporter;
+import me.piitex.app.utils.UserCardImporter;
import me.piitex.os.configurations.InfoFile;
import me.piitex.app.views.characters.CharacterEditView;
import me.piitex.engine.containers.CardContainer;
@@ -15,8 +19,10 @@
import me.piitex.engine.layouts.VerticalLayout;
import me.piitex.engine.loaders.ImageLoader;
import me.piitex.engine.overlays.*;
+import org.json.JSONObject;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -78,10 +84,9 @@ private void buildUserTabContent() {
userDescription.addStyle(Styles.BG_DEFAULT);
userDescription.addStyle(appSettings.getChatTextSize());
userDescription.addStyle(Styles.TEXT_ON_EMPHASIS);
-
rootLayout.addElement(userDescription);
- this.addElement(parentView.buildSubmitBox());
+ addElement(parentView.buildSubmitBox());
}
private CardContainer buildUserDisplay() {
@@ -186,11 +191,87 @@ private VerticalLayout buildUserInput() {
parentView.setUserIconPath(new File(template.getIconPath()));
image.setImage(new ImageLoader(parentView.getUserIconPath()));
}
+
+ if (parentView.getUser() != null) {
+ parentView.getUser().getLorebook().keySet().forEach(s -> parentView.getLoreBookTabInstance().getItems().remove(s));
+ }
+
+ parentView.getLoreBookTabInstance().getItems().putAll(template.getLorebook());
+ parentView.getLoreBookTabInstance().buildLorebookTabContent();
}
parentView.updateInfoData();
});
+ ButtonOverlay importCard = new ButtonBuilder("import").setText("Import User Card").build();
+ importCard.addStyle(Styles.ACCENT);
+ importCard.addStyle(Styles.BUTTON_OUTLINED);
+ importCard.setWidth(200);
+ importCard.setHeight(50);
+
+ FileChooserOverlay fileSelector = new FileChooserOverlay(App.window, importCard);
+ fileSelector.setText("Import user card.");
+ fileSelector.setFileExtensions(new String[]{"*.png"});
+ root.addElement(fileSelector);
+ fileSelector.onFileSelect(event -> {
+ File file = event.getDirectory();
+ try {
+ JSONObject metadata = UserCardImporter.getImageMetaData(file);
+ userDisplayNameInput.setCurrentText(UserCardImporter.getUserDisplay(metadata));
+ userDescription.setCurrentText(UserCardImporter.getUserPersona(metadata));
+
+ if (parentView.getUser() != null) {
+ parentView.getUser().getLorebook().keySet().forEach(s -> parentView.getLoreBookTabInstance().getItems().remove(s));
+ }
+ parentView.getLoreBookTabInstance().getItems().putAll(UserCardImporter.getLoreItems(metadata));
+ parentView.getLoreBookTabInstance().buildLorebookTabContent();
+
+ parentView.setUserIconPath(file);
+ parentView.getInfoFile().set("icon-path-user", file.getAbsolutePath());
+ image.setImage(new ImageLoader(file));
+
+ parentView.updateInfoData();
+
+ } catch (ImageProcessingException | IOException e) {
+ App.logger.error("Error importing user card: ", e);
+ Platform.runLater(() -> {
+ MessageOverlay errorOverlay = new MessageOverlay(0, 0, 500, 50, "Import Failed", "Could not import user card: " + e.getMessage());
+ errorOverlay.addStyle(Styles.DANGER);
+ errorOverlay.addStyle(Styles.BG_DEFAULT);
+ App.window.renderPopup(errorOverlay, 650, 870, 500, 50, false, null);
+ });
+ }
+ });
+
+ ButtonOverlay exportCard = new ButtonBuilder("export").setText("Export User Card").build();
+ if (parentView.getUser() == null) {
+ exportCard.setEnabled(false);
+ }
+ exportCard.addStyle(Styles.ACCENT);
+ exportCard.addStyle(Styles.BUTTON_OUTLINED);
+ exportCard.setWidth(200);
+ exportCard.setHeight(50);
+
+ FileChooserOverlay exportSelector = new FileChooserOverlay(App.window, exportCard);
+ exportSelector.setText("Export user card as.");
+ exportSelector.setFileExtensions(new String[]{"*.png"});
+ root.addElement(exportSelector);
+ exportSelector.onFileSelect(event -> {
+ File file = event.getDirectory();
+ try {
+ // User cannot be null as the button is disabled if it is.
+ ImageCardExporter.exportUser(parentView.getUser(), file);
+ } catch (IOException e) {
+ App.logger.error("Error importing character card: ", e);
+ Platform.runLater(() -> {
+ MessageOverlay errorOverlay = new MessageOverlay(0, 0, 500, 50, "Import Failed", "Could not import user card: " + e.getMessage());
+ errorOverlay.addStyle(Styles.DANGER);
+ errorOverlay.addStyle(Styles.BG_DEFAULT);
+ App.window.renderPopup(errorOverlay, 650, 870, 500, 50, false, null);
+ });
+ }
+ });
+
return root;
}
diff --git a/src/main/java/me/piitex/app/views/chats/ChatView.java b/src/main/java/me/piitex/app/views/chats/ChatView.java
index b2b64af9..68fe0e9d 100644
--- a/src/main/java/me/piitex/app/views/chats/ChatView.java
+++ b/src/main/java/me/piitex/app/views/chats/ChatView.java
@@ -199,7 +199,7 @@ public ChoiceBoxOverlay buildSelection() {
ChoiceBox choiceBox = selection.getChoiceBox();
choiceBox.getSelectionModel().clearSelection();
App.window.clearContainers();
- ChatView cachedView = character.getChatViewCachedNodes().get(chat);
+ ChatView cachedView = character.getChatViewCachedNodes().get(next);
if (next != null && cachedView != null) {
App.logger.info("Using cached selection view...");
App.window.addContainer(cachedView);
@@ -207,6 +207,7 @@ public ChoiceBoxOverlay buildSelection() {
App.window.clearContainers();
App.window.addContainer(new ChatView(character, next, true));
}
+ character.setLastChat(next);
});
return selection;
diff --git a/src/main/java/me/piitex/app/views/chats/components/ChatBoxCard.java b/src/main/java/me/piitex/app/views/chats/components/ChatBoxCard.java
index 212dbbbb..3d7b4a3c 100644
--- a/src/main/java/me/piitex/app/views/chats/components/ChatBoxCard.java
+++ b/src/main/java/me/piitex/app/views/chats/components/ChatBoxCard.java
@@ -45,6 +45,7 @@ public void buildCard() {
displayName = character.getUser().getDisplayName();
} else {
iconPath = (character != null && character.getIconPath() != null && !character.getIconPath().isEmpty() ? character.getIconPath() : new File(App.getAppDirectory(), "icons/character.png").getAbsolutePath());
+ assert character != null; // Character cannot be null at this stage. If the character is null, the error will be caught and thrown before this call.
displayName = character.getDisplayName();
}
diff --git a/src/main/java/me/piitex/app/views/settings/SettingsView.java b/src/main/java/me/piitex/app/views/settings/SettingsView.java
index 1215f330..cebefb09 100644
--- a/src/main/java/me/piitex/app/views/settings/SettingsView.java
+++ b/src/main/java/me/piitex/app/views/settings/SettingsView.java
@@ -50,6 +50,7 @@ public void build() {
scrollContainer.setHorizontalScroll(false);
root.addElement(scrollContainer);
+ layout.addElement(buildWindowScaling());
layout.addElement(buildResolution());
layout.addElement(buildGlobalChatSize());
layout.addElement(buildChatSize());
@@ -59,6 +60,28 @@ public void build() {
layout.addElement(buildAstrixColor());
}
+ public TileContainer buildWindowScaling() {
+ TileContainer tileContainer = new TileContainer(0, 0, appSettings.getWidth() - 300, 120);
+ tileContainer.setMaxSize(appSettings.getWidth() - 300, 120);
+ tileContainer.addStyle(Styles.BORDER_DEFAULT);
+ tileContainer.addStyle(Styles.BG_DEFAULT);
+ tileContainer.addStyle(appSettings.getGlobalTextSize());
+
+ tileContainer.setTitle("Window Scaling");
+ tileContainer.setDescription("Enable/Disable window scaling. Can cause text to become blurry or stretched.");
+
+ ToggleSwitchOverlay toggleSwitchOverlay = new ToggleSwitchOverlay(appSettings.isWindowScaling());
+ toggleSwitchOverlay.onToggle(event -> {
+ App.window.clear();
+ App.window.close(false);
+ appSettings.setWindowScaling(event.getNewValue());
+ App.getInstance().initialization(App.window.getStage());
+ });
+ tileContainer.setAction(toggleSwitchOverlay);
+
+ return tileContainer;
+ }
+
public TileContainer buildResolution() {
TileContainer tileContainer = new TileContainer(0, 0, appSettings.getWidth() - 300, 120);
tileContainer.setMaxSize(appSettings.getWidth() - 300, 120);
@@ -144,6 +167,11 @@ public TileContainer buildChatSize() {
item = Styles.TEXT;
}
appSettings.setChatTextSize(item);
+
+ App.getInstance().getCharacters().values().forEach(character -> character.getChatViewCachedNodes().clear());
+ App.window.clear();
+ App.window.close(false);
+ App.getInstance().initialization(App.window.getStage());
});
tileContainer.setAction(selection);
@@ -195,11 +223,10 @@ public TileContainer buildGlobalChatSize() {
appSettings.setGlobalTextSize(item);
// Refresh view to reflect changes.
- container.getElements().clear();
- build();
- Pane pane = (Pane) container.getNode();
- pane.getChildren().clear();
- pane.getChildren().addAll(container.build());
+ App.getInstance().getCharacters().values().forEach(character -> character.getChatViewCachedNodes().clear());
+ App.window.clear();
+ App.window.close(false);
+ App.getInstance().initialization(App.window.getStage());
});
diff --git a/src/main/java/me/piitex/app/views/users/UserEditView.java b/src/main/java/me/piitex/app/views/users/UserEditView.java
index 93692352..62e805e1 100644
--- a/src/main/java/me/piitex/app/views/users/UserEditView.java
+++ b/src/main/java/me/piitex/app/views/users/UserEditView.java
@@ -18,7 +18,6 @@
import me.piitex.engine.overlays.ButtonBuilder;
import me.piitex.engine.overlays.ButtonOverlay;
import me.piitex.engine.overlays.MessageOverlay;
-import me.piitex.os.configurations.InfoFile;
import java.io.File;
import java.io.IOException;
@@ -38,6 +37,7 @@ public class UserEditView extends EmptyContainer {
private TabsContainer tabsContainer;
private UserTab userTab;
+ private UserLoreBookTab userLoreBookTab;
private static final AppSettings appSettings = App.getInstance().getAppSettings();
@@ -59,6 +59,8 @@ public UserEditView(User user) {
}
public void init() {
+ addStyle(Styles.BG_INSET);
+
HorizontalLayout root = new HorizontalLayout(getWidth(), getHeight());
root.setMaxSize(root.getWidth(), root.getHeight());
addElement(root);
@@ -76,7 +78,7 @@ public void init() {
userTab = new UserTab("User", this);
tabsContainer.addTab(userTab);
- UserLoreBookTab userLoreBookTab = new UserLoreBookTab("Lorebook", this);
+ userLoreBookTab = new UserLoreBookTab("Lorebook", this);
tabsContainer.addTab(userLoreBookTab);
}
@@ -171,6 +173,14 @@ public boolean validate() {
}
}
+ public UserTab getUserTab() {
+ return userTab;
+ }
+
+ public UserLoreBookTab getUserLoreBookTab() {
+ return userLoreBookTab;
+ }
+
public User getUser() {
return user;
}
diff --git a/src/main/java/me/piitex/app/views/users/UsersView.java b/src/main/java/me/piitex/app/views/users/UsersView.java
index c9e79dae..afdd568d 100644
--- a/src/main/java/me/piitex/app/views/users/UsersView.java
+++ b/src/main/java/me/piitex/app/views/users/UsersView.java
@@ -2,7 +2,6 @@
import atlantafx.base.theme.Styles;
import javafx.application.Platform;
-import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
@@ -34,7 +33,7 @@ public class UsersView extends EmptyContainer {
public UsersView() {
super(appSettings.getWidth(), appSettings.getHeight());
-
+ addStyle(Styles.BG_INSET);
HorizontalLayout root = new HorizontalLayout(getWidth(), getHeight());
root.setMaxSize(root.getWidth(), root.getHeight());
addElement(root);
@@ -50,7 +49,8 @@ public UsersView() {
}
public void init() {
- VerticalLayout header = new VerticalLayout(appSettings.getWidth() - 200, 200);
+ VerticalLayout header = new VerticalLayout(appSettings.getWidth() - 200, 100);
+ header.addStyle(Styles.BG_DEFAULT);
header.setMaxSize(header.getWidth(), header.getHeight());
header.setAlignment(Pos.CENTER);
mainPage.addElement(header);
@@ -63,7 +63,6 @@ public void init() {
App.window.addContainer(new UserEditView());
});
header.addElement(newUser);
- header.addElement(new SeparatorOverlay(Orientation.HORIZONTAL));
buildFlowLayout();
}
@@ -75,6 +74,7 @@ public void buildFlowLayout() {
base.setMaxSize(base.getWidth(), base.getHeight());
FlowLayout flowLayout = new FlowLayout(appSettings.getWidth() - 200, 0);
+ flowLayout.setX(10);
flowLayout.setMaxSize(flowLayout.getWidth(), flowLayout.getHeight());
mainPage.addElement(flowLayout);
flowLayout.addStyle(Styles.BORDER_DEFAULT);
diff --git a/src/main/java/me/piitex/app/views/users/tabs/UserTab.java b/src/main/java/me/piitex/app/views/users/tabs/UserTab.java
index 94322570..1bb5838a 100644
--- a/src/main/java/me/piitex/app/views/users/tabs/UserTab.java
+++ b/src/main/java/me/piitex/app/views/users/tabs/UserTab.java
@@ -1,10 +1,13 @@
package me.piitex.app.views.users.tabs;
import atlantafx.base.theme.Styles;
+import com.drew.imaging.ImageProcessingException;
+import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.stage.FileChooser;
import me.piitex.app.App;
import me.piitex.app.configuration.AppSettings;
+import me.piitex.app.utils.UserCardImporter;
import me.piitex.app.views.users.UserEditView;
import me.piitex.engine.containers.CardContainer;
import me.piitex.engine.containers.ScrollContainer;
@@ -13,8 +16,10 @@
import me.piitex.engine.layouts.VerticalLayout;
import me.piitex.engine.loaders.ImageLoader;
import me.piitex.engine.overlays.*;
+import org.json.JSONObject;
import java.io.File;
+import java.io.IOException;
public class UserTab extends Tab {
private final UserEditView userEditView;
@@ -151,6 +156,41 @@ private VerticalLayout buildUserInput() {
});
root.addElement(userDisplayNameInput);
+ ButtonOverlay importCard = new ButtonBuilder("import").setText("Import Character Card").build();
+ if (userEditView.getUser() != null) {
+ importCard.setEnabled(false);
+ }
+ importCard.addStyle(Styles.ACCENT);
+ importCard.addStyle(Styles.BUTTON_OUTLINED);
+ importCard.setWidth(200);
+ importCard.setHeight(50);
+
+ FileChooserOverlay fileSelector = new FileChooserOverlay(App.window, importCard);
+ root.addElement(fileSelector);
+ fileSelector.onFileSelect(event -> {
+ File file = event.getDirectory();
+ try {
+ JSONObject metadata = UserCardImporter.getImageMetaData(file);
+ userDisplayNameInput.setCurrentText(UserCardImporter.getUserDisplay(metadata));
+ userDescription.setCurrentText(UserCardImporter.getUserPersona(metadata));
+
+ userEditView.getLoreBook().clear();
+ userEditView.getLoreBook().putAll(UserCardImporter.getLoreItems(metadata));
+ userEditView.getUserLoreBookTab().buildLorebookTabContent();
+
+ userEditView.setUserIconPath(file);
+ image.setImage(new ImageLoader(file));
+ } catch (ImageProcessingException | IOException e) {
+ App.logger.error("Error importing character card: ", e);
+ Platform.runLater(() -> {
+ MessageOverlay errorOverlay = new MessageOverlay(0, 0, 500, 50, "Import Failed", "Could not import character card: " + e.getMessage());
+ errorOverlay.addStyle(Styles.DANGER);
+ errorOverlay.addStyle(Styles.BG_DEFAULT);
+ App.window.renderPopup(errorOverlay, 650, 870, 500, 50, false, null);
+ });
+ }
+ });
+
return root;
}