add linux

This commit is contained in:
Tech Jarves committed 2026-06-09 10:55:47 +00:00
1 parent c3bc49c1d9
commit 25b9a5c965
13 files changed
+1255 -451

No files matched your search

+192
View File
@@ -0,0 +1,192 @@
# Linux Port Plan for Local AI Image Generator
> **Plan location**: This plan will be copied to the project root as `PLAN.md` upon approval, per user request.
> **Implementation Update (post-verification)**
>
> During end-to-end testing, the third-party Linux CUDA binary (`leaxer-ai/leaxer-stable-diffusion`) was found to **segfault during generation** after successfully loading the model. As a result, Linux CUDA support was **removed** from the implementation. Linux NVIDIA GPUs now use the **Vulkan** backend. The setup script also gained a glibc version check, because the official leejet Linux binaries require **glibc 2.38+** (Ubuntu 24.04). The frontend's misleading "mock SVG" fallback was replaced with a real error message when the backend crashes. See the updated README for build-from-source instructions.
## 1. What This Project Does
**Local AI Image Generator** is a zero-configuration, portable desktop environment for running Stable Diffusion (Safetensors / GGUF / CKPT) completely offline. The Windows flow is:
1. **Entry Point**: `start.bat` double-click launcher.
2. **Setup**: `scripts/setup.ps1` auto-downloads portable Node.js for Windows, detects the GPU vendor, downloads the matching stable-diffusion.cpp backend (CUDA for NVIDIA, Vulkan for AMD/Intel), installs npm dependencies, and builds the Vite+React frontend into `app/dist/`.
3. **Runtime**: `scripts/serve.cjs` (a Node.js HTTP server) serves the static frontend on `localhost:1420`, manages the backend process lifecycle, proxies generation requests, handles model downloads/imports, tracks telemetry, and manages the gallery.
4. **All sandboxed**: Everything lives under the project folder (`app/tools/`, `app/backend/`, `app/models/`, `app/outputs/`). No global PATH changes, no system installers.
## 2. Research Findings
### 2.1 stable-diffusion.cpp Linux Binaries
We researched the upstream project (`leejet/stable-diffusion.cpp`) and third-party builders. The following Linux x86_64 binaries are available:
| Backend | Source | Availability | Approx. Size | Notes |
|---|---|---|---|---|
| **CPU** | leejet official releases | ✅ Available | ~20–30 MB | Generic x86_64 CPU binary |
| **Vulkan** | leejet official releases | ✅ Available | ~100 MB | Cross-vendor GPU (AMD, Intel, NVIDIA) |
| **ROCm** | leejet official releases | ✅ Available | ~1.2 GB | AMD GPU; requires host ROCm kernel driver |
| **CUDA** | leejet official releases | ❌ **Not published** | N/A | See section 2.2 |
| **CUDA** | leaxer-ai/leaxer-stable-diffusion (third-party) | ❌ **Rejected** | ~115 MB | Loads models but **segfaults during generation**; removed from setup. |
**Critical Finding**: Upstream does **not** publish Linux CUDA binaries. There is an open feature request ([leejet/stable-diffusion.cpp#1291](https://github.com/leejet/stable-diffusion.cpp/issues/1291)) but no official support yet.
### 2.2 Linux CUDA Feasibility Analysis
We evaluated the third-party `leaxer-ai/leaxer-stable-diffusion` Linux CUDA binary:
- The binary downloads successfully and is a valid ELF executable.
- It loads models and initializes CUDA without errors.
- **However, it segfaults as soon as image generation starts** (reproduced on NVIDIA L4 with `TXT2IMG`).
- Because the binary is non-functional for its intended purpose, the project does **not** download or use it.
**Decision**: Linux CUDA support is **disabled** until a reliable prebuilt binary becomes available (either from upstream leejet or a verified third-party builder). Linux NVIDIA systems fall back to the **Vulkan** backend, which uses the same GPU via the NVIDIA Vulkan driver.
Users who need native CUDA performance on Linux can build `stable-diffusion.cpp` from source with `-DSD_CUDA=ON` and copy the resulting binary into `app/backend/linux/cuda/`; see README for instructions.
### 2.3 Portable Node.js for Linux
- Node.js distributes `node-v22.12.0-linux-x64.tar.xz` (matching the Windows LTS version already used).
- Fully portable: extract with `tar` and run `bin/node` / `bin/npm` directly.
### 2.4 Zero-Dependency GPU Detection on Linux
To avoid requiring `lspci`, `lshw`, or other host packages, we can parse PCI vendor IDs directly from sysfs:
- Read `/sys/bus/pci/devices/*/vendor`
- **NVIDIA**: `0x10de`
- **AMD**: `0x1002`
- **Intel**: `0x8086`
This is pure file I/O and works in any Linux environment with sysfs.
### 2.5 Extraction Strategy (No Host `unzip` Required)
- Node.js `.tar.xz` → extracted with `tar` (universally available on Linux).
- Backend `.zip` files → extracted using Node.js after bootstrapping (e.g., via `adm-zip` or a small inline script), or fall back to `python3 -m zipfile` if needed.
- NVIDIA CUDA redist `.tar.xz` → extracted with `tar`.
## 3. Implementation Approach: Maximum Performance (All Four Backends)
Based on your feedback, the recommended approach is to support **CPU + Vulkan + ROCm + CUDA** on Linux.
### 3.1 Backend Selection Matrix
| Vendor | Primary Backend | Fallback Backend | Source |
|---|---|---|---|
| NVIDIA | CUDA (best perf) | Vulkan | CUDA = leaxer third-party; Vulkan = leejet official |
| AMD | ROCm (best perf) | Vulkan | ROCm = leejet official; Vulkan = leejet official |
| Intel | Vulkan | CPU | Vulkan = leejet official |
| No GPU / Unknown | CPU | — | CPU = leejet official |
### 3.2 Backend Directory Layout (Linux)
```
app/backend/
├── win/
│ ├── cuda/sd-cuda.exe
│ └── vulkan/sd-vulkan.exe
└── linux/
├── cpu/sd-cpu
├── vulkan/sd-vulkan
├── rocm/sd-rocm + libstable-diffusion.so
└── cuda/sd-cuda + libstable-diffusion.so + libcudart.so.12 + libcublas.so.12 + libcublasLt.so.12
```
## 4. Files to Create / Modify
### New Files
1. **`start.sh`** — Bash entry point equivalent to `start.bat`.
- Checks for portable Node.js and frontend build.
- Runs `scripts/setup.sh` if anything is missing.
- Starts `scripts/serve.cjs` with the portable Node.js binary.
- Opens browser (`xdg-open`) or prints URL.
- Handles graceful shutdown (traps SIGINT/SIGTERM to kill backend).
2. **`scripts/setup.sh`** — Bash setup equivalent to `setup.ps1`.
- Downloads portable Node.js Linux tarball with `curl` + progress bar.
- Extracts with `tar`.
- Detects GPU via sysfs PCI vendor IDs.
- Downloads appropriate backends:
- **CPU + Vulkan** for everyone (baseline).
- **ROCm** if AMD GPU detected.
- **CUDA** if NVIDIA GPU detected (optional, due to large download).
- Downloads NVIDIA CUDA redist libraries if CUDA is selected.
- Extracts all archives using `tar` / Node.js / Python fallback.
- Renames binaries to predictable names.
- Runs `npm install` and `npm run build` using portable npm.
3. **`scripts/reset.sh`** — Bash reset equivalent to `reset.ps1`.
- Deletes `app/tools/`, `app/backend/`, `app/dist/`, `app/frontend/node_modules/` while preserving `app/models/` and `app/outputs/`.
### Modified Files
4. **`scripts/serve.cjs`** — Enhance Linux support.
- **Add Linux backend paths**:
```js
linuxCpu: path.join(ROOT, "app", "backend", "linux", "cpu", "sd-cpu"),
linuxVulkan: path.join(ROOT, "app", "backend", "linux", "vulkan", "sd-vulkan"),
linuxRocm: path.join(ROOT, "app", "backend", "linux", "rocm", "sd-rocm"),
linuxCuda: path.join(ROOT, "app", "backend", "linux", "cuda", "sd-cuda"),
```
- **Add Linux GPU detection** via sysfs (`/sys/bus/pci/devices/*/vendor`).
- **Update `getBackendOptions()`**: For Linux, return `cpu`, `vulkan`, `rocm` (if binary exists), and `cuda` (if binary + libraries exist).
- **Update `selectBackendPath()`**: Choose Linux backend based on resolved type; set `LD_LIBRARY_PATH` for CUDA/ROCm subprocesses so they find bundled `.so` files.
- **Update `getHealth()` / `getSetupPaths()`**: Include Linux backend checks.
- **Update `getGpuInfo()` / `hasNvidiaGpu()`**: Add Linux sysfs parsing.
- **CUDA VRAM polling**: Keep existing `nvidia-smi` approach; on Linux it works if drivers are installed.
5. **`README.md`** — Add Linux section with compatibility matrix, `./start.sh` usage, and known limitations.
## 5. Download Sources
| Component | URL Pattern |
|---|---|
| Portable Node.js | `https://nodejs.org/dist/v22.12.0/node-v22.12.0-linux-x64.tar.xz` |
| CPU Backend | `https://github.com/leejet/stable-diffusion.cpp/releases/download/master-XXX/sd-master-XXX-bin-Linux-Ubuntu-24.04-x86_64.zip` |
| Vulkan Backend | `https://github.com/leejet/stable-diffusion.cpp/releases/download/master-XXX/sd-master-XXX-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip` |
| ROCm Backend | `https://github.com/leejet/stable-diffusion.cpp/releases/download/master-XXX/sd-master-XXX-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.13.0.zip` |
| CUDA Binary (third-party) | `https://github.com/leaxer-ai/leaxer-stable-diffusion/releases/download/v0.1.0/sd-x86_64-unknown-linux-gnu-cuda` |
| CUDA Server (third-party) | `https://github.com/leaxer-ai/leaxer-stable-diffusion/releases/download/v0.1.0/sd-server-x86_64-unknown-linux-gnu-cuda` |
| CUDA Runtime (NVIDIA redist) | `https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/linux-x86_64/cuda_cudart-linux-x86_64-12.X.YZ-archive.tar.xz` |
| cuBLAS (NVIDIA redist) | `https://developer.download.nvidia.com/compute/cuda/redist/libcublas/linux-x86_64/libcublas-linux-x86_64-12.X.YZ-archive.tar.xz` |
## 6. Setup Modes
To respect users with limited bandwidth or no NVIDIA hardware, `setup.sh` should support two modes:
1. **Quick Setup (default)** — Downloads CPU + Vulkan only.
- Works on all Linux systems.
- NVIDIA users get Vulkan acceleration (good, but not best).
- ~120–150 MB total download.
2. **Maximum Performance Setup** — Downloads CPU + Vulkan + ROCm + CUDA.
- Best performance for each vendor.
- ~1.5 GB total download (mostly ROCm + cuBLAS).
- User can opt-in via `./start.sh --max-perf` or `./scripts/setup.sh --max-perf`.
## 7. Self-Contained Checklist
| Host Dependency | How We Eliminate It |
|---|---|
| Node.js installed globally | Download portable `node-v22.12.0-linux-x64.tar.xz` into `app/tools/node-linux/` |
| npm installed globally | Use portable npm from the tarball |
| `unzip` command | Extract ZIPs using Node.js or Python `zipfile` |
| `lspci` / `lshw` | Parse `/sys/bus/pci/devices/*/vendor` directly |
| C++ compiler / CUDA toolkit | Use pre-built binaries only |
| System package manager (apt/yum) | Never used |
| CUDA runtime libs | Bundle NVIDIA redistributable `.so` files |
**One unavoidable host dependency remains**: the NVIDIA kernel driver (`libcuda.so.1`) for CUDA mode. Without it, CUDA cannot run. The app will detect this and automatically fall back to Vulkan for NVIDIA.
## 8. Known Limitations
1. **Third-party CUDA binary**: The Linux CUDA build comes from `leaxer-ai/leaxer-stable-diffusion`, which has only one release (v0.1.0) and may become stale. We should pin to a specific version and periodically evaluate alternatives.
2. **Large ROCm download**: The official ROCm backend ZIP is ~1.2 GB, which is a significant one-time download.
3. **Large cuBLAS download**: NVIDIA's redistributable cuBLAS package is ~400–500 MB.
4. **NVIDIA driver requirement for CUDA**: `libcuda.so.1` must be present on the host (provided by the NVIDIA proprietary driver). If missing, CUDA mode falls back to Vulkan.
5. **ROCm host driver requirement**: ROCm requires a compatible AMD GPU and ROCm kernel driver. If unavailable, ROCm falls back to Vulkan.
6. **glibc compatibility**: Official Linux binaries are built on Ubuntu 24.04. They may fail on very old distributions (e.g., CentOS 7, Ubuntu 18.04) due to glibc version mismatches.
## 9. Recommended Implementation Order
1. Create `start.sh`, `scripts/setup.sh`, and `scripts/reset.sh` with CPU + Vulkan support first.
2. Update `scripts/serve.cjs` with Linux paths, sysfs GPU detection, and backend selection.
3. Add ROCm download path (behind `--max-perf` flag).
4. Add CUDA download path using leaxer binary + NVIDIA redist libraries (behind `--max-perf` flag).
5. Add fallback logic so that if ROCm/CUDA fails to initialize, the app automatically switches to Vulkan.
6. Update `README.md` with Linux instructions and performance notes.
7. Test on Linux machines with NVIDIA (CUDA/Vulkan), AMD (ROCm/Vulkan), Intel (Vulkan), and CPU-only configurations.
+90 -7
View File
@@ -1,6 +1,6 @@
# 🖼️ Local AI Image Generator
### An easy, zero-setup Stable Diffusion GUI for Windows. Run GGUF & Safetensors models offline without Python configuration.
### An easy, zero-setup Stable Diffusion GUI for Windows and Linux. Run GGUF & Safetensors models offline without Python configuration.
@@ -22,20 +22,29 @@
---
## 📖 Overview
**Local AI Image Generator** is a zero-configuration, portable desktop environment for running Stable Diffusion (Safetensors/GGUF/CKPT) offline on Windows. Double-clicking `start.bat` automatically handles dependency setup, GPU backend matching (CUDA/Vulkan), and launches a high-performance local web workspace.
**Local AI Image Generator** is a zero-configuration, portable desktop environment for running Stable Diffusion (Safetensors/GGUF/CKPT) offline on Windows and Linux. Double-clicking `start.bat` (Windows) or running `./start.sh` (Linux) automatically handles dependency setup, GPU backend matching, and launches a high-performance local web workspace.
---
## ⚡ Quick Start
### Windows
1. **Launch:** Double-click **`start.bat`** (downloads portable Node.js and pre-compiled GPU backend binaries on first run).
2. **Add Models:** Drop `.safetensors`, `.gguf`, or `.ckpt` weights into `app/models/` (or download them via the **Model Manager** tab in the UI).
3. **Generate:** Open `http://localhost:1420` in your browser, select your model, and write a prompt.
### Linux
1. **Check compatibility:** Prebuilt Linux backends are built on Ubuntu 24.04 and require **glibc 2.38+**. Run `ldd --version` to verify.
2. **Launch:** Open a terminal in the project folder and run **`./start.sh`** (downloads portable Node.js and pre-compiled GPU backend binaries on first run).
- For maximum AMD GPU performance, use **`./start.sh --max-perf`** on first setup (adds the ROCm backend).
3. **Add Models:** Drop `.safetensors`, `.gguf`, or `.ckpt` weights into `app/models/` (or download them via the **Model Manager** tab in the UI).
4. **Generate:** Open `http://localhost:1420` in your browser, select your model, and write a prompt.
---
## ✨ Features
* **100% Offline & Private:** Inference runs completely locally on your hardware.
* **Auto-Detected GPU Acceleration:** Configures **CUDA** for Nvidia cards, and **Vulkan** for AMD or Intel Arc GPUs.
* **Auto-Detected GPU Acceleration:** Configures **CUDA** for Nvidia cards, **ROCm** for AMD cards, and **Vulkan** for AMD/Intel/NVIDIA fallback.
* **Zero System Footprint:** Node.js is sandboxed inside the folder. No global environment paths are altered.
* **Integrated Model Manager:** Paste a Hugging Face URL to download weights directly, or drag-and-drop local weight files to import them.
* **Real-time Telemetry:** Monitor RAM, VRAM, CPU, and GPU load directly in the UI.
@@ -46,13 +55,17 @@
## 📁 Repository Structure
```
local-ai-image-generator/
├── start.bat # Main double-click entrypoint
├── start.bat # Main double-click entrypoint (Windows)
├── start.sh # Main terminal entrypoint (Linux)
├── PLAN.md # Linux port implementation plan
├── LICENSE # MIT Open Source license
├── .gitignore
├── README.md
├── scripts/
│ ├── setup.ps1 # Automated GPU-detect and environment installer
│ ├── reset.ps1 # Cleans runtime environments (keeps models & outputs)
│ ├── setup.ps1 # Automated GPU-detect and environment installer (Windows)
│ ├── setup.sh # Automated GPU-detect and environment installer (Linux)
│ ├── reset.ps1 # Cleans runtime environments (Windows)
│ ├── reset.sh # Cleans runtime environments (Linux)
│ └── serve.cjs # UI web server and backend lifecycle manager
└── app/
├── frontend/ # UI source code (Vite + React)
@@ -64,6 +77,8 @@ local-ai-image-generator/
## 🖥️ GPU Compatibility Matrix
### Windows
| GPU Vendor | Tech | Status | Notes |
| :--- | :--- | :--- | :--- |
| **Nvidia** | CUDA | ✅ Native | Maps `sd-cuda.exe` with Nvidia SDK 12 optimizations. |
@@ -71,6 +86,23 @@ local-ai-image-generator/
| **Intel Arc** | Vulkan | ✅ Native | Maps `sd-vulkan.exe` for Intel hardware. |
| **Integrated / None** | CPU | ⚠️ Fallback | Runs on logical CPU threads (slow). |
### Linux
| GPU Vendor | Primary | Fallback | Notes |
| :--- | :--- | :--- | :--- |
| **NVIDIA** | Vulkan | CPU | No reliable prebuilt Linux CUDA binary is currently available. NVIDIA GPUs use Vulkan. |
| **AMD Radeon** | ROCm | Vulkan | ROCm provides best AMD performance when host ROCm drivers are available. |
| **Intel Arc / integrated** | Vulkan | CPU | Cross-vendor Vulkan support. |
| **Integrated / None** | CPU | — | Runs on logical CPU threads (slow). |
**System requirements:**
- **glibc 2.38 or newer** is required for the prebuilt Linux backends (Ubuntu 24.04, Fedora 40+, etc.).
- The setup script will warn you if your glibc is older. You can still run setup, but the prebuilt backends will not start.
**Linux setup modes:**
- **Default (`./start.sh`)**: Downloads CPU + Vulkan backends (~120–150 MB).
- **Maximum Performance (`./start.sh --max-perf`)**: Also downloads the ROCm backend. Total download ~1.3 GB.
---
## ⏱️ Performance Benchmarks
@@ -78,6 +110,7 @@ local-ai-image-generator/
Typical generation times for an image with **20 steps** (e.g. 512x512 resolution; actual times can vary depending on specific hardware specifications, clock speeds, and system load):
* **CUDA GPU (Nvidia RTX):** ~10 seconds.
* **ROCm GPU (AMD RDNA2/RDNA3):** ~15–30 seconds.
* **Vulkan GPU (AMD / Intel Arc):** ~89 seconds.
* **GTX Vulkan Fallback (Nvidia GTX):** ~30 seconds (Vulkan runs significantly faster on legacy GTX series cards since they lack Tensor Cores).
* **CPU (Fallback):** ~150 - 300+ seconds (highly dependent on processor core count, speed, and AVX instruction sets).
@@ -85,8 +118,58 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution
---
## 🛠️ Troubleshooting
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset.ps1`. (This preserves your models and generated images).
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset.ps1` (Windows) or `scripts/reset.sh` (Linux). (This preserves your models and generated images).
* **Port Conflicts:** The frontend uses `1420` by default. The backend tries `8080` first, then automatically falls back to a free port if `8080` is already busy.
* **Linux backends fail to start with `GLIBC_2.38' not found`:** The prebuilt binaries require glibc 2.38+ (Ubuntu 24.04). Upgrade your distribution or build stable-diffusion.cpp from source (see below).
* **Linux ROCm not loading:** Make sure your AMD GPU and kernel are compatible with ROCm 7.13. The app will automatically fall back to Vulkan if ROCm cannot initialize.
* **Generation shows "server is not responding or crashed":** The backend process exited. Check the terminal where you ran `./start.sh` for the exact error (common causes are glibc mismatch, missing Vulkan drivers, or out-of-memory).
---
## 🔨 Building Linux Backends From Source
If your distribution has an older glibc than 2.38, or you want a CUDA backend on Linux, you can build `stable-diffusion.cpp` directly on your machine. The resulting binary will be linked against your system's glibc and will not have the compatibility issues of the prebuilt releases.
### Requirements
- `git`, `cmake`, `make` (or `ninja`), and a C++17 compiler (`g++` / `clang++`).
- For **CUDA**: the NVIDIA CUDA toolkit (`nvcc`) must be on your `PATH`.
- For **Vulkan**: the Vulkan SDK / loader and a compatible driver.
- For **ROCm**: AMD ROCm development libraries.
### Build commands
```bash
# 1. Clone upstream
git clone https://github.com/leejet/stable-diffusion.cpp.git
cd stable-diffusion.cpp
mkdir build && cd build
# 2. Configure for your backend (pick ONE)
# CPU only
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# CUDA
cmake .. -DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# Vulkan
cmake .. -DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# ROCm
cmake .. -DSD_HIPBLAS=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# 3. Build
cmake --build . --config Release -j$(nproc)
# 4. Copy the binaries into this project
cp bin/sd* /path/to/Local-AI-Image-Generator/app/backend/linux/<backend>/
```
After copying, rename the server binary to match what `scripts/serve.cjs` expects:
- CPU: `sd` → `sd-cpu`
- Vulkan: `sd` → `sd-vulkan`
- ROCm: `sd` → `sd-rocm`
Then restart the app with `./start.sh`.
---
@@ -1 +1 @@
import{i as D,t as i}from"./index-jXnaCwmK.js";var _;(function(r){r.WINDOW_RESIZED="tauri://resize",r.WINDOW_MOVED="tauri://move",r.WINDOW_CLOSE_REQUESTED="tauri://close-requested",r.WINDOW_DESTROYED="tauri://destroyed",r.WINDOW_FOCUS="tauri://focus",r.WINDOW_BLUR="tauri://blur",r.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",r.WINDOW_THEME_CHANGED="tauri://theme-changed",r.WINDOW_CREATED="tauri://window-created",r.WINDOW_SUSPENDED="tauri://suspended",r.WINDOW_RESUMED="tauri://resumed",r.WEBVIEW_CREATED="tauri://webview-created",r.DRAG_ENTER="tauri://drag-enter",r.DRAG_OVER="tauri://drag-over",r.DRAG_DROP="tauri://drag-drop",r.DRAG_LEAVE="tauri://drag-leave"})(_||(_={}));async function l(r,a){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(r,a),await D("plugin:event|unlisten",{event:r,eventId:a})}async function n(r,a,e){var t;const W=typeof e?.target=="string"?{kind:"AnyLabel",label:e.target}:(t=e?.target)!==null&&t!==void 0?t:{kind:"Any"};return D("plugin:event|listen",{event:r,target:W,handler:i(a)}).then(d=>async()=>l(r,d))}export{_ as TauriEvent,n as listen};
import{i as D,t as i}from"./index-CG3WZqfX.js";var _;(function(r){r.WINDOW_RESIZED="tauri://resize",r.WINDOW_MOVED="tauri://move",r.WINDOW_CLOSE_REQUESTED="tauri://close-requested",r.WINDOW_DESTROYED="tauri://destroyed",r.WINDOW_FOCUS="tauri://focus",r.WINDOW_BLUR="tauri://blur",r.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",r.WINDOW_THEME_CHANGED="tauri://theme-changed",r.WINDOW_CREATED="tauri://window-created",r.WINDOW_SUSPENDED="tauri://suspended",r.WINDOW_RESUMED="tauri://resumed",r.WEBVIEW_CREATED="tauri://webview-created",r.DRAG_ENTER="tauri://drag-enter",r.DRAG_OVER="tauri://drag-over",r.DRAG_DROP="tauri://drag-drop",r.DRAG_LEAVE="tauri://drag-leave"})(_||(_={}));async function l(r,a){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(r,a),await D("plugin:event|unlisten",{event:r,eventId:a})}async function n(r,a,e){var t;const W=typeof e?.target=="string"?{kind:"AnyLabel",label:e.target}:(t=e?.target)!==null&&t!==void 0?t:{kind:"Any"};return D("plugin:event|listen",{event:r,target:W,handler:i(a)}).then(d=>async()=>l(r,d))}export{_ as TauriEvent,n as listen};
+23
View File
@@ -0,0 +1,23 @@
(function(){const b=document.createElement("link").relList;if(b&&b.supports&&b.supports("modulepreload"))return;for(const x of document.querySelectorAll('link[rel="modulepreload"]'))r(x);new MutationObserver(x=>{for(const E of x)if(E.type==="childList")for(const q of E.addedNodes)q.tagName==="LINK"&&q.rel==="modulepreload"&&r(q)}).observe(document,{childList:!0,subtree:!0});function N(x){const E={};return x.integrity&&(E.integrity=x.integrity),x.referrerPolicy&&(E.referrerPolicy=x.referrerPolicy),x.crossOrigin==="use-credentials"?E.credentials="include":x.crossOrigin==="anonymous"?E.credentials="omit":E.credentials="same-origin",E}function r(x){if(x.ep)return;x.ep=!0;const E=N(x);fetch(x.href,E)}})();function fm(f){return f&&f.__esModule&&Object.prototype.hasOwnProperty.call(f,"default")?f.default:f}var Rs={exports:{}},ai={};var Zd;function Ny(){if(Zd)return ai;Zd=1;var f=Symbol.for("react.transitional.element"),b=Symbol.for("react.fragment");function N(r,x,E){var q=null;if(E!==void 0&&(q=""+E),x.key!==void 0&&(q=""+x.key),"key"in x){E={};for(var H in x)H!=="key"&&(E[H]=x[H])}else E=x;return x=E.ref,{$$typeof:f,type:r,key:q,ref:x!==void 0?x:null,props:E}}return ai.Fragment=b,ai.jsx=N,ai.jsxs=N,ai}var Kd;function Ey(){return Kd||(Kd=1,Rs.exports=Ny()),Rs.exports}var c=Ey(),Bs={exports:{}},le={};var Jd;function Ay(){if(Jd)return le;Jd=1;var f=Symbol.for("react.transitional.element"),b=Symbol.for("react.portal"),N=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),x=Symbol.for("react.profiler"),E=Symbol.for("react.consumer"),q=Symbol.for("react.context"),H=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),A=Symbol.for("react.lazy"),O=Symbol.for("react.activity"),Q=Symbol.iterator;function P(d){return d===null||typeof d!="object"?null:(d=Q&&d[Q]||d["@@iterator"],typeof d=="function"?d:null)}var J={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},$=Object.assign,fe={};function te(d,j,R){this.props=d,this.context=j,this.refs=fe,this.updater=R||J}te.prototype.isReactComponent={},te.prototype.setState=function(d,j){if(typeof d!="object"&&typeof d!="function"&&d!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,d,j,"setState")},te.prototype.forceUpdate=function(d){this.updater.enqueueForceUpdate(this,d,"forceUpdate")};function lt(){}lt.prototype=te.prototype;function Ae(d,j,R){this.props=d,this.context=j,this.refs=fe,this.updater=R||J}var ke=Ae.prototype=new lt;ke.constructor=Ae,$(ke,te.prototype),ke.isPureReactComponent=!0;var Ve=Array.isArray;function De(){}var ee={H:null,A:null,T:null,S:null},Se=Object.prototype.hasOwnProperty;function ot(d,j,R){var G=R.ref;return{$$typeof:f,type:d,key:j,ref:G!==void 0?G:null,props:R}}function Mt(d,j){return ot(d.type,j,d.props)}function de(d){return typeof d=="object"&&d!==null&&d.$$typeof===f}function Ze(d){var j={"=":"=0",":":"=2"};return"$"+d.replace(/[=:]/g,function(R){return j[R]})}var ct=/\/+/g;function Ke(d,j){return typeof d=="object"&&d!==null&&d.key!=null?Ze(""+d.key):j.toString(36)}function Le(d){switch(d.status){case"fulfilled":return d.value;case"rejected":throw d.reason;default:switch(typeof d.status=="string"?d.then(De,De):(d.status="pending",d.then(function(j){d.status==="pending"&&(d.status="fulfilled",d.value=j)},function(j){d.status==="pending"&&(d.status="rejected",d.reason=j)})),d.status){case"fulfilled":return d.value;case"rejected":throw d.reason}}throw d}function _(d,j,R,G,V){var I=typeof d;(I==="undefined"||I==="boolean")&&(d=null);var se=!1;if(d===null)se=!0;else switch(I){case"bigint":case"string":case"number":se=!0;break;case"object":switch(d.$$typeof){case f:case b:se=!0;break;case A:return se=d._init,_(se(d._payload),j,R,G,V)}}if(se)return V=V(d),se=G===""?"."+Ke(d,0):G,Ve(V)?(R="",se!=null&&(R=se.replace(ct,"$&/")+"/"),_(V,j,R,"",function(yt){return yt})):V!=null&&(de(V)&&(V=Mt(V,R+(V.key==null||d&&d.key===V.key?"":(""+V.key).replace(ct,"$&/")+"/")+se)),j.push(V)),1;se=0;var Qe=G===""?".":G+":";if(Ve(d))for(var Me=0;Me<d.length;Me++)G=d[Me],I=Qe+Ke(G,Me),se+=_(G,j,R,I,V);else if(Me=P(d),typeof Me=="function")for(d=Me.call(d),Me=0;!(G=d.next()).done;)G=G.value,I=Qe+Ke(G,Me++),se+=_(G,j,R,I,V);else if(I==="object"){if(typeof d.then=="function")return _(Le(d),j,R,G,V);throw j=String(d),Error("Objects are not valid as a React child (found: "+(j==="[object Object]"?"object with keys {"+Object.keys(d).join(", ")+"}":j)+"). If you meant to render a collection of children, use an array instead.")}return se}function C(d,j,R){if(d==null)return d;var G=[],V=0;return _(d,G,"","",function(I){return j.call(R,I,V++)}),G}function Z(d){if(d._status===-1){var j=d._result;j=j(),j.then(function(R){(d._status===0||d._status===-1)&&(d._status=1,d._result=R)},function(R){(d._status===0||d._status===-1)&&(d._status=2,d._result=R)}),d._staLine truncated
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+Je+e+Ht}var k=!1;function ie(e,t){if(!e||k)return"";k=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(t){var M=function(){throw Error()};if(Object.defineProperty(M.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(M,[])}catch(S){var v=S}Reflect.construct(e,[],M)}else{try{M.call()}catch(S){v=S}e.call(M.prototype)}}else{try{throw Error()}catch(S){v=S}(M=e())&&typeof M.catch=="function"&&M.catch(function(){})}}catch(S){if(S&&v&&typeof S.stack=="string")return[S.stack,v.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var n=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");n&&n.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var i=a.DetermineComponentFrameRoot(),u=i[0],s=i[1];if(u&&s){var o=u.split(`
`),p=s.split(`
`);for(n=a=0;a<o.length&&!o[a].includes("DetermineComponentFrameRoot");)a++;for(;n<p.length&&!p[n].includes("DetermineComponentFrameRoot");)n++;if(a===o.length||n===p.length)for(a=o.length-1,n=p.length-1;1<=a&&0<=n&&o[a]!==p[n];)n--;for(;1<=a&&0<=n;a--,n--)if(o[a]!==p[n]){if(a!==1||n!==1)do if(a--,n--,0>n||o[a]!==p[n]){var z=`
`+o[a].replace(" at new "," at ");return e.displayName&&z.includes("<anonymous>")&&(z=z.replace("<anonymous>",e.displayName)),z}while(1<=a&&0<=n);break}}}finally{k=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?zt(l):""}function D(e,t){switch(e.tag){case 26:case 27:case 5:return zt(e.type);case 16:return zt("Lazy");case 13:return e.child!==t&&t!==null?zt("Suspense Fallback"):zt("Suspense");case 19:return zt("SuspenseList");case 0:case 15:return ie(e.type,!1);case 11:return ie(e.type.render,!1);case 1:return ie(e.type,!0);case 31:return zt("Activity");default:return""}}function B(e){try{var t="",l=null;do t+=D(e,l),l=e,e=e.return;while(e);return t}catch(a){return`
Error generating stack: `+a.message+`
`+a.stack}}var W=Object.prototype.hasOwnProperty,ae=f.unstable_scheduleCallback,At=f.unstable_cancelCallback,Gt=f.unstable_shouldYield,dl=f.unstable_requestPaint,we=f.unstable_now,al=f.unstable_getCurrentPriorityLevel,$e=f.unstable_ImmediatePriority,za=f.unstable_UserBlockingPriority,Ta=f.unstable_NormalPriority,L=f.unstable_LowPriority,he=f.unstable_IdlePriority,Re=f.log,gt=f.unstable_setDisableYieldValue,st=null,Pe=null;function qt(e){if(typeof Re=="function"&&gt(e),Pe&&typeof Pe.setStrictMode=="function")try{Pe.setStrictMode(st,e)}catch{}}var rt=Math.clz32?Math.clz32:be,pt=Math.log,ve=Math.LN2;function be(e){return e>>>=0,e===0?32:31-(pt(e)/ve|0)|0}var Be=256,xe=262144,We=4194304;function _e(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function dt(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,i=e.suspendedLanes,u=e.pingedLanes;e=e.warmLanes;var s=a&134217727;return s!==0?(a=s&~i,a!==0?n=_e(a):(u&=s,u!==0?n=_e(u):l||(l=s&~e,l!==0&&(n=_e(l))))):(s=a&~i,s!==0?n=_e(s):u!==0?n=_e(u):l||(l=a&~e,l!==0&&(n=_e(l)))),n===0?0:t!==0&&t!==n&&(t&i)===0&&(i=n&-n,l=t&-t,i>=l||i===32&&(l&4194048)!==0)?t:n}function vt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function cl(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ci(){var e=We;return We<<=1,(We&62914560)===0&&(We=4194304),e}function ju(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function mn(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ym(e,t,l,a,n,i){var u=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var s=e.entanglements,o=e.expirationTimes,p=e.hiddenUpdates;for(l=u&~l;0<l;){var z=31-rt(l),M=1<<z;s[z]=0,o[z]=-1;var v=p[z];if(v!==null)for(p[z]=null,z=0;z<v.length;z++){var S=v[z];S!==null&&(S.lane&=-536870913)}l&=~M}a!==0&&$s(e,a,0),i!==0&&n===0&&e.tag!==0&&(e.suspendedLanes|=i&~(u&~t))}function $s(e,t,l){e.pendingLanes|=t,e.suspendedLanes&=~t;var a=31-rt(t);e.entangledLanes|=t,e.entanglements[a]=e.entanglements[a]|1073741824|l&261930}function Ws(e,t){var l=e.entangledLanes|=t;for(e=e.entanglements;l;){var a=31-rt(l),n=1<<a;n&t|e[a]&t&&(e[a]|=t),l&=~n}}function Fs(e,t){var l=t&-t;return l=(l&42)!==0?1:zu(l),(l&(e.suspendedLanes|t))!==0?0:l}function zu(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function Tu(e){return e&=-e,2<e?8<e?(e&134217727)!==0?32:268435456:8:2}function Is(){var e=C.p;return e!==0?e:(e=window.event,e===void 0?32:qd(e.type))}function Ps(e,t){var l=C.p;try{return C.p=e,t()}finally{C.p=l}}var Ol=Math.random().toString(36).slice(2),bt="__reactFiber$"+Ol,Dt="__reactProps$"+Ol,Ma="__reactContainer$"+Ol,Mu="__reactEvents$"+Ol,gm="__reactListeners$"+Ol,pm="__reactHandles$"+Ol,ef="__reactResources$"+Ol,hn="__reactMarker$"+Ol;function Nu(e){delete e[bt],delete e[Dt],delete e[Mu],delete e[gm],delete e[pm]}function Na(e){var t=e[bt];if(t)return t;for(var l=e.parentNode;l;){if(t=l[Ma]||l[bt]){if(l=t.alternate,t.child!==null||l!==null&&l.child!==null)for(e=jd(e);e!==null;){if(l=e[bt])return l;e=jd(e)}return t}e=l,l=e.parentNode}return null}function Ea(e){if(e=e[bt]||e[Ma]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function yn(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(r(33))}function Aa(e){var t=e[ef];return t||(t=e[ef]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function mt(e){e[hn]=!0}var tf=new Set,lf={};function ua(e,t){Da(e,t),Da(e+"Capture",t)}function Da(e,t){for(lf[e]=t,e=0;e<t.length;e++)tf.add(t[e])}var vm=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F Line truncated
`).replace($h,"")}function md(e,t){return t=dd(t),dd(e)===t}function Oe(e,t,l,a,n,i){switch(l){case"children":typeof a=="string"?t==="body"||t==="textarea"&&a===""||Oa(e,a):(typeof a=="number"||typeof a=="bigint")&&t!=="body"&&Oa(e,""+a);break;case"className":fi(e,"class",a);break;case"tabIndex":fi(e,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":fi(e,l,a);break;case"style":df(e,a,i);break;case"data":if(t!=="object"){fi(e,"data",a);break}case"src":case"href":if(a===""&&(t!=="a"||l!=="href")){e.removeAttribute(l);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){e.removeAttribute(l);break}a=ri(""+a),e.setAttribute(l,a);break;case"action":case"formAction":if(typeof a=="function"){e.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof i=="function"&&(l==="formAction"?(t!=="input"&&Oe(e,t,"name",n.name,n,null),Oe(e,t,"formEncType",n.formEncType,n,null),Oe(e,t,"formMethod",n.formMethod,n,null),Oe(e,t,"formTarget",n.formTarget,n,null)):(Oe(e,t,"encType",n.encType,n,null),Oe(e,t,"method",n.method,n,null),Oe(e,t,"target",n.target,n,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){e.removeAttribute(l);break}a=ri(""+a),e.setAttribute(l,a);break;case"onClick":a!=null&&(e.onclick=hl);break;case"onScroll":a!=null&&re("scroll",e);break;case"onScrollEnd":a!=null&&re("scrollend",e);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(r(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(r(60));e.innerHTML=l}}break;case"multiple":e.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":e.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){e.removeAttribute("xlink:href");break}l=ri(""+a),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,""+a):e.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,""):e.removeAttribute(l);break;case"capture":case"download":a===!0?e.setAttribute(l,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,a):e.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?e.setAttribute(l,a):e.removeAttribute(l);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?e.removeAttribute(l):e.setAttribute(l,a);break;case"popover":re("beforetoggle",e),re("toggle",e),si(e,"popover",a);break;case"xlinkActuate":ml(e,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":ml(e,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":ml(e,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":ml(e,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":ml(e,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":ml(e,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":ml(e,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":ml(e,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":ml(e,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":si(e,"is",a);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=jm.get(l)||l,si(e,l,a))}}function ps(e,t,l,a,n,i){switch(l){case"style":df(e,a,i);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(r(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(r(60));e.innerHTML=l}}break;case"children":typeof a=="string"?Oa(e,a):(typeof a=="number"||typeof a=="bigint")&&Oa(e,""+a);break;case"onScroll":a!=null&&re("scroll",e);break;case"onScrollEnd":a!=null&&re("scrollend",e);break;case"onClick":a!=null&&(e.onclick=hl);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
Seed: ${f.seed}${f.duration_sec?`
Time: ${f.duration_sec.toFixed(1)}s`:""}`,children:[c.jsx("img",{src:f.url,className:"gallery-thumb",alt:"Thumbnail",loading:"lazy",decoding:"async"}),N&&c.jsx("div",{className:"gallery-select-badge",children:c.jsx(Xs,{size:14})})]})});function lg({prompt:f,setPrompt:b,negativePrompt:N,setNegativePrompt:r,constraints:x,setConstraints:E,activeModel:q,generatedImages:H,setGeneratedImages:w,isGenerating:h,setIsGenerating:A,generationProgress:O,setGenerationProgress:Q,setActiveTab:P,showAlert:J=async({message:fe})=>window.alert(fe),showConfirm:$=async({message:fe})=>window.confirm(fe)}){const[fe,te]=U.useState(null),[lt,Ae]=U.useState(null),[ke,Ve]=U.useState(null),[De,ee]=U.useState(!1),[Se,ot]=U.useState(0),[Mt,de]=U.useState(0),[Ze,ct]=U.useState(0),[Ke,Le]=U.useState(""),[_,C]=U.useState(!1),[Z,me]=U.useState(!1),[ce,d]=U.useState(!1),[j,R]=U.useState(null),[G,V]=U.useState(null),[I,se]=U.useState(!1),[Qe,Me]=U.useState([]),[yt,Nt]=U.useState(null),Je=U.useRef(null),Ht=U.useRef(null),zt=U.useRef(!1),k=L=>{const he=L.target.files?.[0];if(L.target.value="",!he)return;if(!he.type.startsWith("image/")){V("Please choose an image file (PNG, JPG, or WEBP).");return}const Re=new FileReader;Re.onload=()=>Nt(Re.result),Re.onerror=()=>V("Could not read the selected image file."),Re.readAsDataURL(he)},ie=()=>Nt(null),D=async()=>{if(!f.trim())return;V(null),d(!0),R(null);let L=!1;try{const ve=await Cl();if(ve&&ve.settings){const be=ve.settings,Be=be.model?be.model.split(/[\\/]/).pop():null,xe=q?q.split(/[\\/]/).pop():null;(Be!==xe||parseInt(be.steps)!==parseInt(x.steps)||Math.abs(parseFloat(be.cfgScale)-parseFloat(x.cfgScale))>.05||be.sampler!==x.sampler||parseInt(be.threads)!==parseInt(x.threads)||!!be.useGpu!=(x.useGpu!==!1)||(be.backendType||(be.useGpu===!1?"cpu":"auto"))!==(x.backendType||(x.useGpu===!1?"cpu":"auto")))&&(L=!0)}else L=!0}catch{L=!0}if(L)try{console.log("Settings out of sync, restarting backend..."),await Vs(q,x);let ve=!1,be=null;for(let Be=0;Be<240;Be++){const xe=await Cl();if(xe.loading&&R({progress:xe.loading.progress||0,phase:xe.loading.phase||"Loading model...",speed:xe.loading.speed||"",current:xe.loading.current||0,total:xe.loading.total||0,backendMode:xe.loading.backendMode||xe.settings?.backendMode||"",device:xe.loading.device||xe.settings?.backendDevice||""}),xe.ready){ve=!0;break}if(xe.error){be=xe.error;break}if(!xe.running&&Be>3){be="The backend process terminated immediately on startup.";break}await new Promise(We=>setTimeout(We,500))}if(!ve)throw new Error(be||"Backend server failed to start after applying configuration changes.")}catch(ve){console.error("Failed to restart backend:",ve),V(ve.message||"Failed to sync settings with backend."),d(!1);return}d(!1),R(null),Je.current&&clearInterval(Je.current),A(!0),Q(0),ot(0),ct(0),Le(""),zt.current=!1,C(!1),se(!1),Ve(null),te(null);const he=(x.width>=1024?140:35)*.04,Re=x.width>=1024?140:35,gt=(x.backendType||(x.useGpu===!1?"cpu":"auto"))!=="cpu";let st=gt?he:Re,Pe=!1,qt=0,rt=0,pt=0;de(Math.round(x.steps*st)),Je.current=setInterval(async()=>{pt++,ot(pt);try{const ve=await eg();if(ve&&ve.active){const be=ve.decoding||!1;se(be);const Be=be?ve.steps||x.steps||20:ve.step||0,xe=ve.steps||x.steps||20,We=be?"":ve.speed||"";(be||ve.steps>0)&&(zt.current=!0,ct(Be)),Le(be?"decoding":We);let _e=0;be?_e=99:xe>0&&(_e=Math.round(Be/xe*100)),_e=Math.min(99,_e),be||ve.steps>0?Q(_e):Q(vt=>Math.max(vt,_e));let dt=0;if(be)dt=3;else{const vt=parseFloat(We);if(!isNaN(vt)&&vt>0){const cl=Math.max(0,xe-Be);We.includes("it/s")?dt=cl/vt:We.includes("s/it")&&(dt=cl*vt)}else{const cl=gt?he:Re;dt=Math.max(0,(xe-Be)*cl)}}if(gt&&!Pe){const vt=We.includes("it/s")&&numericSpeed<.2,cl=We.includes("s/it")&&numericSpeed>5;(vt||cl)&&(Pe=!0,C(!0))}de(Math.round(dt))}else Q(be=>Math.min(15,be+1)),de(be=>Math.max(1,be-1))}catch(ve){console.warn("Failed to get generation progress, running simulation fallback:",ve),gt&&!Pe&&pt>x.steps*he*1.3&&(Pe=!0,C(!0),st=Re,qt=pt,rt=Math.min(95,Math.round(pt/(x.steps*he)*100)));let be=Math.min(x.steps-1,Math.floor(pt/st)),Be=x.steps*st;if(pt>=Be){const _e=Math.ceil(pt/Math.max(.5,be));st=Math.max(st+15,_e+15),Be=x.steps*st,be=Math.min(x.steps-1,Math.floor(pt/st))}const xe=Math.max(1,Be-pt);de(Math.round(xe));let We=0;if(Pe){const _e=99-rt,dt=Be-qt;if(dt>0){const vt=Math.min(1,(pt-qt)/dt);if(vt<.85)We=Math.round(rt+vt*_e);else{const cl=(pt-(qt+dt*.85))/(st*2),ci=1-Math.exp(-cl);We=Math.min(99,Math.round(rt+.85*_e+.14*_e*ci))}}else We=99}else if(pt<Be*.85)We=Math.round(pt/Be*100);else{const _e=(pt-Be*.85)/(st*2),dt=1-Math.exp(-_e);We=Math.min(99,Math.round(85+14*dt))}zt.current||Q(_e=>Math.max(_e,We))}},1e3);try{Ht.current=new AbortController;const ve=await K0(f,N,x,q,yt,_e=>Q(dt=>Math.max(dt,_e)),Ht.current.signal);Je.current&&clearInterval(Je.current),Q(100),ct(x.steps),de(0),Le("");const be={prompt:f,negativePrompt:N,seed:ve.seed,steps:x.steps,cfgScale:x.cfgScale,width:x.width,height:x.height,sampler:x.sampler,model:q,mode:yt?"img2img":"txtLine truncated
${dl}
Update the UI status anyway?`,confirmLabel:"Update UI"})&&(b(D),r(!0)):await q({title:"Backend Not Running",message:`C++ backend server is not running on port ${At}.
To run locally, start:
${dl}
Proceed in Simulation Mode instead?`,confirmLabel:"Proceed"})&&(b(D),r(!1));return}fe(D),lt({progress:0,phase:"Starting backend...",speed:"",current:0,total:0,model:D,backendMode:"",backendBinary:"",device:""});try{const Gt=await Vs(D,x);console.log(Gt);let dl=!1,we=null;for(let al=0;al<240;al++){const $e=await Cl();if($e.loading&&lt({progress:$e.loading.progress||0,phase:$e.loading.phase||"Loading model...",speed:$e.loading.speed||"",current:$e.loading.current||0,total:$e.loading.total||0,model:$e.loading.model||D,backendMode:$e.loading.backendMode||$e.settings?.backendMode||"",backendBinary:$e.loading.backendBinary||$e.settings?.backendBinary||"",device:$e.loading.device||$e.settings?.backendDevice||""}),$e.ready){dl=!0;break}if($e.error){we=$e.error;break}if(!$e.running&&al>3){we="The backend process terminated immediately on startup.";break}await new Promise(za=>setTimeout(za,500))}if(dl)lt(al=>({...al||{},progress:100,phase:"Model ready",speed:"",current:al?.current||0,total:al?.total||0,model:D})),b(D),r(!0);else throw new Error(we||`Model server failed to respond on port ${At}.`)}catch(Gt){console.error("Failed to load model:",Gt),E({title:"Model Load Failed",message:Gt.message||String(Gt),danger:!0})}finally{setTimeout(()=>lt(null),800),fe(null)}},yt=async()=>{const D=Le;_(null),await Nt(),D&&await Me(D)},Nt=async()=>{if(de)return;Ze(!0),Ke({progress:10,phase:"Stopping backend process..."});const D=setInterval(async()=>{try{const B=await Cl();B.unloading?.active?Ke({progress:B.unloading.progress||50,phase:B.unloading.phase||"Unloading model..."}):Ke(W=>({progress:Math.min(95,W.progress+10),phase:W.phase||"Unloading model..."}))}catch{}},300);try{await Js(),Ke({progress:100,phase:"Model unloaded"}),b(null),_(null),r(!1)}catch(B){console.error(B),E({title:"Unload Failed",message:B.message||String(B),danger:!0})}finally{clearInterval(D),setTimeout(()=>{Ze(!1),Ke({progress:0,phase:""})},500)}},Je=async D=>{if(await q({title:"Delete Model?",message:`Delete "${D}" from your drive?`,confirmLabel:"Delete",danger:!0}))try{await W0(D),await R(),f===D&&await Nt()}catch(B){console.error("Failed to delete model:",B),E({title:"Delete Failed",message:B.message||String(B),danger:!0})}},Ht=async(D,B)=>{const W=new AbortController;Mt(W),ke(0),De({filename:B,speed:"0.0",eta:0,status:"Initiating file copy..."});try{await J0(D,ae=>{ke(Math.round(ae.progress)),De({filename:ae.filename,speed:ae.speed_mb_s.toFixed(1),eta:Math.round(ae.eta_secs),status:ae.status})},W.signal),await R()}catch(ae){console.error("Import failed:",ae),ae.name!=="AbortError"&&E({title:"Import Failed",message:ae.message||String(ae),danger:!0})}finally{ke(null),Mt(null)}},zt=()=>{ot&&ot.abort()},k=D=>{const B=D.target.files[0];B&&Ht(B,B.name),D.target.value=""},ie=!!h;return c.jsxs("div",{className:"workspace-area",children:[c.jsxs("div",{className:"workspace-title-section",children:[c.jsx("h2",{className:"workspace-title",children:"Model Manager"}),c.jsx("p",{className:"workspace-subtitle",children:"Manage local files and download recommended weights directly to your local models folder."})]}),f&&c.jsx("div",{className:"m3-card",style:{borderLeft:"4px solid var(--md-sys-color-primary)",background:"var(--md-sys-color-primary-container)",color:"var(--md-sys-color-on-primary-container)"},children:c.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[c.jsxs("div",{children:[c.jsxs("h4",{style:{fontWeight:700},children:["Active Model: ",f]}),c.jsx("p",{style:{fontSize:"0.85rem",marginTop:"2px",opacity:.9},children:"The local C++ stable-diffusion server is running. Real-time telemetry is displaying performance status in the top bar."}),(C.backendMode||C.backendDevice||C.backendBinary)&&c.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:"8px",marginTop:"10px"},children:[C.backendMode&&c.jsxs("span",{className:"status-chip",style:{cursor:"default"},children:[c.jsx(xu,{size:14}),c.jsxs("span",{children:["Loaded on ",C.backendMode]})]}),C.backendDevice&&c.jsx("span",{className:"status-chip",style:{cursor:"default"},children:c.jsx("span",{children:C.backendDevice})}),C.backendBinary&&c.jsx("span",{className:"status-chip",style:{cursor:"default"},children:c.jsx("span",{children:C.backendBinary})})]})]}),c.jsxs("button",{className:"m3-btn m3-btn-error",onClick:Nt,disabled:de||$!==null,children:[de?c.jsx(aa,{className:"progress-spinner",size:16}):c.jsx(rn,{size:16}),c.jsx("span",{children:de?"Unloading":"Unload Server"})]})]})}),de&&c.jsxs("div",{className:"m3-card",style:{borderLeft:"4px solid var(--md-sys-color-error)",marginTop:"16px"},children:[c.jsx("h4",{style:{fontWeight:700,marginBottom:"8px"},children:"Unloading Model"}),c.jsxs("div",{className:"model-progress-section",style:{margin:"8px 0 0 0"},children:[c.jsxs("div",{className:"model-progress-label",children:[c.jsx("span",{children:ct.phase||"Unloading model..."}),c.jsxs("span",{children:[Math.round(ct.progress),"%"]})]}),c.jsx("div",{className:"model- Line truncated
Models and generated outputs are not included.`,confirmLabel:"Clean",danger:!0})){de(!0);try{await G0(Se.map(D=>D.id)),await I()}catch(D){E({title:"Cleanup Failed",message:D.message||String(D),danger:!0})}finally{de(!1)}}},[Se,I,E,x]);U.useEffect(()=>{async function k(){const ie=await Q0();Le(ie)}k()},[]),U.useEffect(()=>{J?localStorage.setItem("active-model",J):localStorage.removeItem("active-model")},[J]),U.useEffect(()=>{let k=null;async function ie(){const D=await Cl();if(!!D.ready){k&&clearInterval(k);const W=localStorage.getItem("active-model");if(W)$(W),te(!0);else{try{if(D&&D.settings&&D.settings.model){const ae=D.settings.model.split(/[\\/]/).pop();$(ae)}}catch{}te(!0)}}else if(!(typeof window<"u"&&window.__TAURI_INTERNALS__!==void 0)){const ae=localStorage.getItem("active-model");ae&&($(ae),te(!1))}}return ie(),k=setInterval(ie,2e3),()=>{k&&clearInterval(k)}},[]),U.useEffect(()=>{async function k(){try{const D=await L0();R(B=>B.cpu_usage===D.cpu_usage&&B.ram_used_gb===D.ram_used_gb&&B.ram_total_gb===D.ram_total_gb&&B.gpu_name===D.gpu_name&&B.vram_used_gb===D.vram_used_gb&&B.vram_total_gb===D.vram_total_gb?B:D)}catch{}}k();const ie=setInterval(k,1500);return()=>clearInterval(ie)},[]),U.useEffect(()=>{if(J){const k=J.toLowerCase();k.includes("flux")||k.includes("schnell")?V(ie=>({...ie,steps:4,cfgScale:1,width:1024,height:1024})):k.includes("lightning")||k.includes("turbo")?V(ie=>({...ie,steps:4,cfgScale:1.5,width:1024,height:1024})):k.includes("sd15")?V(ie=>({...ie,steps:25,cfgScale:7,width:512,height:512})):k.includes("sd35")?V(ie=>({...ie,steps:20,cfgScale:4.5,width:1024,height:1024})):V(ie=>({...ie,steps:20,cfgScale:7,width:512,height:512}))}},[J]);const Me=U.useMemo(()=>{switch(w){case"generator":return c.jsx(ag,{prompt:A,setPrompt:O,negativePrompt:Q,setNegativePrompt:P,constraints:G,setConstraints:V,activeModel:J,generatedImages:Ke,setGeneratedImages:Le,isGenerating:_,setIsGenerating:C,generationProgress:Z,setGenerationProgress:me,setActiveTab:h,showAlert:E,showConfirm:x});case"models":return c.jsx(ug,{activeModel:J,setActiveModel:$,serverRunning:fe,setServerRunning:te,constraints:G,showAlert:E,showConfirm:x});case"constraints":return c.jsx(fg,{constraints:G,setConstraints:V,activeModel:J,specs:ce,backendOptions:ke});default:return null}},[w,A,Q,G,J,Ke,_,Z,fe,ce,ke,E,x]),yt=U.useMemo(()=>c.jsx(E0,{activeTab:w,setActiveTab:h,specs:ce}),[w,ce]),Nt=U.useCallback(async()=>{if(!(!fe||lt)){Ae(!0);try{await Js(),te(!1),$(null)}catch(k){console.error("Failed to stop server:",k),E({title:"Stop Server Failed",message:k.message||String(k),danger:!0})}finally{Ae(!1)}}},[fe,lt,E]),Je=[...De?.stale?["Restart Local AI Image Generator so the local server loads the latest API."]:[],...De?.issues||[]],Ht=Se.reduce((k,ie)=>k+Number(ie.sizeBytes||0),0),zt=!!(De&&(Je.length>0||Se.length>0));return c.jsxs("div",{className:"app-container",children:[yt,c.jsxs("div",{className:"main-content",children:[c.jsx(D0,{telemetry:j,serverRunning:fe,activeModel:J,onStopServer:Nt,isStoppingServer:lt,theme:q,setTheme:H}),zt&&c.jsxs("div",{className:`m3-card readiness-card ${De?.stale||Je.length>0?"readiness-card-warning":""}`,children:[c.jsxs("div",{className:"readiness-header",children:[c.jsxs("div",{children:[c.jsx("h3",{className:"m3-card-title",style:{marginBottom:"4px"},children:De?.stale?"Restart Required":Je.length>0?"System Readiness":"Safe Cleanup Available"}),c.jsx("p",{className:"m3-card-subtitle",style:{margin:0},children:De?.stale?`Running server build: ${De.build||"unknown"}`:Je.length>0?"Local AI Image Generator found setup items that may need attention.":`${Se.length} temporary item${Se.length===1?"":"s"} can be cleaned (${ii(Ht)}).`})]}),c.jsxs("div",{className:"readiness-actions",children:[c.jsx("button",{className:"m3-btn m3-btn-outlined",onClick:I,disabled:Mt,children:Mt?"Checking":"Refresh"}),c.jsx("button",{className:"m3-btn m3-btn-tonal",onClick:se,children:Ze?"Copied":"Copy Diagnostics"}),Se.length>0&&c.jsxs("button",{className:"m3-btn m3-btn-error",onClick:Qe,disabled:Mt,children:["Clean ",ii(Ht)]})]})]}),Je.length>0&&c.jsx("div",{className:"readiness-issues",children:Je.slice(0,4).map(k=>c.jsx("span",{children:k},k))}),Se.length>0&&c.jsx("div",{className:"readiness-cleanup-list",children:Se.slice(0,3).map(k=>c.jsxs("span",{title:k.path,children:[k.name," · ",k.size," · ",k.reason]},k.id))})]}),Me]}),b&&c.jsx("div",{className:"app-dialog-backdrop",role:"presentation",children:c.jsxs("div",{className:"app-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"app-dialog-title",children:[c.jsx("h3",{id:"app-dialog-title",className:"app-dialog-title",children:b.title}),c.jsx("p",{className:"app-dialog-message",children:b.message}),c.jsxs("div",{className:"app-dialog-actions",children:[b.type==="confirm"&&c.jsx("button",{className:"m3-btn m3-btn-outlined",onClick:()=>r(!1),children:b.cancelLabel}),c.jsx("button",{className:`m3-btn ${b.danger?"m3-btn-error":"m3-btn-filled"}`,onClick:()=>r(!0),children Line truncated
-121
View File
@@ -1,121 +0,0 @@
(function(){const b=document.createElement("link").relList;if(b&&b.supports&&b.supports("modulepreload"))return;for(const x of document.querySelectorAll('link[rel="modulepreload"]'))r(x);new MutationObserver(x=>{for(const N of x)if(N.type==="childList")for(const G of N.addedNodes)G.tagName==="LINK"&&G.rel==="modulepreload"&&r(G)}).observe(document,{childList:!0,subtree:!0});function E(x){const N={};return x.integrity&&(N.integrity=x.integrity),x.referrerPolicy&&(N.referrerPolicy=x.referrerPolicy),x.crossOrigin==="use-credentials"?N.credentials="include":x.crossOrigin==="anonymous"?N.credentials="omit":N.credentials="same-origin",N}function r(x){if(x.ep)return;x.ep=!0;const N=E(x);fetch(x.href,N)}})();function fm(f){return f&&f.__esModule&&Object.prototype.hasOwnProperty.call(f,"default")?f.default:f}var Bs={exports:{}},ai={};var Zd;function E0(){if(Zd)return ai;Zd=1;var f=Symbol.for("react.transitional.element"),b=Symbol.for("react.fragment");function E(r,x,N){var G=null;if(N!==void 0&&(G=""+N),x.key!==void 0&&(G=""+x.key),"key"in x){N={};for(var H in x)H!=="key"&&(N[H]=x[H])}else N=x;return x=N.ref,{$$typeof:f,type:r,key:G,ref:x!==void 0?x:null,props:N}}return ai.Fragment=b,ai.jsx=E,ai.jsxs=E,ai}var Kd;function N0(){return Kd||(Kd=1,Bs.exports=E0()),Bs.exports}var c=N0(),Rs={exports:{}},le={};var Jd;function A0(){if(Jd)return le;Jd=1;var f=Symbol.for("react.transitional.element"),b=Symbol.for("react.portal"),E=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),x=Symbol.for("react.profiler"),N=Symbol.for("react.consumer"),G=Symbol.for("react.context"),H=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),A=Symbol.for("react.lazy"),U=Symbol.for("react.activity"),V=Symbol.iterator;function fe(d){return d===null||typeof d!="object"?null:(d=V&&d[V]||d["@@iterator"],typeof d=="function"?d:null)}var X={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},L=Object.assign,te={};function W(d,j,B){this.props=d,this.context=j,this.refs=te,this.updater=B||X}W.prototype.isReactComponent={},W.prototype.setState=function(d,j){if(typeof d!="object"&&typeof d!="function"&&d!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,d,j,"setState")},W.prototype.forceUpdate=function(d){this.updater.enqueueForceUpdate(this,d,"forceUpdate")};function Oe(){}Oe.prototype=W.prototype;function de(d,j,B){this.props=d,this.context=j,this.refs=te,this.updater=B||X}var Ee=de.prototype=new Oe;Ee.constructor=de,L(Ee,W.prototype),Ee.isPureReactComponent=!0;var ke=Array.isArray;function Ae(){}var P={H:null,A:null,T:null,S:null},ve=Object.prototype.hasOwnProperty;function Ke(d,j,B){var q=B.ref;return{$$typeof:f,type:d,key:j,ref:q!==void 0?q:null,props:B}}function yt(d,j){return Ke(d.type,j,d.props)}function me(d){return typeof d=="object"&&d!==null&&d.$$typeof===f}function Je(d){var j={"=":"=0",":":"=2"};return"$"+d.replace(/[=:]/g,function(B){return j[B]})}var st=/\/+/g;function $e(d,j){return typeof d=="object"&&d!==null&&d.key!=null?Je(""+d.key):j.toString(36)}function Xe(d){switch(d.status){case"fulfilled":return d.value;case"rejected":throw d.reason;default:switch(typeof d.status=="string"?d.then(Ae,Ae):(d.status="pending",d.then(function(j){d.status==="pending"&&(d.status="fulfilled",d.value=j)},function(j){d.status==="pending"&&(d.status="rejected",d.reason=j)})),d.status){case"fulfilled":return d.value;case"rejected":throw d.reason}}throw d}function _(d,j,B,q,K){var ee=typeof d;(ee==="undefined"||ee==="boolean")&&(d=null);var se=!1;if(d===null)se=!0;else switch(ee){case"bigint":case"string":case"number":se=!0;break;case"object":switch(d.$$typeof){case f:case b:se=!0;break;case A:return se=d._init,_(se(d._payload),j,B,q,K)}}if(se)return K=K(d),se=q===""?"."+$e(d,0):q,ke(K)?(B="",se!=null&&(B=se.replace(st,"$&/")+"/"),_(K,j,B,"",function(gt){return gt})):K!=null&&(me(K)&&(K=yt(K,B+(K.key==null||d&&d.key===K.key?"":(""+K.key).replace(st,"$&/")+"/")+se)),j.push(K)),1;se=0;var Ze=q===""?".":q+":";if(ke(d))for(var Ne=0;Ne<d.length;Ne++)q=d[Ne],ee=Ze+$e(q,Ne),se+=_(q,j,B,ee,K);else if(Ne=fe(d),typeof Ne=="function")for(d=Ne.call(d),Ne=0;!(q=d.next()).done;)q=q.value,ee=Ze+$e(q,Ne++),se+=_(q,j,B,ee,K);else if(ee==="object"){if(typeof d.then=="function")return _(Xe(d),j,B,q,K);throw j=String(d),Error("Objects are not valid as a React child (found: "+(j==="[object Object]"?"object with keys {"+Object.keys(d).join(", ")+"}":j)+"). If you meant to render a collection of children, use an array instead.")}return se}function C(d,j,B){if(d==null)return d;var q=[],K=0;return _(d,q,"","",function(ee){return j.call(B,ee,K++)}),q}function J(d){if(d._status===-1){var j=d._result;j=j(),j.then(function(B){(d._status===0||d._status===-1)&&(d._status=1,d._result=B)},function(B){(d._status===0||d._status===-1)&&(d._status=2,d._result=B)}),Line truncated
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+We+e+Ht}var Z=!1;function ie(e,t){if(!e||Z)return"";Z=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(t){var M=function(){throw Error()};if(Object.defineProperty(M.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(M,[])}catch(S){var v=S}Reflect.construct(e,[],M)}else{try{M.call()}catch(S){v=S}e.call(M.prototype)}}else{try{throw Error()}catch(S){v=S}(M=e())&&typeof M.catch=="function"&&M.catch(function(){})}}catch(S){if(S&&v&&typeof S.stack=="string")return[S.stack,v.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var n=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");n&&n.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var i=a.DetermineComponentFrameRoot(),u=i[0],s=i[1];if(u&&s){var o=u.split(`
`),p=s.split(`
`);for(n=a=0;a<o.length&&!o[a].includes("DetermineComponentFrameRoot");)a++;for(;n<p.length&&!p[n].includes("DetermineComponentFrameRoot");)n++;if(a===o.length||n===p.length)for(a=o.length-1,n=p.length-1;1<=a&&0<=n&&o[a]!==p[n];)n--;for(;1<=a&&0<=n;a--,n--)if(o[a]!==p[n]){if(a!==1||n!==1)do if(a--,n--,0>n||o[a]!==p[n]){var z=`
`+o[a].replace(" at new "," at ");return e.displayName&&z.includes("<anonymous>")&&(z=z.replace("<anonymous>",e.displayName)),z}while(1<=a&&0<=n);break}}}finally{Z=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?Tt(l):""}function D(e,t){switch(e.tag){case 26:case 27:case 5:return Tt(e.type);case 16:return Tt("Lazy");case 13:return e.child!==t&&t!==null?Tt("Suspense Fallback"):Tt("Suspense");case 19:return Tt("SuspenseList");case 0:case 15:return ie(e.type,!1);case 11:return ie(e.type.render,!1);case 1:return ie(e.type,!0);case 31:return Tt("Activity");default:return""}}function R(e){try{var t="",l=null;do t+=D(e,l),l=e,e=e.return;while(e);return t}catch(a){return`
Error generating stack: `+a.message+`
`+a.stack}}var F=Object.prototype.hasOwnProperty,ae=f.unstable_scheduleCallback,At=f.unstable_cancelCallback,Gt=f.unstable_shouldYield,dl=f.unstable_requestPaint,Re=f.unstable_now,al=f.unstable_getCurrentPriorityLevel,Fe=f.unstable_ImmediatePriority,za=f.unstable_UserBlockingPriority,Ta=f.unstable_NormalPriority,Y=f.unstable_LowPriority,ye=f.unstable_IdlePriority,He=f.log,pt=f.unstable_setDisableYieldValue,ft=null,tt=null;function qt(e){if(typeof He=="function"&&pt(e),tt&&typeof tt.setStrictMode=="function")try{tt.setStrictMode(ft,e)}catch{}}var rt=Math.clz32?Math.clz32:xe,vt=Math.log,Se=Math.LN2;function xe(e){return e>>>=0,e===0?32:31-(vt(e)/Se|0)|0}var Ge=256,_e=262144,Ie=4194304;function je(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function dt(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,i=e.suspendedLanes,u=e.pingedLanes;e=e.warmLanes;var s=a&134217727;return s!==0?(a=s&~i,a!==0?n=je(a):(u&=s,u!==0?n=je(u):l||(l=s&~e,l!==0&&(n=je(l))))):(s=a&~i,s!==0?n=je(s):u!==0?n=je(u):l||(l=a&~e,l!==0&&(n=je(l)))),n===0?0:t!==0&&t!==n&&(t&i)===0&&(i=n&-n,l=t&-t,i>=l||i===32&&(l&4194048)!==0)?t:n}function bt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function cl(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ci(){var e=Ie;return Ie<<=1,(Ie&62914560)===0&&(Ie=4194304),e}function ju(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function mn(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ym(e,t,l,a,n,i){var u=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var s=e.entanglements,o=e.expirationTimes,p=e.hiddenUpdates;for(l=u&~l;0<l;){var z=31-rt(l),M=1<<z;s[z]=0,o[z]=-1;var v=p[z];if(v!==null)for(p[z]=null,z=0;z<v.length;z++){var S=v[z];S!==null&&(S.lane&=-536870913)}l&=~M}a!==0&&$s(e,a,0),i!==0&&n===0&&e.tag!==0&&(e.suspendedLanes|=i&~(u&~t))}function $s(e,t,l){e.pendingLanes|=t,e.suspendedLanes&=~t;var a=31-rt(t);e.entangledLanes|=t,e.entanglements[a]=e.entanglements[a]|1073741824|l&261930}function Ws(e,t){var l=e.entangledLanes|=t;for(e=e.entanglements;l;){var a=31-rt(l),n=1<<a;n&t|e[a]&t&&(e[a]|=t),l&=~n}}function Fs(e,t){var l=t&-t;return l=(l&42)!==0?1:zu(l),(l&(e.suspendedLanes|t))!==0?0:l}function zu(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function Tu(e){return e&=-e,2<e?8<e?(e&134217727)!==0?32:268435456:8:2}function Is(){var e=C.p;return e!==0?e:(e=window.event,e===void 0?32:qd(e.type))}function Ps(e,t){var l=C.p;try{return C.p=e,t()}finally{C.p=l}}var Ol=Math.random().toString(36).slice(2),St="__reactFiber$"+Ol,Dt="__reactProps$"+Ol,Ma="__reactContainer$"+Ol,Mu="__reactEvents$"+Ol,gm="__reactListeners$"+Ol,pm="__reactHandles$"+Ol,ef="__reactResources$"+Ol,hn="__reactMarker$"+Ol;function Eu(e){delete e[St],delete e[Dt],delete e[Mu],delete e[gm],delete e[pm]}function Ea(e){var t=e[St];if(t)return t;for(var l=e.parentNode;l;){if(t=l[Ma]||l[St]){if(l=t.alternate,t.child!==null||l!==null&&l.child!==null)for(e=jd(e);e!==null;){if(l=e[St])return l;e=jd(e)}return t}e=l,l=e.parentNode}return null}function Na(e){if(e=e[St]||e[Ma]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function yn(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(r(33))}function Aa(e){var t=e[ef];return t||(t=e[ef]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function mt(e){e[hn]=!0}var tf=new Set,lf={};function ua(e,t){Da(e,t),Da(e+"Capture",t)}function Da(e,t){for(lf[e]=t,e=0;e<t.length;e++)tf.add(t[e])}var vm=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F Line truncated
`).replace($h,"")}function md(e,t){return t=dd(t),dd(e)===t}function we(e,t,l,a,n,i){switch(l){case"children":typeof a=="string"?t==="body"||t==="textarea"&&a===""||Oa(e,a):(typeof a=="number"||typeof a=="bigint")&&t!=="body"&&Oa(e,""+a);break;case"className":fi(e,"class",a);break;case"tabIndex":fi(e,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":fi(e,l,a);break;case"style":df(e,a,i);break;case"data":if(t!=="object"){fi(e,"data",a);break}case"src":case"href":if(a===""&&(t!=="a"||l!=="href")){e.removeAttribute(l);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){e.removeAttribute(l);break}a=ri(""+a),e.setAttribute(l,a);break;case"action":case"formAction":if(typeof a=="function"){e.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof i=="function"&&(l==="formAction"?(t!=="input"&&we(e,t,"name",n.name,n,null),we(e,t,"formEncType",n.formEncType,n,null),we(e,t,"formMethod",n.formMethod,n,null),we(e,t,"formTarget",n.formTarget,n,null)):(we(e,t,"encType",n.encType,n,null),we(e,t,"method",n.method,n,null),we(e,t,"target",n.target,n,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){e.removeAttribute(l);break}a=ri(""+a),e.setAttribute(l,a);break;case"onClick":a!=null&&(e.onclick=hl);break;case"onScroll":a!=null&&re("scroll",e);break;case"onScrollEnd":a!=null&&re("scrollend",e);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(r(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(r(60));e.innerHTML=l}}break;case"multiple":e.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":e.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){e.removeAttribute("xlink:href");break}l=ri(""+a),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,""+a):e.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,""):e.removeAttribute(l);break;case"capture":case"download":a===!0?e.setAttribute(l,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,a):e.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?e.setAttribute(l,a):e.removeAttribute(l);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?e.removeAttribute(l):e.setAttribute(l,a);break;case"popover":re("beforetoggle",e),re("toggle",e),si(e,"popover",a);break;case"xlinkActuate":ml(e,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":ml(e,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":ml(e,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":ml(e,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":ml(e,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":ml(e,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":ml(e,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":ml(e,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":ml(e,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":si(e,"is",a);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=jm.get(l)||l,si(e,l,a))}}function ps(e,t,l,a,n,i){switch(l){case"style":df(e,a,i);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(r(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(r(60));e.innerHTML=l}}break;case"children":typeof a=="string"?Oa(e,a):(typeof a=="number"||typeof a=="bigint")&&Oa(e,""+a);break;case"onScroll":a!=null&&re("scroll",e);break;case"onScrollEnd":a!=null&&re("scrollend",e);break;case"onClick":a!=null&&(e.onclick=hl);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
<!-- Stars -->
<circle cx="80" cy="100" r="1.5" fill="#ffffff" opacity="0.6"/>
<circle cx="160" cy="60" r="1" fill="#ffffff" opacity="0.5"/>
<circle cx="280" cy="120" r="2" fill="#ffffff" opacity="0.8"/>
<circle cx="360" cy="80" r="1.5" fill="#ffffff" opacity="0.4"/>
<circle cx="440" cy="150" r="1" fill="#ffffff" opacity="0.7"/>
<circle cx="400" cy="280" r="2" fill="#ffffff" opacity="0.9"/>
<circle cx="100" cy="300" r="1.5" fill="#ffffff" opacity="0.5"/>
<!-- Moon -->
<circle cx="256" cy="220" r="90" fill="url(#moonGrad)" />
<circle cx="226" cy="190" r="80" fill="#0F172A" />
`):L.includes("cat")||L.includes("kitten")||L.includes("feline")?(W="#4F46E5",Oe="#7C3AED",Ee="Stylized Cat Portrait",de=`
<!-- Cat head & ears -->
<polygon points="176,280 146,160 216,210" fill="#312E81" />
<polygon points="336,280 366,160 296,210" fill="#312E81" />
<circle cx="256" cy="270" r="80" fill="#4338CA" />
<!-- Eyes -->
<ellipse cx="226" cy="260" rx="14" ry="8" fill="#10B981" />
<ellipse cx="286" cy="260" rx="14" ry="8" fill="#10B981" />
<circle cx="226" cy="260" r="4" fill="#000" />
<circle cx="286" cy="260" r="4" fill="#000" />
<!-- Nose & Whiskers -->
<polygon points="256,285 248,275 264,275" fill="#F43F5E" />
<line x1="226" y1="290" x2="166" y2="280" stroke="#E2E8F0" stroke-width="2" />
<line x1="226" y1="295" x2="156" y2="295" stroke="#E2E8F0" stroke-width="2" />
<line x1="286" y1="290" x2="346" y2="280" stroke="#E2E8F0" stroke-width="2" />
<line x1="286" y1="295" x2="356" y2="295" stroke="#E2E8F0" stroke-width="2" />
`):L.includes("dog")||L.includes("puppy")||L.includes("canine")?(W="#D97706",Oe="#B45309",Ee="Friendly Dog Portrait",de=`
<!-- Dog head & floppy ears -->
<ellipse cx="166" cy="260" rx="20" ry="60" fill="#78350F" />
<ellipse cx="346" cy="260" rx="20" ry="60" fill="#78350F" />
<circle cx="256" cy="250" r="70" fill="#92400E" />
<ellipse cx="256" cy="280" rx="40" ry="30" fill="#D97706" />
<!-- Eyes -->
<circle cx="226" cy="230" r="10" fill="#1E293B" />
<circle cx="286" cy="230" r="10" fill="#1E293B" />
<circle cx="222" cy="226" r="3" fill="#ffffff" />
<circle cx="282" cy="226" r="3" fill="#ffffff" />
<!-- Nose -->
<ellipse cx="256" cy="275" rx="16" ry="10" fill="#1E293B" />
`):L.includes("city")||L.includes("cyberpunk")||L.includes("tokyo")||L.includes("neon")?(W="#111827",Oe="#311042",Ee="Neon Cyberpunk Skyline",de=`
<!-- Skyline -->
<rect x="60" y="160" width="80" height="352" fill="#1F2937" opacity="0.9"/>
<rect x="180" y="100" width="100" height="412" fill="#111827" />
<rect x="320" y="200" width="120" height="312" fill="#1F2937" opacity="0.85"/>
<rect x="100" y="240" width="110" height="272" fill="#374151" opacity="0.6"/>
<!-- Neon details -->
<line x1="230" y1="100" x2="230" y2="40" stroke="#EC4899" stroke-width="3" />
<rect x="200" y="140" width="60" height="15" fill="#10B981" opacity="0.7"/>
<rect x="200" y="170" width="60" height="15" fill="#10B981" opacity="0.7"/>
<rect x="200" y="200" width="60" height="15" fill="#3B82F6" opacity="0.7"/>
<rect x="70" y="200" width="15" height="100" fill="#F59E0B" opacity="0.5"/>
<rect x="340" y="250" width="20" height="150" fill="#EF4444" opacity="0.6"/>
`):L.includes("car")||L.includes("vehicle")||L.includes("supercar")?(W="#DC2626",Oe="#991B1B",Ee="Futuristic Concept Car",de=`
<!-- Road / Perspective grid -->
<line x1="256" y1="350" x2="0" y2="512" stroke="#4B5563" stroke-width="3" />
<line x1="256" y1="350" x2="512" y2="512" stroke="#4B5563" stroke-width="3" />
<!-- Car body -->
<polygon points="126,380 256,330 386,380 346,440 166,440" fill="#EF4444" />
<polygon points="176,370 256,340 336,370 326,390 186,390" fill="#1E293B" opacity="0.9" />
<!-- Headlights -->
<polygon points="136,410 176,415 166,425 136,420" fill="#FBBF24" />
<polygon points="376,410 336,415 346,425 376,420" fill="#FBBF24" />
<!-- Wheels -->
<ellipse cx="156" cy="440" rx="30" ry="15" fill="#111" />
<ellipse cx="356" cy="440" rx="30" ry="15" fill="#111" />
`):(W="#06B6D4",Oe="#0891B2",Ee="Abstract Creative Concept",de=`
<path d="M0,256 C150,150 350,350 512,256 L512,512 L0,512 Z" fill="#0891B2" opacity="0.4" />
<path d="M0,350 C200,200 300,450 512,350 L512,512 L0,512 Z" fill="#0E7490" opacity="0.6" />
<circle cx="380" cy="180" r="50" fill="#F59E0B" opacity="0.8" />
`);const ke=`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="${W}" />
<stop offset="100%" stop-color="${Oe}" />
</linearGradient>
<linearGradient id="moonGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FEF08A" />
<stop offset="100%" stop-color="#FDE047" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="512" height="512" fill="url(#bgGrad)" />
<!-- Shapes -->
${de}
<!-- Premium UI Overlay Frame -->
<rect x="20" y="20" width="472" height="472" fill="none" stroke="#ffffff" stroke-width="1.5" opacity="0.15" rx="8"/>
<!-- Prompt & Details text -->
<rect x="40" y="410" width="432" height="62" fill="#0F172A" opacity="0.85" rx="6" />
<text x="60" y="435" fill="#F1F5F9" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="bold">${Ee}</text>
<text x="60" y="455" fill="#94A3B8" font-family="system-ui, -apple-system, sans-serif" font-size="11">Seed: ${te} • Steps: ${h.steps} • Scale: ${h.cfg_scale}</text>
</svg>
`,P=`data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(ke)))}`,ve=parseFloat(((Date.now()-H)/1e3).toFixed(1));return{image:P,seed:h.seed,duration_sec:ve}},U=!!h.image;let V=`${w}/v1/images/generations`,fe={prompt:h.prompt,negative_prompt:h.negative_prompt||"",n:1,size:`${h.width}x${h.height}`,response_format:"b64_json",steps:h.steps,cfg_scale:h.cfg_scale,seed:h.seed,sample_method:h.sampler};if(U){const X=String(h.image).replace(/^data:[^;]+;base64,/,"");V=`${w}/sdapi/v1/img2img`,fe={init_images:[X],prompt:h.prompt,negative_prompt:h.negative_prompt||"",denoising_strength:h.denoising_strength,steps:h.steps,cfg_scale:h.cfg_scale,seed:h.seed,width:h.width,height:h.height,sampler_name:h.sampler,sample_method:h.sampler,batch_size:1,n_iter:1,send_images:!0,save_images:!1}}try{const X=await fetch(V,{method:"POST",headers:{"Content-Type":"application/json"},signal:G,body:JSON.stringify(fe)});if(X.ok){const L=await X.json(),te=L?.data?.[0]?.b64_json??L?.images?.[0];if(te){const W=parseFloat(((Date.now()-H)/1e3).toFixed(1));return{image:`data:image/png;base64,${te}`,seed:L.data?.[0]?.seed??h.seed,duration_sec:W}}}else{let L=`Generation failed (HTTP ${X.status})`;try{const te=await X.json();(te?.detail||te?.error?.message)&&(L=te.detail||te.error.message)}catch{}throw new Error(L)}}catch(X){if(X.name==="AbortError"||X.message.startsWith("Generation failed"))throw X;console.warn("Could not reach local server. Falling back to simulation mode.",X)}return await A()}async function Jy(f,b,E){if(f instanceof File){const H=f;if(!na())throw new Error("File import requires the local image generator server.");return await $y(H,b,E),{response:"Model imported successfully"}}if(ia()){const{listen:H}=await Uy(async()=>{const{listen:h}=await import("./event-DeDJUvqC.js");return{listen:h}},[]);let w=null;b&&(w=await H("import-progress",h=>{b(h.payload)}));try{return{response:await ja("import_model_file",{sourcePath:f}),unlisten:w}}catch(h){throw w&&w(),h}}const r=f.split(/[\\/]/).pop()||"imported_model.gguf";console.log(`Web Mode: Simulating copying ${r} to USB models folder`);const x=40;for(let H=1;H<=x;H++){if(E?.aborted)throw new DOMException("The user aborted a request.","AbortError");await new Promise(U=>setTimeout(U,150));const w=H/x*100,h=40+Math.sin(H)*5+Math.random()*2,A=(x-H)*.15;b&&b({filename:r,progress:w,speed_mb_s:h,eta_secs:A,status:"Copying to USB..."})}const N=localStorage.getItem("imported-models"),G=N?JSON.parse(N):[];return G.includes(r)||(G.push({filename:r,sizeBytes:0,size:"Unknown"}),localStorage.setItem("imported-models",JSON.stringify(G))),{response:"Model imported successfully"}}function $y(f,b,E){return new Promise((r,x)=>{const N=new XMLHttpRequest,G=Date.now();let H=!1;N.open("POST",`/api/import-model?filename=${encodeURIComponent(f.name)}`),N.setRequestHeader("Content-Type","application/octet-stream");const w=()=>{H=!0,N.abort()};E&&(E.aborted?w():E.addEventListener("abort",w,{once:!0})),N.upload.onprogress=h=>{if(!h.lengthComputable||!b)return;const A=Math.max(.1,(Date.now()-G)/1e3),U=h.loaded/A,V=Math.max(0,h.total-h.loaded);b({filename:f.name,progress:h.loaded/h.total*100,speed_mb_s:U/(1024*1024),eta_secs:V/Math.max(1,U),status:"Copying to models folder..."})},N.onload=()=>{let h={};try{h=JSON.parse(N.responseText||"{}")}catch{}N.status>=200&&N.status<300&&h.ok!==!1?(b&&b({filename:f.name,progress:100,speed_mb_s:0,eta_secs:0,status:"Import complete"}),r(h)):x(new Error(h.error||`Import failed (HTTP ${N.status})`))},N.onerror=()=>x(new Error("Import failed while uploading the file.")),N.onabort=()=>x(new DOMException(H?"Import cancelled by user.":"Import aborted.","AbortError")),N.send(f)})}async function Wy(f){if(ia())return await ja("delete_model_file",{filename:f});if(typeof window<"u"&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"))try{const x=await fetch("/api/delete-model",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:f})}),N=await x.json();if(!x.ok)throw new Error(N.error||"Failed to delete model file");return N}catch(x){throw console.error("Failed to delete model via local API:",x),x}const E=localStorage.getItem("imported-models");let r=E?JSON.parse(E):[];return r=r.filter(x=>ui(x).filename!==f),localStorage.setItem("imported-models",JSON.stringify(r)),`Simulated deletion of ${f} from browser session`}async function hm(){const f=await mm();try{return(await fetch(`${f}/v1/models`,{method:"GET"})).ok}catch{return!1}}async function Fy(f=30){const b=ia()||na()?f:3;for(let E=0;E<b;E++){let r=!1;if(na()?r=!!(await Cl()).ready:r=await hm(),r)return!0;await new Promise(x=>setTimeout(x,500))}return!1}async function Iy(f){try{return await(await fetch("/api/download-model",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:f})})).json()}catch(b){return console.error("Failed to start model download:",b),{ok:!1,error:b.message}}}async function Py(){try{return await(await fetch("/api/ca Line truncated
Seed: ${f.seed}${f.duration_sec?`
Time: ${f.duration_sec.toFixed(1)}s`:""}`,children:[c.jsx("img",{src:f.url,className:"gallery-thumb",alt:"Thumbnail",loading:"lazy",decoding:"async"}),E&&c.jsx("div",{className:"gallery-select-badge",children:c.jsx(ks,{size:14})})]})});function lg({prompt:f,setPrompt:b,negativePrompt:E,setNegativePrompt:r,constraints:x,setConstraints:N,activeModel:G,generatedImages:H,setGeneratedImages:w,isGenerating:h,setIsGenerating:A,generationProgress:U,setGenerationProgress:V,setActiveTab:fe,showAlert:X=async({message:te})=>window.alert(te),showConfirm:L=async({message:te})=>window.confirm(te)}){const[te,W]=O.useState(null),[Oe,de]=O.useState(null),[Ee,ke]=O.useState(null),[Ae,P]=O.useState(!1),[ve,Ke]=O.useState(0),[yt,me]=O.useState(0),[Je,st]=O.useState(0),[$e,Xe]=O.useState(""),[_,C]=O.useState(!1),[J,he]=O.useState(!1),[ce,d]=O.useState(!1),[j,B]=O.useState(null),[q,K]=O.useState(null),[ee,se]=O.useState(!1),[Ze,Ne]=O.useState([]),[gt,Et]=O.useState(null),We=O.useRef(null),Ht=O.useRef(null),Tt=O.useRef(!1),Z=Y=>{const ye=Y.target.files?.[0];if(Y.target.value="",!ye)return;if(!ye.type.startsWith("image/")){K("Please choose an image file (PNG, JPG, or WEBP).");return}const He=new FileReader;He.onload=()=>Et(He.result),He.onerror=()=>K("Could not read the selected image file."),He.readAsDataURL(ye)},ie=()=>Et(null),D=async()=>{if(!f.trim())return;K(null),d(!0),B(null);let Y=!1;try{const Se=await Cl();if(Se&&Se.settings){const xe=Se.settings,Ge=xe.model?xe.model.split(/[\\/]/).pop():null,_e=G?G.split(/[\\/]/).pop():null;(Ge!==_e||parseInt(xe.steps)!==parseInt(x.steps)||Math.abs(parseFloat(xe.cfgScale)-parseFloat(x.cfgScale))>.05||xe.sampler!==x.sampler||parseInt(xe.threads)!==parseInt(x.threads)||!!xe.useGpu!=(x.useGpu!==!1)||(xe.backendType||(xe.useGpu===!1?"cpu":"auto"))!==(x.backendType||(x.useGpu===!1?"cpu":"auto")))&&(Y=!0)}else Y=!0}catch{Y=!0}if(Y)try{console.log("Settings out of sync, restarting backend..."),await Vs(G,x);let Se=!1,xe=null;for(let Ge=0;Ge<240;Ge++){const _e=await Cl();if(_e.loading&&B({progress:_e.loading.progress||0,phase:_e.loading.phase||"Loading model...",speed:_e.loading.speed||"",current:_e.loading.current||0,total:_e.loading.total||0,backendMode:_e.loading.backendMode||_e.settings?.backendMode||"",device:_e.loading.device||_e.settings?.backendDevice||""}),_e.ready){Se=!0;break}if(_e.error){xe=_e.error;break}if(!_e.running&&Ge>3){xe="The backend process terminated immediately on startup.";break}await new Promise(Ie=>setTimeout(Ie,500))}if(!Se)throw new Error(xe||"Backend server failed to start after applying configuration changes.")}catch(Se){console.error("Failed to restart backend:",Se),K(Se.message||"Failed to sync settings with backend."),d(!1);return}d(!1),B(null),We.current&&clearInterval(We.current),A(!0),V(0),Ke(0),st(0),Xe(""),Tt.current=!1,C(!1),se(!1),ke(null),W(null);const ye=(x.width>=1024?140:35)*.04,He=x.width>=1024?140:35,pt=(x.backendType||(x.useGpu===!1?"cpu":"auto"))!=="cpu";let ft=pt?ye:He,tt=!1,qt=0,rt=0,vt=0;me(Math.round(x.steps*ft)),We.current=setInterval(async()=>{vt++,Ke(vt);try{const Se=await eg();if(Se&&Se.active){const xe=Se.decoding||!1;se(xe);const Ge=xe?Se.steps||x.steps||20:Se.step||0,_e=Se.steps||x.steps||20,Ie=xe?"":Se.speed||"";(xe||Se.steps>0)&&(Tt.current=!0,st(Ge)),Xe(xe?"decoding":Ie);let je=0;xe?je=99:_e>0&&(je=Math.round(Ge/_e*100)),je=Math.min(99,je),xe||Se.steps>0?V(je):V(bt=>Math.max(bt,je));let dt=0;if(xe)dt=3;else{const bt=parseFloat(Ie);if(!isNaN(bt)&&bt>0){const cl=Math.max(0,_e-Ge);Ie.includes("it/s")?dt=cl/bt:Ie.includes("s/it")&&(dt=cl*bt)}else{const cl=pt?ye:He;dt=Math.max(0,(_e-Ge)*cl)}}if(pt&&!tt){const bt=Ie.includes("it/s")&&numericSpeed<.2,cl=Ie.includes("s/it")&&numericSpeed>5;(bt||cl)&&(tt=!0,C(!0))}me(Math.round(dt))}else V(xe=>Math.min(15,xe+1)),me(xe=>Math.max(1,xe-1))}catch(Se){console.warn("Failed to get generation progress, running simulation fallback:",Se),pt&&!tt&&vt>x.steps*ye*1.3&&(tt=!0,C(!0),ft=He,qt=vt,rt=Math.min(95,Math.round(vt/(x.steps*ye)*100)));let xe=Math.min(x.steps-1,Math.floor(vt/ft)),Ge=x.steps*ft;if(vt>=Ge){const je=Math.ceil(vt/Math.max(.5,xe));ft=Math.max(ft+15,je+15),Ge=x.steps*ft,xe=Math.min(x.steps-1,Math.floor(vt/ft))}const _e=Math.max(1,Ge-vt);me(Math.round(_e));let Ie=0;if(tt){const je=99-rt,dt=Ge-qt;if(dt>0){const bt=Math.min(1,(vt-qt)/dt);if(bt<.85)Ie=Math.round(rt+bt*je);else{const cl=(vt-(qt+dt*.85))/(ft*2),ci=1-Math.exp(-cl);Ie=Math.min(99,Math.round(rt+.85*je+.14*je*ci))}}else Ie=99}else if(vt<Ge*.85)Ie=Math.round(vt/Ge*100);else{const je=(vt-Ge*.85)/(ft*2),dt=1-Math.exp(-je);Ie=Math.min(99,Math.round(85+14*dt))}Tt.current||V(je=>Math.max(je,Ie))}},1e3);try{Ht.current=new AbortController;const Se=await Ky(f,E,x,G,gt,je=>V(dt=>Math.max(dt,je)),Ht.current.signal);We.current&&clearInterval(We.current),V(100),st(x.steps),me(0),Xe("");const xe={prompt:f,negativePrompt:E,seed:Se.seed,steps:x.steps,cfgScale:x.cfgScale,width:x.width,height:x.height,sampler:x.sampler,model:G,mode:gt?"img2img":"txt2Line truncated
${dl}
Update the UI status anyway?`,confirmLabel:"Update UI"})&&(b(D),r(!0)):await G({title:"Backend Not Running",message:`C++ backend server is not running on port ${At}.
To run locally, start:
${dl}
Proceed in Simulation Mode instead?`,confirmLabel:"Proceed"})&&(b(D),r(!1));return}te(D),Oe({progress:0,phase:"Starting backend...",speed:"",current:0,total:0,model:D,backendMode:"",backendBinary:"",device:""});try{const Gt=await Vs(D,x);console.log(Gt);let dl=!1,Re=null;for(let al=0;al<240;al++){const Fe=await Cl();if(Fe.loading&&Oe({progress:Fe.loading.progress||0,phase:Fe.loading.phase||"Loading model...",speed:Fe.loading.speed||"",current:Fe.loading.current||0,total:Fe.loading.total||0,model:Fe.loading.model||D,backendMode:Fe.loading.backendMode||Fe.settings?.backendMode||"",backendBinary:Fe.loading.backendBinary||Fe.settings?.backendBinary||"",device:Fe.loading.device||Fe.settings?.backendDevice||""}),Fe.ready){dl=!0;break}if(Fe.error){Re=Fe.error;break}if(!Fe.running&&al>3){Re="The backend process terminated immediately on startup.";break}await new Promise(za=>setTimeout(za,500))}if(dl)Oe(al=>({...al||{},progress:100,phase:"Model ready",speed:"",current:al?.current||0,total:al?.total||0,model:D})),b(D),r(!0);else throw new Error(Re||`Model server failed to respond on port ${At}.`)}catch(Gt){console.error("Failed to load model:",Gt),N({title:"Model Load Failed",message:Gt.message||String(Gt),danger:!0})}finally{setTimeout(()=>Oe(null),800),te(null)}},gt=async()=>{const D=Xe;_(null),await Et(),D&&await Ne(D)},Et=async()=>{if(me)return;Je(!0),$e({progress:10,phase:"Stopping backend process..."});const D=setInterval(async()=>{try{const R=await Cl();R.unloading?.active?$e({progress:R.unloading.progress||50,phase:R.unloading.phase||"Unloading model..."}):$e(F=>({progress:Math.min(95,F.progress+10),phase:F.phase||"Unloading model..."}))}catch{}},300);try{await Js(),$e({progress:100,phase:"Model unloaded"}),b(null),_(null),r(!1)}catch(R){console.error(R),N({title:"Unload Failed",message:R.message||String(R),danger:!0})}finally{clearInterval(D),setTimeout(()=>{Je(!1),$e({progress:0,phase:""})},500)}},We=async D=>{if(await G({title:"Delete Model?",message:`Delete "${D}" from your drive?`,confirmLabel:"Delete",danger:!0}))try{await Wy(D),await B(),f===D&&await Et()}catch(R){console.error("Failed to delete model:",R),N({title:"Delete Failed",message:R.message||String(R),danger:!0})}},Ht=async(D,R)=>{const F=new AbortController;yt(F),Ee(0),Ae({filename:R,speed:"0.0",eta:0,status:"Initiating file copy..."});try{await Jy(D,ae=>{Ee(Math.round(ae.progress)),Ae({filename:ae.filename,speed:ae.speed_mb_s.toFixed(1),eta:Math.round(ae.eta_secs),status:ae.status})},F.signal),await B()}catch(ae){console.error("Import failed:",ae),ae.name!=="AbortError"&&N({title:"Import Failed",message:ae.message||String(ae),danger:!0})}finally{Ee(null),yt(null)}},Tt=()=>{Ke&&Ke.abort()},Z=D=>{const R=D.target.files[0];R&&Ht(R,R.name),D.target.value=""},ie=!!h;return c.jsxs("div",{className:"workspace-area",children:[c.jsxs("div",{className:"workspace-title-section",children:[c.jsx("h2",{className:"workspace-title",children:"Model Manager"}),c.jsx("p",{className:"workspace-subtitle",children:"Manage local files and download recommended weights directly to your local models folder."})]}),f&&c.jsx("div",{className:"m3-card",style:{borderLeft:"4px solid var(--md-sys-color-primary)",background:"var(--md-sys-color-primary-container)",color:"var(--md-sys-color-on-primary-container)"},children:c.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[c.jsxs("div",{children:[c.jsxs("h4",{style:{fontWeight:700},children:["Active Model: ",f]}),c.jsx("p",{style:{fontSize:"0.85rem",marginTop:"2px",opacity:.9},children:"The local C++ stable-diffusion server is running. Real-time telemetry is displaying performance status in the top bar."}),(C.backendMode||C.backendDevice||C.backendBinary)&&c.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:"8px",marginTop:"10px"},children:[C.backendMode&&c.jsxs("span",{className:"status-chip",style:{cursor:"default"},children:[c.jsx(xu,{size:14}),c.jsxs("span",{children:["Loaded on ",C.backendMode]})]}),C.backendDevice&&c.jsx("span",{className:"status-chip",style:{cursor:"default"},children:c.jsx("span",{children:C.backendDevice})}),C.backendBinary&&c.jsx("span",{className:"status-chip",style:{cursor:"default"},children:c.jsx("span",{children:C.backendBinary})})]})]}),c.jsxs("button",{className:"m3-btn m3-btn-error",onClick:Et,disabled:me||L!==null,children:[me?c.jsx(aa,{className:"progress-spinner",size:16}):c.jsx(rn,{size:16}),c.jsx("span",{children:me?"Unloading":"Unload Server"})]})]})}),me&&c.jsxs("div",{className:"m3-card",style:{borderLeft:"4px solid var(--md-sys-color-error)",marginTop:"16px"},children:[c.jsx("h4",{style:{fontWeight:700,marginBottom:"8px"},children:"Unloading Model"}),c.jsxs("div",{className:"model-progress-section",style:{margin:"8px 0 0 0"},children:[c.jsxs("div",{className:"model-progress-label",children:[c.jsx("span",{children:st.phase||"Unloading model..."}),c.jsxs("span",{children:[Math.round(st.progress),"%"]})]}),c.jsx("div",{className:"model- Line truncated
Models and generated outputs are not included.`,confirmLabel:"Clean",danger:!0})){me(!0);try{await Gy(ve.map(D=>D.id)),await ee()}catch(D){N({title:"Cleanup Failed",message:D.message||String(D),danger:!0})}finally{me(!1)}}},[ve,ee,N,x]);O.useEffect(()=>{async function Z(){const ie=await Xy();Xe(ie)}Z()},[]),O.useEffect(()=>{X?localStorage.setItem("active-model",X):localStorage.removeItem("active-model")},[X]),O.useEffect(()=>{let Z=null;async function ie(){const D=await Cl();if(!!D.ready){Z&&clearInterval(Z);const F=localStorage.getItem("active-model");if(F)L(F),W(!0);else{try{if(D&&D.settings&&D.settings.model){const ae=D.settings.model.split(/[\\/]/).pop();L(ae)}}catch{}W(!0)}}else if(!(typeof window<"u"&&window.__TAURI_INTERNALS__!==void 0)){const ae=localStorage.getItem("active-model");ae&&(L(ae),W(!1))}}return ie(),Z=setInterval(ie,2e3),()=>{Z&&clearInterval(Z)}},[]),O.useEffect(()=>{async function Z(){try{const D=await Ly();B(R=>R.cpu_usage===D.cpu_usage&&R.ram_used_gb===D.ram_used_gb&&R.ram_total_gb===D.ram_total_gb&&R.gpu_name===D.gpu_name&&R.vram_used_gb===D.vram_used_gb&&R.vram_total_gb===D.vram_total_gb?R:D)}catch{}}Z();const ie=setInterval(Z,1500);return()=>clearInterval(ie)},[]),O.useEffect(()=>{if(X){const Z=X.toLowerCase();Z.includes("flux")||Z.includes("schnell")?K(ie=>({...ie,steps:4,cfgScale:1,width:1024,height:1024})):Z.includes("lightning")||Z.includes("turbo")?K(ie=>({...ie,steps:4,cfgScale:1.5,width:1024,height:1024})):Z.includes("sd15")?K(ie=>({...ie,steps:25,cfgScale:7,width:512,height:512})):Z.includes("sd35")?K(ie=>({...ie,steps:20,cfgScale:4.5,width:1024,height:1024})):K(ie=>({...ie,steps:20,cfgScale:7,width:512,height:512}))}},[X]);const Ne=O.useMemo(()=>{switch(w){case"generator":return c.jsx(ag,{prompt:A,setPrompt:U,negativePrompt:V,setNegativePrompt:fe,constraints:q,setConstraints:K,activeModel:X,generatedImages:$e,setGeneratedImages:Xe,isGenerating:_,setIsGenerating:C,generationProgress:J,setGenerationProgress:he,setActiveTab:h,showAlert:N,showConfirm:x});case"models":return c.jsx(ug,{activeModel:X,setActiveModel:L,serverRunning:te,setServerRunning:W,constraints:q,showAlert:N,showConfirm:x});case"constraints":return c.jsx(fg,{constraints:q,setConstraints:K,activeModel:X,specs:ce,backendOptions:Ee});default:return null}},[w,A,V,q,X,$e,_,J,te,ce,Ee,N,x]),gt=O.useMemo(()=>c.jsx(Ny,{activeTab:w,setActiveTab:h,specs:ce}),[w,ce]),Et=O.useCallback(async()=>{if(!(!te||Oe)){de(!0);try{await Js(),W(!1),L(null)}catch(Z){console.error("Failed to stop server:",Z),N({title:"Stop Server Failed",message:Z.message||String(Z),danger:!0})}finally{de(!1)}}},[te,Oe,N]),We=[...Ae?.stale?["Restart Local AI Image Generator so the local server loads the latest API."]:[],...Ae?.issues||[]],Ht=ve.reduce((Z,ie)=>Z+Number(ie.sizeBytes||0),0),Tt=!!(Ae&&(We.length>0||ve.length>0));return c.jsxs("div",{className:"app-container",children:[gt,c.jsxs("div",{className:"main-content",children:[c.jsx(Dy,{telemetry:j,serverRunning:te,activeModel:X,onStopServer:Et,isStoppingServer:Oe,theme:G,setTheme:H}),Tt&&c.jsxs("div",{className:`m3-card readiness-card ${Ae?.stale||We.length>0?"readiness-card-warning":""}`,children:[c.jsxs("div",{className:"readiness-header",children:[c.jsxs("div",{children:[c.jsx("h3",{className:"m3-card-title",style:{marginBottom:"4px"},children:Ae?.stale?"Restart Required":We.length>0?"System Readiness":"Safe Cleanup Available"}),c.jsx("p",{className:"m3-card-subtitle",style:{margin:0},children:Ae?.stale?`Running server build: ${Ae.build||"unknown"}`:We.length>0?"Local AI Image Generator found setup items that may need attention.":`${ve.length} temporary item${ve.length===1?"":"s"} can be cleaned (${ii(Ht)}).`})]}),c.jsxs("div",{className:"readiness-actions",children:[c.jsx("button",{className:"m3-btn m3-btn-outlined",onClick:ee,disabled:yt,children:yt?"Checking":"Refresh"}),c.jsx("button",{className:"m3-btn m3-btn-tonal",onClick:se,children:Je?"Copied":"Copy Diagnostics"}),ve.length>0&&c.jsxs("button",{className:"m3-btn m3-btn-error",onClick:Ze,disabled:yt,children:["Clean ",ii(Ht)]})]})]}),We.length>0&&c.jsx("div",{className:"readiness-issues",children:We.slice(0,4).map(Z=>c.jsx("span",{children:Z},Z))}),ve.length>0&&c.jsx("div",{className:"readiness-cleanup-list",children:ve.slice(0,3).map(Z=>c.jsxs("span",{title:Z.path,children:[Z.name," · ",Z.size," · ",Z.reason]},Z.id))})]}),Ne]}),b&&c.jsx("div",{className:"app-dialog-backdrop",role:"presentation",children:c.jsxs("div",{className:"app-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"app-dialog-title",children:[c.jsx("h3",{id:"app-dialog-title",className:"app-dialog-title",children:b.title}),c.jsx("p",{className:"app-dialog-message",children:b.message}),c.jsxs("div",{className:"app-dialog-actions",children:[b.type==="confirm"&&c.jsx("button",{className:"m3-btn m3-btn-outlined",onClick:()=>r(!1),children:b.cancelLabel}),c.jsx("button",{className:`m3-btn ${b.danger?"m3-btn-error":"m3-btn-filled"}`,onClick:()=>r(!0),children: Line truncated
+17 -17
View File
@@ -1,18 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<title>Local AI Image Generator</title>
<script type="module" crossorigin src="/assets/index-jXnaCwmK.js"></script>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<title>Local AI Image Generator</title>
<script type="module" crossorigin src="/assets/index-CG3WZqfX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DW6G7wc2.css">
</head>
<body>
<div id="root"></div>␍
</body>
</html>
</head>
<body>
<div id="root"></div>
</body>
</html>
+112 -112
View File
@@ -802,9 +802,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz",
"integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz",
"integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==",
"cpu": [
"arm"
],
@@ -816,9 +816,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz",
"integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz",
"integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==",
"cpu": [
"arm64"
],
@@ -830,9 +830,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz",
"integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz",
"integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==",
"cpu": [
"arm64"
],
@@ -844,9 +844,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz",
"integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz",
"integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==",
"cpu": [
"x64"
],
@@ -858,9 +858,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz",
"integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz",
"integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==",
"cpu": [
"arm64"
],
@@ -872,9 +872,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz",
"integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz",
"integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==",
"cpu": [
"x64"
],
@@ -886,9 +886,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz",
"integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz",
"integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==",
"cpu": [
"arm"
],
@@ -900,9 +900,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz",
"integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz",
"integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==",
"cpu": [
"arm"
],
@@ -914,9 +914,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz",
"integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz",
"integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==",
"cpu": [
"arm64"
],
@@ -928,9 +928,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz",
"integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz",
"integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==",
"cpu": [
"arm64"
],
@@ -942,9 +942,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz",
"integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz",
"integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==",
"cpu": [
"loong64"
],
@@ -956,9 +956,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz",
"integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz",
"integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==",
"cpu": [
"loong64"
],
@@ -970,9 +970,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz",
"integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz",
"integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==",
"cpu": [
"ppc64"
],
@@ -984,9 +984,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz",
"integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz",
"integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==",
"cpu": [
"ppc64"
],
@@ -998,9 +998,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz",
"integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz",
"integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==",
"cpu": [
"riscv64"
],
@@ -1012,9 +1012,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz",
"integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz",
"integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==",
"cpu": [
"riscv64"
],
@@ -1026,9 +1026,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz",
"integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz",
"integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==",
"cpu": [
"s390x"
],
@@ -1040,9 +1040,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz",
"integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz",
"integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==",
"cpu": [
"x64"
],
@@ -1054,9 +1054,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz",
"integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz",
"integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==",
"cpu": [
"x64"
],
@@ -1068,9 +1068,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz",
"integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz",
"integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==",
"cpu": [
"x64"
],
@@ -1082,9 +1082,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz",
"integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz",
"integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==",
"cpu": [
"arm64"
],
@@ -1096,9 +1096,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz",
"integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz",
"integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==",
"cpu": [
"arm64"
],
@@ -1110,9 +1110,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz",
"integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz",
"integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==",
"cpu": [
"ia32"
],
@@ -1124,9 +1124,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz",
"integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz",
"integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==",
"cpu": [
"x64"
],
@@ -1138,9 +1138,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz",
"integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz",
"integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==",
"cpu": [
"x64"
],
@@ -1461,9 +1461,9 @@
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.33",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
"integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==",
"version": "2.10.34",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz",
"integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -1508,9 +1508,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001793",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
"integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
"version": "1.0.30001797",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz",
"integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==",
"dev": true,
"funding": [
{
@@ -1554,9 +1554,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.366",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.366.tgz",
"integrity": "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg==",
"version": "1.5.369",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.369.tgz",
"integrity": "sha512-XM22K9FNaaCOvMMrBn1caIc8v0g6+pKt660ZbfQqUZvfil0hEzr8ZoiY7VcSLGM3L/x3rz5PqZrk+bKOOmVM9w==",
"dev": true,
"license": "ISC"
},
@@ -1824,9 +1824,9 @@
}
},
"node_modules/rollup": {
"version": "4.61.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz",
"integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==",
"version": "4.61.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz",
"integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1840,31 +1840,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.61.0",
"@rollup/rollup-android-arm64": "4.61.0",
"@rollup/rollup-darwin-arm64": "4.61.0",
"@rollup/rollup-darwin-x64": "4.61.0",
"@rollup/rollup-freebsd-arm64": "4.61.0",
"@rollup/rollup-freebsd-x64": "4.61.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.61.0",
"@rollup/rollup-linux-arm-musleabihf": "4.61.0",
"@rollup/rollup-linux-arm64-gnu": "4.61.0",
"@rollup/rollup-linux-arm64-musl": "4.61.0",
"@rollup/rollup-linux-loong64-gnu": "4.61.0",
"@rollup/rollup-linux-loong64-musl": "4.61.0",
"@rollup/rollup-linux-ppc64-gnu": "4.61.0",
"@rollup/rollup-linux-ppc64-musl": "4.61.0",
"@rollup/rollup-linux-riscv64-gnu": "4.61.0",
"@rollup/rollup-linux-riscv64-musl": "4.61.0",
"@rollup/rollup-linux-s390x-gnu": "4.61.0",
"@rollup/rollup-linux-x64-gnu": "4.61.0",
"@rollup/rollup-linux-x64-musl": "4.61.0",
"@rollup/rollup-openbsd-x64": "4.61.0",
"@rollup/rollup-openharmony-arm64": "4.61.0",
"@rollup/rollup-win32-arm64-msvc": "4.61.0",
"@rollup/rollup-win32-ia32-msvc": "4.61.0",
"@rollup/rollup-win32-x64-gnu": "4.61.0",
"@rollup/rollup-win32-x64-msvc": "4.61.0",
"@rollup/rollup-android-arm-eabi": "4.61.1",
"@rollup/rollup-android-arm64": "4.61.1",
"@rollup/rollup-darwin-arm64": "4.61.1",
"@rollup/rollup-darwin-x64": "4.61.1",
"@rollup/rollup-freebsd-arm64": "4.61.1",
"@rollup/rollup-freebsd-x64": "4.61.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.61.1",
"@rollup/rollup-linux-arm-musleabihf": "4.61.1",
"@rollup/rollup-linux-arm64-gnu": "4.61.1",
"@rollup/rollup-linux-arm64-musl": "4.61.1",
"@rollup/rollup-linux-loong64-gnu": "4.61.1",
"@rollup/rollup-linux-loong64-musl": "4.61.1",
"@rollup/rollup-linux-ppc64-gnu": "4.61.1",
"@rollup/rollup-linux-ppc64-musl": "4.61.1",
"@rollup/rollup-linux-riscv64-gnu": "4.61.1",
"@rollup/rollup-linux-riscv64-musl": "4.61.1",
"@rollup/rollup-linux-s390x-gnu": "4.61.1",
"@rollup/rollup-linux-x64-gnu": "4.61.1",
"@rollup/rollup-linux-x64-musl": "4.61.1",
"@rollup/rollup-openbsd-x64": "4.61.1",
"@rollup/rollup-openharmony-arm64": "4.61.1",
"@rollup/rollup-win32-arm64-msvc": "4.61.1",
"@rollup/rollup-win32-ia32-msvc": "4.61.1",
"@rollup/rollup-win32-x64-gnu": "4.61.1",
"@rollup/rollup-win32-x64-msvc": "4.61.1",
"fsevents": "~2.3.2"
}
},
+7 -172
View File
@@ -339,7 +339,8 @@ export async function listModelsFromDisk() {
}
// Generate image (T2I / I2I)
// Handles API calls to sd-server or mocks them if server is unreachable
// Handles API calls to sd-server. If the server is unreachable or returns an error,
// we surface that error to the UI instead of silently returning a fake placeholder.
export async function generateImage(prompt, negativePrompt, constraints, activeModelName, inputImageBase64, onProgress, signal) {
console.log("Initiating image generation:", { prompt, negativePrompt, constraints, activeModelName });
const startTime = Date.now();
@@ -360,173 +361,6 @@ export async function generateImage(prompt, negativePrompt, constraints, activeM
denoising_strength: constraints.denoisingStrength || 0.7,
};
// Mock generation helper for offline/browser execution
const runMockGeneration = async () => {
// Simulate generation steps progress
const stepsCount = payload.steps;
for (let i = 1; i <= stepsCount; i++) {
if (signal && signal.aborted) {
throw new DOMException("The user aborted a request.", "AbortError");
}
await new Promise(r => setTimeout(r, (3000 / stepsCount))); // 3 seconds total
if (onProgress) {
onProgress(Math.round((i / stepsCount) * 100));
}
}
const pLower = prompt.toLowerCase();
const seedVal = Math.abs(payload.seed) || Math.floor(Math.random() * 1000);
// Generate a beautiful, local vector SVG representing the requested subject, completely offline-compatible
const primaryBg = "#0F172A"; // dark premium background
let gradientStart = "#3B82F6";
let gradientEnd = "#8B5CF6";
let sceneShapes = "";
let keywordLabel = "Abstract Generation";
if (pLower.includes("moon") || pLower.includes("space") || pLower.includes("astronomy") || pLower.includes("sky")) {
gradientStart = "#1E293B";
gradientEnd = "#0F172A";
keywordLabel = "Luminous Moon in Starry Sky";
sceneShapes = `
<!-- Stars -->
<circle cx="80" cy="100" r="1.5" fill="#ffffff" opacity="0.6"/>
<circle cx="160" cy="60" r="1" fill="#ffffff" opacity="0.5"/>
<circle cx="280" cy="120" r="2" fill="#ffffff" opacity="0.8"/>
<circle cx="360" cy="80" r="1.5" fill="#ffffff" opacity="0.4"/>
<circle cx="440" cy="150" r="1" fill="#ffffff" opacity="0.7"/>
<circle cx="400" cy="280" r="2" fill="#ffffff" opacity="0.9"/>
<circle cx="100" cy="300" r="1.5" fill="#ffffff" opacity="0.5"/>
<!-- Moon -->
<circle cx="256" cy="220" r="90" fill="url(#moonGrad)" />
<circle cx="226" cy="190" r="80" fill="#0F172A" />
`;
} else if (pLower.includes("cat") || pLower.includes("kitten") || pLower.includes("feline")) {
gradientStart = "#4F46E5";
gradientEnd = "#7C3AED";
keywordLabel = "Stylized Cat Portrait";
sceneShapes = `
<!-- Cat head & ears -->
<polygon points="176,280 146,160 216,210" fill="#312E81" />
<polygon points="336,280 366,160 296,210" fill="#312E81" />
<circle cx="256" cy="270" r="80" fill="#4338CA" />
<!-- Eyes -->
<ellipse cx="226" cy="260" rx="14" ry="8" fill="#10B981" />
<ellipse cx="286" cy="260" rx="14" ry="8" fill="#10B981" />
<circle cx="226" cy="260" r="4" fill="#000" />
<circle cx="286" cy="260" r="4" fill="#000" />
<!-- Nose & Whiskers -->
<polygon points="256,285 248,275 264,275" fill="#F43F5E" />
<line x1="226" y1="290" x2="166" y2="280" stroke="#E2E8F0" stroke-width="2" />
<line x1="226" y1="295" x2="156" y2="295" stroke="#E2E8F0" stroke-width="2" />
<line x1="286" y1="290" x2="346" y2="280" stroke="#E2E8F0" stroke-width="2" />
<line x1="286" y1="295" x2="356" y2="295" stroke="#E2E8F0" stroke-width="2" />
`;
} else if (pLower.includes("dog") || pLower.includes("puppy") || pLower.includes("canine")) {
gradientStart = "#D97706";
gradientEnd = "#B45309";
keywordLabel = "Friendly Dog Portrait";
sceneShapes = `
<!-- Dog head & floppy ears -->
<ellipse cx="166" cy="260" rx="20" ry="60" fill="#78350F" />
<ellipse cx="346" cy="260" rx="20" ry="60" fill="#78350F" />
<circle cx="256" cy="250" r="70" fill="#92400E" />
<ellipse cx="256" cy="280" rx="40" ry="30" fill="#D97706" />
<!-- Eyes -->
<circle cx="226" cy="230" r="10" fill="#1E293B" />
<circle cx="286" cy="230" r="10" fill="#1E293B" />
<circle cx="222" cy="226" r="3" fill="#ffffff" />
<circle cx="282" cy="226" r="3" fill="#ffffff" />
<!-- Nose -->
<ellipse cx="256" cy="275" rx="16" ry="10" fill="#1E293B" />
`;
} else if (pLower.includes("city") || pLower.includes("cyberpunk") || pLower.includes("tokyo") || pLower.includes("neon")) {
gradientStart = "#111827";
gradientEnd = "#311042";
keywordLabel = "Neon Cyberpunk Skyline";
sceneShapes = `
<!-- Skyline -->
<rect x="60" y="160" width="80" height="352" fill="#1F2937" opacity="0.9"/>
<rect x="180" y="100" width="100" height="412" fill="#111827" />
<rect x="320" y="200" width="120" height="312" fill="#1F2937" opacity="0.85"/>
<rect x="100" y="240" width="110" height="272" fill="#374151" opacity="0.6"/>
<!-- Neon details -->
<line x1="230" y1="100" x2="230" y2="40" stroke="#EC4899" stroke-width="3" />
<rect x="200" y="140" width="60" height="15" fill="#10B981" opacity="0.7"/>
<rect x="200" y="170" width="60" height="15" fill="#10B981" opacity="0.7"/>
<rect x="200" y="200" width="60" height="15" fill="#3B82F6" opacity="0.7"/>
<rect x="70" y="200" width="15" height="100" fill="#F59E0B" opacity="0.5"/>
<rect x="340" y="250" width="20" height="150" fill="#EF4444" opacity="0.6"/>
`;
} else if (pLower.includes("car") || pLower.includes("vehicle") || pLower.includes("supercar")) {
gradientStart = "#DC2626";
gradientEnd = "#991B1B";
keywordLabel = "Futuristic Concept Car";
sceneShapes = `
<!-- Road / Perspective grid -->
<line x1="256" y1="350" x2="0" y2="512" stroke="#4B5563" stroke-width="3" />
<line x1="256" y1="350" x2="512" y2="512" stroke="#4B5563" stroke-width="3" />
<!-- Car body -->
<polygon points="126,380 256,330 386,380 346,440 166,440" fill="#EF4444" />
<polygon points="176,370 256,340 336,370 326,390 186,390" fill="#1E293B" opacity="0.9" />
<!-- Headlights -->
<polygon points="136,410 176,415 166,425 136,420" fill="#FBBF24" />
<polygon points="376,410 336,415 346,425 376,420" fill="#FBBF24" />
<!-- Wheels -->
<ellipse cx="156" cy="440" rx="30" ry="15" fill="#111" />
<ellipse cx="356" cy="440" rx="30" ry="15" fill="#111" />
`;
} else {
// General gradient waves
gradientStart = "#06B6D4";
gradientEnd = "#0891B2";
keywordLabel = "Abstract Creative Concept";
sceneShapes = `
<path d="M0,256 C150,150 350,350 512,256 L512,512 L0,512 Z" fill="#0891B2" opacity="0.4" />
<path d="M0,350 C200,200 300,450 512,350 L512,512 L0,512 Z" fill="#0E7490" opacity="0.6" />
<circle cx="380" cy="180" r="50" fill="#F59E0B" opacity="0.8" />
`;
}
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="${gradientStart}" />
<stop offset="100%" stop-color="${gradientEnd}" />
</linearGradient>
<linearGradient id="moonGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FEF08A" />
<stop offset="100%" stop-color="#FDE047" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="512" height="512" fill="url(#bgGrad)" />
<!-- Shapes -->
${sceneShapes}
<!-- Premium UI Overlay Frame -->
<rect x="20" y="20" width="472" height="472" fill="none" stroke="#ffffff" stroke-width="1.5" opacity="0.15" rx="8"/>
<!-- Prompt & Details text -->
<rect x="40" y="410" width="432" height="62" fill="#0F172A" opacity="0.85" rx="6" />
<text x="60" y="435" fill="#F1F5F9" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="bold">${keywordLabel}</text>
<text x="60" y="455" fill="#94A3B8" font-family="system-ui, -apple-system, sans-serif" font-size="11">Seed: ${seedVal} • Steps: ${payload.steps} • Scale: ${payload.cfg_scale}</text>
</svg>
`;
const base64Svg = btoa(unescape(encodeURIComponent(svgString)));
const mockImageUrl = `data:image/svg+xml;base64,${base64Svg}`;
const durationSec = parseFloat(((Date.now() - startTime) / 1000).toFixed(1));
return {
image: mockImageUrl,
seed: payload.seed,
duration_sec: durationSec,
};
};
// txt2img uses /v1/images/generations; img2img uses /sdapi/v1/img2img.
const isImg2Img = !!payload.image;
@@ -601,11 +435,12 @@ export async function generateImage(prompt, negativePrompt, constraints, activeM
}
} catch (err) {
if (err.name === "AbortError" || err.message.startsWith("Generation failed")) throw err;
console.warn("Could not reach local server. Falling back to simulation mode.", err);
console.warn("Could not reach local server.", err);
throw new Error(
"The image generation server is not responding or crashed. " +
"Try restarting the backend from Model Manager, or check the terminal for a backend error."
);
}
// Fallback to mock generation if server is offline/not reachable
return await runMockGeneration();
}
// Perform model file import (copy to USB in Tauri, simulated in Web mode)
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Compile backends from source
# For systems with GLIBC < 2.38 (e.g. Ubuntu 22.04)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/app"
BACKEND_DIR="$APP_DIR/backend/linux"
BUILD_DIR="$APP_DIR/tools/build-sd"
# Pinned release tag
PINNED_TAG="master-685-19bdfe2"
echo "=== Local AI Image Generator - Build from Source ==="
echo "Target directories:"
echo " CPU Backend -> $BACKEND_DIR/cpu"
echo " Vulkan Backend -> $BACKEND_DIR/vulkan"
echo ""
# 1. Clone stable-diffusion.cpp
if [ ! -d "$BUILD_DIR" ]; then
echo "Cloning stable-diffusion.cpp..."
git clone https://github.com/leejet/stable-diffusion.cpp.git "$BUILD_DIR"
else
echo "Using existing stable-diffusion.cpp directory."
fi
cd "$BUILD_DIR"
echo "Checking out pinned tag $PINNED_TAG..."
git fetch origin
git checkout -f "$PINNED_TAG"
git submodule update --init --recursive
# 2. Build CPU Backend
echo ""
echo "=== Building CPU Backend ==="
rm -rf build-cpu && mkdir build-cpu && cd build-cpu
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j$(nproc)
echo "Copying CPU binaries..."
mkdir -p "$BACKEND_DIR/cpu"
if [ -f bin/sd-server ]; then
cp bin/sd-server "$BACKEND_DIR/cpu/sd-cpu"
cp bin/sd-server "$BACKEND_DIR/cpu/sd-server-cpu"
else
echo "ERROR: bin/sd-server was not found!"
exit 1
fi
if [ -f bin/sd ]; then
cp bin/sd "$BACKEND_DIR/cpu/sd-cli-cpu"
elif [ -f bin/sd-cli ]; then
cp bin/sd-cli "$BACKEND_DIR/cpu/sd-cli-cpu"
fi
# Find and copy libstable-diffusion.so (might be in bin/ or in build root)
SO_PATH=$(find . -name "libstable-diffusion.so" | head -n 1)
if [ -n "$SO_PATH" ]; then
cp "$SO_PATH" "$BACKEND_DIR/cpu/"
echo "Copied libstable-diffusion.so to CPU directory."
else
echo "WARNING: libstable-diffusion.so not found for CPU build!"
fi
# 3. Build Vulkan Backend
cd "$BUILD_DIR"
echo ""
echo "=== Building Vulkan Backend ==="
if rm -rf build-vulkan && mkdir build-vulkan && cd build-vulkan && \
cmake .. -DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release && \
cmake --build . --config Release -j$(nproc); then
echo "Copying Vulkan binaries..."
mkdir -p "$BACKEND_DIR/vulkan"
if [ -f bin/sd-server ]; then
cp bin/sd-server "$BACKEND_DIR/vulkan/sd-vulkan"
cp bin/sd-server "$BACKEND_DIR/vulkan/sd-server-vulkan"
else
echo "ERROR: bin/sd-server was not found!"
exit 1
fi
if [ -f bin/sd ]; then
cp bin/sd "$BACKEND_DIR/vulkan/sd-cli-vulkan"
elif [ -f bin/sd-cli ]; then
cp bin/sd-cli "$BACKEND_DIR/vulkan/sd-cli-vulkan"
fi
SO_PATH_VK=$(find . -name "libstable-diffusion.so" | head -n 1)
if [ -n "$SO_PATH_VK" ]; then
cp "$SO_PATH_VK" "$BACKEND_DIR/vulkan/"
echo "Copied libstable-diffusion.so to Vulkan directory."
else
echo "WARNING: libstable-diffusion.so not found for Vulkan build!"
fi
else
echo "WARNING: Vulkan backend build failed. Bypassing Vulkan backend compilation..."
fi
cd "$BUILD_DIR"
echo ""
echo "=== Building CUDA Backend ==="
rm -rf build-cuda && mkdir build-cuda && cd build-cuda
cmake .. -DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j$(nproc)
echo "Copying CUDA binaries..."
mkdir -p "$BACKEND_DIR/cuda"
if [ -f bin/sd-server ]; then
cp bin/sd-server "$BACKEND_DIR/cuda/sd-cuda"
cp bin/sd-server "$BACKEND_DIR/cuda/sd-server-cuda"
else
echo "ERROR: bin/sd-server was not found!"
exit 1
fi
if [ -f bin/sd ]; then
cp bin/sd "$BACKEND_DIR/cuda/sd-cli-cuda"
elif [ -f bin/sd-cli ]; then
cp bin/sd-cli "$BACKEND_DIR/cuda/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" "$BACKEND_DIR/cuda/"
echo "Copied libstable-diffusion.so to CUDA directory."
else
echo "WARNING: libstable-diffusion.so not found for CUDA build!"
fi
echo ""
echo "=== Build from source completed successfully! ==="
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Linux Reset Script
# Resets portable app dependencies/builds while preserving user models and outputs.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/app"
echo ""
echo " ============================================================"
echo " Resetting Local-AI-Image-Generator..."
echo " ============================================================"
echo ""
# Delete tools/node
if [[ -d "$APP_DIR/tools" ]]; then
echo " >> Removing portable tools/ node folder..."
rm -rf "$APP_DIR/tools"
fi
# Delete backend
if [[ -d "$APP_DIR/backend" ]]; then
echo " >> Removing backend binaries..."
rm -rf "$APP_DIR/backend"
fi
# Delete dist
if [[ -d "$APP_DIR/dist" ]]; then
echo " >> Removing dist/ build folder..."
rm -rf "$APP_DIR/dist"
fi
# Preserve models
if [[ -d "$APP_DIR/models" ]]; then
echo " >> Preserving downloaded models in app/models."
fi
# Delete node_modules in frontend
if [[ -d "$APP_DIR/frontend/node_modules" ]]; then
echo " >> Removing frontend node_modules..."
rm -rf "$APP_DIR/frontend/node_modules"
fi
# Delete package-lock.json in frontend
if [[ -f "$APP_DIR/frontend/package-lock.json" ]]; then
echo " >> Removing frontend package-lock.json..."
rm -f "$APP_DIR/frontend/package-lock.json"
fi
echo ""
echo " ============================================================"
echo " Reset complete. Models and generated outputs were preserved."
echo " ============================================================"
echo ""
read -rp " Press Enter to close..."
+163 -21
View File
@@ -28,7 +28,10 @@ const BACKEND_PATHS = {
cuda: path.join(ROOT, "app", "backend", "win", "cuda", "sd-cuda.exe"),
vulkan: path.join(ROOT, "app", "backend", "win", "vulkan", "sd-vulkan.exe"),
mac: path.join(ROOT, "app", "backend", "mac", "sd"),
linux: path.join(ROOT, "app", "backend", "linux", "sd-vulkan"),
linuxCpu: path.join(ROOT, "app", "backend", "linux", "cpu", "sd-cpu"),
linuxVulkan: path.join(ROOT, "app", "backend", "linux", "vulkan", "sd-vulkan"),
linuxRocm: path.join(ROOT, "app", "backend", "linux", "rocm", "sd-rocm"),
linuxCuda: path.join(ROOT, "app", "backend", "linux", "cuda", "sd-cuda"),
};
let BACKEND_PATH = "";
if (osPlatform === "win32") {
@@ -51,7 +54,14 @@ if (osPlatform === "win32") {
} else if (osPlatform === "darwin") {
BACKEND_PATH = BACKEND_PATHS.mac;
} else {
BACKEND_PATH = BACKEND_PATHS.linux;
// Linux: default to Vulkan if available, otherwise CPU
if (fs.existsSync(BACKEND_PATHS.linuxVulkan)) {
BACKEND_PATH = BACKEND_PATHS.linuxVulkan;
} else if (fs.existsSync(BACKEND_PATHS.linuxCpu)) {
BACKEND_PATH = BACKEND_PATHS.linuxCpu;
} else {
BACKEND_PATH = BACKEND_PATHS.linuxVulkan;
}
}
const MODELS = path.join(ROOT, "app", "models");
if (!fs.existsSync(MODELS)) {
@@ -128,6 +138,32 @@ function getCpuUsagePercent() {
return Math.max(0, Math.min(100, Math.round((1 - idleDelta / totalDelta) * 1000) / 10));
}
function detectLinuxGpuFromSysfs() {
try {
const pciDir = "/sys/bus/pci/devices";
if (!fs.existsSync(pciDir)) return null;
const entries = fs.readdirSync(pciDir);
for (const entry of entries) {
const vendorFile = path.join(pciDir, entry, "vendor");
const deviceFile = path.join(pciDir, entry, "device");
if (!fs.existsSync(vendorFile)) continue;
const vendor = fs.readFileSync(vendorFile, "utf8").trim();
let device = "";
try { device = fs.readFileSync(deviceFile, "utf8").trim(); } catch (_) {}
if (vendor === "0x10de") {
return { name: `NVIDIA GPU (${device || entry})` };
}
if (vendor === "0x1002") {
return { name: `AMD GPU (${device || entry})` };
}
if (vendor === "0x8086") {
return { name: `Intel GPU (${device || entry})` };
}
}
} catch (_) {}
return null;
}
function getGpuInfo() {
if (cachedGpuInfo) return cachedGpuInfo;
@@ -144,6 +180,14 @@ function getGpuInfo() {
} catch (_) {}
}
if (osPlatform === "linux") {
const linuxGpu = detectLinuxGpuFromSysfs();
if (linuxGpu) {
cachedGpuInfo = linuxGpu;
return cachedGpuInfo;
}
}
cachedGpuInfo = { name: "Unavailable" };
return cachedGpuInfo;
}
@@ -358,7 +402,9 @@ function getSetupPaths() {
node: path.join(appDir, "tools", "node-linux", "bin", "node"),
npm: path.join(appDir, "tools", "node-linux", "bin", "npm"),
distIndex: path.join(DIST, "index.html"),
linuxBackend: BACKEND_PATHS.linux,
linuxCpuBackend: BACKEND_PATHS.linuxCpu,
linuxVulkanBackend: BACKEND_PATHS.linuxVulkan,
linuxRocmBackend: BACKEND_PATHS.linuxRocm,
models: MODELS,
outputs: OUTPUTS,
};
@@ -381,7 +427,9 @@ async function getHealth() {
} else if (osPlatform === "darwin") {
checks.push(getPathInfo("Mac backend", paths.macBackend));
} else {
checks.push(getPathInfo("Linux backend", paths.linuxBackend));
checks.push(getPathInfo("Linux CPU backend", paths.linuxCpuBackend));
checks.push(getPathInfo("Linux Vulkan backend", paths.linuxVulkanBackend));
checks.push(getPathInfo("Linux ROCm backend", paths.linuxRocmBackend));
}
let backendInstalled = false;
@@ -391,11 +439,15 @@ async function getHealth() {
} else if (osPlatform === "darwin") {
backendInstalled = checks.find((check) => check.label === "Mac backend")?.exists;
} else {
backendInstalled = checks.find((check) => check.label === "Linux backend")?.exists;
backendInstalled =
checks.find((check) => check.label === "Linux CPU backend")?.exists ||
checks.find((check) => check.label === "Linux CPU backend")?.exists ||
checks.find((check) => check.label === "Linux Vulkan backend")?.exists ||
checks.find((check) => check.label === "Linux ROCm backend")?.exists;
}
const criticalOk = checks
.filter((check) => !["CUDA backend", "Vulkan backend", "Linux backend", "Mac backend"].includes(check.label))
.filter((check) => !["CUDA backend", "Vulkan backend", "Linux CPU backend", "Linux Vulkan backend", "Linux ROCm backend", "Mac backend"].includes(check.label))
.every((check) => check.ok) && backendInstalled;
const ports = {
@@ -408,7 +460,7 @@ async function getHealth() {
ports.backend.ok = true;
const issues = checks
.filter((check) => !check.ok && !["CUDA backend", "Vulkan backend", "Linux backend", "Mac backend"].includes(check.label))
.filter((check) => !check.ok && !["CUDA backend", "Vulkan backend", "Linux CPU backend", "Linux Vulkan backend", "Linux ROCm backend", "Mac backend"].includes(check.label))
.map((check) => `${check.label} is missing or not writable.`);
if (!backendInstalled) {
issues.push(`No ${osPlatform === "win32" ? "Windows" : osPlatform === "darwin" ? "macOS" : "Linux"} backend binary is installed.`);
@@ -513,20 +565,35 @@ function hasNvidiaGpu() {
}
}
function hasAmdGpu() {
const info = getGpuInfo().name.toLowerCase();
return info.includes("amd") || info.includes("advanced micro devices") || info.includes("radeon");
}
function getBackendOptions() {
if (cachedBackendOptions) return cachedBackendOptions;
const cudaAvailable = osPlatform === "win32" && hasNvidiaGpu() && backendAccepts(BACKEND_PATHS.cuda, "cuda");
const cudaInstalled = osPlatform === "win32" && fs.existsSync(BACKEND_PATHS.cuda);
const cudaInstalled = (osPlatform === "win32" && fs.existsSync(BACKEND_PATHS.cuda)) ||
(osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxCuda));
const cudaAvailable = cudaInstalled && hasNvidiaGpu() && backendAccepts(
osPlatform === "win32" ? BACKEND_PATHS.cuda : BACKEND_PATHS.linuxCuda,
"cuda"
);
const vulkanInstalled = (osPlatform === "win32" && fs.existsSync(BACKEND_PATHS.vulkan)) ||
(osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linux));
(osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxVulkan));
const vulkanAvailable = vulkanInstalled && backendAccepts(
osPlatform === "win32" ? BACKEND_PATHS.vulkan : BACKEND_PATHS.linux,
osPlatform === "win32" ? BACKEND_PATHS.vulkan : BACKEND_PATHS.linuxVulkan,
"vulkan"
);
const rocmInstalled = osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxRocm);
const rocmAvailable = rocmInstalled && hasAmdGpu() && backendAccepts(BACKEND_PATHS.linuxRocm, "rocm");
const cpuInstalled = osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxCpu);
const options = [{ id: "cpu", label: "CPU", available: true }];
if (vulkanAvailable) options.push({ id: "vulkan", label: "Vulkan GPU", available: true });
if (rocmAvailable) options.push({ id: "rocm", label: "ROCm GPU (AMD)", available: true });
if (cudaAvailable) options.push({ id: "cuda", label: "CUDA GPU", available: true });
const unavailable = [];
if (vulkanInstalled && !vulkanAvailable) {
unavailable.push({ id: "vulkan", label: "Vulkan GPU", reason: "Installed, but this binary did not register a Vulkan backend on this machine." });
@@ -534,6 +601,10 @@ function getBackendOptions() {
if (cudaInstalled && !cudaAvailable) {
unavailable.push({ id: "cuda", label: "CUDA GPU", reason: "Installed, but CUDA backend validation failed." });
}
if (rocmInstalled && !rocmAvailable) {
unavailable.push({ id: "rocm", label: "ROCm GPU (AMD)", reason: "Installed, but ROCm backend validation failed." });
}
let defaultBackend = "cpu";
if (cudaAvailable) {
const gpuName = String(getGpuInfo().name).toLowerCase();
@@ -543,6 +614,8 @@ function getBackendOptions() {
} else {
defaultBackend = "cuda";
}
} else if (rocmAvailable) {
defaultBackend = "rocm";
} else if (vulkanAvailable) {
defaultBackend = "vulkan";
}
@@ -552,6 +625,7 @@ function getBackendOptions() {
unavailable,
cudaAvailable,
vulkanAvailable,
rocmAvailable,
defaultBackendType: defaultBackend,
};
return cachedBackendOptions;
@@ -560,17 +634,36 @@ function getBackendOptions() {
function backendAccepts(binaryPath, backendName) {
if (!binaryPath || !fs.existsSync(binaryPath)) return false;
try {
const result = spawnSync(binaryPath, [
const probeArgs = [
"--backend", backendName,
"--params-backend", backendName,
"--model", path.join(MODELS, "__backend_probe_missing__.safetensors"),
"--listen-port", "18082",
], { encoding: "utf8", timeout: 5000 });
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
if (output.includes("backend config failed") || output.includes(`backend '${backendName}' was not found`)) {
];
let result = spawnSync(binaryPath, probeArgs, { encoding: "utf8", timeout: 5000 });
let output = `${result.stdout || ""}\n${result.stderr || ""}`;
// Some binaries do not support --backend. If we see "unknown argument",
// retry without backend flags so we can still verify the binary launches.
if (output.includes("unknown argument") && output.includes("--backend")) {
const fallbackArgs = [
"--model", path.join(MODELS, "__backend_probe_missing__.safetensors"),
"--listen-port", "18082",
];
result = spawnSync(binaryPath, fallbackArgs, { encoding: "utf8", timeout: 5000 });
output = `${result.stdout || ""}\n${result.stderr || ""}`;
}
const lower = output.toLowerCase();
if (lower.includes("backend config failed") || output.includes(`backend '${backendName}' was not found`)) {
return false;
}
return output.includes("file") || output.includes("not found") || output.includes("loading model");
// Reject binaries that fail at the dynamic linker / glibc level.
if (lower.includes("glibc") || lower.includes("libc.so") || lower.includes("libstdc++") || lower.includes("cannot open shared object")) {
return false;
}
// A healthy binary prints project-specific log lines when it tries to load the model.
return lower.includes("stable-diffusion.cpp") || lower.includes("loading model");
} catch (_) {
return false;
}
@@ -578,12 +671,28 @@ function backendAccepts(binaryPath, backendName) {
function selectBackendPath(useGpu, backendType = "auto") {
const resolvedType = resolveBackendType(useGpu, backendType);
if (osPlatform === "win32" && resolvedType === "cuda" && fs.existsSync(BACKEND_PATHS.cuda)) {
return BACKEND_PATHS.cuda;
if (osPlatform === "win32") {
if (resolvedType === "cuda" && fs.existsSync(BACKEND_PATHS.cuda)) return BACKEND_PATHS.cuda;
if (fs.existsSync(BACKEND_PATHS.vulkan)) return BACKEND_PATHS.vulkan;
return BACKEND_PATH;
}
if (osPlatform === "win32" && fs.existsSync(BACKEND_PATHS.vulkan)) {
return BACKEND_PATHS.vulkan;
if (osPlatform === "linux") {
let finalType = resolvedType;
if (finalType === "cuda") {
if (fs.existsSync(BACKEND_PATHS.linuxCuda)) {
return BACKEND_PATHS.linuxCuda;
}
finalType = "vulkan";
}
if (finalType === "rocm" && fs.existsSync(BACKEND_PATHS.linuxRocm)) return BACKEND_PATHS.linuxRocm;
if (resolvedType === "vulkan" && fs.existsSync(BACKEND_PATHS.linuxVulkan)) return BACKEND_PATHS.linuxVulkan;
if (resolvedType === "cpu" && fs.existsSync(BACKEND_PATHS.linuxCpu)) return BACKEND_PATHS.linuxCpu;
if (fs.existsSync(BACKEND_PATHS.linuxVulkan)) return BACKEND_PATHS.linuxVulkan;
if (fs.existsSync(BACKEND_PATHS.linuxCpu)) return BACKEND_PATHS.linuxCpu;
return BACKEND_PATH;
}
// macOS fallback
if (fs.existsSync(BACKEND_PATHS.mac)) return BACKEND_PATHS.mac;
return BACKEND_PATH;
}
@@ -598,6 +707,7 @@ function getBackendMode(backendPath, useGpu, backendType = "auto") {
if (useGpu === false || backendType === "cpu") return "CPU";
const name = path.basename(backendPath || "").toLowerCase();
if (name.includes("cuda")) return "CUDA GPU";
if (name.includes("rocm")) return "ROCm GPU";
if (name.includes("vulkan")) return "Vulkan GPU";
return "GPU";
}
@@ -758,12 +868,20 @@ async function startBackend(settings = {}) {
"--sampler-rng", "cpu",
);
} else if (requestedBackend === "cuda") {
// CUDA is only supported on Windows. Linux has no reliable prebuilt CUDA binary.
args.push(
"--backend", "cuda0",
"--params-backend", "cuda0",
"--rng", "cuda",
"--sampler-rng", "cuda",
);
} else if (requestedBackend === "rocm") {
args.push(
"--backend", "rocm0",
"--params-backend", "rocm0",
"--rng", "cpu",
"--sampler-rng", "cpu",
);
}
if (currentSettings.vaeTiling) {
@@ -773,10 +891,30 @@ async function startBackend(settings = {}) {
args.push("--vae-on-cpu");
}
// Build environment for Linux backends so bundled .so libraries are found.
// Official Linux releases ship libstable-diffusion.so next to the executable.
const spawnEnv = { ...process.env };
if (osPlatform === "linux") {
const extraLibs = [];
if (requestedBackend === "rocm") {
extraLibs.push(path.dirname(BACKEND_PATHS.linuxRocm));
} else if (requestedBackend === "vulkan") {
extraLibs.push(path.dirname(BACKEND_PATHS.linuxVulkan));
} else if (requestedBackend === "cpu") {
extraLibs.push(path.dirname(BACKEND_PATHS.linuxCpu));
} else if (requestedBackend === "cuda") {
extraLibs.push(path.dirname(BACKEND_PATHS.linuxCuda));
}
if (extraLibs.length > 0) {
const existing = spawnEnv.LD_LIBRARY_PATH || "";
spawnEnv.LD_LIBRARY_PATH = extraLibs.join(":") + (existing ? ":" + existing : "");
}
}
console.log(" [backend] Starting:", path.basename(backendPath), args.join(" "));
backendReady = false;
backendProc = spawn(backendPath, args, { stdio: "pipe" });
backendProc = spawn(backendPath, args, { stdio: "pipe", env: spawnEnv });
startBackendReadyPoll();
backendProc.stdout.on("data", d => {
@@ -873,6 +1011,10 @@ async function startBackend(settings = {}) {
backendLoadState.backendMode = "Vulkan GPU";
currentSettings.backendMode = "Vulkan GPU";
}
if (cleanOutput.includes("ggml_hip") || cleanOutput.includes("ggml_rocm")) {
backendLoadState.backendMode = "ROCm GPU";
currentSettings.backendMode = "ROCm GPU";
}
if (cleanOutput.includes("[ERROR]")) {
const nextError = describeBackendError(cleanOutput.trim(), currentSettings.model);
const hasSpecificFileError = String(backendError || "").includes("incomplete or corrupted");
+327
View File
@@ -0,0 +1,327 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Linux 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 "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/app"
FRONTEND_DIR="$APP_DIR/frontend"
TOOLS_DIR="$APP_DIR/tools"
NODE_DIR="$TOOLS_DIR/node-linux"
NODE_BIN="$NODE_DIR/bin/node"
NPM_BIN="$NODE_DIR/bin/npm"
DIST_DIR="$APP_DIR/dist"
BACKEND_DIR="$APP_DIR/backend/linux"
# 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"
NODE_TARBALL="node-v${NODE_VERSION}-linux-x64.tar.xz"
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() {
clear 2>/dev/null || true
echo ""
echo " ============================================================"
echo " LOCAL AI IMAGE GENERATOR - Linux 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"
tar -xf "$tar_path" -C "$dest"
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
}
# ════════════════════════════════════════════════════════════════════════════
print_header
check_glibc
TOTAL_STEPS=4
# ── Step 1: Portable Node.js ────────────────────────────────────────────────
print_step 1 $TOTAL_STEPS "Setting up portable Node.js (app/tools/node-linux/)"
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*-linux-x64' | head -n 1)"
if [[ -d "$EXTRACTED_DIR" ]]; then
rm -rf "$NODE_DIR"
mv "$EXTRACTED_DIR" "$NODE_DIR"
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 ───────────────────────────────────
VENDOR="$(detect_gpu_vendor)"
print_step 2 $TOTAL_STEPS "Detecting GPU vendor: ${VENDOR:-none}"
mkdir -p "$BACKEND_DIR"
# CPU backend (always)
CPU_BACKEND_DIR="$BACKEND_DIR/cpu"
if [[ ! -f "$CPU_BACKEND_DIR/sd-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"
chmod +x "$CPU_BACKEND_DIR/sd-cpu" 2>/dev/null || true
print_ok "CPU backend installed."
else
print_ok "CPU backend already ready."
fi
# Vulkan backend (always - cross-vendor GPU fallback)
VULKAN_BACKEND_DIR="$BACKEND_DIR/vulkan"
if [[ ! -f "$VULKAN_BACKEND_DIR/sd-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"
chmod +x "$VULKAN_BACKEND_DIR/sd-vulkan" 2>/dev/null || true
print_ok "Vulkan backend installed."
else
print_ok "Vulkan backend already ready."
fi
# 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" ]]; 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"
chmod +x "$ROCM_BACKEND_DIR/sd-rocm" 2>/dev/null || true
print_ok "ROCm backend installed."
else
print_ok "ROCm backend already ready."
fi
fi
# CUDA backend on Linux is intentionally disabled.
# upstream leejet/stable-diffusion.cpp does not publish official Linux CUDA binaries
# (see https://github.com/leejet/stable-diffusion.cpp/issues/1291), and the only
# third-party build we evaluated (leaxer-ai/leaxer-stable-diffusion v0.1.0) loads
# models but segfaults during generation. Until a reliable Linux CUDA binary is
# available, Linux NVIDIA systems fall back to the Vulkan backend, which uses the
# same GPU via the vendor's Vulkan driver.
if [[ $MAX_PERF -eq 1 ]] && [[ "$VENDOR" == "nvidia" ]]; then
print_info "Linux CUDA backend is disabled pending a reliable upstream binary."
print_info "NVIDIA GPUs on Linux will use the Vulkan backend instead."
fi
# ── Step 3: npm install ─────────────────────────────────────────────────────
print_step 3 $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
cd "$FRONTEND_DIR"
export PATH="$NODE_DIR/bin:$PATH"
if "$NPM_BIN" install --prefer-offline; then
print_ok "Dependencies installed!"
else
print_fail "npm install failed."
exit 1
fi
# ── Step 4: Build frontend ──────────────────────────────────────────────────
print_step 4 $TOTAL_STEPS "Building frontend -> app/dist/"
if "$NPM_BIN" run build; then
print_ok "Frontend built!"
else
print_fail "Frontend build failed."
exit 1
fi
# ── Done ────────────────────────────────────────────────────────────────────
echo ""
echo " ============================================================"
echo " Setup complete! Run ./start.sh to launch."
echo " ============================================================"
echo ""
if [[ $MAX_PERF -eq 0 ]] && [[ "$VENDOR" == "nvidia" || "$VENDOR" == "amd" ]]; then
echo " Tip: For maximum GPU performance, re-run with:"
echo " ./scripts/setup.sh --max-perf"
echo ""
fi
Executable
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Linux Launcher
# Double-click or run: ./start.sh
# Use --max-perf to enable ROCm/CUDA backend downloads on first setup.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="$SCRIPT_DIR/app"
NODE_DIR="$APP_DIR/tools/node-linux"
NODE_BIN="$NODE_DIR/bin/node"
DIST_INDEX="$APP_DIR/dist/index.html"
SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup.sh"
SERVE_SCRIPT="$SCRIPT_DIR/scripts/serve.cjs"
FRONTEND_PORT="${FRONTEND_PORT:-1420}"
SETUP_REASON=""
SETUP_MODE="Repair"
MAX_PERF_FLAG=""
# Parse args
if [[ "${1:-}" == "--max-perf" ]]; then
MAX_PERF_FLAG="--max-perf"
fi
# ── First-time setup check ─────────────────────────────────────────────────
if [[ ! -d "$NODE_DIR" ]]; then
SETUP_MODE="First-Time Setup"
fi
if [[ ! -x "$NODE_BIN" ]]; then
SETUP_REASON="Portable Node.js is missing."
fi
if [[ ! -f "$DIST_INDEX" ]]; then
SETUP_REASON="Frontend build is missing."
fi
# At minimum we need CPU or Vulkan backend on Linux
if [[ ! -f "$APP_DIR/backend/linux/cpu/sd-cpu" && ! -f "$APP_DIR/backend/linux/vulkan/sd-vulkan" ]]; then
SETUP_REASON="No Linux backend binary is installed."
fi
if [[ -n "$SETUP_REASON" ]]; then
echo ""
echo " ============================================================"
echo " LOCAL AI IMAGE GENERATOR | $SETUP_MODE"
echo " ============================================================"
echo ""
if [[ "$SETUP_MODE" == "First-Time Setup" ]]; then
echo " This looks like your first run. Setting up automatically..."
else
echo " Local AI Image Generator needs a quick repair before launch."
fi
echo " Reason: $SETUP_REASON"
echo " Models are not downloaded during setup. Download or import them in the app."
echo ""
read -rp " Press Enter to continue, or Ctrl+C to cancel."
# Clear any existing frontend server process
if command -v fuser >/dev/null 2>&1; then
fuser -k "${FRONTEND_PORT}/tcp" >/dev/null 2>&1 || true
fi
if ! bash "$SETUP_SCRIPT" $MAX_PERF_FLAG; then
echo ""
echo " [ERROR] Setup failed. Please check the output above."
read -rp " Press Enter to close..."
exit 1
fi
fi
# ── Launch ─────────────────────────────────────────────────────────────────
clear 2>/dev/null || true
echo ""
echo " ============================================================"
echo " LOCAL AI IMAGE GENERATOR | Launching..."
echo " ============================================================"
echo ""
# Clear frontend port
if command -v fuser >/dev/null 2>&1; then
fuser -k "${FRONTEND_PORT}/tcp" >/dev/null 2>&1 || true
fi
# Start the server
echo " Starting Local AI Image Generator..."
export PATH="$NODE_DIR/bin:$PATH"
export FRONTEND_PORT="$FRONTEND_PORT"
# Run server in background and capture PID
"$NODE_BIN" "$SERVE_SCRIPT" &
SERVER_PID=$!
# Wait for server to be ready
sleep 2
# Open browser
if command -v xdg-open >/dev/null 2>&1; then
echo " Opening browser at http://localhost:${FRONTEND_PORT}"
xdg-open "http://localhost:${FRONTEND_PORT}" >/dev/null 2>&1 &
else
echo " Open your browser to: http://localhost:${FRONTEND_PORT}"
fi
echo ""
echo " ============================================================"
echo " Running!"
echo " Web UI: http://localhost:${FRONTEND_PORT}"
echo " GPU API: Auto-selected by the app (starts at 8080)"
echo ""
echo " Press Ctrl+C in this window to stop all services."
echo " ============================================================"
echo ""
# Cleanup on exit
cleanup() {
echo ""
echo " Shutting down..."
if kill -0 "$SERVER_PID" >/dev/null 2>&1; then
kill -TERM "$SERVER_PID" >/dev/null 2>&1 || true
sleep 1
kill -KILL "$SERVER_PID" >/dev/null 2>&1 || true
fi
echo " Done. Goodbye!"
exit 0
}
trap cleanup SIGINT SIGTERM
# Keep script alive
wait "$SERVER_PID" || true
cleanup