ui: Restructure repo to use tools/ui folder and ui / UI / llama-ui / LLAMA_UI naming (#23064)

* webui: Move static build output from `tools/server/public` to `build/ui` directory

* refactor: Move to `tools/ui`

* refactor: rename CMake variables and preprocessor defines

- Rename LLAMA_BUILD_WEBUI -> LLAMA_BUILD_UI (old kept as deprecated)
- Rename LLAMA_USE_PREBUILT_WEBUI -> LLAMA_USE_PREBUILT_UI (old kept as deprecated)
- Backward compat: old vars auto-forward to new ones with DEPRECATION warning
- Rename internal vars: WEBUI_SOURCE -> UI_SOURCE, WEBUI_SOURCE_DIR -> UI_SOURCE_DIR, etc.
- Rename HF bucket: LLAMA_WEBUI_HF_BUCKET -> LLAMA_UI_HF_BUCKET
- Emit both LLAMA_BUILD_WEBUI and LLAMA_BUILD_UI preprocessor defines
- Emit both LLAMA_WEBUI_DEFAULT_ENABLED and LLAMA_UI_DEFAULT_ENABLED

* refactor: rename CLI flags (--webui -> --ui) with backward compat

- Add --ui/--no-ui (old --webui/--no-webui kept as deprecated aliases)
- Add --ui-config (old --webui-config kept as deprecated alias)
- Add --ui-config-file (old --webui-config-file kept as deprecated alias)
- Add --ui-mcp-proxy/--no-ui-mcp-proxy (old --webui-mcp-proxy kept as deprecated)
- Add new env vars: LLAMA_ARG_UI, LLAMA_ARG_UI_CONFIG, LLAMA_ARG_UI_CONFIG_FILE, LLAMA_ARG_UI_MCP_PROXY
- C++ struct fields: params.ui, params.ui_config_json, params.ui_mcp_proxy added alongside old fields
- Backward compat: old fields synced to new ones in g_params_to_internals

* refactor: update C++ server internals with backward compat

- Rename json_webui_settings -> json_ui_settings (both kept in server_context_meta)
- Rename params.webui usage -> params.ui (both synced, old still works)
- JSON API emits both "ui"/"ui_settings" and "webui"/"webui_settings" keys
- Server routes use params.ui_mcp_proxy || params.webui_mcp_proxy
- Preprocessor guards use #if defined(LLAMA_BUILD_UI) || defined(LLAMA_BUILD_WEBUI)

* refactor: rename CI/CD workflows, artifacts, and build script

- Rename webui-build.yml -> ui-build.yml; artifact webui-build -> ui-build
- Rename webui-publish.yml -> ui-publish.yml; var HF_BUCKET_WEBUI_STATIC_OUTPUT -> HF_BUCKET_UI_STATIC_OUTPUT
- Rename server-webui.yml -> server-ui.yml; job webui-build/checks -> ui-build/checks
- Update server.yml: job/artifact refs webui-build -> ui-build
- Update release.yml: all webui-build/publish refs -> ui-build/publish; HF_TOKEN_WEBUI_STATIC_OUTPUT -> HF_TOKEN_UI_STATIC_OUTPUT
- Update server-self-hosted.yml: webui-build -> ui-build
- Update build-self-hosted.yml: HF_WEBUI_VERSION -> HF_UI_VERSION
- Rename webui-download.cmake -> ui-download.cmake (internal refs updated)
- Update labeler.yml: server/webui -> server/ui path label

* docs: update CODEOWNERS and server README docs

- Update CODEOWNERS: team ggml-org/llama-webui -> ggml-org/llama-ui, path /tools/server/webui/ -> /tools/ui/
- Update server README.md: CLI tables show --ui flags with deprecated --webui aliases
- Update server README-dev.md: "WebUI" -> "UI", paths updated to tools/ui/

* fix: Small fixes for UI build

* fix: CMake.txt syntax

* chore: Formatting

* fix: `.editorconfig` for llama-ui

* chore: Formatting

* refactor: Use `APP_NAME` in Error route

* refactor: Cleanup

* refactor: Single migration service

* make llama-ui a linkable target

* fix: UI Build output

* fix: Missing change

* fix: separate llama-ui npm build output into build/tools/ui/dist subfolder + use cmake npm build instead of downloading ui-build.yml artifacts in CI

* refactor: UI workflows cleanup

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
This commit is contained in:
Aleksander Grygier 2026-05-16 02:02:40 +02:00 committed by GitHub
parent 49d1701bd2
commit 59778f0196
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
565 changed files with 1610 additions and 694 deletions

View File

@ -45,7 +45,7 @@ insert_final_newline = unset
trim_trailing_whitespace = unset trim_trailing_whitespace = unset
insert_final_newline = unset insert_final_newline = unset
[tools/server/webui/**] [tools/ui/**]
indent_style = unset indent_style = unset
indent_size = unset indent_size = unset
end_of_line = unset end_of_line = unset

4
.github/labeler.yml vendored
View File

@ -73,10 +73,10 @@ android:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- examples/llama.android/** - examples/llama.android/**
server/webui: server/ui:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- tools/server/webui/** - tools/ui/**
server: server:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:

View File

@ -68,6 +68,8 @@ jobs:
- name: Determine tag name - name: Determine tag name
id: tag id: tag
uses: ./.github/actions/get-tag-name uses: ./.github/actions/get-tag-name
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
ggml-ci-nvidia-cuda: ggml-ci-nvidia-cuda:
needs: determine-tag needs: determine-tag
@ -81,7 +83,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
nvidia-smi nvidia-smi
GG_BUILD_CUDA=1 bash ./ci/run.sh ~/results/llama.cpp /mnt/llama.cpp GG_BUILD_CUDA=1 bash ./ci/run.sh ~/results/llama.cpp /mnt/llama.cpp
@ -98,7 +100,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
vulkaninfo --summary vulkaninfo --summary
GG_BUILD_VULKAN=1 GGML_VK_DISABLE_COOPMAT2=1 bash ./ci/run.sh ~/results/llama.cpp /mnt/llama.cpp GG_BUILD_VULKAN=1 GGML_VK_DISABLE_COOPMAT2=1 bash ./ci/run.sh ~/results/llama.cpp /mnt/llama.cpp
@ -115,7 +117,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
vulkaninfo --summary vulkaninfo --summary
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp /mnt/llama.cpp GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp /mnt/llama.cpp
@ -205,7 +207,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
GG_BUILD_METAL=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp GG_BUILD_METAL=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
@ -234,7 +236,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
GG_BUILD_WEBGPU=1 GG_BUILD_WEBGPU_DAWN_PREFIX="$GITHUB_WORKSPACE/dawn" \ GG_BUILD_WEBGPU=1 GG_BUILD_WEBGPU_DAWN_PREFIX="$GITHUB_WORKSPACE/dawn" \
bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
@ -251,7 +253,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
vulkaninfo --summary vulkaninfo --summary
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
@ -270,7 +272,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
vulkaninfo --summary vulkaninfo --summary
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
@ -291,7 +293,7 @@ jobs:
MSYSTEM: UCRT64 MSYSTEM: UCRT64
CHERE_INVOKING: 1 CHERE_INVOKING: 1
PATH: C:\msys64\ucrt64\bin;C:\msys64\usr\bin;C:\Windows\System32;${{ env.PATH }} PATH: C:\msys64\ucrt64\bin;C:\msys64\usr\bin;C:\Windows\System32;${{ env.PATH }}
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
vulkaninfo --summary vulkaninfo --summary
# Skip python related tests with GG_BUILD_LOW_PERF=1 since Windows MSYS2 UCRT64 currently fails to create # Skip python related tests with GG_BUILD_LOW_PERF=1 since Windows MSYS2 UCRT64 currently fails to create
@ -332,7 +334,7 @@ jobs:
- name: Test - name: Test
id: ggml-ci id: ggml-ci
env: env:
HF_WEBUI_VERSION: ${{ needs.determine-tag.outputs.tag_name }} HF_UI_VERSION: ${{ needs.determine-tag.outputs.tag_name }}
run: | run: |
source ./openvino_toolkit/setupvars.sh source ./openvino_toolkit/setupvars.sh
GG_BUILD_OPENVINO=1 GGML_OPENVINO_DEVICE=GPU GG_BUILD_LOW_PERF=1 bash ./ci/run.sh ./tmp/results ./tmp/mnt GG_BUILD_OPENVINO=1 GGML_OPENVINO_DEVICE=GPU GG_BUILD_LOW_PERF=1 bash ./ci/run.sh ./tmp/results ./tmp/mnt

View File

@ -36,13 +36,8 @@ env:
CMAKE_ARGS: "-DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON" CMAKE_ARGS: "-DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON"
jobs: jobs:
webui-build:
name: Build WebUI
uses: ./.github/workflows/webui-build.yml
macOS-cpu: macOS-cpu:
needs:
- webui-build
strategy: strategy:
matrix: matrix:
@ -71,11 +66,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -113,8 +109,6 @@ jobs:
name: llama-bin-macos-${{ matrix.build }}.tar.gz name: llama-bin-macos-${{ matrix.build }}.tar.gz
ubuntu-cpu: ubuntu-cpu:
needs:
- webui-build
strategy: strategy:
matrix: matrix:
@ -135,11 +129,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
if: ${{ matrix.build != 's390x' }} if: ${{ matrix.build != 's390x' }}
@ -191,8 +186,6 @@ jobs:
name: llama-bin-ubuntu-${{ matrix.build }}.tar.gz name: llama-bin-ubuntu-${{ matrix.build }}.tar.gz
ubuntu-vulkan: ubuntu-vulkan:
needs:
- webui-build
strategy: strategy:
matrix: matrix:
@ -211,11 +204,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -268,8 +262,6 @@ jobs:
name: llama-bin-ubuntu-vulkan-${{ matrix.build }}.tar.gz name: llama-bin-ubuntu-vulkan-${{ matrix.build }}.tar.gz
android-arm64: android-arm64:
needs:
- webui-build
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -283,11 +275,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -346,8 +339,6 @@ jobs:
name: llama-bin-android-arm64.tar.gz name: llama-bin-android-arm64.tar.gz
ubuntu-24-openvino: ubuntu-24-openvino:
needs:
- webui-build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@ -370,11 +361,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -435,8 +427,6 @@ jobs:
name: llama-bin-ubuntu-openvino-${{ env.OPENVINO_VERSION_MAJOR }}-x64.tar.gz name: llama-bin-ubuntu-openvino-${{ env.OPENVINO_VERSION_MAJOR }}-x64.tar.gz
windows-cpu: windows-cpu:
needs:
- webui-build
runs-on: windows-2025 runs-on: windows-2025
@ -452,11 +442,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -496,8 +487,6 @@ jobs:
name: llama-bin-win-cpu-${{ matrix.arch }}.zip name: llama-bin-win-cpu-${{ matrix.arch }}.zip
windows: windows:
needs:
- webui-build
runs-on: windows-2025 runs-on: windows-2025
@ -522,11 +511,12 @@ jobs:
id: checkout id: checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -587,8 +577,6 @@ jobs:
name: llama-bin-win-${{ matrix.backend }}-${{ matrix.arch }}.zip name: llama-bin-win-${{ matrix.backend }}-${{ matrix.arch }}.zip
windows-cuda: windows-cuda:
needs:
- webui-build
runs-on: windows-2022 runs-on: windows-2022
@ -601,11 +589,12 @@ jobs:
id: checkout id: checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install ccache - name: Install ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -667,8 +656,6 @@ jobs:
name: cudart-llama-bin-win-cuda-${{ matrix.cuda }}-x64.zip name: cudart-llama-bin-win-cuda-${{ matrix.cuda }}-x64.zip
windows-sycl: windows-sycl:
needs:
- webui-build
runs-on: windows-2022 runs-on: windows-2022
@ -708,11 +695,12 @@ jobs:
Expand-Archive -Path "level-zero-win-sdk.zip" -DestinationPath "C:/level-zero-sdk" -Force Expand-Archive -Path "level-zero-win-sdk.zip" -DestinationPath "C:/level-zero-sdk" -Force
"LEVEL_ZERO_V1_SDK_PATH=C:/level-zero-sdk" | Out-File -FilePath $env:GITHUB_ENV -Append "LEVEL_ZERO_V1_SDK_PATH=C:/level-zero-sdk" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -781,8 +769,6 @@ jobs:
name: llama-bin-win-sycl-x64.zip name: llama-bin-win-sycl-x64.zip
ubuntu-24-sycl: ubuntu-24-sycl:
needs:
- webui-build
strategy: strategy:
matrix: matrix:
@ -831,11 +817,12 @@ jobs:
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/level-zero-devel_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O level-zero-devel.deb wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/level-zero-devel_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O level-zero-devel.deb
sudo apt-get install -y ./level-zero.deb ./level-zero-devel.deb sudo apt-get install -y ./level-zero.deb ./level-zero-devel.deb
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache - name: ccache
uses: ggml-org/ccache-action@v1.2.21 uses: ggml-org/ccache-action@v1.2.21
@ -876,8 +863,6 @@ jobs:
name: llama-bin-ubuntu-sycl-${{ matrix.build }}-x64.tar.gz name: llama-bin-ubuntu-sycl-${{ matrix.build }}-x64.tar.gz
ubuntu-22-rocm: ubuntu-22-rocm:
needs:
- webui-build
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@ -895,11 +880,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Free up disk space - name: Free up disk space
uses: ggml-org/free-disk-space@v1.3.1 uses: ggml-org/free-disk-space@v1.3.1
@ -988,8 +974,6 @@ jobs:
name: llama-bin-ubuntu-rocm-${{ env.ROCM_VERSION_SHORT }}-${{ matrix.build }}.tar.gz name: llama-bin-ubuntu-rocm-${{ env.ROCM_VERSION_SHORT }}-${{ matrix.build }}.tar.gz
windows-hip: windows-hip:
needs:
- webui-build
runs-on: windows-2022 runs-on: windows-2022
@ -1007,11 +991,12 @@ jobs:
id: checkout id: checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Grab rocWMMA package - name: Grab rocWMMA package
id: grab_rocwmma id: grab_rocwmma
@ -1259,7 +1244,6 @@ jobs:
runs-on: ubuntu-slim runs-on: ubuntu-slim
needs: needs:
- webui-build
- windows - windows
- windows-cpu - windows-cpu
- windows-cuda - windows-cuda
@ -1404,14 +1388,14 @@ jobs:
} }
} }
webui-publish: ui-publish:
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }} if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
needs: needs:
- release - release
uses: ./.github/workflows/webui-publish.yml uses: ./.github/workflows/ui-publish.yml
with: with:
version_tag: ${{ needs.release.outputs.tag_name }} version_tag: ${{ needs.release.outputs.tag_name }}
secrets: secrets:
hf_token: ${{ secrets.HF_TOKEN_WEBUI_STATIC_OUTPUT }} hf_token: ${{ secrets.HF_TOKEN_UI_STATIC_OUTPUT }}

View File

@ -67,6 +67,13 @@ jobs:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }} ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Build - name: Build
id: cmake_build id: cmake_build
run: | run: |

View File

@ -39,12 +39,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
webui-build:
name: Build WebUI
uses: ./.github/workflows/webui-build.yml
server-metal: server-metal:
needs: webui-build
runs-on: [self-hosted, llama-server, macOS, ARM64] runs-on: [self-hosted, llama-server, macOS, ARM64]
name: server-metal (${{ matrix.wf_name }}) name: server-metal (${{ matrix.wf_name }})
@ -72,11 +67,12 @@ jobs:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }} ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Build - name: Build
id: cmake_build id: cmake_build

View File

@ -54,12 +54,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
webui-build:
name: Build WebUI
uses: ./.github/workflows/webui-build.yml
server: server:
needs: webui-build
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: server (${{ matrix.wf_name }}) name: server (${{ matrix.wf_name }})
@ -98,11 +93,12 @@ jobs:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }} ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/ cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Build - name: Build
id: cmake_build id: cmake_build
@ -136,7 +132,6 @@ jobs:
SLOW_TESTS=1 pytest -v -x SLOW_TESTS=1 pytest -v -x
server-windows: server-windows:
needs: webui-build
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
@ -147,11 +142,10 @@ jobs:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }} ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: Download WebUI build artifact - name: Setup Node.js
uses: actions/download-artifact@v7 uses: actions/setup-node@v6
with: with:
name: webui-build node-version: "24"
path: tools/server/public/
- name: Build - name: Build
id: cmake_build id: cmake_build

View File

@ -1,11 +1,11 @@
name: Build WebUI name: UI Build
on: on:
workflow_call: workflow_call:
jobs: jobs:
build: build:
name: Build WebUI name: Build static output
runs-on: ubuntu-slim runs-on: ubuntu-slim
env: env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
@ -19,26 +19,26 @@ jobs:
with: with:
node-version: "24" node-version: "24"
cache: "npm" cache: "npm"
cache-dependency-path: "tools/server/webui/package-lock.json" cache-dependency-path: "tools/ui/package-lock.json"
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
working-directory: tools/server/webui working-directory: tools/ui
- name: Build application - name: Build application
run: npm run build run: npm run build
working-directory: tools/server/webui working-directory: tools/ui
- name: Generate checksums - name: Generate checksums
run: | run: |
cd tools/server/public cd build/tools/ui/dist
for f in *; do for f in *; do
sha256sum "$f" | awk '{print $1, $2}' >> checksums.txt sha256sum "$f" | awk '{print $1, $2}' >> checksums.txt
done done
- name: Upload built webui - name: Upload built UI
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: webui-build name: ui-build
path: tools/server/public/ path: build/tools/ui/dist/
retention-days: 1 retention-days: 1

View File

@ -1,4 +1,4 @@
name: Server WebUI name: CI (UI)
on: on:
workflow_dispatch: workflow_dispatch:
@ -11,15 +11,15 @@ on:
branches: branches:
- master - master
paths: [ paths: [
'.github/workflows/server-webui.yml', '.github/workflows/ui-ci.yml',
'tools/server/webui/**.*', 'tools/ui/**.*',
'tools/server/tests/**.*' 'tools/server/tests/**.*'
] ]
pull_request: pull_request:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
paths: [ paths: [
'.github/workflows/server-webui.yml', '.github/workflows/ui-ci.yml',
'tools/server/webui/**.*', 'tools/ui/**.*',
'tools/server/tests/**.*' 'tools/server/tests/**.*'
] ]
@ -34,13 +34,13 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
webui-build: ui-build:
name: Build WebUI name: Build static output
uses: ./.github/workflows/webui-build.yml uses: ./.github/workflows/ui-build.yml
webui-checks: ui-checks:
name: WebUI Checks name: UI Checks
needs: webui-build needs: ui-build
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
continue-on-error: true continue-on-error: true
steps: steps:
@ -56,43 +56,43 @@ jobs:
with: with:
node-version: "24" node-version: "24"
cache: "npm" cache: "npm"
cache-dependency-path: "tools/server/webui/package-lock.json" cache-dependency-path: "tools/ui/package-lock.json"
- name: Install dependencies - name: Install dependencies
id: setup id: setup
if: ${{ steps.node.conclusion == 'success' }} if: ${{ steps.node.conclusion == 'success' }}
run: npm ci run: npm ci
working-directory: tools/server/webui working-directory: tools/ui
- name: Run type checking - name: Run type checking
if: ${{ always() && steps.setup.conclusion == 'success' }} if: ${{ always() && steps.setup.conclusion == 'success' }}
run: npm run check run: npm run check
working-directory: tools/server/webui working-directory: tools/ui
- name: Run linting - name: Run linting
if: ${{ always() && steps.setup.conclusion == 'success' }} if: ${{ always() && steps.setup.conclusion == 'success' }}
run: npm run lint run: npm run lint
working-directory: tools/server/webui working-directory: tools/ui
- name: Install Playwright browsers - name: Install Playwright browsers
id: playwright id: playwright
if: ${{ always() && steps.setup.conclusion == 'success' }} if: ${{ always() && steps.setup.conclusion == 'success' }}
run: npx playwright install --with-deps run: npx playwright install --with-deps
working-directory: tools/server/webui working-directory: tools/ui
- name: Run Client tests - name: Run Client tests
if: ${{ always() && steps.playwright.conclusion == 'success' }} if: ${{ always() && steps.playwright.conclusion == 'success' }}
run: npm run test:client run: npm run test:client
working-directory: tools/server/webui working-directory: tools/ui
- name: Run Unit tests - name: Run Unit tests
if: ${{ always() && steps.playwright.conclusion == 'success' }} if: ${{ always() && steps.playwright.conclusion == 'success' }}
run: npm run test:unit run: npm run test:unit
working-directory: tools/server/webui working-directory: tools/ui
e2e-tests: e2e-tests:
name: E2E Tests name: E2E Tests
needs: webui-build needs: ui-build
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
steps: steps:
- name: Checkout code - name: Checkout code
@ -107,36 +107,36 @@ jobs:
with: with:
node-version: "24" node-version: "24"
cache: "npm" cache: "npm"
cache-dependency-path: "tools/server/webui/package-lock.json" cache-dependency-path: "tools/ui/package-lock.json"
- name: Install dependencies - name: Install dependencies
id: setup id: setup
if: ${{ steps.node.conclusion == 'success' }} if: ${{ steps.node.conclusion == 'success' }}
run: npm ci run: npm ci
working-directory: tools/server/webui working-directory: tools/ui
- name: Build application - name: Build application
if: ${{ always() && steps.setup.conclusion == 'success' }} if: ${{ always() && steps.setup.conclusion == 'success' }}
run: npm run build run: npm run build
working-directory: tools/server/webui working-directory: tools/ui
- name: Install Playwright browsers - name: Install Playwright browsers
id: playwright id: playwright
if: ${{ always() && steps.setup.conclusion == 'success' }} if: ${{ always() && steps.setup.conclusion == 'success' }}
run: npx playwright install --with-deps run: npx playwright install --with-deps
working-directory: tools/server/webui working-directory: tools/ui
- name: Build Storybook - name: Build Storybook
if: ${{ always() && steps.playwright.conclusion == 'success' }} if: ${{ always() && steps.playwright.conclusion == 'success' }}
run: npm run build-storybook run: npm run build-storybook
working-directory: tools/server/webui working-directory: tools/ui
- name: Run UI tests - name: Run UI tests
if: ${{ always() && steps.playwright.conclusion == 'success' }} if: ${{ always() && steps.playwright.conclusion == 'success' }}
run: npm run test:ui -- --testTimeout=60000 run: npm run test:ui -- --testTimeout=60000
working-directory: tools/server/webui working-directory: tools/ui
- name: Run E2E tests - name: Run E2E tests
if: ${{ always() && steps.playwright.conclusion == 'success' }} if: ${{ always() && steps.playwright.conclusion == 'success' }}
run: npm run test:e2e run: npm run test:e2e
working-directory: tools/server/webui working-directory: tools/ui

View File

@ -1,4 +1,4 @@
name: WebUI Publish name: UI Publish
on: on:
workflow_call: workflow_call:
@ -14,14 +14,14 @@ on:
jobs: jobs:
publish: publish:
name: Publish WebUI Static Output name: Publish UI Static Output
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
permissions: permissions:
contents: read contents: read
env: env:
HF_BUCKET_NAME: ${{ vars.HF_BUCKET_WEBUI_STATIC_OUTPUT }} HF_BUCKET_NAME: ${{ vars.HF_BUCKET_UI_STATIC_OUTPUT }}
steps: steps:
- name: Checkout code - name: Checkout code
@ -29,11 +29,11 @@ jobs:
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Download WebUI build artifact - name: Download UI build artifact
uses: actions/download-artifact@v7 uses: actions/download-artifact@v7
with: with:
name: webui-build name: ui-build
path: tools/server/public/ path: build/tools/ui/dist/
- name: Install Hugging Face Hub CLI - name: Install Hugging Face Hub CLI
run: pip install -U huggingface_hub run: pip install -U huggingface_hub
@ -44,12 +44,12 @@ jobs:
- name: Sync built files to Hugging Face bucket (version tag) - name: Sync built files to Hugging Face bucket (version tag)
run: | run: |
# Upload the built files to the Hugging Face bucket under the release version # Upload the built files to the Hugging Face bucket under the release version
hf buckets sync tools/server/public hf://buckets/ggml-org/${{ env.HF_BUCKET_NAME }}/${{ inputs.version_tag }} --delete --quiet hf buckets sync build/tools/ui/dist hf://buckets/ggml-org/${{ env.HF_BUCKET_NAME }}/${{ inputs.version_tag }} --delete --quiet
- name: Sync built files to Hugging Face bucket (latest) - name: Sync built files to Hugging Face bucket (latest)
run: | run: |
# Also upload to the 'latest' directory for fallback downloads # Also upload to the 'latest' directory for fallback downloads
hf buckets sync tools/server/public hf://buckets/ggml-org/${{ env.HF_BUCKET_NAME }}/latest --delete --quiet hf buckets sync build/tools/ui/dist hf://buckets/ggml-org/${{ env.HF_BUCKET_NAME }}/latest --delete --quiet
- name: Verify upload - name: Verify upload
run: | run: |

5
.gitignore vendored
View File

@ -54,7 +54,6 @@
/tmp/ /tmp/
/autogen-*.md /autogen-*.md
/common/build-info.cpp /common/build-info.cpp
/tools/server/public
# Deprecated # Deprecated
@ -93,10 +92,12 @@
!/examples/sycl/*.bat !/examples/sycl/*.bat
!/examples/sycl/*.sh !/examples/sycl/*.sh
# Server Web UI temporary files # Server Web UI temporary files (+ legacy directory)
/tools/server/webui/node_modules /tools/server/webui/node_modules
/tools/server/webui/dist /tools/server/webui/dist
/tools/ui/node_modules
/tools/ui/dist
# Python # Python

View File

@ -108,8 +108,23 @@ option(LLAMA_BUILD_TESTS "llama: build tests"
option(LLAMA_BUILD_TOOLS "llama: build tools" ${LLAMA_STANDALONE}) option(LLAMA_BUILD_TOOLS "llama: build tools" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_EXAMPLES "llama: build examples" ${LLAMA_STANDALONE}) option(LLAMA_BUILD_EXAMPLES "llama: build examples" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_SERVER "llama: build server example" ${LLAMA_STANDALONE}) option(LLAMA_BUILD_SERVER "llama: build server example" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_WEBUI "llama: build the embedded Web UI for server" ON) # Deprecated: use LLAMA_BUILD_UI instead (kept for backward compat)
option(LLAMA_USE_PREBUILT_WEBUI "llama: use prebuilt WebUI from HF Bucket when available (requires LLAMA_BUILD_WEBUI=ON)" ON) option(LLAMA_BUILD_WEBUI "llama: build the embedded Web UI for server (deprecated: use LLAMA_BUILD_UI)" ON)
option(LLAMA_USE_PREBUILT_WEBUI "llama: use prebuilt WebUI from HF Bucket when available (deprecated: use LLAMA_USE_PREBUILT_UI)" ON)
# New option names
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" ON)
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available (requires LLAMA_BUILD_UI=ON)" ON)
# Backward compat: when old var is set but new one isn't, forward the value
if(DEFINED LLAMA_BUILD_WEBUI AND NOT DEFINED LLAMA_BUILD_UI)
set(LLAMA_BUILD_UI ${LLAMA_BUILD_WEBUI})
message(DEPRECATION "LLAMA_BUILD_WEBUI is deprecated, use LLAMA_BUILD_UI instead")
endif()
if(DEFINED LLAMA_USE_PREBUILT_WEBUI AND NOT DEFINED LLAMA_USE_PREBUILT_UI)
set(LLAMA_USE_PREBUILT_UI ${LLAMA_USE_PREBUILT_WEBUI})
message(DEPRECATION "LLAMA_USE_PREBUILT_WEBUI is deprecated, use LLAMA_USE_PREBUILT_UI instead")
endif()
option(LLAMA_TOOLS_INSTALL "llama: install tools" ${LLAMA_TOOLS_INSTALL_DEFAULT}) option(LLAMA_TOOLS_INSTALL "llama: install tools" ${LLAMA_TOOLS_INSTALL_DEFAULT})
option(LLAMA_TESTS_INSTALL "llama: install tests" ON) option(LLAMA_TESTS_INSTALL "llama: install tests" ON)

View File

@ -15,7 +15,7 @@
# ggml-org/llama-common : ggerganov, aldehir, angt, danbev, ngxson, pwilkin # ggml-org/llama-common : ggerganov, aldehir, angt, danbev, ngxson, pwilkin
# ggml-org/llama-mtmd : ngxson # ggml-org/llama-mtmd : ngxson
# ggml-org/llama-server : ggerganov, ngxson, allozaur, angt, ServeurpersoCom # ggml-org/llama-server : ggerganov, ngxson, allozaur, angt, ServeurpersoCom
# ggml-org/llama-webui : allozaur # ggml-org/llama-ui : allozaur
/.devops/*.Dockerfile @ngxson /.devops/*.Dockerfile @ngxson
/.github/actions/ @ggml-org/ci /.github/actions/ @ggml-org/ci
@ -107,7 +107,7 @@
/tools/rpc/ @ggml-org/ggml-rpc /tools/rpc/ @ggml-org/ggml-rpc
/tools/server/* @ggml-org/llama-server # no subdir /tools/server/* @ggml-org/llama-server # no subdir
/tools/server/tests/ @ggml-org/llama-server /tools/server/tests/ @ggml-org/llama-server
/tools/server/webui/ @ggml-org/llama-webui /tools/ui/ @ggml-org/llama-ui
/tools/tokenize/ @ggerganov /tools/tokenize/ @ggerganov
/tools/tts/ @ggerganov /tools/tts/ @ggerganov
/vendor/ @ggerganov /vendor/ @ggerganov

View File

@ -2844,28 +2844,64 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.api_prefix = value; params.api_prefix = value;
} }
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_API_PREFIX")); ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_API_PREFIX"));
// Deprecated: use --ui-config instead (kept for backward compat)
add_opt(common_arg( add_opt(common_arg(
{"--webui-config"}, "JSON", {"--webui-config"}, "JSON",
"JSON that provides default WebUI settings (overrides WebUI defaults)", "[DEPRECATED: use --ui-config] JSON that provides default WebUI settings (overrides WebUI defaults)",
[](common_params & params, const std::string & value) { [](common_params & params, const std::string & value) {
params.ui_config_json = value;
params.webui_config_json = value; params.webui_config_json = value;
} }
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI_CONFIG")); ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI_CONFIG"));
add_opt(common_arg(
{"--ui-config"}, "JSON",
"JSON that provides default UI settings (overrides UI defaults)",
[](common_params & params, const std::string & value) {
params.ui_config_json = value;
params.webui_config_json = value;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_UI_CONFIG"));
// Deprecated: use --ui-config-file instead (kept for backward compat)
add_opt(common_arg( add_opt(common_arg(
{"--webui-config-file"}, "PATH", {"--webui-config-file"}, "PATH",
"JSON file that provides default WebUI settings (overrides WebUI defaults)", "[DEPRECATED: use --ui-config-file] JSON file that provides default WebUI settings (overrides WebUI defaults)",
[](common_params & params, const std::string & value) { [](common_params & params, const std::string & value) {
params.webui_config_json = read_file(value); params.ui_config_json = read_file(value);
params.webui_config_json = params.ui_config_json;
} }
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI_CONFIG_FILE")); ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI_CONFIG_FILE"));
add_opt(common_arg(
{"--ui-config-file"}, "PATH",
"JSON file that provides default UI settings (overrides UI defaults)",
[](common_params & params, const std::string & value) {
params.ui_config_json = read_file(value);
params.webui_config_json = params.ui_config_json;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_UI_CONFIG_FILE"));
// Deprecated: use --ui-mcp-proxy instead (kept for backward compat)
add_opt(common_arg( add_opt(common_arg(
{"--webui-mcp-proxy"}, {"--webui-mcp-proxy"},
{"--no-webui-mcp-proxy"}, {"--no-webui-mcp-proxy"},
string_format("experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: %s)", params.webui_mcp_proxy ? "enabled" : "disabled"), "[DEPRECATED: use --ui-mcp-proxy/--no-ui-mcp-proxy] experimental: whether to enable MCP CORS proxy",
[](common_params & params, bool value) { [](common_params & params, bool value) {
params.ui_mcp_proxy = value;
params.webui_mcp_proxy = value; params.webui_mcp_proxy = value;
} }
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI_MCP_PROXY")); ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI_MCP_PROXY"));
add_opt(common_arg(
{"--ui-mcp-proxy"},
{"--no-ui-mcp-proxy"},
"experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)",
[](common_params & params, bool value) {
params.ui_mcp_proxy = value;
params.webui_mcp_proxy = value;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_UI_MCP_PROXY"));
add_opt(common_arg( add_opt(common_arg(
{"--tools"}, "TOOL1,TOOL2,...", {"--tools"}, "TOOL1,TOOL2,...",
"experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)\n" "experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)\n"
@ -2875,14 +2911,26 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.server_tools = parse_csv_row(value); params.server_tools = parse_csv_row(value);
} }
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_TOOLS")); ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_TOOLS"));
// Deprecated: use --ui/--no-ui instead (kept for backward compat)
add_opt(common_arg( add_opt(common_arg(
{"--webui"}, {"--webui"},
{"--no-webui"}, {"--no-webui"},
string_format("whether to enable the Web UI (default: %s)", params.webui ? "enabled" : "disabled"), "[DEPRECATED: use --ui/--no-ui] whether to enable the Web UI",
[](common_params & params, bool value) { [](common_params & params, bool value) {
params.ui = value;
params.webui = value; params.webui = value;
} }
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI")); ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_WEBUI"));
add_opt(common_arg(
{"--ui"},
{"--no-ui"},
string_format("whether to enable the Web UI (default: %s)", params.ui ? "enabled" : "disabled"),
[](common_params & params, bool value) {
params.ui = value;
params.webui = value;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_UI"));
add_opt(common_arg( add_opt(common_arg(
{"--embedding", "--embeddings"}, {"--embedding", "--embeddings"},
string_format("restrict to only support embedding use case; use only with dedicated embedding models (default: %s)", params.embedding ? "enabled" : "disabled"), string_format("restrict to only support embedding use case; use only with dedicated embedding models (default: %s)", params.embedding ? "enabled" : "disabled"),

View File

@ -604,15 +604,23 @@ struct common_params {
std::map<std::string, std::string> default_template_kwargs; std::map<std::string, std::string> default_template_kwargs;
// webui configs // UI configs
#ifdef LLAMA_WEBUI_DEFAULT_ENABLED #ifdef LLAMA_UI_DEFAULT_ENABLED
bool webui = LLAMA_WEBUI_DEFAULT_ENABLED != 0; bool ui = LLAMA_UI_DEFAULT_ENABLED != 0;
#elif defined(LLAMA_WEBUI_DEFAULT_ENABLED)
bool ui = LLAMA_WEBUI_DEFAULT_ENABLED != 0;
#else #else
bool webui = true; // default to enabled when not set bool ui = true; // default to enabled when not set
#endif #endif
// Deprecated: use ui, ui_mcp_proxy, ui_config_json instead
bool webui = ui;
bool webui_mcp_proxy = false; bool webui_mcp_proxy = false;
std::string webui_config_json; std::string webui_config_json;
bool ui_mcp_proxy = false;
std::string ui_config_json;
// "advanced" endpoints are disabled by default for better security // "advanced" endpoints are disabled by default for better security
bool endpoint_slots = true; bool endpoint_slots = true;
bool endpoint_props = false; // only control POST requests, not GET bool endpoint_props = false; // only control POST requests, not GET

View File

@ -1,5 +1,5 @@
# Download webui assets from Hugging Face Bucket at build time # Download UI assets from Hugging Face Bucket at build time
# Usage: cmake -DPUBLIC_DIR=... -DHF_BUCKET=... -DHF_VERSION=... -DASSETS="a;b;c" -P scripts/webui-download.cmake # Usage: cmake -DPUBLIC_DIR=... -DHF_BUCKET=... -DHF_VERSION=... -DASSETS="a;b;c" -P scripts/ui-download.cmake
# #
# Asset provisioning priority: # Asset provisioning priority:
# 1. Pre-built assets already in PUBLIC_DIR (cached from a previous run) # 1. Pre-built assets already in PUBLIC_DIR (cached from a previous run)
@ -14,7 +14,7 @@ set(HF_VERSION "" CACHE STRING "Version to download (empty = resolve from git)
set(ASSETS "" CACHE STRING "Plus-separated list of asset filenames (+)") set(ASSETS "" CACHE STRING "Plus-separated list of asset filenames (+)")
set(STAMP_FILE "" CACHE STRING "Stamp file to create on success (optional)") set(STAMP_FILE "" CACHE STRING "Stamp file to create on success (optional)")
set(SOURCE_DIR "" CACHE STRING "Project source root (to resolve version from git)") set(SOURCE_DIR "" CACHE STRING "Project source root (to resolve version from git)")
set(NPM_DIR "" CACHE STRING "WebUI source directory (to run npm build)") set(NPM_DIR "" CACHE STRING "UI source directory (to run npm build)")
set(HF_ENABLED "" CACHE STRING "Whether to allow HF Bucket download (ON/OFF)") set(HF_ENABLED "" CACHE STRING "Whether to allow HF Bucket download (ON/OFF)")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -25,8 +25,8 @@ if("${RESOLVED_VERSION}" STREQUAL "" AND NOT "${SOURCE_DIR}" STREQUAL "")
if(EXISTS "${SOURCE_DIR}/cmake/build-info.cmake") if(EXISTS "${SOURCE_DIR}/cmake/build-info.cmake")
include("${SOURCE_DIR}/cmake/build-info.cmake") include("${SOURCE_DIR}/cmake/build-info.cmake")
if(NOT "${BUILD_NUMBER}" STREQUAL "" AND NOT BUILD_NUMBER EQUAL 0) if(NOT "${BUILD_NUMBER}" STREQUAL "" AND NOT BUILD_NUMBER EQUAL 0)
set(RESOLVED_VERSION "${BUILD_NUMBER}") set(RESOLVED_VERSION "b${BUILD_NUMBER}")
message(STATUS "WebUI: resolved version from git: ${RESOLVED_VERSION}") message(STATUS "UI: resolved version from git: ${RESOLVED_VERSION}")
endif() endif()
endif() endif()
endif() endif()
@ -43,7 +43,7 @@ if(NOT "${STAMP_FILE}" STREQUAL "" AND EXISTS "${STAMP_FILE}")
file(READ "${STAMP_FILE}" STAMPED_VERSION) file(READ "${STAMP_FILE}" STAMPED_VERSION)
string(STRIP "${STAMPED_VERSION}" STAMPED_VERSION) string(STRIP "${STAMPED_VERSION}" STAMPED_VERSION)
if(NOT "${STAMPED_VERSION}" STREQUAL "${RESOLVED_VERSION}") if(NOT "${STAMPED_VERSION}" STREQUAL "${RESOLVED_VERSION}")
message(STATUS "WebUI: version changed (${STAMPED_VERSION} -> ${RESOLVED_VERSION}), re-building") message(STATUS "UI: version changed (${STAMPED_VERSION} -> ${RESOLVED_VERSION}), re-building")
set(FORCE_REBUILD TRUE) set(FORCE_REBUILD TRUE)
endif() endif()
endif() endif()
@ -60,7 +60,7 @@ foreach(asset ${ASSETS})
endforeach() endforeach()
if(ALL_EXISTS AND NOT FORCE_REBUILD) if(ALL_EXISTS AND NOT FORCE_REBUILD)
message(STATUS "WebUI: all assets already exist in ${PUBLIC_DIR}, skipping") message(STATUS "UI: all assets already exist in ${PUBLIC_DIR}, skipping")
return() return()
endif() endif()
@ -76,27 +76,27 @@ if(NOT PROVISION_SUCCESS AND NOT "${NPM_DIR}" STREQUAL "")
# Check if npm is available before attempting npm build # Check if npm is available before attempting npm build
find_program(NPM_EXECUTABLE npm) find_program(NPM_EXECUTABLE npm)
if(NPM_EXECUTABLE) if(NPM_EXECUTABLE)
message(STATUS "WebUI: building from source in ${NPM_DIR}") message(STATUS "UI: building from source in ${NPM_DIR}")
# Run npm install if node_modules is missing # Run npm install if node_modules is missing
if(NOT EXISTS "${NPM_DIR}/node_modules") if(NOT EXISTS "${NPM_DIR}/node_modules")
message(STATUS "WebUI: running npm install (first time)") message(STATUS "UI: running npm install (first time)")
execute_process( execute_process(
COMMAND npm install COMMAND ${NPM_EXECUTABLE} install
WORKING_DIRECTORY "${NPM_DIR}" WORKING_DIRECTORY "${NPM_DIR}"
RESULT_VARIABLE NPM_INSTALL_RESULT RESULT_VARIABLE NPM_INSTALL_RESULT
OUTPUT_VARIABLE NPM_OUT OUTPUT_VARIABLE NPM_OUT
ERROR_VARIABLE NPM_ERR ERROR_VARIABLE NPM_ERR
) )
if(NOT NPM_INSTALL_RESULT EQUAL 0) if(NOT NPM_INSTALL_RESULT EQUAL 0)
message(STATUS "WebUI: npm install failed (${NPM_INSTALL_RESULT}), falling back to download") message(STATUS "UI: npm install failed (${NPM_INSTALL_RESULT}), falling back to download")
message(STATUS " stderr: ${NPM_ERR}") message(STATUS " stderr: ${NPM_ERR}")
endif() endif()
endif() endif()
# Run the build # Run the build
execute_process( execute_process(
COMMAND npm run build COMMAND ${NPM_EXECUTABLE} run build
WORKING_DIRECTORY "${NPM_DIR}" WORKING_DIRECTORY "${NPM_DIR}"
RESULT_VARIABLE NPM_BUILD_RESULT RESULT_VARIABLE NPM_BUILD_RESULT
OUTPUT_VARIABLE NPM_OUT OUTPUT_VARIABLE NPM_OUT
@ -114,20 +114,20 @@ if(NOT PROVISION_SUCCESS AND NOT "${NPM_DIR}" STREQUAL "")
endforeach() endforeach()
if(ALL_BUILT) if(ALL_BUILT)
message(STATUS "WebUI: local npm build succeeded") message(STATUS "UI: local npm build succeeded")
set(PROVISION_SUCCESS TRUE) set(PROVISION_SUCCESS TRUE)
else() else()
message(STATUS "WebUI: npm build completed but assets missing from ${PUBLIC_DIR}, falling back to download") message(STATUS "UI: npm build completed but assets missing from ${PUBLIC_DIR}, falling back to download")
endif() endif()
else() else()
message(STATUS "WebUI: npm build failed (${NPM_BUILD_RESULT}), falling back to download") message(STATUS "UI: npm build failed (${NPM_BUILD_RESULT}), falling back to download")
message(STATUS " stderr: ${NPM_ERR}") message(STATUS " stderr: ${NPM_ERR}")
endif() endif()
else() else()
message(STATUS "WebUI: npm not found, skipping npm build and trying HF Bucket download") message(STATUS "UI: npm not found, skipping npm build and trying HF Bucket download")
endif() endif()
else() else()
message(STATUS "WebUI: NPM_DIR (${NPM_DIR}) has no package.json, skipping npm build") message(STATUS "UI: NPM_DIR (${NPM_DIR}) has no package.json, skipping npm build")
endif() endif()
endif() endif()
@ -148,7 +148,7 @@ if(NOT PROVISION_SUCCESS AND HF_ENABLED)
string(REGEX REPLACE "^([^:]+):.*$" "\\1" url_label "${entry}") string(REGEX REPLACE "^([^:]+):.*$" "\\1" url_label "${entry}")
string(REGEX REPLACE "^[^:]+:(.*)$" "\\1" base_url "${entry}") string(REGEX REPLACE "^[^:]+:(.*)$" "\\1" base_url "${entry}")
message(STATUS "WebUI: downloading assets from ${url_label}: ${base_url}") message(STATUS "UI: downloading assets from ${url_label}: ${base_url}")
# Download each asset # Download each asset
set(ALL_OK TRUE) set(ALL_OK TRUE)
@ -161,11 +161,11 @@ if(NOT PROVISION_SUCCESS AND HF_ENABLED)
list(GET download_status 0 download_result) list(GET download_status 0 download_result)
if(NOT download_result EQUAL 0) if(NOT download_result EQUAL 0)
list(GET download_status 1 error_message) list(GET download_status 1 error_message)
message(STATUS "WebUI: failed to download ${asset} from ${url_label}: ${error_message}") message(STATUS "UI: failed to download ${asset} from ${url_label}: ${error_message}")
set(ALL_OK FALSE) set(ALL_OK FALSE)
break() break()
endif() endif()
message(STATUS "WebUI: downloaded ${asset}") message(STATUS "UI: downloaded ${asset}")
endforeach() endforeach()
if(NOT ALL_OK) if(NOT ALL_OK)
@ -179,7 +179,7 @@ if(NOT PROVISION_SUCCESS AND HF_ENABLED)
) )
list(GET checksum_status 0 checksum_result) list(GET checksum_status 0 checksum_result)
if(checksum_result EQUAL 0) if(checksum_result EQUAL 0)
message(STATUS "WebUI: verifying checksums...") message(STATUS "UI: verifying checksums...")
file(STRINGS "${PUBLIC_DIR}/checksums.txt" CHECKSUMS_CONTENT) file(STRINGS "${PUBLIC_DIR}/checksums.txt" CHECKSUMS_CONTENT)
foreach(asset ${ASSETS}) foreach(asset ${ASSETS})
set(download_path "${PUBLIC_DIR}/${asset}") set(download_path "${PUBLIC_DIR}/${asset}")
@ -187,12 +187,13 @@ if(NOT PROVISION_SUCCESS AND HF_ENABLED)
string(TOLOWER "${asset_hash}" EXPECTED_HASH_LOWER) string(TOLOWER "${asset_hash}" EXPECTED_HASH_LOWER)
string(REGEX MATCH "${EXPECTED_HASH_LOWER}[ \\t]+${asset}" CHECKSUM_LINE "${CHECKSUMS_CONTENT}") string(REGEX MATCH "${EXPECTED_HASH_LOWER}[ \\t]+${asset}" CHECKSUM_LINE "${CHECKSUMS_CONTENT}")
if(NOT CHECKSUM_LINE) if(NOT CHECKSUM_LINE)
message(WARNING "WebUI: checksum verification failed for ${asset}") message(WARNING "UI: checksum verification failed for ${asset}")
message(WARNING " downloaded file may not match expected checksum, but will be used") set(ALL_OK FALSE)
break()
endif() endif()
endforeach() endforeach()
if(ALL_OK) if(ALL_OK)
message(STATUS "WebUI: all checksums verified") message(STATUS "UI: all checksums verified")
endif() endif()
endif() endif()
@ -203,9 +204,9 @@ if(NOT PROVISION_SUCCESS AND HF_ENABLED)
endforeach() endforeach()
if(PROVISION_SUCCESS) if(PROVISION_SUCCESS)
message(STATUS "WebUI: provisioning complete") message(STATUS "UI: provisioning complete")
else() else()
message(WARNING "WebUI: failed to download assets from HF Bucket (${HF_BUCKET})") message(WARNING "UI: failed to download assets from HF Bucket (${HF_BUCKET})")
endif() endif()
endif() endif()
@ -217,6 +218,6 @@ if(PROVISION_SUCCESS)
file(WRITE "${STAMP_FILE}" "${RESOLVED_VERSION}") file(WRITE "${STAMP_FILE}" "${RESOLVED_VERSION}")
endif() endif()
else() else()
message(WARNING "WebUI: no source available. Neither local build (${NPM_DIR}) nor HF Bucket download succeeded.") message(WARNING "UI: no source available. Neither local build (${NPM_DIR}) nor HF Bucket download succeeded.")
message(WARNING "WebUI: building server without embedded WebUI. Set LLAMA_BUILD_WEBUI=OFF to suppress this warning.") message(WARNING "UI: building server without embedded UI. Set LLAMA_BUILD_UI=OFF to suppress this warning.")
endif() endif()

View File

@ -1,5 +1,5 @@
# CMake equivalent of `xxd -i ${INPUT} ${OUTPUT}` # CMake equivalent of `xxd -i ${INPUT} ${OUTPUT}`
# Usage: cmake -DINPUT=tools/server/public/index.html -DOUTPUT=tools/server/index.html.hpp -P scripts/xxd.cmake # Usage: cmake -DINPUT=build/tools/ui/dist/index.html -DOUTPUT=build/tools/ui/dist/index.html.hpp -P scripts/xxd.cmake
SET(INPUT "" CACHE STRING "Input File") SET(INPUT "" CACHE STRING "Input File")
SET(OUTPUT "" CACHE STRING "Output File") SET(OUTPUT "" CACHE STRING "Output File")

View File

@ -22,6 +22,7 @@ else()
add_subdirectory(perplexity) add_subdirectory(perplexity)
add_subdirectory(quantize) add_subdirectory(quantize)
if (LLAMA_BUILD_SERVER) if (LLAMA_BUILD_SERVER)
add_subdirectory(ui)
add_subdirectory(cli) add_subdirectory(cli)
add_subdirectory(server) add_subdirectory(server)
endif() endif()

View File

@ -40,136 +40,11 @@ set(TARGET_SRCS
server-models.h server-models.h
) )
# Option to specify custom HF bucket for webui (defaults to llama-ui)
# Usage: cmake -B build -DLLAMA_WEBUI_HF_BUCKET=llama-ui
set(LLAMA_WEBUI_HF_BUCKET "llama-ui" CACHE STRING "Hugging Face bucket name for prebuilt webui assets")
if (LLAMA_BUILD_WEBUI)
set(PUBLIC_ASSETS
index.html
bundle.js
bundle.css
loading.html
)
# Determine source of webui assets (priority: local > HF Bucket)
set(WEBUI_SOURCE "")
set(WEBUI_SOURCE_DIR "")
# Priority 1: Check for local webui build output
set(LOCAL_WEBUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/public")
# Verify all required assets exist before declaring local source valid
set(ALL_ASSETS_PRESENT TRUE)
foreach(asset ${PUBLIC_ASSETS})
if(NOT EXISTS "${LOCAL_WEBUI_DIR}/${asset}")
set(ALL_ASSETS_PRESENT FALSE)
break()
endif()
endforeach()
if(ALL_ASSETS_PRESENT)
set(WEBUI_SOURCE "local")
set(WEBUI_SOURCE_DIR "${LOCAL_WEBUI_DIR}")
message(STATUS "WebUI: using local build from ${WEBUI_SOURCE_DIR}")
endif()
# Priority 2: Build-time asset provisioning (npm build HF Bucket fallback)
if(NOT WEBUI_SOURCE_DIR)
# Environment variable takes precedence (e.g., from CI workflows)
if(DEFINED ENV{HF_WEBUI_VERSION})
set(HF_WEBUI_VERSION "$ENV{HF_WEBUI_VERSION}")
# Validate against allowed characters to prevent CMake list separator
# or path-traversal issues in stamp filenames and download URLs
if(NOT HF_WEBUI_VERSION MATCHES "^[A-Za-z0-9._-]+$")
message(FATAL_ERROR "WebUI: invalid HF_WEBUI_VERSION='${HF_WEBUI_VERSION}' - must match ^[A-Za-z0-9._-]+$")
endif()
message(STATUS "WebUI: using HF_WEBUI_VERSION from environment=${HF_WEBUI_VERSION}")
elseif(DEFINED LLAMA_BUILD_NUMBER)
set(HF_WEBUI_VERSION "b${LLAMA_BUILD_NUMBER}")
message(STATUS "WebUI: using LLAMA_BUILD_NUMBER=${HF_WEBUI_VERSION}")
else()
set(HF_WEBUI_VERSION "")
message(STATUS "WebUI: version not specified (will use HF 'latest')")
endif()
# Stamp file embeds the version tag so a changed build number triggers
# a fresh provision run on the next `cmake --build` without reconfiguring.
if("${HF_WEBUI_VERSION}" STREQUAL "")
set(WEBUI_VERSION_TAG "provisioned")
else()
set(WEBUI_VERSION_TAG "${HF_WEBUI_VERSION}")
endif()
set(WEBUI_STAMP "${CMAKE_CURRENT_BINARY_DIR}/.webui-${WEBUI_VERSION_TAG}.stamp")
# Join assets with + separator (safe across all platforms, unlike ; and |)
string(REPLACE ";" "+" PUBLIC_ASSETS_JOINED "${PUBLIC_ASSETS}")
add_custom_command(
OUTPUT ${WEBUI_STAMP}
COMMAND ${CMAKE_COMMAND}
"-DSOURCE_DIR=${PROJECT_SOURCE_DIR}"
"-DPUBLIC_DIR=${CMAKE_CURRENT_SOURCE_DIR}/public"
"-DHF_BUCKET=${LLAMA_WEBUI_HF_BUCKET}"
"-DHF_VERSION=${HF_WEBUI_VERSION}"
"-DHF_ENABLED=${LLAMA_USE_PREBUILT_WEBUI}"
"-DASSETS=${PUBLIC_ASSETS_JOINED}"
"-DSTAMP_FILE=${WEBUI_STAMP}"
"-DNPM_DIR=${CMAKE_CURRENT_SOURCE_DIR}/webui"
-P ${PROJECT_SOURCE_DIR}/scripts/webui-download.cmake
COMMENT "Building/provisioning WebUI assets (npm build -> HF Bucket fallback)"
)
set(WEBUI_SOURCE "provisioned")
set(WEBUI_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/public")
endif()
# Process assets from the determined source
if(WEBUI_SOURCE_DIR)
foreach(asset ${PUBLIC_ASSETS})
set(input "${WEBUI_SOURCE_DIR}/${asset}")
set(output "${CMAKE_CURRENT_BINARY_DIR}/${asset}.hpp")
list(APPEND TARGET_SRCS ${output})
if(WEBUI_SOURCE STREQUAL "local")
# Local build: files exist at configure time
if(NOT EXISTS "${input}")
message(FATAL_ERROR "WebUI asset not found: ${input}")
endif()
set(dependency "${input}")
else()
# HF Bucket: files are downloaded at build time
set(dependency "${WEBUI_STAMP}")
endif()
add_custom_command(
DEPENDS ${dependency}
OUTPUT "${output}"
COMMAND "${CMAKE_COMMAND}" "-DINPUT=${input}" "-DOUTPUT=${output}" -P "${PROJECT_SOURCE_DIR}/scripts/xxd.cmake"
)
set_source_files_properties(${output} PROPERTIES GENERATED TRUE)
endforeach()
add_definitions(-DLLAMA_BUILD_WEBUI)
add_definitions(-DLLAMA_WEBUI_DEFAULT_ENABLED=1)
message(STATUS "WebUI: embedded with source: ${WEBUI_SOURCE}")
else()
# WebUI source not found - issue warning but don't fail the build
# The server will still build but without webui embedded
message(WARNING "WebUI: no source available. Neither local build (tools/server/public/) nor HF Bucket download succeeded.")
message(WARNING "WebUI: building server without embedded WebUI. Set LLAMA_BUILD_WEBUI=OFF to suppress this warning.")
add_definitions(-DLLAMA_WEBUI_DEFAULT_ENABLED=0)
endif()
else()
# WebUI is disabled at build time
add_definitions(-DLLAMA_WEBUI_DEFAULT_ENABLED=0)
endif()
add_executable(${TARGET} ${TARGET_SRCS}) add_executable(${TARGET} ${TARGET_SRCS})
install(TARGETS ${TARGET} RUNTIME) install(TARGETS ${TARGET} RUNTIME)
target_include_directories(${TARGET} PRIVATE ../mtmd) target_include_directories(${TARGET} PRIVATE ../mtmd)
target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR}) target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(${TARGET} PRIVATE server-context PUBLIC llama-common cpp-httplib ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(${TARGET} PRIVATE server-context llama-ui PUBLIC llama-common cpp-httplib ${CMAKE_THREAD_LIBS_INIT})
target_compile_features(${TARGET} PRIVATE cxx_std_17) target_compile_features(${TARGET} PRIVATE cxx_std_17)

View File

@ -224,7 +224,7 @@ The SvelteKit-based Web UI is introduced in this PR: https://github.com/ggml-org
### Architecture ### Architecture
The WebUI follows a layered architecture: The UI follows a layered architecture:
``` ```
Routes → Components → Hooks → Stores → Services → Storage/API Routes → Components → Hooks → Stores → Services → Storage/API
@ -234,7 +234,7 @@ Routes → Components → Hooks → Stores → Services → Storage/API
- **Services** - stateless API/database communication (`ChatService`, `ModelsService`, `PropsService`, `DatabaseService`) - **Services** - stateless API/database communication (`ChatService`, `ModelsService`, `PropsService`, `DatabaseService`)
- **Hooks** - reusable logic (`useModelChangeValidation`, `useProcessingState`) - **Hooks** - reusable logic (`useModelChangeValidation`, `useProcessingState`)
For detailed architecture diagrams, see [`tools/server/webui/docs/`](webui/docs/): For detailed architecture diagrams, see [`tools/ui/docs/`](../ui/docs/):
- `high-level-architecture.mmd` - full architecture with all modules - `high-level-architecture.mmd` - full architecture with all modules
- `high-level-architecture-simplified.mmd` - simplified overview - `high-level-architecture-simplified.mmd` - simplified overview
@ -246,7 +246,7 @@ For detailed architecture diagrams, see [`tools/server/webui/docs/`](webui/docs/
```sh ```sh
# make sure you have Node.js installed # make sure you have Node.js installed
cd tools/server/webui cd tools/ui
npm i npm i
# run dev server (with hot reload) # run dev server (with hot reload)

View File

@ -189,11 +189,11 @@ For the full list of features, please refer to [server's changelog](https://gith
| `--reuse-port` | allow multiple sockets to bind to the same port (default: disabled)<br/>(env: LLAMA_ARG_REUSE_PORT) | | `--reuse-port` | allow multiple sockets to bind to the same port (default: disabled)<br/>(env: LLAMA_ARG_REUSE_PORT) |
| `--path PATH` | path to serve static files from (default: )<br/>(env: LLAMA_ARG_STATIC_PATH) | | `--path PATH` | path to serve static files from (default: )<br/>(env: LLAMA_ARG_STATIC_PATH) |
| `--api-prefix PREFIX` | prefix path the server serves from, without the trailing slash (default: )<br/>(env: LLAMA_ARG_API_PREFIX) | | `--api-prefix PREFIX` | prefix path the server serves from, without the trailing slash (default: )<br/>(env: LLAMA_ARG_API_PREFIX) |
| `--webui-config JSON` | JSON that provides default WebUI settings (overrides WebUI defaults)<br/>(env: LLAMA_ARG_WEBUI_CONFIG) | | `--ui-config JSON` / `--webui-config JSON` (deprecated) | JSON that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG / LLAMA_ARG_WEBUI_CONFIG) |
| `--webui-config-file PATH` | JSON file that provides default WebUI settings (overrides WebUI defaults)<br/>(env: LLAMA_ARG_WEBUI_CONFIG_FILE) | | `--ui-config-file PATH` / `--webui-config-file PATH` (deprecated) | JSON file that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG_FILE / LLAMA_ARG_WEBUI_CONFIG_FILE) |
| `--webui-mcp-proxy, --no-webui-mcp-proxy` | experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_WEBUI_MCP_PROXY) | | `--ui-mcp-proxy, --no-ui-mcp-proxy` / `--webui-mcp-proxy, --no-webui-mcp-proxy` (deprecated) | experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_UI_MCP_PROXY / LLAMA_ARG_WEBUI_MCP_PROXY) |
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, apply_diff, get_datetime<br/>(env: LLAMA_ARG_TOOLS) | | `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, apply_diff, get_datetime<br/>(env: LLAMA_ARG_TOOLS) |
| `--webui, --no-webui` | whether to enable the Web UI (default: enabled)<br/>(env: LLAMA_ARG_WEBUI) | | `--ui, --no-ui` / `--webui, --no-webui` (deprecated) | whether to enable the Web UI (default: enabled)<br/>(env: LLAMA_ARG_UI / LLAMA_ARG_WEBUI) |
| `--embedding, --embeddings` | restrict to only support embedding use case; use only with dedicated embedding models (default: disabled)<br/>(env: LLAMA_ARG_EMBEDDINGS) | | `--embedding, --embeddings` | restrict to only support embedding use case; use only with dedicated embedding models (default: disabled)<br/>(env: LLAMA_ARG_EMBEDDINGS) |
| `--rerank, --reranking` | enable reranking endpoint on server (default: disabled)<br/>(env: LLAMA_ARG_RERANKING) | | `--rerank, --reranking` | enable reranking endpoint on server (default: disabled)<br/>(env: LLAMA_ARG_RERANKING) |
| `--api-key KEY` | API key to use for authentication, multiple keys can be provided as a comma-separated list (default: none)<br/>(env: LLAMA_API_KEY) | | `--api-key KEY` | API key to use for authentication, multiple keys can be provided as a comma-separated list (default: none)<br/>(env: LLAMA_API_KEY) |
@ -1831,10 +1831,12 @@ Apart from error types supported by OAI, we also have custom types that are spec
### Custom default Web UI preferences ### Custom default Web UI preferences
You can specify default preferences for the web UI using `--webui-config <JSON config>` or `--webui-config-file <path to JSON config>`. For example, you can disable pasting long text as attachments and enable rendering Markdown in user messages with this command: You can specify default preferences for the web UI using `--ui-config <JSON config>` or `--ui-config-file <path to JSON config>`. For example, you can disable pasting long text as attachments and enable rendering Markdown in user messages with this command:
```bash ```bash
./llama-server -m model.gguf --webui-config '{"pasteLongTextToFileLen": 0, "renderUserContentAsMarkdown": true}' ./llama-server -m model.gguf --ui-config '{"pasteLongTextToFileLen": 0, "renderUserContentAsMarkdown": true}'
``` ```
You may find available preferences in [settings-config.ts](webui/src/lib/constants/settings-config.ts). > **Note:** The old flags `--webui-config` and `--webui-config-file` are deprecated but still work as aliases.
You may find available preferences in [settings-config.ts](../ui/src/lib/constants/settings-config.ts).

View File

@ -671,7 +671,8 @@ private:
server_metrics metrics; server_metrics metrics;
json json_webui_settings = json::object(); json json_ui_settings = json::object(); // Primary: new name
json json_webui_settings = json::object(); // Deprecated: use json_ui_settings instead (kept for compat)
// Necessary similarity of prompt for slot selection // Necessary similarity of prompt for slot selection
float slot_prompt_similarity = 0.0f; float slot_prompt_similarity = 0.0f;
@ -996,13 +997,18 @@ private:
} }
} }
// populate webui settings // populate UI settings (from either new ui_config_json or deprecated webui_config_json)
{ {
if (!params_base.webui_config_json.empty()) { const std::string & cfg = !params_base.ui_config_json.empty()
? params_base.ui_config_json
: params_base.webui_config_json;
if (!cfg.empty()) {
try { try {
json_webui_settings = json::parse(params_base.webui_config_json); json json_settings = json::parse(cfg);
json_ui_settings = json_settings;
json_webui_settings = json_settings; // deprecated: keep in sync
} catch (const std::exception & e) { } catch (const std::exception & e) {
SRV_ERR("%s: failed to parse webui config: %s\n", __func__, e.what()); SRV_ERR("%s: failed to parse UI config: %s\n", __func__, e.what());
return false; return false;
} }
} }
@ -3292,7 +3298,8 @@ server_context_meta server_context::get_meta() const {
/* has_mtmd */ impl->mctx != nullptr, /* has_mtmd */ impl->mctx != nullptr,
/* has_inp_image */ impl->chat_params.allow_image, /* has_inp_image */ impl->chat_params.allow_image,
/* has_inp_audio */ impl->chat_params.allow_audio, /* has_inp_audio */ impl->chat_params.allow_audio,
/* json_webui_settings */ impl->json_webui_settings, /* json_ui_settings */ impl->json_ui_settings,
/* json_webui_settings */ impl->json_webui_settings, // Deprecated
/* slot_n_ctx */ impl->get_slot_n_ctx(), /* slot_n_ctx */ impl->get_slot_n_ctx(),
/* pooling_type */ llama_pooling_type(impl->ctx_tgt), /* pooling_type */ llama_pooling_type(impl->ctx_tgt),
@ -3814,8 +3821,12 @@ void server_routes::init_routes() {
{ "endpoint_slots", params.endpoint_slots }, { "endpoint_slots", params.endpoint_slots },
{ "endpoint_props", params.endpoint_props }, { "endpoint_props", params.endpoint_props },
{ "endpoint_metrics", params.endpoint_metrics }, { "endpoint_metrics", params.endpoint_metrics },
{ "webui", params.webui }, // New keys
{ "webui_settings", meta->json_webui_settings }, { "ui", params.ui },
{ "ui_settings", meta->json_ui_settings },
// Deprecated: use ui/ui_settings instead (kept for backward compat)
{ "webui", params.webui },
{ "webui_settings", meta->json_webui_settings },
{ "chat_template", tmpl_default }, { "chat_template", tmpl_default },
{ "chat_template_caps", meta->chat_template_caps }, { "chat_template_caps", meta->chat_template_caps },
{ "bos_token", meta->bos_token_str }, { "bos_token", meta->bos_token_str },

View File

@ -21,7 +21,8 @@ struct server_context_meta {
bool has_mtmd; bool has_mtmd;
bool has_inp_image; bool has_inp_image;
bool has_inp_audio; bool has_inp_audio;
json json_webui_settings; json json_ui_settings; // Primary: new name
json json_webui_settings; // Deprecated: use json_ui_settings instead (kept for backward compat)
int slot_n_ctx; int slot_n_ctx;
enum llama_pooling_type pooling_type; enum llama_pooling_type pooling_type;

View File

@ -1,6 +1,7 @@
#include "common.h" #include "common.h"
#include "server-http.h" #include "server-http.h"
#include "server-common.h" #include "server-common.h"
#include "ui.h"
#include <cpp-httplib/httplib.h> #include <cpp-httplib/httplib.h>
@ -10,14 +11,6 @@
#include <string> #include <string>
#include <thread> #include <thread>
#ifdef LLAMA_BUILD_WEBUI
// auto generated files (see README.md for details)
#include "index.html.hpp"
#include "bundle.js.hpp"
#include "bundle.css.hpp"
#include "loading.html.hpp"
#endif
// //
// HTTP implementation using cpp-httplib // HTTP implementation using cpp-httplib
// //
@ -238,10 +231,11 @@ bool server_http_context::init(const common_params & params) {
}; };
auto middleware_server_state = [this](const httplib::Request & req, httplib::Response & res) { auto middleware_server_state = [this](const httplib::Request & req, httplib::Response & res) {
(void)req; // suppress unused parameter warning when LLAMA_BUILD_WEBUI is not defined (void)req; // suppress unused parameter warning when LLAMA_BUILD_UI / LLAMA_BUILD_WEBUI is not defined
bool ready = is_ready.load(); bool ready = is_ready.load();
if (!ready) { if (!ready) {
#ifdef LLAMA_BUILD_WEBUI // Support both old and new preprocessor defines
#if defined(LLAMA_BUILD_UI) || defined(LLAMA_BUILD_WEBUI)
auto tmp = string_split<std::string>(req.path, '.'); auto tmp = string_split<std::string>(req.path, '.');
if (req.path == "/" || (tmp.size() > 0 && tmp.back() == "html")) { if (req.path == "/" || (tmp.size() > 0 && tmp.back() == "html")) {
res.status = 503; res.status = 503;
@ -305,8 +299,10 @@ bool server_http_context::init(const common_params & params) {
// Web UI setup // Web UI setup
// //
if (!params.webui) { // Use new `params.ui` field (backed by old `params.webui` for compat)
SRV_INF("%s", "the WebUI is disabled\n"); if (!params.ui) {
SRV_INF("%s", "The UI is disabled\n");
SRV_INF("%s", "Use --ui/--no-ui (or deprecated --webui/--no-webui) to enable/disable\n");
} else { } else {
// register static assets routes // register static assets routes
if (!params.public_path.empty()) { if (!params.public_path.empty()) {
@ -317,7 +313,8 @@ bool server_http_context::init(const common_params & params) {
return 1; return 1;
} }
} else { } else {
#ifdef LLAMA_BUILD_WEBUI // Support both old and new preprocessor defines
#if defined(LLAMA_BUILD_UI) || defined(LLAMA_BUILD_WEBUI)
// using embedded static index.html // using embedded static index.html
srv->Get(params.api_prefix + "/", [](const httplib::Request & /*req*/, httplib::Response & res) { srv->Get(params.api_prefix + "/", [](const httplib::Request & /*req*/, httplib::Response & res) {
// COEP and COOP headers, required by pyodide (python interpreter) // COEP and COOP headers, required by pyodide (python interpreter)

View File

@ -1152,14 +1152,17 @@ void server_models_routes::init_routes() {
{"role", "router"}, {"role", "router"},
{"max_instances", params.models_max}, {"max_instances", params.models_max},
{"models_autoload", params.models_autoload}, {"models_autoload", params.models_autoload},
// this is a dummy response to make sure webui doesn't break // this is a dummy response to make sure the UI doesn't break
{"model_alias", "llama-server"}, {"model_alias", "llama-server"},
{"model_path", "none"}, {"model_path", "none"},
{"default_generation_settings", { {"default_generation_settings", {
{"params", json{}}, {"params", json{}},
{"n_ctx", 0}, {"n_ctx", 0},
}}, }},
{"webui_settings", webui_settings}, // New key
{"ui_settings", ui_settings},
// Deprecated: use ui_settings instead (kept for backward compat)
{"webui_settings", webui_settings},
{"build_info", std::string(llama_build_info())}, {"build_info", std::string(llama_build_info())},
}); });
return res; return res;

View File

@ -175,15 +175,22 @@ public:
struct server_models_routes { struct server_models_routes {
common_params params; common_params params;
json webui_settings = json::object(); json ui_settings = json::object(); // Primary: new name
json webui_settings = json::object(); // Deprecated: use ui_settings (kept for compat)
server_models models; server_models models;
server_models_routes(const common_params & params, int argc, char ** argv) server_models_routes(const common_params & params, int argc, char ** argv)
: params(params), models(params, argc, argv) { : params(params), models(params, argc, argv) {
if (!this->params.webui_config_json.empty()) { // Support both new ui_config_json and deprecated webui_config_json
const std::string & cfg = !this->params.ui_config_json.empty()
? this->params.ui_config_json
: this->params.webui_config_json;
if (!cfg.empty()) {
try { try {
webui_settings = json::parse(this->params.webui_config_json); json json_settings = json::parse(cfg);
ui_settings = json_settings;
webui_settings = json_settings; // Deprecated: keep in sync
} catch (const std::exception & e) { } catch (const std::exception & e) {
LOG_ERR("%s: failed to parse webui config: %s\n", __func__, e.what()); LOG_ERR("%s: failed to parse UI config: %s\n", __func__, e.what());
throw; throw;
} }
} }

View File

@ -208,7 +208,8 @@ int main(int argc, char ** argv) {
ctx_http.register_gcp_compat(); ctx_http.register_gcp_compat();
// CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP) // CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP)
if (params.webui_mcp_proxy) { // Supports both new ui_mcp_proxy and deprecated webui_mcp_proxy fields
if (params.ui_mcp_proxy || params.webui_mcp_proxy) {
SRV_WRN("%s", "-----------------\n"); SRV_WRN("%s", "-----------------\n");
SRV_WRN("%s", "CORS proxy is enabled, do not expose server to untrusted environments\n"); SRV_WRN("%s", "CORS proxy is enabled, do not expose server to untrusted environments\n");
SRV_WRN("%s", "This feature is EXPERIMENTAL and may be removed or changed in future versions\n"); SRV_WRN("%s", "This feature is EXPERIMENTAL and may be removed or changed in future versions\n");

View File

@ -1,3 +0,0 @@
rm -rf ../public/_app;
rm ../public/favicon.svg;
rm -f ../public/index.html.gz; # deprecated, but may still be generated by older versions of the build process

View File

@ -1,6 +0,0 @@
export const ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = 'LlamaCppWebui.alwaysAllowedTools';
export const CONFIG_LOCALSTORAGE_KEY = 'LlamaCppWebui.config';
export const DISABLED_TOOLS_LOCALSTORAGE_KEY = 'LlamaCppWebui.disabledTools';
export const FAVORITE_MODELS_LOCALSTORAGE_KEY = 'LlamaCppWebui.favoriteModels';
export const MCP_DEFAULT_ENABLED_LOCALSTORAGE_KEY = 'LlamaCppWebui.mcpDefaultEnabled';
export const USER_OVERRIDES_LOCALSTORAGE_KEY = 'LlamaCppWebui.userOverrides';

157
tools/ui/CMakeLists.txt Normal file
View File

@ -0,0 +1,157 @@
set(TARGET llama-ui)
# Deprecated: use LLAMA_UI_HF_BUCKET instead
set(LLAMA_WEBUI_HF_BUCKET "llama-ui" CACHE STRING "Hugging Face bucket name for prebuilt webui assets (deprecated: use LLAMA_UI_HF_BUCKET)")
set(LLAMA_UI_HF_BUCKET "llama-ui" CACHE STRING "Hugging Face bucket name for prebuilt UI assets")
# Backward compat: forward old var to new one
if(DEFINED LLAMA_WEBUI_HF_BUCKET AND NOT DEFINED LLAMA_UI_HF_BUCKET)
set(LLAMA_UI_HF_BUCKET ${LLAMA_WEBUI_HF_BUCKET})
elseif(DEFINED LLAMA_WEBUI_HF_BUCKET AND NOT "${LLAMA_WEBUI_HF_BUCKET}" STREQUAL "${LLAMA_UI_HF_BUCKET}")
message(DEPRECATION "LLAMA_WEBUI_HF_BUCKET is deprecated, use LLAMA_UI_HF_BUCKET instead")
endif()
set(TARGET_SRCS "")
set(UI_COMPILE_DEFS "")
# Support both old (LLAMA_BUILD_WEBUI) and new (LLAMA_BUILD_UI) option names
if(LLAMA_BUILD_WEBUI OR LLAMA_BUILD_UI)
if(LLAMA_BUILD_WEBUI AND NOT LLAMA_BUILD_UI)
message(DEPRECATION "LLAMA_BUILD_WEBUI is deprecated, use LLAMA_BUILD_UI instead")
endif()
set(PUBLIC_ASSETS
index.html
bundle.js
bundle.css
loading.html
)
# Determine source of UI assets (priority: local > HF Bucket)
set(UI_SOURCE "")
set(UI_SOURCE_DIR "")
# Priority 1: Check for local build output
set(LOCAL_UI_DIR "${PROJECT_SOURCE_DIR}/build/tools/ui/dist")
# Verify all required assets exist before declaring local source valid
set(ALL_ASSETS_PRESENT TRUE)
foreach(asset ${PUBLIC_ASSETS})
if(NOT EXISTS "${LOCAL_UI_DIR}/${asset}")
set(ALL_ASSETS_PRESENT FALSE)
break()
endif()
endforeach()
if(ALL_ASSETS_PRESENT)
set(UI_SOURCE "local")
set(UI_SOURCE_DIR "${LOCAL_UI_DIR}")
message(STATUS "UI: using local build from ${UI_SOURCE_DIR}")
endif()
# Priority 2: Build-time asset provisioning (npm build HF Bucket fallback)
if(NOT UI_SOURCE_DIR)
# Environment variable takes precedence (e.g., from CI workflows)
# Deprecated: use HF_UI_VERSION instead
if(DEFINED ENV{HF_WEBUI_VERSION})
set(HF_UI_VERSION "$ENV{HF_WEBUI_VERSION}")
message(DEPRECATION "HF_WEBUI_VERSION env var is deprecated, use HF_UI_VERSION instead")
if(NOT HF_UI_VERSION MATCHES "^[A-Za-z0-9._-]+$")
message(FATAL_ERROR "UI: invalid HF_WEBUI_VERSION='${HF_UI_VERSION}' - must match ^[A-Za-z0-9._-]+$")
endif()
elseif(DEFINED ENV{HF_UI_VERSION})
set(HF_UI_VERSION "$ENV{HF_UI_VERSION}")
if(NOT HF_UI_VERSION MATCHES "^[A-Za-z0-9._-]+$")
message(FATAL_ERROR "UI: invalid HF_UI_VERSION='${HF_UI_VERSION}' - must match ^[A-Za-z0-9._-]+$")
endif()
elseif(DEFINED LLAMA_BUILD_NUMBER)
set(HF_UI_VERSION "b${LLAMA_BUILD_NUMBER}")
message(STATUS "UI: derived HF_UI_VERSION=b${LLAMA_BUILD_NUMBER}")
else()
set(HF_UI_VERSION "")
message(STATUS "UI: version not specified (will use HF 'latest')")
endif()
if("${HF_UI_VERSION}" STREQUAL "")
set(UI_VERSION_TAG "provisioned")
else()
set(UI_VERSION_TAG "${HF_UI_VERSION}")
endif()
set(UI_STAMP "${CMAKE_CURRENT_BINARY_DIR}/.ui-${UI_VERSION_TAG}.stamp")
string(REPLACE ";" "+" PUBLIC_ASSETS_JOINED "${PUBLIC_ASSETS}")
add_custom_command(
OUTPUT ${UI_STAMP}
COMMAND ${CMAKE_COMMAND}
"-DSOURCE_DIR=${PROJECT_SOURCE_DIR}"
"-DPUBLIC_DIR=${PROJECT_SOURCE_DIR}/build/tools/ui/dist"
"-DHF_BUCKET=${LLAMA_UI_HF_BUCKET}"
"-DHF_VERSION=${HF_UI_VERSION}"
"-DHF_ENABLED=${LLAMA_USE_PREBUILT_UI}"
"-DASSETS=${PUBLIC_ASSETS_JOINED}"
"-DSTAMP_FILE=${UI_STAMP}"
"-DNPM_DIR=${PROJECT_SOURCE_DIR}/tools/ui"
-P ${PROJECT_SOURCE_DIR}/scripts/ui-download.cmake
COMMENT "Building/provisioning UI assets (npm build -> HF Bucket fallback)"
)
set(UI_SOURCE "provisioned")
set(UI_SOURCE_DIR "${PROJECT_SOURCE_DIR}/build/tools/ui/dist")
endif()
# Process assets from the determined source
if(UI_SOURCE_DIR)
foreach(asset ${PUBLIC_ASSETS})
set(input "${UI_SOURCE_DIR}/${asset}")
set(output "${CMAKE_CURRENT_BINARY_DIR}/${asset}.hpp")
list(APPEND TARGET_SRCS ${output})
if(UI_SOURCE STREQUAL "local")
if(NOT EXISTS "${input}")
message(FATAL_ERROR "UI asset not found: ${input}")
endif()
set(dependency "${input}")
else()
set(dependency "${UI_STAMP}")
endif()
add_custom_command(
DEPENDS ${dependency}
OUTPUT "${output}"
COMMAND "${CMAKE_COMMAND}" "-DINPUT=${input}" "-DOUTPUT=${output}" -P "${PROJECT_SOURCE_DIR}/scripts/xxd.cmake"
)
set_source_files_properties(${output} PROPERTIES GENERATED TRUE)
endforeach()
list(APPEND UI_COMPILE_DEFS
LLAMA_BUILD_WEBUI # Deprecated: use LLAMA_BUILD_UI
LLAMA_BUILD_UI
LLAMA_WEBUI_DEFAULT_ENABLED=1 # Deprecated: use LLAMA_UI_DEFAULT_ENABLED
LLAMA_UI_DEFAULT_ENABLED=1
)
message(STATUS "UI: embedded with source: ${UI_SOURCE}")
else()
message(WARNING "UI: no source available. Neither local build (build/tools/ui/dist/) nor HF Bucket download succeeded.")
message(WARNING "UI: building server without embedded UI. Set LLAMA_BUILD_UI=OFF to suppress this warning.")
list(APPEND UI_COMPILE_DEFS LLAMA_WEBUI_DEFAULT_ENABLED=0 LLAMA_UI_DEFAULT_ENABLED=0)
endif()
else()
list(APPEND UI_COMPILE_DEFS LLAMA_WEBUI_DEFAULT_ENABLED=0 LLAMA_UI_DEFAULT_ENABLED=0)
endif()
# Build the static library
add_library(${TARGET} STATIC ui.cpp)
target_include_directories(${TARGET} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_compile_definitions(${TARGET} PUBLIC ${UI_COMPILE_DEFS})
if(TARGET_SRCS)
# List generated .hpp files as sources so CMake tracks them as build dependencies
target_sources(${TARGET} PRIVATE ${TARGET_SRCS})
set_source_files_properties(${TARGET_SRCS} PROPERTIES HEADER_FILE_ONLY TRUE)
endif()

View File

@ -2,7 +2,7 @@
A modern, feature-rich web interface for llama-server built with SvelteKit. This UI provides an intuitive chat interface with advanced file handling, conversation management, and comprehensive model interaction capabilities. A modern, feature-rich web interface for llama-server built with SvelteKit. This UI provides an intuitive chat interface with advanced file handling, conversation management, and comprehensive model interaction capabilities.
The WebUI supports two server operation modes: Llama UI supports two server operation modes:
- **MODEL mode** - Single model operation (standard llama-server) - **MODEL mode** - Single model operation (standard llama-server)
- **ROUTER mode** - Multi-model operation with dynamic model loading/unloading - **ROUTER mode** - Multi-model operation with dynamic model loading/unloading
@ -88,7 +88,7 @@ The WebUI supports two server operation modes:
### 1. Install Dependencies ### 1. Install Dependencies
```bash ```bash
cd tools/server/webui cd tools/ui
npm install npm install
``` ```
@ -112,7 +112,7 @@ npm run dev
This starts: This starts:
- **Vite dev server** at `http://localhost:5173` - The main WebUI - **Vite dev server** at `http://localhost:5173` - The main UI frontend app
- **Storybook** at `http://localhost:6006` - Component documentation - **Storybook** at `http://localhost:6006` - Component documentation
The Vite dev server proxies API requests to `http://localhost:8080` (default llama-server port): The Vite dev server proxies API requests to `http://localhost:8080` (default llama-server port):
@ -186,7 +186,7 @@ npm run build
The build process: The build process:
1. **Vite Build** - Bundles all TypeScript, Svelte, and CSS 1. **Vite Build** - Bundles all TypeScript, Svelte, and CSS
2. **Static Adapter** - Outputs to `../public` (llama-server's static file directory) 2. **Static Adapter** - Outputs to `../../build/tools/ui/dist` (llama-server's static file directory)
3. **Post-Build Script** - Cleans up intermediate files 3. **Post-Build Script** - Cleans up intermediate files
4. **Custom Plugin** - Creates `index.html` with: 4. **Custom Plugin** - Creates `index.html` with:
- Inlined favicon as base64 - Inlined favicon as base64
@ -194,7 +194,7 @@ The build process:
- Deterministic output (zeroed timestamps) - Deterministic output (zeroed timestamps)
```text ```text
tools/server/webui/ → build → tools/server/public/ tools/ui/ → build → build/tools/ui/dist/
├── src/ ├── index.html (served by llama-server) ├── src/ ├── index.html (served by llama-server)
├── static/ └── (favicon inlined) ├── static/ └── (favicon inlined)
└── ... └── ...
@ -205,8 +205,8 @@ tools/server/webui/ → build → tools/server/public/
```javascript ```javascript
// svelte.config.js // svelte.config.js
adapter: adapter({ adapter: adapter({
pages: '../public', // Output directory pages: '../../build/tools/ui/dist', // Output directory
assets: '../public', // Static assets assets: '../../build/tools/ui/dist', // Static assets
fallback: 'index.html', // SPA fallback fallback: 'index.html', // SPA fallback
strict: true strict: true
}), }),
@ -217,20 +217,19 @@ output: {
### Integration with llama-server ### Integration with llama-server
The WebUI is embedded directly into the llama-server binary: llama-ui is embedded directly into the llama-server binary:
1. `npm run build` outputs `index.html` to `tools/server/public/` 1. `npm run build` outputs `index.html` to `build/tools/ui/dist/`
2. llama-server compiles this into the binary at build time 2. llama-server compiles this into the binary at build time
3. When accessing `/`, llama-server serves the gzipped HTML 3. When accessing `/`, llama-server serves the bundled HTML
4. All assets are inlined (CSS, JS, fonts, favicon)
This results in a **single portable binary** with the full WebUI included. This results in a **single portable binary** with the full Llama UI included.
--- ---
## Architecture ## Architecture
The WebUI follows a layered architecture with unidirectional data flow: Llama UI follows a layered architecture with unidirectional data flow:
```text ```text
Routes → Components → Hooks → Stores → Services → Storage/API Routes → Components → Hooks → Stores → Services → Storage/API
@ -659,7 +658,7 @@ npm run check # TypeScript type checking
## Project Structure ## Project Structure
```text ```text
tools/server/webui/ tools/ui/
├── src/ ├── src/
│ ├── lib/ │ ├── lib/
│ │ ├── components/ # UI components (app/, ui/) │ │ ├── components/ # UI components (app/, ui/)

View File

@ -58,8 +58,8 @@ sequenceDiagram
end end
end end
alt serverStore.props has webuiSettings alt serverStore.props has uiSettings
settingsStore->>settingsStore: Apply webuiSettings from server settingsStore->>settingsStore: Apply uiSettings from server
Note right of settingsStore: Server-provided UI settings<br/>(e.g. showRawOutputSwitch) Note right of settingsStore: Server-provided UI settings<br/>(e.g. showRawOutputSwitch)
end end

View File

@ -29,7 +29,9 @@ export default ts.config(
'no-undef': 'off', 'no-undef': 'off',
'svelte/no-at-html-tags': 'off', 'svelte/no-at-html-tags': 'off',
// This app uses hash-based routing (#/) where resolve() from $app/paths does not apply // This app uses hash-based routing (#/) where resolve() from $app/paths does not apply
'svelte/no-navigation-without-resolve': 'off' 'svelte/no-navigation-without-resolve': 'off',
// Enforce empty line at end of file
'eol-last': 'error'
} }
}, },
{ {

View File

@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "bash scripts/dev.sh", "dev": "bash scripts/dev.sh",
"build": "vite build && ./scripts/post-build.sh", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"prepare": "svelte-kit sync || echo ''", "prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",

View File

@ -2,7 +2,7 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({ export default defineConfig({
webServer: { webServer: {
command: 'npm run build && http-server ../public -p 8181', command: 'npm run build && http-server ../../build/tools/ui/dist -p 8181',
port: 8181, port: 8181,
timeout: 120000, timeout: 120000,
reuseExistingServer: false reuseExistingServer: false

View File

@ -2,14 +2,14 @@
# Development script for llama-ui # Development script for llama-ui
# #
# This script starts the webui development servers (Storybook and Vite). # This script starts the llama-ui development servers (Storybook and Vite).
# Note: You need to start llama-server separately. # Note: You need to start llama-server separately.
# #
# Usage: # Usage:
# bash scripts/dev.sh # bash scripts/dev.sh
# npm run dev # npm run dev
cd ../../../ cd ../../
# Check and install git hooks if missing # Check and install git hooks if missing
check_and_install_hooks() { check_and_install_hooks() {
@ -22,13 +22,13 @@ check_and_install_hooks() {
if [ "$hooks_missing" = true ]; then if [ "$hooks_missing" = true ]; then
echo "🔧 Git hooks missing, installing them..." echo "🔧 Git hooks missing, installing them..."
cd tools/server/webui cd tools/ui
if bash scripts/install-git-hooks.sh; then if bash scripts/install-git-hooks.sh; then
echo "✅ Git hooks installed successfully" echo "✅ Git hooks installed successfully"
else else
echo "⚠️ Failed to install git hooks, continuing anyway..." echo "⚠️ Failed to install git hooks, continuing anyway..."
fi fi
cd ../../../ cd ../../
else else
echo "✅ Git hooks already installed" echo "✅ Git hooks already installed"
fi fi
@ -48,7 +48,7 @@ trap cleanup SIGINT SIGTERM
echo "🚀 Starting development servers..." echo "🚀 Starting development servers..."
echo "📝 Note: Make sure to start llama-server separately if needed" echo "📝 Note: Make sure to start llama-server separately if needed"
cd tools/server/webui cd tools/ui
# Use --insecure-http-parser to handle malformed HTTP responses from llama-server # Use --insecure-http-parser to handle malformed HTTP responses from llama-server
# (some responses have both Content-Length and Transfer-Encoding headers) # (some responses have both Content-Length and Transfer-Encoding headers)
storybook dev -p 6006 --ci & NODE_OPTIONS="--insecure-http-parser" vite dev --host 0.0.0.0 & storybook dev -p 6006 --ci & NODE_OPTIONS="--insecure-http-parser" vite dev --host 0.0.0.0 &

View File

@ -1,29 +1,29 @@
#!/bin/bash #!/bin/bash
# Script to install pre-commit hook for webui # Script to install pre-commit hook for llama-ui
# Pre-commit: formats, checks, and builds webui # Pre-commit: formats, checks, and builds the UI app
REPO_ROOT=$(git rev-parse --show-toplevel) REPO_ROOT=$(git rev-parse --show-toplevel)
PRE_COMMIT_HOOK="$REPO_ROOT/.git/hooks/pre-commit" PRE_COMMIT_HOOK="$REPO_ROOT/.git/hooks/pre-commit"
echo "Installing pre-commit hook for webui..." echo "Installing pre-commit hook for llama-ui..."
# Create the pre-commit hook # Create the pre-commit hook
cat > "$PRE_COMMIT_HOOK" << 'EOF' cat > "$PRE_COMMIT_HOOK" << 'EOF'
#!/bin/bash #!/bin/bash
# Check if there are any changes in the webui directory # Check if there are any changes in the tools/ui directory
if git diff --cached --name-only | grep -q "^tools/server/webui/"; then if git diff --cached --name-only | grep -q "^tools/ui/"; then
REPO_ROOT=$(git rev-parse --show-toplevel) REPO_ROOT=$(git rev-parse --show-toplevel)
cd "$REPO_ROOT/tools/server/webui" cd "$REPO_ROOT/tools/ui"
# Check if package.json exists # Check if package.json exists
if [ ! -f "package.json" ]; then if [ ! -f "package.json" ]; then
echo "Error: package.json not found in tools/server/webui" echo "Error: package.json not found in tools/ui"
exit 1 exit 1
fi fi
echo "Formatting and checking webui code..." echo "Formatting and checking llama-ui code..."
# Run the format command # Run the format command
npm run format npm run format
@ -46,17 +46,17 @@ if git diff --cached --name-only | grep -q "^tools/server/webui/"; then
exit 1 exit 1
fi fi
echo "✅ Webui code formatted and checked successfully" echo "✅ llama-ui code formatted and checked successfully"
# Build the webui # Build the llama-ui
echo "Building webui..." echo "Building llama-ui..."
npm run build npm run build
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "❌ npm run build failed" echo "❌ npm run build failed"
exit 1 exit 1
fi fi
echo "✅ Webui built successfully" echo "✅ llama-ui built successfully"
fi fi
exit 0 exit 0
@ -70,8 +70,8 @@ if [ $? -eq 0 ]; then
echo " Pre-commit: $PRE_COMMIT_HOOK" echo " Pre-commit: $PRE_COMMIT_HOOK"
echo "" echo ""
echo "The hook will automatically:" echo "The hook will automatically:"
echo " • Format, lint and check webui code before commits" echo " • Format, lint and check llama-ui code before commits"
echo " • Build webui" echo " • Build llama-ui"
else else
echo "❌ Failed to make hook executable" echo "❌ Failed to make hook executable"
exit 1 exit 1

View File

@ -1,4 +1,12 @@
import { readFileSync, writeFileSync, existsSync, readdirSync, copyFileSync } from 'fs'; import {
readFileSync,
writeFileSync,
existsSync,
readdirSync,
copyFileSync,
rmSync,
unlinkSync
} from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import type { Plugin } from 'vite'; import type { Plugin } from 'vite';
@ -11,28 +19,28 @@ const GUIDE_FOR_FRONTEND = `
--> -->
`.trim(); `.trim();
const OUTPUT_DIR = '../../build/tools/ui/dist';
export function llamaCppBuildPlugin(): Plugin { export function llamaCppBuildPlugin(): Plugin {
return { return {
name: 'llamacpp:build', name: 'llamacpp:build',
apply: 'build', apply: 'build',
closeBundle() { closeBundle() {
// Ensure the SvelteKit adapter has finished writing to ../public
setTimeout(() => { setTimeout(() => {
try { try {
const indexPath = resolve('../public/index.html'); const outDir = resolve(OUTPUT_DIR);
const indexPath = resolve(outDir, 'index.html');
if (!existsSync(indexPath)) return; if (!existsSync(indexPath)) return;
let content = readFileSync(indexPath, 'utf-8'); let content = readFileSync(indexPath, 'utf-8');
// Inline favicon as base64 data URL
const faviconPath = resolve('static/favicon.svg'); const faviconPath = resolve('static/favicon.svg');
if (existsSync(faviconPath)) { if (existsSync(faviconPath)) {
const faviconContent = readFileSync(faviconPath, 'utf-8'); const faviconContent = readFileSync(faviconPath, 'utf-8');
const faviconBase64 = Buffer.from(faviconContent).toString('base64'); const faviconBase64 = Buffer.from(faviconContent).toString('base64');
const faviconDataUrl = `data:image/svg+xml;base64,${faviconBase64}`; const faviconDataUrl = `data:image/svg+xml;base64,${faviconBase64}`;
content = content.replace(/href="[^"]*favicon\.svg"/g, `href="${faviconDataUrl}"`); content = content.replace(/href="[^"]*favicon\.svg"/g, `href="${faviconDataUrl}"`);
console.log('✓ Inlined favicon.svg as base64 data URL'); console.log('✓ Inlined favicon.svg as base64 data URL');
} }
@ -48,17 +56,16 @@ export function llamaCppBuildPlugin(): Plugin {
writeFileSync(indexPath, content, 'utf-8'); writeFileSync(indexPath, content, 'utf-8');
console.log('✓ Updated index.html'); console.log('✓ Updated index.html');
// Copy bundle.*.js -> ../public/bundle.js // Copy bundle.*.js -> bundle.js at output root
const immutableDir = resolve('../public/_app/immutable'); const immutableDir = resolve(outDir, '_app/immutable');
const bundleDir = resolve('../public/_app/immutable/assets'); const bundleDir = resolve(outDir, '_app/immutable/assets');
if (existsSync(immutableDir)) { if (existsSync(immutableDir)) {
const jsFiles = readdirSync(immutableDir).filter((f) => f.match(/^bundle\..+\.js$/)); const jsFiles = readdirSync(immutableDir).filter((f) => f.match(/^bundle\..+\.js$/));
if (jsFiles.length > 0) { if (jsFiles.length > 0) {
copyFileSync(resolve(immutableDir, jsFiles[0]), resolve('../public/bundle.js')); copyFileSync(resolve(immutableDir, jsFiles[0]), resolve(outDir, 'bundle.js'));
// Normalize __sveltekit_<hash> to __sveltekit__ in bundle.js // Normalize __sveltekit_<hash> to __sveltekit__ in bundle.js
const bundleJsPath = resolve('../public/bundle.js'); const bundleJsPath = resolve(outDir, 'bundle.js');
let bundleJs = readFileSync(bundleJsPath, 'utf-8'); let bundleJs = readFileSync(bundleJsPath, 'utf-8');
bundleJs = bundleJs.replace(/__sveltekit_[a-z0-9]+/g, '__sveltekit__'); bundleJs = bundleJs.replace(/__sveltekit_[a-z0-9]+/g, '__sveltekit__');
writeFileSync(bundleJsPath, bundleJs, 'utf-8'); writeFileSync(bundleJsPath, bundleJs, 'utf-8');
@ -66,17 +73,29 @@ export function llamaCppBuildPlugin(): Plugin {
} }
} }
// Copy bundle.*.css -> ../public/bundle.css // Copy bundle.*.css -> bundle.css at output root
if (existsSync(bundleDir)) { if (existsSync(bundleDir)) {
const cssFiles = readdirSync(bundleDir).filter((f) => f.match(/^bundle\..+\.css$/)); const cssFiles = readdirSync(bundleDir).filter((f) => f.match(/^bundle\..+\.css$/));
if (cssFiles.length > 0) { if (cssFiles.length > 0) {
copyFileSync(resolve(bundleDir, cssFiles[0]), resolve('../public/bundle.css')); copyFileSync(resolve(bundleDir, cssFiles[0]), resolve(outDir, 'bundle.css'));
console.log(`✓ Copied ${cssFiles[0]} -> bundle.css`); console.log(`✓ Copied ${cssFiles[0]} -> bundle.css`);
} }
} }
// Cleanup: remove _app directory, favicon.svg, and legacy index.html.gz
const appDir = resolve(outDir, '_app');
if (existsSync(appDir)) {
rmSync(appDir, { recursive: true, force: true });
console.log('✓ Removed _app directory');
}
const faviconOut = resolve(outDir, 'favicon.svg');
if (existsSync(faviconOut)) {
unlinkSync(faviconOut);
console.log('✓ Removed favicon.svg');
}
} catch (error) { } catch (error) {
console.error('Failed to update index.html:', error); console.error('Failed to process build output:', error);
} }
}, 100); }, 100);
} }

View File

@ -54,7 +54,12 @@
} }
const attachmentMenu = useAttachmentMenu( const attachmentMenu = useAttachmentMenu(
() => ({ hasVisionModality, hasAudioModality, hasMcpPromptsSupport, hasMcpResourcesSupport }), () => ({
hasVisionModality,
hasAudioModality,
hasMcpPromptsSupport,
hasMcpResourcesSupport
}),
() => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }), () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
() => { () => {
dropdownOpen = false; dropdownOpen = false;

View File

@ -46,7 +46,12 @@
let sheetOpen = $state(false); let sheetOpen = $state(false);
const attachmentMenu = useAttachmentMenu( const attachmentMenu = useAttachmentMenu(
() => ({ hasVisionModality, hasAudioModality, hasMcpPromptsSupport, hasMcpResourcesSupport }), () => ({
hasVisionModality,
hasAudioModality,
hasMcpPromptsSupport,
hasMcpResourcesSupport
}),
() => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }), () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
() => { () => {
sheetOpen = false; sheetOpen = false;

View File

@ -5,6 +5,7 @@
import * as DropdownMenu from '$lib/components/ui/dropdown-menu'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import * as Tooltip from '$lib/components/ui/tooltip'; import * as Tooltip from '$lib/components/ui/tooltip';
import { toolsStore } from '$lib/stores/tools.svelte'; import { toolsStore } from '$lib/stores/tools.svelte';
import { CLI_FLAGS } from '$lib/constants';
import { mcpStore } from '$lib/stores/mcp.svelte'; import { mcpStore } from '$lib/stores/mcp.svelte';
import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte'; import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte';
@ -33,7 +34,7 @@
<Info class="mt-0.5 h-4 w-4 shrink-0" /> <Info class="mt-0.5 h-4 w-4 shrink-0" />
<span> <span>
Run llama-server with <code>--tools</code> flag to enable Run llama-server with <code>{CLI_FLAGS.TOOLS}</code> flag to enable
<strong>Built-in Tools</strong>. <strong>Built-in Tools</strong>.
</span> </span>

Some files were not shown because too many files have changed in this diff Show More