implement modular backend setup scripts, new frontend components, and expanded server services
This commit is contained in:
1 parent
bd89699994
commit
ffec37fd0a
27 files changed
+309
-149
No files matched your search
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Optional CoreML NPU setup for Apple Silicon macOS systems.
|
||||
# Sets up a virtual environment and installs Apple's ml-stable-diffusion.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
VENV_DIR="$ROOT_DIR/app/backend/mac/coreml_venv"
|
||||
PYTHON_BIN="$VENV_DIR/bin/python"
|
||||
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " Local AI Image Generator - Apple Silicon CoreML NPU Setup"
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
|
||||
if [[ "$(uname -s)" != "Darwin" ]]; then
|
||||
echo " [ERROR] This setup script is for macOS only." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(uname -m)" != "arm64" ]]; then
|
||||
echo " [ERROR] CoreML NPU support requires Apple Silicon (M1/M2/M3/M4/etc. arm64)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo " [ERROR] python3 is required. Install Python 3 first (via Homebrew or Xcode)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "$PYTHON_BIN" ]]; then
|
||||
echo " Creating Python environment: $VENV_DIR"
|
||||
if ! python3 -m venv "$VENV_DIR"; then
|
||||
echo " [ERROR] Could not create the virtual environment."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " Installing CoreML Stable Diffusion dependencies (numpy, coremltools, diffusers)..."
|
||||
"$PYTHON_BIN" -m pip install --upgrade pip
|
||||
"$PYTHON_BIN" -m pip install numpy coremltools diffusers transformers huggingface-hub pillow
|
||||
|
||||
echo " Installing Apple's python-coreml-stable-diffusion package..."
|
||||
if ! "$PYTHON_BIN" -m pip install "git+https://github.com/apple/ml-stable-diffusion.git"; then
|
||||
echo " [ERROR] Failed to install Apple's ml-stable-diffusion repository."
|
||||
echo " Make sure 'git' is installed on your system."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " Verifying CoreML environment..."
|
||||
if ! "$PYTHON_BIN" -c "from python_coreml_stable_diffusion.pipeline import CoreMLStableDiffusionPipeline; print(' ANE (CoreML) Pipeline verified successfully!')"; then
|
||||
echo " [ERROR] Verification failed. Please check the error details above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " CoreML NPU setup complete."
|
||||
echo " Restart the launcher (./mac.sh) and download a CoreML model!"
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
@@ -0,0 +1,200 @@
|
||||
param([string]$Release = "b9668")
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$llmRoot = Join-Path $appDir "llm-backend\win"
|
||||
|
||||
function Enable-Tls12 {
|
||||
try {
|
||||
$tls12 = [Enum]::ToObject([Net.SecurityProtocolType], 3072)
|
||||
[Net.ServicePointManager]::SecurityProtocol =
|
||||
[Net.ServicePointManager]::SecurityProtocol -bor $tls12
|
||||
} catch {
|
||||
throw "TLS 1.2 could not be enabled: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function Format-Bytes {
|
||||
param([long]$b)
|
||||
if ($b -gt 1GB) { return "{0:N2} GB" -f ($b / 1GB) }
|
||||
if ($b -gt 1MB) { return "{0:N1} MB" -f ($b / 1MB) }
|
||||
return "{0:N0} KB" -f ($b / 1KB)
|
||||
}
|
||||
|
||||
function Format-Speed {
|
||||
param([double]$bps)
|
||||
if ($bps -gt 1MB) { return "{0:N1} MB/s" -f ($bps / 1MB) }
|
||||
return "{0:N0} KB/s" -f ($bps / 1KB)
|
||||
}
|
||||
|
||||
function Download-File {
|
||||
param([string]$Url, [string]$DestPath, [string]$Label)
|
||||
|
||||
$barWidth = 48
|
||||
$lastBytes = [long]0
|
||||
$lastTime = [DateTime]::Now
|
||||
|
||||
Write-Host " >> Downloading $Label..."
|
||||
|
||||
try {
|
||||
Enable-Tls12
|
||||
$req = [System.Net.HttpWebRequest]::Create($Url)
|
||||
$req.UserAgent = "Mozilla/5.0"
|
||||
$resp = $req.GetResponse()
|
||||
$total = [long]$resp.ContentLength
|
||||
$stream = $resp.GetResponseStream()
|
||||
$out = [System.IO.File]::Create($DestPath)
|
||||
$buf = New-Object byte[] 65536
|
||||
$done = [long]0
|
||||
|
||||
while ($true) {
|
||||
$read = $stream.Read($buf, 0, $buf.Length)
|
||||
if ($read -le 0) { break }
|
||||
$out.Write($buf, 0, $read)
|
||||
$done += $read
|
||||
|
||||
$now = [DateTime]::Now
|
||||
$elapsed = ($now - $lastTime).TotalSeconds
|
||||
if ($elapsed -ge 0.35) {
|
||||
$speed = ($done - $lastBytes) / $elapsed
|
||||
$lastBytes = $done
|
||||
$lastTime = $now
|
||||
$pct = if ($total -gt 0) { [int](($done / $total) * 100) } else { 0 }
|
||||
$fill = [int](($pct / 100) * $barWidth)
|
||||
$bar = ("#" * $fill) + ("-" * ($barWidth - $fill))
|
||||
|
||||
$eta = ""
|
||||
if ($speed -gt 0 -and $total -gt 0) {
|
||||
$rem = [int](($total - $done) / $speed)
|
||||
$eta = " ETA $([int]($rem/60))m$($rem%60)s"
|
||||
}
|
||||
|
||||
$dl = Format-Bytes $done
|
||||
$tot = if ($total -gt 0) { " / " + (Format-Bytes $total) } else { "" }
|
||||
$spd = Format-Speed $speed
|
||||
Write-Host -NoNewline "`r [$bar] $pct% $dl$tot $spd$eta "
|
||||
}
|
||||
}
|
||||
|
||||
$out.Close(); $stream.Close()
|
||||
Write-Host "`r [$("#" * $barWidth)] 100% $(Format-Bytes $done) Done! " -ForegroundColor Green
|
||||
Write-Host ""
|
||||
} catch {
|
||||
Write-Host ""
|
||||
throw "Download failed: $_"
|
||||
}
|
||||
}
|
||||
|
||||
function Install-LlamaArchive {
|
||||
param([string]$Variant, [string]$AssetName)
|
||||
|
||||
$dest = Join-Path $llmRoot $Variant
|
||||
$server = Join-Path $dest "llama-server.exe"
|
||||
if (Test-Path $server) {
|
||||
Write-Host " OK llama.cpp $Variant backend already ready."
|
||||
return
|
||||
}
|
||||
|
||||
$archive = Join-Path $toolsDir $AssetName
|
||||
$extract = Join-Path $toolsDir "llama-$Variant-extract"
|
||||
$url = "https://github.com/ggml-org/llama.cpp/releases/download/$Release/$AssetName"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir, $dest | Out-Null
|
||||
Remove-Item $archive, $extract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Download-File -Url $url -DestPath $archive -Label "llama.cpp $Variant backend ($Release)"
|
||||
|
||||
Write-Host " >> Extracting llama.cpp $Variant backend..."
|
||||
Expand-Archive -Path $archive -DestinationPath $extract -Force
|
||||
|
||||
Get-ChildItem $extract -Recurse -File | ForEach-Object {
|
||||
Copy-Item $_.FullName (Join-Path $dest $_.Name) -Force
|
||||
}
|
||||
Remove-Item $archive, $extract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if ($Variant -eq "cuda") {
|
||||
$cudartAsset = "cudart-llama-bin-win-cuda-12.4-x64.zip"
|
||||
$cudartArchive = Join-Path $toolsDir $cudartAsset
|
||||
$cudartExtract = Join-Path $toolsDir "llama-cudart-extract"
|
||||
$cudartUrl = "https://github.com/ggml-org/llama.cpp/releases/download/$Release/$cudartAsset"
|
||||
|
||||
Remove-Item $cudartArchive, $cudartExtract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Download-File -Url $cudartUrl -DestPath $cudartArchive -Label "llama.cpp CUDA runtime library ($Release)"
|
||||
|
||||
Write-Host " >> Extracting CUDA runtime libraries..."
|
||||
Expand-Archive -Path $cudartArchive -DestinationPath $cudartExtract -Force
|
||||
|
||||
Get-ChildItem $cudartExtract -Recurse -File | ForEach-Object {
|
||||
Copy-Item $_.FullName (Join-Path $dest $_.Name) -Force
|
||||
}
|
||||
Remove-Item $cudartArchive, $cudartExtract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (-not (Test-Path $server)) {
|
||||
throw "llama-server.exe was not found after extracting $AssetName"
|
||||
}
|
||||
Write-Host " OK llama.cpp $Variant backend installed."
|
||||
}
|
||||
|
||||
function Try-InstallLlamaArchive {
|
||||
param([string]$Variant, [string]$AssetName, [string]$Reason)
|
||||
|
||||
try {
|
||||
Install-LlamaArchive -Variant $Variant -AssetName $AssetName
|
||||
} catch {
|
||||
Write-Host " !! Skipping optional llama.cpp $Variant backend: $Reason" -ForegroundColor Yellow
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkYellow
|
||||
}
|
||||
}
|
||||
|
||||
$hasNvidia = $false
|
||||
try {
|
||||
$gpus = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue
|
||||
foreach ($gpu in $gpus) {
|
||||
if ($gpu.Name -like "*NVIDIA*") {
|
||||
$hasNvidia = $true
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
if (-not $hasNvidia) {
|
||||
try {
|
||||
& nvidia-smi *> $null
|
||||
if ($LASTEXITCODE -eq 0) { $hasNvidia = $true }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if ($hasNvidia) {
|
||||
Try-InstallLlamaArchive -Variant "cuda" -AssetName "llama-$Release-bin-win-cuda-12.4-x64.zip" -Reason "NVIDIA CUDA acceleration"
|
||||
}
|
||||
|
||||
$hasAmd = $false
|
||||
try {
|
||||
$gpus = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue
|
||||
foreach ($gpu in $gpus) {
|
||||
if ($gpu.Name -like "*AMD*" -or $gpu.Name -like "*Radeon*") {
|
||||
$hasAmd = $true
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
if ($hasAmd) {
|
||||
Try-InstallLlamaArchive -Variant "hip" -AssetName "llama-$Release-bin-win-hip-x64.zip" -Reason "AMD HIP acceleration"
|
||||
}
|
||||
# Intel Arc/Graphics detection for SYCL backend
|
||||
$hasIntel = $false
|
||||
try {
|
||||
$gpus = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue
|
||||
foreach ($gpu in $gpus) {
|
||||
if ($gpu.Name -like "*Intel*") {
|
||||
$hasIntel = $true
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
if ($hasIntel) {
|
||||
Try-InstallLlamaArchive -Variant "sycl" -AssetName "llama-$Release-bin-win-sycl-x64.zip" -Reason "Intel SYCL acceleration"
|
||||
}
|
||||
Install-LlamaArchive -Variant "vulkan" -AssetName "llama-$Release-bin-win-vulkan-x64.zip"
|
||||
Install-LlamaArchive -Variant "cpu" -AssetName "llama-$Release-bin-win-cpu-x64.zip"
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
RELEASE="${LLAMA_RELEASE:-b9668}"
|
||||
PLATFORM="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
download_and_extract() {
|
||||
local asset="$1"
|
||||
local dest="$2"
|
||||
local archive="$TOOLS_DIR/$asset"
|
||||
local url="https://github.com/ggml-org/llama.cpp/releases/download/$RELEASE/$asset"
|
||||
|
||||
if [[ -x "$dest/llama-server" ]]; then
|
||||
echo " OK llama.cpp backend already ready: $dest"
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$TOOLS_DIR" "$dest"
|
||||
rm -f "$archive" "$archive.part"
|
||||
echo " >> Downloading $asset"
|
||||
curl -fSL --progress-bar "$url" -o "$archive.part"
|
||||
mv "$archive.part" "$archive"
|
||||
tar -xzf "$archive" -C "$dest" --strip-components=1
|
||||
rm -f "$archive"
|
||||
chmod +x "$dest"/llama-* 2>/dev/null || true
|
||||
|
||||
if [[ ! -x "$dest/llama-server" ]]; then
|
||||
echo " XX llama-server was not found after extracting $asset" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_and_extract_optional() {
|
||||
local asset="$1"
|
||||
local dest="$2"
|
||||
local reason="$3"
|
||||
|
||||
if [[ -x "$dest/llama-server" ]]; then
|
||||
echo " OK optional llama.cpp backend already ready: $dest"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if download_and_extract "$asset" "$dest"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo " !! Skipping optional llama.cpp backend for $reason ($asset)" >&2
|
||||
rm -rf "$dest"
|
||||
return 0
|
||||
}
|
||||
|
||||
has_command() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
has_linux_gpu_vendor() {
|
||||
local vendor="$1"
|
||||
[[ -d /sys/bus/pci/devices ]] || return 1
|
||||
grep -Ril "$vendor" /sys/bus/pci/devices/*/vendor >/dev/null 2>&1
|
||||
}
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
if [[ "$ARCH" == "arm64" ]]; then
|
||||
download_and_extract "llama-$RELEASE-bin-macos-arm64.tar.gz" "$APP_DIR/llm-backend/mac/arm64"
|
||||
else
|
||||
download_and_extract "llama-$RELEASE-bin-macos-x64.tar.gz" "$APP_DIR/llm-backend/mac/x64"
|
||||
fi
|
||||
elif [[ "$PLATFORM" == "Linux" ]]; then
|
||||
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
|
||||
download_and_extract "llama-$RELEASE-bin-ubuntu-vulkan-arm64.tar.gz" "$APP_DIR/llm-backend/linux/vulkan"
|
||||
download_and_extract "llama-$RELEASE-bin-ubuntu-arm64.tar.gz" "$APP_DIR/llm-backend/linux/cpu"
|
||||
else
|
||||
if has_command nvidia-smi || has_linux_gpu_vendor "0x10de"; then
|
||||
download_and_extract_optional "llama-$RELEASE-bin-ubuntu-cuda-12.4-x64.tar.gz" "$APP_DIR/llm-backend/linux/cuda" "NVIDIA CUDA acceleration"
|
||||
fi
|
||||
if has_command rocminfo || has_linux_gpu_vendor "0x1002"; then
|
||||
download_and_extract_optional "llama-$RELEASE-bin-ubuntu-rocm-x64.tar.gz" "$APP_DIR/llm-backend/linux/rocm" "AMD ROCm acceleration"
|
||||
fi
|
||||
if has_linux_gpu_vendor "0x8086"; then
|
||||
download_and_extract_optional "llama-$RELEASE-bin-ubuntu-sycl-fp32-x64.tar.gz" "$APP_DIR/llm-backend/linux/sycl" "Intel SYCL acceleration"
|
||||
fi
|
||||
download_and_extract "llama-$RELEASE-bin-ubuntu-vulkan-x64.tar.gz" "$APP_DIR/llm-backend/linux/vulkan"
|
||||
download_and_extract "llama-$RELEASE-bin-ubuntu-x64.tar.gz" "$APP_DIR/llm-backend/linux/cpu"
|
||||
fi
|
||||
else
|
||||
echo "Unsupported platform: $PLATFORM" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,46 @@
|
||||
# Optional OpenVINO NPU setup for Intel AI Boost systems.
|
||||
# This does not modify the existing stable-diffusion.cpp backends.
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$venvDir = Join-Path $rootDir "app\tools\openvino-venv-win"
|
||||
$pythonExe = Join-Path $venvDir "Scripts\python.exe"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ============================================================"
|
||||
Write-Host " Local AI Image Generator - OpenVINO NPU Setup"
|
||||
Write-Host " ============================================================"
|
||||
Write-Host ""
|
||||
|
||||
$npu = Get-CimInstance Win32_PnPEntity -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like "*Intel(R) AI Boost*" -or ($_.Name -match "NPU" -and $_.PNPClass -eq "ComputeAccelerator") } |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $npu) {
|
||||
Write-Host " [ERROR] Intel AI Boost NPU was not detected on this system." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " Detected: $($npu.Name)"
|
||||
|
||||
if (-not (Test-Path $pythonExe)) {
|
||||
Write-Host " Creating Python environment: $venvDir"
|
||||
python -m venv $venvDir
|
||||
}
|
||||
|
||||
Write-Host " Installing OpenVINO GenAI runtime..."
|
||||
& $pythonExe -m pip install --upgrade pip
|
||||
& $pythonExe -m pip install openvino openvino-genai pillow huggingface-hub
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " Verifying OpenVINO NPU device..."
|
||||
& $pythonExe -c "import openvino as ov; core=ov.Core(); print('Available devices:', core.available_devices); raise SystemExit(0 if 'NPU' in core.available_devices else 2)"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ============================================================"
|
||||
Write-Host " OpenVINO NPU setup complete."
|
||||
Write-Host " Restart windows.bat, then download an OpenVINO NPU model."
|
||||
Write-Host " ============================================================"
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Optional OpenVINO NPU setup for Linux Intel Core Ultra systems.
|
||||
# The Intel NPU kernel/user-mode driver must already be installed.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
VENV_DIR="$ROOT_DIR/app/tools/openvino-venv-linux"
|
||||
PYTHON_BIN="$VENV_DIR/bin/python"
|
||||
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " Local AI Image Generator - Linux OpenVINO NPU Setup"
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
|
||||
if [[ "$(uname -s)" != "Linux" ]]; then
|
||||
echo " [ERROR] This setup script is for Linux only." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(uname -m)" != "x86_64" ]]; then
|
||||
echo " [ERROR] OpenVINO NPU support requires x86_64 Linux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo " [ERROR] python3 is required. Install Python 3 and the venv module." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KERNEL_VERSION="$(uname -r)"
|
||||
KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
|
||||
KERNEL_REST="${KERNEL_VERSION#*.}"
|
||||
KERNEL_MINOR="${KERNEL_REST%%.*}"
|
||||
if (( KERNEL_MAJOR < 6 || (KERNEL_MAJOR == 6 && KERNEL_MINOR < 6) )); then
|
||||
echo " [ERROR] Intel NPU support requires Linux kernel 6.6 or newer."
|
||||
echo " Current kernel: $KERNEL_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -e /dev/accel/accel0 ]]; then
|
||||
echo " [ERROR] Intel NPU device /dev/accel/accel0 was not found."
|
||||
echo " Install the Intel Linux NPU driver and reboot first."
|
||||
echo " Driver: https://github.com/intel/linux-npu-driver"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "$PYTHON_BIN" ]]; then
|
||||
echo " Creating Python environment: $VENV_DIR"
|
||||
if ! python3 -m venv "$VENV_DIR"; then
|
||||
echo " [ERROR] Could not create the virtual environment."
|
||||
echo " On Ubuntu, install python3-venv and retry."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " Installing OpenVINO GenAI runtime..."
|
||||
"$PYTHON_BIN" -m pip install --upgrade pip
|
||||
"$PYTHON_BIN" -m pip install openvino openvino-genai pillow huggingface-hub
|
||||
|
||||
echo ""
|
||||
echo " Verifying OpenVINO NPU device..."
|
||||
"$PYTHON_BIN" - <<'PY'
|
||||
import openvino as ov
|
||||
|
||||
core = ov.Core()
|
||||
devices = core.available_devices
|
||||
print(" Available devices:", ", ".join(devices))
|
||||
if "NPU" not in devices:
|
||||
raise SystemExit(
|
||||
" [ERROR] OpenVINO is installed, but NPU is unavailable. "
|
||||
"Verify the Intel NPU driver and device permissions."
|
||||
)
|
||||
print(" NPU:", core.get_property("NPU", "FULL_DEVICE_NAME"))
|
||||
PY
|
||||
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " OpenVINO NPU setup complete."
|
||||
echo " Run ./linux.sh, then download an OpenVINO NPU model."
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
@@ -0,0 +1,52 @@
|
||||
# Local AI Studio - Kokoro TTS setup for Windows
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$nodeDir = Join-Path $toolsDir "node-win"
|
||||
$nodeExe = Join-Path $nodeDir "node.exe"
|
||||
$npmCmd = Join-Path $nodeDir "npm.cmd"
|
||||
$runtimeDir = Join-Path $appDir "tts-runtime"
|
||||
$modelsDir = Join-Path $appDir "tts-models"
|
||||
$outputsDir = Join-Path $appDir "tts-outputs"
|
||||
$cacheDir = Join-Path $appDir "tts-cache"
|
||||
|
||||
function Print-OK { param([string]$m); Write-Host " OK $m" -ForegroundColor Green }
|
||||
function Print-Info { param([string]$m); Write-Host " >> $m" -ForegroundColor Cyan }
|
||||
function Print-Fail { param([string]$m); Write-Host " XX $m" -ForegroundColor Red }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ============================================================" -ForegroundColor Cyan
|
||||
Write-Host " Setting up Kokoro ONNX Text-to-Speech runtime" -ForegroundColor Cyan
|
||||
Write-Host " ============================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $runtimeDir, $modelsDir, $outputsDir, $cacheDir | Out-Null
|
||||
|
||||
if (-not (Test-Path $nodeExe) -or -not (Test-Path $npmCmd)) {
|
||||
Print-Fail "Portable Node.js is missing. Run scripts/setup/setup.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$packageJson = Join-Path $runtimeDir "package.json"
|
||||
if (-not (Test-Path $packageJson)) {
|
||||
'{"private":true,"type":"module","dependencies":{"kokoro-js":"^1.2.1"}}' | Out-File -FilePath $packageJson -Encoding utf8
|
||||
}
|
||||
|
||||
Push-Location $runtimeDir
|
||||
$oldPath = $env:PATH
|
||||
try {
|
||||
$env:PATH = "$nodeDir;$env:PATH"
|
||||
Print-Info "Installing kokoro-js into app/tts-runtime..."
|
||||
& $npmCmd install --prefer-offline 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Print-Fail "kokoro-js install failed."
|
||||
exit 1
|
||||
}
|
||||
} finally {
|
||||
$env:PATH = $oldPath
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Print-OK "Kokoro TTS runtime is ready."
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Local AI Studio - Kokoro TTS setup for Linux/macOS
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
PLATFORM="$(uname -s)"
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
NODE_DIR="$TOOLS_DIR/node-mac"
|
||||
else
|
||||
NODE_DIR="$TOOLS_DIR/node-linux"
|
||||
fi
|
||||
|
||||
NODE_BIN="$NODE_DIR/bin/node"
|
||||
NPM_BIN="$NODE_DIR/bin/npm"
|
||||
RUNTIME_DIR="$APP_DIR/tts-runtime"
|
||||
MODELS_DIR="$APP_DIR/tts-models"
|
||||
OUTPUTS_DIR="$APP_DIR/tts-outputs"
|
||||
CACHE_DIR="$APP_DIR/tts-cache"
|
||||
|
||||
print_ok() { echo " OK $1"; }
|
||||
print_info() { echo " >> $1"; }
|
||||
print_fail() { echo " XX $1"; }
|
||||
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " Setting up Kokoro ONNX Text-to-Speech runtime"
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
|
||||
mkdir -p "$RUNTIME_DIR" "$MODELS_DIR" "$OUTPUTS_DIR" "$CACHE_DIR"
|
||||
|
||||
if [[ ! -x "$NODE_BIN" || ! -x "$NPM_BIN" ]]; then
|
||||
print_fail "Portable Node.js is missing. Run scripts/setup/setup.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$RUNTIME_DIR/package.json" ]]; then
|
||||
cat > "$RUNTIME_DIR/package.json" <<'JSON'
|
||||
{"private":true,"type":"module","dependencies":{"kokoro-js":"^1.2.1"}}
|
||||
JSON
|
||||
fi
|
||||
|
||||
cd "$RUNTIME_DIR"
|
||||
export PATH="$NODE_DIR/bin:$PATH"
|
||||
print_info "Installing kokoro-js into app/tts-runtime..."
|
||||
"$NPM_BIN" install --prefer-offline
|
||||
|
||||
print_ok "Kokoro TTS runtime is ready."
|
||||
@@ -0,0 +1,105 @@
|
||||
param([string]$Release = "v1.9.1")
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$speechWinRoot = Join-Path $appDir "speech-backend\win"
|
||||
$speechRoot = Join-Path $speechWinRoot "cpu"
|
||||
$speechVulkanRoot = Join-Path $speechWinRoot "vulkan"
|
||||
|
||||
function Enable-Tls12 {
|
||||
try {
|
||||
$tls12 = [Enum]::ToObject([Net.SecurityProtocolType], 3072)
|
||||
[Net.ServicePointManager]::SecurityProtocol =
|
||||
[Net.ServicePointManager]::SecurityProtocol -bor $tls12
|
||||
} catch {
|
||||
throw "TLS 1.2 could not be enabled: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function Download-File {
|
||||
param([string]$Url, [string]$DestPath, [string]$Label)
|
||||
Write-Host " >> Downloading $Label..."
|
||||
Enable-Tls12
|
||||
$client = New-Object System.Net.WebClient
|
||||
$client.Headers.Add("User-Agent", "Local-AI-Image-Generator")
|
||||
try {
|
||||
$client.DownloadFile($Url, $DestPath)
|
||||
} finally {
|
||||
$client.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Find-FirstFile {
|
||||
param([string]$Root, [string[]]$Names)
|
||||
foreach ($name in $Names) {
|
||||
$found = Get-ChildItem $Root -Recurse -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -ieq $name } |
|
||||
Select-Object -First 1
|
||||
if ($found) { return $found.FullName }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir, $speechRoot, $speechVulkanRoot | Out-Null
|
||||
|
||||
$cliPath = Join-Path $speechRoot "whisper-cli.exe"
|
||||
if (Test-Path $cliPath) {
|
||||
Write-Host " OK whisper.cpp speech backend already ready."
|
||||
if (-not (Test-Path (Join-Path $speechVulkanRoot "whisper-cli.exe"))) {
|
||||
Write-Host " .. Optional Vulkan GPU backend slot: app/speech-backend/win/vulkan"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
$legacyCli = Join-Path $speechWinRoot "whisper-cli.exe"
|
||||
$legacyServer = Join-Path $speechWinRoot "whisper-server.exe"
|
||||
if (Test-Path $legacyCli) {
|
||||
Copy-Item $legacyCli $cliPath -Force
|
||||
if (Test-Path $legacyServer) {
|
||||
Copy-Item $legacyServer (Join-Path $speechRoot "whisper-server.exe") -Force
|
||||
}
|
||||
Get-ChildItem $speechWinRoot -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -notin @("whisper-cli.exe", "whisper-server.exe") } |
|
||||
ForEach-Object { Copy-Item $_.FullName (Join-Path $speechRoot $_.Name) -Force }
|
||||
Write-Host " OK migrated existing whisper.cpp CPU backend to app/speech-backend/win/cpu."
|
||||
Write-Host " .. For GPU transcription, place a Vulkan-enabled whisper.cpp build in app/speech-backend/win/vulkan."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$asset = "whisper-bin-x64.zip"
|
||||
$archive = Join-Path $toolsDir $asset
|
||||
$extract = Join-Path $toolsDir "whisper-extract"
|
||||
$url = "https://github.com/ggml-org/whisper.cpp/releases/download/$Release/$asset"
|
||||
|
||||
Remove-Item $archive, $extract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Download-File -Url $url -DestPath $archive -Label "whisper.cpp speech backend ($Release)"
|
||||
|
||||
Write-Host " >> Extracting whisper.cpp speech backend..."
|
||||
Expand-Archive -Path $archive -DestinationPath $extract -Force
|
||||
|
||||
$cli = Find-FirstFile -Root $extract -Names @("whisper-cli.exe", "main.exe")
|
||||
$server = Find-FirstFile -Root $extract -Names @("whisper-server.exe", "server.exe")
|
||||
if (-not $cli) {
|
||||
throw "whisper-cli.exe was not found after extracting $asset"
|
||||
}
|
||||
|
||||
Get-ChildItem $extract -Recurse -File | ForEach-Object {
|
||||
Copy-Item $_.FullName (Join-Path $speechRoot $_.Name) -Force
|
||||
}
|
||||
if ((Split-Path -Leaf $cli) -ine "whisper-cli.exe") {
|
||||
Copy-Item $cli $cliPath -Force
|
||||
}
|
||||
if ($server -and (Split-Path -Leaf $server) -ine "whisper-server.exe") {
|
||||
Copy-Item $server (Join-Path $speechRoot "whisper-server.exe") -Force
|
||||
}
|
||||
|
||||
Remove-Item $archive, $extract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (-not (Test-Path $cliPath)) {
|
||||
throw "whisper-cli.exe was not installed."
|
||||
}
|
||||
Write-Host " OK whisper.cpp speech backend installed."
|
||||
Write-Host " .. CPU backend path: app/speech-backend/win/cpu"
|
||||
Write-Host " .. Optional Vulkan GPU backend path: app/speech-backend/win/vulkan"
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
RELEASE="${WHISPER_RELEASE:-v1.9.1}"
|
||||
PLATFORM="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
download_and_extract() {
|
||||
local asset="$1"
|
||||
local dest="$2"
|
||||
local archive="$TOOLS_DIR/$asset"
|
||||
local url="https://github.com/ggml-org/whisper.cpp/releases/download/$RELEASE/$asset"
|
||||
|
||||
if [[ -x "$dest/whisper-cli" ]]; then
|
||||
echo " OK whisper.cpp speech backend already ready: $dest"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$TOOLS_DIR" "$dest"
|
||||
rm -f "$archive" "$archive.part"
|
||||
echo " >> Downloading $asset"
|
||||
curl -fSL --progress-bar "$url" -o "$archive.part"
|
||||
mv "$archive.part" "$archive"
|
||||
tar -xzf "$archive" -C "$dest" --strip-components=1
|
||||
rm -f "$archive"
|
||||
chmod +x "$dest"/whisper-* "$dest"/main "$dest"/server 2>/dev/null || true
|
||||
|
||||
if [[ ! -x "$dest/whisper-cli" && -x "$dest/main" ]]; then
|
||||
cp "$dest/main" "$dest/whisper-cli"
|
||||
chmod +x "$dest/whisper-cli"
|
||||
fi
|
||||
if [[ ! -x "$dest/whisper-server" && -x "$dest/server" ]]; then
|
||||
cp "$dest/server" "$dest/whisper-server"
|
||||
chmod +x "$dest/whisper-server"
|
||||
fi
|
||||
|
||||
if [[ ! -x "$dest/whisper-cli" ]]; then
|
||||
echo " XX whisper-cli was not found after extracting $asset" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||
mkdir -p "$APP_DIR/speech-backend/linux/cpu" "$APP_DIR/speech-backend/linux/vulkan"
|
||||
if [[ ! -x "$APP_DIR/speech-backend/linux/cpu/whisper-cli" && -x "$APP_DIR/speech-backend/linux/whisper-cli" ]]; then
|
||||
cp "$APP_DIR/speech-backend/linux"/whisper-* "$APP_DIR/speech-backend/linux/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/linux"/main "$APP_DIR/speech-backend/linux/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/linux"/server "$APP_DIR/speech-backend/linux/cpu/" 2>/dev/null || true
|
||||
chmod +x "$APP_DIR/speech-backend/linux/cpu"/whisper-* "$APP_DIR/speech-backend/linux/cpu"/main "$APP_DIR/speech-backend/linux/cpu"/server 2>/dev/null || true
|
||||
echo " OK migrated existing whisper.cpp CPU backend to app/speech-backend/linux/cpu."
|
||||
fi
|
||||
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
|
||||
download_and_extract "whisper-bin-ubuntu-arm64.tar.gz" "$APP_DIR/speech-backend/linux/cpu"
|
||||
else
|
||||
download_and_extract "whisper-bin-ubuntu-x64.tar.gz" "$APP_DIR/speech-backend/linux/cpu"
|
||||
fi
|
||||
echo " .. CPU backend path: app/speech-backend/linux/cpu"
|
||||
echo " .. Optional Vulkan GPU backend path: app/speech-backend/linux/vulkan"
|
||||
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
mkdir -p "$APP_DIR/speech-backend/mac/cpu" "$APP_DIR/speech-backend/mac/metal"
|
||||
if [[ ! -x "$APP_DIR/speech-backend/mac/cpu/whisper-cli" && -x "$APP_DIR/speech-backend/mac/whisper-cli" ]]; then
|
||||
cp "$APP_DIR/speech-backend/mac"/whisper-* "$APP_DIR/speech-backend/mac/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/mac"/main "$APP_DIR/speech-backend/mac/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/mac"/server "$APP_DIR/speech-backend/mac/cpu/" 2>/dev/null || true
|
||||
chmod +x "$APP_DIR/speech-backend/mac/cpu"/whisper-* "$APP_DIR/speech-backend/mac/cpu"/main "$APP_DIR/speech-backend/mac/cpu"/server 2>/dev/null || true
|
||||
echo " OK migrated existing whisper.cpp backend to app/speech-backend/mac/cpu."
|
||||
fi
|
||||
if [[ -x "$APP_DIR/speech-backend/mac/cpu/whisper-cli" || -x "$APP_DIR/speech-backend/mac/metal/whisper-cli" ]]; then
|
||||
echo " OK whisper.cpp macOS speech backend already ready."
|
||||
else
|
||||
echo " !! No official portable macOS whisper.cpp CLI archive is available for this setup script yet."
|
||||
echo " Build whisper.cpp manually and copy whisper-cli to app/speech-backend/mac/cpu or app/speech-backend/mac/metal."
|
||||
fi
|
||||
else
|
||||
echo "Unsupported platform: $PLATFORM" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,519 @@
|
||||
# Local AI Image Generator - Setup Script
|
||||
# scripts/setup/ lives under root, app/ is a root sibling of scripts/
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$frontendDir = Join-Path $appDir "frontend"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$nodeDir = Join-Path $toolsDir "node-win"
|
||||
$nodeExe = Join-Path $nodeDir "node.exe"
|
||||
$npmCmd = Join-Path $nodeDir "npm.cmd"
|
||||
$distDir = Join-Path $appDir "dist"
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
function Print-Header {
|
||||
Write-Host ""
|
||||
Write-Host " ============================================================" -ForegroundColor Cyan
|
||||
Write-Host " LOCAL AI IMAGE GENERATOR - First-Time Setup" -ForegroundColor Cyan
|
||||
Write-Host " 100% Self-Contained | No System Install Required" -ForegroundColor DarkCyan
|
||||
Write-Host " ============================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Print-Step {
|
||||
param([int]$n, [int]$total, [string]$title)
|
||||
Write-Host ""
|
||||
Write-Host " [$n/$total] $title" -ForegroundColor White
|
||||
Write-Host (" " + "-" * 56) -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
function Print-OK { param([string]$m); Write-Host " OK $m" -ForegroundColor Green }
|
||||
function Print-Info { param([string]$m); Write-Host " >> $m" -ForegroundColor Cyan }
|
||||
function Print-Warn { param([string]$m); Write-Host " !! $m" -ForegroundColor Yellow }
|
||||
function Print-Fail { param([string]$m); Write-Host " XX $m" -ForegroundColor Red }
|
||||
|
||||
function Format-Bytes {
|
||||
param([long]$b)
|
||||
if ($b -gt 1GB) { return "{0:N2} GB" -f ($b / 1GB) }
|
||||
if ($b -gt 1MB) { return "{0:N1} MB" -f ($b / 1MB) }
|
||||
return "{0:N0} KB" -f ($b / 1KB)
|
||||
}
|
||||
|
||||
function Format-Speed {
|
||||
param([double]$bps)
|
||||
if ($bps -gt 1MB) { return "{0:N1} MB/s" -f ($bps / 1MB) }
|
||||
return "{0:N0} KB/s" -f ($bps / 1KB)
|
||||
}
|
||||
|
||||
function Enable-Tls12 {
|
||||
try {
|
||||
# Use the protocol's numeric value because older .NET enum definitions
|
||||
# do not expose the Tls12 member even when Windows supports TLS 1.2.
|
||||
$tls12 = [Enum]::ToObject([Net.SecurityProtocolType], 3072)
|
||||
[Net.ServicePointManager]::SecurityProtocol =
|
||||
[Net.ServicePointManager]::SecurityProtocol -bor $tls12
|
||||
} catch {
|
||||
throw "TLS 1.2 could not be enabled. This setup requires 64-bit Windows 10 or Windows 11 with current system updates. $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-RichDownload {
|
||||
param([string]$Url, [string]$Dest, [string]$Label)
|
||||
Print-Info "Downloading: $Label"
|
||||
Write-Host ""
|
||||
|
||||
$barWidth = 48
|
||||
$lastBytes = [long]0
|
||||
$lastTime = [DateTime]::Now
|
||||
|
||||
try {
|
||||
Enable-Tls12
|
||||
$req = [System.Net.HttpWebRequest]::Create($Url)
|
||||
$req.UserAgent = "Mozilla/5.0"
|
||||
$resp = $req.GetResponse()
|
||||
$total = [long]$resp.ContentLength
|
||||
$stream = $resp.GetResponseStream()
|
||||
$out = [System.IO.File]::Create($Dest)
|
||||
$buf = New-Object byte[] 65536
|
||||
$done = [long]0
|
||||
|
||||
while ($true) {
|
||||
$read = $stream.Read($buf, 0, $buf.Length)
|
||||
if ($read -le 0) { break }
|
||||
$out.Write($buf, 0, $read)
|
||||
$done += $read
|
||||
|
||||
$now = [DateTime]::Now
|
||||
$elapsed = ($now - $lastTime).TotalSeconds
|
||||
if ($elapsed -ge 0.35) {
|
||||
$speed = ($done - $lastBytes) / $elapsed
|
||||
$lastBytes = $done
|
||||
$lastTime = $now
|
||||
$pct = if ($total -gt 0) { [int](($done / $total) * 100) } else { 0 }
|
||||
$fill = [int](($pct / 100) * $barWidth)
|
||||
$bar = ("#" * $fill) + ("-" * ($barWidth - $fill))
|
||||
|
||||
$eta = ""
|
||||
if ($speed -gt 0 -and $total -gt 0) {
|
||||
$rem = [int](($total - $done) / $speed)
|
||||
$eta = " ETA $([int]($rem/60))m$($rem%60)s"
|
||||
}
|
||||
|
||||
$dl = Format-Bytes $done
|
||||
$tot = if ($total -gt 0) { " / " + (Format-Bytes $total) } else { "" }
|
||||
$spd = Format-Speed $speed
|
||||
Write-Host -NoNewline "`r [$bar] $pct% $dl$tot $spd$eta "
|
||||
}
|
||||
}
|
||||
|
||||
$out.Close(); $stream.Close()
|
||||
Write-Host "`r [$("#" * $barWidth)] 100% $(Format-Bytes $done) Done! " -ForegroundColor Green
|
||||
Write-Host ""
|
||||
return $true
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Print-Fail "Download failed: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Expand-WithProgress {
|
||||
param([string]$ZipPath, [string]$Destination, [string]$Label)
|
||||
Print-Info "Extracting $Label..."
|
||||
Write-Host ""
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
$zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
|
||||
$total = $zip.Entries.Count
|
||||
$barW = 48
|
||||
$i = 0
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
|
||||
|
||||
foreach ($entry in $zip.Entries) {
|
||||
$i++
|
||||
$pct = [int](($i / $total) * 100)
|
||||
$fill = [int](($pct / 100) * $barW)
|
||||
$bar = ("#" * $fill) + ("-" * ($barW - $fill))
|
||||
$name = $entry.Name
|
||||
if ($name.Length -gt 28) { $name = "..." + $name.Substring($name.Length - 28) }
|
||||
Write-Host -NoNewline "`r [$bar] $pct% $name "
|
||||
|
||||
if ($entry.FullName.EndsWith("/") -or $entry.FullName.EndsWith("\")) {
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $Destination $entry.FullName) | Out-Null
|
||||
} else {
|
||||
$destFile = Join-Path $Destination $entry.FullName
|
||||
$destDir = Split-Path -Parent $destFile
|
||||
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destFile, $true)
|
||||
}
|
||||
}
|
||||
|
||||
$zip.Dispose()
|
||||
Write-Host "`r [$("#" * $barW)] 100% $total files extracted! " -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
Print-Header
|
||||
|
||||
$steps = 7
|
||||
|
||||
# ── Step 1: Portable Node.js ──────────────────────────────────────────────────
|
||||
Print-Step 1 $steps "Setting up portable Node.js (app/tools/node-win/)"
|
||||
|
||||
$nodeReady = $false
|
||||
if ((Test-Path $nodeExe) -and (Test-Path $npmCmd)) {
|
||||
try {
|
||||
$v = & $nodeExe --version
|
||||
# Test if npm is functional (doesn't throw parsing/unexpected token errors)
|
||||
$npmTest = & $nodeExe (Join-Path $nodeDir "node_modules\npm\bin\npm-cli.js") --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0 -and $npmTest -match "^\d+\.\d+\.\d+") {
|
||||
$nodeReady = $true
|
||||
Print-OK "Portable Node.js already ready: $v"
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (-not $nodeReady) {
|
||||
if (Test-Path $nodeDir) {
|
||||
Print-Warn "Portable Node.js installation is corrupted or incomplete. Cleaning up..."
|
||||
Remove-Item $nodeDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$nodeZip = Join-Path $toolsDir "node.zip"
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
|
||||
|
||||
$ok = Invoke-RichDownload `
|
||||
-Url "https://nodejs.org/dist/v22.12.0/node-v22.12.0-win-x64.zip" `
|
||||
-Dest $nodeZip `
|
||||
-Label "Node.js v22.12.0 LTS (Portable ZIP)"
|
||||
|
||||
if (-not $ok) { Print-Fail "Cannot download Node.js."; Read-Host; exit 1 }
|
||||
|
||||
Expand-WithProgress -ZipPath $nodeZip -Destination $toolsDir -Label "Node.js"
|
||||
Remove-Item $nodeZip -Force
|
||||
|
||||
$extracted = Get-ChildItem $toolsDir -Directory | Where-Object { $_.Name -like "node-v*" } | Select-Object -First 1
|
||||
if ($extracted) {
|
||||
if (Test-Path $nodeDir) { Remove-Item $nodeDir -Recurse -Force }
|
||||
Rename-Item $extracted.FullName "node-win"
|
||||
}
|
||||
|
||||
# Pause briefly to allow USB drive writes to flush
|
||||
Print-Info "Waiting for disk to flush..."
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Re-verify
|
||||
$nodeReady = $false
|
||||
if ((Test-Path $nodeExe) -and (Test-Path $npmCmd)) {
|
||||
try {
|
||||
$v = & $nodeExe --version
|
||||
$npmTest = & $nodeExe (Join-Path $nodeDir "node_modules\npm\bin\npm-cli.js") --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0 -and $npmTest -match "^\d+\.\d+\.\d+") {
|
||||
$nodeReady = $true
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (-not $nodeReady) {
|
||||
Print-Fail "Portable Node.js install is incomplete or corrupted. Close any running Local AI Image Generator windows, delete app/tools/node-win, then run setup again."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
|
||||
Print-OK "Portable Node.js ready: $v"
|
||||
}
|
||||
|
||||
# ── Step 2: stable-diffusion.cpp GPU Backend (Dynamic Detection) ──────────────
|
||||
$hasNvidia = $false
|
||||
try {
|
||||
$gpus = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue
|
||||
foreach ($gpu in $gpus) {
|
||||
if ($gpu.Name -like "*NVIDIA*") {
|
||||
$hasNvidia = $true
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
if (-not $hasNvidia) {
|
||||
try {
|
||||
& nvidia-smi *> $null
|
||||
if ($LASTEXITCODE -eq 0) { $hasNvidia = $true }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if ($hasNvidia) {
|
||||
Print-Step 2 $steps "Setting up stable-diffusion.cpp CUDA GPU backend (app/backend/win/cuda/)"
|
||||
$backendDest = Join-Path $appDir "backend\win\cuda"
|
||||
$backendExe = Join-Path $backendDest "sd-cuda.exe"
|
||||
$backendDll = Join-Path $backendDest "stable-diffusion.dll"
|
||||
|
||||
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
|
||||
Print-OK "CUDA GPU backend binaries already ready."
|
||||
} else {
|
||||
$backendZip = Join-Path $toolsDir "sd-cuda.zip"
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $backendDest | Out-Null
|
||||
|
||||
$ok = Invoke-RichDownload `
|
||||
-Url "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-cuda12-x64.zip" `
|
||||
-Dest $backendZip `
|
||||
-Label "stable-diffusion.cpp CUDA Backend (Windows x64)"
|
||||
|
||||
if (-not $ok) { Print-Fail "Cannot download CUDA backend binaries."; Read-Host; exit 1 }
|
||||
|
||||
$tempExt = Join-Path $toolsDir "sd-cuda-temp"
|
||||
Expand-WithProgress -ZipPath $backendZip -Destination $tempExt -Label "CUDA Backend"
|
||||
Remove-Item $backendZip -Force
|
||||
|
||||
# Move files and rename sd.exe/sd-server.exe to sd-cuda.exe
|
||||
if (Test-Path $tempExt) {
|
||||
$extractedExe = Join-Path $tempExt "bin\sd-server.exe"
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd-server.exe" }
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "bin\sd.exe" }
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd.exe" }
|
||||
|
||||
$extractedDll = Join-Path $tempExt "bin\stable-diffusion.dll"
|
||||
if (-not (Test-Path $extractedDll)) { $extractedDll = Join-Path $tempExt "stable-diffusion.dll" }
|
||||
|
||||
if (Test-Path $extractedExe) { Copy-Item $extractedExe $backendExe -Force }
|
||||
if (Test-Path $extractedDll) { Copy-Item $extractedDll $backendDll -Force }
|
||||
|
||||
# Copy any other DLLs or EXEs
|
||||
Get-ChildItem $tempExt -Filter "*.dll" -Recurse | ForEach-Object { Copy-Item $_.FullName $backendDest -Force }
|
||||
Get-ChildItem $tempExt -Filter "*.exe" -Recurse | ForEach-Object {
|
||||
if ($_.FullName -ne $extractedExe) { Copy-Item $_.FullName $backendDest -Force }
|
||||
}
|
||||
Remove-Item $tempExt -Recurse -Force
|
||||
}
|
||||
|
||||
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
|
||||
Print-OK "CUDA GPU backend binaries installed successfully!"
|
||||
} else {
|
||||
Print-Fail "Failed to copy backend binaries to app/backend/win/cuda/."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$cudaDllsExist = (Test-Path (Join-Path $backendDest "cublas64_12.dll")) -and `
|
||||
(Test-Path (Join-Path $backendDest "cublasLt64_12.dll")) -and `
|
||||
(Test-Path (Join-Path $backendDest "cudart64_12.dll"))
|
||||
|
||||
if (-not $cudaDllsExist) {
|
||||
Print-Info "CUDA runtime DLLs are missing from backend folder. Downloading portable CUDA v12 runtime..."
|
||||
$dllZip = Join-Path $toolsDir "cuda-dlls.zip"
|
||||
$ok = Invoke-RichDownload `
|
||||
-Url "https://github.com/ggml-org/llama.cpp/releases/download/b9509/cudart-llama-bin-win-cuda-12.4-x64.zip" `
|
||||
-Dest $dllZip `
|
||||
-Label "CUDA v12 Runtime DLLs (llama.cpp)"
|
||||
|
||||
if ($ok) {
|
||||
Expand-WithProgress -ZipPath $dllZip -Destination $backendDest -Label "CUDA Runtime DLLs"
|
||||
Remove-Item $dllZip -Force
|
||||
Print-OK "CUDA runtime DLLs set up successfully!"
|
||||
} else {
|
||||
Print-Warn "Could not download portable CUDA runtime DLLs automatically. If the app fails to start in CUDA mode, you may need to install the CUDA Toolkit manually."
|
||||
}
|
||||
}
|
||||
|
||||
Print-Step 2 $steps "Setting up stable-diffusion.cpp Vulkan GPU backend for comparison (app/backend/win/vulkan/)"
|
||||
$backendDest = Join-Path $appDir "backend\win\vulkan"
|
||||
$backendExe = Join-Path $backendDest "sd-vulkan.exe"
|
||||
$backendDll = Join-Path $backendDest "stable-diffusion.dll"
|
||||
|
||||
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
|
||||
Print-OK "Vulkan GPU backend binaries already ready."
|
||||
} else {
|
||||
$backendZip = Join-Path $toolsDir "sd-vulkan.zip"
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $backendDest | Out-Null
|
||||
|
||||
$ok = Invoke-RichDownload `
|
||||
-Url "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-vulkan-x64.zip" `
|
||||
-Dest $backendZip `
|
||||
-Label "stable-diffusion.cpp Vulkan Backend (Windows x64)"
|
||||
|
||||
if (-not $ok) { Print-Fail "Cannot download Vulkan backend binaries."; Read-Host; exit 1 }
|
||||
|
||||
$tempExt = Join-Path $toolsDir "sd-vulkan-temp"
|
||||
Expand-WithProgress -ZipPath $backendZip -Destination $tempExt -Label "Vulkan Backend"
|
||||
Remove-Item $backendZip -Force
|
||||
|
||||
if (Test-Path $tempExt) {
|
||||
$extractedExe = Join-Path $tempExt "bin\sd-server.exe"
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd-server.exe" }
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "bin\sd.exe" }
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd.exe" }
|
||||
|
||||
$extractedDll = Join-Path $tempExt "bin\stable-diffusion.dll"
|
||||
if (-not (Test-Path $extractedDll)) { $extractedDll = Join-Path $tempExt "stable-diffusion.dll" }
|
||||
|
||||
if (Test-Path $extractedExe) { Copy-Item $extractedExe $backendExe -Force }
|
||||
if (Test-Path $extractedDll) { Copy-Item $extractedDll $backendDll -Force }
|
||||
|
||||
Get-ChildItem $tempExt -Filter "*.dll" -Recurse | ForEach-Object { Copy-Item $_.FullName $backendDest -Force }
|
||||
Get-ChildItem $tempExt -Filter "*.exe" -Recurse | ForEach-Object {
|
||||
if ($_.FullName -ne $extractedExe) { Copy-Item $_.FullName $backendDest -Force }
|
||||
}
|
||||
Remove-Item $tempExt -Recurse -Force
|
||||
}
|
||||
|
||||
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
|
||||
Print-OK "Vulkan GPU backend binaries installed successfully!"
|
||||
} else {
|
||||
Print-Fail "Failed to copy backend binaries to app/backend/win/vulkan/."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Print-Step 2 $steps "Setting up stable-diffusion.cpp Vulkan GPU backend (app/backend/win/vulkan/)"
|
||||
$backendDest = Join-Path $appDir "backend\win\vulkan"
|
||||
$backendExe = Join-Path $backendDest "sd-vulkan.exe"
|
||||
$backendDll = Join-Path $backendDest "stable-diffusion.dll"
|
||||
|
||||
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
|
||||
Print-OK "Vulkan GPU backend binaries already ready."
|
||||
} else {
|
||||
$backendZip = Join-Path $toolsDir "sd-vulkan.zip"
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $backendDest | Out-Null
|
||||
|
||||
$ok = Invoke-RichDownload `
|
||||
-Url "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-vulkan-x64.zip" `
|
||||
-Dest $backendZip `
|
||||
-Label "stable-diffusion.cpp Vulkan Backend (Windows x64)"
|
||||
|
||||
if (-not $ok) { Print-Fail "Cannot download Vulkan backend binaries."; Read-Host; exit 1 }
|
||||
|
||||
$tempExt = Join-Path $toolsDir "sd-vulkan-temp"
|
||||
Expand-WithProgress -ZipPath $backendZip -Destination $tempExt -Label "Vulkan Backend"
|
||||
Remove-Item $backendZip -Force
|
||||
|
||||
# Move files and rename sd.exe/sd-server.exe to sd-vulkan.exe
|
||||
if (Test-Path $tempExt) {
|
||||
$extractedExe = Join-Path $tempExt "bin\sd-server.exe"
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd-server.exe" }
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "bin\sd.exe" }
|
||||
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd.exe" }
|
||||
|
||||
$extractedDll = Join-Path $tempExt "bin\stable-diffusion.dll"
|
||||
if (-not (Test-Path $extractedDll)) { $extractedDll = Join-Path $tempExt "stable-diffusion.dll" }
|
||||
|
||||
if (Test-Path $extractedExe) { Copy-Item $extractedExe $backendExe -Force }
|
||||
if (Test-Path $extractedDll) { Copy-Item $extractedDll $backendDll -Force }
|
||||
|
||||
# Copy any other DLLs or EXEs
|
||||
Get-ChildItem $tempExt -Filter "*.dll" -Recurse | ForEach-Object { Copy-Item $_.FullName $backendDest -Force }
|
||||
Get-ChildItem $tempExt -Filter "*.exe" -Recurse | ForEach-Object {
|
||||
if ($_.FullName -ne $extractedExe) { Copy-Item $_.FullName $backendDest -Force }
|
||||
}
|
||||
Remove-Item $tempExt -Recurse -Force
|
||||
}
|
||||
|
||||
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
|
||||
Print-OK "Vulkan GPU backend binaries installed successfully!"
|
||||
} else {
|
||||
Print-Fail "Failed to copy backend binaries to app/backend/win/vulkan/."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Step 3: npm install ───────────────────────────────────────────────────────
|
||||
Print-Step 3 $steps "Setting up llama.cpp text backends (Vulkan + CPU)"
|
||||
& (Join-Path $scriptDir "setup-llama.ps1")
|
||||
if (-not $?) {
|
||||
Print-Fail "llama.cpp setup failed."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
|
||||
Print-Step 4 $steps "Setting up whisper.cpp speech backend"
|
||||
& (Join-Path $scriptDir "setup-whisper.ps1")
|
||||
if (-not $?) {
|
||||
Print-Fail "whisper.cpp setup failed."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
|
||||
Print-Step 5 $steps "Setting up Kokoro ONNX text-to-speech runtime"
|
||||
& (Join-Path $scriptDir "setup-tts.ps1")
|
||||
if (-not $?) {
|
||||
Print-Fail "Kokoro TTS setup failed."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
|
||||
Print-Step 6 $steps "Installing frontend dependencies (app/frontend/)"
|
||||
Write-Host ""
|
||||
|
||||
if (-not (Test-Path $npmCmd)) {
|
||||
Print-Fail "npm.cmd was not found at $npmCmd"
|
||||
Print-Fail "Close any running Local AI Image Generator windows, delete app/tools/node-win, then run setup again."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
|
||||
# If node_modules is a Unix symlink/junction, remove it so Windows can install natively into a real directory
|
||||
$nodeModulesDir = Join-Path $frontendDir "node_modules"
|
||||
$activeOsFile = Join-Path $frontendDir ".active_modules_os"
|
||||
$prevOs = ""
|
||||
if (Test-Path $activeOsFile) {
|
||||
$prevOs = (Get-Content $activeOsFile -Raw).Trim()
|
||||
}
|
||||
|
||||
if (Test-Path $nodeModulesDir) {
|
||||
$item = Get-Item $nodeModulesDir
|
||||
if ($item.Attributes -match "ReparsePoint") {
|
||||
Print-Info "Removing Unix symlink for node_modules on Windows..."
|
||||
Remove-Item $nodeModulesDir -Force -ErrorAction SilentlyContinue
|
||||
} elseif ($prevOs -ne "windows" -and $prevOs -ne "") {
|
||||
# It's a real folder, but it belongs to another OS (e.g. linux or mac)
|
||||
Print-Info "Swapping out node_modules to node_modules_$prevOs..."
|
||||
$targetDir = Join-Path $frontendDir "node_modules_$prevOs"
|
||||
if (Test-Path $targetDir) { Remove-Item $targetDir -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
Move-Item $nodeModulesDir $targetDir -Force
|
||||
}
|
||||
}
|
||||
|
||||
# Now swap in node_modules_windows if it exists
|
||||
$winModulesDir = Join-Path $frontendDir "node_modules_windows"
|
||||
if ((Test-Path $winModulesDir) -and -not (Test-Path $nodeModulesDir)) {
|
||||
Print-Info "Swapping in node_modules_windows..."
|
||||
Move-Item $winModulesDir $nodeModulesDir -Force
|
||||
}
|
||||
|
||||
# Mark windows as active
|
||||
"windows" | Out-File -FilePath $activeOsFile -NoNewline -Encoding utf8
|
||||
|
||||
Push-Location $frontendDir
|
||||
$oldPath = $env:PATH
|
||||
try {
|
||||
$env:PATH = "$nodeDir;$env:PATH"
|
||||
& $npmCmd install --prefer-offline 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Print-Fail "npm install failed."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
Write-Host ""
|
||||
Print-OK "Dependencies installed!"
|
||||
|
||||
# ── Step 4: Build frontend ────────────────────────────────────────────────
|
||||
Print-Step 7 $steps "Building frontend -> app/dist/"
|
||||
Write-Host ""
|
||||
|
||||
& $npmCmd run build 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Print-Fail "Frontend build failed."
|
||||
Read-Host; exit 1
|
||||
}
|
||||
Write-Host ""
|
||||
Print-OK "Frontend built!"
|
||||
} finally {
|
||||
$env:PATH = $oldPath
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# ── Done ─────────────────────────────────────────────────────────────────────
|
||||
Write-Host ""
|
||||
Write-Host " ============================================================" -ForegroundColor Green
|
||||
Write-Host " Setup complete! Just double-click windows.bat to launch." -ForegroundColor Green
|
||||
Write-Host " ============================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Read-Host " Press Enter to close..."
|
||||
@@ -0,0 +1,685 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Local AI Image Generator - Linux/macOS Setup Script
|
||||
# Self-contained: no apt/yum/pacman, no global Node.js install.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
FRONTEND_DIR="$APP_DIR/frontend"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
DIST_DIR="$APP_DIR/dist"
|
||||
PLATFORM="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
PLATFORM_LABEL="macOS"
|
||||
NODE_DIR="$TOOLS_DIR/node-mac"
|
||||
BACKEND_DIR="$APP_DIR/backend/mac"
|
||||
else
|
||||
PLATFORM_LABEL="Linux"
|
||||
NODE_DIR="$TOOLS_DIR/node-linux"
|
||||
BACKEND_DIR="$APP_DIR/backend/linux"
|
||||
fi
|
||||
|
||||
NODE_BIN="$NODE_DIR/bin/node"
|
||||
NPM_BIN="$NODE_DIR/bin/npm"
|
||||
|
||||
# Check if filesystem supports symlinks
|
||||
USE_SYMLINKS=true
|
||||
TEST_LINK="$ROOT_DIR/.test_symlink"
|
||||
rm -f "$TEST_LINK"
|
||||
if ln -s "test" "$TEST_LINK" 2>/dev/null; then
|
||||
rm -f "$TEST_LINK"
|
||||
else
|
||||
USE_SYMLINKS=false
|
||||
fi
|
||||
|
||||
# Release pins
|
||||
SD_RELEASE="master-685-19bdfe2"
|
||||
SD_SHORT_HASH="${SD_RELEASE##*-}"
|
||||
SD_BASE_URL="https://github.com/leejet/stable-diffusion.cpp/releases/download/$SD_RELEASE"
|
||||
NODE_VERSION="22.12.0"
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
if [[ "$ARCH" == "arm64" ]]; then
|
||||
NODE_PLATFORM_ARCH="darwin-arm64"
|
||||
else
|
||||
NODE_PLATFORM_ARCH="darwin-x64"
|
||||
fi
|
||||
NODE_TARBALL="node-v${NODE_VERSION}-${NODE_PLATFORM_ARCH}.tar.gz"
|
||||
else
|
||||
NODE_PLATFORM_ARCH="linux-x64"
|
||||
NODE_TARBALL="node-v${NODE_VERSION}-${NODE_PLATFORM_ARCH}.tar.xz"
|
||||
fi
|
||||
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/$NODE_TARBALL"
|
||||
|
||||
# Flags
|
||||
MAX_PERF=0
|
||||
if [[ "${1:-}" == "--max-perf" ]]; then
|
||||
MAX_PERF=1
|
||||
fi
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
print_header() {
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " LOCAL AI IMAGE GENERATOR - $PLATFORM_LABEL First-Time Setup"
|
||||
echo " 100% Self-Contained | No System Install Required"
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
}
|
||||
|
||||
print_step() {
|
||||
local n="$1" total="$2" title="$3"
|
||||
echo ""
|
||||
echo " [$n/$total] $title"
|
||||
echo " ------------------------------------------------------------"
|
||||
}
|
||||
|
||||
print_ok() { echo " OK $1"; }
|
||||
print_info() { echo " >> $1"; }
|
||||
print_warn() { echo " !! $1"; }
|
||||
print_fail() { echo " XX $1"; }
|
||||
|
||||
format_bytes() {
|
||||
local b="${1:-0}"
|
||||
if command -v bc >/dev/null 2>&1; then
|
||||
if (( b > 1073741824 )); then printf "%.2f GB" "$(echo "scale=4; $b / 1073741824" | bc)"; return; fi
|
||||
if (( b > 1048576 )); then printf "%.1f MB" "$(echo "scale=4; $b / 1048576" | bc)"; return; fi
|
||||
if (( b > 1024 )); then printf "%.0f KB" "$(echo "scale=4; $b / 1024" | bc)"; return; fi
|
||||
else
|
||||
if (( b > 1073741824 )); then printf "%.2f GB" "$(awk "BEGIN {printf \"%.2f\", $b/1073741824}")"; return; fi
|
||||
if (( b > 1048576 )); then printf "%.1f MB" "$(awk "BEGIN {printf \"%.1f\", $b/1048576}")"; return; fi
|
||||
if (( b > 1024 )); then printf "%.0f KB" "$(awk "BEGIN {printf \"%.0f\", $b/1024}")"; return; fi
|
||||
fi
|
||||
printf "%s B" "$b"
|
||||
}
|
||||
|
||||
# Rich download with progress bar
|
||||
download_file() {
|
||||
local url="$1" dest="$2" label="$3"
|
||||
print_info "Downloading: $label"
|
||||
echo ""
|
||||
|
||||
local tmp_dest="${dest}.part"
|
||||
rm -f "$tmp_dest"
|
||||
|
||||
curl -fSL --progress-bar "$url" -o "$tmp_dest" || {
|
||||
print_fail "Download failed: $url"
|
||||
rm -f "$tmp_dest"
|
||||
return 1
|
||||
}
|
||||
|
||||
mv "$tmp_dest" "$dest"
|
||||
echo ""
|
||||
local fsize
|
||||
fsize="$(stat -c%s "$dest" 2>/dev/null || stat -f%z "$dest" 2>/dev/null || echo 0)"
|
||||
print_ok "Downloaded $(format_bytes "$fsize")"
|
||||
}
|
||||
|
||||
# Extract ZIP using Python zipfile (preferred), unzip, or Node.js adm-zip
|
||||
extract_zip() {
|
||||
local zip_path="$1" dest="$2" label="$3"
|
||||
print_info "Extracting: $label"
|
||||
mkdir -p "$dest"
|
||||
|
||||
# Prefer Python zipfile (most reliable, no host deps beyond python3)
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 -c "
|
||||
import zipfile, sys, os
|
||||
with zipfile.ZipFile(sys.argv[1], 'r') as z:
|
||||
for member in z.namelist():
|
||||
z.extract(member, sys.argv[2])
|
||||
" "$zip_path" "$dest" && return 0
|
||||
fi
|
||||
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
unzip -o -q "$zip_path" -d "$dest" && return 0
|
||||
fi
|
||||
|
||||
if command -v python >/dev/null 2>&1; then
|
||||
python -c "import zipfile, sys; zipfile.ZipFile(sys.argv[1], 'r').extractall(sys.argv[2])" "$zip_path" "$dest" && return 0
|
||||
fi
|
||||
|
||||
print_fail "No ZIP extractor available (tried python3, unzip, python)."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Extract tar.xz
|
||||
extract_tarxz() {
|
||||
local tar_path="$1" dest="$2" label="$3"
|
||||
print_info "Extracting: $label"
|
||||
mkdir -p "$dest"
|
||||
if [ "$USE_SYMLINKS" = false ]; then
|
||||
# On filesystems that do not support symlinks, tar will fail when attempting to create symlinks
|
||||
# (e.g. for npm/npx/corepack in Node.js bin/). We allow it to continue but verify key files.
|
||||
local tar_exit=0
|
||||
tar -xf "$tar_path" -C "$dest" || tar_exit=$?
|
||||
if [[ $tar_exit -ne 0 ]]; then
|
||||
# Check if node binary was extracted successfully
|
||||
local check_file
|
||||
check_file="$(find "$dest" -type f -name "node" | head -n 1)"
|
||||
if [[ -n "$check_file" ]]; then
|
||||
print_warn "tar returned exit code $tar_exit due to symlink failures on this filesystem, but bin/node was successfully extracted."
|
||||
else
|
||||
print_fail "tar extraction failed with exit code $tar_exit (could not find bin/node)"
|
||||
exit $tar_exit
|
||||
fi
|
||||
fi
|
||||
else
|
||||
tar -xf "$tar_path" -C "$dest"
|
||||
fi
|
||||
print_ok "Extracted $label"
|
||||
}
|
||||
|
||||
detect_gpu_vendor() {
|
||||
local vendor=""
|
||||
if [[ -d /sys/bus/pci/devices ]]; then
|
||||
for vfile in /sys/bus/pci/devices/*/vendor; do
|
||||
if [[ -f "$vfile" ]]; then
|
||||
local vid
|
||||
vid="$(cat "$vfile" 2>/dev/null || true)"
|
||||
case "$vid" in
|
||||
"0x10de") vendor="nvidia" ;;
|
||||
"0x1002") vendor="amd" ;;
|
||||
"0x8086") [[ -z "$vendor" ]] && vendor="intel" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo "$vendor"
|
||||
}
|
||||
|
||||
# Official Linux binaries are built on Ubuntu 24.04 and link against glibc 2.38+.
|
||||
# This routine prints a warning on older distributions so users know upfront.
|
||||
check_glibc() {
|
||||
local required="2.38"
|
||||
local current=""
|
||||
if command -v ldd >/dev/null 2>&1; then
|
||||
current="$(ldd --version 2>/dev/null | head -n1 | grep -oP '[0-9]+\.[0-9]+' | head -n1 || true)"
|
||||
fi
|
||||
if [[ -z "$current" ]]; then
|
||||
print_warn "Could not detect glibc version. Prebuilt Linux backends require glibc $required+ (Ubuntu 24.04)."
|
||||
return
|
||||
fi
|
||||
if [[ "$(printf '%s\n' "$required" "$current" | sort -V | head -n1)" != "$required" ]]; then
|
||||
print_warn "Detected glibc $current. Prebuilt Linux backends require glibc $required+ (Ubuntu 24.04)."
|
||||
print_info "You can still use the app, but the downloaded backends will not start on this system."
|
||||
print_info "To fix: upgrade to Ubuntu 24.04+ or build stable-diffusion.cpp from source (see README)."
|
||||
fi
|
||||
}
|
||||
|
||||
copy_binaries_from_extracted() {
|
||||
local extracted_dir="$1" dest_dir="$2" main_name="$3" server_name="$4"
|
||||
|
||||
while IFS= read -r -d '' f; do
|
||||
local base
|
||||
base="$(basename "$f")"
|
||||
case "$base" in
|
||||
sd|sd-cli) cp "$f" "$dest_dir/$main_name" ;;
|
||||
sd-server) cp "$f" "$dest_dir/$server_name" ;;
|
||||
esac
|
||||
done < <(find "$extracted_dir" -type f \( -name "sd" -o -name "sd-cli" -o -name "sd-server" \) -print0 2>/dev/null)
|
||||
|
||||
# Copy any .so files
|
||||
find "$extracted_dir" -type f -name "*.so" -exec cp {} "$dest_dir/" \; 2>/dev/null || true
|
||||
}
|
||||
|
||||
copy_macos_backend_from_extracted() {
|
||||
local extracted_dir="$1" dest_dir="$2"
|
||||
local target="$dest_dir/sd"
|
||||
local server_bin=""
|
||||
local cli_bin=""
|
||||
|
||||
server_bin="$(find "$extracted_dir" -type f -name "sd-server" | head -n 1)"
|
||||
cli_bin="$(find "$extracted_dir" -type f \( -name "sd" -o -name "sd-cli" \) | head -n 1)"
|
||||
|
||||
if [[ -n "$server_bin" ]]; then
|
||||
cp "$server_bin" "$target"
|
||||
elif [[ -n "$cli_bin" ]]; then
|
||||
cp "$cli_bin" "$target"
|
||||
else
|
||||
print_fail "No sd-server, sd, or sd-cli binary was found in the macOS backend archive."
|
||||
return 1
|
||||
fi
|
||||
|
||||
find "$extracted_dir" -type f \( -name "*.dylib" -o -name "*.metallib" \) -exec cp {} "$dest_dir/" \; 2>/dev/null || true
|
||||
chmod +x "$target" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
print_header
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||
check_glibc
|
||||
fi
|
||||
|
||||
TOTAL_STEPS=7
|
||||
|
||||
# ── Step 1: Portable Node.js ────────────────────────────────────────────────
|
||||
print_step 1 $TOTAL_STEPS "Setting up portable Node.js ($NODE_DIR/)"
|
||||
|
||||
if [[ -x "$NODE_BIN" && -x "$NPM_BIN" ]]; then
|
||||
VERSION=$("$NODE_BIN" --version)
|
||||
print_ok "Portable Node.js already ready: $VERSION"
|
||||
else
|
||||
mkdir -p "$TOOLS_DIR"
|
||||
NODE_TAR_PATH="$TOOLS_DIR/$NODE_TARBALL"
|
||||
|
||||
download_file "$NODE_URL" "$NODE_TAR_PATH" "Node.js v${NODE_VERSION} LTS (Portable tarball)"
|
||||
extract_tarxz "$NODE_TAR_PATH" "$TOOLS_DIR" "Node.js"
|
||||
rm -f "$NODE_TAR_PATH"
|
||||
|
||||
EXTRACTED_DIR="$(find "$TOOLS_DIR" -maxdepth 1 -type d -name "node-v*-${NODE_PLATFORM_ARCH}" | head -n 1)"
|
||||
if [[ -d "$EXTRACTED_DIR" ]]; then
|
||||
rm -rf "$NODE_DIR"
|
||||
mv "$EXTRACTED_DIR" "$NODE_DIR"
|
||||
fi
|
||||
|
||||
if [ "$USE_SYMLINKS" = false ]; then
|
||||
print_info "Filesystem does not support symlinks. Creating shell wrappers for npm, npx, and corepack..."
|
||||
rm -f "$NODE_DIR/bin/npm" "$NODE_DIR/bin/npx" "$NODE_DIR/bin/corepack"
|
||||
|
||||
cat << 'EOF' > "$NODE_DIR/bin/npm"
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$0")
|
||||
exec "$basedir/node" "$basedir/../lib/node_modules/npm/bin/npm-cli.js" "$@"
|
||||
EOF
|
||||
chmod +x "$NODE_DIR/bin/npm"
|
||||
|
||||
cat << 'EOF' > "$NODE_DIR/bin/npx"
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$0")
|
||||
exec "$basedir/node" "$basedir/../lib/node_modules/npm/bin/npx-cli.js" "$@"
|
||||
EOF
|
||||
chmod +x "$NODE_DIR/bin/npx"
|
||||
|
||||
cat << 'EOF' > "$NODE_DIR/bin/corepack"
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$0")
|
||||
exec "$basedir/node" "$basedir/../lib/node_modules/corepack/dist/corepack.js" "$@"
|
||||
EOF
|
||||
chmod +x "$NODE_DIR/bin/corepack"
|
||||
|
||||
print_ok "Created shell wrappers."
|
||||
fi
|
||||
|
||||
if [[ ! -x "$NODE_BIN" || ! -x "$NPM_BIN" ]]; then
|
||||
print_fail "Portable Node.js install is incomplete."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$("$NODE_BIN" --version)
|
||||
print_ok "Portable Node.js ready: $VERSION"
|
||||
fi
|
||||
|
||||
# ── Step 2: stable-diffusion.cpp Backends ───────────────────────────────────
|
||||
mkdir -p "$BACKEND_DIR"
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
print_step 2 $TOTAL_STEPS "Setting up stable-diffusion.cpp Metal backend (app/backend/mac/)"
|
||||
if [[ "$ARCH" != "arm64" ]]; then
|
||||
print_fail "The official macOS backend binary is Apple Silicon only (arm64)."
|
||||
print_info "macOS Intel hardware is completely unsupported and has not been tested."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAC_BACKEND="$BACKEND_DIR/sd"
|
||||
if [[ -x "$MAC_BACKEND" ]]; then
|
||||
print_ok "macOS Metal backend already ready."
|
||||
else
|
||||
MAC_ZIP="$TOOLS_DIR/sd-mac-metal.zip"
|
||||
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Darwin-macOS-15.7.7-arm64.zip" "$MAC_ZIP" "stable-diffusion.cpp Metal Backend (macOS arm64)"
|
||||
extract_zip "$MAC_ZIP" "$BACKEND_DIR/extracted" "macOS Metal Backend"
|
||||
rm -f "$MAC_ZIP"
|
||||
copy_macos_backend_from_extracted "$BACKEND_DIR/extracted" "$BACKEND_DIR"
|
||||
rm -rf "$BACKEND_DIR/extracted"
|
||||
print_ok "macOS Metal backend installed."
|
||||
fi
|
||||
|
||||
# CoreML NPU Environment setup (macOS Apple Silicon only)
|
||||
print_info "Setting up CoreML Python virtual environment for Apple Silicon ANE (NPU)..."
|
||||
VENV_DIR="$BACKEND_DIR/coreml_venv"
|
||||
PYTHON_BIN="$VENV_DIR/bin/python"
|
||||
|
||||
if [[ ! -x "$PYTHON_BIN" ]]; then
|
||||
print_info "Creating Python virtual environment at $VENV_DIR..."
|
||||
if ! python3 -m venv "$VENV_DIR"; then
|
||||
print_warn "Could not create the virtual environment for CoreML. CoreML NPU mode will be unavailable."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -x "$PYTHON_BIN" ]]; then
|
||||
print_info "Installing CoreML dependencies (this may take a couple of minutes)..."
|
||||
if "$PYTHON_BIN" -m pip install --upgrade pip >/dev/null 2>&1 && \
|
||||
"$PYTHON_BIN" -m pip install numpy coremltools diffusers transformers huggingface-hub pillow >/dev/null 2>&1 && \
|
||||
"$PYTHON_BIN" -m pip install "git+https://github.com/apple/ml-stable-diffusion.git" >/dev/null 2>&1; then
|
||||
print_ok "CoreML ANE (NPU) environment ready."
|
||||
else
|
||||
print_warn "CoreML dependencies installation failed. CoreML NPU mode will be unavailable."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
VENDOR="$(detect_gpu_vendor)"
|
||||
print_step 2 $TOTAL_STEPS "Detecting GPU vendor: ${VENDOR:-none}"
|
||||
|
||||
# CPU backend (always)
|
||||
CPU_BACKEND_DIR="$BACKEND_DIR/cpu"
|
||||
if [[ ! -f "$CPU_BACKEND_DIR/sd-cpu" || ! -f "$CPU_BACKEND_DIR/sd-server-cpu" ]]; then
|
||||
mkdir -p "$CPU_BACKEND_DIR"
|
||||
CPU_ZIP="$TOOLS_DIR/sd-cpu.zip"
|
||||
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Linux-Ubuntu-24.04-x86_64.zip" "$CPU_ZIP" "stable-diffusion.cpp CPU Backend (Linux x86_64)"
|
||||
extract_zip "$CPU_ZIP" "$CPU_BACKEND_DIR/extracted" "CPU Backend"
|
||||
rm -f "$CPU_ZIP"
|
||||
copy_binaries_from_extracted "$CPU_BACKEND_DIR/extracted" "$CPU_BACKEND_DIR" "sd-cpu" "sd-server-cpu"
|
||||
rm -rf "$CPU_BACKEND_DIR/extracted"
|
||||
print_ok "CPU backend installed."
|
||||
else
|
||||
print_ok "CPU backend already ready."
|
||||
fi
|
||||
chmod +x "$CPU_BACKEND_DIR/sd-cpu" "$CPU_BACKEND_DIR/sd-server-cpu" 2>/dev/null || true
|
||||
|
||||
# Vulkan backend (always - cross-vendor GPU fallback)
|
||||
VULKAN_BACKEND_DIR="$BACKEND_DIR/vulkan"
|
||||
if [[ ! -f "$VULKAN_BACKEND_DIR/sd-vulkan" || ! -f "$VULKAN_BACKEND_DIR/sd-server-vulkan" ]]; then
|
||||
mkdir -p "$VULKAN_BACKEND_DIR"
|
||||
VULKAN_ZIP="$TOOLS_DIR/sd-vulkan.zip"
|
||||
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip" "$VULKAN_ZIP" "stable-diffusion.cpp Vulkan Backend (Linux x86_64)"
|
||||
extract_zip "$VULKAN_ZIP" "$VULKAN_BACKEND_DIR/extracted" "Vulkan Backend"
|
||||
rm -f "$VULKAN_ZIP"
|
||||
copy_binaries_from_extracted "$VULKAN_BACKEND_DIR/extracted" "$VULKAN_BACKEND_DIR" "sd-vulkan" "sd-server-vulkan"
|
||||
rm -rf "$VULKAN_BACKEND_DIR/extracted"
|
||||
print_ok "Vulkan backend installed."
|
||||
else
|
||||
print_ok "Vulkan backend already ready."
|
||||
fi
|
||||
chmod +x "$VULKAN_BACKEND_DIR/sd-vulkan" "$VULKAN_BACKEND_DIR/sd-server-vulkan" 2>/dev/null || true
|
||||
|
||||
# ROCm backend (optional --max-perf, or auto-detected AMD)
|
||||
ROCM_BACKEND_DIR="$BACKEND_DIR/rocm"
|
||||
if [[ $MAX_PERF -eq 1 ]] && [[ "$VENDOR" == "amd" || "$VENDOR" == "" ]]; then
|
||||
if [[ ! -f "$ROCM_BACKEND_DIR/sd-rocm" || ! -f "$ROCM_BACKEND_DIR/sd-server-rocm" ]]; then
|
||||
mkdir -p "$ROCM_BACKEND_DIR"
|
||||
ROCM_ZIP="$TOOLS_DIR/sd-rocm.zip"
|
||||
print_warn "ROCm backend is ~1.2 GB. This may take a while..."
|
||||
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.13.0.zip" "$ROCM_ZIP" "stable-diffusion.cpp ROCm Backend (Linux x86_64)"
|
||||
extract_zip "$ROCM_ZIP" "$ROCM_BACKEND_DIR/extracted" "ROCm Backend"
|
||||
rm -f "$ROCM_ZIP"
|
||||
copy_binaries_from_extracted "$ROCM_BACKEND_DIR/extracted" "$ROCM_BACKEND_DIR" "sd-rocm" "sd-server-rocm"
|
||||
rm -rf "$ROCM_BACKEND_DIR/extracted"
|
||||
print_ok "ROCm backend installed."
|
||||
else
|
||||
print_ok "ROCm backend already ready."
|
||||
fi
|
||||
chmod +x "$ROCM_BACKEND_DIR/sd-rocm" "$ROCM_BACKEND_DIR/sd-server-rocm" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# CUDA backend on Linux: ask user, download prebuilt first, and compile from source as a fallback.
|
||||
CUDA_BACKEND_DIR="$BACKEND_DIR/cuda"
|
||||
if [[ "$VENDOR" == "nvidia" ]]; then
|
||||
if [[ ! -f "$CUDA_BACKEND_DIR/sd-cuda" || ! -f "$CUDA_BACKEND_DIR/sd-server-cuda" ]]; then
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
echo " NVIDIA GPU Detected"
|
||||
echo " ============================================================"
|
||||
echo " To get the best performance, you can use the CUDA backend."
|
||||
echo " Setting this up can download a prebuilt binary or compile from"
|
||||
echo " source (which takes 10-15 minutes)."
|
||||
echo ""
|
||||
echo " Alternatively, you can use the Vulkan backend which is already"
|
||||
echo " installed and runs immediately (recommended for GTX cards)."
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
|
||||
CHOOSE_CUDA="n"
|
||||
if [[ -t 0 ]]; then
|
||||
read -t 30 -rp " Do you want to proceed with CUDA setup? [y/N]: " CHOOSE_CUDA || CHOOSE_CUDA="n"
|
||||
else
|
||||
print_info "Non-interactive environment detected; defaulting to Vulkan."
|
||||
fi
|
||||
|
||||
if [[ "$CHOOSE_CUDA" =~ ^[Yy]$ ]]; then
|
||||
TRY_DOWNLOAD=1
|
||||
PREBUILT_URL="https://github.com/leaxer-ai/leaxer-stable-diffusion/releases/download/v0.1.0/sd-server-x86_64-unknown-linux-gnu-cuda"
|
||||
PREBUILT_CLI_URL="https://github.com/leaxer-ai/leaxer-stable-diffusion/releases/download/v0.1.0/sd-x86_64-unknown-linux-gnu-cuda"
|
||||
|
||||
mkdir -p "$CUDA_BACKEND_DIR"
|
||||
|
||||
print_info "Attempting to download prebuilt CUDA binary..."
|
||||
if download_file "$PREBUILT_URL" "$CUDA_BACKEND_DIR/sd-server-cuda" "Prebuilt Linux CUDA Server" && \
|
||||
download_file "$PREBUILT_CLI_URL" "$CUDA_BACKEND_DIR/sd-cuda" "Prebuilt Linux CUDA CLI"; then
|
||||
|
||||
chmod +x "$CUDA_BACKEND_DIR/sd-server-cuda" "$CUDA_BACKEND_DIR/sd-cuda" 2>/dev/null || true
|
||||
|
||||
print_info "Testing downloaded prebuilt CUDA binary..."
|
||||
if "$CUDA_BACKEND_DIR/sd-server-cuda" --help >/dev/null 2>&1; then
|
||||
print_ok "Prebuilt CUDA binary verified and works! Skipping compilation."
|
||||
TRY_DOWNLOAD=0
|
||||
else
|
||||
print_warn "Prebuilt CUDA binary failed verification test (missing libraries or library mismatch)."
|
||||
rm -f "$CUDA_BACKEND_DIR/sd-server-cuda" "$CUDA_BACKEND_DIR/sd-cuda"
|
||||
fi
|
||||
else
|
||||
print_warn "Failed to download prebuilt CUDA binary."
|
||||
rm -f "$CUDA_BACKEND_DIR/sd-server-cuda" "$CUDA_BACKEND_DIR/sd-cuda"
|
||||
fi
|
||||
|
||||
if [[ $TRY_DOWNLOAD -eq 1 ]]; then
|
||||
if command -v nvcc >/dev/null 2>&1 && command -v cmake >/dev/null 2>&1 && command -v git >/dev/null 2>&1; then
|
||||
print_info "NVIDIA GPU and CUDA compilation tools (nvcc, cmake, git) detected."
|
||||
print_info "Building CUDA backend from source..."
|
||||
|
||||
BUILD_DIR="$TOOLS_DIR/build-sd"
|
||||
JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
|
||||
|
||||
if [[ ! -d "$BUILD_DIR" ]]; then
|
||||
print_info "Cloning stable-diffusion.cpp..."
|
||||
git clone https://github.com/leejet/stable-diffusion.cpp.git "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
PUSHED_DIR="$(pwd)"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
print_info "Checking out pinned tag $SD_RELEASE..."
|
||||
git fetch origin
|
||||
git checkout -f "$SD_RELEASE"
|
||||
git submodule update --init --recursive
|
||||
|
||||
rm -rf build-cuda && mkdir build-cuda && cd build-cuda
|
||||
|
||||
print_info "Running cmake for CUDA backend..."
|
||||
if cmake .. -DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA_FORCE_MMQ=ON && \
|
||||
cmake --build . --config Release -j"$JOBS"; then
|
||||
|
||||
mkdir -p "$CUDA_BACKEND_DIR"
|
||||
if [[ -f bin/sd-server ]]; then
|
||||
cp bin/sd-server "$CUDA_BACKEND_DIR/sd-cuda"
|
||||
cp bin/sd-server "$CUDA_BACKEND_DIR/sd-server-cuda"
|
||||
else
|
||||
print_fail "CUDA build succeeded but bin/sd-server was not found."
|
||||
cd "$PUSHED_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f bin/sd ]]; then
|
||||
cp bin/sd "$CUDA_BACKEND_DIR/sd-cli-cuda"
|
||||
elif [[ -f bin/sd-cli ]]; then
|
||||
cp bin/sd-cli "$CUDA_BACKEND_DIR/sd-cli-cuda"
|
||||
fi
|
||||
|
||||
SO_PATH_CUDA=$(find . -name "libstable-diffusion.so" | head -n 1)
|
||||
if [[ -n "$SO_PATH_CUDA" ]]; then
|
||||
cp "$SO_PATH_CUDA" "$CUDA_BACKEND_DIR/"
|
||||
fi
|
||||
|
||||
chmod +x "$CUDA_BACKEND_DIR/sd-cuda" "$CUDA_BACKEND_DIR/sd-server-cuda" 2>/dev/null || true
|
||||
if [[ -f "$CUDA_BACKEND_DIR/sd-cli-cuda" ]]; then
|
||||
chmod +x "$CUDA_BACKEND_DIR/sd-cli-cuda" 2>/dev/null || true
|
||||
fi
|
||||
print_ok "CUDA backend compiled and installed successfully from source."
|
||||
else
|
||||
print_warn "CUDA backend build from source failed. Falling back to Vulkan."
|
||||
fi
|
||||
cd "$PUSHED_DIR"
|
||||
else
|
||||
print_warn "CUDA compilation tools (nvcc, cmake, and/or git) are missing."
|
||||
print_info "To compile the CUDA backend, install the NVIDIA CUDA Toolkit, cmake, and git."
|
||||
print_info "Falling back to Vulkan."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
print_info "Declined CUDA setup. Using Vulkan GPU backend instead."
|
||||
fi
|
||||
else
|
||||
print_ok "CUDA backend already ready."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Step 3: npm install ─────────────────────────────────────────────────────
|
||||
print_step 3 $TOTAL_STEPS "Setting up llama.cpp text backend"
|
||||
bash "$SCRIPT_DIR/setup-llama.sh"
|
||||
|
||||
print_step 4 $TOTAL_STEPS "Setting up whisper.cpp speech backend"
|
||||
bash "$SCRIPT_DIR/setup-whisper.sh"
|
||||
|
||||
print_step 5 $TOTAL_STEPS "Setting up Kokoro ONNX text-to-speech runtime"
|
||||
bash "$SCRIPT_DIR/setup-tts.sh"
|
||||
|
||||
print_step 6 $TOTAL_STEPS "Installing frontend dependencies (app/frontend/)"
|
||||
|
||||
if [[ ! -x "$NPM_BIN" ]]; then
|
||||
print_fail "Portable npm was not found at $NPM_BIN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure correct OS-specific node_modules folder is symlinked or swapped to avoid conflicts
|
||||
FRONTEND_NODE_MODULES="$FRONTEND_DIR/node_modules"
|
||||
ACTIVE_OS_FILE="$FRONTEND_DIR/.active_modules_os"
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
OS_NODE_MODULES="$FRONTEND_DIR/node_modules_mac"
|
||||
OS_LABEL="node_modules_mac"
|
||||
CURRENT_OS="mac"
|
||||
else
|
||||
OS_NODE_MODULES="$FRONTEND_DIR/node_modules_linux"
|
||||
OS_LABEL="node_modules_linux"
|
||||
CURRENT_OS="linux"
|
||||
fi
|
||||
|
||||
# Attempt to create a test symlink to check if filesystem supports symlinks
|
||||
USE_SYMLINKS=true
|
||||
TEST_LINK="$FRONTEND_DIR/.test_symlink"
|
||||
rm -f "$TEST_LINK"
|
||||
if ln -s "$OS_LABEL" "$TEST_LINK" 2>/dev/null; then
|
||||
rm -f "$TEST_LINK"
|
||||
else
|
||||
USE_SYMLINKS=false
|
||||
fi
|
||||
|
||||
if [ "$USE_SYMLINKS" = true ]; then
|
||||
if [[ -d "$FRONTEND_NODE_MODULES" && ! -L "$FRONTEND_NODE_MODULES" ]]; then
|
||||
print_info "Migrating existing node_modules to $OS_LABEL..."
|
||||
if [[ -d "$OS_NODE_MODULES" ]]; then
|
||||
rm -rf "$FRONTEND_NODE_MODULES"
|
||||
else
|
||||
mv "$FRONTEND_NODE_MODULES" "$OS_NODE_MODULES"
|
||||
fi
|
||||
fi
|
||||
rm -rf "$FRONTEND_NODE_MODULES"
|
||||
mkdir -p "$OS_NODE_MODULES"
|
||||
ln -sf "$OS_LABEL" "$FRONTEND_NODE_MODULES"
|
||||
else
|
||||
# Fallback: Filesystem does not support symlinks (e.g. FAT32/exFAT)
|
||||
print_info "Filesystem does not support symlinks. Using directory swapping fallback..."
|
||||
|
||||
if [[ -L "$FRONTEND_NODE_MODULES" || -f "$FRONTEND_NODE_MODULES" ]]; then
|
||||
rm -rf "$FRONTEND_NODE_MODULES"
|
||||
fi
|
||||
|
||||
PREV_OS=""
|
||||
if [[ -f "$ACTIVE_OS_FILE" ]]; then
|
||||
PREV_OS=$(cat "$ACTIVE_OS_FILE")
|
||||
fi
|
||||
|
||||
if [[ -d "$FRONTEND_NODE_MODULES" && "$PREV_OS" != "$CURRENT_OS" ]]; then
|
||||
if [[ -n "$PREV_OS" ]]; then
|
||||
print_info "Swapping out node_modules to node_modules_$PREV_OS..."
|
||||
rm -rf "$FRONTEND_DIR/node_modules_$PREV_OS"
|
||||
mv "$FRONTEND_NODE_MODULES" "$FRONTEND_DIR/node_modules_$PREV_OS"
|
||||
else
|
||||
print_info "Saving node_modules as node_modules_windows..."
|
||||
rm -rf "$FRONTEND_DIR/node_modules_windows"
|
||||
mv "$FRONTEND_NODE_MODULES" "$FRONTEND_DIR/node_modules_windows"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -d "$OS_NODE_MODULES" && ! -d "$FRONTEND_NODE_MODULES" ]]; then
|
||||
print_info "Swapping in $OS_LABEL..."
|
||||
mv "$OS_NODE_MODULES" "$FRONTEND_NODE_MODULES"
|
||||
elif [[ ! -d "$FRONTEND_NODE_MODULES" ]]; then
|
||||
mkdir -p "$FRONTEND_NODE_MODULES"
|
||||
fi
|
||||
|
||||
echo "$CURRENT_OS" > "$ACTIVE_OS_FILE"
|
||||
fi
|
||||
|
||||
cd "$FRONTEND_DIR"
|
||||
export PATH="$NODE_DIR/bin:$PATH"
|
||||
|
||||
if [ "$USE_SYMLINKS" = false ]; then
|
||||
if "$NPM_BIN" install --prefer-offline --no-bin-links; then
|
||||
print_ok "Dependencies installed!"
|
||||
else
|
||||
print_fail "npm install failed."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if "$NPM_BIN" install --prefer-offline; then
|
||||
print_ok "Dependencies installed!"
|
||||
else
|
||||
print_fail "npm install failed."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Step 4: Build frontend ──────────────────────────────────────────────────
|
||||
print_step 7 $TOTAL_STEPS "Building frontend -> app/dist/"
|
||||
|
||||
if [ "$USE_SYMLINKS" = false ]; then
|
||||
# If symlinks are disabled, run vite directly using the local node executable.
|
||||
if "$NODE_BIN" node_modules/vite/bin/vite.js build; then
|
||||
print_ok "Frontend built!"
|
||||
else
|
||||
print_fail "Frontend build failed."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if "$NPM_BIN" run build; then
|
||||
print_ok "Frontend built!"
|
||||
else
|
||||
print_fail "Frontend build failed."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Done ────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo " ============================================================"
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
echo " Setup complete! Run ./mac.sh to launch."
|
||||
else
|
||||
echo " Setup complete! Run ./linux.sh to launch."
|
||||
fi
|
||||
echo " ============================================================"
|
||||
echo ""
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" && $MAX_PERF -eq 0 ]] && [[ "$VENDOR" == "nvidia" || "$VENDOR" == "amd" ]]; then
|
||||
echo " Tip: For maximum GPU performance, re-run with:"
|
||||
echo " ./scripts/setup/setup.sh --max-perf"
|
||||
echo ""
|
||||
fi
|
||||
Reference in new issue
Block a user