implement modular backend setup scripts, new frontend components, and expanded server services
This commit is contained in:
1 parent
bd89699994
commit
ffec37fd0a
27 files changed
+309
-149
No files matched your search
@@ -73,24 +73,24 @@ The normal platform setup installs both image and text backends. Text and image
|
||||
## 📁 Repository Structure
|
||||
```
|
||||
local-ai-image-generator/
|
||||
├── windows.bat # Main double-click entrypoint (Windows)
|
||||
├── linux.sh # Main terminal entrypoint (Linux)
|
||||
├── mac.sh # Main terminal entrypoint (macOS)
|
||||
├── PLAN.md # Linux port implementation plan
|
||||
├── LICENSE # MIT Open Source license
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
├── scripts/
|
||||
│ ├── setup.ps1 # Automated GPU-detect and environment installer (Windows)
|
||||
│ ├── setup.sh # Automated GPU-detect and environment installer (Linux/macOS)
|
||||
│ ├── reset.ps1 # Cleans runtime environments (Windows)
|
||||
│ ├── reset.sh # Cleans runtime environments (Linux/macOS)
|
||||
│ └── serve.cjs # UI web server and backend lifecycle manager
|
||||
└── app/
|
||||
├── frontend/ # UI source code (Vite + React)
|
||||
├── models/ # Place weights here (.safetensors, .gguf, .ckpt)
|
||||
└── outputs/ # Saved images and parameters metadata
|
||||
```
|
||||
|-- windows.bat # Main double-click entrypoint (Windows)
|
||||
|-- linux.sh # Main terminal entrypoint (Linux)
|
||||
|-- mac.sh # Main terminal entrypoint (macOS)
|
||||
|-- PLAN.md # Linux port implementation plan
|
||||
|-- LICENSE # MIT Open Source license
|
||||
|-- .gitignore
|
||||
|-- README.md
|
||||
|-- scripts/
|
||||
| |-- setup/ # Platform setup and backend installers
|
||||
| |-- reset/ # Runtime cleanup scripts
|
||||
| |-- server/ # UI server and backend lifecycle manager
|
||||
| |-- workers/ # Local worker processes
|
||||
| |-- build/ # Optional source build helpers
|
||||
| `-- config/ # Runtime configuration catalogs
|
||||
`-- app/
|
||||
|-- frontend/ # UI source code (Vite + React)
|
||||
|-- models/ # Place weights here (.safetensors, .gguf, .ckpt)
|
||||
`-- outputs/ # Saved images and parameters metadata
|
||||
|
||||
---
|
||||
|
||||
@@ -157,9 +157,9 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution
|
||||
---
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset.ps1` (Windows) or `scripts/reset.sh` (Linux/macOS). (This preserves your models and generated images).
|
||||
* **Port Conflicts:** The frontend uses `1420` by default. The backend tries `8080` first, then automatically falls back to a free port if `8080` is already busy.
|
||||
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset/reset.ps1` (Windows) or `scripts/reset/reset.sh` (Linux/macOS). (This preserves your models and generated images).
|
||||
* **Linux backends fail to start with `GLIBC_2.38' not found`:** The prebuilt binaries require glibc 2.38+ (Ubuntu 24.04). Upgrade your distribution or build stable-diffusion.cpp from source (see below).
|
||||
* **Port Conflicts:** The frontend uses `1420` by default. The backend tries `8080` first, then automatically falls back to a free port if `8080` is already busy.
|
||||
* **Linux ROCm not loading:** Make sure your AMD GPU and kernel are compatible with ROCm 7.13. The app will automatically fall back to Vulkan if ROCm cannot initialize.
|
||||
* **Windows exits with code `3221225781`:** This is `0xC0000135`, which means Windows could not load a required backend DLL. For AMD/Intel Vulkan, update the GPU driver with Vulkan support, then rerun setup so `app/backend/win/vulkan/` is repaired. For NVIDIA CUDA, update the NVIDIA driver and rerun setup so CUDA runtime DLLs are restored.
|
||||
* **Generation shows \"server is not responding or crashed\":** The backend process exited. Check the terminal where you ran `./linux.sh` or `./mac.sh` for the exact error (common causes are glibc mismatch, missing Vulkan drivers, or out-of-memory).
|
||||
@@ -168,9 +168,9 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution
|
||||
|
||||
## 🔨 Building Linux Backends From Source
|
||||
|
||||
The setup script (`scripts/setup.sh`) now automates building and setting up the CUDA backend from source when selected. If you want to manually build all backends (CPU, Vulkan, and CUDA) at once, you can run the included `scripts/build_from_source.sh` script.
|
||||
The setup script (`scripts/setup/setup.sh`) now automates building and setting up the CUDA backend from source when selected. If you want to manually build all backends (CPU, Vulkan, and CUDA) at once, you can run the included `scripts/build/build_from_source.sh` script.
|
||||
|
||||
For macOS, the included `scripts/build_from_source.sh` builds the Metal backend and copies it to `app/backend/mac/sd`.
|
||||
For macOS, the included `scripts/build/build_from_source.sh` builds the Metal backend and copies it to `app/backend/mac/sd`.
|
||||
|
||||
### Requirements
|
||||
- `git`, `cmake`, `make` (or `ninja`), and a C++17 compiler (`g++` / `clang++`).
|
||||
@@ -210,8 +210,7 @@ cmake --build . --config Release -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sy
|
||||
cp bin/sd* /path/to/Local-AI-Image-Generator/app/backend/linux/<backend>/
|
||||
```
|
||||
|
||||
After copying, rename the server binary to match what `scripts/serve.cjs` expects:
|
||||
- CPU: `sd` → `sd-cpu`
|
||||
After copying, rename the server binary to match what `scripts/server/serve.cjs` expects:
|
||||
- Vulkan: `sd` → `sd-vulkan`
|
||||
- ROCm: `sd` → `sd-rocm`
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ function App() {
|
||||
return {
|
||||
language: parsed.language || "auto",
|
||||
threads: Math.max(1, Math.min(32, Number(parsed.threads) || Math.max(1, Math.min(8, navigator.hardwareConcurrency || 4)))),
|
||||
backendPreference: ["auto", "vulkan", "metal", "cpu"].includes(parsed.backendPreference) ? parsed.backendPreference : "auto",
|
||||
translate: parsed.translate === true,
|
||||
};
|
||||
} catch (_) {}
|
||||
@@ -176,6 +177,7 @@ function App() {
|
||||
return {
|
||||
language: "auto",
|
||||
threads: Math.max(1, Math.min(8, navigator.hardwareConcurrency || 4)),
|
||||
backendPreference: "auto",
|
||||
translate: false,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -964,7 +964,7 @@ function Settings({
|
||||
<SectionHeader
|
||||
icon={Volume2}
|
||||
title="Speech Transcription"
|
||||
count={3}
|
||||
count={4}
|
||||
color="#10b981"
|
||||
isExpanded={expandedSections.speech}
|
||||
onToggle={() => toggleSection("speech")}
|
||||
@@ -982,6 +982,23 @@ function Settings({
|
||||
Transcription Settings
|
||||
</div>
|
||||
<div className="m3-field-group">
|
||||
<div className="m3-text-field">
|
||||
<label className="m3-text-field-label">Default Backend</label>
|
||||
<select
|
||||
className="m3-input"
|
||||
value={speechSettings.backendPreference || "auto"}
|
||||
onChange={(e) => updateSpeechSetting("backendPreference", e.target.value)}
|
||||
>
|
||||
<option value="auto">Auto - GPU if installed</option>
|
||||
<option value="vulkan">Vulkan GPU</option>
|
||||
<option value="metal">Metal GPU</option>
|
||||
<option value="cpu">CPU</option>
|
||||
</select>
|
||||
<span className="settings-option-desc" style={{ marginTop: "4px", display: "block" }}>
|
||||
Auto uses a GPU whisper.cpp backend when its binary exists, then falls back to CPU.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="m3-text-field">
|
||||
<label className="m3-text-field-label">Default Language</label>
|
||||
<select
|
||||
|
||||
@@ -145,10 +145,12 @@ export default function SpeechTranscriber({
|
||||
|
||||
const language = speechSettings?.language || "auto";
|
||||
const threads = speechSettings?.threads || 4;
|
||||
const backendPreference = speechSettings?.backendPreference || "auto";
|
||||
const translate = speechSettings?.translate === true;
|
||||
|
||||
const setLanguage = (val) => setSpeechSettings((prev) => ({ ...prev, language: val }));
|
||||
const setThreads = (val) => setSpeechSettings((prev) => ({ ...prev, threads: val }));
|
||||
const setBackendPreference = (val) => setSpeechSettings((prev) => ({ ...prev, backendPreference: val }));
|
||||
|
||||
const [audioBlob, setAudioBlob] = useState(null);
|
||||
const [audioName, setAudioName] = useState("");
|
||||
@@ -163,6 +165,9 @@ export default function SpeechTranscriber({
|
||||
const abortRef = useRef(null);
|
||||
|
||||
const installedModels = useMemo(() => models.filter((model) => model.installed), [models]);
|
||||
const isSelectedRuntimeLoaded = status.ready &&
|
||||
status.settings?.model === selectedModel &&
|
||||
(status.settings?.backendPreference || "auto") === backendPreference;
|
||||
|
||||
const isEnglishOnly = useMemo(() => {
|
||||
if (!selectedModel) return false;
|
||||
@@ -237,8 +242,8 @@ export default function SpeechTranscriber({
|
||||
if (!selectedModel) return;
|
||||
setIsLoadingModel(true);
|
||||
try {
|
||||
if (!status.ready || status.settings?.model !== selectedModel) {
|
||||
await startSpeech(selectedModel, { language, threads });
|
||||
if (!isSelectedRuntimeLoaded) {
|
||||
await startSpeech(selectedModel, { language, threads, backendPreference });
|
||||
}
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
@@ -300,10 +305,10 @@ export default function SpeechTranscriber({
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
try {
|
||||
if (!status.ready || status.settings?.model !== selectedModel) {
|
||||
if (!isSelectedRuntimeLoaded) {
|
||||
setIsLoadingModel(true);
|
||||
try {
|
||||
await startSpeech(selectedModel, { language, threads });
|
||||
await startSpeech(selectedModel, { language, threads, backendPreference });
|
||||
await refresh();
|
||||
} finally {
|
||||
setIsLoadingModel(false);
|
||||
@@ -313,6 +318,7 @@ export default function SpeechTranscriber({
|
||||
model: selectedModel,
|
||||
language,
|
||||
threads,
|
||||
backendPreference,
|
||||
translate,
|
||||
filename: audioName || "audio.wav",
|
||||
signal: controller.signal,
|
||||
@@ -386,6 +392,17 @@ export default function SpeechTranscriber({
|
||||
</label>
|
||||
|
||||
<div className="speech-controls-row">
|
||||
<label className="speech-label">
|
||||
Backend
|
||||
<select className="m3-input" value={backendPreference} onChange={(event) => setBackendPreference(event.target.value)}>
|
||||
<option value="auto">Auto (GPU if installed)</option>
|
||||
{(status.backends || []).map((backend) => (
|
||||
<option key={backend.key} value={backend.key}>
|
||||
{backend.label}{backend.installed ? " - installed" : " - missing"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="speech-label">
|
||||
Language
|
||||
<select className="m3-input" value={language} onChange={(event) => setLanguage(event.target.value)}>
|
||||
@@ -420,10 +437,10 @@ export default function SpeechTranscriber({
|
||||
<button
|
||||
className="m3-btn m3-btn-filled"
|
||||
onClick={handleLoadSpeechModel}
|
||||
disabled={!selectedModel || isLoadingModel || isTranscribing || (status.ready && status.settings?.model === selectedModel)}
|
||||
disabled={!selectedModel || isLoadingModel || isTranscribing || isSelectedRuntimeLoaded}
|
||||
>
|
||||
{isLoadingModel ? <LoaderCircle className="progress-spinner" size={14} /> : <Play size={14} />}
|
||||
<span>{status.ready && status.settings?.model === selectedModel ? "Loaded" : isLoadingModel ? "Loading" : "Load"}</span>
|
||||
<span>{isSelectedRuntimeLoaded ? "Loaded" : isLoadingModel ? "Loading" : "Load"}</span>
|
||||
</button>
|
||||
<button className="m3-btn m3-btn-outlined" onClick={refresh}>
|
||||
<Play size={14} />
|
||||
|
||||
@@ -593,6 +593,7 @@ export async function startSpeech(model, options = {}) {
|
||||
model,
|
||||
language: options.language || "auto",
|
||||
threads: options.threads,
|
||||
backendPreference: options.backendPreference || "auto",
|
||||
}),
|
||||
});
|
||||
return await readJsonResponse(res, "The local server returned an invalid speech start response.");
|
||||
@@ -609,6 +610,7 @@ export async function transcribeSpeech(fileOrBlob, options = {}) {
|
||||
if (options.language) params.set("language", options.language);
|
||||
if (options.filename) params.set("filename", options.filename);
|
||||
if (options.threads) params.set("threads", String(options.threads));
|
||||
if (options.backendPreference) params.set("backendPreference", options.backendPreference);
|
||||
if (options.translate) params.set("translate", "true");
|
||||
|
||||
const res = await fetch(`/api/speech/transcribe?${params.toString()}`, {
|
||||
|
||||
@@ -23,8 +23,8 @@ CPU_BACKEND_PATH="$APP_DIR/backend/linux/cpu/sd-cpu"
|
||||
PLATFORM_LABEL="Linux"
|
||||
|
||||
DIST_INDEX="$APP_DIR/dist/index.html"
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup.sh"
|
||||
SERVE_SCRIPT="$SCRIPT_DIR/scripts/serve.cjs"
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup/setup.sh"
|
||||
SERVE_SCRIPT="$SCRIPT_DIR/scripts/server/serve.cjs"
|
||||
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-1420}"
|
||||
LLM_PORT="${LLM_PORT:-10086}"
|
||||
@@ -51,7 +51,7 @@ for arg in "$@"; do
|
||||
done
|
||||
|
||||
if [[ $SETUP_OPENVINO -eq 1 ]]; then
|
||||
bash "$SCRIPT_DIR/scripts/setup-openvino-npu.sh"
|
||||
bash "$SCRIPT_DIR/scripts/setup/setup-openvino-npu.sh"
|
||||
fi
|
||||
|
||||
# ── Setup node_modules to avoid OS conflicts ────────────────────────────────
|
||||
@@ -133,7 +133,7 @@ LLM_ROCM_PATH="$APP_DIR/llm-backend/linux/rocm/llama-server"
|
||||
LLM_SYCL_PATH="$APP_DIR/llm-backend/linux/sycl/llama-server"
|
||||
LLM_VULKAN_PATH="$APP_DIR/llm-backend/linux/vulkan/llama-server"
|
||||
LLM_CPU_PATH="$APP_DIR/llm-backend/linux/cpu/llama-server"
|
||||
SPEECH_BACKEND_PATH="$APP_DIR/speech-backend/linux/whisper-cli"
|
||||
SPEECH_BACKEND_PATH="$APP_DIR/speech-backend/linux/cpu/whisper-cli"
|
||||
TTS_RUNTIME_PATH="$APP_DIR/tts-runtime/node_modules/kokoro-js"
|
||||
if [[ ! -x "$CPU_BACKEND_PATH" || ! -x "$CPU_SERVER_PATH" ]] && [[ ! -x "$BACKEND_PATH" || ! -x "$VULKAN_SERVER_PATH" ]]; then
|
||||
SETUP_REASON="Linux backend binaries are missing or not executable."
|
||||
|
||||
@@ -27,8 +27,8 @@ TTS_RUNTIME_PATH="$APP_DIR/tts-runtime/node_modules/kokoro-js"
|
||||
PLATFORM_LABEL="macOS"
|
||||
|
||||
DIST_INDEX="$APP_DIR/dist/index.html"
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup.sh"
|
||||
SERVE_SCRIPT="$SCRIPT_DIR/scripts/serve.cjs"
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup/setup.sh"
|
||||
SERVE_SCRIPT="$SCRIPT_DIR/scripts/server/serve.cjs"
|
||||
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-1420}"
|
||||
LLM_PORT="${LLM_PORT:-10086}"
|
||||
|
||||
Executable → Regular
+1
-1
@@ -7,7 +7,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
PLATFORM="$(uname -s)"
|
||||
if [ "$PLATFORM" = "Darwin" ]; then
|
||||
File renamed without changes.
@@ -1,8 +1,8 @@
|
||||
# scripts/reset.ps1
|
||||
# scripts/reset/reset.ps1
|
||||
# Resets portable app dependencies/builds while preserving user models and outputs.
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent $scriptDir
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
|
||||
Write-Host ""
|
||||
Executable → Regular
+1
-1
@@ -7,7 +7,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
|
||||
echo ""
|
||||
@@ -38,7 +38,7 @@ let PORT_SPEECH = PREFERRED_SPEECH_PORT;
|
||||
let PORT_TTS = PREFERRED_TTS_PORT;
|
||||
const MAX_JSON_BODY_BYTES = 64 * 1024 * 1024;
|
||||
const SERVER_BUILD = "text-image-v1";
|
||||
const ROOT = path.join(__dirname, "..");
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const DIST = path.join(ROOT, "app", "dist");
|
||||
const osPlatform = process.platform;
|
||||
const BACKEND_PATHS = {
|
||||
@@ -130,12 +130,25 @@ if (!fs.existsSync(TTS_CACHE)) {
|
||||
fs.mkdirSync(TTS_CACHE, { recursive: true });
|
||||
}
|
||||
const TTS_RUNTIME = path.join(ROOT, "app", "tts-runtime");
|
||||
const TTS_WORKER = path.join(ROOT, "scripts", "tts-kokoro-worker.mjs");
|
||||
const TTS_WORKER = path.join(ROOT, "scripts", "workers", "tts-kokoro-worker.mjs");
|
||||
const SPEECH_BACKEND_ROOT = path.join(ROOT, "app", "speech-backend");
|
||||
const SPEECH_BACKEND_PATHS = {
|
||||
winVulkanCli: path.join(SPEECH_BACKEND_ROOT, "win", "vulkan", "whisper-cli.exe"),
|
||||
winVulkanServer: path.join(SPEECH_BACKEND_ROOT, "win", "vulkan", "whisper-server.exe"),
|
||||
winCpuCli: path.join(SPEECH_BACKEND_ROOT, "win", "cpu", "whisper-cli.exe"),
|
||||
winCpuServer: path.join(SPEECH_BACKEND_ROOT, "win", "cpu", "whisper-server.exe"),
|
||||
winCli: path.join(ROOT, "app", "speech-backend", "win", "whisper-cli.exe"),
|
||||
winServer: path.join(ROOT, "app", "speech-backend", "win", "whisper-server.exe"),
|
||||
linuxVulkanCli: path.join(SPEECH_BACKEND_ROOT, "linux", "vulkan", "whisper-cli"),
|
||||
linuxVulkanServer: path.join(SPEECH_BACKEND_ROOT, "linux", "vulkan", "whisper-server"),
|
||||
linuxCpuCli: path.join(SPEECH_BACKEND_ROOT, "linux", "cpu", "whisper-cli"),
|
||||
linuxCpuServer: path.join(SPEECH_BACKEND_ROOT, "linux", "cpu", "whisper-server"),
|
||||
linuxCli: path.join(ROOT, "app", "speech-backend", "linux", "whisper-cli"),
|
||||
linuxServer: path.join(ROOT, "app", "speech-backend", "linux", "whisper-server"),
|
||||
macMetalCli: path.join(SPEECH_BACKEND_ROOT, "mac", "metal", "whisper-cli"),
|
||||
macMetalServer: path.join(SPEECH_BACKEND_ROOT, "mac", "metal", "whisper-server"),
|
||||
macCpuCli: path.join(SPEECH_BACKEND_ROOT, "mac", "cpu", "whisper-cli"),
|
||||
macCpuServer: path.join(SPEECH_BACKEND_ROOT, "mac", "cpu", "whisper-server"),
|
||||
macCli: path.join(ROOT, "app", "speech-backend", "mac", "whisper-cli"),
|
||||
macServer: path.join(ROOT, "app", "speech-backend", "mac", "whisper-server"),
|
||||
};
|
||||
@@ -227,6 +240,7 @@ let speechSettings = {
|
||||
model: null,
|
||||
language: "auto",
|
||||
threads: Math.max(1, Math.min(8, os.cpus().length || 4)),
|
||||
backendPreference: "auto",
|
||||
backendBinary: "",
|
||||
backendMode: "",
|
||||
};
|
||||
@@ -1391,8 +1405,8 @@ function getOpenVinoNpuInfo() {
|
||||
const python = getOpenVinoPython();
|
||||
if (!python) {
|
||||
const setupScript = osPlatform === "win32"
|
||||
? "scripts/setup-openvino-npu.ps1"
|
||||
: "bash scripts/setup-openvino-npu.sh";
|
||||
? "scripts/setup/setup-openvino-npu.ps1"
|
||||
: "bash scripts/setup/setup-openvino-npu.sh";
|
||||
cachedOpenVinoNpuInfo = {
|
||||
supported: false,
|
||||
reason: `OpenVINO GenAI runtime is not installed. Run ${setupScript} first.`,
|
||||
@@ -1669,25 +1683,86 @@ function findExistingFile(candidates) {
|
||||
return candidates.find((candidate) => candidate && fs.existsSync(candidate)) || "";
|
||||
}
|
||||
|
||||
function getSpeechBackend() {
|
||||
function getSpeechBackendCandidates() {
|
||||
if (osPlatform === "win32") {
|
||||
return {
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.winCli, path.join(ROOT, "app", "speech-backend", "win", "main.exe")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.winServer, path.join(ROOT, "app", "speech-backend", "win", "server.exe")]),
|
||||
mode: "whisper.cpp CPU",
|
||||
};
|
||||
return [
|
||||
{
|
||||
key: "vulkan",
|
||||
label: "Vulkan GPU",
|
||||
mode: "whisper.cpp Vulkan GPU",
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.winVulkanCli, path.join(SPEECH_BACKEND_ROOT, "win", "vulkan", "main.exe")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.winVulkanServer, path.join(SPEECH_BACKEND_ROOT, "win", "vulkan", "server.exe")]),
|
||||
pathHint: path.join(SPEECH_BACKEND_ROOT, "win", "vulkan"),
|
||||
},
|
||||
{
|
||||
key: "cpu",
|
||||
label: "CPU",
|
||||
mode: "whisper.cpp CPU",
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.winCpuCli, SPEECH_BACKEND_PATHS.winCli, path.join(SPEECH_BACKEND_ROOT, "win", "cpu", "main.exe"), path.join(SPEECH_BACKEND_ROOT, "win", "main.exe")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.winCpuServer, SPEECH_BACKEND_PATHS.winServer, path.join(SPEECH_BACKEND_ROOT, "win", "cpu", "server.exe"), path.join(SPEECH_BACKEND_ROOT, "win", "server.exe")]),
|
||||
pathHint: path.join(SPEECH_BACKEND_ROOT, "win", "cpu"),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (osPlatform === "darwin") {
|
||||
return {
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.macCli, path.join(ROOT, "app", "speech-backend", "mac", "main")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.macServer, path.join(ROOT, "app", "speech-backend", "mac", "server")]),
|
||||
mode: "whisper.cpp Metal/CPU",
|
||||
};
|
||||
return [
|
||||
{
|
||||
key: "metal",
|
||||
label: "Metal GPU",
|
||||
mode: "whisper.cpp Metal GPU",
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.macMetalCli, path.join(SPEECH_BACKEND_ROOT, "mac", "metal", "main")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.macMetalServer, path.join(SPEECH_BACKEND_ROOT, "mac", "metal", "server")]),
|
||||
pathHint: path.join(SPEECH_BACKEND_ROOT, "mac", "metal"),
|
||||
},
|
||||
{
|
||||
key: "cpu",
|
||||
label: "CPU",
|
||||
mode: "whisper.cpp CPU",
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.macCpuCli, SPEECH_BACKEND_PATHS.macCli, path.join(SPEECH_BACKEND_ROOT, "mac", "cpu", "main"), path.join(SPEECH_BACKEND_ROOT, "mac", "main")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.macCpuServer, SPEECH_BACKEND_PATHS.macServer, path.join(SPEECH_BACKEND_ROOT, "mac", "cpu", "server"), path.join(SPEECH_BACKEND_ROOT, "mac", "server")]),
|
||||
pathHint: path.join(SPEECH_BACKEND_ROOT, "mac", "cpu"),
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: "vulkan",
|
||||
label: "Vulkan GPU",
|
||||
mode: "whisper.cpp Vulkan GPU",
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.linuxVulkanCli, path.join(SPEECH_BACKEND_ROOT, "linux", "vulkan", "main")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.linuxVulkanServer, path.join(SPEECH_BACKEND_ROOT, "linux", "vulkan", "server")]),
|
||||
pathHint: path.join(SPEECH_BACKEND_ROOT, "linux", "vulkan"),
|
||||
},
|
||||
{
|
||||
key: "cpu",
|
||||
label: "CPU",
|
||||
mode: "whisper.cpp CPU",
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.linuxCpuCli, SPEECH_BACKEND_PATHS.linuxCli, path.join(SPEECH_BACKEND_ROOT, "linux", "cpu", "main"), path.join(SPEECH_BACKEND_ROOT, "linux", "main")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.linuxCpuServer, SPEECH_BACKEND_PATHS.linuxServer, path.join(SPEECH_BACKEND_ROOT, "linux", "cpu", "server"), path.join(SPEECH_BACKEND_ROOT, "linux", "server")]),
|
||||
pathHint: path.join(SPEECH_BACKEND_ROOT, "linux", "cpu"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeSpeechBackendPreference(value) {
|
||||
const raw = String(value || "auto").trim().toLowerCase();
|
||||
if (["auto", "cpu", "vulkan", "metal"].includes(raw)) return raw;
|
||||
return "auto";
|
||||
}
|
||||
|
||||
function getSpeechBackend(preference = speechSettings.backendPreference) {
|
||||
const candidates = getSpeechBackendCandidates().map((candidate) => ({
|
||||
...candidate,
|
||||
installed: Boolean(candidate.cli),
|
||||
}));
|
||||
const preferred = normalizeSpeechBackendPreference(preference);
|
||||
const selected = preferred === "auto"
|
||||
? candidates.find((candidate) => candidate.installed) || candidates[candidates.length - 1]
|
||||
: candidates.find((candidate) => candidate.key === preferred) || candidates.find((candidate) => candidate.installed) || candidates[candidates.length - 1];
|
||||
return {
|
||||
cli: findExistingFile([SPEECH_BACKEND_PATHS.linuxCli, path.join(ROOT, "app", "speech-backend", "linux", "main")]),
|
||||
server: findExistingFile([SPEECH_BACKEND_PATHS.linuxServer, path.join(ROOT, "app", "speech-backend", "linux", "server")]),
|
||||
mode: "whisper.cpp CPU",
|
||||
...(selected || { key: "cpu", label: "CPU", mode: "whisper.cpp CPU", cli: "", server: "" }),
|
||||
preference: preferred,
|
||||
candidates,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1846,7 +1921,7 @@ function runExclusiveTtsOperation(operation) {
|
||||
async function startTts(settings = {}) {
|
||||
const runtime = getTtsRuntimeStatus();
|
||||
if (!runtime.installed) {
|
||||
throw new Error("Kokoro TTS runtime is not installed. Run scripts/setup-tts for this platform.");
|
||||
throw new Error("Kokoro TTS runtime is not installed. Run scripts/setup/setup-tts for this platform.");
|
||||
}
|
||||
const model = resolveTtsModel(settings.model || ttsSettings.model);
|
||||
await killBackend();
|
||||
@@ -2041,9 +2116,10 @@ function runExclusiveSpeechOperation(operation) {
|
||||
}
|
||||
|
||||
async function startSpeech(settings = {}) {
|
||||
const backend = getSpeechBackend();
|
||||
const backendPreference = normalizeSpeechBackendPreference(settings.backendPreference || speechSettings.backendPreference);
|
||||
const backend = getSpeechBackend(backendPreference);
|
||||
if (!backend.cli) {
|
||||
throw new Error("whisper.cpp is not installed. Run the platform setup script to install the speech backend.");
|
||||
throw new Error(`${backend.label || "Selected"} whisper.cpp backend is not installed. Run setup or copy a compatible binary to ${backend.pathHint || "app/speech-backend"}.`);
|
||||
}
|
||||
const model = resolveSpeechModel(settings.model);
|
||||
await killBackend();
|
||||
@@ -2057,6 +2133,7 @@ async function startSpeech(settings = {}) {
|
||||
model: model.filename,
|
||||
language: settings.language || speechSettings.language || "auto",
|
||||
threads: Math.max(1, Math.min(32, Number(settings.threads) || speechSettings.threads || 4)),
|
||||
backendPreference,
|
||||
backendBinary: path.basename(backend.cli),
|
||||
backendMode: backend.mode,
|
||||
};
|
||||
@@ -2146,9 +2223,10 @@ function transcribeWavBuffer(buffer, options = {}) {
|
||||
reject(new Error("Speech transcription currently accepts WAV audio only."));
|
||||
return;
|
||||
}
|
||||
const backend = getSpeechBackend();
|
||||
const backendPreference = normalizeSpeechBackendPreference(options.backendPreference || speechSettings.backendPreference);
|
||||
const backend = getSpeechBackend(backendPreference);
|
||||
if (!backend.cli) {
|
||||
reject(new Error("whisper.cpp CLI backend is not installed."));
|
||||
reject(new Error(`${backend.label || "Selected"} whisper.cpp CLI backend is not installed.`));
|
||||
return;
|
||||
}
|
||||
const model = resolveSpeechModel(options.model || speechSettings.model);
|
||||
@@ -2192,6 +2270,7 @@ function transcribeWavBuffer(buffer, options = {}) {
|
||||
windowsHide: true,
|
||||
env: {
|
||||
...process.env,
|
||||
...(osPlatform === "win32" ? { PATH: path.dirname(backend.cli) + (process.env.PATH ? `;${process.env.PATH}` : "") } : {}),
|
||||
...(osPlatform === "linux" ? { LD_LIBRARY_PATH: path.dirname(backend.cli) + (process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : "") } : {}),
|
||||
...(osPlatform === "darwin" ? { DYLD_LIBRARY_PATH: path.dirname(backend.cli) + (process.env.DYLD_LIBRARY_PATH ? `:${process.env.DYLD_LIBRARY_PATH}` : "") } : {}),
|
||||
},
|
||||
@@ -2229,6 +2308,8 @@ function transcribeWavBuffer(buffer, options = {}) {
|
||||
model: model.filename,
|
||||
modelName: model.name,
|
||||
language,
|
||||
backend: backend.key,
|
||||
backendMode: backend.mode,
|
||||
sourceFilename,
|
||||
durationMs: Date.now() - startedAt,
|
||||
raw,
|
||||
@@ -2656,7 +2737,7 @@ function getCoreMLNpuInfo() {
|
||||
return {
|
||||
supported: true,
|
||||
ready: false,
|
||||
reason: "CoreML Python environment is not set up. Run scripts/setup-coreml-npu.sh first.",
|
||||
reason: "CoreML Python environment is not set up. Run scripts/setup/setup-coreml-npu.sh first.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3307,7 +3388,7 @@ async function startOpenVinoWorker(settings = {}) {
|
||||
device: currentSettings.backendDevice,
|
||||
};
|
||||
|
||||
const workerPath = path.join(ROOT, "scripts", "openvino_npu_worker.py");
|
||||
const workerPath = path.join(ROOT, "scripts", "workers", "openvino_npu_worker.py");
|
||||
const cacheDir = path.join(ROOT, "app", "tools", "openvino-cache", "512x512");
|
||||
console.log(` [openvino-npu] Starting 512x512 worker on port ${openvinoPort}`);
|
||||
openvinoProc = spawn(npuInfo.python, [
|
||||
@@ -5030,6 +5111,8 @@ const server = http.createServer(async (req, res) => {
|
||||
backendPath: backend.cli || "",
|
||||
serverPath: backend.server || "",
|
||||
backendMode: backend.mode,
|
||||
backendPreference: backend.preference,
|
||||
backends: backend.candidates,
|
||||
error: speechError,
|
||||
settings: speechSettings,
|
||||
transcription: speechTranscriptionState,
|
||||
@@ -5066,6 +5149,7 @@ const server = http.createServer(async (req, res) => {
|
||||
language: parsed.searchParams.get("language") || "auto",
|
||||
filename: parsed.searchParams.get("filename") || req.headers["x-filename"] || "recording.wav",
|
||||
threads: parsed.searchParams.get("threads") || speechSettings.threads,
|
||||
backendPreference: parsed.searchParams.get("backendPreference") || speechSettings.backendPreference,
|
||||
translate: parsed.searchParams.get("translate") === "true",
|
||||
}));
|
||||
return json(res, 200, { ok: true, transcription: result });
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
RELEASE="${WHISPER_RELEASE:-v1.9.1}"
|
||||
PLATFORM="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
download_and_extract() {
|
||||
local asset="$1"
|
||||
local dest="$2"
|
||||
local archive="$TOOLS_DIR/$asset"
|
||||
local url="https://github.com/ggml-org/whisper.cpp/releases/download/$RELEASE/$asset"
|
||||
|
||||
if [[ -x "$dest/whisper-cli" ]]; then
|
||||
echo " OK whisper.cpp speech backend already ready: $dest"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$TOOLS_DIR" "$dest"
|
||||
rm -f "$archive" "$archive.part"
|
||||
echo " >> Downloading $asset"
|
||||
curl -fSL --progress-bar "$url" -o "$archive.part"
|
||||
mv "$archive.part" "$archive"
|
||||
tar -xzf "$archive" -C "$dest" --strip-components=1
|
||||
rm -f "$archive"
|
||||
chmod +x "$dest"/whisper-* "$dest"/main "$dest"/server 2>/dev/null || true
|
||||
|
||||
if [[ ! -x "$dest/whisper-cli" && -x "$dest/main" ]]; then
|
||||
cp "$dest/main" "$dest/whisper-cli"
|
||||
chmod +x "$dest/whisper-cli"
|
||||
fi
|
||||
if [[ ! -x "$dest/whisper-server" && -x "$dest/server" ]]; then
|
||||
cp "$dest/server" "$dest/whisper-server"
|
||||
chmod +x "$dest/whisper-server"
|
||||
fi
|
||||
|
||||
if [[ ! -x "$dest/whisper-cli" ]]; then
|
||||
echo " XX whisper-cli was not found after extracting $asset" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
|
||||
download_and_extract "whisper-bin-ubuntu-arm64.tar.gz" "$APP_DIR/speech-backend/linux"
|
||||
else
|
||||
download_and_extract "whisper-bin-ubuntu-x64.tar.gz" "$APP_DIR/speech-backend/linux"
|
||||
fi
|
||||
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
mkdir -p "$APP_DIR/speech-backend/mac"
|
||||
if [[ -x "$APP_DIR/speech-backend/mac/whisper-cli" ]]; then
|
||||
echo " OK whisper.cpp macOS speech backend already ready."
|
||||
else
|
||||
echo " !! No official portable macOS whisper.cpp CLI archive is available for this setup script yet."
|
||||
echo " Build whisper.cpp manually and copy whisper-cli to app/speech-backend/mac/whisper-cli."
|
||||
fi
|
||||
else
|
||||
echo "Unsupported platform: $PLATFORM" >&2
|
||||
exit 1
|
||||
fi
|
||||
Executable → Regular
+1
-1
@@ -7,7 +7,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
VENV_DIR="$ROOT_DIR/app/backend/mac/coreml_venv"
|
||||
PYTHON_BIN="$VENV_DIR/bin/python"
|
||||
|
||||
@@ -2,7 +2,7 @@ param([string]$Release = "b9668")
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent $scriptDir
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$llmRoot = Join-Path $appDir "llm-backend\win"
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
RELEASE="${LLAMA_RELEASE:-b9668}"
|
||||
@@ -4,7 +4,7 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent $scriptDir
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$venvDir = Join-Path $rootDir "app\tools\openvino-venv-win"
|
||||
$pythonExe = Join-Path $venvDir "Scripts\python.exe"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
VENV_DIR="$ROOT_DIR/app/tools/openvino-venv-linux"
|
||||
PYTHON_BIN="$VENV_DIR/bin/python"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Local AI Studio - Kokoro TTS setup for Windows
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent $scriptDir
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$nodeDir = Join-Path $toolsDir "node-win"
|
||||
@@ -25,7 +25,7 @@ Write-Host ""
|
||||
New-Item -ItemType Directory -Force -Path $runtimeDir, $modelsDir, $outputsDir, $cacheDir | Out-Null
|
||||
|
||||
if (-not (Test-Path $nodeExe) -or -not (Test-Path $npmCmd)) {
|
||||
Print-Fail "Portable Node.js is missing. Run scripts/setup.ps1 first."
|
||||
Print-Fail "Portable Node.js is missing. Run scripts/setup/setup.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
PLATFORM="$(uname -s)"
|
||||
@@ -37,7 +37,7 @@ echo ""
|
||||
mkdir -p "$RUNTIME_DIR" "$MODELS_DIR" "$OUTPUTS_DIR" "$CACHE_DIR"
|
||||
|
||||
if [[ ! -x "$NODE_BIN" || ! -x "$NPM_BIN" ]]; then
|
||||
print_fail "Portable Node.js is missing. Run scripts/setup.sh first."
|
||||
print_fail "Portable Node.js is missing. Run scripts/setup/setup.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -2,10 +2,12 @@ param([string]$Release = "v1.9.1")
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent $scriptDir
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
$speechRoot = Join-Path $appDir "speech-backend\win"
|
||||
$speechWinRoot = Join-Path $appDir "speech-backend\win"
|
||||
$speechRoot = Join-Path $speechWinRoot "cpu"
|
||||
$speechVulkanRoot = Join-Path $speechWinRoot "vulkan"
|
||||
|
||||
function Enable-Tls12 {
|
||||
try {
|
||||
@@ -41,11 +43,29 @@ function Find-FirstFile {
|
||||
return $null
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir, $speechRoot | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $toolsDir, $speechRoot, $speechVulkanRoot | Out-Null
|
||||
|
||||
$cliPath = Join-Path $speechRoot "whisper-cli.exe"
|
||||
if (Test-Path $cliPath) {
|
||||
Write-Host " OK whisper.cpp speech backend already ready."
|
||||
if (-not (Test-Path (Join-Path $speechVulkanRoot "whisper-cli.exe"))) {
|
||||
Write-Host " .. Optional Vulkan GPU backend slot: app/speech-backend/win/vulkan"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
$legacyCli = Join-Path $speechWinRoot "whisper-cli.exe"
|
||||
$legacyServer = Join-Path $speechWinRoot "whisper-server.exe"
|
||||
if (Test-Path $legacyCli) {
|
||||
Copy-Item $legacyCli $cliPath -Force
|
||||
if (Test-Path $legacyServer) {
|
||||
Copy-Item $legacyServer (Join-Path $speechRoot "whisper-server.exe") -Force
|
||||
}
|
||||
Get-ChildItem $speechWinRoot -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -notin @("whisper-cli.exe", "whisper-server.exe") } |
|
||||
ForEach-Object { Copy-Item $_.FullName (Join-Path $speechRoot $_.Name) -Force }
|
||||
Write-Host " OK migrated existing whisper.cpp CPU backend to app/speech-backend/win/cpu."
|
||||
Write-Host " .. For GPU transcription, place a Vulkan-enabled whisper.cpp build in app/speech-backend/win/vulkan."
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -81,3 +101,5 @@ if (-not (Test-Path $cliPath)) {
|
||||
throw "whisper-cli.exe was not installed."
|
||||
}
|
||||
Write-Host " OK whisper.cpp speech backend installed."
|
||||
Write-Host " .. CPU backend path: app/speech-backend/win/cpu"
|
||||
Write-Host " .. Optional Vulkan GPU backend path: app/speech-backend/win/vulkan"
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
RELEASE="${WHISPER_RELEASE:-v1.9.1}"
|
||||
PLATFORM="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
download_and_extract() {
|
||||
local asset="$1"
|
||||
local dest="$2"
|
||||
local archive="$TOOLS_DIR/$asset"
|
||||
local url="https://github.com/ggml-org/whisper.cpp/releases/download/$RELEASE/$asset"
|
||||
|
||||
if [[ -x "$dest/whisper-cli" ]]; then
|
||||
echo " OK whisper.cpp speech backend already ready: $dest"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$TOOLS_DIR" "$dest"
|
||||
rm -f "$archive" "$archive.part"
|
||||
echo " >> Downloading $asset"
|
||||
curl -fSL --progress-bar "$url" -o "$archive.part"
|
||||
mv "$archive.part" "$archive"
|
||||
tar -xzf "$archive" -C "$dest" --strip-components=1
|
||||
rm -f "$archive"
|
||||
chmod +x "$dest"/whisper-* "$dest"/main "$dest"/server 2>/dev/null || true
|
||||
|
||||
if [[ ! -x "$dest/whisper-cli" && -x "$dest/main" ]]; then
|
||||
cp "$dest/main" "$dest/whisper-cli"
|
||||
chmod +x "$dest/whisper-cli"
|
||||
fi
|
||||
if [[ ! -x "$dest/whisper-server" && -x "$dest/server" ]]; then
|
||||
cp "$dest/server" "$dest/whisper-server"
|
||||
chmod +x "$dest/whisper-server"
|
||||
fi
|
||||
|
||||
if [[ ! -x "$dest/whisper-cli" ]]; then
|
||||
echo " XX whisper-cli was not found after extracting $asset" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||
mkdir -p "$APP_DIR/speech-backend/linux/cpu" "$APP_DIR/speech-backend/linux/vulkan"
|
||||
if [[ ! -x "$APP_DIR/speech-backend/linux/cpu/whisper-cli" && -x "$APP_DIR/speech-backend/linux/whisper-cli" ]]; then
|
||||
cp "$APP_DIR/speech-backend/linux"/whisper-* "$APP_DIR/speech-backend/linux/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/linux"/main "$APP_DIR/speech-backend/linux/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/linux"/server "$APP_DIR/speech-backend/linux/cpu/" 2>/dev/null || true
|
||||
chmod +x "$APP_DIR/speech-backend/linux/cpu"/whisper-* "$APP_DIR/speech-backend/linux/cpu"/main "$APP_DIR/speech-backend/linux/cpu"/server 2>/dev/null || true
|
||||
echo " OK migrated existing whisper.cpp CPU backend to app/speech-backend/linux/cpu."
|
||||
fi
|
||||
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
|
||||
download_and_extract "whisper-bin-ubuntu-arm64.tar.gz" "$APP_DIR/speech-backend/linux/cpu"
|
||||
else
|
||||
download_and_extract "whisper-bin-ubuntu-x64.tar.gz" "$APP_DIR/speech-backend/linux/cpu"
|
||||
fi
|
||||
echo " .. CPU backend path: app/speech-backend/linux/cpu"
|
||||
echo " .. Optional Vulkan GPU backend path: app/speech-backend/linux/vulkan"
|
||||
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
mkdir -p "$APP_DIR/speech-backend/mac/cpu" "$APP_DIR/speech-backend/mac/metal"
|
||||
if [[ ! -x "$APP_DIR/speech-backend/mac/cpu/whisper-cli" && -x "$APP_DIR/speech-backend/mac/whisper-cli" ]]; then
|
||||
cp "$APP_DIR/speech-backend/mac"/whisper-* "$APP_DIR/speech-backend/mac/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/mac"/main "$APP_DIR/speech-backend/mac/cpu/" 2>/dev/null || true
|
||||
cp "$APP_DIR/speech-backend/mac"/server "$APP_DIR/speech-backend/mac/cpu/" 2>/dev/null || true
|
||||
chmod +x "$APP_DIR/speech-backend/mac/cpu"/whisper-* "$APP_DIR/speech-backend/mac/cpu"/main "$APP_DIR/speech-backend/mac/cpu"/server 2>/dev/null || true
|
||||
echo " OK migrated existing whisper.cpp backend to app/speech-backend/mac/cpu."
|
||||
fi
|
||||
if [[ -x "$APP_DIR/speech-backend/mac/cpu/whisper-cli" || -x "$APP_DIR/speech-backend/mac/metal/whisper-cli" ]]; then
|
||||
echo " OK whisper.cpp macOS speech backend already ready."
|
||||
else
|
||||
echo " !! No official portable macOS whisper.cpp CLI archive is available for this setup script yet."
|
||||
echo " Build whisper.cpp manually and copy whisper-cli to app/speech-backend/mac/cpu or app/speech-backend/mac/metal."
|
||||
fi
|
||||
else
|
||||
echo "Unsupported platform: $PLATFORM" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,8 +1,8 @@
|
||||
# Local AI Image Generator - Setup Script
|
||||
# scripts/ lives at root, app/ is a sibling folder
|
||||
# scripts/setup/ lives under root, app/ is a root sibling of scripts/
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$rootDir = Split-Path -Parent $scriptDir
|
||||
$rootDir = Split-Path -Parent (Split-Path -Parent $scriptDir)
|
||||
$appDir = Join-Path $rootDir "app"
|
||||
$frontendDir = Join-Path $appDir "frontend"
|
||||
$toolsDir = Join-Path $appDir "tools"
|
||||
Executable → Regular
+2
-2
@@ -7,7 +7,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
FRONTEND_DIR="$APP_DIR/frontend"
|
||||
TOOLS_DIR="$APP_DIR/tools"
|
||||
@@ -680,6 +680,6 @@ echo ""
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" && $MAX_PERF -eq 0 ]] && [[ "$VENDOR" == "nvidia" || "$VENDOR" == "amd" ]]; then
|
||||
echo " Tip: For maximum GPU performance, re-run with:"
|
||||
echo " ./scripts/setup.sh --max-perf"
|
||||
echo " ./scripts/setup/setup.sh --max-perf"
|
||||
echo ""
|
||||
fi
|
||||
File renamed without changes.
File renamed without changes.
+3
-3
@@ -6,7 +6,7 @@ set APP=%~dp0app
|
||||
set NODE=%APP%\tools\node-win\node.exe
|
||||
set NPM=%APP%\tools\node-win\npm.cmd
|
||||
set DIST=%APP%\dist\index.html
|
||||
set SETUP=%~dp0scripts\setup.ps1
|
||||
set SETUP=%~dp0scripts\setup\setup.ps1
|
||||
set CUDA_BACKEND=%APP%\backend\win\cuda\sd-cuda.exe
|
||||
set VULKAN_BACKEND=%APP%\backend\win\vulkan\sd-vulkan.exe
|
||||
set LLM_CUDA_BACKEND=%APP%\llm-backend\win\cuda\llama-server.exe
|
||||
@@ -14,9 +14,9 @@ set LLM_HIP_BACKEND=%APP%\llm-backend\win\hip\llama-server.exe
|
||||
set LLM_VULKAN_BACKEND=%APP%\llm-backend\win\vulkan\llama-server.exe
|
||||
set LLM_SYCL_BACKEND=%APP%\llm-backend\win\sycl\llama-server.exe
|
||||
set LLM_CPU_BACKEND=%APP%\llm-backend\win\cpu\llama-server.exe
|
||||
set SPEECH_BACKEND=%APP%\speech-backend\win\whisper-cli.exe
|
||||
set SPEECH_BACKEND=%APP%\speech-backend\win\cpu\whisper-cli.exe
|
||||
set TTS_RUNTIME=%APP%\tts-runtime\node_modules\kokoro-js
|
||||
set SERVE=%~dp0scripts\serve.cjs
|
||||
set SERVE=%~dp0scripts\server\serve.cjs
|
||||
if "%FRONTEND_PORT%"=="" set FRONTEND_PORT=1420
|
||||
if "%LLM_PORT%"=="" set LLM_PORT=10086
|
||||
set SETUP_REASON=
|
||||
|
||||
Reference in new issue
Block a user