From 4a840679d64e24d10f78a068756c7eb482bf3dd7 Mon Sep 17 00:00:00 2001 From: techjarves Date: Tue, 23 Jun 2026 05:48:26 +0530 Subject: [PATCH] implement modular frontend components, backend services, and multi-platform configuration scripts for the local AI image generator --- app/frontend/package-lock.json | 2028 ------------------ app/frontend/src/App.jsx | 1 + app/frontend/src/components/Generator.jsx | 7 +- app/frontend/src/components/ModelManager.jsx | 47 +- app/frontend/src/components/Settings.jsx | 186 +- app/frontend/src/services/api.js | 18 +- scripts/reset/reset.ps1 | 5 +- scripts/reset/reset.sh | 4 +- scripts/server/serve.cjs | 423 +++- scripts/setup/setup-coreml-npu.sh | 47 +- scripts/setup/setup-openvino-npu.ps1 | 110 +- scripts/setup/setup-tts.ps1 | 2 +- scripts/setup/setup.ps1 | 75 +- scripts/workers/coreml_server.py | 250 +++ 14 files changed, 1114 insertions(+), 2089 deletions(-) delete mode 100644 app/frontend/package-lock.json create mode 100644 scripts/workers/coreml_server.py diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json deleted file mode 100644 index 09d23b4..0000000 --- a/app/frontend/package-lock.json +++ /dev/null @@ -1,2028 +0,0 @@ -{ - "name": "tauri-app", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "tauri-app", - "version": "0.1.0", - "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-opener": "^2", - "lucide-react": "^1.17.0", - "react": "^19.1.0", - "react-dom": "^19.1.0" - }, - "devDependencies": { - "@tauri-apps/cli": "^2", - "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.4" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", - "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", - "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", - "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", - "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", - "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", - "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", - "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", - "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", - "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", - "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", - "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", - "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", - "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", - "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", - "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", - "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", - "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", - "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", - "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", - "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", - "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", - "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", - "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", - "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", - "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tauri-apps/api": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", - "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz", - "integrity": "sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.11.2", - "@tauri-apps/cli-darwin-x64": "2.11.2", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.2", - "@tauri-apps/cli-linux-arm64-gnu": "2.11.2", - "@tauri-apps/cli-linux-arm64-musl": "2.11.2", - "@tauri-apps/cli-linux-riscv64-gnu": "2.11.2", - "@tauri-apps/cli-linux-x64-gnu": "2.11.2", - "@tauri-apps/cli-linux-x64-musl": "2.11.2", - "@tauri-apps/cli-win32-arm64-msvc": "2.11.2", - "@tauri-apps/cli-win32-ia32-msvc": "2.11.2", - "@tauri-apps/cli-win32-x64-msvc": "2.11.2" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.2.tgz", - "integrity": "sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.2.tgz", - "integrity": "sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.2.tgz", - "integrity": "sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.2.tgz", - "integrity": "sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.2.tgz", - "integrity": "sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.2.tgz", - "integrity": "sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.2.tgz", - "integrity": "sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.2.tgz", - "integrity": "sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.2.tgz", - "integrity": "sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.2.tgz", - "integrity": "sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.2.tgz", - "integrity": "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/plugin-opener": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz", - "integrity": "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.11.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.372", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", - "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lucide-react": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.18.0.tgz", - "integrity": "sha512-LZDb7H/0YfM+RJncD0hDQRCAu+vSGODqpe35TuVI8EuXaRjkczbsx7p8dY4J87F/MUSj6bpYqeI8nw8qXaAdmA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.7" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", - "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.0", - "@rollup/rollup-android-arm64": "4.62.0", - "@rollup/rollup-darwin-arm64": "4.62.0", - "@rollup/rollup-darwin-x64": "4.62.0", - "@rollup/rollup-freebsd-arm64": "4.62.0", - "@rollup/rollup-freebsd-x64": "4.62.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", - "@rollup/rollup-linux-arm-musleabihf": "4.62.0", - "@rollup/rollup-linux-arm64-gnu": "4.62.0", - "@rollup/rollup-linux-arm64-musl": "4.62.0", - "@rollup/rollup-linux-loong64-gnu": "4.62.0", - "@rollup/rollup-linux-loong64-musl": "4.62.0", - "@rollup/rollup-linux-ppc64-gnu": "4.62.0", - "@rollup/rollup-linux-ppc64-musl": "4.62.0", - "@rollup/rollup-linux-riscv64-gnu": "4.62.0", - "@rollup/rollup-linux-riscv64-musl": "4.62.0", - "@rollup/rollup-linux-s390x-gnu": "4.62.0", - "@rollup/rollup-linux-x64-gnu": "4.62.0", - "@rollup/rollup-linux-x64-musl": "4.62.0", - "@rollup/rollup-openbsd-x64": "4.62.0", - "@rollup/rollup-openharmony-arm64": "4.62.0", - "@rollup/rollup-win32-arm64-msvc": "4.62.0", - "@rollup/rollup-win32-ia32-msvc": "4.62.0", - "@rollup/rollup-win32-x64-gnu": "4.62.0", - "@rollup/rollup-win32-x64-msvc": "4.62.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", - "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - } - } -} diff --git a/app/frontend/src/App.jsx b/app/frontend/src/App.jsx index 32df91b..2e4aa58 100644 --- a/app/frontend/src/App.jsx +++ b/app/frontend/src/App.jsx @@ -769,6 +769,7 @@ function App() { activeModel={activeModel} specs={specs} backendOptions={backendOptions} + setBackendOptions={setBackendOptions} serverRunning={serverRunning} setServerRunning={setServerRunning} setActiveModel={setActiveModel} diff --git a/app/frontend/src/components/Generator.jsx b/app/frontend/src/components/Generator.jsx index 7929037..5c5a92d 100644 --- a/app/frontend/src/components/Generator.jsx +++ b/app/frontend/src/components/Generator.jsx @@ -183,8 +183,11 @@ function Generator({ const settings = status.settings; const currentModelName = settings.model ? settings.model.split(/[\\/]/).pop() : null; const targetModelName = activeModel ? activeModel.split(/[\\/]/).pop() : null; + const requestedBackend = constraints.backendType || (constraints.useGpu === false ? "cpu" : "auto"); + const currentBackend = settings.backendType || (settings.useGpu === false ? "cpu" : "auto"); + const shouldCompareModel = requestedBackend !== "openvino-npu"; - if (currentModelName !== targetModelName || + if ((shouldCompareModel && currentModelName !== targetModelName) || parseInt(settings.steps) !== parseInt(constraints.steps) || Math.abs(parseFloat(settings.cfgScale) - parseFloat(constraints.cfgScale)) > 0.05 || settings.sampler !== constraints.sampler || @@ -192,7 +195,7 @@ function Generator({ Boolean(settings.useGpu) !== (constraints.useGpu !== false) || parseInt(settings.width || 512) !== parseInt(constraints.width || 512) || parseInt(settings.height || 512) !== parseInt(constraints.height || 512) || - (settings.backendType || (settings.useGpu === false ? "cpu" : "auto")) !== (constraints.backendType || (constraints.useGpu === false ? "cpu" : "auto"))) { + currentBackend !== requestedBackend) { needsRestart = true; } } else { diff --git a/app/frontend/src/components/ModelManager.jsx b/app/frontend/src/components/ModelManager.jsx index 69d95e7..c044987 100644 --- a/app/frontend/src/components/ModelManager.jsx +++ b/app/frontend/src/components/ModelManager.jsx @@ -726,6 +726,44 @@ function ModelManager({ return; } + const isOpenVinoSelected = constraints.backendType === "openvino-npu"; + const isOpenVinoModel = modelInfo.backendType === "openvino-npu"; + const isCoreMLModel = modelInfo.format === "CoreML" || modelInfo.backendType === "apple-npu"; + const isStandardWeights = /\.(safetensors|ckpt|gguf)$/i.test(modelId) || (!isOpenVinoModel && !isCoreMLModel); + if (isOpenVinoSelected && isStandardWeights) { + const gpuBackend = backendOptions?.options?.find((backend) => + ["vulkan", "cuda", "rocm", "metal"].includes(backend.id) + ); + const cpuBackend = backendOptions?.options?.find((backend) => backend.id === "cpu"); + const targetBackend = gpuBackend || cpuBackend; + + if (!targetBackend) { + await showAlert({ + title: "Switch Backend Required", + message: `"${modelId}" is a standard weights file. OpenVINO NPU can only load downloaded OpenVINO model folders, and no GPU or CPU backend is currently installed.`, + danger: true, + }); + return; + } + + const confirmed = await showConfirm({ + title: "Switch Accelerator?", + message: `"${modelId}" cannot load on OpenVINO NPU. Switch to ${targetBackend.label} and load it there?`, + confirmLabel: `Switch to ${targetBackend.label}`, + cancelLabel: "Cancel", + }); + if (!confirmed) return; + + const nextConstraints = { + ...constraints, + backendType: targetBackend.id, + useGpu: targetBackend.id !== "cpu", + }; + setConstraints(nextConstraints); + await performLoadModel(modelId, nextConstraints); + return; + } + const totalSizeBytes = Number(modelInfo.sizeBytes) || 0; if (totalSizeBytes <= 0) { await performLoadModel(modelId); @@ -944,6 +982,7 @@ function ModelManager({ let isReady = false; let crashError = null; + let readyStatus = null; const isOpenVinoModel = modelInfo?.backendType === "openvino-npu"; const maxStartupPolls = (isOpenVinoModel || isCoreMLModel) ? 1200 : 240; for (let i = 0; i < maxStartupPolls; i++) { @@ -967,6 +1006,7 @@ function ModelManager({ } if (status.ready) { isReady = true; + readyStatus = status; break; } if (status.error) { @@ -981,6 +1021,9 @@ function ModelManager({ } if (isReady) { + const loadedModelId = readyStatus?.settings?.backendType === "openvino-npu" + ? (readyStatus.settings.model || readyStatus.loading?.model || modelId) + : modelId; setModelLoadProgress((prev) => ({ ...(prev || {}), progress: 100, @@ -988,9 +1031,9 @@ function ModelManager({ speed: "", current: prev?.current || 0, total: prev?.total || 0, - model: modelId, + model: loadedModelId, })); - setActiveModel(modelId); + setActiveModel(loadedModelId); setServerRunning(true); } else { throw new Error(crashError || `Model server failed to respond on port ${backendPort}.`); diff --git a/app/frontend/src/components/Settings.jsx b/app/frontend/src/components/Settings.jsx index 4fa0ecd..bbcd365 100644 --- a/app/frontend/src/components/Settings.jsx +++ b/app/frontend/src/components/Settings.jsx @@ -3,9 +3,22 @@ import { Crop, Sliders, Cpu, Info, MessageSquare, SlidersHorizontal, Zap, ChevronDown, Image, Type, Settings2, Gauge, Brain, Sparkles, Monitor, HardDrive, MemoryStick, Thermometer, Hash, Layers, - ChevronRight, Box, Wand2, Lightbulb, RotateCcw, Check, Palette, Volume2 + ChevronRight, Box, Wand2, Lightbulb, RotateCcw, Check, Palette, Volume2, + DownloadCloud, RefreshCw } from "lucide-react"; -import { stopServer, formatBytes, getLlmBackends, getLlmStats, getLlmStatus, benchmarkLlm, startLlm, stopLlm } from "../services/api"; +import { + stopServer, + formatBytes, + getLlmBackends, + getLlmStats, + getLlmStatus, + benchmarkLlm, + startLlm, + stopLlm, + downloadBackend, + getDownloadProgress, + getBackendOptions, +} from "../services/api"; import { THEMES } from "../themes"; const ASPECT_RATIOS = [ @@ -200,6 +213,7 @@ function Settings({ activeModel, specs, backendOptions, + setBackendOptions, serverRunning, setServerRunning, setActiveModel, @@ -225,6 +239,13 @@ function Settings({ const [llmBackends, setLlmBackends] = useState({ available: [], candidates: [] }); const [llmStats, setLlmStats] = useState({ benchmarks: [] }); const [benchmarkBusy, setBenchmarkBusy] = useState(false); + const [backendDownload, setBackendDownload] = useState({ + active: false, + backendId: "", + progress: 0, + speed: "", + error: null, + }); const [expandedSections, setExpandedSections] = useState(() => { return { @@ -273,6 +294,68 @@ function Settings({ const availableBackends = backendOptions?.options?.length ? backendOptions.options : [{ id: "cpu", label: "CPU", available: true }]; + const unavailableBackends = Array.isArray(backendOptions?.unavailable) + ? backendOptions.unavailable.filter((backend) => !availableBackends.some((available) => available.id === backend.id)) + : []; + const visibleBackends = [...availableBackends, ...unavailableBackends.map((backend) => ({ ...backend, available: false }))]; + + const refreshBackendOptions = useCallback(async () => { + if (typeof setBackendOptions !== "function") return; + const nextOptions = await getBackendOptions(); + setBackendOptions(nextOptions); + }, [setBackendOptions]); + + useEffect(() => { + refreshBackendOptions().catch(() => {}); + const handleFocus = () => refreshBackendOptions().catch(() => {}); + const handleVisibility = () => { + if (document.visibilityState === "visible") handleFocus(); + }; + window.addEventListener("focus", handleFocus); + document.addEventListener("visibilitychange", handleVisibility); + return () => { + window.removeEventListener("focus", handleFocus); + document.removeEventListener("visibilitychange", handleVisibility); + }; + }, [refreshBackendOptions]); + + useEffect(() => { + if (!backendDownload.active) return undefined; + let cancelled = false; + + const poll = async () => { + try { + const progress = await getDownloadProgress(); + if (cancelled) return; + setBackendDownload((prev) => ({ + ...prev, + active: Boolean(progress.active), + progress: Number(progress.progress ?? prev.progress ?? 0), + speed: progress.speed || prev.speed || "", + error: progress.error || null, + })); + + if (!progress.active && !progress.error) { + await refreshBackendOptions(); + } + } catch (err) { + if (!cancelled) { + setBackendDownload((prev) => ({ + ...prev, + active: false, + error: err.message || "Download failed", + })); + } + } + }; + + poll(); + const interval = setInterval(poll, 700); + return () => { + cancelled = true; + clearInterval(interval); + }; + }, [backendDownload.active, refreshBackendOptions]); const updateConstraint = (key, value) => { setConstraints((prev) => ({ @@ -466,6 +549,33 @@ function Settings({ })); }; + const handleBackendDownload = async (backend) => { + if (!backend?.id || backendDownload.active) return; + try { + setBackendDownload({ + active: true, + backendId: backend.id, + progress: 0, + speed: "Starting", + error: null, + }); + await downloadBackend(backend.id); + } catch (err) { + setBackendDownload({ + active: false, + backendId: backend.id, + progress: 0, + speed: "", + error: err.message || "Download failed", + }); + await showAlert({ + title: "Backend Download Failed", + message: err.message || "Could not start backend download.", + danger: true, + }); + } + }; + // ─── Image Settings ─── const renderImageSettings = () => ( <> @@ -667,17 +777,79 @@ function Settings({ Accelerator
- {availableBackends.map((b) => ( + {visibleBackends.map((b) => { + const isAvailable = b.available !== false; + const isDownloading = backendDownload.active && backendDownload.backendId === b.id; + return ( - ))} + ); + })}
+ {unavailableBackends.length > 0 && ( +
+ {unavailableBackends.map((backend) => { + const isDownloading = backendDownload.active && backendDownload.backendId === backend.id; + const progress = Math.max(0, Math.min(100, Math.round(backendDownload.progress || 0))); + return ( +
+
+
+ {backend.label} +
+
+ {isDownloading + ? `${progress}% ${backendDownload.speed ? `- ${backendDownload.speed}` : ""}` + : backendDownload.error && backendDownload.backendId === backend.id + ? backendDownload.error + : backend.reason} +
+ {isDownloading && ( +
+
+
+ )} +
+ +
+ ); + })} +
+ )}
diff --git a/app/frontend/src/services/api.js b/app/frontend/src/services/api.js index 062557a..3623892 100644 --- a/app/frontend/src/services/api.js +++ b/app/frontend/src/services/api.js @@ -205,6 +205,15 @@ export async function getBackendOptions() { }; } +export async function downloadBackend(backendId) { + const res = await fetch("/api/download-backend", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ backend_id: backendId }), + }); + return await readJsonResponse(res, "The local server returned an invalid backend download response."); +} + // Get list of model files on the USB export async function listLocalModels() { if (isTauri()) { @@ -247,19 +256,24 @@ export async function startServer(modelPath, constraints) { } // Web/portable mode — call serve.cjs management API + const backendType = constraints.backendType || (constraints.useGpu === false ? "cpu" : "auto"); const modelName = modelPath ? modelPath.split(/[\\/]/).pop() : null; + const isOpenVinoBackend = backendType === "openvino-npu"; + const requestModel = isOpenVinoBackend && /\.(safetensors|ckpt|gguf)$/i.test(modelName || "") + ? null + : modelName; try { const res = await fetch("/api/restart-backend", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - model: modelName, + model: requestModel, steps: constraints.steps || 20, cfgScale: constraints.cfgScale || 7.0, sampler: constraints.sampler || "euler_a", threads: constraints.threads || 8, use_gpu: constraints.useGpu !== false, - backend_type: constraints.backendType || (constraints.useGpu === false ? "cpu" : "auto"), + backend_type: backendType, width: constraints.width || 512, height: constraints.height || 512, vae_tiling: constraints.vaeTiling !== false, diff --git a/scripts/reset/reset.ps1 b/scripts/reset/reset.ps1 index d6d6bbf..6c9d24f 100644 --- a/scripts/reset/reset.ps1 +++ b/scripts/reset/reset.ps1 @@ -11,10 +11,10 @@ Write-Host " Resetting Local AI Studio..." -ForegroundColor Yellow Write-Host " ============================================================" -ForegroundColor Yellow Write-Host "" -# Delete tools/node +# Delete portable tools/runtime folder $toolsDir = Join-Path $appDir "tools" if (Test-Path $toolsDir) { - Write-Host " >> Removing portable tools/ node folder..." -ForegroundColor Cyan + Write-Host " >> Removing portable tools/runtime folder..." -ForegroundColor Cyan Remove-Item $toolsDir -Recurse -Force -ErrorAction SilentlyContinue } @@ -23,6 +23,7 @@ $backendDir = Join-Path $appDir "backend" if (Test-Path $backendDir) { Write-Host " >> Removing image backend binaries..." -ForegroundColor Cyan Remove-Item $backendDir -Recurse -Force -ErrorAction SilentlyContinue + Write-Host " Preserving source workers in scripts/workers." -ForegroundColor DarkGray } # Delete llama.cpp backend diff --git a/scripts/reset/reset.sh b/scripts/reset/reset.sh index 56c7778..7dfef68 100644 --- a/scripts/reset/reset.sh +++ b/scripts/reset/reset.sh @@ -16,9 +16,9 @@ echo " Resetting Local AI Studio..." echo " ============================================================" echo "" -# Delete tools/node +# Delete portable tools/runtime folder if [[ -d "$APP_DIR/tools" ]]; then - echo " >> Removing portable tools/ node folder..." + echo " >> Removing portable tools/runtime folder..." rm -rf "$APP_DIR/tools" fi diff --git a/scripts/server/serve.cjs b/scripts/server/serve.cjs index 185f46f..f271f94 100644 --- a/scripts/server/serve.cjs +++ b/scripts/server/serve.cjs @@ -41,10 +41,12 @@ const MAX_JSON_BODY_BYTES = 64 * 1024 * 1024; const SERVER_BUILD = "text-image-v1"; const ROOT = path.join(__dirname, "..", ".."); const DIST = path.join(ROOT, "app", "dist"); +const TOOLS = path.join(ROOT, "app", "tools"); const osPlatform = process.platform; 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"), + cpu: path.join(ROOT, "app", "backend", "win", "cpu", "sd-cpu.exe"), mac: path.join(ROOT, "app", "backend", "mac", "sd"), linuxCpu: path.join(ROOT, "app", "backend", "linux", "cpu", "sd-server-cpu"), linuxVulkan: path.join(ROOT, "app", "backend", "linux", "vulkan", "sd-server-vulkan"), @@ -67,6 +69,8 @@ if (osPlatform === "win32") { if (hasNvidia && fs.existsSync(BACKEND_PATHS.cuda)) { BACKEND_PATH = BACKEND_PATHS.cuda; + } else if (fs.existsSync(BACKEND_PATHS.cpu)) { + BACKEND_PATH = BACKEND_PATHS.cpu; } else { BACKEND_PATH = BACKEND_PATHS.vulkan; } @@ -224,6 +228,7 @@ const OPENVINO_NPU_MODELS = [ // ── Backend process state ───────────────────────────────────────────────────── let backendProc = null; +let backendProcSeq = 0; let backendReady = false; let backendError = null; let openvinoProc = null; @@ -404,6 +409,24 @@ function getCpuUsagePercent() { return Math.max(0, Math.min(100, Math.round((1 - idleDelta / totalDelta) * 1000) / 10)); } +function hasVulkanRuntime() { + if (osPlatform !== "win32") return true; + try { + const sysPaths = [ + path.join(process.env.SystemRoot || "C:\\Windows", "System32", "vulkan-1.dll"), + path.join(process.env.SystemRoot || "C:\\Windows", "SysWOW64", "vulkan-1.dll"), + ]; + for (const dllPath of sysPaths) { + if (fs.existsSync(dllPath)) return true; + } + try { + execSync("where vulkan-1.dll", { stdio: "ignore" }); + return true; + } catch (_) {} + } catch (_) {} + return false; +} + function detectLinuxGpuFromSysfs() { try { const pciDir = "/sys/bus/pci/devices"; @@ -776,6 +799,74 @@ function getGpuInfo() { // ── NPU Detection ───────────────────────────────────────────────────────────── let cachedNpuInfo = null; +let cachedHasNpuHardware = null; + +function hasNpuHardware() { + if (cachedHasNpuHardware !== null) return cachedHasNpuHardware; + + if (osPlatform !== "win32" && osPlatform !== "linux") { + cachedHasNpuHardware = false; + return cachedHasNpuHardware; + } + + try { + if (osPlatform === "win32") { + const deviceResult = spawnSync("wmic", [ + "path", "win32_pnpentity", "where", "Name like '%NPU%' or Name like '%Intel%AI%' or Name like '%VPU%'", "get", "Name", "/value" + ], { encoding: "utf8", timeout: 10000, stdio: ["ignore", "pipe", "pipe"] }); + if (deviceResult.status === 0 && deviceResult.stdout && /NPU|AI Boost|VPU/i.test(deviceResult.stdout)) { + cachedHasNpuHardware = true; + return cachedHasNpuHardware; + } + + const cpuResult = spawnSync("wmic", ["cpu", "get", "Name", "/value"], { + encoding: "utf8", + timeout: 10000, + stdio: ["ignore", "pipe", "pipe"], + }); + if (cpuResult.status === 0 && cpuResult.stdout) { + const cpuName = cpuResult.stdout.toLowerCase(); + if (cpuName.includes("ultra") && (cpuName.match(/ultra\s*\d/) || cpuName.includes("core(tm) ultra"))) { + cachedHasNpuHardware = true; + return cachedHasNpuHardware; + } + } + } else if (osPlatform === "linux") { + const lspciResult = spawnSync("lspci", [], { encoding: "utf8", timeout: 10000, stdio: ["ignore", "pipe", "pipe"] }); + if (lspciResult.status === 0 && lspciResult.stdout && lspciResult.stdout.toLowerCase().includes("npu")) { + cachedHasNpuHardware = true; + return cachedHasNpuHardware; + } + + try { + const sysPci = fs.readdirSync("/sys/bus/pci/devices"); + for (const dev of sysPci) { + const vendorPath = `/sys/bus/pci/devices/${dev}/vendor`; + const classPath = `/sys/bus/pci/devices/${dev}/class`; + if (!fs.existsSync(vendorPath) || !fs.existsSync(classPath)) continue; + const vendor = fs.readFileSync(vendorPath, "utf8").trim(); + const devClass = fs.readFileSync(classPath, "utf8").trim(); + if (vendor === "0x8086" && (devClass.includes("1180") || devClass.includes("acce"))) { + cachedHasNpuHardware = true; + return cachedHasNpuHardware; + } + } + } catch (_) {} + + try { + const cpuInfo = fs.readFileSync("/proc/cpuinfo", "utf8").toLowerCase(); + if (cpuInfo.includes("ultra") && cpuInfo.includes("intel")) { + cachedHasNpuHardware = true; + return cachedHasNpuHardware; + } + } catch (_) {} + } + } catch (_) {} + + cachedHasNpuHardware = false; + return cachedHasNpuHardware; +} + function detectNpu() { if (cachedNpuInfo !== null) return cachedNpuInfo; cachedNpuInfo = { detected: false, vendor: null, name: null }; @@ -1372,15 +1463,10 @@ function getOpenVinoPythonCandidates() { const candidates = []; if (process.env.OPENVINO_PYTHON) candidates.push(process.env.OPENVINO_PYTHON); if (osPlatform === "win32") { - candidates.push(path.join(ROOT, "app", "tools", "openvino-venv-win", "Scripts", "python.exe")); - candidates.push(path.join(ROOT, "app", "tools", "openvino-venv", "Scripts", "python.exe")); // legacy fallback - candidates.push("C:\\tmp\\npu-test-venv\\Scripts\\python.exe"); - candidates.push("python"); + candidates.push(path.join(ROOT, "app", "tools", "python-win", "python.exe")); } else { candidates.push(path.join(ROOT, "app", "tools", "openvino-venv-linux", "bin", "python")); candidates.push(path.join(ROOT, "app", "tools", "openvino-venv", "bin", "python")); // legacy fallback - candidates.push("python3"); - candidates.push("python"); } return [...new Set(candidates)]; } @@ -2432,6 +2518,7 @@ function getSetupPaths() { distIndex: path.join(DIST, "index.html"), cudaBackend: BACKEND_PATHS.cuda, vulkanBackend: BACKEND_PATHS.vulkan, + cpuBackend: BACKEND_PATHS.cpu, models: MODELS, outputs: OUTPUTS, }; @@ -2472,6 +2559,7 @@ async function getHealth() { if (osPlatform === "win32") { checks.push(getPathInfo("CUDA backend", paths.cudaBackend)); checks.push(getPathInfo("Vulkan backend", paths.vulkanBackend)); + checks.push(getPathInfo("CPU backend", paths.cpuBackend)); } else if (osPlatform === "darwin") { checks.push(getPathInfo("Mac backend", paths.macBackend)); } else { @@ -2483,7 +2571,8 @@ async function getHealth() { let backendInstalled = false; if (osPlatform === "win32") { backendInstalled = checks.find((check) => check.label === "CUDA backend")?.exists || - checks.find((check) => check.label === "Vulkan backend")?.exists; + checks.find((check) => check.label === "Vulkan backend")?.exists || + checks.find((check) => check.label === "CPU backend")?.exists; } else if (osPlatform === "darwin") { backendInstalled = checks.find((check) => check.label === "Mac backend")?.exists; } else { @@ -2495,7 +2584,7 @@ async function getHealth() { } const criticalOk = checks - .filter((check) => !["CUDA backend", "Vulkan backend", "Linux CPU backend", "Linux Vulkan backend", "Linux ROCm backend", "Mac backend"].includes(check.label)) + .filter((check) => !["CUDA backend", "Vulkan backend", "CPU backend", "Linux CPU backend", "Linux Vulkan backend", "Linux ROCm backend", "Mac backend"].includes(check.label)) .every((check) => check.ok) && backendInstalled; const ports = { @@ -2508,7 +2597,7 @@ async function getHealth() { ports.backend.ok = true; const issues = checks - .filter((check) => !check.ok && !["CUDA backend", "Vulkan backend", "Linux CPU backend", "Linux Vulkan backend", "Linux ROCm backend", "Mac backend"].includes(check.label)) + .filter((check) => !check.ok && !["CUDA backend", "Vulkan backend", "CPU 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.`); @@ -2645,6 +2734,9 @@ function isRunningInWSL() { } function getVulkanUnavailableReason() { + if (osPlatform === "win32" && !hasVulkanRuntime()) { + return "The Vulkan runtime (vulkan-1.dll) is missing. Install or update your GPU vendor driver with Vulkan support, then run setup again so the Vulkan backend folder is repaired."; + } if (osPlatform === "linux" && isRunningInWSL()) { const gpuName = getGpuInfo().name; const lowerGpu = gpuName.toLowerCase(); @@ -2764,13 +2856,15 @@ function getBackendOptions() { ); const vulkanInstalled = (osPlatform === "win32" && fs.existsSync(BACKEND_PATHS.vulkan)) || (osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxVulkan)); - const vulkanAvailable = vulkanInstalled && backendAccepts( + const vulkanRuntimeAvailable = osPlatform !== "win32" || hasVulkanRuntime(); + const vulkanAvailable = vulkanInstalled && vulkanRuntimeAvailable && backendAccepts( 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 cpuInstalled = (osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxCpu)) || + (osPlatform === "win32" && fs.existsSync(BACKEND_PATHS.cpu)); const metalInstalled = osPlatform === "darwin" && fs.existsSync(BACKEND_PATHS.mac); const metalAvailable = metalInstalled; const coremlNpu = getCoreMLNpuInfo(); @@ -2778,7 +2872,8 @@ function getBackendOptions() { const openvinoModels = getOpenVinoModelInfo(); const openvinoNpuAvailable = openvinoNpu.supported && openvinoModels.some((model) => model.installed); - const options = [{ id: "cpu", label: "CPU", available: true }]; + const options = []; + if (cpuInstalled) options.push({ id: "cpu", label: "CPU", available: true }); if (metalAvailable) options.push({ id: "metal", label: "Metal GPU", available: true }); if (coremlNpu.supported && coremlNpu.ready) options.push({ id: "apple-npu", label: "Apple Neural Engine (NPU)", available: true }); if (vulkanAvailable) options.push({ id: "vulkan", label: "Vulkan GPU", available: true }); @@ -2802,9 +2897,18 @@ function getBackendOptions() { if (coremlNpu.supported && !coremlNpu.ready) { unavailable.push({ id: "apple-npu", label: "Apple Neural Engine (NPU)", reason: coremlNpu.reason }); } + if (!cpuInstalled && (osPlatform === "win32" || osPlatform === "linux")) { + unavailable.push({ + id: "cpu", + label: "CPU", + reason: osPlatform === "win32" + ? "Windows CPU backend is not installed. Run scripts/setup/setup.ps1 to install app/backend/win/cpu." + : "Linux CPU backend is not installed. Run setup again to install app/backend/linux/cpu.", + }); + } if (openvinoNpu.supported && !openvinoNpuAvailable) { unavailable.push({ id: "openvino-npu", label: "NPU (OpenVINO)", reason: "Runtime is ready, but no OpenVINO NPU model is downloaded." }); - } else if (!openvinoNpu.supported && (osPlatform === "win32" || osPlatform === "linux")) { + } else if (!openvinoNpu.supported && hasNpuHardware() && (osPlatform === "win32" || osPlatform === "linux")) { unavailable.push({ id: "openvino-npu", label: "NPU (OpenVINO)", reason: openvinoNpu.reason }); } @@ -2908,6 +3012,10 @@ function selectBackendPath(useGpu, backendType = "auto", modelPath = "") { const resolvedType = resolveBackendType(useGpu, backendType, modelPath); if (osPlatform === "win32") { if (resolvedType === "cuda" && fs.existsSync(BACKEND_PATHS.cuda)) return BACKEND_PATHS.cuda; + if (resolvedType === "cpu" && fs.existsSync(BACKEND_PATHS.cpu)) return BACKEND_PATHS.cpu; + if (resolvedType === "vulkan" && fs.existsSync(BACKEND_PATHS.vulkan)) return BACKEND_PATHS.vulkan; + if (fs.existsSync(BACKEND_PATHS.cuda)) return BACKEND_PATHS.cuda; + if (fs.existsSync(BACKEND_PATHS.cpu)) return BACKEND_PATHS.cpu; if (fs.existsSync(BACKEND_PATHS.vulkan)) return BACKEND_PATHS.vulkan; return BACKEND_PATH; } @@ -3347,7 +3455,11 @@ function getOpenVinoResolution(settings = {}) { async function startOpenVinoWorker(settings = {}) { const npuInfo = getOpenVinoNpuInfo(); if (!npuInfo.supported) throw new Error(npuInfo.reason || "OpenVINO NPU is not available."); - const model = findOpenVinoModel(settings.model) || getOpenVinoModelInfo().find((item) => item.installed); + const requestedModel = settings.model ? findOpenVinoModel(settings.model) : null; + if (settings.model && !requestedModel) { + throw new Error("OpenVINO NPU requires a downloaded OpenVINO model. Standard .safetensors/.ckpt weights must use Vulkan, CUDA, or CPU."); + } + const model = requestedModel || getOpenVinoModelInfo().find((item) => item.installed); if (!model || !model.installed) { throw new Error("OpenVINO NPU model is not downloaded. Download an OpenVINO NPU model from Model Manager first."); } @@ -3512,18 +3624,19 @@ function getDefaultModel() { function killBackend() { return new Promise(resolve => { resetGenerationState(); - if (!backendProc) { + const proc = backendProc; + if (!proc) { backendUnloadState = { active: false, phase: "", progress: 0 }; resolve(); return; } backendUnloadState = { active: true, phase: "Stopping backend process...", progress: 10 }; backendReady = false; - backendProc.kill("SIGTERM"); + try { proc.kill("SIGTERM"); } catch (_) {} backendUnloadState = { active: true, phase: "Waiting for process exit...", progress: 50 }; setTimeout(() => { - try { backendProc.kill("SIGKILL"); } catch (_) {} - backendProc = null; + try { proc.kill("SIGKILL"); } catch (_) {} + if (backendProc === proc) backendProc = null; backendUnloadState = { active: false, phase: "Backend unloaded", progress: 100 }; resolve(); }, 2000); @@ -3958,6 +4071,14 @@ async function startBackend(settings = {}) { currentSettings.backendType = resolvedBackendType; currentSettings.useGpu = resolvedBackendType !== "cpu"; const backendPath = selectBackendPath(currentSettings.useGpu, currentSettings.backendType, currentSettings.model); + if (!backendPath) { + const setupHint = resolvedBackendType === "apple-npu" + ? "CoreML Python environment is not set up. Run scripts/setup/setup-coreml-npu.sh first." + : resolvedBackendType === "cpu" && osPlatform === "win32" + ? "Windows CPU backend is not installed. Run scripts/setup.ps1 again so app/backend/win/cpu is created." + : "No compatible backend executable was found."; + throw new Error(setupHint); + } const backendMode = getBackendMode(backendPath, currentSettings.useGpu, currentSettings.backendType); currentSettings.backendMode = backendMode; currentSettings.backendBinary = path.basename(backendPath); @@ -3987,7 +4108,7 @@ async function startBackend(settings = {}) { if (requestedBackend === "apple-npu") { args = [ - path.join(ROOT, "app", "backend", "mac", "coreml_server.py"), + path.join(ROOT, "scripts", "workers", "coreml_server.py"), "--listen-port", String(PORT_BACKEND), "--model", currentSettings.model, "--steps", String(currentSettings.steps), @@ -4072,10 +4193,12 @@ async function startBackend(settings = {}) { console.log(" [backend] Starting:", path.basename(backendPath), args.join(" ")); backendReady = false; - backendProc = spawn(backendPath, args, { stdio: "pipe", env: spawnEnv }); + const procSeq = ++backendProcSeq; + const proc = spawn(backendPath, args, { stdio: "pipe", env: spawnEnv }); + backendProc = proc; startBackendReadyPoll(); - backendProc.stdout.on("data", d => { + proc.stdout.on("data", d => { const output = d.toString(); process.stdout.write(" [sd] " + output); const cleanOutput = stripAnsi(output); @@ -4155,7 +4278,7 @@ async function startBackend(settings = {}) { } }); - backendProc.stderr.on("data", d => { + proc.stderr.on("data", d => { const output = d.toString(); process.stderr.write(" [sd-err] " + output); const cleanOutput = stripAnsi(output); @@ -4194,10 +4317,14 @@ async function startBackend(settings = {}) { } } }); - backendProc.on("exit", code => { + proc.on("exit", (code, signal) => { + if (backendProc !== proc) { + console.log(" [backend] stale process exited with code", code, signal ? `(signal ${signal})` : "", `(process ${procSeq})`); + return; + } backendReady = false; backendProc = null; - console.log(" [backend] exited with code", code); + console.log(" [backend] exited with code", code, signal ? `(signal ${signal})` : "", `(process ${procSeq})`); if (code !== null && code !== 0) { if (!backendError) { backendError = describeBackendExitCode(code, currentSettings.backendBinary || BACKEND_PATH); @@ -4276,6 +4403,221 @@ function startOpenVinoModelDownload(modelId) { }); } +const IMAGE_BACKEND_DOWNLOADS = { + "cpu": { + id: "cpu", + label: "CPU", + url: "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-avx2-x64.zip", + destDir: path.join(ROOT, "app", "backend", "win", "cpu"), + exeName: "sd-cpu.exe", + requiredDll: "stable-diffusion.dll", + }, + "vulkan": { + id: "vulkan", + label: "Vulkan GPU", + url: "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-vulkan-x64.zip", + destDir: path.join(ROOT, "app", "backend", "win", "vulkan"), + exeName: "sd-vulkan.exe", + requiredDll: "stable-diffusion.dll", + }, + "cuda": { + id: "cuda", + label: "CUDA GPU", + url: "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-cuda12-x64.zip", + destDir: path.join(ROOT, "app", "backend", "win", "cuda"), + exeName: "sd-cuda.exe", + requiredDll: "stable-diffusion.dll", + }, +}; + +function listFilesRecursive(dir) { + const out = []; + if (!fs.existsSync(dir)) return out; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + out.push(...listFilesRecursive(full)); + } else if (entry.isFile()) { + out.push(full); + } + } + return out; +} + +function installImageBackendArchive(zipPath, backend) { + const tempDir = path.join(TOOLS, `image-backend-${backend.id}-extract-${Date.now()}`); + try { + fs.rmSync(tempDir, { recursive: true, force: true }); + fs.mkdirSync(tempDir, { recursive: true }); + fs.mkdirSync(backend.destDir, { recursive: true }); + + const tar = spawnSync("tar.exe", ["-xf", zipPath, "-C", tempDir], { encoding: "utf8" }); + if (tar.status !== 0) { + throw new Error((tar.stderr || tar.stdout || "Could not extract backend archive.").trim()); + } + + const files = listFilesRecursive(tempDir); + const exeSource = files.find((file) => ["sd-server.exe", "sd.exe"].includes(path.basename(file).toLowerCase())); + const dllSource = files.find((file) => path.basename(file).toLowerCase() === backend.requiredDll.toLowerCase()); + if (!exeSource || !dllSource) { + throw new Error("Backend archive did not contain the expected stable-diffusion executable and DLL."); + } + + fs.copyFileSync(exeSource, path.join(backend.destDir, backend.exeName)); + fs.copyFileSync(dllSource, path.join(backend.destDir, backend.requiredDll)); + for (const file of files) { + const ext = path.extname(file).toLowerCase(); + if (ext !== ".dll" && ext !== ".exe") continue; + if (file === exeSource) continue; + fs.copyFileSync(file, path.join(backend.destDir, path.basename(file))); + } + + if (!fs.existsSync(path.join(backend.destDir, backend.exeName)) || !fs.existsSync(path.join(backend.destDir, backend.requiredDll))) { + throw new Error(`Backend install did not create ${backend.exeName} and ${backend.requiredDll}.`); + } + cachedBackendOptions = null; + } finally { + try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (_) {} + } +} + +function startImageBackendDownload(backendId, redirectCount = 0, redirectUrl = "") { + if (downloadState.active && redirectCount === 0) { + throw new Error("Another download is already active."); + } + if (osPlatform !== "win32") { + throw new Error("In-app backend downloads are currently available for Windows image backends."); + } + const backend = IMAGE_BACKEND_DOWNLOADS[String(backendId || "").toLowerCase()]; + if (!backend) { + throw new Error("This backend cannot be installed from the app yet. Use the platform setup script."); + } + + fs.mkdirSync(TOOLS, { recursive: true }); + const url = redirectUrl || backend.url; + const filename = `${backend.id}-backend.zip`; + const destPath = path.join(TOOLS, filename); + const tempPath = `${destPath}.part`; + if (redirectCount === 0) { + try { fs.unlinkSync(tempPath); } catch (_) {} + downloadState = { + active: true, + filename: backend.label, + progress: 0, + speed: "0 MB/s", + eta: 0, + totalBytes: 0, + downloadedBytes: 0, + error: null, + kind: "backend", + backendId: backend.id, + }; + } + + console.log(` [backend-download] Downloading ${backend.label} backend from ${url}`); + let downloadFinalized = false; + const fileStream = fs.createWriteStream(tempPath); + const failDownload = (message, err = null) => { + if (downloadFinalized) return; + downloadFinalized = true; + downloadState.active = false; + downloadState.error = message; + if (err) console.error(" [backend-download]", message, err); + else console.error(" [backend-download] Failed:", message); + try { fileStream.close(); } catch (_) {} + try { fs.unlinkSync(tempPath); } catch (_) {} + activeDownload = null; + }; + + fileStream.on("error", (err) => failDownload(`Could not write ${filename}: ${err.message}`, err)); + const client = url.startsWith("https") ? https : http; + const request = client.get(url, { + headers: { + "User-Agent": "Local-AI-Image-Generator/1.0 (+https://github.com/techjarves/Local-AI-Image-Generator)", + "Accept": "application/zip, application/octet-stream, */*", + }, + }, (response) => { + activeDownload = { request, fileStream, destPath, tempPath }; + if ([301, 302, 303, 307, 308].includes(response.statusCode)) { + const nextUrl = response.headers.location ? new URL(response.headers.location, url).toString() : ""; + if (!nextUrl) return failDownload("Redirect response did not include a Location header."); + if (redirectCount > 10) return failDownload("Too many redirects."); + downloadState.speed = "Following redirect"; + request.removeAllListeners("error"); + request.destroy(); + response.resume(); + fileStream.close(); + try { fs.unlinkSync(tempPath); } catch (_) {} + activeDownload = null; + startImageBackendDownload(backend.id, redirectCount + 1, nextUrl); + return; + } + if (response.statusCode !== 200) { + return failDownload(describeDownloadHttpError(response.statusCode, url, response.headers)); + } + + const totalBytes = parseInt(response.headers["content-length"], 10) || 0; + downloadState.totalBytes = totalBytes; + let downloadedBytes = 0; + let lastTime = Date.now(); + let lastDownloaded = 0; + + response.on("data", (chunk) => { + downloadedBytes += chunk.length; + downloadState.downloadedBytes = downloadedBytes; + fileStream.write(chunk); + const now = Date.now(); + const elapsed = (now - lastTime) / 1000; + if (elapsed >= 0.5) { + const chunkSpeed = (downloadedBytes - lastDownloaded) / elapsed; + lastDownloaded = downloadedBytes; + lastTime = now; + downloadState.speed = `${(chunkSpeed / (1024 * 1024)).toFixed(1)} MB/s`; + if (totalBytes > 0) { + downloadState.progress = Math.round((downloadedBytes / totalBytes) * 100); + downloadState.eta = Math.round((totalBytes - downloadedBytes) / Math.max(1, chunkSpeed)); + } else { + downloadState.progress = -1; + downloadState.eta = -1; + } + } + }); + + response.on("aborted", () => failDownload(`Download interrupted before ${backend.label} backend finished.`)); + response.on("error", (err) => failDownload(`Download stream failed before ${backend.label} backend finished.`, err)); + response.on("end", () => { + if (downloadFinalized) return; + if (totalBytes > 0 && downloadedBytes !== totalBytes) { + failDownload(`Download incomplete for ${backend.label}: received ${formatBytes(downloadedBytes)} of ${formatBytes(totalBytes)}.`); + return; + } + fileStream.end(() => { + try { + try { fs.unlinkSync(destPath); } catch (_) {} + fs.renameSync(tempPath, destPath); + downloadState.speed = "Installing"; + downloadState.progress = 95; + installImageBackendArchive(destPath, backend); + try { fs.unlinkSync(destPath); } catch (_) {} + downloadFinalized = true; + downloadState.active = false; + downloadState.progress = 100; + downloadState.downloadedBytes = downloadedBytes; + downloadState.error = null; + downloadState.speed = "Complete"; + activeDownload = null; + console.log(` [backend-download] Installed ${backend.label} backend`); + } catch (err) { + failDownload(`Could not install ${backend.label} backend: ${err.message}`, err); + } + }); + }); + }); + + request.on("error", (err) => failDownload(err.message, err)); + activeDownload = { request, fileStream, destPath, tempPath }; +} + // ── Generation State (Real-time progress parser) ───────────────────────────── let generationState = { active: false, @@ -5903,6 +6245,20 @@ async function getLlmfitRecommendations(useCase = "chat", limit = 10) { return json(res, 200, { ok: true }); } + // POST /api/download-backend + if (req.url === "/api/download-backend" && req.method === "POST") { + const body = await readJsonBody(req, res); + if (!body) return; + if (downloadState.active) return json(res, 409, { ok: false, error: "Another download is already active." }); + try { + const backendId = String(body.backend_id || body.backendId || body.id || ""); + startImageBackendDownload(backendId); + return json(res, 200, { ok: true, message: "Backend download started", backendId }); + } catch (err) { + return json(res, 400, { ok: false, error: err.message || String(err) }); + } + } + // POST /api/download-model if (req.url === "/api/download-model" && req.method === "POST") { const body = await readJsonBody(req, res); @@ -5958,6 +6314,25 @@ async function getLlmfitRecommendations(useCase = "chat", limit = 10) { // GET /api/download-progress if (req.url === "/api/download-progress" && req.method === "GET") { + if (!downloadState.active && downloadState.kind === "backend" && downloadState.backendId) { + const backend = IMAGE_BACKEND_DOWNLOADS[downloadState.backendId]; + const installed = backend && + fs.existsSync(path.join(backend.destDir, backend.exeName)) && + fs.existsSync(path.join(backend.destDir, backend.requiredDll)); + if (installed && !downloadState.error) { + cachedBackendOptions = null; + downloadState = { + active: false, + filename: "", + progress: 0, + speed: "0 MB/s", + eta: 0, + totalBytes: 0, + downloadedBytes: 0, + error: null, + }; + } + } return json(res, 200, downloadState); } diff --git a/scripts/setup/setup-coreml-npu.sh b/scripts/setup/setup-coreml-npu.sh index f6dcccf..7358811 100644 --- a/scripts/setup/setup-coreml-npu.sh +++ b/scripts/setup/setup-coreml-npu.sh @@ -11,6 +11,37 @@ ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" VENV_DIR="$ROOT_DIR/app/backend/mac/coreml_venv" PYTHON_BIN="$VENV_DIR/bin/python" +is_coreml_python_supported() { + "$1" - <<'PY' >/dev/null 2>&1 +import sys +raise SystemExit(0 if (3, 9) <= sys.version_info[:2] < (3, 12) else 1) +PY +} + +find_coreml_python() { + local candidate + if [[ -n "${COREML_SETUP_PYTHON:-}" ]]; then + if is_coreml_python_supported "$COREML_SETUP_PYTHON"; then + echo "$COREML_SETUP_PYTHON" + return 0 + fi + return 1 + fi + + for candidate in python3.11 python3.10 python3.9 python3; do + if command -v "$candidate" >/dev/null 2>&1 && is_coreml_python_supported "$candidate"; then + command -v "$candidate" + return 0 + fi + done + + return 1 +} + +coreml_venv_ready() { + [[ -x "$PYTHON_BIN" ]] && "$PYTHON_BIN" -c "import sys; assert (3, 9) <= sys.version_info[:2] < (3, 12); import python_coreml_stable_diffusion, diffusers, transformers" >/dev/null 2>&1 +} + echo "" echo " ============================================================" echo " Local AI Image Generator - Apple Silicon CoreML NPU Setup" @@ -27,14 +58,22 @@ if [[ "$(uname -m)" != "arm64" ]]; then exit 1 fi -if ! command -v python3 >/dev/null 2>&1; then - echo " [ERROR] python3 is required. Install Python 3 first (via Homebrew or Xcode)." >&2 +SETUP_PYTHON="$(find_coreml_python || true)" +if [[ -z "$SETUP_PYTHON" ]]; then + echo " [ERROR] CoreML setup requires Python 3.9, 3.10, or 3.11." >&2 + echo " Apple's ml-stable-diffusion dependency stack is not compatible with Python 3.12+." >&2 + echo " Install python@3.11 with Homebrew, or set COREML_SETUP_PYTHON to a supported Python." >&2 exit 1 fi +if [[ -x "$PYTHON_BIN" ]] && ! coreml_venv_ready; then + echo " Existing CoreML environment is incomplete or uses Python 3.12+. Recreating it..." + rm -rf "$VENV_DIR" +fi + if [[ ! -x "$PYTHON_BIN" ]]; then echo " Creating Python environment: $VENV_DIR" - if ! python3 -m venv "$VENV_DIR"; then + if ! "$SETUP_PYTHON" -m venv "$VENV_DIR"; then echo " [ERROR] Could not create the virtual environment." exit 1 fi @@ -42,7 +81,7 @@ fi echo " Installing CoreML Stable Diffusion dependencies (numpy, coremltools, diffusers)..." "$PYTHON_BIN" -m pip install --upgrade pip -"$PYTHON_BIN" -m pip install numpy coremltools diffusers transformers huggingface-hub pillow +"$PYTHON_BIN" -m pip install "numpy<1.24" coremltools diffusers transformers huggingface-hub pillow echo " Installing Apple's python-coreml-stable-diffusion package..." if ! "$PYTHON_BIN" -m pip install "git+https://github.com/apple/ml-stable-diffusion.git"; then diff --git a/scripts/setup/setup-openvino-npu.ps1 b/scripts/setup/setup-openvino-npu.ps1 index ad6d6b1..348db27 100644 --- a/scripts/setup/setup-openvino-npu.ps1 +++ b/scripts/setup/setup-openvino-npu.ps1 @@ -1,16 +1,105 @@ # Optional OpenVINO NPU setup for Intel AI Boost systems. -# This does not modify the existing stable-diffusion.cpp backends. +# Installs a portable Python runtime under app/tools/python-win. $ErrorActionPreference = "Stop" $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir) -$venvDir = Join-Path $rootDir "app\tools\openvino-venv-win" -$pythonExe = Join-Path $venvDir "Scripts\python.exe" +$appDir = Join-Path $rootDir "app" +$toolsDir = Join-Path $appDir "tools" +$pythonDir = Join-Path $toolsDir "python-win" +$pythonExe = Join-Path $pythonDir "python.exe" +$pythonZip = Join-Path $toolsDir "python-win.zip" +$getPip = Join-Path $toolsDir "get-pip.py" + +$pythonVersion = "3.11.9" +$pythonZipUrl = "https://www.python.org/ftp/python/$pythonVersion/python-$pythonVersion-embed-amd64.zip" +$getPipUrl = "https://bootstrap.pypa.io/get-pip.py" + +function Enable-Tls12 { + try { + $tls12 = [Enum]::ToObject([Net.SecurityProtocolType], 3072) + [Net.ServicePointManager]::SecurityProtocol = + [Net.ServicePointManager]::SecurityProtocol -bor $tls12 + } catch {} +} + +function Download-File { + param([string]$Url, [string]$Dest, [string]$Label) + Write-Host " Downloading $Label..." + Enable-Tls12 + New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Dest) | Out-Null + $client = New-Object System.Net.WebClient + $client.Headers.Add("User-Agent", "Local-AI-Image-Generator-Setup") + $client.DownloadFile($Url, $Dest) +} + +function Expand-Zip { + param([string]$ZipPath, [string]$Destination) + if (Test-Path $Destination) { + Remove-Item $Destination -Recurse -Force -ErrorAction SilentlyContinue + } + New-Item -ItemType Directory -Force -Path $Destination | Out-Null + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($ZipPath, $Destination) +} + +function Enable-EmbeddedPythonSitePackages { + $pth = Get-ChildItem $pythonDir -Filter "python*._pth" | Select-Object -First 1 + if (-not $pth) { throw "Embedded Python ._pth file not found in $pythonDir" } + + $content = Get-Content $pth.FullName + $hasSitePackages = $content | Where-Object { $_.Trim() -eq "Lib\site-packages" } | Select-Object -First 1 + $addedSitePackages = $false + $next = foreach ($line in $content) { + if (-not $hasSitePackages -and -not $addedSitePackages -and $line.Trim() -eq "import site") { + "Lib\site-packages" + $addedSitePackages = $true + } + if ($line.Trim() -eq "#import site") { + if (-not $hasSitePackages -and -not $addedSitePackages) { + "Lib\site-packages" + $addedSitePackages = $true + } + "import site" + } else { + $line + } + } + Set-Content -Path $pth.FullName -Value $next -Encoding ASCII +} + +function Ensure-PortablePython { + if (Test-Path $pythonExe) { + try { + & $pythonExe -c "import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 11) else 1)" + if ($LASTEXITCODE -eq 0) { + Write-Host " Portable Python already ready: $pythonExe" + return + } + } catch {} + + Write-Host " Existing portable Python is invalid. Reinstalling..." + Remove-Item $pythonDir -Recurse -Force -ErrorAction SilentlyContinue + } + + Download-File -Url $pythonZipUrl -Dest $pythonZip -Label "Portable Python $pythonVersion" + Expand-Zip -ZipPath $pythonZip -Destination $pythonDir + Remove-Item $pythonZip -Force -ErrorAction SilentlyContinue + Enable-EmbeddedPythonSitePackages + + Download-File -Url $getPipUrl -Dest $getPip -Label "pip bootstrap" + & $pythonExe $getPip --no-warn-script-location + if ($LASTEXITCODE -ne 0) { throw "pip bootstrap failed for portable Python." } + Remove-Item $getPip -Force -ErrorAction SilentlyContinue + + & $pythonExe -m pip --version | Out-Host +} Write-Host "" Write-Host " ============================================================" Write-Host " Local AI Image Generator - OpenVINO NPU Setup" +Write-Host " Portable Python runtime: app/tools/python-win" Write-Host " ============================================================" Write-Host "" @@ -25,22 +114,23 @@ if (-not $npu) { Write-Host " Detected: $($npu.Name)" -if (-not (Test-Path $pythonExe)) { - Write-Host " Creating Python environment: $venvDir" - python -m venv $venvDir -} +Ensure-PortablePython -Write-Host " Installing OpenVINO GenAI runtime..." -& $pythonExe -m pip install --upgrade pip -& $pythonExe -m pip install openvino openvino-genai pillow huggingface-hub +Write-Host " Installing OpenVINO GenAI runtime into portable Python..." +& $pythonExe -m pip install --upgrade pip --no-warn-script-location --progress-bar off +if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed." } +& $pythonExe -m pip install openvino openvino-genai pillow huggingface-hub --no-warn-script-location --progress-bar off +if ($LASTEXITCODE -ne 0) { throw "OpenVINO package install failed." } Write-Host "" Write-Host " Verifying OpenVINO NPU device..." & $pythonExe -c "import openvino as ov; core=ov.Core(); print('Available devices:', core.available_devices); raise SystemExit(0 if 'NPU' in core.available_devices else 2)" +if ($LASTEXITCODE -ne 0) { throw "OpenVINO installed, but NPU was not available to the portable runtime." } Write-Host "" Write-Host " ============================================================" Write-Host " OpenVINO NPU setup complete." +Write-Host " Portable Python: $pythonExe" Write-Host " Restart windows.bat, then download an OpenVINO NPU model." Write-Host " ============================================================" Write-Host "" diff --git a/scripts/setup/setup-tts.ps1 b/scripts/setup/setup-tts.ps1 index fc003e3..9c840da 100644 --- a/scripts/setup/setup-tts.ps1 +++ b/scripts/setup/setup-tts.ps1 @@ -39,7 +39,7 @@ $oldPath = $env:PATH try { $env:PATH = "$nodeDir;$env:PATH" Print-Info "Installing kokoro-js into app/tts-runtime..." - & $npmCmd install --prefer-offline 2>&1 + & $npmCmd install --prefer-offline --loglevel=error if ($LASTEXITCODE -ne 0) { Print-Fail "kokoro-js install failed." exit 1 diff --git a/scripts/setup/setup.ps1 b/scripts/setup/setup.ps1 index 70321b4..e6f1a5a 100644 --- a/scripts/setup/setup.ps1 +++ b/scripts/setup/setup.ps1 @@ -158,7 +158,7 @@ function Expand-WithProgress { # ══════════════════════════════════════════════════════════════════════════════ Print-Header -$steps = 7 +$steps = 8 # ── Step 1: Portable Node.js ────────────────────────────────────────────────── Print-Step 1 $steps "Setting up portable Node.js (app/tools/node-win/)" @@ -242,6 +242,56 @@ if (-not $hasNvidia) { } catch {} } +Print-Step 2 $steps "Setting up stable-diffusion.cpp CPU backend (app/backend/win/cpu/)" +$cpuBackendDest = Join-Path $appDir "backend\win\cpu" +$cpuBackendExe = Join-Path $cpuBackendDest "sd-cpu.exe" +$cpuBackendDll = Join-Path $cpuBackendDest "stable-diffusion.dll" + +if ((Test-Path $cpuBackendExe) -and (Test-Path $cpuBackendDll)) { + Print-OK "CPU backend binaries already ready." +} else { + $cpuBackendZip = Join-Path $toolsDir "sd-cpu.zip" + New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null + New-Item -ItemType Directory -Force -Path $cpuBackendDest | Out-Null + + $ok = Invoke-RichDownload ` + -Url "https://github.com/leejet/stable-diffusion.cpp/releases/download/master-669-2d40a8b/sd-master-2d40a8b-bin-win-avx2-x64.zip" ` + -Dest $cpuBackendZip ` + -Label "stable-diffusion.cpp CPU Backend (Windows x64 AVX2)" + + if (-not $ok) { Print-Fail "Cannot download CPU backend binaries."; Read-Host; exit 1 } + + $tempExt = Join-Path $toolsDir "sd-cpu-temp" + Expand-WithProgress -ZipPath $cpuBackendZip -Destination $tempExt -Label "CPU Backend" + Remove-Item $cpuBackendZip -Force + + if (Test-Path $tempExt) { + $extractedExe = Join-Path $tempExt "bin\sd-server.exe" + if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd-server.exe" } + if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "bin\sd.exe" } + if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd.exe" } + + $extractedDll = Join-Path $tempExt "bin\stable-diffusion.dll" + if (-not (Test-Path $extractedDll)) { $extractedDll = Join-Path $tempExt "stable-diffusion.dll" } + + if (Test-Path $extractedExe) { Copy-Item $extractedExe $cpuBackendExe -Force } + if (Test-Path $extractedDll) { Copy-Item $extractedDll $cpuBackendDll -Force } + + Get-ChildItem $tempExt -Filter "*.dll" -Recurse | ForEach-Object { Copy-Item $_.FullName $cpuBackendDest -Force } + Get-ChildItem $tempExt -Filter "*.exe" -Recurse | ForEach-Object { + if ($_.FullName -ne $extractedExe) { Copy-Item $_.FullName $cpuBackendDest -Force } + } + Remove-Item $tempExt -Recurse -Force + } + + if ((Test-Path $cpuBackendExe) -and (Test-Path $cpuBackendDll)) { + Print-OK "CPU backend binaries installed successfully!" + } else { + Print-Fail "Failed to copy backend binaries to app/backend/win/cpu/." + Read-Host; exit 1 + } +} + if ($hasNvidia) { Print-Step 2 $steps "Setting up stable-diffusion.cpp CUDA GPU backend (app/backend/win/cuda/)" $backendDest = Join-Path $appDir "backend\win\cuda" @@ -441,7 +491,22 @@ if (-not $?) { Read-Host; exit 1 } -Print-Step 6 $steps "Installing frontend dependencies (app/frontend/)" +$npu = Get-CimInstance Win32_PnPEntity -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like "*Intel(R) AI Boost*" -or ($_.Name -match "NPU" -and $_.PNPClass -eq "ComputeAccelerator") } | + Select-Object -First 1 + +Print-Step 6 $steps "Setting up portable OpenVINO NPU runtime" +if ($npu) { + & (Join-Path $scriptDir "setup-openvino-npu.ps1") + if (-not $?) { + Print-Fail "OpenVINO NPU setup failed." + Read-Host; exit 1 + } +} else { + Print-Info "Intel AI Boost NPU not detected. Skipping OpenVINO NPU runtime." +} + +Print-Step 7 $steps "Installing frontend dependencies (app/frontend/)" Write-Host "" if (-not (Test-Path $npmCmd)) { @@ -486,7 +551,7 @@ Push-Location $frontendDir $oldPath = $env:PATH try { $env:PATH = "$nodeDir;$env:PATH" - & $npmCmd install --prefer-offline 2>&1 + & $npmCmd install --prefer-offline --loglevel=error if ($LASTEXITCODE -ne 0) { Print-Fail "npm install failed." Read-Host; exit 1 @@ -495,10 +560,10 @@ try { Print-OK "Dependencies installed!" # ── Step 4: Build frontend ──────────────────────────────────────────────── - Print-Step 7 $steps "Building frontend -> app/dist/" + Print-Step 8 $steps "Building frontend -> app/dist/" Write-Host "" - & $npmCmd run build 2>&1 + & $npmCmd run build if ($LASTEXITCODE -ne 0) { Print-Fail "Frontend build failed." Read-Host; exit 1 diff --git a/scripts/workers/coreml_server.py b/scripts/workers/coreml_server.py new file mode 100644 index 0000000..60be525 --- /dev/null +++ b/scripts/workers/coreml_server.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +import argparse +import base64 +import json +import os +import shutil +import subprocess +import sys +import tempfile +import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + + +MODEL_VERSION_BY_NAME = { + "coreml-stable-diffusion-v1-5": "runwayml/stable-diffusion-v1-5", + "stable-diffusion-v1-5": "runwayml/stable-diffusion-v1-5", + "stable-diffusion-v1-4": "CompVis/stable-diffusion-v1-4", + "cyberrealistic": "runwayml/stable-diffusion-v1-5", +} + + +def json_response(handler, code, payload): + body = json.dumps(payload).encode("utf-8") + handler.send_response(code) + handler.send_header("Content-Type", "application/json") + handler.send_header("Access-Control-Allow-Origin", "*") + handler.send_header("Content-Length", str(len(body))) + handler.end_headers() + handler.wfile.write(body) + + +def find_coreml_resource_dir(model_path: Path) -> Path: + candidates = [ + model_path, + model_path / "Resources", + model_path / "compiled", + model_path / "packages", + model_path / "split_einsum_v2" / "compiled", + model_path / "split_einsum" / "compiled", + model_path / "original" / "compiled", + model_path / "split_einsum_v2" / "packages", + model_path / "split_einsum" / "packages", + model_path / "original" / "packages", + ] + + required_names = { + "text_encoder.mlmodelc", + "text_encoder.mlpackage", + "textencoder.mlmodelc", + "textencoder.mlpackage", + } + for candidate in candidates: + if not candidate.is_dir(): + continue + names = {entry.name.lower() for entry in candidate.iterdir()} + has_text_encoder = bool(names & required_names) + has_unet = "unet.mlmodelc" in names or "unet.mlpackage" in names + if has_text_encoder and has_unet: + return candidate + + for candidate in model_path.rglob("*"): + if not candidate.is_dir(): + continue + names = {entry.name.lower() for entry in candidate.iterdir()} + has_text_encoder = bool(names & required_names) + has_unet = "unet.mlmodelc" in names or "unet.mlpackage" in names + if has_text_encoder and has_unet: + return candidate + + raise FileNotFoundError( + f"Could not find CoreML resources under {model_path}. " + "Expected a folder containing text_encoder and unet .mlmodelc/.mlpackage files." + ) + + +def infer_model_version(model_path: Path) -> str: + override = os.environ.get("COREML_MODEL_VERSION") + if override: + return override + lower = model_path.name.lower() + for needle, version in MODEL_VERSION_BY_NAME.items(): + if needle in lower: + return version + return "runwayml/stable-diffusion-v1-5" + + +def infer_model_sources(resource_dir: Path) -> str: + names = {entry.name.lower() for entry in resource_dir.iterdir()} + if any(name.endswith(".mlmodelc") for name in names): + return "compiled" + return "packages" + + +def latest_png(root: Path) -> Path: + images = sorted(root.rglob("*.png"), key=lambda p: p.stat().st_mtime, reverse=True) + if not images: + raise FileNotFoundError("CoreML generation finished but did not write a PNG output.") + return images[0] + + +class CoreMLServerState: + def __init__(self, model: Path, steps: int, cfg_scale: float): + self.model = model + self.resources = find_coreml_resource_dir(model) + self.model_version = infer_model_version(model) + self.steps = steps + self.cfg_scale = cfg_scale + self.started_at = time.time() + + +def make_handler(state: CoreMLServerState): + class Handler(BaseHTTPRequestHandler): + def do_OPTIONS(self): + self.send_response(204) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.send_header("Access-Control-Allow-Methods", "GET,POST,OPTIONS") + self.end_headers() + + def do_GET(self): + if self.path == "/health": + json_response(self, 200, { + "ok": True, + "ready": True, + "model": str(state.model), + "resources": str(state.resources), + "model_version": state.model_version, + "uptime_sec": round(time.time() - state.started_at, 1), + }) + return + if self.path == "/v1/models": + json_response(self, 200, { + "object": "list", + "data": [{ + "id": state.model.name, + "object": "model", + "owned_by": "local-coreml", + }], + }) + return + json_response(self, 404, {"ok": False, "error": "Unknown endpoint"}) + + def do_POST(self): + if self.path != "/v1/images/generations": + json_response(self, 404, {"ok": False, "error": "Unknown endpoint"}) + return + + try: + length = int(self.headers.get("Content-Length", "0") or "0") + payload = json.loads(self.rfile.read(length).decode("utf-8") or "{}") + prompt = str(payload.get("prompt") or "").strip() + if not prompt: + raise ValueError("Prompt is required") + + size = str(payload.get("size") or "512x512").lower().split("x") + if len(size) == 2 and size != ["512", "512"]: + raise ValueError("CoreML models in this app currently generate at their compiled resolution, normally 512x512.") + + steps = max(1, int(payload.get("steps") or state.steps or 30)) + guidance = float(payload.get("cfg_scale") or state.cfg_scale or 7.0) + seed = int(payload.get("seed")) if payload.get("seed") not in (None, -1) else int(time.time_ns() % (2 ** 32)) + negative_prompt = str(payload.get("negative_prompt") or "").strip() + + started = time.time() + out_dir = Path(tempfile.mkdtemp(prefix="portable-diffusion-coreml-")) + cmd = [ + sys.executable, + "-m", + "python_coreml_stable_diffusion.pipeline", + "--prompt", + prompt, + "-i", + str(state.resources), + "-o", + str(out_dir), + "--compute-unit", + os.environ.get("COREML_COMPUTE_UNIT", "CPU_AND_NE"), + "--seed", + str(seed), + "--model-version", + state.model_version, + "--num-inference-steps", + str(steps), + "--guidance-scale", + str(guidance), + "--model-sources", + infer_model_sources(state.resources), + ] + if negative_prompt: + cmd.extend(["--negative-prompt", negative_prompt]) + + print("[coreml-npu] generating image", flush=True) + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + ) + output_lines = [] + assert proc.stdout is not None + for line in proc.stdout: + output_lines.append(line.rstrip()) + print(line, end="", flush=True) + code = proc.wait() + if code != 0: + raise RuntimeError("CoreML generation failed with code %s:\n%s" % (code, "\n".join(output_lines[-30:]))) + + image_path = latest_png(out_dir) + encoded = base64.b64encode(image_path.read_bytes()).decode("ascii") + shutil.rmtree(out_dir, ignore_errors=True) + print("[coreml-npu] decoding complete", flush=True) + json_response(self, 200, { + "created": int(time.time()), + "data": [{ + "b64_json": encoded, + "seed": seed, + }], + "duration_sec": round(time.time() - started, 2), + }) + except Exception as exc: + json_response(self, 500, {"ok": False, "error": str(exc)}) + + def log_message(self, fmt, *args): + print("[coreml-npu-http] " + fmt % args, flush=True) + + return Handler + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--listen-port", required=True, type=int) + parser.add_argument("--model", required=True, type=Path) + parser.add_argument("--steps", type=int, default=30) + parser.add_argument("--cfg-scale", type=float, default=7.0) + args = parser.parse_args() + + state = CoreMLServerState(args.model, args.steps, args.cfg_scale) + print(f"[coreml-npu] Model: {state.model}", flush=True) + print(f"[coreml-npu] Resources: {state.resources}", flush=True) + print(f"[coreml-npu] Model version: {state.model_version}", flush=True) + print("| 1/1 - CoreML resources ready", flush=True) + server = ThreadingHTTPServer(("127.0.0.1", args.listen_port), make_handler(state)) + print(f"[coreml-npu] listening on http://127.0.0.1:{args.listen_port}", flush=True) + server.serve_forever() + + +if __name__ == "__main__": + main()