diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 78f34d3a..00000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,39 +0,0 @@ -#-------------------------------------------------------------------------------# -# Discover all capabilities of Qodana in our documentation # -# https://www.jetbrains.com/help/qodana/about-qodana.html # -#-------------------------------------------------------------------------------# - -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - master - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.2 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - with: - # When pr-mode is set to true, Qodana analyzes only the files that have been changed - pr-mode: false - use-caches: true - post-pr-comment: true - use-annotations: true - # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job - upload-result: false - # quick-fixes available in Ultimate and Ultimate Plus plans - push-fixes: 'none' \ No newline at end of file diff --git a/pom.xml b/pom.xml index b13cc9aa..eeaa66b5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.piitex.app character-chat-app - 1.1.3.1105 + 1.1.4 character-chat-app @@ -25,7 +25,7 @@ org.apache.logging.log4j log4j-api - 2.25.2 + 2.25.4 org.apache.logging.log4j @@ -42,7 +42,7 @@ me.piitex.engine ren-engine - 1.0.7-SNAPSHOT + 1.0.8-SNAPSHOT org.junit.jupiter diff --git a/qodana.yaml b/qodana.yaml deleted file mode 100644 index 6d050d82..00000000 --- a/qodana.yaml +++ /dev/null @@ -1,46 +0,0 @@ -#-------------------------------------------------------------------------------# -# Qodana analysis is configured by qodana.yaml file # -# https://www.jetbrains.com/help/qodana/qodana-yaml.html # -#-------------------------------------------------------------------------------# -################################################################################# -# WARNING: Do not store sensitive information in this file, # -# as its contents will be included in the Qodana report. # -################################################################################# -version: '1.0' -#Specify inspection profile for code analysis -profile: - name: qodana.starter -#Enable inspections -#include: -# - name: -#Disable inspections -#exclude: -# - name: -# paths: -# - -projectJDK: '25' #(Applied in CI/CD pipeline) -#Execute shell command before Qodana execution (Applied in CI/CD pipeline) -#bootstrap: sh ./prepare-qodana.sh -#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) -#plugins: -# - id: #(plugin id can be found at https://plugins.jetbrains.com) -# Quality gate. Will fail the CI/CD pipeline if any condition is not met -# severityThresholds - configures maximum thresholds for different problem severities -# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code -# Code Coverage is available in Ultimate and Ultimate Plus plans -#failureConditions: -# severityThresholds: -# any: 15 -# critical: 5 -# testCoverageThresholds: -# fresh: 70 -# total: 50 -#Qodana supports other languages, for example, Python, JavaScript, TypeScript, Go, C#, PHP -#For all supported languages see https://www.jetbrains.com/help/qodana/linters.html -linter: 'jetbrains/qodana-jvm-community:2025.2' -include: - - name: UNUSED_IMPORT - - name: ThrowablePrintStackTrace - - name: GroovyUnnecessaryReturn - - name: FieldMayBeFinal - - name: UnnecessaryLocalVariable diff --git a/src/main/java/me/piitex/app/App.java b/src/main/java/me/piitex/app/App.java index a63cd397..46ce51ba 100644 --- a/src/main/java/me/piitex/app/App.java +++ b/src/main/java/me/piitex/app/App.java @@ -13,20 +13,19 @@ import me.piitex.app.backend.User; import me.piitex.app.backend.server.DeviceProcess; import me.piitex.app.backend.server.ServerProcess; -import me.piitex.app.backend.server.ServerSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.configuration.AppSettings; -import me.piitex.app.updater.ApplicationUpdater; import me.piitex.app.updater.LLamaBackendUpdater; import me.piitex.app.views.HomeView; import me.piitex.app.views.Positions; import me.piitex.engine.WindowBuilder; +import me.piitex.engine.loaders.image.BaseImageLoader; import me.piitex.os.OSPathing; import me.piitex.os.OSUtil; import me.piitex.os.configurations.InfoFile; import me.piitex.engine.Window; import me.piitex.engine.containers.EmptyContainer; import me.piitex.engine.fxloader.FXLoad; -import me.piitex.engine.loaders.ImageLoader; import me.piitex.engine.overlays.AlertOverlay; import me.piitex.engine.overlays.ButtonBuilder; import me.piitex.engine.overlays.ButtonOverlay; @@ -41,8 +40,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.util.*; import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Future; public class App extends FXLoad { private ServerSettings settings; @@ -70,6 +72,7 @@ public class App extends FXLoad { public static final Logger logger = LogManager.getLogger(App.class); + private final ConcurrentLinkedQueue characterLoadQueue = new ConcurrentLinkedQueue<>(); private volatile boolean loading = true; private volatile boolean error = false; @@ -122,13 +125,12 @@ public void preInitialization() { loadUserTemplates(); loadCharacters(); App.logger.info("Finished pre-initialization."); - loading = false; }); threadPoolManager.submitTask(this::loadBackendServer); } @Override - public void initialization(Stage initialStage) { + public void initialization() { // Error will pass if another instance is running, if (error) return; App.logger.info("Loading app from '{}'", getAppDirectory().getAbsolutePath()); @@ -207,7 +209,7 @@ public void initialization(Stage initialStage) { logger.info("Screen Size ({},{})", dimension.width, dimension.height); File logo = new File(getExecutedDirectory(), "logo.png"); - window = new WindowBuilder("Chat App").setIcon(new ImageLoader(logo)).setScale((appSettings.isWindowScaling()) && !mobile).setAntiAliasing(false).setDimensions(setWidth, setHeight).build(); + window = new WindowBuilder("Chat App").setIcon(new BaseImageLoader(logo)).setScale((appSettings.isWindowScaling()) && !mobile).setAntiAliasing(false).setDimensions(setWidth, setHeight).build(); // Initialize global positions. Needed for the rendering process. Positions.initialize(); @@ -342,20 +344,44 @@ public void loadCharacters() { logger.error("Could not initialize characters directory. Program may lack permission to access file system."); return; } + + Collection> tasks = new HashSet<>(); + for (File file : files) { - logger.info("Loading character '{}'...", file.getName()); if (file.isDirectory()) { - String id = file.getName(); - // Check if info file exists - File info = new File(file, "character.info"); - if (info.exists()) { - InfoFile infoFile = new InfoFile(info, true); - characters.put(id, new Character(id, infoFile)); - } else { - logger.error("Character file does not exist for '{}'", file.getName()); - } + tasks.add(App.getThreadPoolManager().submitTask(() -> { + characterLoadQueue.add(file.getName()); + logger.info("Loading character '{}'...", file.getName()); + String id = file.getName(); + // Check if info file exists + File info = new File(file, "character.info"); + if (info.exists()) { + InfoFile infoFile = new InfoFile(info, true); + characters.put(id, new Character(id, infoFile)); + } else { + logger.error("Character file does not exist for '{}'", file.getName()); + } + + characterLoadQueue.remove(file.getName()); + })); } } + + int total = tasks.size(); + Collection> completed; + do { + completed = new HashSet<>(); + for (Future task : tasks) { + if (task.isDone()) { + completed.add(task); + } + } + + if (completed.size() == total) { + loading = false; + } + } while (loading); + } public void loadUserTemplates() { @@ -399,26 +425,21 @@ public void performUpdates() { logger.info("Model list updated."); downloader.shutdown(); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { App.logger.error("Failed to fetch download size.", e); } - // Microslop, the multi trillion dollar company that can't handle more than 50 API requests. - App.logger.info("Checking for application updates..."); - ApplicationUpdater applicationUpdater = new ApplicationUpdater(getVersion()); - //applicationUpdater.checkForUpdates(); - App.logger.info("Checking for backend version..."); - if (settings.getDevice().equals("error")) { App.logger.info("Could not load backend devices. Force checking updates..."); settings.setDevice("Auto"); lLamaBackendUpdater = new LLamaBackendUpdater("0"); } else { - File backendVersionFile = Arrays.stream(getBackendDirectory().listFiles()).filter(file -> file.getName().endsWith(".txt")).findAny().orElse(null); + File backendVersionFile = Arrays.stream(Objects.requireNonNull(getBackendDirectory().listFiles())).filter(file -> file.getName().endsWith(".txt")).findAny().orElse(null); if (backendVersionFile != null) { lLamaBackendUpdater = new LLamaBackendUpdater(backendVersionFile.getName().split(".txt")[0]); } else { + App.logger.info("No update file found."); lLamaBackendUpdater = new LLamaBackendUpdater("0"); } } @@ -469,7 +490,7 @@ public Window buildErrorWindow(String message) { Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); File logo = new File(getExecutedDirectory(), "logo.png"); - window = new WindowBuilder("Error").setDimensions(400, 150).setIcon(new ImageLoader(logo)).build(); + window = new WindowBuilder("Error").setDimensions(400, 150).setIcon(new BaseImageLoader(logo)).build(); EmptyContainer emptyContainer = new EmptyContainer(window.getWidth(), window.getHeight()); window.addContainer(emptyContainer); @@ -484,15 +505,19 @@ public Window buildErrorWindow(String message) { emptyContainer.addElement(kill); kill.onClick(event -> { App.logger.info("Killing old process."); - if (ProcessUtil.killProcess(Long.parseLong(settings.getInfoFile().get("main-pid")))) { - App.logger.info("Old process was destroyed gracefully."); - Platform.exit(); - System.exit(0); - } else { - App.logger.info("Forcefully killing old process."); - ProcessUtil.terminateProcess(Long.parseLong(settings.getInfoFile().get("main-pid"))); - } - + Optional handle = ProcessUtil.getRunningProcess(Long.parseLong(settings.getInfoFile().get("main-pid"))); + handle.ifPresent(processHandle -> { + if (processHandle.info().toString().contains("java.exe") || processHandle.info().toString().contains("javaw.exe") || processHandle.info().toString().contains("CCA.exe")) { + if (ProcessUtil.killProcess(Long.parseLong(settings.getInfoFile().get("main-pid")))) { + App.logger.info("Old process was destroyed gracefully."); + Platform.exit(); + System.exit(0); + } else { + App.logger.info("Forcefully killing old process."); + ProcessUtil.terminateProcess(Long.parseLong(settings.getInfoFile().get("main-pid"))); + } + } + }); appSettings.getInfoFile().set("main-pid", ""); }); diff --git a/src/main/java/me/piitex/app/Main.java b/src/main/java/me/piitex/app/Main.java index 6b97cfda..7c77bf29 100644 --- a/src/main/java/me/piitex/app/Main.java +++ b/src/main/java/me/piitex/app/Main.java @@ -21,7 +21,6 @@ public static void main(String[] args) { if (Arrays.asList(args).contains("--force-updates")) { forceUpdate = true; } - new App(); Application.launch(App.class); } } diff --git a/src/main/java/me/piitex/app/backend/Character.java b/src/main/java/me/piitex/app/backend/Character.java index ae92abba..8b22d4fc 100644 --- a/src/main/java/me/piitex/app/backend/Character.java +++ b/src/main/java/me/piitex/app/backend/Character.java @@ -118,10 +118,8 @@ private void loadChats() { if (getChatDirectory() == null || !getChatDirectory().exists()) return; for (File file : getChatDirectory().listFiles()) { if (file.isDirectory()) continue; - App.getThreadPoolManager().submitTask(() -> { - Chat chat = new Chat(file); - chats.add(chat); - }); + Chat chat = new Chat(file); + chats.add(chat); } } diff --git a/src/main/java/me/piitex/app/backend/User.java b/src/main/java/me/piitex/app/backend/User.java index ef9a9153..28cd0bfa 100644 --- a/src/main/java/me/piitex/app/backend/User.java +++ b/src/main/java/me/piitex/app/backend/User.java @@ -1,9 +1,10 @@ package me.piitex.app.backend; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import org.jetbrains.annotations.Nullable; import me.piitex.app.App; import me.piitex.os.configurations.InfoFile; -import me.piitex.engine.loaders.ImageLoader; import me.piitex.engine.overlays.ImageOverlay; import java.io.File; @@ -141,7 +142,7 @@ public static ImageOverlay getUserAvatar(String iconPath, double width, double h } - ImageLoader loader = new ImageLoader(file); + ImageLoader loader = new BaseImageLoader(file); loader.setWidth(width); loader.setHeight(height); diff --git a/src/main/java/me/piitex/app/backend/server/ModelTestProcess.java b/src/main/java/me/piitex/app/backend/server/ModelTestProcess.java index 3e89a2ee..9a6b273e 100644 --- a/src/main/java/me/piitex/app/backend/server/ModelTestProcess.java +++ b/src/main/java/me/piitex/app/backend/server/ModelTestProcess.java @@ -2,6 +2,7 @@ import me.piitex.app.App; import me.piitex.app.backend.Model; +import me.piitex.app.configuration.ServerSettings; import me.piitex.os.OSUtil; import java.io.File; @@ -136,6 +137,8 @@ private LinkedList getParameters(File server, ServerSettings settings) { parameters.add("-no-cnv"); parameters.add("--no-warmup"); + parameters.add("-lv"); + parameters.add("4"); return parameters; } @@ -188,15 +191,19 @@ protected void processOutput() { try (Scanner scanner = new Scanner(new FileInputStream(output))) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); + if (line.contains(" ")) { + line = line.split(" ", 2)[1]; + } + line = line.replace("I ", "").trim(); if (line.contains("cleaning up before exit...") || line.contains("failed to load model") || line.contains("error while handling") || line.startsWith("error:") || line.startsWith("ROCm error:")) { App.logger.error("ERROR: Could not start backend server."); error = true; break; } if (line.startsWith("print_info: n_layer") && model.getSettings().getTotalLayers() == 0) { - line = line.split("=")[1].trim(); - App.logger.info("Total Model Layers: {}", line); - model.getSettings().setTotalLayers(Integer.parseInt(line)); + String layers = line.substring(15).split("=")[1].trim(); + App.logger.info("Total Model Layers: {}", layers); + model.getSettings().setTotalLayers(Integer.parseInt(layers)); } if (line.contains("common_memory_breakdown_print:") && line.contains("|") && line.contains("=")) { String[] parts = line.split("\\|"); @@ -264,10 +271,12 @@ protected void processOutput() { if (computeBufferMiB > 0.0) { model.getSettings().setComputeBufferSize(computeBufferMiB); } - } catch (FileNotFoundException e) { + } catch (Exception e) { error = true; - stop(); + App.logger.error("Err while testing model.", e); } + + App.logger.info("Model data processed!"); stop(); } diff --git a/src/main/java/me/piitex/app/backend/server/Server.java b/src/main/java/me/piitex/app/backend/server/Server.java index bfdd58cd..ab4124af 100644 --- a/src/main/java/me/piitex/app/backend/server/Server.java +++ b/src/main/java/me/piitex/app/backend/server/Server.java @@ -6,6 +6,7 @@ import me.piitex.app.backend.ChatMessage; import me.piitex.app.backend.Response; import me.piitex.app.configuration.ModelSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.utils.Placeholder; import me.piitex.engine.Element; import me.piitex.engine.containers.CardContainer; @@ -106,6 +107,14 @@ private static HttpPost prepareOAIStreamRequest(Response response) throws JSONEx */ private static void addModelSettingsToJSON(JSONObject toPost, ModelSettings settings) throws JSONException { toPost.put("stream", true); + + if (settings.isReasoning()) { + JSONObject chatTemplateKwargs = new JSONObject(); + chatTemplateKwargs.put("enable_thinking", true); + chatTemplateKwargs.put("thinking", true); + toPost.put("chat_template_kwargs", chatTemplateKwargs); + } + toPost.put("temperature", settings.getTemperature()); toPost.put("dynatemp_range", settings.getDynamicTempRage()); toPost.put("dynatemp_exponent", settings.getDynamicExponent()); @@ -215,8 +224,9 @@ private static boolean processStreamChunk(String content, StringBuilder appender } JSONObject delta = arrayObject.getJSONObject("delta"); + String line = delta.optString("content", ""); - if (line.equalsIgnoreCase("null")) continue; + if (line.isEmpty() || line.equalsIgnoreCase("null")) continue; appender.append(line); diff --git a/src/main/java/me/piitex/app/backend/server/ServerProcess.java b/src/main/java/me/piitex/app/backend/server/ServerProcess.java index 8b14639e..4298c7bb 100644 --- a/src/main/java/me/piitex/app/backend/server/ServerProcess.java +++ b/src/main/java/me/piitex/app/backend/server/ServerProcess.java @@ -5,9 +5,13 @@ import javafx.application.Platform; import me.piitex.app.App; import me.piitex.app.backend.Model; +import me.piitex.app.configuration.ServerSettings; import me.piitex.engine.PopupPosition; import me.piitex.engine.overlays.MessageOverlay; import me.piitex.os.OSUtil; +import oshi.SystemInfo; +import oshi.hardware.GraphicsCard; +import oshi.hardware.HardwareAbstractionLayer; import java.io.File; import java.io.FileInputStream; @@ -176,6 +180,24 @@ private LinkedList getParameters(File server, ServerSettings settings) { // The usage is a percentage of the total layers (model.getGpuLayers()); double TOTAL_AVAILABLE_VRAM_MIB = App.getInstance().getAppSettings().getTotalGpuVram(); + if (TOTAL_AVAILABLE_VRAM_MIB <= 0) { + App.logger.warn("VRAM is set to 0. Attempting to fetch VRAM..."); + // Fallback to Oshi + SystemInfo systemInfo = new SystemInfo(); + HardwareAbstractionLayer hardwareAbstractionLayer = systemInfo.getHardware(); + GraphicsCard graphicsCard = hardwareAbstractionLayer.getGraphicsCards().getFirst(); + if (graphicsCard != null && graphicsCard.getVRam() > 0) { + App.logger.info("Using GPU memory..."); + TOTAL_AVAILABLE_VRAM_MIB = graphicsCard.getVRam() / (1024.0 * 1024.0); + } else { + App.logger.error("Could not find dedicated GPU. Using global memory pool..."); + TOTAL_AVAILABLE_VRAM_MIB = systemInfo.getHardware().getMemory().getTotal() / (1024.0 * 1024.0); + App.logger.info("VRAM: {} MiB", TOTAL_AVAILABLE_VRAM_MIB); + } + + App.getInstance().getAppSettings().setTotalGpuVram(TOTAL_AVAILABLE_VRAM_MIB); + } + double KV_CACHE = model.getSettings().getKvCacheSize(); double COMPUTED_BUFFER_SIZE = model.getSettings().getComputeBufferSize(); double FIXED_OVERHEAD_MIB = KV_CACHE + COMPUTED_BUFFER_SIZE; @@ -207,8 +229,8 @@ private LinkedList getParameters(File server, ServerSettings settings) { if (VRAM_PER_LAYER_MIB > 0.0) { layers = (int) Math.floor(LAYER_ALLOC_BUDGET / VRAM_PER_LAYER_MIB); } else { - App.logger.warn("VRAM per layer (dataPerLayer) is 0.0, defaulting layers to 0."); - layers = 0; + App.logger.warn("VRAM per layer (dataPerLayer) is 0.0, defaulting layers to {}.", model.getSettings().getTotalLayers()); + layers = model.getSettings().getTotalLayers(); } // Cap the offloaded layers @@ -242,6 +264,19 @@ private LinkedList getParameters(File server, ServerSettings settings) { parameters.add(model.getSettings().getReasoningTemplate()); } + if (model.getSettings().isForceDisableReasoning()) { + App.logger.info("Force disable reasoning...."); + parameters.add("--no-prefill-assistant"); + parameters.add("-rea"); + parameters.add("off"); + } else { + if (model.getSettings().isReasoning()) { + App.logger.info("Enabling thinking mode...."); + parameters.add("-rea"); + parameters.add("on"); + } + } + if (!model.getSettings().getChatTemplate().equalsIgnoreCase("default")) { App.logger.debug("Setting chat template..."); parameters.add("--chat-template"); @@ -265,6 +300,8 @@ private LinkedList getParameters(File server, ServerSettings settings) { parameters.add("--port"); parameters.add("8187"); parameters.add("--no-webui"); + parameters.add("-lv"); + parameters.add("4"); if (settings.isHost()) { App.logger.info("Server is listening on 0.0.0.0"); @@ -291,7 +328,7 @@ protected void waitForServer() { process.destroy(); break; } - if (line.contains("starting the main loop")) { + if (line.contains("starting the main loop") || line.contains("model loaded") || line.contains("server is listening on")) { App.logger.info("Backend server stated!"); started = true; break; diff --git a/src/main/java/me/piitex/app/configuration/ModelSettings.java b/src/main/java/me/piitex/app/configuration/ModelSettings.java index 29ae48aa..cc4ef16e 100644 --- a/src/main/java/me/piitex/app/configuration/ModelSettings.java +++ b/src/main/java/me/piitex/app/configuration/ModelSettings.java @@ -26,9 +26,12 @@ public class ModelSettings { private int dryPenaltyTokens = -1; private String mmProj = "None / Disabled"; private String chatTemplate = "default"; - private String reasoningTemplate = "disabled"; + private String reasoningTemplate = "auto"; private boolean useDefault; private boolean jinja = false; + private boolean reasoning = false; + private int reasoningBudget = -1; + private boolean forceDisableReasoning = false; private int totalLayers = 0; private double dataPerLayer; private double kvCacheSize; @@ -184,6 +187,15 @@ public ModelSettings(InfoFile infoFile) { if (infoFile.hasKey("change")) { this.change = infoFile.getBoolean("change"); } + if (infoFile.hasKey("force-disable-reason")) { + this.forceDisableReasoning = infoFile.getBoolean("force-disable-reason"); + } + if (infoFile.hasKey("reasoning")) { + this.reasoning = infoFile.getBoolean("reasoning"); + } + if (infoFile.hasKey("reasoning-budget")) { + this.reasoningBudget = infoFile.getInteger("reasoning-budget"); + } } public String getModelInstructions() { @@ -474,6 +486,33 @@ public void setChange(boolean change) { infoFile.set("change", change); } + public int getReasoningBudget() { + return reasoningBudget; + } + + public boolean isReasoning() { + return reasoning; + } + + public void setReasoning(boolean reasoning) { + this.reasoning = reasoning; + infoFile.set("reasoning", reasoning); + } + + public void setReasoningBudget(int reasoningBudget) { + this.reasoningBudget = reasoningBudget; + infoFile.set("reasoning-budget", reasoningBudget); + } + + public boolean isForceDisableReasoning() { + return forceDisableReasoning; + } + + public void setForceDisableReasoning(boolean forceDisableReasoning) { + this.forceDisableReasoning = forceDisableReasoning; + infoFile.set("force-disable-reason", forceDisableReasoning); + } + @Nullable public InfoFile getInfoFile() { return infoFile; diff --git a/src/main/java/me/piitex/app/backend/server/ServerSettings.java b/src/main/java/me/piitex/app/configuration/ServerSettings.java similarity index 99% rename from src/main/java/me/piitex/app/backend/server/ServerSettings.java rename to src/main/java/me/piitex/app/configuration/ServerSettings.java index db26c3a6..92485ffd 100644 --- a/src/main/java/me/piitex/app/backend/server/ServerSettings.java +++ b/src/main/java/me/piitex/app/configuration/ServerSettings.java @@ -1,4 +1,4 @@ -package me.piitex.app.backend.server; +package me.piitex.app.configuration; import me.piitex.app.App; import me.piitex.app.backend.Model; diff --git a/src/main/java/me/piitex/app/updater/ApplicationUpdater.java b/src/main/java/me/piitex/app/updater/ApplicationUpdater.java deleted file mode 100644 index b99c4a3a..00000000 --- a/src/main/java/me/piitex/app/updater/ApplicationUpdater.java +++ /dev/null @@ -1,141 +0,0 @@ -package me.piitex.app.updater; - -import javafx.application.Platform; -import me.piitex.app.App; -import me.piitex.app.backend.server.ServerProcess; -import me.piitex.engine.Window; -import me.piitex.engine.WindowBuilder; -import me.piitex.engine.containers.Container; -import me.piitex.engine.containers.EmptyContainer; -import me.piitex.engine.loaders.ImageLoader; -import me.piitex.engine.overlays.ButtonBuilder; -import me.piitex.engine.overlays.ButtonOverlay; -import me.piitex.engine.overlays.ProgressBarOverlay; -import me.piitex.engine.overlays.TextOverlay; -import me.piitex.os.*; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; - -public class ApplicationUpdater { - private String currentVersion; - private Window window; - private Container container; - - public ApplicationUpdater(String currentVersion) { - this.currentVersion = currentVersion; - } - - public void checkForUpdates() { - App.logger.info("Checking version: {}", currentVersion); - GitHubUtil gitHubUtil = new GitHubUtil("https://api.github.com/repos/HackusatePvP/character-chat-app/"); - try { - String latestVersionString = gitHubUtil.getLatestReleaseJson().getString("tag_name"); - Version latestVersion = VersionUtil.parseVersion(latestVersionString); - App.logger.info("Latest tag is {}", latestVersionString); - - if (!currentVersion.startsWith("v")) { - currentVersion = "v" + currentVersion; - } - Version version = VersionUtil.parseVersion(currentVersion); - - if (version.compareTo(latestVersion) < 0) { - App.logger.info("New application version available."); - Platform.runLater(() -> { - buildAndDisplayUpdateWindow(gitHubUtil); - }); - } else { - App.logger.info("No update available."); - } - - } catch (IOException | URISyntaxException e) { - App.logger.error("Could not fetch latest app release!", e); - } - } - - public void buildAndDisplayUpdateWindow(GitHubUtil gitHubUtil) { - window = new WindowBuilder("Update").setDimensions(400, 150).setIcon(new ImageLoader(new File(App.getAppDirectory(), "logo.png"))).build(); - container = new EmptyContainer(400, 150); - window.addContainer(container); - - TextOverlay textOverlay = new TextOverlay("App updates available. Click 'Update' to start."); - textOverlay.setY(20); - textOverlay.setX(10); - container.addElement(textOverlay); - - ButtonOverlay buttonOverlay = new ButtonBuilder("update").setText("Update").build(); - container.addElement(buttonOverlay); - buttonOverlay.setX(150); - buttonOverlay.setY(70); - - buttonOverlay.onClick(_ -> { - App.window.close(false); - App.getInstance().getCharacters().clear(); - App.getInstance().getUserTemplates().clear(); - App.reloadModelList(); - if (ServerProcess.getCurrentServer() != null) { - ServerProcess serverProcess = ServerProcess.getCurrentServer(); - serverProcess.stop(); - } - - container.removeAllElements(); - TextOverlay updateInfo = new TextOverlay("Preparing for installation..."); - updateInfo.setY(20); - updateInfo.setX(10); - container.addElement(updateInfo); - - ProgressBarOverlay progressBarOverlay = new ProgressBarOverlay(); - progressBarOverlay.setX(150); - progressBarOverlay.setY(70); - container.addElement(progressBarOverlay); - }); - App.window.getStage().getScene().getRoot().setDisable(true); - window.getStage().setAlwaysOnTop(true); - window.render(); - } - - private void downloadUpdate(GitHubUtil gitHubUtil, TextOverlay textOverlay, ProgressBarOverlay progressBarOverlay) { - textOverlay.setText("Download updates..."); - progressBarOverlay.getProgressBar().setProgress(0); - - App.getThreadPoolManager().submitTask(() -> { - try { - gitHubUtil.downloadAsset(gitHubUtil.getLatestReleaseID(), new File("download.jar"), new DownloadListener() { - @Override - public void onDownloadStart(DownloadInfo info) { - App.logger.info("Starting download..."); - } - - @Override - public void onDownloadProgress(DownloadInfo info) { - progressBarOverlay.getProgressBar().progressProperty().set(info.getDownloadProgress()); - } - - @Override - public void onDownloadComplete(DownloadInfo info, File outputFile) { - App.logger.info("Update completed! Shutting down..."); - - // Call the file to be moved after the runtime shuts down. - - Platform.exit(); - System.exit(1); - } - - @Override - public void onDownloadError(DownloadInfo info, Exception e) { - - } - - @Override - public void onDownloadCancel(DownloadInfo info) { - - } - - }); - } catch (IOException | URISyntaxException e) { - // TOOD: Display error window - } - }); - } -} diff --git a/src/main/java/me/piitex/app/updater/LLamaBackendUpdater.java b/src/main/java/me/piitex/app/updater/LLamaBackendUpdater.java index 74788457..b4d5b51a 100644 --- a/src/main/java/me/piitex/app/updater/LLamaBackendUpdater.java +++ b/src/main/java/me/piitex/app/updater/LLamaBackendUpdater.java @@ -4,6 +4,8 @@ import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.control.ProgressIndicator; +import javafx.scene.paint.Color; +import javafx.scene.text.TextAlignment; import me.piitex.app.App; import me.piitex.app.backend.Model; import me.piitex.app.backend.server.ServerProcess; @@ -13,11 +15,8 @@ import me.piitex.engine.containers.DownloadContainer; import me.piitex.engine.containers.EmptyContainer; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; -import me.piitex.engine.overlays.ButtonBuilder; -import me.piitex.engine.overlays.ButtonOverlay; -import me.piitex.engine.overlays.ProgressBarOverlay; -import me.piitex.engine.overlays.TextOverlay; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.overlays.*; import me.piitex.os.*; import org.apache.commons.io.FileUtils; import org.json.JSONObject; @@ -64,10 +63,8 @@ private void fetchLatestRelease() { public synchronized boolean isUpdateAvailable() { if (current != null && latest != null) { - System.out.println("Not null"); return current.compareTo(latest) < 0; } - System.out.println("False"); return false; } @@ -82,7 +79,7 @@ public boolean startUpdate() { } public void buildAndDisplayUpdateWindow(GitHubUtil gitHubUtil) { - window = new WindowBuilder("Update").setDimensions(450, 200).setIcon(new ImageLoader(new File(App.getAppDirectory(), "logo.png"))).build(); + window = new WindowBuilder("Update").setDimensions(450, 200).setIcon(new BaseImageLoader(new File(App.getExecutedDirectory(), "logo.png"))).build(); container = new EmptyContainer(window.getWidth(), window.getHeight()); window.addContainer(container); window.getStage().setOnHidden(windowEvent -> { @@ -115,9 +112,21 @@ public void buildAndDisplayUpdateWindow(GitHubUtil gitHubUtil) { container.removeAllElements(); if (OSUtil.getOS().contains("Windows")) { - downloadCudaBackendNew(gitHubUtil); + try { + downloadCudaBackendNew(gitHubUtil); + } catch (Exception ignored) { + VerticalLayout errorBox = new VerticalLayout(container.getWidth() - 20, container.getHeight() - 20); + errorBox.setMaxSize(errorBox.getWidth(), errorBox.getHeight()); + container.addElement(errorBox); + + TextFlowOverlay error = new TextFlowOverlay("Error occurred while fetching update. Please try updating at a later point.", errorBox.getWidth(), errorBox.getHeight()); + error.setTextAlignment(TextAlignment.CENTER); + error.setMaxSize(error.getWidth(), error.getHeight()); + error.setTextFillColor(Color.RED); + errorBox.addElement(error); + } } else { - + downloadVulkanBackendNew(gitHubUtil, null); } }); diff --git a/src/main/java/me/piitex/app/views/HomeView.java b/src/main/java/me/piitex/app/views/HomeView.java index 7b346fb5..39a7bbd2 100644 --- a/src/main/java/me/piitex/app/views/HomeView.java +++ b/src/main/java/me/piitex/app/views/HomeView.java @@ -23,7 +23,7 @@ public class HomeView extends EmptyContainer { private final SidebarView sidebarView; public HomeView() { - int height = App.getInstance().getAppSettings().getHeight() - 50; + double height = App.window.getDrawHeight(); super(600, height); this.sidebarView = new SidebarView(); if (App.mobile) { @@ -49,8 +49,8 @@ public void init() { boolean loading = App.getInstance().isLoading(); while (loading) { loading = App.getInstance().isLoading(); - if (!loading) break; } + Platform.runLater(() -> { root.removeElement(1); buildBody(); diff --git a/src/main/java/me/piitex/app/views/LoadingView.java b/src/main/java/me/piitex/app/views/LoadingView.java index cf5620d0..74b9c640 100644 --- a/src/main/java/me/piitex/app/views/LoadingView.java +++ b/src/main/java/me/piitex/app/views/LoadingView.java @@ -1,6 +1,5 @@ package me.piitex.app.views; -import atlantafx.base.theme.Styles; import javafx.geometry.Pos; import me.piitex.engine.layouts.VerticalLayout; diff --git a/src/main/java/me/piitex/app/views/Positions.java b/src/main/java/me/piitex/app/views/Positions.java index 5ef69d22..6b672c8d 100644 --- a/src/main/java/me/piitex/app/views/Positions.java +++ b/src/main/java/me/piitex/app/views/Positions.java @@ -14,7 +14,7 @@ public class Positions { */ public static int SIDEBAR_WIDTH; public static int SIDEBAR_WIDTH_COLLAPSE; - public static int SIDEBAR_HEIGHT; + public static double SIDEBAR_HEIGHT; /* ########################### @@ -80,7 +80,7 @@ private static void initializeDesktop() { SIDEBAR_WIDTH = 200; SIDEBAR_WIDTH_COLLAPSE = 50; - SIDEBAR_HEIGHT = (int) window.getHeight(); + SIDEBAR_HEIGHT = window.getDrawHeight(); MODEL_CONFIGURATION_SCROLL_HEIGHT = window.getHeight() - 100; MODEL_CONFIGURATION_LAYOUT_WIDTH = window.getWidth() - SIDEBAR_WIDTH - 25; // -25 to account for spacing diff --git a/src/main/java/me/piitex/app/views/SidebarView.java b/src/main/java/me/piitex/app/views/SidebarView.java index b9826e8e..94971f67 100644 --- a/src/main/java/me/piitex/app/views/SidebarView.java +++ b/src/main/java/me/piitex/app/views/SidebarView.java @@ -194,29 +194,4 @@ private VerticalLayout buildVersionLayout() { return layout; } -} - -// ButtonOverlay users = new ButtonBuilder("users").setText("User Templates").setIcon(new FontIcon(Material2MZ.MEMORY)).build(); -// users.addStyle(appSettings.getGlobalTextSize()); -// users.setWidth(rootWidth); -// users.setAlignment(Pos.BASELINE_LEFT); -// addElement(users); -// users.onClick(event -> { -// MessageOverlay warning = new MessageOverlay("Development", "User templates are still in development."); -// warning.addStyle(Styles.WARNING); -// App.window.renderPopup(warning, PopupPosition.BOTTOM_CENTER, 400, 100, true); -// -// App.window.clearContainers(); -// App.window.addContainer(new UsersView()); -// }); -// -// ButtonOverlay characters = new ButtonBuilder("characters").setText("New Character").setIcon(new FontIcon(Material2MZ.PERSON)).build(); -// characters.addStyle(appSettings.getGlobalTextSize()); -// characters.setWidth(rootWidth); -// addElement(characters); -// characters.setAlignment(Pos.BASELINE_LEFT); -// characters.onClick(event -> { -// App.window.clearContainers(); -// App.window.addContainer(new CharacterEditView(null).getRoot()); -// }); - +} \ No newline at end of file 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 8fc95c5f..7b7e2677 100644 --- a/src/main/java/me/piitex/app/views/characters/CharactersView.java +++ b/src/main/java/me/piitex/app/views/characters/CharactersView.java @@ -23,7 +23,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.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import org.apache.commons.io.FileUtils; import org.kordamp.ikonli.material2.Material2AL; @@ -269,7 +269,7 @@ private void deleteCharacter(FlowLayout base, CardContainer card, Character char App.getThreadPoolManager().submitSchedule(() -> { try { App.logger.info("Removing image from cache '{}'", character.getIconPath()); - ImageLoader.imageCache.remove(character.getIconPath()); // Clear image from cache. + ImageLoader.clearCache(); App.logger.info("Deleting Character: {}", character.getId()); FileUtils.deleteDirectory(character.getCharacterDirectory()); } catch (IOException 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 737c8e07..2cd0349a 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 @@ -20,7 +20,8 @@ import me.piitex.engine.containers.tabs.Tab; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import me.piitex.app.backend.Character; import org.json.JSONObject; @@ -104,7 +105,7 @@ private CardContainer buildCharacterDisplay() { currentIconPath = new File(App.getAppDirectory(), "icons/character.png"); } - ImageLoader loader = new ImageLoader(currentIconPath); + ImageLoader loader = new BaseImageLoader(currentIconPath); loader.setWidth(256); loader.setHeight(256); @@ -134,7 +135,7 @@ private CardContainer buildCharacterDisplay() { parentView.updateInfoData(); - ImageLoader imageLoader = new ImageLoader(selectedFile); + ImageLoader imageLoader = new BaseImageLoader(selectedFile); imageLoader.setWidth(256); imageLoader.setHeight(256); @@ -207,7 +208,7 @@ private VerticalLayout buildCharacterInput(@Nullable Character character, boolea parentView.setCharacterIconPath(file); parentView.getInfoFile().set("icon-path", file.getAbsolutePath()); - image.setImage(new ImageLoader(file)); + image.setImage(new BaseImageLoader(file)); parentView.updateInfoData(); 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 942609bf..60e9deab 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 @@ -10,6 +10,8 @@ import me.piitex.app.configuration.AppSettings; import me.piitex.app.utils.ImageCardExporter; import me.piitex.app.utils.UserCardImporter; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.os.configurations.InfoFile; import me.piitex.app.views.characters.CharacterEditView; import me.piitex.engine.containers.CardContainer; @@ -17,7 +19,6 @@ import me.piitex.engine.containers.tabs.Tab; 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.json.JSONObject; @@ -103,7 +104,7 @@ private CardContainer buildUserDisplay() { currentIconPath = new File(App.getAppDirectory(), "icons/character.png"); } - ImageLoader loader = new ImageLoader(currentIconPath); + ImageLoader loader = new BaseImageLoader(currentIconPath); loader.setWidth(256); loader.setHeight(256); @@ -133,7 +134,7 @@ private CardContainer buildUserDisplay() { parentView.updateInfoData(); - ImageLoader imageLoader = new ImageLoader(selectedFile); + ImageLoader imageLoader = new BaseImageLoader(selectedFile); imageLoader.setWidth(256); imageLoader.setHeight(256); @@ -189,7 +190,7 @@ private VerticalLayout buildUserInput() { if (template.getIconPath() != null && !template.getIconPath().isEmpty()) { parentView.setUserIconPath(new File(template.getIconPath())); - image.setImage(new ImageLoader(parentView.getUserIconPath())); + image.setImage(new BaseImageLoader(parentView.getUserIconPath())); } if (parentView.getUser() != null) { @@ -228,7 +229,7 @@ private VerticalLayout buildUserInput() { parentView.setUserIconPath(file); parentView.getInfoFile().set("icon-path-user", file.getAbsolutePath()); - image.setImage(new ImageLoader(file)); + image.setImage(new BaseImageLoader(file)); parentView.updateInfoData(); diff --git a/src/main/java/me/piitex/app/views/chats/ChatPageView.java b/src/main/java/me/piitex/app/views/chats/ChatPageView.java index 5ccbe9db..5adb3528 100644 --- a/src/main/java/me/piitex/app/views/chats/ChatPageView.java +++ b/src/main/java/me/piitex/app/views/chats/ChatPageView.java @@ -12,6 +12,7 @@ import me.piitex.app.backend.Response; import me.piitex.app.backend.Role; import me.piitex.app.backend.server.Server; +import me.piitex.app.backend.server.ServerProcess; import me.piitex.app.configuration.AppSettings; import me.piitex.app.utils.Placeholder; import me.piitex.app.views.chats.components.ControlBarView; @@ -20,7 +21,8 @@ import me.piitex.engine.containers.ScrollContainer; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import org.kordamp.ikonli.material2.Material2MZ; @@ -114,7 +116,7 @@ public VerticalLayout buildMessageBox(ChatMessage chatMessage) { layout.addElement(separator); //TODO: Make Avatar circular - int avatarSize = 128; + int avatarSize = 196; if (chatMessage.getSender() == Role.ASSISTANT) { displayBox.setAlignment(Pos.CENTER_RIGHT); @@ -123,9 +125,10 @@ public VerticalLayout buildMessageBox(ChatMessage chatMessage) { iconPath = new File(App.getExecutedDirectory(), "icons/charater.png").getAbsolutePath(); } - ImageLoader imageLoader = new ImageLoader(new File(iconPath)); + ImageLoader imageLoader = new BaseImageLoader(new File(iconPath)); imageLoader.setWidth(avatarSize); imageLoader.setHeight(avatarSize); + imageLoader.setSmoothing(true); ImageOverlay avatar = new ImageOverlay(imageLoader); avatar.setFitWidth(avatarSize); avatar.setFitHeight(avatarSize); @@ -143,9 +146,10 @@ public VerticalLayout buildMessageBox(ChatMessage chatMessage) { iconPath = new File(App.getExecutedDirectory(), "icons/charater.png").getAbsolutePath(); } - ImageLoader imageLoader = new ImageLoader(new File(iconPath)); + ImageLoader imageLoader = new BaseImageLoader(new File(iconPath)); imageLoader.setWidth(avatarSize); imageLoader.setHeight(avatarSize); + imageLoader.setSmoothing(true); ImageOverlay avatar = new ImageOverlay(imageLoader); avatar.setFitWidth(avatarSize); avatar.setFitHeight(avatarSize); @@ -171,6 +175,8 @@ public VerticalLayout buildMessageBox(ChatMessage chatMessage) { public void generateResponse(String prompt, boolean update) { Chat chat = parent.getChat(); + if (ServerProcess.getCurrentServer() == null) return; + // Remove regenerate button from last message if (!chatRoot.getElements().isEmpty()) { VerticalLayout lastMessageBox = (VerticalLayout) chatRoot.getLastElement(); 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 f14f6bfa..b615edf2 100644 --- a/src/main/java/me/piitex/app/views/chats/ChatView.java +++ b/src/main/java/me/piitex/app/views/chats/ChatView.java @@ -21,7 +21,7 @@ public class ChatView extends EmptyContainer { private static final AppSettings APP_SETTINGS = App.getInstance().getAppSettings(); public ChatView(@NotNull Character character, @Nullable Chat chat) { - super(APP_SETTINGS.getWidth(), APP_SETTINGS.getHeight()); + super(APP_SETTINGS.getWidth(), App.window.getDrawHeight()); this.character = character; if (chat == null) { // Create a new chat @@ -34,7 +34,7 @@ public ChatView(@NotNull Character character, @Nullable Chat chat) { } public void init() { - layout = new HorizontalLayout(APP_SETTINGS.getWidth() - 100, APP_SETTINGS.getHeight()); + layout = new HorizontalLayout(getWidth() - 100, getHeight()); layout.setMaxSize(layout.getWidth(), layout.getHeight()); layout.setSpacing(5); layout.addStyle(Styles.BG_INSET); diff --git a/src/main/java/me/piitex/app/views/chats/ChatViewSidebar.java b/src/main/java/me/piitex/app/views/chats/ChatViewSidebar.java index fa8c83d6..937ab754 100644 --- a/src/main/java/me/piitex/app/views/chats/ChatViewSidebar.java +++ b/src/main/java/me/piitex/app/views/chats/ChatViewSidebar.java @@ -5,10 +5,12 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.paint.Color; +import javafx.stage.FileChooser; import me.piitex.app.App; import me.piitex.app.backend.Character; import me.piitex.app.backend.Chat; import me.piitex.app.configuration.AppSettings; +import me.piitex.app.utils.ChatUtil; import me.piitex.app.views.HomeView; import me.piitex.app.views.LoadingView; import me.piitex.engine.containers.BorderContainer; @@ -20,6 +22,7 @@ import me.piitex.engine.overlays.*; import org.kordamp.ikonli.material2.Material2AL; +import javax.crypto.IllegalBlockSizeException; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -31,6 +34,7 @@ public class ChatViewSidebar extends EmptyContainer { private final ChatView parent; private final VerticalLayout root; + private ChoiceBoxOverlay chatSelection; private static final AppSettings APP_SETTINGS = App.getInstance().getAppSettings(); @@ -100,16 +104,70 @@ private StackContainer buildCurrentChatSelection(Layout layout) { downloadIcon.setColor(Color.LIGHTBLUE); ButtonOverlay downloadChat = new ButtonBuilder("download").addStyle(Styles.FLAT).setIcon(downloadIcon).build(); downloadChat.setTooltip("Download Chat"); + downloadIcon.onClick(_ -> { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Save Chat File"); + chooser.setInitialFileName(currentChat.getCurrentText() + ".dat"); + chooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Chat Message", ".dat")); + File file = chooser.showSaveDialog(App.window.getStage()); + + if (file.isDirectory()) return; + if (!file.getParentFile().exists()) return; + + // Now save the data inside the file. + try { + file.createNewFile(); + } catch (IOException e) { + App.logger.error("Could not create chat file!", e); + } + + try { + ChatUtil.exportChat(parent.getChat(), file); + } catch (IOException e) { + App.logger.error("Could not export chat file!", e); + } + }); IconOverlay importIcon = new IconOverlay(Material2AL.IMPORT_EXPORT); importIcon.setColor(Color.LIGHTYELLOW); ButtonOverlay importChat = new ButtonBuilder("import").addStyle(Styles.FLAT).setIcon(importIcon).build(); importChat.setTooltip("Import Chat"); + importChat.onClick(_ -> { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Save Chat File"); + chooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Chat Message", ".dat")); + + File file = chooser.showOpenDialog(App.window.getStage()); + if (!file.exists() || file.isDirectory()) return; + + try { + ChatUtil.importChat(parent.getCharacter(), file); + App.logger.info("Imported Chat: '{}'", file.getAbsolutePath()); + + Chat chat = parent.getCharacter().getChat("import-" + file.getName()); + parent.getCharacter().setLastChat(chat); + App.window.clearContainers(); + EmptyContainer progressContainer = new EmptyContainer(APP_SETTINGS.getWidth(), APP_SETTINGS.getHeight()); + progressContainer.addElement(new LoadingView("Loading chat...", progressContainer.getWidth(), progressContainer.getHeight())); + App.window.addContainer(progressContainer); + + App.getThreadPoolManager().submitTask(() -> { + ChatView chatView = new ChatView(parent.getCharacter(), chat); + Node assemble = chatView.assemble(); + Platform.runLater(() -> { + App.window.clearContainers(); + App.window.addContainer(chatView, assemble); + }); + }); + } catch (IOException | IllegalBlockSizeException e) { + App.logger.error("Could not import chat file!", e); + } + }); IconOverlay renameIcon = new IconOverlay(Material2AL.EDIT); renameIcon.setColor(Color.LIGHTGREEN); ButtonOverlay renameChat = new ButtonBuilder("rename").addStyle(Styles.FLAT).setIcon(renameIcon).build(); - renameChat.setTooltip("Rename Chat"); + renameChat.setTooltip("Enter the new name in the text box. Press this button to apply."); renameIcon.onClick(_ -> { Character character = parent.getCharacter(); Chat chat = parent.getChat(); @@ -154,7 +212,7 @@ private StackContainer buildCurrentChatSelection(Layout layout) { chats.add(chat.getFile().getName()); } - ChoiceBoxOverlay chatSelection = new ChoiceBoxOverlay(chats); + chatSelection = new ChoiceBoxOverlay(chats); chatSelection.setWidth(layout.getWidth()); chatSelection.setDefaultItem(parent.getChat().getFile().getName()); layout.addElement(chatSelection); diff --git a/src/main/java/me/piitex/app/views/creator/CreatorView.java b/src/main/java/me/piitex/app/views/creator/CreatorView.java index 24111192..02ecaff0 100644 --- a/src/main/java/me/piitex/app/views/creator/CreatorView.java +++ b/src/main/java/me/piitex/app/views/creator/CreatorView.java @@ -10,7 +10,6 @@ import me.piitex.app.views.SidebarView; import me.piitex.app.views.creator.characters.CharacterCreator; import me.piitex.app.views.creator.users.UserCreator; -import me.piitex.app.views.users.UsersView; import me.piitex.engine.containers.EmptyContainer; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; diff --git a/src/main/java/me/piitex/app/views/creator/characters/CharacterCustomizationView.java b/src/main/java/me/piitex/app/views/creator/characters/CharacterCustomizationView.java index 899c169f..ded08e91 100644 --- a/src/main/java/me/piitex/app/views/creator/characters/CharacterCustomizationView.java +++ b/src/main/java/me/piitex/app/views/creator/characters/CharacterCustomizationView.java @@ -16,7 +16,8 @@ import me.piitex.engine.containers.TileContainer; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import me.piitex.os.configurations.InfoFile; import org.json.JSONObject; @@ -56,7 +57,7 @@ public CharacterCustomizationView(CharacterCreator parent, InfoFile infoFile, do root.setAlignment(Pos.CENTER); addProperties("progress", "Character"); - scrollContainer = new ScrollContainer(root, width - 5, height - 40); + scrollContainer = new ScrollContainer(root, width - 15, height - 40); scrollContainer.setMaxSize(scrollContainer.getWidth(), scrollContainer.getHeight()); scrollContainer.setHorizontalScroll(false); scrollContainer.setScrollWhenNeeded(false); @@ -178,7 +179,7 @@ private VerticalLayout buildSettingsLayout() { characterIdInput.setCurrentText(id); characterDisplayInput.setCurrentText(displayName); characterPersonaInput.setCurrentText(persona); - characterImage.setImage(new ImageLoader(file)); + characterImage.setImage(new BaseImageLoader(file)); loreLayout.removeAllElements(); loreItems.forEach((s, s2) -> loreLayout.addElement(buildLoreEntry(s, s2))); } catch (ImageProcessingException | IOException e) { @@ -257,9 +258,9 @@ private VerticalLayout buildImagesLayout() { ImageLoader imageLoader; if (infoFile.hasKey("icon-path")) { - imageLoader = new ImageLoader(new File(infoFile.get("icon-path"))); + imageLoader = new BaseImageLoader(new File(infoFile.get("icon-path"))); } else { - imageLoader = new ImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); + imageLoader = new BaseImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); } imageLoader.setWidth(imageSize); imageLoader.setHeight(imageSize); @@ -277,7 +278,7 @@ private VerticalLayout buildImagesLayout() { if (file != null && file.isFile() && file.exists()) { App.logger.info("Updating image to '{}'", file.getAbsoluteFile()); - ImageLoader newImage = new ImageLoader(file); + ImageLoader newImage = new BaseImageLoader(file); newImage.setWidth(imageSize); newImage.setHeight(imageSize); characterImage.setImage(newImage); @@ -290,7 +291,7 @@ private VerticalLayout buildImagesLayout() { ButtonOverlay reset = new ButtonBuilder("rst").setText("Reset Image").addStyle(Styles.FLAT).build(); layout.addElement(reset); reset.onClick(_ -> { - ImageLoader loader = new ImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); + ImageLoader loader = new BaseImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); characterImage.setImage(loader); infoFile.set("icon-path", imageLoader.getFile().getAbsolutePath()); }); diff --git a/src/main/java/me/piitex/app/views/creator/characters/CharacterUserCustomizationView.java b/src/main/java/me/piitex/app/views/creator/characters/CharacterUserCustomizationView.java index 13480229..26817a29 100644 --- a/src/main/java/me/piitex/app/views/creator/characters/CharacterUserCustomizationView.java +++ b/src/main/java/me/piitex/app/views/creator/characters/CharacterUserCustomizationView.java @@ -16,7 +16,8 @@ import me.piitex.engine.containers.TileContainer; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import me.piitex.os.configurations.InfoFile; import org.json.JSONObject; @@ -48,7 +49,7 @@ public CharacterUserCustomizationView(CharacterCreator parent, InfoFile infoFile root.setAlignment(Pos.CENTER); addProperties("progress", "User"); - ScrollContainer scrollContainer = new ScrollContainer(root, width, height); + ScrollContainer scrollContainer = new ScrollContainer(root, width - 15, height - 40); scrollContainer.setHorizontalScroll(false); scrollContainer.setScrollWhenNeeded(false); scrollContainer.setMaxSize(width, height); @@ -112,7 +113,7 @@ private VerticalLayout buildSettingsLayout() { tempLore.clear(); userDisplayInput.setCurrentText(user.getDisplayName()); userPersonaInput.setCurrentText(user.getPersona()); - userImage.setImage(new ImageLoader(new File(user.getIconPath()))); + userImage.setImage(new BaseImageLoader(new File(user.getIconPath()))); loreLayout.removeAllElements(); loreItems.forEach((s, s2) -> loreLayout.addElement(buildLoreEntry(s, s2))); }); @@ -183,7 +184,7 @@ private VerticalLayout buildSettingsLayout() { tempLore.clear(); userDisplayInput.setCurrentText(displayName); userPersonaInput.setCurrentText(persona); - userImage.setImage(new ImageLoader(file)); + userImage.setImage(new BaseImageLoader(file)); loreLayout.removeAllElements(); loreItems.forEach((s, s2) -> loreLayout.addElement(buildLoreEntry(s, s2))); } catch (ImageProcessingException | IOException e) { @@ -266,7 +267,7 @@ private VerticalLayout buildImagesLayout() { image = new File(App.getExecutedDirectory(), "icons/character.png"); } - ImageLoader imageLoader = new ImageLoader(image); + ImageLoader imageLoader = new BaseImageLoader(image); imageLoader.setWidth(imageSize); imageLoader.setHeight(imageSize); @@ -284,7 +285,7 @@ private VerticalLayout buildImagesLayout() { if (file != null && !file.isDirectory() && file.exists()) { App.logger.info("Updating image to '{}'", file.getAbsoluteFile()); - ImageLoader newImage = new ImageLoader(file); + ImageLoader newImage = new BaseImageLoader(file); newImage.setWidth(imageSize); newImage.setHeight(imageSize); userImage.setImage(newImage); @@ -297,7 +298,7 @@ private VerticalLayout buildImagesLayout() { ButtonOverlay reset = new ButtonBuilder("rst").setText("Reset Image").addStyle(Styles.FLAT).build(); layout.addElement(reset); reset.onClick(_ -> { - ImageLoader loader = new ImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); + ImageLoader loader = new BaseImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); userImage.setImage(loader); infoFile.set("user-icon-path", loader.getFile().getAbsolutePath()); }); diff --git a/src/main/java/me/piitex/app/views/creator/characters/ChatCustomizationView.java b/src/main/java/me/piitex/app/views/creator/characters/ChatCustomizationView.java index 38aba3b9..a5373048 100644 --- a/src/main/java/me/piitex/app/views/creator/characters/ChatCustomizationView.java +++ b/src/main/java/me/piitex/app/views/creator/characters/ChatCustomizationView.java @@ -31,7 +31,7 @@ public ChatCustomizationView(CharacterCreator parent, InfoFile infoFile, double root.setSpacing(50); addProperties("progress", "User"); - ScrollContainer scrollContainer = new ScrollContainer(root, width, height); + ScrollContainer scrollContainer = new ScrollContainer(root, width - 15, height - 40); scrollContainer.setHorizontalScroll(false); scrollContainer.setScrollWhenNeeded(false); scrollContainer.setMaxSize(width, height); diff --git a/src/main/java/me/piitex/app/views/creator/characters/FinishCharacterCreatorView.java b/src/main/java/me/piitex/app/views/creator/characters/FinishCharacterCreatorView.java index f62cbf81..da996062 100644 --- a/src/main/java/me/piitex/app/views/creator/characters/FinishCharacterCreatorView.java +++ b/src/main/java/me/piitex/app/views/creator/characters/FinishCharacterCreatorView.java @@ -14,7 +14,7 @@ import me.piitex.engine.containers.ScrollContainer; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import me.piitex.os.configurations.InfoFile; import org.kordamp.ikonli.material2.Material2AL; @@ -23,7 +23,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.TreeMap; import java.util.concurrent.TimeUnit; public class FinishCharacterCreatorView extends EmptyContainer { @@ -44,7 +43,7 @@ public FinishCharacterCreatorView(CharacterCreator parent, InfoFile infoFile, do root.setSpacing(50); addProperties("progress", "User"); - ScrollContainer scrollContainer = new ScrollContainer(root, width, height); + ScrollContainer scrollContainer = new ScrollContainer(root, width - 15, height - 40); scrollContainer.setHorizontalScroll(false); scrollContainer.setScrollWhenNeeded(false); scrollContainer.setMaxSize(width, height); diff --git a/src/main/java/me/piitex/app/views/creator/users/UserCustomizationView.java b/src/main/java/me/piitex/app/views/creator/users/UserCustomizationView.java index e5d0c657..3550fdef 100644 --- a/src/main/java/me/piitex/app/views/creator/users/UserCustomizationView.java +++ b/src/main/java/me/piitex/app/views/creator/users/UserCustomizationView.java @@ -13,7 +13,8 @@ import me.piitex.engine.containers.ScrollContainer; import me.piitex.engine.containers.TileContainer; import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.loaders.ImageLoader; +import me.piitex.engine.loaders.image.BaseImageLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import me.piitex.os.configurations.InfoFile; import org.json.JSONObject; @@ -172,7 +173,7 @@ private VerticalLayout buildSettingsLayout() { userIdInput.setCurrentText(id); userDisplayInput.setCurrentText(displayName); userPersonaInput.setCurrentText(persona); - userImage.setImage(new ImageLoader(file)); + userImage.setImage(new BaseImageLoader(file)); parent.getUserLoreCustomizationView().getLoreLayout().removeAllElements(); loreItems.forEach((s, s2) -> parent.getUserLoreCustomizationView().getLoreLayout().addElement(parent.getUserLoreCustomizationView().buildLoreEntry(s, s2))); @@ -253,9 +254,9 @@ private VerticalLayout buildImagesLayout() { ImageLoader imageLoader; if (infoFile.hasKey("icon-path")) { - imageLoader = new ImageLoader(new File(infoFile.get("icon-path"))); + imageLoader = new BaseImageLoader(new File(infoFile.get("icon-path"))); } else { - imageLoader = new ImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); + imageLoader = new BaseImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); } imageLoader.setWidth(imageSize); imageLoader.setHeight(imageSize); @@ -273,7 +274,7 @@ private VerticalLayout buildImagesLayout() { if (file != null && file.isFile() && file.exists()) { App.logger.info("Updating image to '{}'", file.getAbsoluteFile()); - ImageLoader newImage = new ImageLoader(file); + ImageLoader newImage = new BaseImageLoader(file); newImage.setWidth(imageSize); newImage.setHeight(imageSize); userImage.setImage(newImage); @@ -286,7 +287,7 @@ private VerticalLayout buildImagesLayout() { ButtonOverlay reset = new ButtonBuilder("rst").setText("Reset Image").addStyle(Styles.FLAT).build(); layout.addElement(reset); reset.onClick(_ -> { - ImageLoader loader = new ImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); + ImageLoader loader = new BaseImageLoader(new File(App.getExecutedDirectory(), "icons/character.png")); userImage.setImage(loader); infoFile.set("icon-path", imageLoader.getFile().getAbsolutePath()); }); diff --git a/src/main/java/me/piitex/app/views/models/ModelEditView.java b/src/main/java/me/piitex/app/views/models/ModelEditView.java index 96795a60..b287b170 100644 --- a/src/main/java/me/piitex/app/views/models/ModelEditView.java +++ b/src/main/java/me/piitex/app/views/models/ModelEditView.java @@ -42,7 +42,10 @@ public class ModelEditView extends EmptyContainer { private int dryPenaltyTokens = -1; private String mmProj = "None / Disabled"; private String chatTemplate = "default"; - private String reasoningTemplate = "disabled"; + private String reasoningTemplate = "auto"; + private int reasoningBudget = 1; + private boolean reasoning = false; + private boolean forceDisableReasoning = false; private boolean jinja = false; public ModelEditView(ModelSettings settings) { @@ -90,7 +93,10 @@ public ModelEditView(ModelSettings settings) { layout.addElement(buildDryAllowedLength()); layout.addElement(buildDryPenaltyToken()); layout.addElement(buildChatTemplates()); + layout.addElement(buildReasoningEnable()); + layout.addElement(buildReasoningBudget()); layout.addElement(buildReasoningTemplate()); + layout.addElement(buildForceReasoning()); layout.addElement(buildJinjaTemplate()); page.addElement(buildSubmitBox()); @@ -119,6 +125,9 @@ private void initializeSettings() { this.dryPenaltyTokens = settings.getDryPenaltyTokens(); this.chatTemplate = settings.getChatTemplate(); this.reasoningTemplate = settings.getReasoningTemplate(); + this.reasoning = settings.isReasoning(); + this.reasoningBudget = settings.getReasoningBudget(); + this.forceDisableReasoning = settings.isForceDisableReasoning(); this.jinja = settings.isJinja(); } @@ -676,6 +685,7 @@ private TileContainer buildReasoningTemplate() { items.add("deepseek"); items.add("none"); items.add("disabled"); + items.add("auto"); ComboBoxOverlay selection = new ComboBoxOverlay(items, 400, 50); selection.setDefaultItem(reasoningTemplate); @@ -687,6 +697,72 @@ private TileContainer buildReasoningTemplate() { return tileContainer; } + private TileContainer buildReasoningEnable() { + TileContainer tileContainer = new TileContainer(0, 0); + tileContainer.addStyle(Styles.BORDER_DEFAULT); + tileContainer.addStyle(Styles.BG_DEFAULT); + tileContainer.addStyle(appSettings.getGlobalTextSize()); + tileContainer.setMaxSize(appSettings.getWidth() - 300, 150); + tileContainer.setTitle("Enable Reasoning"); + tileContainer.setDescription("Enables thinking mode."); + + IconOverlay info = new IconOverlay(Material2AL.INFO); + info.setTooltip("Only enable if the model is capable of reasoning."); + tileContainer.setGraphic(info); + + ToggleSwitchOverlay switchOverlay = new ToggleSwitchOverlay(reasoning); + switchOverlay.onToggle(event -> { + this.reasoning = event.getNewValue(); + }); + tileContainer.setAction(switchOverlay); + + return tileContainer; + } + + private TileContainer buildReasoningBudget() { + TileContainer tileContainer = new TileContainer(0, 0); + tileContainer.addStyle(Styles.BORDER_DEFAULT); + tileContainer.addStyle(Styles.BG_DEFAULT); + tileContainer.addStyle(appSettings.getGlobalTextSize()); + tileContainer.setMaxSize(appSettings.getWidth() - 300, 150); + tileContainer.setTitle("Reasoning Budget"); + tileContainer.setDescription("Set the maximum tokens allowed for reasoning."); + + IconOverlay info = new IconOverlay(Material2AL.INFO); + info.setTooltip("Default is -1 which disables the limit."); + tileContainer.setGraphic(info); + + SpinnerNumberOverlay input = new SpinnerNumberOverlay(-1, Double.MAX_VALUE, reasoningBudget); + input.onValueChange(event -> { + this.reasoningBudget = event.getNewValue().intValue(); + }); + tileContainer.setAction(input); + + return tileContainer; + } + + private TileContainer buildForceReasoning() { + TileContainer tileContainer = new TileContainer(0, 0); + tileContainer.addStyle(Styles.BORDER_DEFAULT); + tileContainer.addStyle(Styles.BG_DEFAULT); + tileContainer.addStyle(appSettings.getGlobalTextSize()); + tileContainer.setMaxSize(appSettings.getWidth() - 300, 150); + tileContainer.setTitle("Force Disable Reasoning"); + tileContainer.setDescription("Forcefully disables reasoning."); + + IconOverlay info = new IconOverlay(Material2AL.INFO); + info.setTooltip("Only enable if the reasoning is incompatible with llama.cpp."); + tileContainer.setGraphic(info); + + ToggleSwitchOverlay switchOverlay = new ToggleSwitchOverlay(forceDisableReasoning); + switchOverlay.onToggle(event -> { + this.forceDisableReasoning = event.getNewValue(); + }); + tileContainer.setAction(switchOverlay); + + return tileContainer; + } + private TileContainer buildJinjaTemplate() { TileContainer tileContainer = new TileContainer(0, 0); tileContainer.addStyle(Styles.BORDER_DEFAULT); @@ -751,6 +827,9 @@ public HorizontalLayout buildSubmitBox() { settings.setMmProj(mmProj); settings.setChatTemplate(chatTemplate); settings.setReasoningTemplate(reasoningTemplate); + settings.setReasoning(reasoning); + settings.setReasoningBudget(reasoningBudget); + settings.setForceDisableReasoning(forceDisableReasoning); settings.setJinja(jinja); settings.setChange(true); diff --git a/src/main/java/me/piitex/app/views/models/tabs/ConfigurationTab.java b/src/main/java/me/piitex/app/views/models/tabs/ConfigurationTab.java index 4609d6be..b8d18fd1 100644 --- a/src/main/java/me/piitex/app/views/models/tabs/ConfigurationTab.java +++ b/src/main/java/me/piitex/app/views/models/tabs/ConfigurationTab.java @@ -11,6 +11,8 @@ import me.piitex.app.backend.Model; import me.piitex.app.backend.server.*; import me.piitex.app.configuration.AppSettings; +import me.piitex.app.configuration.ServerSettings; +import me.piitex.app.views.HomeView; import me.piitex.engine.Element; import me.piitex.engine.PopupPosition; import me.piitex.engine.containers.CardContainer; @@ -23,11 +25,14 @@ import me.piitex.engine.layouts.VerticalLayout; import me.piitex.engine.overlays.*; import me.piitex.os.OSUtil; +import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.concurrent.TimeUnit; import static me.piitex.app.views.Positions.*; @@ -59,17 +64,15 @@ public ConfigurationTab(TabsContainer tabsContainer) { scrollContainer.setScrollWhenNeeded(false); addElement(scrollContainer); // Adds the scroll container - // TODO: Allow remote server routing. - // When enabled it allows the user to remotely connect to an endpoint. - // Not sure how control of the server would work. layout.addElement(buildServerZone()); + layout.addElement(buildBackendReset()); layout.addElement(buildHostTile()); layout.addElement(buildRemoteModeTile()); layout.addElement(buildBackend()); layout.addElement(buildGpuDevice()); + layout.addElement(buildCurrentModel()); layout.addElement(buildRunningModel()); layout.addElement(buildModelPathTile()); - layout.addElement(buildCurrentModel()); layout.addElement(buildGpuLayers()); layout.addElement(buildMemoryLock()); layout.addElement(buildFlashAttention()); @@ -77,16 +80,67 @@ public ConfigurationTab(TabsContainer tabsContainer) { Platform.runLater(this::handleServerLoad); } + public TileContainer buildBackendReset() { + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); + container.setTitle("Reset backend files."); + container.setDescription("Deletes all backend files and prompts a re-installation."); + container.addStyle(Styles.BG_DEFAULT); + container.addStyle(Styles.BORDER_DEFAULT); + container.addStyle(appSettings.getGlobalTextSize()); + + VerticalLayout configLayout = new VerticalLayout(400, container.getHeight()); + configLayout.setSpacing(10); + configLayout.setAlignment(Pos.CENTER_RIGHT); + + ButtonOverlay reset = new ButtonOverlay(new ButtonBuilder("reset").setText("Reset").addStyle(Styles.DANGER).addStyle(Styles.BUTTON_OUTLINED)); + reset.onClick(_ -> { + if (ServerProcess.getCurrentServer() == null) return; + ServerProcess.getCurrentServer().stop(); + File backendDir = App.getBackendDirectory(); + for (File dir : backendDir.listFiles()) { + if (dir.isDirectory()) { + App.logger.info("Deleted '{}'", dir.getAbsolutePath()); + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + File backendVersionFile = Arrays.stream(Objects.requireNonNull(backendDir.listFiles())).filter(file -> file.getName().endsWith(".txt")).findAny().orElse(null); + if (backendVersionFile != null) { + try { + FileUtils.delete(backendVersionFile); + App.logger.info("Deleted backend version file..."); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + App.getInstance().performUpdates(); + + App.window.clearContainers(); + App.window.addContainer(new HomeView()); + }); + configLayout.addElement(reset); + + container.setAction(configLayout); + + return container; + } + public TileContainer buildHostTile() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 180); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Set device as host."); container.setDescription("Allows other devices to connect to this devices backend server."); container.addStyle(Styles.BG_DEFAULT); container.addStyle(Styles.BORDER_DEFAULT); container.addStyle(appSettings.getGlobalTextSize()); - VerticalLayout configLayout = new VerticalLayout(400, 150); + VerticalLayout configLayout = new VerticalLayout(400, container.getHeight()); configLayout.setSpacing(10); configLayout.setAlignment(Pos.CENTER_RIGHT); @@ -100,15 +154,15 @@ public TileContainer buildHostTile() { } public TileContainer buildRemoteModeTile() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 180); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Remote Server Mode"); container.setDescription("Setup a remote connection to use a different device to run models."); container.addStyle(Styles.BG_DEFAULT); container.addStyle(Styles.BORDER_DEFAULT); container.addStyle(appSettings.getGlobalTextSize()); - VerticalLayout configLayout = new VerticalLayout(400, 150); + VerticalLayout configLayout = new VerticalLayout(400, container.getHeight()); configLayout.setSpacing(10); configLayout.setAlignment(Pos.CENTER_RIGHT); @@ -136,8 +190,8 @@ public TileContainer buildRemoteModeTile() { } public TileContainer buildModelPathTile() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 100); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Model Path"); container.setDescription("Select the folder for your models."); container.addStyle(Styles.BG_DEFAULT); @@ -186,8 +240,8 @@ private ButtonOverlay actionButton(TileContainer container) { public TileContainer buildCurrentModel() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 100); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Model Selection"); container.setDescription("Select a model to use. Will require a \"reload\"."); container.addStyle(Styles.BG_DEFAULT); @@ -226,15 +280,15 @@ public TileContainer buildCurrentModel() { } public TileContainer buildGpuLayers() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 100); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("GPU Usage"); container.setDescription("Percentage of total VRAM to use. Recommended to keep below 80%."); container.addStyle(Styles.BG_DEFAULT); container.addStyle(Styles.BORDER_DEFAULT); container.addStyle(appSettings.getGlobalTextSize()); - VerticalLayout action = new VerticalLayout(200, 100); + VerticalLayout action = new VerticalLayout(200, container.getHeight()); action.setAlignment(Pos.CENTER); SliderOverlay input = new SliderOverlay(0, 100, settings.getGpuUsage()); @@ -269,8 +323,8 @@ public Double fromString(String string) { } public TileContainer buildMemoryLock() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 100); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Memory Lock"); container.setDescription("Locks model in RAM. Can improve generation times. Disables model swapping."); container.addStyle(Styles.BG_DEFAULT); @@ -287,8 +341,8 @@ public TileContainer buildMemoryLock() { } public TileContainer buildFlashAttention() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 100); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Flash Attention"); container.setDescription("Toggles flash attention. Designed to speed up training and inference while reducing memory usage. In some rare cases it can greatly reduce quality."); container.addStyle(Styles.BG_DEFAULT); @@ -305,8 +359,8 @@ public TileContainer buildFlashAttention() { } public TileContainer buildRunningModel() { - TileContainer container = new TileContainer(0, -1); - container.setMaxSize(layout.getWidth(), 100); + TileContainer container = new TileContainer(layout.getWidth(), -1); + container.setMaxSize(container.getWidth(), container.getHeight()); container.setTitle("Current Model"); container.setDescription("The current running model that is loaded. Will be null if no model is active."); container.addStyle(Styles.BG_DEFAULT); @@ -328,8 +382,8 @@ public TileContainer buildRunningModel() { } public CardContainer buildServerZone() { - CardContainer card = new CardContainer(0, 0, layout.getWidth(), 200); - card.setMaxSize(layout.getWidth(), 200); + CardContainer card = new CardContainer(0, 0, layout.getWidth(), -1); + card.setMaxSize(card.getWidth(), card.getHeight()); TextOverlay text = new TextOverlay("Server Zone"); text.addStyle(Styles.TITLE_3); @@ -341,11 +395,14 @@ public CardContainer buildServerZone() { desc.addStyle(appSettings.getGlobalTextSize()); card.setBody(desc); - HorizontalLayout layout = new HorizontalLayout(0, 0); + + HorizontalLayout layout = new HorizontalLayout(-1, 80); + layout.setMaxSize(layout.getWidth(), layout.getHeight()); layout.setSpacing(20); - layout.setAlignment(Pos.CENTER); + layout.setAlignment(Pos.BOTTOM_CENTER); card.setFooter(layout); + start = new ButtonBuilder("start").setText("Start").build(); start.setEnabled(true); start.addStyle(Styles.SUCCESS); @@ -427,7 +484,7 @@ public CardContainer buildServerZone() { }); - stop.onClick(event -> { + stop.onClick(_ -> { if (ServerProcess.getCurrentServer() == null) { return; } @@ -506,13 +563,11 @@ public void onServerLoadingComplete(boolean success) { Platform.runLater(() -> { if (App.window.getCurrentPopup() != null) { // Check if popup still exists App.window.removeContainer(App.window.getCurrentPopup()); - - start.getNode().setDisable(false); - stop.getNode().setDisable(false); - reload.getNode().setDisable(false); + start.setEnabled(true); + stop.setEnabled(true); + reload.setEnabled(true); } }); - // Crucial: Remove the listener if it's a one-time event, to prevent memory leaks serverProcess.removeServerLoadingListener(this); } }); diff --git a/src/main/java/me/piitex/app/views/models/tabs/DownloadTab.java b/src/main/java/me/piitex/app/views/models/tabs/DownloadTab.java index bb9c890d..2579fa0b 100644 --- a/src/main/java/me/piitex/app/views/models/tabs/DownloadTab.java +++ b/src/main/java/me/piitex/app/views/models/tabs/DownloadTab.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; import java.net.SocketTimeoutException; +import java.net.URISyntaxException; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -233,7 +234,7 @@ private void setupFileInfoFetch(HorizontalLayout tileLayout, String key, String long fileSize = 0; try { fileSize = downloader.getRemoteFileSize(url); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { App.logger.warn("Failed to fetch download size. Connection could not be established: '{}'", url); } String fileName = url.substring(url.lastIndexOf('/') + 1); @@ -315,7 +316,7 @@ private void createDownloadInputs(HorizontalLayout tileLayout, ButtonOverlay dow DownloadInfo existingInfo = downloader.getDownloadInfo(url); RingProgressIndicator progressIndicator = new RingProgressIndicator(0, false); - IconOverlay stopButton = createStopButton(url, destinationFile, downloadIcon, tileLayout, fileInfoRef); + IconOverlay stopButton = createStopButton(url, destinationFile, existingInfo, fileInfoRef); TextField downloadSpeed = new TextField(""); downloadSpeed.setMaxWidth(100); @@ -411,7 +412,7 @@ public void onDownloadCancel(DownloadInfo info) { } } - private IconOverlay createStopButton(String url, File fileToDelete, ButtonOverlay downloadIcon, HorizontalLayout tileLayout, AtomicReference fileInfoRef) { + private IconOverlay createStopButton(String url, File fileToDelete, DownloadInfo info, AtomicReference fileInfoRef) { IconOverlay stop = new IconOverlay(Material2MZ.STOP_CIRCLE); stop.setColor(Color.RED); stop.onClick(event -> { diff --git a/src/main/java/me/piitex/app/views/models/tabs/ListTab.java b/src/main/java/me/piitex/app/views/models/tabs/ListTab.java index 7fecfec0..ad019616 100644 --- a/src/main/java/me/piitex/app/views/models/tabs/ListTab.java +++ b/src/main/java/me/piitex/app/views/models/tabs/ListTab.java @@ -4,6 +4,7 @@ import atlantafx.base.theme.Tweaks; import javafx.application.Platform; import javafx.geometry.Pos; +import javafx.scene.paint.Color; import me.piitex.app.App; import me.piitex.app.backend.Model; import me.piitex.app.configuration.AppSettings; @@ -74,7 +75,7 @@ public void buildModelCards(Layout layout) { TitledLayout root = new TitledLayout(model.getFile().getName() + " (" + formattedFileSize + "GB)", scrollContainer.getWidth() - 100, -1); root.setMaxSize(root.getWidth(), -1); root.addStyle(Styles.DENSE); - root.setSpacing(50); + root.setSpacing(25); root.setAlignment(Pos.TOP_CENTER); root.addStyle(Tweaks.ALT_ICON); root.setExpanded(model.getSettings().isDefault()); @@ -100,37 +101,16 @@ public void buildModelCards(Layout layout) { location.setEnabled(false); body.addElement(location); - HorizontalLayout footer = new HorizontalLayout(800, 50); + HorizontalLayout footer = new HorizontalLayout(400, 50); + footer.setAlignment(Pos.CENTER_LEFT); root.addElement(footer); - - CheckBoxOverlay defaultModel = new CheckBoxOverlay(model.getSettings().isDefault(),"Set as default"); - footer.addElement(defaultModel); - defaultModel.onSet(event -> { - // If set to true scan all models and set default to false - // Then set this one to true - // Also, re-render the view - - // Reloop models and disable any defaults - for (Model m : App.getModels("exclude")) { - if (m == model) continue; - if (m.getSettings().isDefault()) { - m.getSettings().setDefault(false); - } - } - model.getSettings().setDefault(event.getNewValue()); - - tabsContainer.replaceTab(tabsContainer.getTabs().get("List"), new ListTab(tabsContainer)); - tabsContainer.setSelectedTab("List"); - }); - - HorizontalLayout subFooter = new HorizontalLayout(400, 50); - subFooter.setX(20); - subFooter.setSpacing(40); - footer.addElement(subFooter); + footer.setSpacing(30); IconOverlay settings = new IconOverlay(Material2MZ.SETTINGS); + settings.setIconSize(18); + settings.setColor(Color.BLUE); settings.setTooltip("Go to model settings."); - subFooter.addElement(settings); + footer.addElement(settings); settings.addStyle(Styles.ACCENT); settings.addStyle(Styles.LARGE); settings.onClick(event -> { @@ -139,9 +119,11 @@ public void buildModelCards(Layout layout) { }); IconOverlay delete = new IconOverlay(Material2AL.DELETE_FOREVER); + delete.setIconSize(18); + delete.setColor(Color.RED); delete.setTooltip("Delete the model."); delete.addStyle(Styles.DANGER); - subFooter.addElement(delete); + footer.addElement(delete); delete.onClick(event -> { // Confirm the model deletion. 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 98f219fa..ede18af4 100644 --- a/src/main/java/me/piitex/app/views/settings/SettingsView.java +++ b/src/main/java/me/piitex/app/views/settings/SettingsView.java @@ -79,7 +79,7 @@ public TileContainer buildWindowScaling() { App.window.clear(); App.window.close(false); appSettings.setWindowScaling(event.getNewValue()); - App.getInstance().initialization(App.window.getStage()); + App.getInstance().initialization(); }); tileContainer.setAction(toggleSwitchOverlay); @@ -175,7 +175,7 @@ public TileContainer buildChatSize() { App.getInstance().getCharacters().values().forEach(character -> character.getChatViewCachedNodes().clear()); App.window.clear(); App.window.close(false); - App.getInstance().initialization(App.window.getStage()); + App.getInstance().initialization(); }); tileContainer.setAction(selection); @@ -230,7 +230,7 @@ public TileContainer buildGlobalChatSize() { App.getInstance().getCharacters().values().forEach(character -> character.getChatViewCachedNodes().clear()); App.window.clear(); App.window.close(false); - App.getInstance().initialization(App.window.getStage()); + App.getInstance().initialization(); }); diff --git a/src/main/java/me/piitex/app/views/setup/ConfigureBackendView.java b/src/main/java/me/piitex/app/views/setup/ConfigureBackendView.java index f109c785..28f4d5d4 100644 --- a/src/main/java/me/piitex/app/views/setup/ConfigureBackendView.java +++ b/src/main/java/me/piitex/app/views/setup/ConfigureBackendView.java @@ -6,7 +6,7 @@ import javafx.scene.text.TextAlignment; import javafx.util.StringConverter; import me.piitex.app.App; -import me.piitex.app.backend.server.ServerSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.configuration.AppSettings; import me.piitex.app.views.Positions; import me.piitex.engine.layouts.VerticalLayout; diff --git a/src/main/java/me/piitex/app/views/setup/ConfigureModelView.java b/src/main/java/me/piitex/app/views/setup/ConfigureModelView.java index 9f5f37ac..a1277c59 100644 --- a/src/main/java/me/piitex/app/views/setup/ConfigureModelView.java +++ b/src/main/java/me/piitex/app/views/setup/ConfigureModelView.java @@ -3,13 +3,11 @@ import atlantafx.base.theme.Styles; import javafx.geometry.Orientation; import javafx.geometry.Pos; -import javafx.scene.paint.Color; import javafx.stage.DirectoryChooser; import me.piitex.app.App; -import me.piitex.app.backend.server.ServerSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.configuration.AppSettings; import me.piitex.app.views.Positions; -import me.piitex.engine.containers.TileContainer; import me.piitex.engine.layouts.HorizontalLayout; import me.piitex.engine.layouts.VerticalLayout; import me.piitex.engine.overlays.*; diff --git a/src/main/java/me/piitex/app/views/setup/GPUSetupView.java b/src/main/java/me/piitex/app/views/setup/GPUSetupView.java index 85a17bc0..f050103d 100644 --- a/src/main/java/me/piitex/app/views/setup/GPUSetupView.java +++ b/src/main/java/me/piitex/app/views/setup/GPUSetupView.java @@ -5,7 +5,7 @@ import javafx.geometry.Pos; import me.piitex.app.App; import me.piitex.app.backend.server.DeviceProcess; -import me.piitex.app.backend.server.ServerSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.configuration.AppSettings; import me.piitex.app.views.Positions; import me.piitex.engine.layouts.VerticalLayout; @@ -63,6 +63,12 @@ private void init() { backendBox.addElement(backendSelection); backendSelection.onItemSelect(event -> { settings.setBackend(event.getNewValue()); + + try { + new DeviceProcess(event.getNewValue()); + } catch (IOException e) { + App.logger.error("Could not load devices for '" + event.getNewValue() + "'!", e); + } }); VerticalLayout gpuBox = new VerticalLayout(-1, -1); diff --git a/src/main/java/me/piitex/app/views/setup/SetupModelView.java b/src/main/java/me/piitex/app/views/setup/SetupModelView.java index b1145200..4c849a13 100644 --- a/src/main/java/me/piitex/app/views/setup/SetupModelView.java +++ b/src/main/java/me/piitex/app/views/setup/SetupModelView.java @@ -7,7 +7,7 @@ import javafx.scene.text.TextAlignment; import me.piitex.app.App; import me.piitex.app.backend.server.ServerProcess; -import me.piitex.app.backend.server.ServerSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.configuration.AppSettings; import me.piitex.app.views.Positions; import me.piitex.app.views.creator.characters.CharacterCreator; @@ -18,7 +18,6 @@ import me.piitex.os.FileDownloader; import me.piitex.os.OSUtil; -import java.awt.*; import java.io.File; import java.io.IOException; diff --git a/src/main/java/me/piitex/app/views/setup/SetupRunnerView.java b/src/main/java/me/piitex/app/views/setup/SetupRunnerView.java index 450b6963..75e4af8f 100644 --- a/src/main/java/me/piitex/app/views/setup/SetupRunnerView.java +++ b/src/main/java/me/piitex/app/views/setup/SetupRunnerView.java @@ -2,16 +2,14 @@ import atlantafx.base.theme.Styles; import javafx.application.Platform; -import javafx.beans.binding.Bindings; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.control.ProgressBar; import javafx.scene.paint.Color; import me.piitex.app.App; import me.piitex.app.backend.Model; -import me.piitex.app.backend.server.DeviceProcess; import me.piitex.app.backend.server.ServerProcess; -import me.piitex.app.backend.server.ServerSettings; +import me.piitex.app.configuration.ServerSettings; import me.piitex.app.configuration.AppSettings; import me.piitex.app.views.Positions; import me.piitex.engine.layouts.VerticalLayout; @@ -92,6 +90,12 @@ private void startRunner() { } private void downloadSmallModel() { + File model = new File(App.getModelsDirectory(), "gemma-3-270m-it-UD-IQ2_M.gguf"); + if (model.exists()) { + startLLama(model); + return; + } + FileDownloader fileDownloader = new FileDownloader(); fileDownloader.addDownloadListener(new DownloadListener() { @Override diff --git a/src/main/java/me/piitex/app/views/users/UserEditView.java b/src/main/java/me/piitex/app/views/users/UserEditView.java deleted file mode 100644 index f49bb7aa..00000000 --- a/src/main/java/me/piitex/app/views/users/UserEditView.java +++ /dev/null @@ -1,227 +0,0 @@ -package me.piitex.app.views.users; - -import atlantafx.base.theme.Styles; -import javafx.geometry.Pos; -import javafx.scene.control.TextField; -import me.piitex.app.App; -import me.piitex.app.backend.User; -import me.piitex.app.configuration.AppSettings; -import me.piitex.app.views.SidebarView; -import me.piitex.app.views.users.tabs.UserLoreBookTab; -import me.piitex.app.views.users.tabs.UserTab; -import me.piitex.engine.PopupPosition; -import me.piitex.engine.containers.DialogueContainer; -import me.piitex.engine.containers.EmptyContainer; -import me.piitex.engine.containers.tabs.TabsContainer; -import me.piitex.engine.layouts.HorizontalLayout; -import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.overlays.ButtonBuilder; -import me.piitex.engine.overlays.ButtonOverlay; -import me.piitex.engine.overlays.MessageOverlay; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.TreeMap; - -public class UserEditView extends EmptyContainer { - private User user; - private String userId = ""; - private String userDisplay = ""; - private String userPersona = ""; - private File userIconPath; - - // Lore Map - private TreeMap loreBook; - - private TabsContainer tabsContainer; - private UserTab userTab; - private UserLoreBookTab userLoreBookTab; - - private static final AppSettings appSettings = App.getInstance().getAppSettings(); - - public UserEditView() { - super(appSettings.getWidth(), appSettings.getHeight()); - this.loreBook = new TreeMap<>(); - init(); - } - - public UserEditView(User user) { - super(appSettings.getWidth(), appSettings.getHeight()); - this.user = user; - this.userId = user.getId(); - this.userDisplay = user.getDisplayName(); - this.userPersona = user.getPersona(); - this.userIconPath = new File(user.getIconPath()); - this.loreBook = user.getLorebook(); - init(); - } - - public void init() { - addStyle(Styles.BG_INSET); - - HorizontalLayout root = new HorizontalLayout(getWidth(), getHeight()); - root.setMaxSize(root.getWidth(), root.getHeight()); - addElement(root); - - SidebarView sidebarView = new SidebarView(); - root.addElement(sidebarView); - - VerticalLayout mainPage = new VerticalLayout(appSettings.getWidth() - 200, 0); - mainPage.setMaxSize(mainPage.getWidth(), mainPage.getHeight()); - root.addElement(mainPage); - - tabsContainer = new TabsContainer(0, 0, mainPage.getWidth(), appSettings.getHeight()); - mainPage.addElement(tabsContainer); - - userTab = new UserTab("User", this); - tabsContainer.addTab(userTab); - - userLoreBookTab = new UserLoreBookTab("Lorebook", this); - tabsContainer.addTab(userLoreBookTab); - } - - public HorizontalLayout buildSubmitBox() { - HorizontalLayout layout = new HorizontalLayout(appSettings.getWidth() - 300, 50); - layout.setY(appSettings.getHeight() - 150); - layout.setSpacing(20); - layout.setAlignment(Pos.CENTER); - - ButtonOverlay cancel = new ButtonBuilder("cancel").setText("Cancel").build(); - cancel.addStyle(Styles.DANGER); - cancel.addStyle(Styles.BUTTON_OUTLINED); - layout.addElement(cancel); - - ButtonOverlay submit = new ButtonBuilder("submit").setText("Submit").build(); - submit.addStyle(Styles.SUCCESS); - submit.addStyle(Styles.BUTTON_OUTLINED); - layout.addElements(submit); - - cancel.onClick(event -> { - DialogueContainer dialogueContainer = new DialogueContainer("Do you want to exit without saving?", 500, 500); - - ButtonOverlay stay = new ButtonBuilder("stay").setText("Stay").build(); - stay.setWidth(150); - stay.addStyle(Styles.SUCCESS); - stay.onClick(_ -> App.window.removeContainer(dialogueContainer)); - - ButtonOverlay leave = new ButtonBuilder("leave").setText("Leave").build(); - leave.setWidth(150); - leave.addStyle(Styles.DANGER); - leave.onClick(_ -> { - App.window.clearContainers(); - App.window.addContainer(new UsersView()); - }); - - dialogueContainer.setCancelButton(stay); - dialogueContainer.setConfirmButton(leave); - - App.window.renderPopup(dialogueContainer, event.getHandler().getSceneX(), event.getHandler().getSceneY() - 100, 500, 500); - }); - - submit.onClick(event -> { - if (!validate()) { - return; - } - - if (user == null) { - user = new User(userId); - } - user.setDisplayName(userDisplay); - user.setPersona(userPersona); - user.setLorebook(loreBook); - if (userIconPath == null || !userIconPath.exists()) { - userIconPath = new File(App.getAppDirectory(), "icons/character.png"); - } - try { - File output = new File(user.getUserDirectory(), "user.png"); - Files.copy(userIconPath.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING); - user.setIconPath(output.getAbsolutePath()); - } catch (IOException e) { - App.logger.error("Failed to move image to user.", e); - } - - App.getInstance().getUserTemplates().putIfAbsent(userId, user); - - App.window.clearContainers(); - App.window.addContainer(new UsersView()); - }); - - return layout; - } - - public boolean validate() { - if (userId.isEmpty() || ((TextField) userTab.getUserIdInput().getNode()).getText().isEmpty()) { - tabsContainer.getTabPane().getSelectionModel().select(userTab.getJfxTab()); - MessageOverlay required = new MessageOverlay(0, 0, 600, 100, "User ID", "ID is required."); - required.addStyle(Styles.WARNING); - required.addStyle(Styles.BG_DEFAULT); - App.window.renderPopup(required, PopupPosition.CENTER, 600, 100, true); - userTab.getUserIdInput().getNode().requestFocus(); - return false; - } else if (userDisplay.isEmpty() || ((TextField) userTab.getUserDisplayNameInput().getNode()).getText().isEmpty()) { - tabsContainer.getTabPane().getSelectionModel().select(userTab.getJfxTab()); - MessageOverlay required = new MessageOverlay(0, 0, 600, 100, "Display Name", "Display name is required."); - required.addStyle(Styles.WARNING); - required.addStyle(Styles.BG_DEFAULT); - App.window.renderPopup(required, PopupPosition.CENTER, 600, 100, true); - userTab.getUserIdInput().getNode().requestFocus(); - return false; - } else { - return true; - } - } - - public UserTab getUserTab() { - return userTab; - } - - public UserLoreBookTab getUserLoreBookTab() { - return userLoreBookTab; - } - - public User getUser() { - return user; - } - - public String getUserId() { - return userId; - } - - public File getUserIconPath() { - return userIconPath; - } - - public String getUserDisplay() { - return userDisplay; - } - - public String getUserPersona() { - return userPersona; - } - - public TreeMap getLoreBook() { - return loreBook; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public void setUserDisplay(String userDisplay) { - this.userDisplay = userDisplay; - } - - public void setUserPersona(String userPersona) { - this.userPersona = userPersona; - } - - public void setUserIconPath(File userIconPath) { - this.userIconPath = userIconPath; - } - - public void setLoreBook(TreeMap loreBook) { - this.loreBook = loreBook; - } -} diff --git a/src/main/java/me/piitex/app/views/users/UserTemplateView.java b/src/main/java/me/piitex/app/views/users/UserTemplateView.java index 8e48e8b3..f164127f 100644 --- a/src/main/java/me/piitex/app/views/users/UserTemplateView.java +++ b/src/main/java/me/piitex/app/views/users/UserTemplateView.java @@ -7,6 +7,7 @@ import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.paint.Color; +import javafx.scene.text.Font; import javafx.stage.FileChooser; import me.piitex.app.App; import me.piitex.app.backend.User; @@ -23,7 +24,8 @@ 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.loaders.FontLoader; +import me.piitex.engine.loaders.image.ImageLoader; import me.piitex.engine.overlays.*; import org.apache.commons.io.FileUtils; import org.kordamp.ikonli.material2.Material2AL; @@ -58,21 +60,34 @@ public void init() { root.addElement(sidebarView); root.setSpacing(35); - if (App.getInstance().isLoading()) { - root.addElement(new LoadingView("Loading data...", root.getWidth(), 650)); - App.getThreadPoolManager().submitSchedule(() -> { - boolean loading = App.getInstance().isLoading(); - while (loading) { - loading = App.getInstance().isLoading(); - if (!loading) break; - } - Platform.runLater(() -> { - root.removeElement(1); - buildUsers(); - }); - }, 1, TimeUnit.SECONDS); + if (!App.getInstance().getUserTemplates().isEmpty()) { + if (App.getInstance().isLoading()) { + root.addElement(new LoadingView("Loading data...", root.getWidth(), 650)); + App.getThreadPoolManager().submitSchedule(() -> { + boolean loading = App.getInstance().isLoading(); + while (loading) { + loading = App.getInstance().isLoading(); + if (!loading) break; + } + Platform.runLater(() -> { + root.removeElement(1); + buildUsers(); + }); + }, 1, TimeUnit.SECONDS); + } else { + buildUsers(); + } } else { - buildUsers(); + VerticalLayout layout = new VerticalLayout(appSettings.getWidth() - Positions.SIDEBAR_WIDTH - 25, -1); + layout.setMaxSize(layout.getWidth(), layout.getHeight()); + layout.setSpacing(20); + layout.setAlignment(Pos.CENTER); + layout.addStyle(Styles.BORDER_DEFAULT); + root.addElement(layout); + + TextOverlay body = new TextOverlay("Create your first user in the creator page."); + body.setFont(new FontLoader(Font.getDefault(), 24)); + layout.addElement(body); } } @@ -102,7 +117,7 @@ public void buildUsers() { imageWidth = 256; imageHeight = 256; cardWidth = 280; - cardHeight = 380; + cardHeight = 350; } body.setScrollWhenNeeded(false); body.setHorizontalScroll(false); @@ -119,16 +134,11 @@ public void buildUsers() { CardContainer card = new CardContainer(0,0, cardWidth, cardHeight); card.setMaxSize(cardWidth, cardHeight); - VerticalLayout displayBox = new VerticalLayout(0, 330); + VerticalLayout displayBox = new VerticalLayout(0, cardHeight - 50); displayBox.setSpacing(15); displayBox.setAlignment(Pos.TOP_CENTER); - TextOverlay helper = new TextOverlay("Click to chat"); - helper.setUnderline(true); - displayBox.addElement(helper); - ContextMenu contextMenu = new ContextMenu(); - MenuItem edit = new MenuItem("Edit"); edit.setOnAction(_ -> editUser(user)); MenuItem copy = new MenuItem("Copy"); @@ -270,7 +280,7 @@ private void deleteUser(FlowLayout base, CardContainer card, me.piitex.app.backe // Cleanup image usage VerticalLayout verticalLayout = (VerticalLayout) card.getBody(); - ImageOverlay imageOverlay = (ImageOverlay) verticalLayout.getElementAt(1); + ImageOverlay imageOverlay = (ImageOverlay) verticalLayout.getElementAt(0); // When setting to null the engine will dispose of the image and the JVM will call gc. imageOverlay.setImage(null); @@ -281,9 +291,10 @@ private void deleteUser(FlowLayout base, CardContainer card, me.piitex.app.backe App.getThreadPoolManager().submitSchedule(() -> { try { App.logger.info("Removing image from cache '{}'", user.getIconPath()); - ImageLoader.imageCache.remove(user.getIconPath()); // Clear image from cache. + ImageLoader.clearCache(); App.logger.info("Deleting User: {}", user.getId()); FileUtils.deleteDirectory(user.getUserDirectory()); + App.getInstance().getUserTemplates().remove(user.getId()); } catch (IOException e) { App.logger.error("Could not delete directory!", e); } diff --git a/src/main/java/me/piitex/app/views/users/UsersView.java b/src/main/java/me/piitex/app/views/users/UsersView.java deleted file mode 100644 index af780630..00000000 --- a/src/main/java/me/piitex/app/views/users/UsersView.java +++ /dev/null @@ -1,254 +0,0 @@ -package me.piitex.app.views.users; - -import atlantafx.base.theme.Styles; -import javafx.application.Platform; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.MenuItem; -import javafx.scene.paint.Color; -import me.piitex.app.App; -import me.piitex.app.backend.User; -import me.piitex.app.configuration.AppSettings; -import me.piitex.app.views.LoadingView; -import me.piitex.app.views.SidebarView; -import me.piitex.engine.containers.Container; -import me.piitex.engine.containers.CardContainer; -import me.piitex.engine.containers.DialogueContainer; -import me.piitex.engine.containers.EmptyContainer; -import me.piitex.engine.containers.ScrollContainer; -import me.piitex.engine.layouts.FlowLayout; -import me.piitex.engine.layouts.HorizontalLayout; -import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.overlays.*; -import org.apache.commons.io.FileUtils; -import org.kordamp.ikonli.material2.Material2AL; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -public class UsersView extends EmptyContainer { - private static final AppSettings appSettings = App.getInstance().getAppSettings(); - private final VerticalLayout mainPage; - - 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); - - SidebarView sidebarView = new SidebarView(); - root.addElement(sidebarView); - - mainPage = new VerticalLayout(appSettings.getWidth() - 200, 0); - mainPage.setMaxSize(mainPage.getWidth(), mainPage.getHeight()); - root.addElement(mainPage); - - init(); - } - - public void init() { - 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); - - ButtonOverlay newUser = new ButtonBuilder("new").setText("New User").build(); - newUser.addStyle(Styles.SUCCESS); - newUser.addStyle(Styles.BUTTON_OUTLINED); - newUser.onClick(_ -> { - App.window.clearContainers(); - App.window.addContainer(new UserEditView()); - }); - header.addElement(newUser); - - buildFlowLayout(); - } - - public void buildFlowLayout() { - VerticalLayout flowRoot = new VerticalLayout(appSettings.getWidth() - 200, 0); - - ScrollContainer base = new ScrollContainer(flowRoot, 10, 10, mainPage.getMaxWidth(), -1); - 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); - - for (User user : App.getInstance().getUserTemplates().values()) { - CardContainer card = new CardContainer(0,0, 280, 380); - card.setMaxSize(card.getWidth(), card.getHeight()); - - VerticalLayout displayBox = new VerticalLayout(0, 330); - displayBox.setSpacing(15); - displayBox.setAlignment(Pos.TOP_CENTER); - - TextOverlay id = new TextOverlay(user.getId()); - displayBox.addElement(id); - - - ContextMenu contextMenu = new ContextMenu(); - - MenuItem edit = new MenuItem("Edit"); - edit.setOnAction(_ -> editUser(user)); - MenuItem copy = new MenuItem("Copy"); - copy.setOnAction(_ -> duplicateUser(user)); - MenuItem delete = new MenuItem("Delete"); - delete.setOnAction(_ -> deleteUser(user, flowLayout, card)); - - contextMenu.getItems().add(edit); - contextMenu.getItems().add(copy); - contextMenu.getItems().add(delete); - - ImageOverlay icon = User.getUserAvatar(user.getIconPath(), 256, 256); - if (icon != null && icon.getImage() != null) { - icon.setPreserveRatio(false); - displayBox.addElement(icon); - } - - TextOverlay name = new TextOverlay(user.getDisplayName()); - displayBox.addElement(name); - - card.setBody(displayBox); - - card.setFooter(buildControlBox(flowLayout, card, user)); - - flowLayout.addElement(card); - } - } - - public HorizontalLayout buildControlBox(FlowLayout base, CardContainer card, User user) { - HorizontalLayout root = new HorizontalLayout(200, 25); - root.setIndex(10); - root.setSpacing(20); - if (!App.mobile) { - root.setAlignment(Pos.BASELINE_CENTER); - } - - IconOverlay edit = new IconOverlay(Material2AL.EDIT); - edit.setTooltip("Edit the user"); - edit.setColor(Color.GREEN); - edit.onClick(event -> { - editUser(user); - }); - root.addElement(edit); - - IconOverlay duplicate = new IconOverlay(Material2AL.FILE_COPY); - duplicate.setTooltip("Duplicate the user."); - duplicate.setColor(Color.YELLOW); - duplicate.onClick(event -> { - duplicateUser(user); - }); - root.addElement(duplicate); - - IconOverlay delete = new IconOverlay(Material2AL.DELETE_FOREVER); - delete.setColor(Color.RED); - delete.setTooltip("Delete the user."); - delete.onClick(event -> { - deleteUser(base, card, user, event.getHandler().getSceneX(), event.getHandler().getSceneY()); - }); - root.addElement(delete); - return root; - } - - private void editUser(User user) { - App.window.clearContainers(); - EmptyContainer progressContainer = new EmptyContainer(App.getInstance().getAppSettings().getWidth(), App.getInstance().getAppSettings().getHeight()); - progressContainer.addElement(new LoadingView("Loading User data...", progressContainer.getWidth(), progressContainer.getHeight())); - App.window.addContainer(progressContainer); - - App.getThreadPoolManager().submitTask(() -> { - Container container = new UserEditView(user); - Node assemble = container.assemble(); - Platform.runLater(() -> { - App.window.clearContainers(); - App.window.addContainer(container, assemble); - }); - }); - } - - private void duplicateUser(User user) { - // Duplicate the User. - String newId = user.getId() + " (copy)"; - while (App.getInstance().getUser(newId) != null) { - newId += " (copy)"; - } - - // Edit the User in the edit view rather than duplicating the files - // Allow the id to be edited and changed. - - // Create a copy of the User. - App.window.clearContainers(); - EmptyContainer progressContainer = new EmptyContainer(App.getInstance().getAppSettings().getWidth(), App.getInstance().getAppSettings().getHeight()); - progressContainer.addElement(new LoadingView("Loading User data...", progressContainer.getWidth(), progressContainer.getHeight())); - App.window.addContainer(progressContainer); - - User duplicated = new User(newId, null); - App.getThreadPoolManager().submitTask(() -> { - duplicated.copy(user); - UserEditView editView = new UserEditView(duplicated); - Node assemble = editView.assemble(); - Platform.runLater(() -> { - App.window.clearContainers(); - App.window.addContainer(editView, assemble); - }); - }); - } - - private void deleteUser(FlowLayout base, CardContainer card, User user, double x, double y) { - DialogueContainer dialogueContainer = new DialogueContainer("Delete '" + user.getId() + "'?", 500, 500); - - ButtonOverlay cancel = new ButtonBuilder("cancel").setText("Keep").build(); - cancel.setWidth(150); - cancel.addStyle(Styles.SUCCESS); - cancel.onClick(_ -> { - App.window.removeContainer(dialogueContainer); - }); - - ButtonOverlay confirm = new ButtonBuilder("confirm").setText("Delete").build(); - confirm.setWidth(150); - confirm.addStyle(Styles.DANGER); - confirm.onClick(_ -> { - App.getInstance().getUserTemplates().remove(user.getId()); - App.window.removeContainer(dialogueContainer); - - // Cleanup image usage - VerticalLayout verticalLayout = (VerticalLayout) card.getBody(); - ImageOverlay imageOverlay = (ImageOverlay) verticalLayout.getElementAt(1); - - // When setting to null the engine will dispose of the image and the JVM will call gc. - imageOverlay.setImage(null); - - deleteUserDirectory(user); - base.removeElement(card); - }); - - dialogueContainer.setCancelButton(cancel); - dialogueContainer.setConfirmButton(confirm); - - // Render this on top - App.window.renderPopup(dialogueContainer, x, y, 500, 500); - } - - private void deleteUser(User user, FlowLayout base, CardContainer card) { - App.getInstance().getUserTemplates().remove(user.getId()); - deleteUserDirectory(user); - base.removeElement(card); - } - - private void deleteUserDirectory(User user) { - // Add a delay to ensure all io operations are completed. - App.getThreadPoolManager().submitSchedule(() -> { - App.logger.info("Deleting User: {}", user.getId()); - try { - FileUtils.deleteDirectory(user.getUserDirectory()); - } catch (IOException e) { - App.logger.error("Could not delete directory!", e); - } - }, 1, TimeUnit.SECONDS); - } -} diff --git a/src/main/java/me/piitex/app/views/users/tabs/UserLoreBookTab.java b/src/main/java/me/piitex/app/views/users/tabs/UserLoreBookTab.java deleted file mode 100644 index 7e0afe72..00000000 --- a/src/main/java/me/piitex/app/views/users/tabs/UserLoreBookTab.java +++ /dev/null @@ -1,134 +0,0 @@ -package me.piitex.app.views.users.tabs; - -import atlantafx.base.theme.Styles; -import javafx.geometry.Pos; -import me.piitex.app.App; -import me.piitex.app.configuration.AppSettings; -import me.piitex.app.views.users.UserEditView; -import me.piitex.engine.containers.CardContainer; -import me.piitex.engine.containers.ScrollContainer; -import me.piitex.engine.containers.tabs.Tab; -import me.piitex.engine.layouts.HorizontalLayout; -import me.piitex.engine.layouts.VerticalLayout; -import me.piitex.engine.overlays.*; -import org.kordamp.ikonli.javafx.FontIcon; -import org.kordamp.ikonli.material2.Material2AL; - -import java.util.Map; - -public class UserLoreBookTab extends Tab { - private final UserEditView userEditView; - private TextFieldOverlay addKeyInput; - private TextAreaOverlay addValueInput; - private ScrollContainer scrollLoreContainer; - - private static final AppSettings appSettings = App.getInstance().getAppSettings(); - - public UserLoreBookTab(String text, UserEditView userEditView) { - super(text); - this.userEditView = userEditView; - buildLorebookTabContent(); - } - - public void buildLorebookTabContent() { - removeAllElements(); - - VerticalLayout rootLayout = new VerticalLayout(appSettings.getWidth() - 300, appSettings.getHeight()); - rootLayout.setSpacing(50); - rootLayout.setAlignment(Pos.TOP_CENTER); - this.addElement(rootLayout); - - IconOverlay info = new IconOverlay(Material2AL.INFO); - info.setTooltip("Use the following placeholders; {char}, {{char}}, {chara}, {{chara}}, {character}, {{character}}, {user}, {{user}}, {usr}, {{usr}}"); - rootLayout.addElement(info); - - HorizontalLayout displayBox = new HorizontalLayout(0, -1); - displayBox.setAlignment(Pos.TOP_CENTER); - displayBox.setSpacing(100); - rootLayout.addElement(displayBox); - - CardContainer addContainer = new CardContainer(0, 0, 400, 400); - addContainer.setMaxSize(400, 400); - - addKeyInput = new TextFieldOverlay("", "Separate multiple keys with a comma (,)", 0, 0, 200, 50); - addContainer.setHeader(addKeyInput); - - addValueInput = new TextAreaOverlay("", "Enter the lore info", 0, 0, 400, 200); - addContainer.setBody(addValueInput); - - HorizontalLayout buttonBox = new HorizontalLayout(200, 50); - buttonBox.setAlignment(Pos.CENTER); - - ButtonOverlay add = new ButtonBuilder("add").setText("Add").build(); - add.addStyle(Styles.SUCCESS); - add.addStyle(Styles.BUTTON_OUTLINED); - buttonBox.addElement(add); - addContainer.setFooter(buttonBox); - - scrollLoreContainer = getLoreItems(); - add.onClick(_ -> { - String keyText = addKeyInput.getCurrentText(); - String valueText = addValueInput.getCurrentText(); - - if (keyText.isEmpty() || valueText.isEmpty()) { - return; - } - - userEditView.getLoreBook().put(keyText, valueText); - - VerticalLayout scrollLayout = (VerticalLayout) scrollLoreContainer.getLayout(); - CardContainer newLoreEntryCard = buildLoreEntry(keyText, scrollLayout); - scrollLayout.addElement(newLoreEntryCard); - - addKeyInput.setCurrentText(""); - addValueInput.setCurrentText(""); - }); - - displayBox.addElement(addContainer); - displayBox.addElement(scrollLoreContainer); - - this.addElement(userEditView.buildSubmitBox()); - } - - private ScrollContainer getLoreItems() { - VerticalLayout scrollLayout = new VerticalLayout(0, -1); - - ScrollContainer root = new ScrollContainer(scrollLayout, 0, 0, 450, appSettings.getHeight() - 300); - root.setMaxSize(450, appSettings.getHeight() - 300); - root.setVerticalScroll(true); - root.setScrollWhenNeeded(false); - root.setHorizontalScroll(false); - root.setPannable(true); - - App.logger.info("Building lore cache..."); - for (Map.Entry entry : userEditView.getLoreBook().entrySet()) { - scrollLayout.addElement(buildLoreEntry(entry.getKey(), scrollLayout)); - } - return root; - } - - private CardContainer buildLoreEntry(String key, VerticalLayout scrollContainer) { - CardContainer card = new CardContainer(0, 0, 400, 300); - - TextFieldOverlay entryKey = new TextFieldOverlay(key, 0, 0, 400, 50); - entryKey.setEnabled(false); // Make key read-only - card.setHeader(entryKey); - - TextAreaOverlay entryValue = new TextAreaOverlay(userEditView.getLoreBook().get(key), "", 400, 200); - entryValue.onInputSetEvent(event -> userEditView.getLoreBook().put(key, event.getInput())); - card.setBody(entryValue); - - ButtonOverlay remove = new ButtonBuilder("remove").setText("Remove").build(); - remove.setX(170); - remove.addStyle(Styles.DANGER); - remove.addStyle(Styles.BUTTON_OUTLINED); - card.setFooter(remove); - - remove.onClick(_ -> { - userEditView.getLoreBook().remove(key); - scrollContainer.removeElement(card); - }); - - return card; - } -} 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 deleted file mode 100644 index 0033b8a0..00000000 --- a/src/main/java/me/piitex/app/views/users/tabs/UserTab.java +++ /dev/null @@ -1,208 +0,0 @@ -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; -import me.piitex.engine.containers.tabs.Tab; -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.json.JSONObject; - -import java.io.File; -import java.io.IOException; - -public class UserTab extends Tab { - private final UserEditView userEditView; - - private TextFieldOverlay userIdInput; - private TextFieldOverlay userDisplayNameInput; - private RichTextAreaOverlay userDescription; - - private ImageOverlay image; - - private static final AppSettings appSettings = App.getInstance().getAppSettings(); - - public UserTab(String text, UserEditView userEditView) { - super(text); - this.userEditView = userEditView; - init(); - } - - public void init() { - VerticalLayout rootLayout = new VerticalLayout(appSettings.getWidth() - 315, 0); - rootLayout.setSpacing(40); - rootLayout.setAlignment(Pos.TOP_CENTER); - - ScrollContainer scrollContainer = new ScrollContainer(rootLayout, 0, 0, appSettings.getWidth() - 300, appSettings.getHeight() - 200); - scrollContainer.setMaxSize(scrollContainer.getWidth(), scrollContainer.getHeight()); - scrollContainer.setHorizontalScroll(false); - scrollContainer.setPannable(true); - addElement(scrollContainer); - - HorizontalLayout displayBox = new HorizontalLayout(600, 320); - displayBox.setMaxSize(600, 320); - displayBox.addStyle(Styles.BORDER_SUBTLE); - displayBox.setSpacing(20); - rootLayout.addElement(displayBox); - - CardContainer displayCard = buildUserDisplay(); - displayBox.addElement(displayCard); - displayBox.addElement(buildUserInput()); - - double scaleFactor = (double) appSettings.getWidth() / 1920.0; - userDescription = new RichTextAreaOverlay(userEditView.getUserPersona(), 600, 400 * scaleFactor); - userDescription.setBackgroundColor(appSettings.getThemeDefaultColor(appSettings.getTheme())); - userDescription.setBorderColor(appSettings.getThemeBorderColor(appSettings.getTheme())); - userDescription.setTextFill(appSettings.getThemeTextColor(appSettings.getTheme())); - userDescription.setMaxHeight(400 * scaleFactor); - userDescription.setMaxWidth(600); - userDescription.onInputSetEvent(event -> { - userEditView.setUserPersona(event.getInput()); - }); - userDescription.addStyle(Styles.BG_DEFAULT); - userDescription.addStyle(appSettings.getChatTextSize()); - userDescription.addStyle(Styles.TEXT_ON_EMPHASIS); - - rootLayout.addElement(userDescription); - - addElement(userEditView.buildSubmitBox()); - } - - private CardContainer buildUserDisplay() { - CardContainer root = new CardContainer(0, 0, 300, 320); - - VerticalLayout layout = new VerticalLayout(300, 320); - root.setBody(layout); - layout.setAlignment(Pos.TOP_CENTER); - layout.setSpacing(25); - - // Use parentView's userIconPath - File currentIconPath = userEditView.getUserIconPath(); - if (currentIconPath == null || !currentIconPath.exists() || currentIconPath.isDirectory()) { - currentIconPath = new File(App.getAppDirectory(), "icons/character.png"); - } - - ImageLoader loader = new ImageLoader(currentIconPath); - loader.setWidth(256); - loader.setHeight(256); - - image = new ImageOverlay(loader); - image.setFitWidth(256); - image.setFitHeight(256); - image.setPreserveRatio(false); - - layout.addElement(image); - - TextOverlay upload = new TextOverlay("Click to upload image"); - upload.setTextFill(javafx.scene.paint.Color.WHITE); - upload.setUnderline(true); - layout.addElement(upload); - - root.onClick(event -> { - FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Select an image.", "*.img", "*.png", "*.webp", "*.jpg", "*.gif")); - if (appSettings.getImagesPath() != null && !appSettings.getImagesPath().isEmpty()) { - chooser.setInitialDirectory(new File(appSettings.getImagesPath())); - } - File selectedFile = chooser.showOpenDialog(App.window.getStage()); - if (selectedFile != null) { - userEditView.setUserIconPath(selectedFile); - appSettings.setImagesPath(selectedFile.getParent()); - userEditView.setUserIconPath(selectedFile.getAbsoluteFile()); - - ImageLoader imageLoader = new ImageLoader(selectedFile); - imageLoader.setWidth(256); - imageLoader.setHeight(256); - - image.setImage(imageLoader); - } - }); - - return root; - } - - private VerticalLayout buildUserInput() { - VerticalLayout root = new VerticalLayout(250, 150); - root.setAlignment(Pos.BASELINE_CENTER); - root.setMaxSize(250, 200); - root.setSpacing(10); - - userIdInput = new TextFieldOverlay(userEditView.getUserId(), 0, 0, 200, 50); - userIdInput.setEnabled(true); - userIdInput.setHintText("Unique Identifier"); - userIdInput.onInputSetEvent(event -> { - userEditView.setUserId(event.getInput()); - }); - if (userEditView.getUser() != null) { - userIdInput.setEnabled(false); - } - root.addElement(userIdInput); - - userDisplayNameInput = new TextFieldOverlay(userEditView.getUserDisplay(), 0, 0, 200, 50); - userDisplayNameInput.setEnabled(true); - userDisplayNameInput.setHintText("Display Name"); - userDisplayNameInput.onInputSetEvent(event -> { - userEditView.setUserDisplay(event.getInput()); - }); - 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; - } - - public TextFieldOverlay getUserIdInput() { - return userIdInput; - } - - public TextFieldOverlay getUserDisplayNameInput() { - return userDisplayNameInput; - } - - public RichTextAreaOverlay getUserDescription() { - return userDescription; - } -} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 8c8647ed..42635b48 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -22,6 +22,7 @@ requires org.apache.commons.lang3; requires org.apache.commons.compress; requires javafx.base; + requires com.github.oshi; opens me.piitex.app to javafx.fxml; exports me.piitex.app;