diff --git a/README.md b/README.md index 6bf612df4a..583e26eef3 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,14 @@ Unsloth Studio (Beta) works on **Windows, Linux, WSL** and **macOS**. #### MacOS, Linux, WSL: For MacOS, ensure you have `cmake` installed. If not, run `brew install cmake`. ```bash +curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh +``` +If you don't have `curl`, use `wget`: +```bash +wget -qO- https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh +``` +Or manually: +```bash curl -LsSf https://astral.sh/uv/install.sh | sh uv venv unsloth_studio --python 3.13 source unsloth_studio/bin/activate @@ -67,9 +75,12 @@ source unsloth_studio/bin/activate unsloth studio -H 0.0.0.0 -p 8888 ``` -#### Windows: -Run in Windows Powershell: -```bash +#### Windows PowerShell (One time): +```powershell +irm https://raw.githubusercontent.com/unslothai/unsloth/main/install.ps1 | iex +``` +Or manually: +```powershell winget install -e --id Python.Python.3.13 winget install --id=astral-sh.uv -e uv venv unsloth_studio --python 3.13 @@ -79,7 +90,7 @@ unsloth studio setup unsloth studio -H 0.0.0.0 -p 8888 ``` Then to launch every time: -```bash +```powershell .\unsloth_studio\Scripts\activate unsloth studio -H 0.0.0.0 -p 8888 ``` diff --git a/build.sh b/build.sh index 4948a73ae4..3118e8810a 100644 --- a/build.sh +++ b/build.sh @@ -4,8 +4,52 @@ set -euo pipefail # 1. Build frontend (Vite outputs to dist/) cd studio/frontend + +# Clean stale dist to force a full rebuild +rm -rf dist + +# Tailwind v4's oxide scanner respects .gitignore in parent directories. +# Python venvs create a .gitignore with "*" (ignore everything), which +# prevents Tailwind from scanning .tsx source files for class names. +# Temporarily hide any such .gitignore during the build, then restore it. +_HIDDEN_GITIGNORES=() +_dir="$(pwd)" +while [ "$_dir" != "/" ]; do + _dir="$(dirname "$_dir")" + if [ -f "$_dir/.gitignore" ] && grep -qx '\*' "$_dir/.gitignore" 2>/dev/null; then + mv "$_dir/.gitignore" "$_dir/.gitignore._twbuild" + _HIDDEN_GITIGNORES+=("$_dir/.gitignore") + fi +done + +_restore_gitignores() { + for _gi in "${_HIDDEN_GITIGNORES[@]+"${_HIDDEN_GITIGNORES[@]}"}"; do + mv "${_gi}._twbuild" "$_gi" 2>/dev/null || true + done +} +trap _restore_gitignores EXIT + npm install npm run build # outputs to studio/frontend/dist/ + +_restore_gitignores +trap - EXIT + +# Validate CSS output -- catch truncated Tailwind builds before packaging +MAX_CSS_SIZE=$(find dist/assets -name '*.css' -exec wc -c {} + 2>/dev/null | sort -n | tail -1 | awk '{print $1}') +if [ -z "$MAX_CSS_SIZE" ]; then + echo "❌ ERROR: No CSS files were emitted into dist/assets." + echo " The frontend build may have failed silently." + exit 1 +fi +if [ "$MAX_CSS_SIZE" -lt 100000 ]; then + echo "❌ ERROR: Largest CSS file is only $((MAX_CSS_SIZE / 1024))KB (expected >100KB)." + echo " Tailwind may not have scanned all source files." + echo " Check for .gitignore files blocking the Tailwind oxide scanner." + exit 1 +fi +echo "✅ Frontend CSS validated (${MAX_CSS_SIZE} bytes)" + cd ../.. # 2. Clean old artifacts diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000000..0d65ef4e06 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,119 @@ +# Unsloth Studio Installer for Windows PowerShell +# Usage: irm https://raw.githubusercontent.com/unslothai/unsloth/main/install.ps1 | iex +# Local: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; .\install.ps1 + +function Install-UnslothStudio { + $ErrorActionPreference = "Stop" + + $VenvName = "unsloth_studio" + $PythonVersion = "3.13" + + Write-Host "" + Write-Host "=========================================" + Write-Host " Unsloth Studio Installer (Windows)" + Write-Host "=========================================" + Write-Host "" + + # ── Helper: refresh PATH from registry (preserving current session entries) ── + function Refresh-SessionPath { + $machine = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + $user = [System.Environment]::GetEnvironmentVariable("Path", "User") + $env:Path = "$machine;$user;$env:Path" + } + + # ── Check winget ── + if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { + Write-Host "Error: winget is not available." -ForegroundColor Red + Write-Host " Install it from https://aka.ms/getwinget" -ForegroundColor Yellow + Write-Host " or install Python $PythonVersion and uv manually, then re-run." -ForegroundColor Yellow + return + } + + # ── Install Python if no compatible version (3.11-3.13) found ── + $DetectedPythonVersion = "" + if (Get-Command python -ErrorAction SilentlyContinue) { + $pyVer = python --version 2>&1 + if ($pyVer -match "Python (3\.1[1-3])\.\d+") { + Write-Host "==> Python already installed: $pyVer" + $DetectedPythonVersion = $Matches[1] + } + } + if (-not $DetectedPythonVersion) { + Write-Host "==> Installing Python ${PythonVersion}..." + winget install -e --id Python.Python.3.13 --accept-package-agreements --accept-source-agreements + Refresh-SessionPath + if ($LASTEXITCODE -ne 0) { + # winget returns non-zero for "already installed" -- only fail if python is truly missing + if (-not (Get-Command python -ErrorAction SilentlyContinue)) { + Write-Host "[ERROR] Python installation failed (exit code $LASTEXITCODE)" -ForegroundColor Red + return + } + } + $DetectedPythonVersion = $PythonVersion + } + + # ── Install uv if not present ── + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Host "==> Installing uv package manager..." + winget install --id=astral-sh.uv -e --accept-package-agreements --accept-source-agreements + Refresh-SessionPath + # Fallback: if winget didn't put uv on PATH, try the PowerShell installer + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Host " Trying alternative uv installer..." + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + Refresh-SessionPath + } + } + + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Host "Error: uv could not be installed." -ForegroundColor Red + Write-Host " Install it from https://docs.astral.sh/uv/" -ForegroundColor Yellow + return + } + + # ── Create venv (skip if it already exists and has a valid interpreter) ── + $VenvPython = Join-Path $VenvName "Scripts\python.exe" + if (-not (Test-Path $VenvPython)) { + if (Test-Path $VenvName) { Remove-Item -Recurse -Force $VenvName } + Write-Host "==> Creating Python ${DetectedPythonVersion} virtual environment (${VenvName})..." + uv venv $VenvName --python $DetectedPythonVersion + if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Failed to create virtual environment (exit code $LASTEXITCODE)" -ForegroundColor Red + return + } + } else { + Write-Host "==> Virtual environment ${VenvName} already exists, skipping creation." + } + + # ── Install unsloth directly into the venv (no activation needed) ── + Write-Host "==> Installing unsloth (this may take a few minutes)..." + uv pip install --python $VenvPython unsloth --torch-backend=auto + if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Failed to install unsloth (exit code $LASTEXITCODE)" -ForegroundColor Red + return + } + + # ── Run studio setup ── + # setup.ps1 will handle installing Git, CMake, Visual Studio Build Tools, + # CUDA Toolkit, Node.js, and other dependencies automatically via winget. + Write-Host "==> Running unsloth studio setup..." + $UnslothExe = Join-Path $VenvName "Scripts\unsloth.exe" + & $UnslothExe studio setup + if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] unsloth studio setup failed (exit code $LASTEXITCODE)" -ForegroundColor Red + return + } + + Write-Host "" + Write-Host "=========================================" + Write-Host " Unsloth Studio installed!" + Write-Host "=========================================" + Write-Host "" + Write-Host " To launch, run:" + Write-Host "" + Write-Host " .\${VenvName}\Scripts\activate" + Write-Host " unsloth studio -H 0.0.0.0 -p 8888" + Write-Host "" +} + +Install-UnslothStudio diff --git a/install.sh b/install.sh new file mode 100755 index 0000000000..5e846a2feb --- /dev/null +++ b/install.sh @@ -0,0 +1,212 @@ +#!/bin/sh +# Unsloth Studio Installer +# Usage (curl): curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh +# Usage (wget): wget -qO- https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh +set -e + +VENV_NAME="unsloth_studio" +PYTHON_VERSION="3.13" + +# ── Helper: download a URL to a file (supports curl and wget) ── +download() { + if command -v curl >/dev/null 2>&1; then + curl -LsSf "$1" -o "$2" + elif command -v wget >/dev/null 2>&1; then + wget -qO "$2" "$1" + else + echo "Error: neither curl nor wget found. Install one and re-run." + exit 1 + fi +} + +# ── Helper: check if a single package is available on the system ── +_is_pkg_installed() { + case "$1" in + build-essential) command -v gcc >/dev/null 2>&1 ;; + libcurl4-openssl-dev) + command -v dpkg >/dev/null 2>&1 && dpkg -s "$1" >/dev/null 2>&1 ;; + pciutils) + command -v lspci >/dev/null 2>&1 ;; + *) command -v "$1" >/dev/null 2>&1 ;; + esac +} + +# ── Helper: install packages via apt, escalating to sudo only if needed ── +# Usage: _smart_apt_install pkg1 pkg2 pkg3 ... +_smart_apt_install() { + _PKGS="$*" + + # Step 1: Try installing without sudo (works when already root) + apt-get update -y >/dev/null 2>&1 || true + apt-get install -y $_PKGS >/dev/null 2>&1 || true + + # Step 2: Check which packages are still missing + _STILL_MISSING="" + for _pkg in $_PKGS; do + if ! _is_pkg_installed "$_pkg"; then + _STILL_MISSING="$_STILL_MISSING $_pkg" + fi + done + _STILL_MISSING=$(echo "$_STILL_MISSING" | sed 's/^ *//') + + if [ -z "$_STILL_MISSING" ]; then + return 0 + fi + + # Step 3: Escalate -- need elevated permissions for remaining packages + if command -v sudo >/dev/null 2>&1; then + echo "" + echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo " WARNING: We require sudo elevated permissions to install:" + echo " $_STILL_MISSING" + echo " If you accept, we'll run sudo now, and it'll prompt your password." + echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "" + printf " Accept? [Y/n] " + if [ -r /dev/tty ]; then + read -r REPLY /dev/null; then + OS="wsl" +fi +echo "==> Platform: $OS" + +# ── Check system dependencies ── +# cmake and git are needed by unsloth studio setup to build the GGUF inference +# engine (llama.cpp). build-essential and libcurl-dev are also needed on Linux. +MISSING="" + +command -v cmake >/dev/null 2>&1 || MISSING="$MISSING cmake" +command -v git >/dev/null 2>&1 || MISSING="$MISSING git" + +case "$OS" in + macos) + # Xcode Command Line Tools provide the C/C++ compiler + if ! xcode-select -p >/dev/null 2>&1; then + echo "" + echo "==> Xcode Command Line Tools are required." + echo " Installing (a system dialog will appear)..." + xcode-select --install 2>/dev/null || true + echo " After the installation completes, please re-run this script." + exit 1 + fi + ;; + linux|wsl) + # curl or wget is needed for downloads; check both + if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then + MISSING="$MISSING curl" + fi + command -v gcc >/dev/null 2>&1 || MISSING="$MISSING build-essential" + # libcurl dev headers for llama.cpp HTTPS support + if command -v dpkg >/dev/null 2>&1; then + dpkg -s libcurl4-openssl-dev >/dev/null 2>&1 || MISSING="$MISSING libcurl4-openssl-dev" + fi + ;; +esac + +MISSING=$(echo "$MISSING" | sed 's/^ *//') + +if [ -n "$MISSING" ]; then + echo "" + echo "==> Unsloth Studio needs these packages: $MISSING" + echo " These are needed to build the GGUF inference engine." + + case "$OS" in + macos) + if ! command -v brew >/dev/null 2>&1; then + echo "" + echo " Homebrew is required to install them." + echo " Install Homebrew from https://brew.sh then re-run this script." + exit 1 + fi + brew install $MISSING + ;; + linux|wsl) + if command -v apt-get >/dev/null 2>&1; then + _smart_apt_install $MISSING + else + echo " apt-get is not available. Please install with your package manager:" + echo " $MISSING" + echo " Then re-run Unsloth Studio setup." + exit 1 + fi + ;; + esac + echo "" +else + echo "==> All system dependencies found." +fi + +# ── Install uv ── +if ! command -v uv >/dev/null 2>&1; then + echo "==> Installing uv package manager..." + _uv_tmp=$(mktemp) + download "https://astral.sh/uv/install.sh" "$_uv_tmp" + sh "$_uv_tmp" + rm -f "$_uv_tmp" + if [ -f "$HOME/.local/bin/env" ]; then + . "$HOME/.local/bin/env" + fi + export PATH="$HOME/.local/bin:$PATH" +fi + +# ── Create venv (skip if it already exists and has a valid interpreter) ── +if [ ! -x "$VENV_NAME/bin/python" ]; then + [ -e "$VENV_NAME" ] && rm -rf "$VENV_NAME" + echo "==> Creating Python ${PYTHON_VERSION} virtual environment (${VENV_NAME})..." + uv venv "$VENV_NAME" --python "$PYTHON_VERSION" +else + echo "==> Virtual environment ${VENV_NAME} already exists, skipping creation." +fi + +# ── Install unsloth directly into the venv (no activation needed) ── +echo "==> Installing unsloth (this may take a few minutes)..." +uv pip install --python "$VENV_NAME/bin/python" unsloth --torch-backend=auto + +# ── Run studio setup ── +echo "==> Running unsloth studio setup..." +"$VENV_NAME/bin/unsloth" studio setup None: logger = get_logger(__name__) -def get_colab_url(port: int = 8000) -> str: +def get_colab_url(port: int = 8888) -> str: """ Get the actual Colab proxy URL for a port. """ @@ -60,7 +60,7 @@ def get_colab_url(port: int = 8000) -> str: return f"http://localhost:{port}" -def show_link(port: int = 8000): +def show_link(port: int = 8888): """Display a styled clickable link to the UI.""" from IPython.display import display, HTML @@ -68,8 +68,8 @@ def show_link(port: int = 8000): url = get_colab_url(port) short_url = ( - url[: url.index("-", url.index("8000-") + 5) + 1] + "..." - if "8000-" in url + url[: url.index("-", url.index(f"{port}-") + len(str(port)) + 1) + 1] + "..." + if f"{port}-" in url else url ) html = f""" @@ -96,7 +96,7 @@ def show_link(port: int = 8000): display(HTML(html)) -def start(port: int = 8000): +def start(port: int = 8888): """ Start Unsloth Studio server in Colab and display the URL. diff --git a/studio/backend/run.py b/studio/backend/run.py index 2f064ab92e..5388d3148b 100644 --- a/studio/backend/run.py +++ b/studio/backend/run.py @@ -155,7 +155,7 @@ def _graceful_shutdown(server = None): def run_server( host: str = "0.0.0.0", - port: int = 8000, + port: int = 8888, frontend_path: Path = Path(__file__).resolve().parent.parent / "frontend" / "dist", silent: bool = False, ): @@ -224,13 +224,17 @@ def _run(): if not silent: display_host = _resolve_external_ip() if host == "0.0.0.0" else host + print("") + print("=" * 50) + print(f"🦥 Open your web browser, and enter http://localhost:{port}") + print("=" * 50) print("") print("=" * 50) print(f"🦥 Unsloth Studio is running on port {port}") - print(f" Local: http://localhost:{port}") - print(f" External: http://{display_host}:{port}") - print(f" API: http://{display_host}:{port}/api") - print(f" Health: http://{display_host}:{port}/api/health") + print(f" Local Access: http://localhost:{port}") + print(f" Worldwide Web Address: http://{display_host}:{port}") + print(f" API: http://{display_host}:{port}/api") + print(f" Health: http://{display_host}:{port}/api/health") print("=" * 50) return app @@ -243,7 +247,7 @@ def _run(): parser = argparse.ArgumentParser(description = "Run Unsloth UI Backend server") parser.add_argument("--host", default = "0.0.0.0", help = "Host to bind to") - parser.add_argument("--port", type = int, default = 8000, help = "Port to bind to") + parser.add_argument("--port", type = int, default = 8888, help = "Port to bind to") parser.add_argument( "--frontend", type = str, diff --git a/studio/frontend/vite.config.ts b/studio/frontend/vite.config.ts index 6e9a03df95..8d34383a48 100644 --- a/studio/frontend/vite.config.ts +++ b/studio/frontend/vite.config.ts @@ -17,11 +17,11 @@ export default defineConfig({ allowedHosts: true, proxy: { "/api": { - target: "http://127.0.0.1:8000", + target: "http://127.0.0.1:8888", changeOrigin: true, }, "/v1": { - target: "http://127.0.0.1:8000", + target: "http://127.0.0.1:8888", changeOrigin: true, }, "/seed/inspect": { diff --git a/studio/setup.ps1 b/studio/setup.ps1 index c768c066af..7091920ab9 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -855,10 +855,31 @@ if ($IsPipInstall) { Write-Host "[INFO] Frontend source changed since last build -- rebuilding..." -ForegroundColor Yellow } } -$NeedFrontendBuild = $true if ($NeedFrontendBuild -and -not $IsPipInstall) { Write-Host "" Write-Host "Building frontend..." -ForegroundColor Cyan + + # ── Tailwind v4 .gitignore workaround ── + # Tailwind v4's oxide scanner respects .gitignore in parent directories. + # Python venvs create a .gitignore with "*" (ignore everything), which + # prevents Tailwind from scanning .tsx source files for class names. + # Temporarily hide any such .gitignore during the build, then restore it. + $HiddenGitignores = @() + $WalkDir = (Get-Item $FrontendDir).Parent.FullName + while ($WalkDir -and $WalkDir -ne [System.IO.Path]::GetPathRoot($WalkDir)) { + $gi = Join-Path $WalkDir ".gitignore" + if (Test-Path $gi) { + $content = Get-Content $gi -Raw -ErrorAction SilentlyContinue + if ($content -and ($content.Trim() -match '^\*$')) { + $hidden = "$gi._twbuild" + Rename-Item -Path $gi -NewName (Split-Path $hidden -Leaf) -Force + $HiddenGitignores += $gi + Write-Host " [INFO] Temporarily hiding $gi (venv .gitignore blocks Tailwind scanner)" -ForegroundColor DarkGray + } + } + $WalkDir = Split-Path $WalkDir -Parent + } + # npm writes warnings to stderr; lower ErrorActionPreference so PS doesn't # treat them as terminating errors (same pattern as the pip section below). $prevEAP_npm = $ErrorActionPreference @@ -868,6 +889,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) { if ($LASTEXITCODE -ne 0) { Pop-Location $ErrorActionPreference = $prevEAP_npm + foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue } Write-Host "[ERROR] npm install failed (exit code $LASTEXITCODE)" -ForegroundColor Red Write-Host " Try running 'npm install' manually in frontend/ to see errors" -ForegroundColor Yellow exit 1 @@ -876,12 +898,27 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) { if ($LASTEXITCODE -ne 0) { Pop-Location $ErrorActionPreference = $prevEAP_npm + foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue } Write-Host "[ERROR] npm run build failed (exit code $LASTEXITCODE)" -ForegroundColor Red exit 1 } Pop-Location $ErrorActionPreference = $prevEAP_npm - Write-Host "[OK] Frontend built to frontend/dist" -ForegroundColor Green + + # ── Restore hidden .gitignore files ── + foreach ($gi in $HiddenGitignores) { + Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue + } + + # ── Validate CSS output ── + $CssFiles = Get-ChildItem (Join-Path $DistDir "assets") -Filter "*.css" -ErrorAction SilentlyContinue + $MaxCssSize = ($CssFiles | Measure-Object -Property Length -Maximum).Maximum + if ($MaxCssSize -lt 100000) { + Write-Host "[WARN] Largest CSS file is only $([math]::Round($MaxCssSize / 1024))KB -- Tailwind may not have scanned all source files." -ForegroundColor Yellow + Write-Host " Expected >100KB. Check for .gitignore files blocking the Tailwind oxide scanner." -ForegroundColor Yellow + } else { + Write-Host "[OK] Frontend built to frontend/dist (CSS: $([math]::Round($MaxCssSize / 1024))KB)" -ForegroundColor Green + } } if (Test-Path $OxcValidatorDir) { @@ -1374,6 +1411,6 @@ Write-Host "+===============================================+" -ForegroundColor Write-Host "| Setup Complete! |" -ForegroundColor Green Write-Host "| |" -ForegroundColor Green Write-Host "| Launch with: |" -ForegroundColor Green -Write-Host "| unsloth studio -H 0.0.0.0 -p 8000 |" -ForegroundColor Green +Write-Host "| unsloth studio -H 0.0.0.0 -p 8888 |" -ForegroundColor Green Write-Host "| |" -ForegroundColor Green Write-Host "+===============================================+" -ForegroundColor Green diff --git a/studio/setup.sh b/studio/setup.sh index 02c49afc15..3e56aebf38 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -59,7 +59,6 @@ if [ -d "$SCRIPT_DIR/frontend/dist" ]; then _NEED_FRONTEND_BUILD=false fi fi -_NEED_FRONTEND_BUILD=true if [ "$_NEED_FRONTEND_BUILD" = false ]; then echo "✅ Frontend already built and up to date -- skipping Node/npm check." else @@ -160,6 +159,16 @@ run_quiet "npm run build" npm run build _restore_gitignores trap - EXIT + +# Validate CSS output -- catch truncated Tailwind builds +_MAX_CSS=$(find "$SCRIPT_DIR/frontend/dist/assets" -name '*.css' -exec wc -c {} + 2>/dev/null | sort -n | tail -1 | awk '{print $1}') +if [ -z "$_MAX_CSS" ]; then + echo "⚠️ WARNING: No CSS files were emitted. The frontend build may have failed." +elif [ "$_MAX_CSS" -lt 100000 ]; then + echo "⚠️ WARNING: Largest CSS file is only $((_MAX_CSS / 1024))KB (expected >100KB)." + echo " Tailwind may not have scanned all source files. Check for .gitignore interference." +fi + cd "$SCRIPT_DIR" echo "✅ Frontend built to frontend/dist" @@ -308,10 +317,59 @@ echo "✅ Transformers 5.x pre-installed to $VENV_T5_DIR/" if grep -qi microsoft /proc/version 2>/dev/null; then echo "" echo "⚠️ WSL detected -- installing build dependencies for GGUF export..." - echo " You may be prompted for your password." - sudo apt-get update -y - sudo apt-get install -y build-essential cmake curl git libcurl4-openssl-dev - echo "✅ GGUF build dependencies installed" + _GGUF_DEPS="pciutils build-essential cmake curl git libcurl4-openssl-dev" + + # Try without sudo first (works when already root) + apt-get update -y >/dev/null 2>&1 || true + apt-get install -y $_GGUF_DEPS >/dev/null 2>&1 || true + + # Check which packages are still missing + _STILL_MISSING="" + for _pkg in $_GGUF_DEPS; do + case "$_pkg" in + build-essential) command -v gcc >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;; + pciutils) command -v lspci >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;; + libcurl4-openssl-dev) dpkg -s "$_pkg" >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;; + *) command -v "$_pkg" >/dev/null 2>&1 || _STILL_MISSING="$_STILL_MISSING $_pkg" ;; + esac + done + _STILL_MISSING=$(echo "$_STILL_MISSING" | sed 's/^ *//') + + if [ -z "$_STILL_MISSING" ]; then + echo "✅ GGUF build dependencies installed" + elif command -v sudo >/dev/null 2>&1; then + echo "" + echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo " WARNING: We require sudo elevated permissions to install:" + echo " $_STILL_MISSING" + echo " If you accept, we'll run sudo now, and it'll prompt your password." + echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "" + printf " Accept? [Y/n] " + if [ -r /dev/tty ]; then + read -r REPLY Optional[Path]: @studio_app.callback(invoke_without_command = True) def studio_default( ctx: typer.Context, - port: int = typer.Option(8000, "--port", "-p"), + port: int = typer.Option(8888, "--port", "-p"), host: str = typer.Option("0.0.0.0", "--host", "-H"), frontend: Optional[Path] = typer.Option(None, "--frontend", "-f"), silent: bool = typer.Option(False, "--silent", "-q"), @@ -93,7 +93,7 @@ def studio_default( run_py = _find_run_py() if studio_python and run_py: if not silent: - typer.echo("Launching with studio venv...") + typer.echo("Launching Unsloth Studio... Please wait...") args = [ str(studio_python), str(run_py), diff --git a/unsloth_cli/commands/ui.py b/unsloth_cli/commands/ui.py index eb6a8a69e8..4457465e96 100644 --- a/unsloth_cli/commands/ui.py +++ b/unsloth_cli/commands/ui.py @@ -12,7 +12,7 @@ def ui( port: int = typer.Option( - 8000, "--port", "-p", help = "Port to run the UI server on." + 8888, "--port", "-p", help = "Port to run the UI server on." ), host: str = typer.Option( "0.0.0.0", "--host", "-H", help = "Host address to bind to." @@ -40,7 +40,7 @@ def ui( run_py = _find_run_py() if studio_python and run_py: if not silent: - typer.echo("Launching with studio venv...") + typer.echo("Launching Unsloth Studio... Please wait...") args = [ str(studio_python), str(run_py),