Electron: Add a folder picker for the models directory

This commit is contained in:
oobabooga
2026-05-13 20:34:07 -07:00
parent 2c254cb80d
commit 47fdee9cb1
7 changed files with 52 additions and 3 deletions

View File

@@ -207,7 +207,7 @@ jobs:
chmod +x textgen
fi
mv desktop/main.js desktop/package.json .
mv desktop/main.js desktop/preload.js desktop/package.json .
rm -rf desktop
# 5c. Restructure: textgen-VERSION/{textgen, user_data/, app/<everything else>}

View File

@@ -145,6 +145,10 @@ div.svelte-iyf88w {
padding: 0 !important;
}
.slim-textbox {
padding: 0 !important;
}
#download-label, #upload-label {
min-height: 0
}

View File

@@ -1,4 +1,4 @@
const { app, BrowserWindow, Menu, screen } = require("electron");
const { app, BrowserWindow, Menu, dialog, ipcMain, screen } = require("electron");
const { spawn } = require("child_process");
const path = require("path");
const fs = require("fs");
@@ -111,7 +111,12 @@ function createWindow(port) {
...bounds,
title: TITLE,
autoHideMenuBar: true,
webPreferences: { nodeIntegration: false, contextIsolation: true, spellcheck: false },
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: false,
contextIsolation: true,
spellcheck: false,
},
});
if (state && state.maximized) mainWindow.maximize();
mainWindow.webContents.on("context-menu", (_, params) => {
@@ -147,6 +152,11 @@ async function waitForPortAndOpen(port) {
}, 500);
}
ipcMain.handle("pick-directory", async () => {
const result = await dialog.showOpenDialog(mainWindow, { properties: ["openDirectory"] });
return result.canceled ? null : result.filePaths[0];
});
app.whenReady().then(() => {
serverProcess = spawn(python, ["server.py", "--portable", "--api", ...userArgs], {
cwd: baseDir,
@@ -159,6 +169,7 @@ app.whenReady().then(() => {
PYTHONUNBUFFERED: "1",
FORCE_COLOR: "1",
TERM: "xterm-256color",
TEXTGEN_ELECTRON: "1",
},
});
if (!isWin) serverProcess.unref();

View File

@@ -40,6 +40,7 @@ processing_message = ''
gradio = {}
persistent_interface_state = {}
need_restart = False
is_electron = os.environ.get('TEXTGEN_ELECTRON') == '1'
# Parser copied from https://github.com/vladmandic/automatic
parser = argparse.ArgumentParser(description="TextGen", conflict_handler='resolve', add_help=True, formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=55, indent_increment=2, width=200))

View File

@@ -248,6 +248,9 @@ def list_interface_input_elements():
'include_past_attachments',
]
if shared.is_electron:
elements += ['model_dir']
if not shared.args.portable:
# Image generation elements
elements += [
@@ -515,6 +518,9 @@ def setup_auto_save():
]
if shared.is_electron:
change_elements += ['model_dir']
if not shared.args.portable:
# Image generation tab (ui_image_generation.py)
change_elements += [

View File

@@ -1,3 +1,5 @@
from pathlib import Path
import gradio as gr
from modules import shared, ui, utils
@@ -11,6 +13,10 @@ def create_ui():
with gr.Column():
gr.Markdown("## Settings")
shared.gradio['toggle_dark_mode'] = gr.Button('Toggle light/dark theme 💡', elem_classes='refresh-button')
if shared.is_electron:
with gr.Row():
shared.gradio['model_dir'] = gr.Textbox(label='Models directory', value=shared.settings['model_dir'], scale=4, elem_classes='slim-textbox')
shared.gradio['model_dir_browse'] = gr.Button('Browse', elem_classes=['refresh-button', 'refresh-button-medium'])
shared.gradio['show_two_notebook_columns'] = gr.Checkbox(label='Show two columns in the Notebook tab', value=shared.settings['show_two_notebook_columns'])
shared.gradio['paste_to_attachment'] = gr.Checkbox(label='Turn long pasted text into attachments in the Chat tab', value=shared.settings['paste_to_attachment'], elem_id='paste_to_attachment')
shared.gradio['include_past_attachments'] = gr.Checkbox(label='Include attachments/search results from previous messages in the chat prompt', value=shared.settings['include_past_attachments'])
@@ -36,6 +42,13 @@ def create_ui():
lambda x: 'dark' if x == 'light' else 'light', gradio('theme_state'), gradio('theme_state')).then(
None, None, None, js=f'() => {{{ui.dark_theme_js}; toggleDarkMode(); localStorage.setItem("theme", document.body.classList.contains("dark") ? "dark" : "light")}}')
if shared.is_electron:
shared.gradio['model_dir_browse'].click(
None, gradio('model_dir'), gradio('model_dir'),
js='async (current) => { const p = await window.electronAPI.pickDirectory(); return p === null ? current : p; }')
shared.gradio['model_dir'].change(apply_model_dir, gradio('model_dir'), None, show_progress=False)
shared.gradio['show_two_notebook_columns'].change(
handle_default_to_notebook_change,
gradio('show_two_notebook_columns', 'textbox-default', 'output_textbox', 'prompt_menu-default', 'textbox-notebook', 'prompt_menu-notebook'),
@@ -86,6 +99,12 @@ def handle_default_to_notebook_change(show_two_columns, default_input, default_o
]
def apply_model_dir(value):
shared.args.model_dir = value
if Path(value).is_dir():
shared.user_config = shared.load_user_config()
def set_interface_arguments(extensions, bool_active):
shared.args.extensions = extensions

View File

@@ -261,6 +261,10 @@ if __name__ == "__main__":
if has_mcp_config():
logger.warning(f"MCP stdio servers will be loaded from \"{shared.user_data_dir / 'mcp.json'}\"")
if shared.is_electron:
shared.settings['model_dir'] = shared.args.model_dir
shared.default_settings['model_dir'] = shared.args.model_dir
if settings_file is not None:
logger.info(f"Loading settings from \"{settings_file}\"")
with open(settings_file, 'r', encoding='utf-8') as f:
@@ -269,6 +273,10 @@ if __name__ == "__main__":
if new_settings:
shared.settings.update(new_settings)
if shared.is_electron:
shared.args.model_dir = shared.settings['model_dir']
shared.user_config = shared.load_user_config()
# Apply CLI overrides for image model settings (CLI flags take precedence over saved settings)
shared.apply_image_model_cli_overrides()