Skip to content

Commit aeb49a4

Browse files
feat: OneTrainerWeb — Electron + React frontend with FastAPI bridge
Replaces the customtkinter desktop GUI with an Electron + React web frontend, connected to the existing Python training backend via a FastAPI bridge. Existing backend code (modules/, scripts/, training_presets/) is unchanged — the bridge communicates only through the TrainCallbacks / TrainCommands interface. Adds: - web/gui/ Electron + React + Vite + Tailwind (TypeScript strict) - web/backend/ FastAPI bridge importing existing modules/ directly - web/scripts/ AST-based generators that emit TypeScript types and UI schema from the Python config/enum/CTk sources - start-web-ui.{bat,sh}, install/update script integration - .github/workflows/web-ui-lint.yml (ESLint + Prettier check on PRs) - .pre-commit-config.yaml hooks: ESLint shim + hardcoded-options guard Also folds in a follow-up pass porting general code-quality improvements (bugfixes, refactors, small UX additions) from a parallel fork's more advanced branch, while deliberately excluding that branch's new features (job queue, DPO training, cloud/RunPod, validation checker, OFT merging, distillation, bucket analysis) and the field-override binding system those features depend on: - Drop the allowed_roots allow-list from validate_path(); presets.py sandboxes its own paths via base_match() instead. - main.py reuses paths.py's PROJECT_ROOT instead of duplicating the project-root/sys.path bootstrap. - Add real tensorboard process management (/tensorboard/launch, /stop). - Add /system/cache/status, /system/cache/clear, and Scalene-backed profiling endpoints (dump-stacks, toggle). - Simplify custom-sample field copying to sample_config.from_dict(...). - Let /concepts GET/PUT target a path override, and add POST /concepts/save-caption for the mask editor's caption panel. - Let /tools/convert accept per-conversion quantization overrides. - start_training() accepts a pre-built config; fix the always-on tensorboard subprocess handle not being a class attribute (a fresh TrainerService instance could otherwise lose track of an already-running subprocess). - _safe_literal_eval() in generate_types.py fixes optimizer defaults using float('inf') silently extracting as {} (ast.literal_eval rejects Call nodes); the OptimizerKeyDetail type union is now derived from actual data. - _sanitize_default() in generate_ui_schema.py encodes Infinity/-Infinity/NaN as strings so the browser's JSON.parse doesn't choke on them. - FormEntry no longer clobbers what the user is typing when the parsed value echoes back through config (e.g. "-0" -> "0" mid-keystroke). - ModalBase gains size="full" and closeOnEscape. - TopBar's preset dropdown is now a custom menu supporting inline deletion of user presets. - SampleParamsModal gains prompt/negative-prompt fields and a seed reroll. - MaskEditorPage gains a caption editor panel with arrow-key navigation. - BottomBar gains a tensorboard launch button. - Fixed default-value/config bugs: ConceptsPage's resolution_override ("" -> "512"), and SamplingPage's hardcoded image-format dropdown now sources from the generated ImageFormatValues enum. - Vite's dev server already falls back off a busy port 5173, but Electron's main process had no way to discover which port it landed on; added a Vite plugin that writes the resolved port to .vite-port and an Electron-side poller that reads it, replacing the hardcoded dev-server URL and a wait-on-a-fixed-port step that would have hung forever if 5173 was taken. Verified with a standalone script (web/gui/scripts/verify-port-fallback.mjs, run via `npm run verify:port-fallback`). Claude-Session: https://claude.ai/code/session_019pHqnjipbR4G8eGRQcemTq
1 parent 07254ad commit aeb49a4

174 files changed

Lines changed: 25423 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/web-ui-lint.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: web-ui-lint
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "web/gui/**"
7+
- ".github/workflows/web-ui-lint.yml"
8+
push:
9+
branches: [master, Webui]
10+
paths:
11+
- "web/gui/**"
12+
13+
jobs:
14+
lint:
15+
runs-on: ubuntu-latest
16+
defaults:
17+
run:
18+
working-directory: web/gui
19+
steps:
20+
- uses: actions/checkout@v4
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: "20"
24+
- run: npm install --no-audit --no-fund
25+
- run: npm run lint
26+
- run: npm run format:check

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ debug*.py
2020
config.json
2121
secrets.json
2222
*.zip
23+
.env*
24+
!.env.example
2325

2426
# environments
2527
/.venv*
@@ -38,3 +40,14 @@ pixi.toml
3840
train.bat
3941
debug_report.log
4042
config_diff.txt
43+
44+
# Web UI
45+
web/gui/dist/
46+
web/gui/release/
47+
web/gui/node_modules/
48+
web/gui/public/ui-schema.json
49+
web/gui/src/renderer/types/generated/
50+
web/gui/.vite-port
51+
web/backend/__pycache__/
52+
web/backend/**/__pycache__/
53+
web/backend/generated/

.pre-commit-config.yaml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,25 @@ repos:
1616
- repo: https://github.com/astral-sh/ruff-pre-commit
1717
rev: v0.15.17
1818
hooks:
19-
# Run the Ruff linter, but not the formatter.
2019
- id: ruff
2120
args: ["--fix"]
2221
types_or: [ python, pyi, jupyter ]
2322

23+
- repo: local
24+
hooks:
25+
- id: eslint
26+
name: ESLint (frontend)
27+
entry: python -m web.scripts.run_eslint
28+
language: system
29+
files: '^web/gui/.*\.(ts|tsx)$'
30+
pass_filenames: false
31+
32+
- id: check-hardcoded-dropdown-options
33+
name: No hardcoded <Select options> outside types/generated/
34+
entry: python -m web.scripts.check_hardcoded_options
35+
language: system
36+
files: '^web/gui/src/renderer/.*\.(ts|tsx)$'
37+
pass_filenames: false
38+
2439
ci:
2540
autofix_prs: false

install.bat

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,38 @@ if errorlevel 1 (
273273
echo %GRN%CUDA is available.%RESET%
274274
)
275275

276+
rem 7) Generate UI schema and build web UI
277+
echo.
278+
echo %CYAN%Generating UI schema...%RESET%
279+
python -m web.scripts.generate_ui_schema
280+
if errorlevel 1 (
281+
echo %YEL%WARNING: UI schema generation failed. Using existing schema.%RESET%
282+
)
283+
284+
echo.
285+
where node >NUL 2>NUL
286+
if errorlevel 1 (
287+
echo %YEL%Node.js not found. Skipping web UI build.%RESET%
288+
echo To use the web UI, install Node.js from https://nodejs.org/ and re-run install.bat
289+
) else (
290+
echo %CYAN%Building web UI...%RESET%
291+
pushd web\gui
292+
call npm install
293+
if errorlevel 1 (
294+
echo %YEL%WARNING: npm install failed. Web UI will not be available.%RESET%
295+
popd
296+
goto :skip_web_build
297+
)
298+
call npm run build:electron
299+
if errorlevel 1 (
300+
echo %YEL%WARNING: Web UI build failed. Web UI will not be available.%RESET%
301+
) else (
302+
echo %GRN%Web UI built successfully.%RESET%
303+
)
304+
popd
305+
)
306+
:skip_web_build
307+
276308
echo.
277309
echo %GRN%**** Install successful^^! ****%RESET%
278310
echo.

lib.include.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,4 +444,39 @@ function prepare_runtime_environment {
444444
else
445445
install_requirements_in_active_env_if_necessary
446446
fi
447+
448+
# Regenerate the web UI schema and rebuild the Electron bundle when possible.
449+
print "Generating UI schema..."
450+
if ! run_python_in_active_env -m web.scripts.generate_ui_schema; then
451+
print_warning "UI schema generation failed. Using existing schema."
452+
fi
453+
454+
build_web_ui_if_available
455+
}
456+
457+
# Builds the Electron+React web UI when Node.js is available.
458+
# During "install" runs it builds unconditionally; otherwise only rebuilds
459+
# when a previously built bundle is detected, so regular headless users
460+
# are not forced to install Node.js.
461+
function build_web_ui_if_available {
462+
if ! command -v node &> /dev/null; then
463+
print "Node.js not found. Skipping web UI build."
464+
print "To use the web UI, install Node.js from https://nodejs.org/"
465+
return 0
466+
fi
467+
468+
local gui_dir="${SCRIPT_DIR}/web/gui"
469+
local dist_marker="${gui_dir}/dist/main/main/index.cjs"
470+
471+
if [[ -f "${dist_marker}" ]] || [[ "${1:-}" == "install" ]]; then
472+
print "Building web UI..."
473+
(
474+
cd "${gui_dir}"
475+
npm install || { print_warning "npm install failed. Web UI may be stale."; return 0; }
476+
npm run build:electron || { print_warning "Web UI build failed. Web UI may be stale."; return 0; }
477+
print "Web UI built successfully."
478+
)
479+
else
480+
print "Web UI not previously built. Run start-web-ui.sh to build and launch."
481+
fi
447482
}

start-web-ui.bat

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
@echo off
2+
cd /d "%~dp0"
3+
4+
set "OT_DEV="
5+
for %%a in (%*) do (
6+
if "%%a"=="--dev" set "OT_DEV=1"
7+
)
8+
9+
if not exist "scripts\train_ui.py" (
10+
echo Error: train_ui.py does not exist, you have done something very wrong. Reclone the repository.
11+
goto :end
12+
)
13+
14+
if not defined PYTHON (
15+
where python >NUL 2>NUL
16+
if errorlevel 1 (
17+
echo Error: Python is not installed or not in PATH
18+
goto :end
19+
)
20+
set PYTHON=python
21+
)
22+
if not defined VENV_DIR (set "VENV_DIR=%~dp0venv")
23+
24+
:check_venv
25+
dir "%VENV_DIR%" > NUL 2> NUL
26+
if not errorlevel 1 goto :activate_venv
27+
echo venv not found, please run install.bat first
28+
goto :end
29+
30+
:activate_venv
31+
echo activating venv %VENV_DIR%
32+
if not exist "%VENV_DIR%\Scripts\python.exe" (
33+
echo Error: Python executable not found in virtual environment
34+
goto :end
35+
)
36+
set PYTHON="%VENV_DIR%\Scripts\python.exe" -X utf8
37+
echo Using Python %PYTHON%
38+
39+
REM Disable Xet (buggy) - https://github.com/Nerogar/OneTrainer/issues/949
40+
if not defined HF_HUB_DISABLE_XET (
41+
set "HF_HUB_DISABLE_XET=1"
42+
)
43+
44+
:check_python_version
45+
%PYTHON% --version
46+
if errorlevel 1 (
47+
echo Error: Failed to get Python version
48+
goto :end
49+
)
50+
%PYTHON% "%~dp0scripts\util\version_check.py" 3.10 3.14 2>&1
51+
if errorlevel 1 (
52+
goto :end
53+
)
54+
55+
:check_node
56+
where node >NUL 2>NUL
57+
if errorlevel 1 (
58+
echo Error: Node.js is not installed or not in PATH
59+
echo Please install Node.js from https://nodejs.org/
60+
goto :end
61+
)
62+
63+
:check_gui_built
64+
if not exist "web\gui\dist\main\main\index.cjs" (
65+
echo Error: Web GUI has not been built yet.
66+
echo Please run install.bat or update.bat first to build the web UI.
67+
goto :end
68+
)
69+
70+
:launch
71+
echo Starting OneTrainer Web UI...
72+
cd web\gui
73+
call npx electron .
74+
cd ..\..
75+
if errorlevel 1 (
76+
echo Error: Web UI exited with code %ERRORLEVEL%
77+
)
78+
79+
:end
80+
pause

start-web-ui.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
source "${BASH_SOURCE[0]%/*}/lib.include.sh"
6+
7+
for arg in "$@"; do
8+
if [[ "$arg" == "--dev" ]]; then
9+
export OT_DEV=1
10+
fi
11+
done
12+
13+
# Xet is buggy - https://github.com/Nerogar/OneTrainer/issues/949
14+
if [[ -z "${HF_HUB_DISABLE_XET+x}" ]]; then
15+
export HF_HUB_DISABLE_XET=1
16+
fi
17+
18+
prepare_runtime_environment
19+
20+
if ! command -v node &> /dev/null; then
21+
echo "Error: Node.js is not installed or not in PATH"
22+
echo "Please install Node.js from https://nodejs.org/"
23+
exit 1
24+
fi
25+
26+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27+
28+
if [[ ! -f "$SCRIPT_DIR/web/gui/dist/main/main/index.cjs" ]]; then
29+
echo "Error: Web GUI has not been built yet."
30+
echo "Please run install.sh or update.sh first to build the web UI."
31+
exit 1
32+
fi
33+
34+
echo "Starting OneTrainer Web UI..."
35+
cd "$SCRIPT_DIR/web/gui"
36+
npx electron .

update.bat

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,41 @@ if errorlevel 1 (
141141
goto :end_error
142142
)
143143

144+
REM Regenerate UI schema and rebuild web UI if previously built
145+
echo.
146+
echo Generating UI schema...
147+
"%PYTHON%" -m web.scripts.generate_ui_schema
148+
if errorlevel 1 (
149+
echo WARNING: UI schema generation failed. Using existing schema.
150+
)
151+
REM Reset errorlevel so schema failure does not affect exit code
152+
cmd /c "exit /b 0"
153+
154+
where node >NUL 2>NUL
155+
if errorlevel 1 (
156+
echo Node.js not found. Skipping web UI rebuild.
157+
) else (
158+
if exist "web\gui\dist\main\main\index.cjs" (
159+
echo Rebuilding web UI...
160+
pushd web\gui
161+
call npm install
162+
if errorlevel 1 (
163+
echo WARNING: npm install failed. Web UI may be stale.
164+
popd
165+
goto :end_success
166+
)
167+
call npm run build:electron
168+
if errorlevel 1 (
169+
echo WARNING: Web UI rebuild failed. Web UI may be stale.
170+
) else (
171+
echo Web UI rebuilt successfully.
172+
)
173+
popd
174+
) else (
175+
echo Web UI not previously built. Run start-web-ui.bat to launch.
176+
)
177+
)
178+
144179
:end_success
145180
echo.
146181
echo ***********

0 commit comments

Comments
 (0)