diff --git a/README.md b/README.md index bfc9b614ec..47efbffa71 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Run and train AI models with a unified local interface. unsloth studio ui homepage -Unsloth Studio lets you run and train text, [audio](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning), [embedding](https://unsloth.ai/docs/new/embedding-finetuning), [vision](https://unsloth.ai/docs/basics/vision-fine-tuning) models on Windows, Linux and macOS. +Unsloth Studio (Beta) lets you run and train text, [audio](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning), [embedding](https://unsloth.ai/docs/new/embedding-finetuning), [vision](https://unsloth.ai/docs/basics/vision-fine-tuning) models on Windows, Linux and macOS. ## ⭐ Features Unsloth provides several key features for both inference and training: @@ -42,7 +42,7 @@ Unsloth provides several key features for both inference and training: Unsloth can be used in two ways: through **[Unsloth Studio](https://unsloth.ai/docs/new/studio/)**, the web UI, or through **Unsloth Core**, the code-based version. Each has different requirements. ### Unsloth Studio (web UI) -Unsloth Studio works on **Windows, Linux, WSL** and **macOS**. +Unsloth Studio (Beta) works on **Windows, Linux, WSL** and **macOS**. * **CPU:** Supported for **chat inference only** * **NVIDIA:** Training works on RTX 30/40/50, Blackwell, DGX Spark, Station and more @@ -51,7 +51,26 @@ Unsloth Studio works on **Windows, Linux, WSL** and **macOS**. * **Coming soon:** Training support for Apple MLX, AMD, and Intel. * **Multi-GPU:** Available now, with a major upgrade on the way -#### MacOS, Linux or WSL Setup (One time): +#### MacOS, Linux, WSL Setup: +```bash +curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/install.sh | sh +``` +If you don't have `curl`, use `wget`. Then to launch after setup: +```bash +source unsloth_studio/bin/activate +unsloth studio -H 0.0.0.0 -p 8888 +``` + +#### Windows PowerShell Setup: +```powershell +irm https://raw.githubusercontent.com/unslothai/unsloth/main/install.ps1 | iex +``` +Then to launch after setup: +```powershell +& .\unsloth_studio\Scripts\unsloth.exe studio -H 0.0.0.0 -p 8888 +``` + +#### MacOS, Linux, WSL developer installs: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh uv venv unsloth_studio --python 3.13 @@ -60,14 +79,9 @@ uv pip install unsloth --torch-backend=auto unsloth studio setup unsloth studio -H 0.0.0.0 -p 8888 ``` -Then to launch every time: -```bash -source unsloth_studio/bin/activate -unsloth studio -H 0.0.0.0 -p 8888 -``` -#### Windows PowerShell (One time): -```bash +#### Windows PowerShell developer installs: +```powershell winget install -e --id Python.Python.3.13 winget install --id=astral-sh.uv -e uv venv unsloth_studio --python 3.13 @@ -76,15 +90,18 @@ uv pip install unsloth --torch-backend=auto unsloth studio setup unsloth studio -H 0.0.0.0 -p 8888 ``` -Then to launch every time: + +#### Docker +Use our [Docker image](https://hub.docker.com/r/unsloth/unsloth) ```unsloth/unsloth``` container. Run: ```bash -.\unsloth_studio\Scripts\activate -unsloth studio -H 0.0.0.0 -p 8888 -``` +docker run -d -e JUPYTER_PASSWORD="mypassword" \ + -p 8888:8888 -p 8000:8000 -p 2222:22 \ + -v $(pwd)/work:/workspace/work \ + --gpus all \ + unsloth/unsloth + ``` -Use our [Docker image](https://hub.docker.com/r/unsloth/unsloth) ```unsloth/unsloth``` container. Read our [Docker Guide](https://unsloth.ai/docs/get-started/install/docker). - -#### Nightly Installation - MacOS, Linux or WSL Setup (One time): +#### Nightly Install - MacOS, Linux, WSL: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh git clone --filter=blob:none https://github.com/unslothai/unsloth.git unsloth_studio @@ -102,7 +119,8 @@ source .venv/bin/activate unsloth studio -H 0.0.0.0 -p 8888 ``` -#### Nightly Installation - Windows Powershell (One time): +#### Nightly Install - Windows: +Run in Windows Powershell: ```bash winget install -e --id Python.Python.3.13 winget install --id=astral-sh.uv -e 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 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/core/training/trainer.py b/studio/backend/core/training/trainer.py index bc252d1fa5..ede213647d 100644 --- a/studio/backend/core/training/trainer.py +++ b/studio/backend/core/training/trainer.py @@ -772,6 +772,11 @@ class UnslothTrainer: if self.should_stop: return False + if full_finetuning: + # Enable training mode for full fine-tuning + # This ensures all model parameters are trainable; otherwise, they might be frozen. + self.model.for_training() + self._update_progress(status_message = "Model loaded successfully") logger.info("Model loaded successfully") return True diff --git a/studio/backend/requirements/extras.txt b/studio/backend/requirements/extras.txt index 170b1c9ec8..d783975a4f 100644 --- a/studio/backend/requirements/extras.txt +++ b/studio/backend/requirements/extras.txt @@ -26,8 +26,9 @@ omegaconf einx pyloudnorm openai-whisper -# uroman # 4.0 MB - romanization, no imports found -# MeCab # 19.9 MB - Japanese tokenizer, no imports found +uroman # 4.0 MB - used for Outetts. +MeCab # 19.9 MB - used for Outetts. +inflect # number-to-words, required by OuteTTS loguru flatten_dict ffmpy 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 @@ _shutdown_event = 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_server( 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 @@ if __name__ == "__main__": 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/backend/utils/models/model_config.py b/studio/backend/utils/models/model_config.py index aaf15994be..13f1b5febf 100644 --- a/studio/backend/utils/models/model_config.py +++ b/studio/backend/utils/models/model_config.py @@ -630,7 +630,10 @@ _AUDIO_TOKEN_PATTERNS = { "whisper": lambda tokens: "<|startoftranscript|>" in tokens, "audio_vlm": lambda tokens: "" in tokens, "bicodec": lambda tokens: any(t.startswith("<|bicodec_") for t in tokens), - "dac": lambda tokens: "<|audio_start|>" in tokens and "<|audio_end|>" in tokens, + "dac": lambda tokens: "<|audio_start|>" in tokens + and "<|audio_end|>" in tokens + and "<|text_start|>" in tokens + and "<|text_end|>" in tokens, "snac": lambda tokens: sum(1 for t in tokens if t.startswith(" 10000, } diff --git a/studio/frontend/src/app/routes/__root.tsx b/studio/frontend/src/app/routes/__root.tsx index 0725d383c7..d7780c6743 100644 --- a/studio/frontend/src/app/routes/__root.tsx +++ b/studio/frontend/src/app/routes/__root.tsx @@ -15,10 +15,16 @@ import { AppProvider } from "../provider"; const CHAT_ONLY_ALLOWED = new Set(["/", "/chat", "/login", "/signup", "/change-password"]); +function isChatOnlyAllowed(pathname: string): boolean { + if (CHAT_ONLY_ALLOWED.has(pathname)) return true; + if (pathname === "/data-recipes" || pathname.startsWith("/data-recipes/")) return true; + return false; +} + export const Route = createRootRoute({ beforeLoad: ({ location }) => { const chatOnly = usePlatformStore.getState().isChatOnly(); - if (chatOnly && !CHAT_ONLY_ALLOWED.has(location.pathname)) { + if (chatOnly && !isChatOnlyAllowed(location.pathname)) { throw redirect({ to: "/chat" }); } }, diff --git a/studio/frontend/src/components/navbar.tsx b/studio/frontend/src/components/navbar.tsx index 0f159add77..d90276a641 100644 --- a/studio/frontend/src/components/navbar.tsx +++ b/studio/frontend/src/components/navbar.tsx @@ -93,7 +93,7 @@ export function Navbar() { const disabledByTraining = isTrainingRunning && item.href !== "/studio"; const disabledByDevice = - chatOnly && item.href !== "/chat"; + chatOnly && item.href !== "/chat" && item.href !== "/data-recipes"; if (!item.enabled || disabledByTraining || disabledByDevice) { return ( {/* Right: mobile */} -
+
{tourId ? ( ) : null} +
+ Theme + +
diff --git a/studio/frontend/src/components/ui/dialog.tsx b/studio/frontend/src/components/ui/dialog.tsx index bb9e6816d5..5556c42e2b 100644 --- a/studio/frontend/src/components/ui/dialog.tsx +++ b/studio/frontend/src/components/ui/dialog.tsx @@ -189,6 +189,7 @@ export { DialogHeader, DialogOverlay, DialogPortal, + DialogPortalContainerContext, DialogTitle, DialogTrigger, }; diff --git a/studio/frontend/src/components/ui/sheet.tsx b/studio/frontend/src/components/ui/sheet.tsx index 8e7ad75e0d..d2b12e6cba 100644 --- a/studio/frontend/src/components/ui/sheet.tsx +++ b/studio/frontend/src/components/ui/sheet.tsx @@ -3,8 +3,10 @@ import { Dialog as SheetPrimitive } from "radix-ui"; import type * as React from "react"; +import { useState } from "react"; import { Button } from "@/components/ui/button"; +import { DialogPortalContainerContext } from "@/components/ui/dialog"; import { cn } from "@/lib/utils"; import { Cancel01Icon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; @@ -69,6 +71,7 @@ function SheetContent({ overlayClassName?: string; overlayPosition?: "fixed" | "absolute"; }) { + const [contentEl, setContentEl] = useState(null); return ( - {children} + + {children} + {showCloseButton && (