diff --git a/server.py b/server.py
index 6432f0c6..acb0e2ad 100644
--- a/server.py
+++ b/server.py
@@ -5,6 +5,8 @@
from flask import Response, stream_with_context
from waitress import serve
import webbrowser
+from tkinter import filedialog, Tk
+import tkinter as tk
import torch
@@ -272,6 +274,20 @@ def api_delete_session():
if verbose: print("->", result)
return json.dumps(result) + "\n"
+@app.route("/api/select_folder")
+def api_select_folder():
+ global api_lock, verbose
+ if verbose: print("/api/select_folder")
+ with api_lock:
+ root = Tk()
+ root.withdraw() # Hide the main window
+ root.attributes('-topmost', True) # Make sure dialog appears on top
+ folder = filedialog.askdirectory()
+ root.destroy()
+ result = {"result": "ok", "path": folder if folder else ""}
+ if verbose: print("->", result)
+ return json.dumps(result) + "\n"
+
@app.route("/api/remove_model", methods=['POST'])
def api_remove_model():
global api_lock, verbose
@@ -467,4 +483,3 @@ def api_cancel_notepad_generate():
print(f" -- Opening UI in default web browser")
serve(app, host = host, port = port, threads = 8)
-
diff --git a/static/controls.css b/static/controls.css
index 2abeeebb..e725f20e 100644
--- a/static/controls.css
+++ b/static/controls.css
@@ -1,6 +1,6 @@
.vflex_line {
- height-min: 32px;
+ min-height: 32px;
}
.checkbox {
diff --git a/static/controls.js b/static/controls.js
index 1bbd7677..9b9a556b 100644
--- a/static/controls.js
+++ b/static/controls.js
@@ -39,6 +39,28 @@ export class LabelTextbox {
if (placeholder) this.tb.placeholder = placeholder;
this.tb.spellcheck = false;
this.tb.value = this.data[this.data_id] ? this.data[this.data_id] : "";
+
+ // Create hidden span to measure text width
+ this.measureSpan = document.createElement("span");
+ this.measureSpan.style.visibility = "hidden";
+ this.measureSpan.style.position = "absolute";
+ this.measureSpan.style.whiteSpace = "pre";
+ // Copy font styles from input to span for accurate measurement
+ this.measureSpan.style.font = window.getComputedStyle(this.tb).font;
+ document.body.appendChild(this.measureSpan);
+
+ // Function to update input width based on content
+ const updateWidth = () => {
+ this.measureSpan.textContent = this.tb.value || this.tb.placeholder;
+ const width = this.measureSpan.offsetWidth;
+ this.tb.style.width = (width + 20) + 'px'; // Add padding
+ };
+
+ // Update width on input
+ this.tb.addEventListener("input", updateWidth);
+
+ // Initial width update
+ updateWidth();
this.tb.addEventListener("focus", () => {
//console.log(this.data[this.data_id]);
@@ -98,6 +120,10 @@ export class LabelTextbox {
refresh() {
let v = this.data[this.data_id] ? this.data[this.data_id] : null;
this.tb.value = v;
+ // Update width when refreshing
+ this.measureSpan.textContent = this.tb.value || this.tb.placeholder;
+ const width = this.measureSpan.offsetWidth;
+ this.tb.style.width = (width + 20) + 'px';
this.refreshCB();
}
@@ -249,6 +275,10 @@ export class LabelNumbox extends LabelTextbox {
this.min = min;
this.max = max;
this.decimals = decimals;
+
+ // Override dynamic width calculation for numeric inputs
+ this.measureSpan = null; // Remove the span used for width measurement
+ this.tb.style.width = null; // Remove any inline width style
}
interpret(value) {
@@ -263,6 +293,11 @@ export class LabelNumbox extends LabelTextbox {
refresh() {
this.tb.value = this.data[this.data_id].toFixed(this.decimals);
this.refreshCB();
+ // Override parent's refresh method to prevent dynamic width calculation
+ if (this.measureSpan) {
+ document.body.removeChild(this.measureSpan);
+ this.measureSpan = null;
+ }
}
}
diff --git a/static/models.css b/static/models.css
index a65f7503..14357a76 100644
--- a/static/models.css
+++ b/static/models.css
@@ -59,7 +59,6 @@
padding: 20px;
height: calc(100vh - 40px);
overflow-y: auto;
- flex-grow: 1;
}
.model-view-text {
@@ -138,8 +137,42 @@
color: var(--textcolor-dim);
}
-.model-view-item-textbox.wide {
+.folder-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ padding: 4px;
+ margin-right: 8px;
+ border-radius: 4px;
+}
+
+.folder-button:hover {
+ background-color: var(--background-color-active);
+}
+
+.folder-button:hover svg {
+ filter: brightness(1.3);
+}
+
+.folder-button svg {
+ width: 24px;
+ height: 24px;
+ fill: var(--textcolor-head);
+}
+
+.model-directory-container {
+ display: flex;
+ align-items: center;
flex-grow: 1;
+ min-width: 0; /* Allow container to shrink below content size */
+ overflow: hidden; /* Prevent overflow */
+}
+
+.model-view-item-textbox.wide {
+ flex: 0 1 auto; /* Allow textbox to shrink */
+ min-width: 100px; /* Minimum width */
+ max-width: calc(100% - 40px); /* Maximum width accounting for folder icon */
}
.model-view-item-textbox.shortright {
@@ -181,6 +214,3 @@
flex-grow: 1;
justify-content: end;
}
-
-
-
diff --git a/static/models.js b/static/models.js
index b5c646de..a8cdc20b 100644
--- a/static/models.js
+++ b/static/models.js
@@ -311,8 +311,48 @@ export class ModelView {
this.element.appendChild(util.newDiv(null, "model-view-text divider", ""));
this.element.appendChild(util.newDiv(null, "model-view-text spacer", ""));
- this.tb_model_directory = new controls.LabelTextbox("model-view-item-left", "Model directory", "model-view-item-textbox wide", "~/models/my_model/", this.modelInfo, "model_directory", null, () => { this.send() } );
- this.element.appendChild(this.tb_model_directory.element);
+// Create container for model directory input and folder button
+let modelDirContainer = util.newDiv(null, "model-directory-container");
+this.element.appendChild(modelDirContainer);
+
+ // Function to normalize path separators based on platform
+ const normalizePath = (path) => {
+ const isWindows = navigator.platform.toLowerCase().includes('win');
+ if (isWindows) {
+ return path.replace(/\//g, '\\');
+ }
+ return path;
+ };
+
+ // Create a function to handle folder selection
+ const selectFolder = async () => {
+ try {
+ const response = await fetch("/api/select_folder");
+ const data = await response.json();
+ if (data.result === "ok" && data.path) {
+ // Update the model directory textbox with the selected path
+ this.modelInfo.model_directory = normalizePath(data.path);
+ this.tb_model_directory.refresh();
+ this.send();
+ }
+ } catch (err) {
+ console.log('Folder selection error:', err);
+ }
+ };
+
+// Create model directory textbox
+this.tb_model_directory = new controls.LabelTextbox("model-view-item-left", "Model directory", "model-view-item-textbox wide", "~/models/my_model/", this.modelInfo, "model_directory", null, () => { this.send() } );
+modelDirContainer.appendChild(this.tb_model_directory.label);
+
+// Create folder button
+let folderButton = util.newDiv(null, "folder-button");
+folderButton.appendChild(util.newIcon("folder-icon"));
+folderButton.addEventListener("click", () => {
+ selectFolder();
+});
+modelDirContainer.appendChild(folderButton);
+
+modelDirContainer.appendChild(this.tb_model_directory.tb);
this.element_model = util.newHFlex();
this.element_model_error = util.newHFlex();
@@ -380,8 +420,39 @@ export class ModelView {
//this.element_model.appendChild(util.newDiv(null, "model-view-text spacer", ""));
this.element_draft_model.appendChild(util.newDiv(null, "model-view-text spacer", ""));
+ // Create container for draft model directory input and folder button
+ let draftModelDirContainer = util.newDiv(null, "model-directory-container");
+ this.element_draft_model.appendChild(draftModelDirContainer);
+
+ // Create a function to handle draft folder selection
+ const selectDraftFolder = async () => {
+ try {
+ const response = await fetch("/api/select_folder");
+ const data = await response.json();
+ if (data.result === "ok" && data.path) {
+ // Update the draft model directory textbox with the selected path
+ this.modelInfo.draft_model_directory = normalizePath(data.path);
+ this.tb_draft_model_directory.refresh();
+ this.send();
+ }
+ } catch (err) {
+ console.log('Folder selection error:', err);
+ }
+ };
+
+ // Create draft model directory textbox
this.tb_draft_model_directory = new controls.LabelTextbox("model-view-item-left", "Draft model directory", "model-view-item-textbox wide", "~/models/my_draft_model/", this.modelInfo, "draft_model_directory", null, () => { this.send() } );
- this.element_draft_model.appendChild(this.tb_draft_model_directory.element);
+ draftModelDirContainer.appendChild(this.tb_draft_model_directory.label);
+
+ // Create folder button
+ let draftFolderButton = util.newDiv(null, "folder-button");
+ draftFolderButton.appendChild(util.newIcon("folder-icon"));
+ draftFolderButton.addEventListener("click", () => {
+ selectDraftFolder();
+ });
+ draftModelDirContainer.appendChild(draftFolderButton);
+
+ draftModelDirContainer.appendChild(this.tb_draft_model_directory.tb);
this.element_draft_model_s = util.newHFlex();
this.element_draft_model_error = util.newHFlex();
diff --git a/templates/svg_icons.html b/templates/svg_icons.html
index 79f46b13..0bae703a 100644
--- a/templates/svg_icons.html
+++ b/templates/svg_icons.html
@@ -64,6 +64,14 @@
+
+