From aa5ffea067851d21fbf0e431c7873af991cb97f1 Mon Sep 17 00:00:00 2001 From: dualchimerra Date: Sun, 27 Sep 2026 12:09:38 +0200 Subject: [PATCH] chore: drop remaining Chinese-locale mentions, ignore docs/research - Reword cp936/gbk/"Chinese" comments to neutral non-UTF-8 codepage wording - Remove unused AnnouncementPost type (zh/en fields) - Fix stale comments in Settings.tsx and TagsInput.tsx - Gitignore docs/research/ (local notes with private training names) Co-Authored-By: Claude Opus 5.5 --- .gitignore | 3 + CHANGELOG.md | 2 +- docs/announcements/2026-05-09-v0.5.0.md | 2 +- docs/design/preprocess-train-scope-plan.md | 2 +- docs/design/queue-pause-spike-report.md | 2 +- runtime/anima_train.py | 2 +- studio.bat | 432 +++++++++---------- studio/domain/errors.py | 4 +- studio/infrastructure/logging.py | 2 +- studio/services/runtime/flash_attention.py | 2 +- studio/supervisor/core.py | 2 +- studio/web/src/api/client.ts | 10 - studio/web/src/components/TagsInput.tsx | 2 +- studio/web/src/pages/project/steps/Train.tsx | 2 +- studio/web/src/pages/tools/Settings.tsx | 2 +- studio/workers/_base.py | 4 +- tests/test_studio_bat_ascii.py | 2 +- tools/download_models.py | 2 +- tools/launcher.py | 4 +- tools/mask_check.py | 2 +- tools/mem_probe.py | 2 +- 21 files changed, 240 insertions(+), 247 deletions(-) diff --git a/.gitignore b/.gitignore index 1aae15b..3d24490 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,6 @@ _ui_backup/ # The whole directory can be deleted at any time; deleting it just means # it gets re-downloaded next time. /.cache/ + +# Local research notes (contain private training names) +/docs/research/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fb145f..1813d14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1613,7 +1613,7 @@ Test image generation + prior generation + Setup rewrite + Settings split + CLTa - Backend: `versions.list_project_state_ckpts()` / `list_project_lora_ckpts()` + `/api/projects/{pid}/state_ckpts` / `/lora_ckpts` endpoints - Fixes the root UX problem: previously you had to dig five levels deep from REPO_ROOT to `output/training_state_step*.pt` just to resume - **Setup rewrite: GPU-aware torch on first install + venv stale check + --reinstall as a lifeline** - - `studio.bat` is pure ASCII now (no more crashes under cp936 cmd.exe) + a test as backstop + - `studio.bat` is pure ASCII now (no more crashes under non-UTF-8 cmd.exe) + a test as backstop - bootstrap: prefers `py -3` on Windows, iterates version checks on Linux - venv stale check + `--reinstall` flag (environment lifeline) - GPU-aware torch on first install; big warning when a CPU-only build gets installed by mistake diff --git a/docs/announcements/2026-05-09-v0.5.0.md b/docs/announcements/2026-05-09-v0.5.0.md index 8a74a96..ae80787 100644 --- a/docs/announcements/2026-05-09-v0.5.0.md +++ b/docs/announcements/2026-05-09-v0.5.0.md @@ -26,7 +26,7 @@ Test image generation + prior generation + Setup rewrite + Settings split + CLTa - Backend: `versions.list_project_state_ckpts()` / `list_project_lora_ckpts()` + `/api/projects/{pid}/state_ckpts` / `/lora_ckpts` endpoints - Fixes the root UX problem: previously you had to dig five levels deep from REPO_ROOT to `output/training_state_step*.pt` just to resume - **Setup rewrite: GPU-aware torch on first install + venv stale check + --reinstall as a lifeline** - - `studio.bat` is pure ASCII now (no more crashes under cp936 cmd.exe) + a test as backstop + - `studio.bat` is pure ASCII now (no more crashes under non-UTF-8 cmd.exe) + a test as backstop - bootstrap: prefers `py -3` on Windows, iterates version checks on Linux - venv stale check + `--reinstall` flag (environment lifeline) - GPU-aware torch on first install; big warning when a CPU-only build gets installed by mistake diff --git a/docs/design/preprocess-train-scope-plan.md b/docs/design/preprocess-train-scope-plan.md index 198863a..852dd2d 100644 --- a/docs/design/preprocess-train-scope-plan.md +++ b/docs/design/preprocess-train-scope-plan.md @@ -893,5 +893,5 @@ moves from project level to version/train/. Module boundaries are unchanged. - `studio/api/routers/projects/curation.py:117-186,271,342` — thumb endpoint bucket=train + 2 duplicates endpoint URLs - `studio/web/src/components/Sidebar.tsx:11-29,266-274,472` — STEP order / phase mapping / project-scope regex - `studio/web/src/api/client.ts` — `VersionPhase` / `PHASE_ORDER` / `PHASE_SKIPPABLE` -- `studio/web/src/i18n/locales/{zh,en}.json` — "(optional)" copy + idx numbering +- `studio/web/src/i18n/locales/{ru,en}.json` — "(optional)" copy + idx numbering - `studio/web/src/pages/project/steps/Preprocess*.tsx` — data contract + 4 sub-pages diff --git a/docs/design/queue-pause-spike-report.md b/docs/design/queue-pause-spike-report.md index 32965bf..da219f2 100644 --- a/docs/design/queue-pause-spike-report.md +++ b/docs/design/queue-pause-spike-report.md @@ -79,7 +79,7 @@ was never emitted." ### Parent stdout's own encoding A minor pitfall unrelated to the main project: on Windows, the shell's default -codepage (cp936 / cp932) raises `UnicodeEncodeError` when encoding non-ASCII +ANSI codepage raises `UnicodeEncodeError` when encoding non-ASCII `print` output. `env["PYTHONIOENCODING"] = "utf-8"` only takes effect for the **child process** — the parent itself needs `sys.stdout.reconfigure(encoding="utf-8")`. The real supervisor writes to diff --git a/runtime/anima_train.py b/runtime/anima_train.py index 407d910..cce8d74 100644 --- a/runtime/anima_train.py +++ b/runtime/anima_train.py @@ -40,7 +40,7 @@ if _ps not in sys.path: sys.path.insert(0, _ps) -# The Windows console defaults to cp936; logging / print writing non-ASCII text +# The Windows console defaults to the ANSI codepage; logging / print writing non-ASCII text # raises UnicodeEncodeError, and the default handler's errors='backslashreplace' # turns it into \uXXXX escapes -- that's the source of the garbled text seen in task logs. # Force stdout/stderr to UTF-8 + replace so non-ASCII text / emoji always print directly. diff --git a/studio.bat b/studio.bat index 3e19acb..b4d7af8 100644 --- a/studio.bat +++ b/studio.bat @@ -1,216 +1,216 @@ -@echo off -chcp 65001 >nul -REM AnimaStudio Windows shortcut -- forwards to: python -m studio -REM Usage: -REM .\studio.bat same as: python -m studio run -REM .\studio.bat --reinstall DELETE venv\ and rebuild (studio_data\ kept) -REM .\studio.bat --torch=cu128 force GPU torch (cu128/cu126/cu124/cu118/cpu) -REM .\studio.bat dev frontend + backend dev mode -REM .\studio.bat dev --fe-port 5174 Vite on port 5174 -REM .\studio.bat dev --port 8766 backend on port 8766 -REM .\studio.bat build build frontend only -REM .\studio.bat test run pytest + vitest -REM -REM Note: PowerShell needs the `.\` prefix; cmd.exe accepts either. -REM -REM NOTE: This file MUST stay pure ASCII. cmd.exe parses .bat files with the -REM system ANSI codepage BEFORE `chcp 65001` takes effect, so any non-ASCII -REM byte breaks line parsing on Japanese (cp932), Chinese (cp936), etc. hosts. - -setlocal enabledelayedexpansion -cd /d "%~dp0" - -REM Force Python to UTF-8 stdout/stderr so prints with non-ASCII (Chinese) -REM don't crash on non-UTF-8 system locales (e.g. cp932 Japanese). -set PYTHONUTF8=1 -set PYTHONIOENCODING=utf-8 - -REM Keep pip's wheel cache inside the project folder (several GB -- the CUDA -REM torch wheel alone is 2-3GB, and the default is %LOCALAPPDATA%\pip\Cache on -REM the system drive). Everything else is redirected by -REM studio\infrastructure\local_cache.py, which is the authority for the full -REM list; only pip has to be set here, because the first-run bootstrap below -REM shells out to pip before any of our Python code runs. ALS_SYSTEM_CACHES=1 -REM opts out, and an existing PIP_CACHE_DIR is never overridden. -if not defined ALS_SYSTEM_CACHES ( - if not defined PIP_CACHE_DIR ( - set PIP_CACHE_DIR=%~dp0.cache\pip - if not exist "%~dp0.cache\pip" mkdir "%~dp0.cache\pip" 2>nul - ) -) - -REM Parse our flags; collect remaining args to forward to Python. -set REINSTALL=0 -set TORCH_TAG= -set PASSTHROUGH= -:argloop -if "%~1"=="" goto argdone -if /i "%~1"=="--reinstall" ( - set REINSTALL=1 -) else ( - REM Check for --torch= prefix - set _ARG=%~1 - if "!_ARG:~0,8!"=="--torch=" ( - set TORCH_TAG=!_ARG:~8! - set PASSTHROUGH=!PASSTHROUGH! --torch !TORCH_TAG! - ) else ( - set PASSTHROUGH=!PASSTHROUGH! %1 - ) -) -shift -goto argloop -:argdone - -set REQ_MARKER=venv\.studio-requirements.sha256 - -REM --reinstall: nuke venv before detection. studio_data\ is untouched. -if "%REINSTALL%"=="1" ( - if exist venv ( - echo [studio] --reinstall: venv\ will be DELETED and rebuilt. - echo [studio] - studio_data\ ^(your projects + LoRA weights^) is NOT touched - echo [studio] - any user-installed pip packages outside requirements.txt will be lost - set /p ANS="Continue? [y/N] " - if /i not "!ANS!"=="y" ( - echo [studio] --reinstall aborted - exit /b 0 - ) - echo [studio] removing venv\... - rmdir /s /q venv || (echo studio.bat: failed to remove venv 1>&2 & goto :fail) - ) -) - -if exist "venv\Scripts\python.exe" ( - set PYTHON=venv\Scripts\python.exe - REM PR-S0: warn if existing venv is < 3.10 ^(user can `--reinstall` to fix^) - venv\Scripts\python.exe -c "import sys;sys.exit(0 if sys.version_info>=(3,10) else 1)" >nul 2>nul - if errorlevel 1 ( - echo [studio] WARNING: venv\ uses Python ^< 3.10; some deps may fail to install. 1>&2 - echo [studio] consider .\studio.bat --reinstall to recreate with a newer Python. 1>&2 - ) -) else if exist ".venv\Scripts\python.exe" ( - set PYTHON=.venv\Scripts\python.exe - .venv\Scripts\python.exe -c "import sys;sys.exit(0 if sys.version_info>=(3,10) else 1)" >nul 2>nul - if errorlevel 1 ( - echo [studio] WARNING: .venv\ uses Python ^< 3.10; some deps may fail to install. 1>&2 - echo [studio] consider .\studio.bat --reinstall to recreate with a newer Python. 1>&2 - ) -) else ( - REM PR-S0: prefer `py -3` over `python`. Many Windows users keep an old - REM system `python` on PATH (3.9 etc) for legacy compat and a newer Python - REM via the `py` launcher (3.13 default). `py -3` picks the highest 3.x - REM installed via python.org installers. Fall back to `python` for users - REM without the launcher (uv / conda / scoop installs). - set BOOTSTRAP_PY= - where py >nul 2>nul - if not errorlevel 1 set BOOTSTRAP_PY=py -3 - if not defined BOOTSTRAP_PY ( - where python >nul 2>nul - if errorlevel 1 ( - echo studio.bat: neither `py` nor `python` found on PATH. Install Python 3.10+ from https://www.python.org/. 1>&2 - goto :fail - ) - set BOOTSTRAP_PY=python - ) - REM Version check: warn if < 3.10 ^(README requires 3.10+; some deps will fail to install on older^) - !BOOTSTRAP_PY! -c "import sys;sys.exit(0 if sys.version_info>=(3,10) else 1)" >nul 2>nul - if errorlevel 1 ( - echo [studio] WARNING: bootstrap Python is older than 3.10; some deps may fail to install. 1>&2 - echo [studio] If you have a newer Python installed, set PY_PYTHON=3.13 ^(or your version^) and retry. 1>&2 - ) - echo [studio] No venv detected. Creating venv\ via `!BOOTSTRAP_PY!` -- first run may take a few minutes... - !BOOTSTRAP_PY! -m venv venv || (echo studio.bat: failed to create venv 1>&2 & goto :fail) - set PYTHON=venv\Scripts\python.exe - - !PYTHON! -m pip install --upgrade pip -i https://mirrors.cloud.tencent.com/pypi/simple/ || (echo studio.bat: failed to upgrade pip 1>&2 & goto :fail) - - REM GPU-aware torch first install (PR-S1a). Without this, requirements.txt's - REM bare `torch>=2.0.0` makes pip pull the CPU wheel. Installing CUDA torch - REM from PyTorch's index FIRST satisfies the constraint, pip won't replace. - REM --torch= overrides auto-detection (useful on CPU-only rentals). - if defined TORCH_TAG ( - set TORCH_INDEX=https://download.pytorch.org/whl/!TORCH_TAG! - echo [studio] setup: --torch=!TORCH_TAG! specified; installing torch from !TORCH_INDEX! - !PYTHON! -m pip install torch torchvision --index-url !TORCH_INDEX! - if errorlevel 1 ( - echo [studio] setup: forced torch install failed; will fall back to PyPI default in requirements.txt - ) - ) else ( - set TORCH_INDEX= - for /f "delims=" %%i in ('!PYTHON! tools\select_torch_index.py 2^>nul') do set TORCH_INDEX=%%i - if defined TORCH_INDEX ( - echo [studio] setup: NVIDIA GPU detected; installing torch from !TORCH_INDEX! - !PYTHON! -m pip install torch torchvision --index-url !TORCH_INDEX! - if errorlevel 1 ( - echo [studio] setup: CUDA torch install failed; will fall back to PyPI default in requirements.txt - echo [studio] setup: you can fix manually later via Studio Settings ^> PyTorch ^> Reinstall - ) - ) - ) - - if exist requirements.txt ( - echo [studio] Installing Python dependencies -- will retry via Tencent mirror if slow... - !PYTHON! -m pip install -r requirements.txt - if errorlevel 1 ( - echo [studio] pip install failed, retrying via Tencent mirror... - !PYTHON! -m pip install -r requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple/ || (echo studio.bat: pip install failed 1>&2 & goto :fail) - ) - ) else ( - echo studio.bat: requirements.txt not found, skipping dependency install 1>&2 - ) - REM PR-S1b: write hash marker after fresh install - !PYTHON! tools\check_requirements_changed.py --marker %REQ_MARKER% --update-marker >nul 2>nul -) - -REM PR-S1b: stale check. If requirements.txt content hash differs from marker, -REM run `pip install -r requirements.txt` (no --upgrade) to add missing deps only. -set STALE= -for /f "delims=" %%i in ('!PYTHON! tools\check_requirements_changed.py --marker %REQ_MARKER% 2^>nul') do set STALE=%%i -if "!STALE!"=="stale" ( - echo [studio] requirements.txt changed since last sync; installing new deps ^(no upgrade^)... - !PYTHON! -m pip install -r requirements.txt - if errorlevel 1 ( - echo [studio] WARNING: dep sync failed; existing venv still works but may miss new deps 1>&2 - echo [studio] try studio.bat --reinstall if errors persist 1>&2 - ) else ( - !PYTHON! tools\check_requirements_changed.py --marker %REQ_MARKER% --update-marker >nul 2>nul - echo [studio] dep sync complete - ) -) - -REM Restart loop (PR-A): if cli.py exits but tmp\restart still present, loop -REM back. cli.py's own inner loop handles the common case (server requests -REM restart); this outer loop is the safety net. -REM -REM Special exit code 42 (PR-D, installer self-update): cli.py detected that -REM cli.py / studio.sh / studio.bat itself was just replaced. cmd.exe has the -REM .bat parsed in memory; if we keep looping, we risk running stale wrapper -REM logic. So we `start` a fresh copy of ourselves and exit. Unlike POSIX -REM `exec`, this spawns a new process (different PID) -- /b keeps the same -REM console window, so the user-visible effect is the same. See ADR 0002. -:run_loop -!PYTHON! -m studio %PASSTHROUGH% -set STUDIO_ERR=%ERRORLEVEL% -if exist tmp\restart ( - if !STUDIO_ERR! EQU 42 ( - echo [studio] launcher updated, re-exec wrapper - del /q tmp\restart 2>nul - start "" /b "%~f0" %PASSTHROUGH% - exit /b 0 - ) - echo [studio] restart requested ^(wrapper loop^) - del /q tmp\restart 2>nul - goto run_loop -) - -if %STUDIO_ERR% NEQ 0 ( - echo. - echo [studio] Exit code %STUDIO_ERR%. Press any key to close... - pause >nul -) -exit /b %STUDIO_ERR% - -:fail -echo. -echo [studio] setup failed. Press any key to close... -pause >nul -exit /b 1 +@echo off +chcp 65001 >nul +REM AnimaStudio Windows shortcut -- forwards to: python -m studio +REM Usage: +REM .\studio.bat same as: python -m studio run +REM .\studio.bat --reinstall DELETE venv\ and rebuild (studio_data\ kept) +REM .\studio.bat --torch=cu128 force GPU torch (cu128/cu126/cu124/cu118/cpu) +REM .\studio.bat dev frontend + backend dev mode +REM .\studio.bat dev --fe-port 5174 Vite on port 5174 +REM .\studio.bat dev --port 8766 backend on port 8766 +REM .\studio.bat build build frontend only +REM .\studio.bat test run pytest + vitest +REM +REM Note: PowerShell needs the `.\` prefix; cmd.exe accepts either. +REM +REM NOTE: This file MUST stay pure ASCII. cmd.exe parses .bat files with the +REM system ANSI codepage BEFORE `chcp 65001` takes effect, so any non-ASCII +REM byte breaks line parsing on hosts with a non-UTF-8 ANSI codepage. + +setlocal enabledelayedexpansion +cd /d "%~dp0" + +REM Force Python to UTF-8 stdout/stderr so prints with non-ASCII +REM don't crash on non-UTF-8 system locales (e.g. cp932 Japanese). +set PYTHONUTF8=1 +set PYTHONIOENCODING=utf-8 + +REM Keep pip's wheel cache inside the project folder (several GB -- the CUDA +REM torch wheel alone is 2-3GB, and the default is %LOCALAPPDATA%\pip\Cache on +REM the system drive). Everything else is redirected by +REM studio\infrastructure\local_cache.py, which is the authority for the full +REM list; only pip has to be set here, because the first-run bootstrap below +REM shells out to pip before any of our Python code runs. ALS_SYSTEM_CACHES=1 +REM opts out, and an existing PIP_CACHE_DIR is never overridden. +if not defined ALS_SYSTEM_CACHES ( + if not defined PIP_CACHE_DIR ( + set PIP_CACHE_DIR=%~dp0.cache\pip + if not exist "%~dp0.cache\pip" mkdir "%~dp0.cache\pip" 2>nul + ) +) + +REM Parse our flags; collect remaining args to forward to Python. +set REINSTALL=0 +set TORCH_TAG= +set PASSTHROUGH= +:argloop +if "%~1"=="" goto argdone +if /i "%~1"=="--reinstall" ( + set REINSTALL=1 +) else ( + REM Check for --torch= prefix + set _ARG=%~1 + if "!_ARG:~0,8!"=="--torch=" ( + set TORCH_TAG=!_ARG:~8! + set PASSTHROUGH=!PASSTHROUGH! --torch !TORCH_TAG! + ) else ( + set PASSTHROUGH=!PASSTHROUGH! %1 + ) +) +shift +goto argloop +:argdone + +set REQ_MARKER=venv\.studio-requirements.sha256 + +REM --reinstall: nuke venv before detection. studio_data\ is untouched. +if "%REINSTALL%"=="1" ( + if exist venv ( + echo [studio] --reinstall: venv\ will be DELETED and rebuilt. + echo [studio] - studio_data\ ^(your projects + LoRA weights^) is NOT touched + echo [studio] - any user-installed pip packages outside requirements.txt will be lost + set /p ANS="Continue? [y/N] " + if /i not "!ANS!"=="y" ( + echo [studio] --reinstall aborted + exit /b 0 + ) + echo [studio] removing venv\... + rmdir /s /q venv || (echo studio.bat: failed to remove venv 1>&2 & goto :fail) + ) +) + +if exist "venv\Scripts\python.exe" ( + set PYTHON=venv\Scripts\python.exe + REM PR-S0: warn if existing venv is < 3.10 ^(user can `--reinstall` to fix^) + venv\Scripts\python.exe -c "import sys;sys.exit(0 if sys.version_info>=(3,10) else 1)" >nul 2>nul + if errorlevel 1 ( + echo [studio] WARNING: venv\ uses Python ^< 3.10; some deps may fail to install. 1>&2 + echo [studio] consider .\studio.bat --reinstall to recreate with a newer Python. 1>&2 + ) +) else if exist ".venv\Scripts\python.exe" ( + set PYTHON=.venv\Scripts\python.exe + .venv\Scripts\python.exe -c "import sys;sys.exit(0 if sys.version_info>=(3,10) else 1)" >nul 2>nul + if errorlevel 1 ( + echo [studio] WARNING: .venv\ uses Python ^< 3.10; some deps may fail to install. 1>&2 + echo [studio] consider .\studio.bat --reinstall to recreate with a newer Python. 1>&2 + ) +) else ( + REM PR-S0: prefer `py -3` over `python`. Many Windows users keep an old + REM system `python` on PATH (3.9 etc) for legacy compat and a newer Python + REM via the `py` launcher (3.13 default). `py -3` picks the highest 3.x + REM installed via python.org installers. Fall back to `python` for users + REM without the launcher (uv / conda / scoop installs). + set BOOTSTRAP_PY= + where py >nul 2>nul + if not errorlevel 1 set BOOTSTRAP_PY=py -3 + if not defined BOOTSTRAP_PY ( + where python >nul 2>nul + if errorlevel 1 ( + echo studio.bat: neither `py` nor `python` found on PATH. Install Python 3.10+ from https://www.python.org/. 1>&2 + goto :fail + ) + set BOOTSTRAP_PY=python + ) + REM Version check: warn if < 3.10 ^(README requires 3.10+; some deps will fail to install on older^) + !BOOTSTRAP_PY! -c "import sys;sys.exit(0 if sys.version_info>=(3,10) else 1)" >nul 2>nul + if errorlevel 1 ( + echo [studio] WARNING: bootstrap Python is older than 3.10; some deps may fail to install. 1>&2 + echo [studio] If you have a newer Python installed, set PY_PYTHON=3.13 ^(or your version^) and retry. 1>&2 + ) + echo [studio] No venv detected. Creating venv\ via `!BOOTSTRAP_PY!` -- first run may take a few minutes... + !BOOTSTRAP_PY! -m venv venv || (echo studio.bat: failed to create venv 1>&2 & goto :fail) + set PYTHON=venv\Scripts\python.exe + + !PYTHON! -m pip install --upgrade pip -i https://mirrors.cloud.tencent.com/pypi/simple/ || (echo studio.bat: failed to upgrade pip 1>&2 & goto :fail) + + REM GPU-aware torch first install (PR-S1a). Without this, requirements.txt's + REM bare `torch>=2.0.0` makes pip pull the CPU wheel. Installing CUDA torch + REM from PyTorch's index FIRST satisfies the constraint, pip won't replace. + REM --torch= overrides auto-detection (useful on CPU-only rentals). + if defined TORCH_TAG ( + set TORCH_INDEX=https://download.pytorch.org/whl/!TORCH_TAG! + echo [studio] setup: --torch=!TORCH_TAG! specified; installing torch from !TORCH_INDEX! + !PYTHON! -m pip install torch torchvision --index-url !TORCH_INDEX! + if errorlevel 1 ( + echo [studio] setup: forced torch install failed; will fall back to PyPI default in requirements.txt + ) + ) else ( + set TORCH_INDEX= + for /f "delims=" %%i in ('!PYTHON! tools\select_torch_index.py 2^>nul') do set TORCH_INDEX=%%i + if defined TORCH_INDEX ( + echo [studio] setup: NVIDIA GPU detected; installing torch from !TORCH_INDEX! + !PYTHON! -m pip install torch torchvision --index-url !TORCH_INDEX! + if errorlevel 1 ( + echo [studio] setup: CUDA torch install failed; will fall back to PyPI default in requirements.txt + echo [studio] setup: you can fix manually later via Studio Settings ^> PyTorch ^> Reinstall + ) + ) + ) + + if exist requirements.txt ( + echo [studio] Installing Python dependencies -- will retry via Tencent mirror if slow... + !PYTHON! -m pip install -r requirements.txt + if errorlevel 1 ( + echo [studio] pip install failed, retrying via Tencent mirror... + !PYTHON! -m pip install -r requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple/ || (echo studio.bat: pip install failed 1>&2 & goto :fail) + ) + ) else ( + echo studio.bat: requirements.txt not found, skipping dependency install 1>&2 + ) + REM PR-S1b: write hash marker after fresh install + !PYTHON! tools\check_requirements_changed.py --marker %REQ_MARKER% --update-marker >nul 2>nul +) + +REM PR-S1b: stale check. If requirements.txt content hash differs from marker, +REM run `pip install -r requirements.txt` (no --upgrade) to add missing deps only. +set STALE= +for /f "delims=" %%i in ('!PYTHON! tools\check_requirements_changed.py --marker %REQ_MARKER% 2^>nul') do set STALE=%%i +if "!STALE!"=="stale" ( + echo [studio] requirements.txt changed since last sync; installing new deps ^(no upgrade^)... + !PYTHON! -m pip install -r requirements.txt + if errorlevel 1 ( + echo [studio] WARNING: dep sync failed; existing venv still works but may miss new deps 1>&2 + echo [studio] try studio.bat --reinstall if errors persist 1>&2 + ) else ( + !PYTHON! tools\check_requirements_changed.py --marker %REQ_MARKER% --update-marker >nul 2>nul + echo [studio] dep sync complete + ) +) + +REM Restart loop (PR-A): if cli.py exits but tmp\restart still present, loop +REM back. cli.py's own inner loop handles the common case (server requests +REM restart); this outer loop is the safety net. +REM +REM Special exit code 42 (PR-D, installer self-update): cli.py detected that +REM cli.py / studio.sh / studio.bat itself was just replaced. cmd.exe has the +REM .bat parsed in memory; if we keep looping, we risk running stale wrapper +REM logic. So we `start` a fresh copy of ourselves and exit. Unlike POSIX +REM `exec`, this spawns a new process (different PID) -- /b keeps the same +REM console window, so the user-visible effect is the same. See ADR 0002. +:run_loop +!PYTHON! -m studio %PASSTHROUGH% +set STUDIO_ERR=%ERRORLEVEL% +if exist tmp\restart ( + if !STUDIO_ERR! EQU 42 ( + echo [studio] launcher updated, re-exec wrapper + del /q tmp\restart 2>nul + start "" /b "%~f0" %PASSTHROUGH% + exit /b 0 + ) + echo [studio] restart requested ^(wrapper loop^) + del /q tmp\restart 2>nul + goto run_loop +) + +if %STUDIO_ERR% NEQ 0 ( + echo. + echo [studio] Exit code %STUDIO_ERR%. Press any key to close... + pause >nul +) +exit /b %STUDIO_ERR% + +:fail +echo. +echo [studio] setup failed. Press any key to close... +pause >nul +exit /b 1 diff --git a/studio/domain/errors.py b/studio/domain/errors.py index 3e28335..2e34671 100644 --- a/studio/domain/errors.py +++ b/studio/domain/errors.py @@ -20,11 +20,11 @@ - the `message` field is **English** -- the frontend looks up `code` in an i18n table to render a localized string; `message` is only the English fallback display. This avoids reviving the ADR-0008 §cross-D - Chinese-string-matching trap through DomainError. + localized-string-matching trap through DomainError. - the `code` field is named **domain.action** (`preset.not_found` / `curation.duplicate`). - short-term (0.12.0), existing service errors like PresetError still have - Chinese messages (C3 only adds the base class, doesn't touch messages), + legacy messages (C3 only adds the base class, doesn't touch messages), but **new code must** use an English message + i18n code. Usage: diff --git a/studio/infrastructure/logging.py b/studio/infrastructure/logging.py index 97e74a2..37947bb 100644 --- a/studio/infrastructure/logging.py +++ b/studio/infrastructure/logging.py @@ -230,7 +230,7 @@ def __init__(self) -> None: def reconfigure_console_utf8() -> None: - """Windows consoles default to cp932/cp936, so writing non-ASCII text / emoji raises + """Windows consoles default to a legacy ANSI codepage, so writing non-ASCII text / emoji raises UnicodeEncodeError. Forces stdout/stderr into UTF-8 + replace mode so the logger never raises. diff --git a/studio/services/runtime/flash_attention.py b/studio/services/runtime/flash_attention.py index bbdf8b3..b8e4020 100644 --- a/studio/services/runtime/flash_attention.py +++ b/studio/services/runtime/flash_attention.py @@ -106,7 +106,7 @@ def detect_env() -> dict[str, Any]: # the driver version is always stored separately as `driver_cuda_ver` for the UI to display and for troubleshooting (so the user can see # a case like "driver supports cu130, PyTorch is cu128" and immediately understand why the wheel should be cu128). driver_cuda_ver: Optional[str] = None - # Historically only caught (subprocess.SubprocessError, OSError); on a Windows Chinese locale (cp936), + # Historically only caught (subprocess.SubprocessError, OSError); on a non-UTF-8 Windows locale, # decoding nvidia-smi output with text=True could raise UnicodeDecodeError (not one of those subclasses), # causing a raw 500. Switched to errors='replace' to avoid that, and widened the except to Exception. try: diff --git a/studio/supervisor/core.py b/studio/supervisor/core.py index 25cd333..050ad7a 100644 --- a/studio/supervisor/core.py +++ b/studio/supervisor/core.py @@ -1365,7 +1365,7 @@ def _popen( creationflags = 0 if os.name == "nt": creationflags = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined] - # Windows defaults stdout to cp936; any worker writing Chinese text / emoji + # Windows defaults stdout to the ANSI codepage; any worker writing non-ASCII text / emoji # would trigger a UnicodeEncodeError, and logging's default backslashreplace # would turn it into \uXXXX, filling the task log with garbled text. Give # every child process a UTF-8 + unbuffered fallback here. diff --git a/studio/web/src/api/client.ts b/studio/web/src/api/client.ts index 1ed9ceb..ac0e2d6 100644 --- a/studio/web/src/api/client.ts +++ b/studio/web/src/api/client.ts @@ -2180,16 +2180,6 @@ export interface ModelsRootMigrateStatus { error: string } -export interface AnnouncementPost { - id: string - date: string - tag: 'release' | 'notice' | 'migration' - title: { zh: string; en: string } - body: { zh: string; en: string } - pin: boolean - version: string | null -} - export const api = { health: () => req('/api/health'), systemStats: () => req('/api/system/stats'), diff --git a/studio/web/src/components/TagsInput.tsx b/studio/web/src/components/TagsInput.tsx index 1be335b..6a36cd6 100644 --- a/studio/web/src/components/TagsInput.tsx +++ b/studio/web/src/components/TagsInput.tsx @@ -13,7 +13,7 @@ export function parseTags(s: string): string[] { * - **Editing (focus)**: a plain-text ``, commas / spaces typed freely. Controlled by the text, not the * array, to avoid a "join-back on every keystroke" wiping out a comma / trailing space being typed. An autocomplete * popover pops up below the input; keyboard up/down + Enter/Tab select. - * - **Idle (blur)**: renders tags as chips, scannable at a glance; shows the Chinese translation when there's a match. + * - **Idle (blur)**: renders tags as chips, scannable at a glance. * * On blur the text is normalized to `tags.join(', ')`, so re-entering edit mode shows the canonical form. * Use this directly for cases that already have their own outer label (Settings' SettingsField); for a diff --git a/studio/web/src/pages/project/steps/Train.tsx b/studio/web/src/pages/project/steps/Train.tsx index ac9ed37..72bd79d 100644 --- a/studio/web/src/pages/project/steps/Train.tsx +++ b/studio/web/src/pages/project/steps/Train.tsx @@ -441,7 +441,7 @@ export default function TrainPage() { } catch (e) { const msg = String(e) // Match on the error code, not on the message: the message is localized - // (and was never Chinese to begin with), so a text match silently turned + // so a text match silently turned // the overwrite prompt into a plain error toast. if ((e as ApiError).code === 'preset.exists') { const overwrite = await confirm(t('train.alreadyExists', { name: trimmed }), { diff --git a/studio/web/src/pages/tools/Settings.tsx b/studio/web/src/pages/tools/Settings.tsx index 7267200..038526b 100644 --- a/studio/web/src/pages/tools/Settings.tsx +++ b/studio/web/src/pages/tools/Settings.tsx @@ -252,7 +252,7 @@ const MODEL_DESCRIPTION_KEYS: Record = { cltagger: 'settings.modelDescriptions.cltagger', } -// Catalog names come from the backend in Chinese for the main models. +// The backend sends English catalog names; localize the main models on the frontend. const MODEL_NAME_KEYS: Record = { anima_main: 'settings.modelNames.animaMain', krea2_main: 'settings.modelNames.krea2Main', diff --git a/studio/workers/_base.py b/studio/workers/_base.py index 2fb469c..fcee5f1 100644 --- a/studio/workers/_base.py +++ b/studio/workers/_base.py @@ -10,7 +10,7 @@ - calling run + sys.exit Optional helper `reconfigure_console_utf8()` -- the Windows console defaults to -cp932/cp936, so writing non-ASCII / emoji raises UnicodeEncodeError; calling this once +a legacy ANSI codepage, so writing non-ASCII / emoji raises UnicodeEncodeError; calling this once switches stdout/stderr to UTF-8 + replace mode. Currently only tag_worker needs it (other workers don't write non-ASCII captions). """ @@ -68,7 +68,7 @@ def _worker_kind_from_argv() -> str: def reconfigure_console_utf8() -> None: - """The Windows console defaults to cp932/cp936, so writing non-ASCII / emoji raises + """The Windows console defaults to a legacy ANSI codepage, so writing non-ASCII / emoji raises UnicodeEncodeError. Forces stdout/stderr to UTF-8 + replace-unencodable-chars, so progress output never throws. diff --git a/tests/test_studio_bat_ascii.py b/tests/test_studio_bat_ascii.py index d147fd5..a4ba8c4 100644 --- a/tests/test_studio_bat_ascii.py +++ b/tests/test_studio_bat_ascii.py @@ -26,7 +26,7 @@ def test_studio_bat_is_pure_ascii() -> None: f"studio.bat contains {len(bad_offsets)} non-ASCII byte(s) (first 5 detailed below):\n" + "\n".join(snippets) + "\n\nstudio.bat must stay pure ASCII -- cmd.exe parses it with the system ANSI " - "codepage before chcp 65001 runs, and UTF-8 Chinese bytes get split into garbled " + "codepage before chcp 65001 runs, and multi-byte UTF-8 characters get split into garbled " "commands. Put any non-ASCII messages inside echo, routed through a Python " "process that has already set PYTHONUTF8 / chcp 65001." ) diff --git a/tools/download_models.py b/tools/download_models.py index 80671bf..7667d3f 100644 --- a/tools/download_models.py +++ b/tools/download_models.py @@ -28,7 +28,7 @@ import sys from pathlib import Path -# Windows consoles using cp936/cp932 raise UnicodeEncodeError on non-ASCII / emoji; force UTF-8. +# Windows consoles using a legacy ANSI codepage raise UnicodeEncodeError on non-ASCII / emoji; force UTF-8. for _stream in (sys.stdout, sys.stderr): try: _stream.reconfigure(encoding="utf-8", errors="replace") diff --git a/tools/launcher.py b/tools/launcher.py index b30fa43..d629a4d 100644 --- a/tools/launcher.py +++ b/tools/launcher.py @@ -62,8 +62,8 @@ # --------------------------------------------------------------------------- # # **Every string this file prints must be pure ASCII**, the same discipline as studio.bat. -# The Windows console decodes using the system ANSI codepage (Russian cp866, Chinese cp936, -# Japanese cp932); a frozen exe writing a single `->`-style arrow glyph is an instant +# The Windows console decodes using the system ANSI codepage (e.g. Russian cp866, +# Western cp1252); a frozen exe writing a single `->`-style arrow glyph is an instant # UnicodeEncodeError crash -- and the crash usually happens **in the error path itself** # (die()'s hint line), so the user doesn't see "Python isn't installed" but a PyInstaller # traceback instead. Comments and docstrings aren't bound by this rule (they're never diff --git a/tools/mask_check.py b/tools/mask_check.py index 2f8b766..a2bd746 100644 --- a/tools/mask_check.py +++ b/tools/mask_check.py @@ -33,7 +33,7 @@ import sys from pathlib import Path -# CJK output crashes under Windows console cp932; force utf-8 +# Non-ASCII output crashes under Windows console cp932; force utf-8 if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") diff --git a/tools/mem_probe.py b/tools/mem_probe.py index b593926..c44b4ec 100644 --- a/tools/mem_probe.py +++ b/tools/mem_probe.py @@ -171,7 +171,7 @@ def read(self, target_pid: int) -> tuple[float, float, float]: # ---------------------------------------------------------------- main loop def main() -> None: - # The console may be cp932/gbk or some other non-utf-8 codepage (this machine uses cp932); + # The console may be cp932 or some other non-utf-8 codepage (this machine uses cp932); # non-ASCII print output would raise UnicodeEncodeError and crash the probe itself. # Force utf-8 + errors=replace uniformly. for _s in (sys.stdout, sys.stderr):