mirror of
https://github.com/mudler/LocalAI.git
synced 2026-02-03 11:13:31 -05:00
Compare commits
1 Commits
test/ci
...
workaround
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59343860ef |
91
.github/workflows/backend.yml
vendored
91
.github/workflows/backend.yml
vendored
@@ -105,19 +105,6 @@ jobs:
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'cublas'
|
||||
cuda-major-version: "12"
|
||||
cuda-minor-version: "9"
|
||||
platforms: 'linux/amd64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-gpu-nvidia-cuda-12-pocket-tts'
|
||||
runs-on: 'ubuntu-latest'
|
||||
base-image: "ubuntu:24.04"
|
||||
skip-drivers: 'false'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'cublas'
|
||||
cuda-major-version: "12"
|
||||
cuda-minor-version: "0"
|
||||
@@ -353,19 +340,6 @@ jobs:
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'cublas'
|
||||
cuda-major-version: "13"
|
||||
cuda-minor-version: "0"
|
||||
platforms: 'linux/amd64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-gpu-nvidia-cuda-13-pocket-tts'
|
||||
runs-on: 'ubuntu-latest'
|
||||
base-image: "ubuntu:24.04"
|
||||
skip-drivers: 'false'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'cublas'
|
||||
cuda-major-version: "13"
|
||||
cuda-minor-version: "0"
|
||||
@@ -431,19 +405,6 @@ jobs:
|
||||
backend: "vibevoice"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
- build-type: 'l4t'
|
||||
cuda-major-version: "13"
|
||||
cuda-minor-version: "0"
|
||||
platforms: 'linux/arm64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-nvidia-l4t-cuda-13-arm64-pocket-tts'
|
||||
runs-on: 'ubuntu-24.04-arm'
|
||||
base-image: "ubuntu:24.04"
|
||||
skip-drivers: 'false'
|
||||
ubuntu-version: '2404'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
- build-type: 'l4t'
|
||||
cuda-major-version: "13"
|
||||
cuda-minor-version: "0"
|
||||
@@ -680,19 +641,6 @@ jobs:
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'hipblas'
|
||||
cuda-major-version: ""
|
||||
cuda-minor-version: ""
|
||||
platforms: 'linux/amd64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-gpu-rocm-hipblas-pocket-tts'
|
||||
runs-on: 'arc-runner-set'
|
||||
base-image: "rocm/dev-ubuntu-24.04:6.4.4"
|
||||
skip-drivers: 'false'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'hipblas'
|
||||
cuda-major-version: ""
|
||||
cuda-minor-version: ""
|
||||
@@ -824,19 +772,6 @@ jobs:
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2204'
|
||||
- build-type: 'l4t'
|
||||
cuda-major-version: "12"
|
||||
cuda-minor-version: "0"
|
||||
platforms: 'linux/arm64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-nvidia-l4t-pocket-tts'
|
||||
runs-on: 'ubuntu-24.04-arm'
|
||||
base-image: "nvcr.io/nvidia/l4t-jetpack:r36.4.0"
|
||||
skip-drivers: 'true'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2204'
|
||||
- build-type: 'l4t'
|
||||
cuda-major-version: "12"
|
||||
cuda-minor-version: "0"
|
||||
@@ -890,19 +825,6 @@ jobs:
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'intel'
|
||||
cuda-major-version: ""
|
||||
cuda-minor-version: ""
|
||||
platforms: 'linux/amd64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-gpu-intel-pocket-tts'
|
||||
runs-on: 'arc-runner-set'
|
||||
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||
skip-drivers: 'false'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'intel'
|
||||
cuda-major-version: ""
|
||||
cuda-minor-version: ""
|
||||
@@ -1356,19 +1278,6 @@ jobs:
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: ''
|
||||
cuda-major-version: ""
|
||||
cuda-minor-version: ""
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
tag-latest: 'auto'
|
||||
tag-suffix: '-cpu-pocket-tts'
|
||||
runs-on: 'ubuntu-latest'
|
||||
base-image: "ubuntu:24.04"
|
||||
skip-drivers: 'false'
|
||||
backend: "pocket-tts"
|
||||
dockerfile: "./backend/Dockerfile.python"
|
||||
context: "./"
|
||||
ubuntu-version: '2404'
|
||||
backend-jobs-darwin:
|
||||
uses: ./.github/workflows/backend_build_darwin.yml
|
||||
strategy:
|
||||
|
||||
10
.github/workflows/image-pr.yml
vendored
10
.github/workflows/image-pr.yml
vendored
@@ -41,7 +41,7 @@
|
||||
platforms: 'linux/amd64'
|
||||
tag-latest: 'false'
|
||||
tag-suffix: '-gpu-nvidia-cuda-12'
|
||||
runs-on: 'bigger-runner'
|
||||
runs-on: 'ubuntu-latest'
|
||||
base-image: "ubuntu:24.04"
|
||||
makeflags: "--jobs=3 --output-sync=target"
|
||||
ubuntu-version: '2404'
|
||||
@@ -51,7 +51,7 @@
|
||||
platforms: 'linux/amd64'
|
||||
tag-latest: 'false'
|
||||
tag-suffix: '-gpu-nvidia-cuda-13'
|
||||
runs-on: 'bigger-runner'
|
||||
runs-on: 'ubuntu-latest'
|
||||
base-image: "ubuntu:22.04"
|
||||
makeflags: "--jobs=3 --output-sync=target"
|
||||
ubuntu-version: '2404'
|
||||
@@ -61,7 +61,7 @@
|
||||
tag-suffix: '-hipblas'
|
||||
base-image: "rocm/dev-ubuntu-24.04:6.4.4"
|
||||
grpc-base-image: "ubuntu:24.04"
|
||||
runs-on: 'bigger-runner'
|
||||
runs-on: 'ubuntu-latest'
|
||||
makeflags: "--jobs=3 --output-sync=target"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'sycl'
|
||||
@@ -70,14 +70,14 @@
|
||||
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||
grpc-base-image: "ubuntu:24.04"
|
||||
tag-suffix: 'sycl'
|
||||
runs-on: 'bigger-runner'
|
||||
runs-on: 'ubuntu-latest'
|
||||
makeflags: "--jobs=3 --output-sync=target"
|
||||
ubuntu-version: '2404'
|
||||
- build-type: 'vulkan'
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
tag-latest: 'false'
|
||||
tag-suffix: '-vulkan-core'
|
||||
runs-on: 'bigger-runner'
|
||||
runs-on: 'ubuntu-latest'
|
||||
base-image: "ubuntu:24.04"
|
||||
makeflags: "--jobs=4 --output-sync=target"
|
||||
ubuntu-version: '2404'
|
||||
|
||||
21
.github/workflows/test-extra.yml
vendored
21
.github/workflows/test-extra.yml
vendored
@@ -265,23 +265,4 @@ jobs:
|
||||
- name: Test moonshine
|
||||
run: |
|
||||
make --jobs=5 --output-sync=target -C backend/python/moonshine
|
||||
make --jobs=5 --output-sync=target -C backend/python/moonshine test
|
||||
tests-pocket-tts:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- name: Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential ffmpeg
|
||||
sudo apt-get install -y ca-certificates cmake curl patch python3-pip
|
||||
# Install UV
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
pip install --user --no-cache-dir grpcio-tools==1.64.1
|
||||
- name: Test pocket-tts
|
||||
run: |
|
||||
make --jobs=5 --output-sync=target -C backend/python/pocket-tts
|
||||
make --jobs=5 --output-sync=target -C backend/python/pocket-tts test
|
||||
make --jobs=5 --output-sync=target -C backend/python/moonshine test
|
||||
27
Dockerfile
27
Dockerfile
@@ -16,7 +16,7 @@ RUN apt-get update && \
|
||||
|
||||
# The requirements-drivers target is for BUILD_TYPE specific items. If you need to install something specific to CUDA, or specific to ROCM, it goes here.
|
||||
FROM requirements AS requirements-drivers
|
||||
|
||||
ARG VULKAN_FROM_SOURCE=false
|
||||
ARG BUILD_TYPE
|
||||
ARG CUDA_MAJOR_VERSION=12
|
||||
ARG CUDA_MINOR_VERSION=0
|
||||
@@ -41,24 +41,29 @@ RUN <<EOT bash
|
||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils mesa-vulkan-drivers
|
||||
if [ "amd64" = "$TARGETARCH" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
if [ "amd64" = "$TARGETARCH" ] && [ "${VULKAN_FROM_SOURCE}" = "true" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.328.1/linux/vulkansdk-linux-x86_64-1.4.328.1.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
mkdir -p /opt/vulkan-sdk && \
|
||||
mv 1.4.335.0 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.335.0 && \
|
||||
mv 1.4.328.1 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.328.1 && \
|
||||
./vulkansdk --no-deps --maxjobs \
|
||||
vulkan-loader \
|
||||
vulkan-validationlayers \
|
||||
vulkan-extensionlayer \
|
||||
vulkan-tools \
|
||||
shaderc && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/share/* /usr/share/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/share/* /usr/share/ && \
|
||||
rm -rf /opt/vulkan-sdk
|
||||
elif [ "amd64" = "${TARGETARCH}}" ]; then
|
||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc && \
|
||||
wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list http://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y vulkan-sdk
|
||||
fi
|
||||
if [ "arm64" = "$TARGETARCH" ]; then
|
||||
mkdir vulkan && cd vulkan && \
|
||||
|
||||
30
Makefile
30
Makefile
@@ -1,5 +1,5 @@
|
||||
# Disable parallel execution for backend builds
|
||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/piper backends/stablediffusion-ggml backends/whisper backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/stablediffusion-ggml-darwin backends/vllm backends/moonshine backends/pocket-tts
|
||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/piper backends/stablediffusion-ggml backends/whisper backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/stablediffusion-ggml-darwin backends/vllm backends/moonshine
|
||||
|
||||
GOCMD=go
|
||||
GOTEST=$(GOCMD) test
|
||||
@@ -9,7 +9,7 @@ LAUNCHER_BINARY_NAME=local-ai-launcher
|
||||
|
||||
CUDA_MAJOR_VERSION?=13
|
||||
CUDA_MINOR_VERSION?=0
|
||||
UBUNTU_VERSION?=2404
|
||||
UBUNTU_VERSION?=2204
|
||||
UBUNTU_CODENAME?=noble
|
||||
|
||||
GORELEASER?=
|
||||
@@ -316,7 +316,6 @@ prepare-test-extra: protogen-python
|
||||
$(MAKE) -C backend/python/vllm
|
||||
$(MAKE) -C backend/python/vibevoice
|
||||
$(MAKE) -C backend/python/moonshine
|
||||
$(MAKE) -C backend/python/pocket-tts
|
||||
|
||||
test-extra: prepare-test-extra
|
||||
$(MAKE) -C backend/python/transformers test
|
||||
@@ -325,7 +324,6 @@ test-extra: prepare-test-extra
|
||||
$(MAKE) -C backend/python/vllm test
|
||||
$(MAKE) -C backend/python/vibevoice test
|
||||
$(MAKE) -C backend/python/moonshine test
|
||||
$(MAKE) -C backend/python/pocket-tts test
|
||||
|
||||
DOCKER_IMAGE?=local-ai
|
||||
DOCKER_AIO_IMAGE?=local-ai-aio
|
||||
@@ -449,16 +447,17 @@ BACKEND_FASTER_WHISPER = faster-whisper|python|.|false|true
|
||||
BACKEND_COQUI = coqui|python|.|false|true
|
||||
BACKEND_BARK = bark|python|.|false|true
|
||||
BACKEND_EXLLAMA2 = exllama2|python|.|false|true
|
||||
BACKEND_RFDETR = rfdetr|python|.|false|true
|
||||
BACKEND_KITTEN_TTS = kitten-tts|python|.|false|true
|
||||
BACKEND_NEUTTS = neutts|python|.|false|true
|
||||
BACKEND_KOKORO = kokoro|python|.|false|true
|
||||
BACKEND_VLLM = vllm|python|.|false|true
|
||||
BACKEND_DIFFUSERS = diffusers|python|.|--progress=plain|true
|
||||
BACKEND_CHATTERBOX = chatterbox|python|.|false|true
|
||||
BACKEND_VIBEVOICE = vibevoice|python|.|--progress=plain|true
|
||||
BACKEND_MOONSHINE = moonshine|python|.|false|true
|
||||
BACKEND_POCKET_TTS = pocket-tts|python|.|false|true
|
||||
|
||||
# Python backends with ./backend context
|
||||
BACKEND_RFDETR = rfdetr|python|./backend|false|true
|
||||
BACKEND_KITTEN_TTS = kitten-tts|python|./backend|false|true
|
||||
BACKEND_NEUTTS = neutts|python|./backend|false|true
|
||||
BACKEND_KOKORO = kokoro|python|./backend|false|true
|
||||
BACKEND_VLLM = vllm|python|./backend|false|true
|
||||
BACKEND_DIFFUSERS = diffusers|python|./backend|--progress=plain|true
|
||||
BACKEND_CHATTERBOX = chatterbox|python|./backend|false|true
|
||||
BACKEND_VIBEVOICE = vibevoice|python|./backend|--progress=plain|true
|
||||
BACKEND_MOONSHINE = moonshine|python|./backend|false|true
|
||||
|
||||
# Helper function to build docker image for a backend
|
||||
# Usage: $(call docker-build-backend,BACKEND_NAME,DOCKERFILE_TYPE,BUILD_CONTEXT,PROGRESS_FLAG,NEEDS_BACKEND_ARG)
|
||||
@@ -504,13 +503,12 @@ $(eval $(call generate-docker-build-target,$(BACKEND_DIFFUSERS)))
|
||||
$(eval $(call generate-docker-build-target,$(BACKEND_CHATTERBOX)))
|
||||
$(eval $(call generate-docker-build-target,$(BACKEND_VIBEVOICE)))
|
||||
$(eval $(call generate-docker-build-target,$(BACKEND_MOONSHINE)))
|
||||
$(eval $(call generate-docker-build-target,$(BACKEND_POCKET_TTS)))
|
||||
|
||||
# Pattern rule for docker-save targets
|
||||
docker-save-%: backend-images
|
||||
docker save local-ai-backend:$* -o backend-images/$*.tar
|
||||
|
||||
docker-build-backends: docker-build-llama-cpp docker-build-rerankers docker-build-vllm docker-build-transformers docker-build-diffusers docker-build-kokoro docker-build-faster-whisper docker-build-coqui docker-build-bark docker-build-chatterbox docker-build-vibevoice docker-build-exllama2 docker-build-moonshine docker-build-pocket-tts
|
||||
docker-build-backends: docker-build-llama-cpp docker-build-rerankers docker-build-vllm docker-build-transformers docker-build-diffusers docker-build-kokoro docker-build-faster-whisper docker-build-coqui docker-build-bark docker-build-chatterbox docker-build-vibevoice docker-build-exllama2 docker-build-moonshine
|
||||
|
||||
########################################################
|
||||
### END Backends
|
||||
|
||||
21
README.md
21
README.md
@@ -111,8 +111,6 @@
|
||||
|
||||
## 💻 Quickstart
|
||||
|
||||
> ⚠️ **Note:** The `install.sh` script is currently experiencing issues due to the heavy changes currently undergoing in LocalAI and may produce broken or misconfigured installations. Please use Docker installation (see below) or manual binary installation until [issue #8032](https://github.com/mudler/LocalAI/issues/8032) is resolved.
|
||||
|
||||
Run the installer script:
|
||||
|
||||
```bash
|
||||
@@ -130,7 +128,7 @@ For more installation options, see [Installer Options](https://localai.io/instal
|
||||
|
||||
> Note: the DMGs are not signed by Apple as quarantined. See https://github.com/mudler/LocalAI/issues/6268 for a workaround, fix is tracked here: https://github.com/mudler/LocalAI/issues/6244
|
||||
|
||||
### Containers (Docker, podman, ...)
|
||||
Or run with docker:
|
||||
|
||||
> **💡 Docker Run vs Docker Start**
|
||||
>
|
||||
@@ -139,13 +137,13 @@ For more installation options, see [Installer Options](https://localai.io/instal
|
||||
>
|
||||
> If you've already run LocalAI before and want to start it again, use: `docker start -i local-ai`
|
||||
|
||||
#### CPU only image:
|
||||
### CPU only image:
|
||||
|
||||
```bash
|
||||
docker run -ti --name local-ai -p 8080:8080 localai/localai:latest
|
||||
```
|
||||
|
||||
#### NVIDIA GPU Images:
|
||||
### NVIDIA GPU Images:
|
||||
|
||||
```bash
|
||||
# CUDA 13.0
|
||||
@@ -162,25 +160,25 @@ docker run -ti --name local-ai -p 8080:8080 --gpus all localai/localai:latest-nv
|
||||
docker run -ti --name local-ai -p 8080:8080 --gpus all localai/localai:latest-nvidia-l4t-arm64-cuda-13
|
||||
```
|
||||
|
||||
#### AMD GPU Images (ROCm):
|
||||
### AMD GPU Images (ROCm):
|
||||
|
||||
```bash
|
||||
docker run -ti --name local-ai -p 8080:8080 --device=/dev/kfd --device=/dev/dri --group-add=video localai/localai:latest-gpu-hipblas
|
||||
```
|
||||
|
||||
#### Intel GPU Images (oneAPI):
|
||||
### Intel GPU Images (oneAPI):
|
||||
|
||||
```bash
|
||||
docker run -ti --name local-ai -p 8080:8080 --device=/dev/dri/card1 --device=/dev/dri/renderD128 localai/localai:latest-gpu-intel
|
||||
```
|
||||
|
||||
#### Vulkan GPU Images:
|
||||
### Vulkan GPU Images:
|
||||
|
||||
```bash
|
||||
docker run -ti --name local-ai -p 8080:8080 localai/localai:latest-gpu-vulkan
|
||||
```
|
||||
|
||||
#### AIO Images (pre-downloaded models):
|
||||
### AIO Images (pre-downloaded models):
|
||||
|
||||
```bash
|
||||
# CPU version
|
||||
@@ -297,7 +295,6 @@ LocalAI supports a comprehensive range of AI backends with multiple acceleration
|
||||
| **silero-vad** | Voice Activity Detection | CPU |
|
||||
| **neutts** | Text-to-speech with voice cloning | CUDA 12/13, ROCm, CPU |
|
||||
| **vibevoice** | Real-time TTS with voice cloning | CUDA 12/13, ROCm, Intel, CPU |
|
||||
| **pocket-tts** | Lightweight CPU-based TTS | CUDA 12/13, ROCm, Intel, CPU |
|
||||
|
||||
### Image & Video Generation
|
||||
| Backend | Description | Acceleration Support |
|
||||
@@ -319,8 +316,8 @@ LocalAI supports a comprehensive range of AI backends with multiple acceleration
|
||||
|-------------------|-------------------|------------------|
|
||||
| **NVIDIA CUDA 12** | All CUDA-compatible backends | Nvidia hardware |
|
||||
| **NVIDIA CUDA 13** | All CUDA-compatible backends | Nvidia hardware |
|
||||
| **AMD ROCm** | llama.cpp, whisper, vllm, transformers, diffusers, rerankers, coqui, kokoro, bark, neutts, vibevoice, pocket-tts | AMD Graphics |
|
||||
| **Intel oneAPI** | llama.cpp, whisper, stablediffusion, vllm, transformers, diffusers, rfdetr, rerankers, exllama2, coqui, kokoro, bark, vibevoice, pocket-tts | Intel Arc, Intel iGPUs |
|
||||
| **AMD ROCm** | llama.cpp, whisper, vllm, transformers, diffusers, rerankers, coqui, kokoro, bark, neutts, vibevoice | AMD Graphics |
|
||||
| **Intel oneAPI** | llama.cpp, whisper, stablediffusion, vllm, transformers, diffusers, rfdetr, rerankers, exllama2, coqui, kokoro, bark, vibevoice | Intel Arc, Intel iGPUs |
|
||||
| **Apple Metal** | llama.cpp, whisper, diffusers, MLX, MLX-VLM, bark-cpp | Apple M1/M2/M3+ |
|
||||
| **Vulkan** | llama.cpp, whisper, stablediffusion | Cross-platform GPUs |
|
||||
| **NVIDIA Jetson (CUDA 12)** | llama.cpp, whisper, stablediffusion, diffusers, rfdetr | ARM64 embedded AI (AGX Orin, etc.) |
|
||||
|
||||
@@ -46,24 +46,29 @@ RUN <<EOT bash
|
||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
||||
if [ "amd64" = "$TARGETARCH" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
if [ "amd64" = "$TARGETARCH" ] && [ "${VULKAN_FROM_SOURCE}" = "true" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.328.1/linux/vulkansdk-linux-x86_64-1.4.328.1.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
mkdir -p /opt/vulkan-sdk && \
|
||||
mv 1.4.335.0 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.335.0 && \
|
||||
mv 1.4.328.1 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.328.1 && \
|
||||
./vulkansdk --no-deps --maxjobs \
|
||||
vulkan-loader \
|
||||
vulkan-validationlayers \
|
||||
vulkan-extensionlayer \
|
||||
vulkan-tools \
|
||||
shaderc && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/share/* /usr/share/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/share/* /usr/share/ && \
|
||||
rm -rf /opt/vulkan-sdk
|
||||
elif [ "amd64" = "${TARGETARCH}}" ]; then
|
||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc && \
|
||||
wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list http://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y vulkan-sdk
|
||||
fi
|
||||
if [ "arm64" = "$TARGETARCH" ]; then
|
||||
mkdir vulkan && cd vulkan && \
|
||||
|
||||
@@ -103,24 +103,29 @@ RUN <<EOT bash
|
||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
||||
if [ "amd64" = "$TARGETARCH" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
if [ "amd64" = "$TARGETARCH" ] && [ "${VULKAN_FROM_SOURCE}" = "true" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.328.1/linux/vulkansdk-linux-x86_64-1.4.328.1.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
mkdir -p /opt/vulkan-sdk && \
|
||||
mv 1.4.335.0 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.335.0 && \
|
||||
mv 1.4.328.1 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.328.1 && \
|
||||
./vulkansdk --no-deps --maxjobs \
|
||||
vulkan-loader \
|
||||
vulkan-validationlayers \
|
||||
vulkan-extensionlayer \
|
||||
vulkan-tools \
|
||||
shaderc && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/share/* /usr/share/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/share/* /usr/share/ && \
|
||||
rm -rf /opt/vulkan-sdk
|
||||
elif [ "amd64" = "${TARGETARCH}}" ]; then
|
||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc && \
|
||||
wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list http://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y vulkan-sdk
|
||||
fi
|
||||
if [ "arm64" = "$TARGETARCH" ]; then
|
||||
mkdir vulkan && cd vulkan && \
|
||||
|
||||
@@ -60,24 +60,29 @@ RUN <<EOT bash
|
||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
||||
if [ "amd64" = "$TARGETARCH" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||
if [ "amd64" = "$TARGETARCH" ] && [ "${VULKAN_FROM_SOURCE}" = "true" ]; then
|
||||
wget "https://sdk.lunarg.com/sdk/download/1.4.328.1/linux/vulkansdk-linux-x86_64-1.4.328.1.tar.xz" && \
|
||||
tar -xf vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
rm vulkansdk-linux-x86_64-1.4.328.1.tar.xz && \
|
||||
mkdir -p /opt/vulkan-sdk && \
|
||||
mv 1.4.335.0 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.335.0 && \
|
||||
mv 1.4.328.1 /opt/vulkan-sdk/ && \
|
||||
cd /opt/vulkan-sdk/1.4.328.1 && \
|
||||
./vulkansdk --no-deps --maxjobs \
|
||||
vulkan-loader \
|
||||
vulkan-validationlayers \
|
||||
vulkan-extensionlayer \
|
||||
vulkan-tools \
|
||||
shaderc && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.335.0/x86_64/share/* /usr/share/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/bin/* /usr/bin/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/lib/* /usr/lib/x86_64-linux-gnu/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/include/* /usr/include/ && \
|
||||
cp -rfv /opt/vulkan-sdk/1.4.328.1/x86_64/share/* /usr/share/ && \
|
||||
rm -rf /opt/vulkan-sdk
|
||||
elif [ "amd64" = "${TARGETARCH}}" ]; then
|
||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc && \
|
||||
wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list http://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y vulkan-sdk
|
||||
fi
|
||||
if [ "arm64" = "$TARGETARCH" ]; then
|
||||
mkdir vulkan && cd vulkan && \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
LLAMA_VERSION?=785a71008573e2d84728fb0ba9e851d72d3f8fab
|
||||
LLAMA_VERSION?=b1377188784f9aea26b8abde56d4aee8c733eec7
|
||||
LLAMA_REPO?=https://github.com/ggerganov/llama.cpp
|
||||
|
||||
CMAKE_ARGS?=
|
||||
|
||||
@@ -8,7 +8,7 @@ JOBS?=$(shell nproc --ignore=1)
|
||||
|
||||
# stablediffusion.cpp (ggml)
|
||||
STABLEDIFFUSION_GGML_REPO?=https://github.com/leejet/stable-diffusion.cpp
|
||||
STABLEDIFFUSION_GGML_VERSION?=7010bb4dff7bd55b03d35ef9772142c21699eba9
|
||||
STABLEDIFFUSION_GGML_VERSION?=0e52afc6513cc2dea9a1a017afc4a008d5acf2b0
|
||||
|
||||
CMAKE_ARGS+=-DGGML_MAX_NAME=128
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ JOBS?=$(shell nproc --ignore=1)
|
||||
|
||||
# whisper.cpp version
|
||||
WHISPER_REPO?=https://github.com/ggml-org/whisper.cpp
|
||||
WHISPER_CPP_VERSION?=2eeeba56e9edd762b4b38467bab96c2517163158
|
||||
WHISPER_CPP_VERSION?=679bdb53dbcbfb3e42685f50c7ff367949fd4d48
|
||||
SO_TARGET?=libgowhisper.so
|
||||
|
||||
CMAKE_ARGS+=-DBUILD_SHARED_LIBS=OFF
|
||||
|
||||
@@ -428,28 +428,6 @@
|
||||
nvidia-l4t-cuda-12: "nvidia-l4t-vibevoice"
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-vibevoice"
|
||||
icon: https://avatars.githubusercontent.com/u/6154722?s=200&v=4
|
||||
- &pocket-tts
|
||||
urls:
|
||||
- https://github.com/kyutai-labs/pocket-tts
|
||||
description: |
|
||||
Pocket TTS is a lightweight text-to-speech model designed to run efficiently on CPUs.
|
||||
tags:
|
||||
- text-to-speech
|
||||
- TTS
|
||||
license: mit
|
||||
name: "pocket-tts"
|
||||
alias: "pocket-tts"
|
||||
capabilities:
|
||||
nvidia: "cuda12-pocket-tts"
|
||||
intel: "intel-pocket-tts"
|
||||
amd: "rocm-pocket-tts"
|
||||
nvidia-l4t: "nvidia-l4t-pocket-tts"
|
||||
default: "cpu-pocket-tts"
|
||||
nvidia-cuda-13: "cuda13-pocket-tts"
|
||||
nvidia-cuda-12: "cuda12-pocket-tts"
|
||||
nvidia-l4t-cuda-12: "nvidia-l4t-pocket-tts"
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-pocket-tts"
|
||||
icon: https://avatars.githubusercontent.com/u/6154722?s=200&v=4
|
||||
- &piper
|
||||
name: "piper"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-piper"
|
||||
@@ -1627,86 +1605,3 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-cuda-13-arm64-vibevoice"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-nvidia-l4t-cuda-13-arm64-vibevoice
|
||||
## pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "pocket-tts-development"
|
||||
capabilities:
|
||||
nvidia: "cuda12-pocket-tts-development"
|
||||
intel: "intel-pocket-tts-development"
|
||||
amd: "rocm-pocket-tts-development"
|
||||
nvidia-l4t: "nvidia-l4t-pocket-tts-development"
|
||||
default: "cpu-pocket-tts-development"
|
||||
nvidia-cuda-13: "cuda13-pocket-tts-development"
|
||||
nvidia-cuda-12: "cuda12-pocket-tts-development"
|
||||
nvidia-l4t-cuda-12: "nvidia-l4t-pocket-tts-development"
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-pocket-tts-development"
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cpu-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-cpu-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cpu-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-cpu-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cuda12-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-gpu-nvidia-cuda-12-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cuda12-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-12-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-nvidia-cuda-12-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cuda13-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-gpu-nvidia-cuda-13-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cuda13-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-13-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-nvidia-cuda-13-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "intel-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-intel-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-gpu-intel-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "intel-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-intel-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-intel-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "rocm-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-rocm-hipblas-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-gpu-rocm-hipblas-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "rocm-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-rocm-hipblas-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-rocm-hipblas-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "nvidia-l4t-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-nvidia-l4t-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "nvidia-l4t-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-nvidia-l4t-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cuda13-nvidia-l4t-arm64-pocket-tts"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-cuda-13-arm64-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-nvidia-l4t-cuda-13-arm64-pocket-tts
|
||||
- !!merge <<: *pocket-tts
|
||||
name: "cuda13-nvidia-l4t-arm64-pocket-tts-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-cuda-13-arm64-pocket-tts"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-nvidia-l4t-cuda-13-arm64-pocket-tts
|
||||
|
||||
@@ -41,14 +41,6 @@ from optimum.quanto import freeze, qfloat8, quantize
|
||||
from transformers import T5EncoderModel
|
||||
from safetensors.torch import load_file
|
||||
|
||||
# Import LTX-2 specific utilities
|
||||
try:
|
||||
from diffusers.pipelines.ltx2.export_utils import encode_video as ltx2_encode_video
|
||||
LTX2_AVAILABLE = True
|
||||
except ImportError:
|
||||
LTX2_AVAILABLE = False
|
||||
ltx2_encode_video = None
|
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
|
||||
COMPEL = os.environ.get("COMPEL", "0") == "1"
|
||||
XPU = os.environ.get("XPU", "0") == "1"
|
||||
@@ -298,20 +290,6 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
|
||||
pipe.enable_model_cpu_offload()
|
||||
return pipe
|
||||
|
||||
# LTX2ImageToVideoPipeline - needs img2vid flag, CPU offload, and special handling
|
||||
if pipeline_type == "LTX2ImageToVideoPipeline":
|
||||
self.img2vid = True
|
||||
self.ltx2_pipeline = True
|
||||
pipe = load_diffusers_pipeline(
|
||||
class_name="LTX2ImageToVideoPipeline",
|
||||
model_id=request.Model,
|
||||
torch_dtype=torchType,
|
||||
variant=variant
|
||||
)
|
||||
if not DISABLE_CPU_OFFLOAD:
|
||||
pipe.enable_model_cpu_offload()
|
||||
return pipe
|
||||
|
||||
# ================================================================
|
||||
# Dynamic pipeline loading - the default path for most pipelines
|
||||
# Uses the dynamic loader to instantiate any pipeline by class name
|
||||
@@ -426,7 +404,6 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
|
||||
fromSingleFile = request.Model.startswith("http") or request.Model.startswith("/") or local
|
||||
self.img2vid = False
|
||||
self.txt2vid = False
|
||||
self.ltx2_pipeline = False
|
||||
|
||||
# Load pipeline using dynamic loader
|
||||
# Special cases that require custom initialization are handled first
|
||||
@@ -709,44 +686,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
|
||||
print(f"Generating video with {kwargs=}", file=sys.stderr)
|
||||
|
||||
# Generate video frames based on pipeline type
|
||||
if self.ltx2_pipeline or self.PipelineType == "LTX2ImageToVideoPipeline":
|
||||
# LTX-2 image-to-video generation with audio
|
||||
if not LTX2_AVAILABLE:
|
||||
return backend_pb2.Result(success=False, message="LTX-2 pipeline requires diffusers.pipelines.ltx2.export_utils")
|
||||
|
||||
# LTX-2 uses 'image' parameter instead of 'start_image'
|
||||
if request.start_image:
|
||||
image = load_image(request.start_image)
|
||||
kwargs["image"] = image
|
||||
# Remove start_image if it was added
|
||||
kwargs.pop("start_image", None)
|
||||
|
||||
# LTX-2 uses 'frame_rate' instead of 'fps'
|
||||
frame_rate = float(fps)
|
||||
kwargs["frame_rate"] = frame_rate
|
||||
|
||||
# LTX-2 requires output_type="np" and return_dict=False
|
||||
kwargs["output_type"] = "np"
|
||||
kwargs["return_dict"] = False
|
||||
|
||||
# Generate video and audio
|
||||
video, audio = self.pipe(**kwargs)
|
||||
|
||||
# Convert video to uint8 format
|
||||
video = (video * 255).round().astype("uint8")
|
||||
video = torch.from_numpy(video)
|
||||
|
||||
# Use LTX-2's encode_video function which handles audio
|
||||
ltx2_encode_video(
|
||||
video[0],
|
||||
fps=frame_rate,
|
||||
audio=audio[0].float().cpu(),
|
||||
audio_sample_rate=self.pipe.vocoder.config.output_sampling_rate,
|
||||
output_path=request.dst,
|
||||
)
|
||||
|
||||
return backend_pb2.Result(message="Video generated successfully", success=True)
|
||||
elif self.PipelineType == "WanPipeline":
|
||||
if self.PipelineType == "WanPipeline":
|
||||
# WAN2.2 text-to-video generation
|
||||
output = self.pipe(**kwargs)
|
||||
frames = output.frames[0] # WAN2.2 returns frames in this format
|
||||
@@ -787,7 +727,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
|
||||
else:
|
||||
return backend_pb2.Result(success=False, message=f"Pipeline {self.PipelineType} does not support video generation")
|
||||
|
||||
# Export video (for non-LTX-2 pipelines)
|
||||
# Export video
|
||||
export_to_video(frames, request.dst, fps=fps)
|
||||
|
||||
return backend_pb2.Result(message="Video generated successfully", success=True)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
.PHONY: pocket-tts
|
||||
pocket-tts:
|
||||
bash install.sh
|
||||
|
||||
.PHONY: run
|
||||
run: pocket-tts
|
||||
@echo "Running pocket-tts..."
|
||||
bash run.sh
|
||||
@echo "pocket-tts run."
|
||||
|
||||
.PHONY: test
|
||||
test: pocket-tts
|
||||
@echo "Testing pocket-tts..."
|
||||
bash test.sh
|
||||
@echo "pocket-tts tested."
|
||||
|
||||
.PHONY: protogen-clean
|
||||
protogen-clean:
|
||||
$(RM) backend_pb2_grpc.py backend_pb2.py
|
||||
|
||||
.PHONY: clean
|
||||
clean: protogen-clean
|
||||
rm -rf venv __pycache__
|
||||
@@ -1,255 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This is an extra gRPC server of LocalAI for Pocket TTS
|
||||
"""
|
||||
from concurrent import futures
|
||||
import time
|
||||
import argparse
|
||||
import signal
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import scipy.io.wavfile
|
||||
import backend_pb2
|
||||
import backend_pb2_grpc
|
||||
import torch
|
||||
from pocket_tts import TTSModel
|
||||
|
||||
import grpc
|
||||
|
||||
def is_float(s):
|
||||
"""Check if a string can be converted to float."""
|
||||
try:
|
||||
float(s)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def is_int(s):
|
||||
"""Check if a string can be converted to int."""
|
||||
try:
|
||||
int(s)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
|
||||
|
||||
# If MAX_WORKERS are specified in the environment use it, otherwise default to 1
|
||||
MAX_WORKERS = int(os.environ.get('PYTHON_GRPC_MAX_WORKERS', '1'))
|
||||
|
||||
# Implement the BackendServicer class with the service methods
|
||||
class BackendServicer(backend_pb2_grpc.BackendServicer):
|
||||
"""
|
||||
BackendServicer is the class that implements the gRPC service
|
||||
"""
|
||||
def Health(self, request, context):
|
||||
return backend_pb2.Reply(message=bytes("OK", 'utf-8'))
|
||||
|
||||
def LoadModel(self, request, context):
|
||||
# Get device
|
||||
if torch.cuda.is_available():
|
||||
print("CUDA is available", file=sys.stderr)
|
||||
device = "cuda"
|
||||
else:
|
||||
print("CUDA is not available", file=sys.stderr)
|
||||
device = "cpu"
|
||||
mps_available = hasattr(torch.backends, "mps") and torch.backends.mps.is_available()
|
||||
if mps_available:
|
||||
device = "mps"
|
||||
if not torch.cuda.is_available() and request.CUDA:
|
||||
return backend_pb2.Result(success=False, message="CUDA is not available")
|
||||
|
||||
# Normalize potential 'mpx' typo to 'mps'
|
||||
if device == "mpx":
|
||||
print("Note: device 'mpx' detected, treating it as 'mps'.", file=sys.stderr)
|
||||
device = "mps"
|
||||
|
||||
# Validate mps availability if requested
|
||||
if device == "mps" and not torch.backends.mps.is_available():
|
||||
print("Warning: MPS not available. Falling back to CPU.", file=sys.stderr)
|
||||
device = "cpu"
|
||||
|
||||
self.device = device
|
||||
|
||||
options = request.Options
|
||||
|
||||
# empty dict
|
||||
self.options = {}
|
||||
|
||||
# The options are a list of strings in this form optname:optvalue
|
||||
# We are storing all the options in a dict so we can use it later when
|
||||
# generating the audio
|
||||
for opt in options:
|
||||
if ":" not in opt:
|
||||
continue
|
||||
key, value = opt.split(":", 1) # Split only on first colon
|
||||
# if value is a number, convert it to the appropriate type
|
||||
if is_float(value):
|
||||
value = float(value)
|
||||
elif is_int(value):
|
||||
value = int(value)
|
||||
elif value.lower() in ["true", "false"]:
|
||||
value = value.lower() == "true"
|
||||
self.options[key] = value
|
||||
|
||||
# Default voice for caching
|
||||
self.default_voice_url = self.options.get("default_voice", None)
|
||||
self._voice_cache = {}
|
||||
|
||||
try:
|
||||
print("Loading Pocket TTS model", file=sys.stderr)
|
||||
self.tts_model = TTSModel.load_model()
|
||||
print(f"Model loaded successfully. Sample rate: {self.tts_model.sample_rate}", file=sys.stderr)
|
||||
|
||||
# Pre-load default voice if specified
|
||||
if self.default_voice_url:
|
||||
try:
|
||||
print(f"Pre-loading default voice: {self.default_voice_url}", file=sys.stderr)
|
||||
voice_state = self.tts_model.get_state_for_audio_prompt(self.default_voice_url)
|
||||
self._voice_cache[self.default_voice_url] = voice_state
|
||||
print("Default voice loaded successfully", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to pre-load default voice: {e}", file=sys.stderr)
|
||||
|
||||
except Exception as err:
|
||||
return backend_pb2.Result(success=False, message=f"Unexpected {err=}, {type(err)=}")
|
||||
|
||||
return backend_pb2.Result(message="Model loaded successfully", success=True)
|
||||
|
||||
def _get_voice_state(self, voice_input):
|
||||
"""
|
||||
Get voice state from cache or load it.
|
||||
voice_input can be:
|
||||
- HuggingFace URL (e.g., hf://kyutai/tts-voices/alba-mackenna/casual.wav)
|
||||
- Local file path
|
||||
- None (use default)
|
||||
"""
|
||||
# Use default if no voice specified
|
||||
if not voice_input:
|
||||
voice_input = self.default_voice_url
|
||||
|
||||
if not voice_input:
|
||||
return None
|
||||
|
||||
# Check cache first
|
||||
if voice_input in self._voice_cache:
|
||||
return self._voice_cache[voice_input]
|
||||
|
||||
# Load voice state
|
||||
try:
|
||||
print(f"Loading voice from: {voice_input}", file=sys.stderr)
|
||||
voice_state = self.tts_model.get_state_for_audio_prompt(voice_input)
|
||||
self._voice_cache[voice_input] = voice_state
|
||||
return voice_state
|
||||
except Exception as e:
|
||||
print(f"Error loading voice from {voice_input}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
def TTS(self, request, context):
|
||||
try:
|
||||
# Determine voice input
|
||||
# Priority: request.voice > AudioPath (from ModelOptions) > default
|
||||
voice_input = None
|
||||
|
||||
if request.voice:
|
||||
voice_input = request.voice
|
||||
elif hasattr(request, 'AudioPath') and request.AudioPath:
|
||||
# Use AudioPath as voice file
|
||||
if os.path.isabs(request.AudioPath):
|
||||
voice_input = request.AudioPath
|
||||
elif hasattr(request, 'ModelFile') and request.ModelFile:
|
||||
model_file_base = os.path.dirname(request.ModelFile)
|
||||
voice_input = os.path.join(model_file_base, request.AudioPath)
|
||||
elif hasattr(request, 'ModelPath') and request.ModelPath:
|
||||
voice_input = os.path.join(request.ModelPath, request.AudioPath)
|
||||
else:
|
||||
voice_input = request.AudioPath
|
||||
|
||||
# Get voice state
|
||||
voice_state = self._get_voice_state(voice_input)
|
||||
if voice_state is None:
|
||||
return backend_pb2.Result(
|
||||
success=False,
|
||||
message=f"Voice not found or failed to load: {voice_input}. Please provide a valid voice URL or file path."
|
||||
)
|
||||
|
||||
# Prepare text
|
||||
text = request.text.strip()
|
||||
|
||||
if not text:
|
||||
return backend_pb2.Result(
|
||||
success=False,
|
||||
message="Text is empty"
|
||||
)
|
||||
|
||||
print(f"Generating audio for text: {text[:50]}...", file=sys.stderr)
|
||||
|
||||
# Generate audio
|
||||
audio = self.tts_model.generate_audio(voice_state, text)
|
||||
|
||||
# Audio is a 1D torch tensor containing PCM data
|
||||
if audio is None or audio.numel() == 0:
|
||||
return backend_pb2.Result(
|
||||
success=False,
|
||||
message="No audio generated"
|
||||
)
|
||||
|
||||
# Save audio to file
|
||||
output_path = request.dst
|
||||
if not output_path:
|
||||
output_path = "/tmp/pocket-tts-output.wav"
|
||||
|
||||
# Ensure output directory exists
|
||||
output_dir = os.path.dirname(output_path)
|
||||
if output_dir and not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Convert torch tensor to numpy and save
|
||||
audio_numpy = audio.numpy()
|
||||
scipy.io.wavfile.write(output_path, self.tts_model.sample_rate, audio_numpy)
|
||||
print(f"Saved audio to {output_path}", file=sys.stderr)
|
||||
|
||||
except Exception as err:
|
||||
print(f"Error in TTS: {err}", file=sys.stderr)
|
||||
print(traceback.format_exc(), file=sys.stderr)
|
||||
return backend_pb2.Result(success=False, message=f"Unexpected {err=}, {type(err)=}")
|
||||
|
||||
return backend_pb2.Result(success=True)
|
||||
|
||||
def serve(address):
|
||||
server = grpc.server(futures.ThreadPoolExecutor(max_workers=MAX_WORKERS),
|
||||
options=[
|
||||
('grpc.max_message_length', 50 * 1024 * 1024), # 50MB
|
||||
('grpc.max_send_message_length', 50 * 1024 * 1024), # 50MB
|
||||
('grpc.max_receive_message_length', 50 * 1024 * 1024), # 50MB
|
||||
])
|
||||
backend_pb2_grpc.add_BackendServicer_to_server(BackendServicer(), server)
|
||||
server.add_insecure_port(address)
|
||||
server.start()
|
||||
print("Server started. Listening on: " + address, file=sys.stderr)
|
||||
|
||||
# Define the signal handler function
|
||||
def signal_handler(sig, frame):
|
||||
print("Received termination signal. Shutting down...")
|
||||
server.stop(0)
|
||||
sys.exit(0)
|
||||
|
||||
# Set the signal handlers for SIGINT and SIGTERM
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(_ONE_DAY_IN_SECONDS)
|
||||
except KeyboardInterrupt:
|
||||
server.stop(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Run the gRPC server.")
|
||||
parser.add_argument(
|
||||
"--addr", default="localhost:50051", help="The address to bind the server to."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
serve(args.addr)
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
backend_dir=$(dirname $0)
|
||||
if [ -d $backend_dir/common ]; then
|
||||
source $backend_dir/common/libbackend.sh
|
||||
else
|
||||
source $backend_dir/../common/libbackend.sh
|
||||
fi
|
||||
|
||||
# This is here because the Intel pip index is broken and returns 200 status codes for every package name, it just doesn't return any package links.
|
||||
# This makes uv think that the package exists in the Intel pip index, and by default it stops looking at other pip indexes once it finds a match.
|
||||
# We need uv to continue falling through to the pypi default index to find optimum[openvino] in the pypi index
|
||||
# the --upgrade actually allows us to *downgrade* torch to the version provided in the Intel pip index
|
||||
if [ "x${BUILD_PROFILE}" == "xintel" ]; then
|
||||
EXTRA_PIP_INSTALL_FLAGS+=" --upgrade --index-strategy=unsafe-first-match"
|
||||
fi
|
||||
|
||||
# Use python 3.12 for l4t
|
||||
if [ "x${BUILD_PROFILE}" == "xl4t13" ]; then
|
||||
PYTHON_VERSION="3.12"
|
||||
PYTHON_PATCH="12"
|
||||
PY_STANDALONE_TAG="20251120"
|
||||
fi
|
||||
|
||||
if [ "x${BUILD_PROFILE}" == "xl4t12" ]; then
|
||||
USE_PIP=true
|
||||
fi
|
||||
|
||||
installRequirements
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
backend_dir=$(dirname $0)
|
||||
if [ -d $backend_dir/common ]; then
|
||||
source $backend_dir/common/libbackend.sh
|
||||
else
|
||||
source $backend_dir/../common/libbackend.sh
|
||||
fi
|
||||
|
||||
python3 -m grpc_tools.protoc -I../.. -I./ --python_out=. --grpc_python_out=. backend.proto
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||
pocket-tts
|
||||
scipy
|
||||
torch
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/cu121
|
||||
pocket-tts
|
||||
scipy
|
||||
torch
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/cu130
|
||||
pocket-tts
|
||||
scipy
|
||||
torch
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/rocm6.3
|
||||
pocket-tts
|
||||
scipy
|
||||
torch==2.7.1+rocm6.3
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/
|
||||
pocket-tts
|
||||
scipy
|
||||
torch==2.5.1+cxx11.abi
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://pypi.jetson-ai-lab.io/jp6/cu129/
|
||||
pocket-tts
|
||||
scipy
|
||||
torch
|
||||
@@ -1,4 +0,0 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/cu130
|
||||
pocket-tts
|
||||
scipy
|
||||
torch
|
||||
@@ -1,4 +0,0 @@
|
||||
pocket-tts
|
||||
scipy
|
||||
torch==2.7.1
|
||||
torchvision==0.22.1
|
||||
@@ -1,4 +0,0 @@
|
||||
grpcio==1.71.0
|
||||
protobuf
|
||||
certifi
|
||||
packaging==24.1
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
backend_dir=$(dirname $0)
|
||||
if [ -d $backend_dir/common ]; then
|
||||
source $backend_dir/common/libbackend.sh
|
||||
else
|
||||
source $backend_dir/../common/libbackend.sh
|
||||
fi
|
||||
|
||||
startBackend $@
|
||||
@@ -1,141 +0,0 @@
|
||||
"""
|
||||
A test script to test the gRPC service
|
||||
"""
|
||||
import unittest
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import tempfile
|
||||
import backend_pb2
|
||||
import backend_pb2_grpc
|
||||
|
||||
import grpc
|
||||
|
||||
|
||||
class TestBackendServicer(unittest.TestCase):
|
||||
"""
|
||||
TestBackendServicer is the class that tests the gRPC service
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
This method sets up the gRPC service by starting the server
|
||||
"""
|
||||
self.service = subprocess.Popen(["python3", "backend.py", "--addr", "localhost:50051"])
|
||||
time.sleep(30)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
"""
|
||||
This method tears down the gRPC service by terminating the server
|
||||
"""
|
||||
self.service.terminate()
|
||||
self.service.wait()
|
||||
|
||||
def test_server_startup(self):
|
||||
"""
|
||||
This method tests if the server starts up successfully
|
||||
"""
|
||||
try:
|
||||
self.setUp()
|
||||
with grpc.insecure_channel("localhost:50051") as channel:
|
||||
stub = backend_pb2_grpc.BackendStub(channel)
|
||||
response = stub.Health(backend_pb2.HealthMessage())
|
||||
self.assertEqual(response.message, b'OK')
|
||||
except Exception as err:
|
||||
print(err)
|
||||
self.fail("Server failed to start")
|
||||
finally:
|
||||
self.tearDown()
|
||||
|
||||
def test_load_model(self):
|
||||
"""
|
||||
This method tests if the model is loaded successfully
|
||||
"""
|
||||
try:
|
||||
self.setUp()
|
||||
with grpc.insecure_channel("localhost:50051") as channel:
|
||||
stub = backend_pb2_grpc.BackendStub(channel)
|
||||
response = stub.LoadModel(backend_pb2.ModelOptions())
|
||||
print(response)
|
||||
self.assertTrue(response.success)
|
||||
self.assertEqual(response.message, "Model loaded successfully")
|
||||
except Exception as err:
|
||||
print(err)
|
||||
self.fail("LoadModel service failed")
|
||||
finally:
|
||||
self.tearDown()
|
||||
|
||||
def test_tts_with_hf_voice(self):
|
||||
"""
|
||||
This method tests TTS generation with HuggingFace voice URL
|
||||
"""
|
||||
try:
|
||||
self.setUp()
|
||||
with grpc.insecure_channel("localhost:50051") as channel:
|
||||
stub = backend_pb2_grpc.BackendStub(channel)
|
||||
# Load model
|
||||
response = stub.LoadModel(backend_pb2.ModelOptions())
|
||||
self.assertTrue(response.success)
|
||||
|
||||
# Create temporary output file
|
||||
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
|
||||
output_path = tmp_file.name
|
||||
|
||||
# Test TTS with HuggingFace voice URL
|
||||
tts_request = backend_pb2.TTSRequest(
|
||||
text="Hello world, this is a test.",
|
||||
dst=output_path,
|
||||
voice="azelma"
|
||||
)
|
||||
tts_response = stub.TTS(tts_request)
|
||||
self.assertTrue(tts_response.success)
|
||||
|
||||
# Verify output file exists and is not empty
|
||||
self.assertTrue(os.path.exists(output_path))
|
||||
self.assertGreater(os.path.getsize(output_path), 0)
|
||||
|
||||
# Cleanup
|
||||
os.unlink(output_path)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
self.fail("TTS service failed")
|
||||
finally:
|
||||
self.tearDown()
|
||||
|
||||
def test_tts_with_default_voice(self):
|
||||
"""
|
||||
This method tests TTS generation with default voice (via AudioPath in LoadModel)
|
||||
"""
|
||||
try:
|
||||
self.setUp()
|
||||
with grpc.insecure_channel("localhost:50051") as channel:
|
||||
stub = backend_pb2_grpc.BackendStub(channel)
|
||||
# Load model with default voice
|
||||
load_request = backend_pb2.ModelOptions(
|
||||
Options=["default_voice:azelma"]
|
||||
)
|
||||
response = stub.LoadModel(load_request)
|
||||
self.assertTrue(response.success)
|
||||
|
||||
# Create temporary output file
|
||||
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
|
||||
output_path = tmp_file.name
|
||||
|
||||
# Test TTS without specifying voice (should use default)
|
||||
tts_request = backend_pb2.TTSRequest(
|
||||
text="Hello world, this is a test.",
|
||||
dst=output_path
|
||||
)
|
||||
tts_response = stub.TTS(tts_request)
|
||||
self.assertTrue(tts_response.success)
|
||||
|
||||
# Verify output file exists and is not empty
|
||||
self.assertTrue(os.path.exists(output_path))
|
||||
self.assertGreater(os.path.getsize(output_path), 0)
|
||||
|
||||
# Cleanup
|
||||
os.unlink(output_path)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
self.fail("TTS service with default voice failed")
|
||||
finally:
|
||||
self.tearDown()
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
backend_dir=$(dirname $0)
|
||||
if [ -d $backend_dir/common ]; then
|
||||
source $backend_dir/common/libbackend.sh
|
||||
else
|
||||
source $backend_dir/../common/libbackend.sh
|
||||
fi
|
||||
|
||||
runUnittests
|
||||
@@ -6,4 +6,4 @@ transformers
|
||||
bitsandbytes
|
||||
outetts
|
||||
sentence-transformers==5.2.0
|
||||
protobuf==6.33.4
|
||||
protobuf==6.33.2
|
||||
@@ -6,4 +6,4 @@ transformers
|
||||
bitsandbytes
|
||||
outetts
|
||||
sentence-transformers==5.2.0
|
||||
protobuf==6.33.4
|
||||
protobuf==6.33.2
|
||||
@@ -6,4 +6,4 @@ transformers
|
||||
bitsandbytes
|
||||
outetts
|
||||
sentence-transformers==5.2.0
|
||||
protobuf==6.33.4
|
||||
protobuf==6.33.2
|
||||
@@ -8,4 +8,4 @@ bitsandbytes
|
||||
outetts
|
||||
bitsandbytes
|
||||
sentence-transformers==5.2.0
|
||||
protobuf==6.33.4
|
||||
protobuf==6.33.2
|
||||
@@ -10,4 +10,4 @@ intel-extension-for-transformers
|
||||
bitsandbytes
|
||||
outetts
|
||||
sentence-transformers==5.2.0
|
||||
protobuf==6.33.4
|
||||
protobuf==6.33.2
|
||||
@@ -1,5 +1,5 @@
|
||||
grpcio==1.76.0
|
||||
protobuf==6.33.4
|
||||
protobuf==6.33.2
|
||||
certifi
|
||||
setuptools
|
||||
scipy==1.15.1
|
||||
|
||||
@@ -56,7 +56,7 @@ type RunCMD struct {
|
||||
UseSubtleKeyComparison bool `env:"LOCALAI_SUBTLE_KEY_COMPARISON" default:"false" help:"If true, API Key validation comparisons will be performed using constant-time comparisons rather than simple equality. This trades off performance on each request for resiliancy against timing attacks." group:"hardening"`
|
||||
DisableApiKeyRequirementForHttpGet bool `env:"LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET" default:"false" help:"If true, a valid API key is not required to issue GET requests to portions of the web ui. This should only be enabled in secure testing environments" group:"hardening"`
|
||||
DisableMetricsEndpoint bool `env:"LOCALAI_DISABLE_METRICS_ENDPOINT,DISABLE_METRICS_ENDPOINT" default:"false" help:"Disable the /metrics endpoint" group:"api"`
|
||||
HttpGetExemptedEndpoints []string `env:"LOCALAI_HTTP_GET_EXEMPTED_ENDPOINTS" default:"^/$,^/browse/?$,^/talk/?$,^/p2p/?$,^/chat/?$,^/image/?$,^/text2image/?$,^/tts/?$,^/static/.*$,^/swagger.*$" help:"If LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET is overriden to true, this is the list of endpoints to exempt. Only adjust this in case of a security incident or as a result of a personal security posture review" group:"hardening"`
|
||||
HttpGetExemptedEndpoints []string `env:"LOCALAI_HTTP_GET_EXEMPTED_ENDPOINTS" default:"^/$,^/browse/?$,^/talk/?$,^/p2p/?$,^/chat/?$,^/text2image/?$,^/tts/?$,^/static/.*$,^/swagger.*$" help:"If LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET is overriden to true, this is the list of endpoints to exempt. Only adjust this in case of a security incident or as a result of a personal security posture review" group:"hardening"`
|
||||
Peer2Peer bool `env:"LOCALAI_P2P,P2P" name:"p2p" default:"false" help:"Enable P2P mode" group:"p2p"`
|
||||
Peer2PeerDHTInterval int `env:"LOCALAI_P2P_DHT_INTERVAL,P2P_DHT_INTERVAL" default:"360" name:"p2p-dht-interval" help:"Interval for DHT refresh (used during token generation)" group:"p2p"`
|
||||
Peer2PeerOTPInterval int `env:"LOCALAI_P2P_OTP_INTERVAL,P2P_OTP_INTERVAL" default:"9000" name:"p2p-otp-interval" help:"Interval for OTP refresh (used during token generation)" group:"p2p"`
|
||||
|
||||
@@ -108,15 +108,7 @@ func API(application *application.Application) (*echo.Echo, error) {
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
err := next(c)
|
||||
|
||||
// Fix for #7989: Reduce log verbosity of Web UI polling
|
||||
// If the path is /api/operations and the request was successful (200),
|
||||
// we log it at DEBUG level (hidden by default) instead of INFO.
|
||||
if req.URL.Path == "/api/operations" && res.Status == 200 {
|
||||
xlog.Debug("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status)
|
||||
} else {
|
||||
xlog.Info("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status)
|
||||
}
|
||||
xlog.Info("HTTP request", "method", req.Method, "path", req.URL.Path, "status", res.Status)
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
@@ -219,7 +219,7 @@ func RegisterUIRoutes(app *echo.Echo,
|
||||
return c.Render(200, "views/chat", summary)
|
||||
})
|
||||
|
||||
app.GET("/image/:model", func(c echo.Context) error {
|
||||
app.GET("/text2image/:model", func(c echo.Context) error {
|
||||
modelConfigs := cl.GetAllModelsConfigs()
|
||||
modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY)
|
||||
|
||||
@@ -233,10 +233,10 @@ func RegisterUIRoutes(app *echo.Echo,
|
||||
}
|
||||
|
||||
// Render index
|
||||
return c.Render(200, "views/image", summary)
|
||||
return c.Render(200, "views/text2image", summary)
|
||||
})
|
||||
|
||||
app.GET("/image", func(c echo.Context) error {
|
||||
app.GET("/text2image", func(c echo.Context) error {
|
||||
modelConfigs := cl.GetAllModelsConfigs()
|
||||
modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY)
|
||||
|
||||
@@ -266,7 +266,7 @@ func RegisterUIRoutes(app *echo.Echo,
|
||||
}
|
||||
|
||||
// Render index
|
||||
return c.Render(200, "views/image", summary)
|
||||
return c.Render(200, "views/text2image", summary)
|
||||
})
|
||||
|
||||
app.GET("/tts/:model", func(c echo.Context) error {
|
||||
@@ -318,56 +318,6 @@ func RegisterUIRoutes(app *echo.Echo,
|
||||
return c.Render(200, "views/tts", summary)
|
||||
})
|
||||
|
||||
app.GET("/video/:model", func(c echo.Context) error {
|
||||
modelConfigs := cl.GetAllModelsConfigs()
|
||||
modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY)
|
||||
|
||||
summary := map[string]interface{}{
|
||||
"Title": "LocalAI - Generate videos with " + c.Param("model"),
|
||||
"BaseURL": middleware.BaseURL(c),
|
||||
"ModelsConfig": modelConfigs,
|
||||
"ModelsWithoutConfig": modelsWithoutConfig,
|
||||
"Model": c.Param("model"),
|
||||
"Version": internal.PrintableVersion(),
|
||||
}
|
||||
|
||||
// Render index
|
||||
return c.Render(200, "views/video", summary)
|
||||
})
|
||||
|
||||
app.GET("/video", func(c echo.Context) error {
|
||||
modelConfigs := cl.GetAllModelsConfigs()
|
||||
modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY)
|
||||
|
||||
if len(modelConfigs)+len(modelsWithoutConfig) == 0 {
|
||||
// If no model is available redirect to the index which suggests how to install models
|
||||
return c.Redirect(302, middleware.BaseURL(c))
|
||||
}
|
||||
|
||||
modelThatCanBeUsed := ""
|
||||
title := "LocalAI - Generate videos"
|
||||
|
||||
for _, b := range modelConfigs {
|
||||
if b.HasUsecases(config.FLAG_VIDEO) {
|
||||
modelThatCanBeUsed = b.Name
|
||||
title = "LocalAI - Generate videos with " + modelThatCanBeUsed
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
summary := map[string]interface{}{
|
||||
"Title": title,
|
||||
"BaseURL": middleware.BaseURL(c),
|
||||
"ModelsConfig": modelConfigs,
|
||||
"ModelsWithoutConfig": modelsWithoutConfig,
|
||||
"Model": modelThatCanBeUsed,
|
||||
"Version": internal.PrintableVersion(),
|
||||
}
|
||||
|
||||
// Render index
|
||||
return c.Render(200, "views/video", summary)
|
||||
})
|
||||
|
||||
// Traces UI
|
||||
app.GET("/traces", func(c echo.Context) error {
|
||||
summary := map[string]interface{}{
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
// Helper function to convert file to base64
|
||||
function fileToBase64(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// Remove data:image/...;base64, prefix if present
|
||||
const base64 = reader.result.split(',')[1] || reader.result;
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
function genVideo(event) {
|
||||
event.preventDefault();
|
||||
promptVideo();
|
||||
}
|
||||
|
||||
async function promptVideo() {
|
||||
const loader = document.getElementById("loader");
|
||||
const input = document.getElementById("input");
|
||||
const generateBtn = document.getElementById("generate-btn");
|
||||
const resultDiv = document.getElementById("result");
|
||||
const resultPlaceholder = document.getElementById("result-placeholder");
|
||||
|
||||
// Show loader and disable form
|
||||
loader.classList.remove("hidden");
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
input.disabled = true;
|
||||
generateBtn.disabled = true;
|
||||
|
||||
// Store the prompt for later restoration
|
||||
const prompt = input.value.trim();
|
||||
if (!prompt) {
|
||||
alert("Please enter a prompt");
|
||||
loader.classList.add("hidden");
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "flex";
|
||||
}
|
||||
input.disabled = false;
|
||||
generateBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all form values
|
||||
const model = document.getElementById("video-model").value;
|
||||
const size = document.getElementById("video-size").value;
|
||||
const negativePrompt = document.getElementById("negative-prompt").value.trim();
|
||||
|
||||
// Parse size into width and height
|
||||
const sizeParts = size.split("x");
|
||||
let width = 512;
|
||||
let height = 512;
|
||||
if (sizeParts.length === 2) {
|
||||
width = parseInt(sizeParts[0]) || 512;
|
||||
height = parseInt(sizeParts[1]) || 512;
|
||||
}
|
||||
|
||||
// Video-specific parameters
|
||||
const secondsInput = document.getElementById("video-seconds").value.trim();
|
||||
const seconds = secondsInput ? secondsInput : undefined;
|
||||
const fpsInput = document.getElementById("video-fps").value.trim();
|
||||
const fps = fpsInput ? parseInt(fpsInput) : 16;
|
||||
const framesInput = document.getElementById("video-frames").value.trim();
|
||||
const numFrames = framesInput ? parseInt(framesInput) : undefined;
|
||||
|
||||
// Advanced parameters
|
||||
const stepInput = document.getElementById("video-steps").value.trim();
|
||||
const step = stepInput ? parseInt(stepInput) : undefined;
|
||||
const seedInput = document.getElementById("video-seed").value.trim();
|
||||
const seed = seedInput ? parseInt(seedInput) : undefined;
|
||||
const cfgScaleInput = document.getElementById("video-cfg-scale").value.trim();
|
||||
const cfgScale = cfgScaleInput ? parseFloat(cfgScaleInput) : undefined;
|
||||
|
||||
// Prepare request body
|
||||
const requestBody = {
|
||||
model: model,
|
||||
prompt: prompt,
|
||||
width: width,
|
||||
height: height,
|
||||
fps: fps,
|
||||
};
|
||||
|
||||
if (negativePrompt) {
|
||||
requestBody.negative_prompt = negativePrompt;
|
||||
}
|
||||
|
||||
if (seconds !== undefined) {
|
||||
requestBody.seconds = seconds;
|
||||
}
|
||||
|
||||
if (numFrames !== undefined) {
|
||||
requestBody.num_frames = numFrames;
|
||||
}
|
||||
|
||||
if (step !== undefined) {
|
||||
requestBody.step = step;
|
||||
}
|
||||
|
||||
if (seed !== undefined) {
|
||||
requestBody.seed = seed;
|
||||
}
|
||||
|
||||
if (cfgScale !== undefined) {
|
||||
requestBody.cfg_scale = cfgScale;
|
||||
}
|
||||
|
||||
// Handle file inputs
|
||||
try {
|
||||
// Start image (for img2video)
|
||||
const startImageInput = document.getElementById("start-image");
|
||||
if (startImageInput.files.length > 0) {
|
||||
const base64 = await fileToBase64(startImageInput.files[0]);
|
||||
requestBody.start_image = base64;
|
||||
}
|
||||
|
||||
// End image
|
||||
const endImageInput = document.getElementById("end-image");
|
||||
if (endImageInput.files.length > 0) {
|
||||
const base64 = await fileToBase64(endImageInput.files[0]);
|
||||
requestBody.end_image = base64;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing image files:", error);
|
||||
resultDiv.innerHTML = '<p class="text-xs text-red-500 p-2">Error processing image files: ' + error.message + '</p>';
|
||||
loader.classList.add("hidden");
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
input.disabled = false;
|
||||
generateBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make API request
|
||||
try {
|
||||
const response = await fetch("v1/videos/generations", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
if (json.error) {
|
||||
// Display error
|
||||
resultDiv.innerHTML = '<p class="text-xs text-red-500 p-2">Error: ' + json.error.message + '</p>';
|
||||
loader.classList.add("hidden");
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
input.disabled = false;
|
||||
generateBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear result div and hide placeholder
|
||||
resultDiv.innerHTML = '';
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
|
||||
// Display generated video
|
||||
if (json.data && json.data.length > 0) {
|
||||
json.data.forEach((item, index) => {
|
||||
const videoContainer = document.createElement("div");
|
||||
videoContainer.className = "flex flex-col";
|
||||
|
||||
// Create video element
|
||||
const video = document.createElement("video");
|
||||
video.controls = true;
|
||||
video.className = "w-full h-auto rounded-lg";
|
||||
video.preload = "metadata";
|
||||
|
||||
if (item.url) {
|
||||
video.src = item.url;
|
||||
} else if (item.b64_json) {
|
||||
video.src = "data:video/mp4;base64," + item.b64_json;
|
||||
} else {
|
||||
return; // Skip invalid items
|
||||
}
|
||||
|
||||
videoContainer.appendChild(video);
|
||||
|
||||
// Create caption container
|
||||
const captionDiv = document.createElement("div");
|
||||
captionDiv.className = "mt-2 p-2 bg-[var(--color-bg-secondary)] rounded-lg text-xs";
|
||||
|
||||
// Prompt caption
|
||||
const promptCaption = document.createElement("p");
|
||||
promptCaption.className = "text-[var(--color-text-primary)] mb-1.5 break-words";
|
||||
promptCaption.innerHTML = '<strong>Prompt:</strong> ' + escapeHtml(prompt);
|
||||
captionDiv.appendChild(promptCaption);
|
||||
|
||||
// Negative prompt if provided
|
||||
if (negativePrompt) {
|
||||
const negativeCaption = document.createElement("p");
|
||||
negativeCaption.className = "text-[var(--color-text-secondary)] mb-1.5 break-words";
|
||||
negativeCaption.innerHTML = '<strong>Negative Prompt:</strong> ' + escapeHtml(negativePrompt);
|
||||
captionDiv.appendChild(negativeCaption);
|
||||
}
|
||||
|
||||
// Generation details
|
||||
const detailsDiv = document.createElement("div");
|
||||
detailsDiv.className = "flex flex-wrap gap-3 text-[10px] text-[var(--color-text-secondary)] mt-1.5";
|
||||
detailsDiv.innerHTML = `
|
||||
<span><strong>Size:</strong> ${width}x${height}</span>
|
||||
${fps ? `<span><strong>FPS:</strong> ${fps}</span>` : ''}
|
||||
${numFrames !== undefined ? `<span><strong>Frames:</strong> ${numFrames}</span>` : ''}
|
||||
${seconds !== undefined ? `<span><strong>Duration:</strong> ${seconds}s</span>` : ''}
|
||||
${step !== undefined ? `<span><strong>Steps:</strong> ${step}</span>` : ''}
|
||||
${seed !== undefined ? `<span><strong>Seed:</strong> ${seed}</span>` : ''}
|
||||
${cfgScale !== undefined ? `<span><strong>CFG Scale:</strong> ${cfgScale}</span>` : ''}
|
||||
`;
|
||||
captionDiv.appendChild(detailsDiv);
|
||||
|
||||
// Copy prompt button
|
||||
const copyBtn = document.createElement("button");
|
||||
copyBtn.className = "mt-1.5 px-2 py-0.5 text-[10px] bg-[var(--color-primary)] text-white rounded hover:opacity-80";
|
||||
copyBtn.innerHTML = '<i class="fas fa-copy mr-1"></i>Copy Prompt';
|
||||
copyBtn.onclick = () => {
|
||||
navigator.clipboard.writeText(prompt).then(() => {
|
||||
copyBtn.innerHTML = '<i class="fas fa-check mr-1"></i>Copied!';
|
||||
setTimeout(() => {
|
||||
copyBtn.innerHTML = '<i class="fas fa-copy mr-1"></i>Copy Prompt';
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
captionDiv.appendChild(copyBtn);
|
||||
|
||||
videoContainer.appendChild(captionDiv);
|
||||
resultDiv.appendChild(videoContainer);
|
||||
});
|
||||
// Hide placeholder when videos are displayed
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
} else {
|
||||
resultDiv.innerHTML = '<p class="text-xs text-[var(--color-text-secondary)] p-2">No videos were generated.</p>';
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error generating video:", error);
|
||||
resultDiv.innerHTML = '<p class="text-xs text-red-500 p-2">Error: ' + error.message + '</p>';
|
||||
if (resultPlaceholder) {
|
||||
resultPlaceholder.style.display = "none";
|
||||
}
|
||||
} finally {
|
||||
// Hide loader and re-enable form
|
||||
loader.classList.add("hidden");
|
||||
input.disabled = false;
|
||||
generateBtn.disabled = false;
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to escape HTML
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const input = document.getElementById("input");
|
||||
const form = document.getElementById("genvideo");
|
||||
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
|
||||
if (form) {
|
||||
form.addEventListener("submit", genVideo);
|
||||
}
|
||||
|
||||
// Handle Enter key press in the prompt input (but allow Shift+Enter for new lines)
|
||||
if (input) {
|
||||
input.addEventListener("keydown", function(event) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
genVideo(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hide loader initially
|
||||
const loader = document.getElementById("loader");
|
||||
if (loader) {
|
||||
loader.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
@@ -315,7 +315,7 @@
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if eq . "FLAG_IMAGE" }}
|
||||
<a href="image/{{$backendCfg.Name}}" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[var(--color-success)]/10 text-green-300 hover:bg-[var(--color-success)]/20 transition-colors" title="Image">
|
||||
<a href="text2image/{{$backendCfg.Name}}" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[var(--color-success)]/10 text-green-300 hover:bg-[var(--color-success)]/20 transition-colors" title="Image">
|
||||
<i class="fas fa-image text-[8px] mr-1"></i>Image
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<a href="chat/" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-2 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-secondary)] flex items-center group text-sm">
|
||||
<i class="fa-solid fa-comments text-[var(--color-primary)] mr-1.5 text-sm group-hover:scale-110 transition-transform"></i>Chat
|
||||
</a>
|
||||
<a href="image/" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-2 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-secondary)] flex items-center group text-sm">
|
||||
<a href="text2image/" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-2 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-secondary)] flex items-center group text-sm">
|
||||
<i class="fas fa-image text-[var(--color-primary)] mr-1.5 text-sm group-hover:scale-110 transition-transform"></i>Images
|
||||
</a>
|
||||
<a href="tts/" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-2 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-secondary)] flex items-center group text-sm">
|
||||
@@ -85,7 +85,7 @@
|
||||
<a href="chat/" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center text-sm">
|
||||
<i class="fa-solid fa-comments text-[var(--color-primary)] mr-3 w-5 text-center text-sm"></i>Chat
|
||||
</a>
|
||||
<a href="image/" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center text-sm">
|
||||
<a href="text2image/" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center text-sm">
|
||||
<i class="fas fa-image text-[var(--color-primary)] mr-3 w-5 text-center text-sm"></i>Images
|
||||
</a>
|
||||
<a href="tts/" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center text-sm">
|
||||
|
||||
@@ -28,19 +28,19 @@
|
||||
{{ $cfg := . }}
|
||||
{{ range .KnownUsecaseStrings }}
|
||||
{{ if eq . "FLAG_IMAGE" }}
|
||||
<option value="image/{{$cfg.Name}}" {{ if eq $cfg.Name $model }} selected {{end}} class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{$cfg.Name}}</option>
|
||||
<option value="text2image/{{$cfg.Name}}" {{ if eq $cfg.Name $model }} selected {{end}} class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{$cfg.Name}}</option>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range .ModelsWithoutConfig }}
|
||||
<option value="image/{{.}}" {{ if eq . $model }} selected {{ end }} class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{.}}</option>
|
||||
<option value="text2image/{{.}}" {{ if eq . $model }} selected {{ end }} class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input id="image-model" type="hidden" value="{{.Model}}">
|
||||
<form id="genimage" action="image/{{.Model}}" method="get">
|
||||
<form id="genimage" action="text2image/{{.Model}}" method="get">
|
||||
<!-- Basic Settings -->
|
||||
<div class="space-y-2">
|
||||
<!-- Prompt -->
|
||||
@@ -326,4 +326,4 @@
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,315 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{template "views/partials/head" .}}
|
||||
<script defer src="static/video.js"></script>
|
||||
|
||||
<body class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] flex flex-col h-screen">
|
||||
<div class="flex flex-col flex-1 overflow-hidden">
|
||||
|
||||
{{template "views/partials/navbar" .}}
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Two Column Layout: Settings on Left, Preview on Right -->
|
||||
<div class="flex flex-col lg:flex-row flex-1 gap-4 p-4 overflow-hidden">
|
||||
<!-- Left Column: Generation Settings -->
|
||||
<div class="flex-shrink-0 lg:w-1/4 flex flex-col min-h-0">
|
||||
<div class="card p-3 space-y-3 overflow-y-auto flex-1">
|
||||
<!-- Model Selection - Compact -->
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<label class="text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide flex-shrink-0">Model</label>
|
||||
</div>
|
||||
<select x-data="{ link : '' }" x-model="link" x-init="$watch('link', value => window.location = link)"
|
||||
id="model-select"
|
||||
class="input w-full p-1.5 text-xs"
|
||||
>
|
||||
<option value="" disabled class="text-[var(--color-text-secondary)]">Select a model</option>
|
||||
{{ $model:=.Model}}
|
||||
{{ range .ModelsConfig }}
|
||||
{{ $cfg := . }}
|
||||
{{ range .KnownUsecaseStrings }}
|
||||
{{ if eq . "FLAG_VIDEO" }}
|
||||
<option value="video/{{$cfg.Name}}" {{ if eq $cfg.Name $model }} selected {{end}} class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{$cfg.Name}}</option>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range .ModelsWithoutConfig }}
|
||||
<option value="video/{{.}}" {{ if eq . $model }} selected {{ end }} class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input id="video-model" type="hidden" value="{{.Model}}">
|
||||
<form id="genvideo" action="video/{{.Model}}" method="get">
|
||||
<!-- Basic Settings -->
|
||||
<div class="space-y-2">
|
||||
<!-- Prompt -->
|
||||
<div class="space-y-1">
|
||||
<label for="input" class="block text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
|
||||
<i class="fas fa-magic mr-1.5 text-[var(--color-primary)]"></i>Prompt
|
||||
</label>
|
||||
<textarea
|
||||
id="input"
|
||||
name="input"
|
||||
placeholder="Describe the video you want to generate..."
|
||||
autocomplete="off"
|
||||
rows="3"
|
||||
class="input w-full p-1.5 text-xs resize-y"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Negative Prompt -->
|
||||
<div class="space-y-1">
|
||||
<label for="negative-prompt" class="block text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
|
||||
<i class="fas fa-ban mr-1.5 text-[var(--color-primary)]"></i>Negative Prompt
|
||||
</label>
|
||||
<textarea
|
||||
id="negative-prompt"
|
||||
name="negative-prompt"
|
||||
placeholder="Things to avoid in the video..."
|
||||
rows="2"
|
||||
class="input w-full p-1.5 text-xs resize-y"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Size Selection with Presets -->
|
||||
<div class="space-y-1">
|
||||
<label for="video-size" class="block text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
|
||||
<i class="fas fa-expand-arrows-alt mr-1.5 text-[var(--color-primary)]"></i>Video Size
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-1.5 mb-1.5">
|
||||
<button type="button" class="size-preset px-2 py-0.5 text-[10px] rounded border border-[var(--color-border)] hover:bg-[var(--color-bg-secondary)]" data-size="256x256">256×256</button>
|
||||
<button type="button" class="size-preset px-2 py-0.5 text-[10px] rounded border border-[var(--color-border)] hover:bg-[var(--color-bg-secondary)]" data-size="512x512">512×512</button>
|
||||
<button type="button" class="size-preset px-2 py-0.5 text-[10px] rounded border border-[var(--color-border)] hover:bg-[var(--color-bg-secondary)]" data-size="768x768">768×768</button>
|
||||
<button type="button" class="size-preset px-2 py-0.5 text-[10px] rounded border border-[var(--color-border)] hover:bg-[var(--color-bg-secondary)]" data-size="1024x1024">1024×1024</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
id="video-size"
|
||||
value="512x512"
|
||||
placeholder="e.g., 256x256, 512x512, 1024x1024"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Video Duration / FPS / Frames -->
|
||||
<div class="space-y-1">
|
||||
<label for="video-seconds" class="block text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
|
||||
<i class="fas fa-clock mr-1.5 text-[var(--color-primary)]"></i>Duration (seconds)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="video-seconds"
|
||||
name="seconds"
|
||||
min="1"
|
||||
max="60"
|
||||
placeholder="Leave empty for default"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="video-fps" class="block text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
|
||||
<i class="fas fa-film mr-1.5 text-[var(--color-primary)]"></i>FPS
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="video-fps"
|
||||
name="fps"
|
||||
min="1"
|
||||
max="60"
|
||||
value="16"
|
||||
placeholder="Frames per second"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="video-frames" class="block text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">
|
||||
<i class="fas fa-images mr-1.5 text-[var(--color-primary)]"></i>Number of Frames
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="video-frames"
|
||||
name="num_frames"
|
||||
min="1"
|
||||
max="500"
|
||||
placeholder="Leave empty for default"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Settings (Collapsible) -->
|
||||
<div class="space-y-2">
|
||||
<button type="button" id="advanced-toggle" class="w-full flex items-center justify-between px-2 py-1.5 text-xs rounded text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] transition-colors">
|
||||
<span><i class="fa-solid fa-sliders mr-1.5 text-[var(--color-primary)]"></i> Advanced Settings</span>
|
||||
<i class="fas fa-chevron-down text-[10px]" id="advanced-chevron"></i>
|
||||
</button>
|
||||
<div id="advanced-settings" class="hidden p-2 bg-[var(--color-bg-secondary)] border border-[var(--color-primary-border)]/20 rounded pl-4 border-l-2 border-[var(--color-bg-secondary)] space-y-2">
|
||||
<!-- Steps -->
|
||||
<div class="space-y-1">
|
||||
<label for="video-steps" class="block text-xs text-[var(--color-text-secondary)]">
|
||||
<i class="fas fa-step-forward mr-1.5 text-[var(--color-primary)]"></i>Steps
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="video-steps"
|
||||
name="step"
|
||||
min="1"
|
||||
max="100"
|
||||
placeholder="Leave empty for default"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Seed -->
|
||||
<div class="space-y-1">
|
||||
<label for="video-seed" class="block text-xs text-[var(--color-text-secondary)]">
|
||||
<i class="fas fa-seedling mr-1.5 text-[var(--color-primary)]"></i>Seed
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="video-seed"
|
||||
name="seed"
|
||||
min="0"
|
||||
placeholder="Leave empty for random"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- CFG Scale -->
|
||||
<div class="space-y-1">
|
||||
<label for="video-cfg-scale" class="block text-xs text-[var(--color-text-secondary)]">
|
||||
<i class="fas fa-sliders-h mr-1.5 text-[var(--color-primary)]"></i>CFG Scale
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="video-cfg-scale"
|
||||
name="cfg_scale"
|
||||
min="0"
|
||||
max="20"
|
||||
step="0.1"
|
||||
placeholder="Leave empty for default"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Inputs (Collapsible) -->
|
||||
<div class="space-y-2">
|
||||
<button type="button" id="image-inputs-toggle" class="w-full flex items-center justify-between px-2 py-1.5 text-xs rounded text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] transition-colors">
|
||||
<span><i class="fa-solid fa-image mr-1.5 text-[var(--color-primary)]"></i> Image Inputs</span>
|
||||
<i class="fas fa-chevron-down text-[10px]" id="image-inputs-chevron"></i>
|
||||
</button>
|
||||
<div id="image-inputs-settings" class="hidden p-2 bg-[var(--color-bg-secondary)] border border-[var(--color-primary-border)]/20 rounded pl-4 border-l-2 border-[var(--color-bg-secondary)] space-y-2">
|
||||
<!-- Start Image (img2video) -->
|
||||
<div class="space-y-1">
|
||||
<label for="start-image" class="block text-xs text-[var(--color-text-secondary)]">
|
||||
<i class="fas fa-play-circle mr-1.5 text-[var(--color-primary)]"></i>Start Image (img2video)
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="start-image"
|
||||
name="start_image"
|
||||
accept="image/*"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- End Image -->
|
||||
<div class="space-y-1">
|
||||
<label for="end-image" class="block text-xs text-[var(--color-text-secondary)]">
|
||||
<i class="fas fa-stop-circle mr-1.5 text-[var(--color-primary)]"></i>End Image
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="end-image"
|
||||
name="end_image"
|
||||
accept="image/*"
|
||||
class="input p-1.5 text-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
id="generate-btn"
|
||||
class="w-full px-2 py-1.5 text-xs rounded text-[var(--color-bg-primary)] bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 transition-colors font-medium"
|
||||
>
|
||||
<i class="fas fa-video mr-1.5"></i>Generate Video
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Video Preview -->
|
||||
<div class="flex-grow lg:w-3/4 flex flex-col min-h-0">
|
||||
<div class="relative flex-1 min-h-0 overflow-y-auto">
|
||||
<!-- Loading Animation -->
|
||||
<div id="loader" class="hidden absolute inset-0 flex items-center justify-center bg-[var(--color-bg-primary)]/80 rounded-xl z-10">
|
||||
<div class="text-center">
|
||||
<svg class="animate-spin h-10 w-10 text-[var(--color-primary)] mx-auto mb-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="text-xs text-[var(--color-text-secondary)]">Generating video...</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Placeholder when no videos -->
|
||||
<div id="result-placeholder" class="min-h-[400px] flex items-center justify-center flex-shrink-0">
|
||||
<p class="text-xs text-[var(--color-text-secondary)] italic text-center">Your generated videos will appear here</p>
|
||||
</div>
|
||||
<!-- Results container -->
|
||||
<div id="result" class="grid grid-cols-1 gap-4 pb-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Collapsible sections
|
||||
document.getElementById('advanced-toggle').addEventListener('click', function() {
|
||||
const settings = document.getElementById('advanced-settings');
|
||||
const chevron = document.getElementById('advanced-chevron');
|
||||
settings.classList.toggle('hidden');
|
||||
chevron.classList.toggle('fa-chevron-down');
|
||||
chevron.classList.toggle('fa-chevron-up');
|
||||
});
|
||||
|
||||
document.getElementById('image-inputs-toggle').addEventListener('click', function() {
|
||||
const settings = document.getElementById('image-inputs-settings');
|
||||
const chevron = document.getElementById('image-inputs-chevron');
|
||||
settings.classList.toggle('hidden');
|
||||
chevron.classList.toggle('fa-chevron-down');
|
||||
chevron.classList.toggle('fa-chevron-up');
|
||||
});
|
||||
|
||||
// Size preset buttons
|
||||
document.querySelectorAll('.size-preset').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const size = this.getAttribute('data-size');
|
||||
document.getElementById('video-size').value = size;
|
||||
// Update active state
|
||||
document.querySelectorAll('.size-preset').forEach(btn => {
|
||||
btn.classList.remove('bg-[var(--color-primary)]', 'text-white');
|
||||
});
|
||||
this.classList.add('bg-[var(--color-primary)]', 'text-white');
|
||||
});
|
||||
});
|
||||
|
||||
// Set initial active size preset
|
||||
document.querySelector('.size-preset[data-size="512x512"]').classList.add('bg-[var(--color-primary)]', 'text-white');
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -164,57 +164,6 @@ curl http://localhost:8080/tts -H "Content-Type: application/json" -d '{
|
||||
}' | aplay
|
||||
```
|
||||
|
||||
### Pocket TTS
|
||||
|
||||
[Pocket TTS](https://github.com/kyutai-labs/pocket-tts) is a lightweight text-to-speech model designed to run efficiently on CPUs. It supports voice cloning through HuggingFace voice URLs or local audio files.
|
||||
|
||||
#### Setup
|
||||
|
||||
Install the `pocket-tts` model in the Model gallery or run `local-ai run models install pocket-tts`.
|
||||
|
||||
#### Usage
|
||||
|
||||
Use the tts endpoint by specifying the pocket-tts backend:
|
||||
|
||||
```
|
||||
curl http://localhost:8080/tts -H "Content-Type: application/json" -d '{
|
||||
"model": "pocket-tts",
|
||||
"input":"Hello world, this is a test."
|
||||
}' | aplay
|
||||
```
|
||||
|
||||
#### Voice cloning
|
||||
|
||||
Pocket TTS supports voice cloning through built-in voice names, HuggingFace URLs, or local audio files. You can configure a model with a specific voice:
|
||||
|
||||
```yaml
|
||||
name: pocket-tts
|
||||
backend: pocket-tts
|
||||
tts:
|
||||
voice: "azelma" # Built-in voice name
|
||||
# Or use HuggingFace URL: "hf://kyutai/tts-voices/alba-mackenna/casual.wav"
|
||||
# Or use local file path: "path/to/voice.wav"
|
||||
# Available built-in voices: alba, marius, javert, jean, fantine, cosette, eponine, azelma
|
||||
```
|
||||
|
||||
You can also pre-load a default voice for faster first generation:
|
||||
|
||||
```yaml
|
||||
name: pocket-tts
|
||||
backend: pocket-tts
|
||||
options:
|
||||
- "default_voice:azelma" # Pre-load this voice when model loads
|
||||
```
|
||||
|
||||
Then you can use the model:
|
||||
|
||||
```
|
||||
curl http://localhost:8080/tts -H "Content-Type: application/json" -d '{
|
||||
"model": "pocket-tts",
|
||||
"input":"Hello world, this is a test."
|
||||
}' | aplay
|
||||
```
|
||||
|
||||
### Vall-E-X
|
||||
|
||||
[VALL-E-X](https://github.com/Plachtaa/VALL-E-X) is an open source implementation of Microsoft's VALL-E X zero-shot TTS model.
|
||||
|
||||
@@ -20,7 +20,7 @@ Choose the installation method that best suits your needs:
|
||||
|
||||
1. **[Docker](docker/)** ⭐ **Recommended** - Works on all platforms, easiest setup
|
||||
2. **[macOS](macos/)** - Download and install the DMG application
|
||||
3. **[Linux](linux/)** - Install on Linux using binaries (install.sh script currently has issues - see [issue #8032](https://github.com/mudler/LocalAI/issues/8032))
|
||||
3. **[Linux](linux/)** - Install on Linux using the one-liner script or binaries
|
||||
4. **[Kubernetes](kubernetes/)** - Deploy LocalAI on Kubernetes clusters
|
||||
5. **[Build from Source](build/)** - Build LocalAI from source code
|
||||
|
||||
@@ -36,6 +36,6 @@ This will start LocalAI. The API will be available at `http://localhost:8080`. F
|
||||
|
||||
For other platforms:
|
||||
- **macOS**: Download the [DMG](macos/)
|
||||
- **Linux**: See the [Linux installation guide](linux/) for installation options. **Note:** The `install.sh` script is currently experiencing issues - see [issue #8032](https://github.com/mudler/LocalAI/issues/8032) for details.
|
||||
- **Linux**: Use the `curl https://localai.io/install.sh | sh` [one-liner](linux/)
|
||||
|
||||
For detailed instructions, see the [Docker installation guide](docker/).
|
||||
|
||||
@@ -6,11 +6,7 @@ url: '/installation/linux/'
|
||||
---
|
||||
|
||||
|
||||
## One-Line Installer
|
||||
|
||||
{{% notice warning %}}
|
||||
**The `install.sh` script is currently experiencing issues and may produce broken or misconfigured installations. Please use alternative installation methods (Docker or manual binary installation) until [issue #8032](https://github.com/mudler/LocalAI/issues/8032) is resolved.**
|
||||
{{% /notice %}}
|
||||
## One-Line Installer (Recommended)
|
||||
|
||||
The fastest way to install LocalAI on Linux is with the installation script:
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ For more information on VRAM management, see [VRAM and Memory Management]({{%rel
|
||||
| `--opaque-errors` | `false` | If true, all error responses are replaced with blank 500 errors. This is intended only for hardening against information leaks and is normally not recommended | `$LOCALAI_OPAQUE_ERRORS` |
|
||||
| `--use-subtle-key-comparison` | `false` | If true, API Key validation comparisons will be performed using constant-time comparisons rather than simple equality. This trades off performance on each request for resilience against timing attacks | `$LOCALAI_SUBTLE_KEY_COMPARISON` |
|
||||
| `--disable-api-key-requirement-for-http-get` | `false` | If true, a valid API key is not required to issue GET requests to portions of the web UI. This should only be enabled in secure testing environments | `$LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET` |
|
||||
| `--http-get-exempted-endpoints` | `^/$,^/browse/?$,^/talk/?$,^/p2p/?$,^/chat/?$,^/image/?$,^/text2image/?$,^/tts/?$,^/static/.*$,^/swagger.*$` | If `--disable-api-key-requirement-for-http-get` is overridden to true, this is the list of endpoints to exempt. Only adjust this in case of a security incident or as a result of a personal security posture review | `$LOCALAI_HTTP_GET_EXEMPTED_ENDPOINTS` |
|
||||
| `--http-get-exempted-endpoints` | `^/$,^/browse/?$,^/talk/?$,^/p2p/?$,^/chat/?$,^/text2image/?$,^/tts/?$,^/static/.*$,^/swagger.*$` | If `--disable-api-key-requirement-for-http-get` is overridden to true, this is the list of endpoints to exempt. Only adjust this in case of a security incident or as a result of a personal security posture review | `$LOCALAI_HTTP_GET_EXEMPTED_ENDPOINTS` |
|
||||
|
||||
## P2P Flags
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ LocalAI will attempt to automatically load models which are not explicitly confi
|
||||
| [silero-vad](https://github.com/snakers4/silero-vad) with [Golang bindings](https://github.com/streamer45/silero-vad-go) | Silero VAD | no | Voice Activity Detection | no | no | CPU |
|
||||
| [neutts](https://github.com/neuphonic/neuttsair) | NeuTTSAir | no | Text-to-speech with voice cloning | no | no | CUDA 12/13, ROCm, CPU |
|
||||
| [vibevoice](https://github.com/microsoft/VibeVoice) | VibeVoice-Realtime | no | Real-time text-to-speech with voice cloning | no | no | CUDA 12/13, ROCm, Intel, CPU |
|
||||
| [pocket-tts](https://github.com/kyutai-labs/pocket-tts) | Pocket TTS | no | Lightweight CPU-based text-to-speech with voice cloning | no | no | CUDA 12/13, ROCm, Intel, CPU |
|
||||
| [mlx-audio](https://github.com/Blaizzy/mlx-audio) | MLX | no | Text-tospeech | no | no | Metal (Apple Silicon) |
|
||||
|
||||
## Image & Video Generation
|
||||
|
||||
@@ -478,16 +478,6 @@
|
||||
- filename: voices/streaming_model/en-Davis_man.pt
|
||||
uri: https://raw.githubusercontent.com/microsoft/VibeVoice/main/demo/voices/streaming_model/en-Davis_man.pt
|
||||
sha256: 67561d63bfa2153616e4c02fd967007c182593fc53738a6ad94bf5f84e8832ac
|
||||
- &pocket-tts
|
||||
url: "github:mudler/LocalAI/gallery/pocket-tts.yaml@master"
|
||||
icon: https://avatars.githubusercontent.com/u/6154722?s=200&v=4
|
||||
license: mit
|
||||
tags:
|
||||
- text-to-speech
|
||||
- TTS
|
||||
name: "pocket-tts"
|
||||
urls:
|
||||
- https://github.com/kyutai-labs/pocket-tts
|
||||
- &qwen3vl
|
||||
url: "github:mudler/LocalAI/gallery/qwen3.yaml@master"
|
||||
icon: https://cdn-avatars.huggingface.co/v1/production/uploads/620760a26e3b7210c2ff1943/-s1gyJfvbE1RgO5iBeNOi.png
|
||||
@@ -1247,63 +1237,6 @@
|
||||
cuda: true
|
||||
pipeline_type: QwenImageEditPipeline
|
||||
enable_parameters: num_inference_steps,image
|
||||
- <x2
|
||||
name: "ltx-2"
|
||||
url: "github:mudler/LocalAI/gallery/virtual.yaml@master"
|
||||
urls:
|
||||
- https://huggingface.co/Lightricks/LTX-2
|
||||
license: ltx-2-community-license-agreement
|
||||
tags:
|
||||
- diffusers
|
||||
- gpu
|
||||
- image-to-video
|
||||
- video-generation
|
||||
- audio-video
|
||||
description: |
|
||||
**LTX-2** is a DiT-based audio-video foundation model designed to generate synchronized video and audio within a single model. It brings together the core building blocks of modern video generation, with open weights and a focus on practical, local execution.
|
||||
|
||||
**Key Features:**
|
||||
- **Joint Audio-Video Generation**: Generates synchronized video and audio in a single model
|
||||
- **Image-to-Video**: Converts static images into dynamic videos with matching audio
|
||||
- **High Quality**: Produces realistic video with natural motion and synchronized audio
|
||||
- **Open Weights**: Available under the LTX-2 Community License Agreement
|
||||
|
||||
**Model Details:**
|
||||
- **Model Type**: Diffusion-based audio-video foundation model
|
||||
- **Architecture**: DiT (Diffusion Transformer) based
|
||||
- **Developed by**: Lightricks
|
||||
- **Paper**: [LTX-2: Efficient Joint Audio-Visual Foundation Model](https://arxiv.org/abs/2601.03233)
|
||||
|
||||
**Usage Tips:**
|
||||
- Width & height settings must be divisible by 32
|
||||
- Frame count must be divisible by 8 + 1 (e.g., 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 113, 121)
|
||||
- Recommended settings: width=768, height=512, num_frames=121, frame_rate=24.0
|
||||
- For best results, use detailed prompts describing motion and scene dynamics
|
||||
|
||||
**Limitations:**
|
||||
- This model is not intended or able to provide factual information
|
||||
- Prompt following is heavily influenced by the prompting-style
|
||||
- When generating audio without speech, the audio may be of lower quality
|
||||
|
||||
**Citation:**
|
||||
```bibtex
|
||||
@article{hacohen2025ltx2,
|
||||
title={LTX-2: Efficient Joint Audio-Visual Foundation Model},
|
||||
author={HaCohen, Yoav and Brazowski, Benny and Chiprut, Nisan and others},
|
||||
journal={arXiv preprint arXiv:2601.03233},
|
||||
year={2025}
|
||||
}
|
||||
```
|
||||
overrides:
|
||||
backend: diffusers
|
||||
low_vram: true
|
||||
parameters:
|
||||
model: Lightricks/LTX-2
|
||||
diffusers:
|
||||
cuda: true
|
||||
pipeline_type: LTX2ImageToVideoPipeline
|
||||
options:
|
||||
- torch_dtype:bf16
|
||||
- &gptoss
|
||||
name: "gpt-oss-20b"
|
||||
url: "github:mudler/LocalAI/gallery/harmony.yaml@master"
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: localai
|
||||
|
||||
config_file: |-
|
||||
name: pocket-tts
|
||||
backend: pocket-tts
|
||||
description: |
|
||||
Pocket TTS is a lightweight text-to-speech model designed to run efficiently on CPUs.
|
||||
This model supports voice cloning through HuggingFace voice URLs or local audio files.
|
||||
|
||||
parameters:
|
||||
model: ""
|
||||
|
||||
# TTS configuration
|
||||
tts:
|
||||
# Voice selection - can be:
|
||||
# 1. Built-in voice name (e.g., "alba", "marius", "javert", "jean", "fantine", "cosette", "eponine", "azelma")
|
||||
# 2. HuggingFace URL (e.g., "hf://kyutai/tts-voices/alba-mackenna/casual.wav")
|
||||
# 3. Local file path (relative to model directory or absolute)
|
||||
# voice: "azelma"
|
||||
# Alternative: use audio_path to specify a voice file directly
|
||||
# audio_path: "hf://kyutai/tts-voices/alba-mackenna/casual.wav"
|
||||
|
||||
known_usecases:
|
||||
- tts
|
||||
|
||||
# Backend-specific options
|
||||
# These are passed as "key:value" strings to the backend
|
||||
options:
|
||||
# Default voice to pre-load (optional)
|
||||
# Can be a voice name or HuggingFace URL
|
||||
# If set, this voice will be loaded when the model loads for faster first generation
|
||||
- "default_voice:azelma"
|
||||
# - "default_voice:hf://kyutai/tts-voices/alba-mackenna/casual.wav"
|
||||
16
go.mod
16
go.mod
@@ -6,7 +6,7 @@ toolchain go1.24.5
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
fyne.io/fyne/v2 v2.7.2
|
||||
fyne.io/fyne/v2 v2.7.1
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/alecthomas/kong v1.13.0
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/gofrs/flock v0.13.0
|
||||
github.com/google/go-containerregistry v0.20.7
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gpustack/gguf-parser-go v0.23.1
|
||||
github.com/gpustack/gguf-parser-go v0.22.1
|
||||
github.com/hpcloud/tail v1.0.0
|
||||
github.com/ipfs/go-log v1.0.5
|
||||
github.com/jaypipes/ghw v0.21.2
|
||||
@@ -34,11 +34,11 @@ require (
|
||||
github.com/modelcontextprotocol/go-sdk v1.2.0
|
||||
github.com/mudler/cogito v0.7.2
|
||||
github.com/mudler/edgevpn v0.31.1
|
||||
github.com/mudler/go-processmanager v0.1.0
|
||||
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82
|
||||
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2
|
||||
github.com/mudler/xlog v0.0.5
|
||||
github.com/onsi/ginkgo/v2 v2.27.5
|
||||
github.com/onsi/gomega v1.39.0
|
||||
github.com/onsi/ginkgo/v2 v2.27.3
|
||||
github.com/onsi/gomega v1.38.3
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/otiai10/openaigo v1.7.0
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||
@@ -59,6 +59,7 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.39.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
oras.land/oras-go/v2 v2.6.0
|
||||
@@ -73,11 +74,10 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.12.0 // indirect
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
|
||||
@@ -324,7 +324,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
|
||||
28
go.sum
28
go.sum
@@ -8,10 +8,10 @@ dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
fyne.io/fyne/v2 v2.7.2 h1:XiNpWkn0PzX43ZCjbb0QYGg1RCxVbugwfVgikWZBCMw=
|
||||
fyne.io/fyne/v2 v2.7.2/go.mod h1:PXbqY3mQmJV3J1NRUR2VbVgUUx3vgvhuFJxyjRK/4Ug=
|
||||
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
||||
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
fyne.io/fyne/v2 v2.7.1 h1:ja7rNHWWEooha4XBIZNnPP8tVFwmTfwMJdpZmLxm2Zc=
|
||||
fyne.io/fyne/v2 v2.7.1/go.mod h1:xClVlrhxl7D+LT+BWYmcrW4Nf+dJTvkhnPgji7spAwE=
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 h1:eA5/u2XRd8OUkoMqEv3IBlFYSruNlXD8bRHDiqm0VNI=
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
@@ -292,8 +292,8 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gpustack/gguf-parser-go v0.23.1 h1:0U7DOrsi7ryx2L/dlMy+BSQ5bJV4AuMEIgGBs4RK46A=
|
||||
github.com/gpustack/gguf-parser-go v0.23.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
|
||||
github.com/gpustack/gguf-parser-go v0.22.1 h1:FRnEDWqT0Rcplr/R9ctCRSN2+3DhVsf6dnR5/i9JA4E=
|
||||
github.com/gpustack/gguf-parser-go v0.22.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
@@ -513,8 +513,8 @@ github.com/mudler/edgevpn v0.31.1 h1:7qegiDWd0kAg6ljhNHxqvp8hbo/6BbzSdbb7/2WZfiY
|
||||
github.com/mudler/edgevpn v0.31.1/go.mod h1:ftV5B0nKFzm4R8vR80UYnCb2nf7lxCRgAALxUEEgCf8=
|
||||
github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc h1:RxwneJl1VgvikiX28EkpdAyL4yQVnJMrbquKospjHyA=
|
||||
github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc/go.mod h1:O7SwdSWMilAWhBZMK9N9Y/oBDyMMzshE3ju8Xkexwig=
|
||||
github.com/mudler/go-processmanager v0.1.0 h1:fcSKgF9U/a1Z7KofAFeZnke5YseadCI5GqL9oT0LS3E=
|
||||
github.com/mudler/go-processmanager v0.1.0/go.mod h1:h6kmHUZeafr+k5hRYpGLMzJFH4hItHffgpRo2QIkP+o=
|
||||
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82 h1:FVT07EI8njvsD4tC2Hw8Xhactp5AWhsQWD4oTeQuSAU=
|
||||
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82/go.mod h1:Urp7LG5jylKoDq0663qeBh0pINGcRl35nXdKx82PSoU=
|
||||
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2 h1:+WHsL/j6EWOMUiMVIOJNKOwSKiQt/qDPc9fePCf87fA=
|
||||
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2/go.mod h1:EA8Ashhd56o32qN7ouPKFSRUs/Z+LrRCF4v6R2Oarm8=
|
||||
github.com/mudler/water v0.0.0-20250808092830-dd90dcf09025 h1:WFLP5FHInarYGXi6B/Ze204x7Xy6q/I4nCZnWEyPHK0=
|
||||
@@ -561,10 +561,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
|
||||
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||
github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8=
|
||||
github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
|
||||
github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -978,8 +978,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
@@ -1492,7 +1492,7 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC
|
||||
results := []FuncCallResults{}
|
||||
llmResults := []string{}
|
||||
|
||||
extractJSON := func(results []string) (result []FuncCallResults, e error) {
|
||||
returnResult := func(results []string) (result []FuncCallResults, e error) {
|
||||
// As we have to change the result before processing, we can't stream the answer token-by-token (yet?)
|
||||
result = make([]FuncCallResults, 0)
|
||||
|
||||
@@ -1593,7 +1593,7 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC
|
||||
if len(llmResults) == 0 {
|
||||
llmResults = append(llmResults, llmresult)
|
||||
}
|
||||
results, _ = extractJSON(llmResults)
|
||||
results, _ = returnResult(llmResults)
|
||||
}
|
||||
|
||||
// Determine which XML format to use (if any)
|
||||
@@ -1632,16 +1632,8 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC
|
||||
// But skip if JSONRegexMatch or ResponseRegex was used (they already extracted the content)
|
||||
xmlResults, err := ParseXML(llmresult, xmlFormat)
|
||||
if err == nil && len(xmlResults) > 0 {
|
||||
// Check if JSON is inside XML tags, if so, skip it
|
||||
for _, result := range xmlResults {
|
||||
jsonResults, _ := extractJSON([]string{result.Name})
|
||||
if len(jsonResults) > 0 {
|
||||
xlog.Debug("Found valid JSON inside XML tags, skipping XML parsing", "json_count", len(jsonResults))
|
||||
} else {
|
||||
xlog.Debug("Found additional XML tool calls alongside JSON", "xml_count", len(xmlResults))
|
||||
results = append(results, xmlResults...)
|
||||
}
|
||||
}
|
||||
xlog.Debug("Found additional XML tool calls alongside JSON", "xml_count", len(xmlResults))
|
||||
results = append(results, xmlResults...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -820,23 +820,6 @@ Final text`
|
||||
Expect(results[0].Name).To(Equal("first"))
|
||||
Expect(results[1].Name).To(Equal("second"))
|
||||
})
|
||||
|
||||
It("should not duplicate parse JSON inside tool_call tags", func() {
|
||||
// This test reproduces a bug where JSON inside <tool_call> tags
|
||||
// gets parsed twice: once as JSON (correctly) and once as XML (incorrectly)
|
||||
// The XML parser should not run when JSON parsing already found valid results
|
||||
input := `<tool_call>
|
||||
{"name": "get_current_weather", "arguments": {"location": "Beijing", "unit": "celsius"}}
|
||||
</tool_call>`
|
||||
|
||||
results := ParseFunctionCall(input, functionConfig)
|
||||
// Should only have 1 result, not 2 (one correct + one malformed)
|
||||
Expect(results).To(HaveLen(1), "Should not create duplicate entries when JSON is inside XML tags")
|
||||
Expect(results[0].Name).To(Equal("get_current_weather"))
|
||||
Expect(results[0].Arguments).To(Equal(`{"location":"Beijing","unit":"celsius"}`))
|
||||
// Verify the name is not the entire JSON object (which would indicate malformed XML parsing)
|
||||
Expect(results[0].Name).NotTo(ContainSubstring(`{"name"`), "Function name should not contain JSON object")
|
||||
})
|
||||
})
|
||||
|
||||
Context("Iterative Parser (ChatMsgParser)", func() {
|
||||
|
||||
@@ -148,10 +148,10 @@ package_cuda_libs() {
|
||||
done
|
||||
|
||||
# Copy CUDA target directory for runtime compilation support
|
||||
# if [ -d "/usr/local/cuda/targets" ]; then
|
||||
# mkdir -p "$TARGET_LIB_DIR/../cuda"
|
||||
# cp -arfL /usr/local/cuda/targets "$TARGET_LIB_DIR/../cuda/" 2>/dev/null || true
|
||||
# fi
|
||||
if [ -d "/usr/local/cuda/targets" ]; then
|
||||
mkdir -p "$TARGET_LIB_DIR/../cuda"
|
||||
cp -arfL /usr/local/cuda/targets "$TARGET_LIB_DIR/../cuda/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "CUDA libraries packaged successfully"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user