mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-01 02:02:20 +00:00
build(ui): build Admin UI from source in every Docker image and on PyPI publish
The Next.js bundle under litellm/proxy/_experimental/out has historically been checked into the repository so the proxy could ship a pre-built dashboard. This bloated the repo (~21 MB of generated artifacts) and made every UI PR unreviewable because npm-rebuilt hashed asset filenames showed up as massive diffs. Wire every code path that produces a distributable artifact to rebuild the UI from source instead: - docker/build_admin_ui.sh now always builds the UI (no more enterprise-only early exit), copies it to litellm/proxy/_experimental/out, and restructures HTML so extensionless /ui routes work. - docker/Dockerfile.alpine and docker/Dockerfile.non_root now invoke build_admin_ui.sh (the other Dockerfiles already did). non_root drops its bespoke restructure loop since the build script handles it. - .github/workflows/publish_to_pypi.yml builds the UI before 'uv build' so the published wheel/sdist still ships the dashboard. - ui/litellm-dashboard/build_ui.sh creates the destination dir if missing and restructures HTML routes the same way the Docker pipeline does. - ui/litellm-dashboard/build_release_ui.sh no longer commits artifacts to git; it just delegates to docker/build_admin_ui.sh. Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
c38e23e6d3
commit
52562f018e
6 changed files with 89 additions and 99 deletions
15
.github/workflows/publish_to_pypi.yml
vendored
15
.github/workflows/publish_to_pypi.yml
vendored
|
|
@ -121,6 +121,21 @@ jobs:
|
|||
version: "0.10.9"
|
||||
enable-cache: false
|
||||
|
||||
- name: Set up Node.js for Admin UI build
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: ui/litellm-dashboard/package-lock.json
|
||||
|
||||
- name: Build Admin UI
|
||||
# The Next.js bundle under litellm/proxy/_experimental/out is no longer
|
||||
# checked into the repository; build it here so it gets packaged into
|
||||
# the published wheel/sdist.
|
||||
run: |
|
||||
chmod +x docker/build_admin_ui.sh
|
||||
./docker/build_admin_ui.sh
|
||||
|
||||
- name: Copy model prices backup
|
||||
run: cp model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ RUN uv sync --frozen --no-install-project --no-install-workspace --no-default-gr
|
|||
# Copy full source tree
|
||||
COPY . .
|
||||
|
||||
# Build Admin UI before final sync
|
||||
RUN sed -i 's/\r$//' docker/build_admin_ui.sh && chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
|
||||
|
||||
# Install project and workspace packages (fast - deps already cached)
|
||||
RUN uv sync --frozen --no-default-groups --no-editable \
|
||||
--extra proxy \
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ RUN for i in 1 2 3; do \
|
|||
curl \
|
||||
openssl \
|
||||
libsndfile \
|
||||
nodejs && break || sleep 5; \
|
||||
nodejs \
|
||||
npm && break || sleep 5; \
|
||||
done
|
||||
|
||||
ENV UV_PROJECT_ENVIRONMENT=/app/.venv \
|
||||
|
|
@ -55,22 +56,19 @@ COPY . .
|
|||
# Set non-root flag for build time consistency
|
||||
ENV LITELLM_NON_ROOT=true
|
||||
|
||||
# Stage the pre-built Admin UI from the checked-in Next.js static export.
|
||||
# _experimental/out/ is regenerated as part of the release runbook.
|
||||
# Restructure extensionless routes (foo.html -> foo/index.html) to match the layout
|
||||
# proxy_server.py expects, and drop a readiness marker.
|
||||
# Build the Admin UI from source. The pre-built Next.js bundle is no longer
|
||||
# checked into the repository — build_admin_ui.sh produces it and writes to
|
||||
# litellm/proxy/_experimental/out (already restructured for /ui routes).
|
||||
RUN sed -i 's/\r$//' docker/build_admin_ui.sh && chmod +x docker/build_admin_ui.sh && \
|
||||
./docker/build_admin_ui.sh
|
||||
|
||||
# Stage the freshly built Admin UI for the runtime stage. The build script
|
||||
# already restructured extensionless routes (foo.html -> foo/index.html) to
|
||||
# match the layout proxy_server.py expects; we just drop a readiness marker.
|
||||
RUN mkdir -p /var/lib/litellm/ui /var/lib/litellm/assets && \
|
||||
cp -r /app/litellm/proxy/_experimental/out/. /var/lib/litellm/ui/ && \
|
||||
cp /app/litellm/proxy/logo.jpg /var/lib/litellm/assets/logo.jpg && \
|
||||
( cd /var/lib/litellm/ui && \
|
||||
for html_file in *.html; do \
|
||||
if [ "$html_file" != "index.html" ] && [ -f "$html_file" ]; then \
|
||||
folder_name="${html_file%.html}" && \
|
||||
mkdir -p "$folder_name" && \
|
||||
mv "$html_file" "$folder_name/index.html"; \
|
||||
fi; \
|
||||
done && \
|
||||
touch .litellm_ui_ready )
|
||||
touch /var/lib/litellm/ui/.litellm_ui_ready
|
||||
|
||||
RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \
|
||||
if [ "$PROXY_EXTRAS_SOURCE" = "published" ]; then \
|
||||
|
|
|
|||
|
|
@ -1,73 +1,49 @@
|
|||
#!/bin/bash
|
||||
|
||||
# # try except this script
|
||||
# set -e
|
||||
# Builds the Admin UI Next.js bundle and publishes it to
|
||||
# litellm/proxy/_experimental/out so the proxy can serve it.
|
||||
#
|
||||
# This script is invoked from every UI-bearing Dockerfile and from the
|
||||
# release pipeline. The pre-built bundle is no longer checked into the
|
||||
# repository, so this script must run successfully whenever the
|
||||
# resulting Docker image / Python wheel needs to ship the dashboard.
|
||||
|
||||
set -e
|
||||
|
||||
# print current dir
|
||||
echo
|
||||
pwd
|
||||
|
||||
|
||||
# only run this step for litellm enterprise, we run this if enterprise/enterprise_ui/_enterprise.json exists
|
||||
if [ ! -f "enterprise/enterprise_ui/enterprise_colors.json" ]; then
|
||||
echo "Admin UI - using default LiteLLM UI"
|
||||
exit 0
|
||||
# Apply the enterprise color palette when present so OSS and enterprise
|
||||
# images can share this build script.
|
||||
if [ -f "enterprise/enterprise_ui/enterprise_colors.json" ]; then
|
||||
echo "Using enterprise UI color palette"
|
||||
cp enterprise/enterprise_ui/enterprise_colors.json ui/litellm-dashboard/ui_colors.json
|
||||
else
|
||||
echo "Using default LiteLLM UI color palette"
|
||||
fi
|
||||
|
||||
echo "Building Custom Admin UI..."
|
||||
|
||||
# Install dependencies
|
||||
# Check if we are on macOS
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
# Install dependencies using Homebrew
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Error: Homebrew not found. Please install Homebrew and try again."
|
||||
exit 1
|
||||
fi
|
||||
brew update
|
||||
brew install curl
|
||||
else
|
||||
# Assume Linux, try using apt-get
|
||||
if command -v apt-get &> /dev/null; then
|
||||
apt-get update
|
||||
apt-get install -y curl
|
||||
elif command -v apk &> /dev/null; then
|
||||
# Try using apk if apt-get is not available
|
||||
apk update
|
||||
apk add curl
|
||||
else
|
||||
echo "Error: Unsupported package manager. Cannot install dependencies."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
NVM_VERSION="v0.40.4"
|
||||
NVM_CHECKSUM="4b7412c49960c7d31e8df72da90c1fb5b8cccb419ac99537b737028d497aba4f"
|
||||
NVM_SCRIPT=$(mktemp)
|
||||
trap 'rm -f "$NVM_SCRIPT"' EXIT
|
||||
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" -o "$NVM_SCRIPT"
|
||||
if command -v sha256sum &>/dev/null; then
|
||||
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | sha256sum -c -
|
||||
elif command -v shasum &>/dev/null; then
|
||||
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | shasum -a 256 -c -
|
||||
else
|
||||
echo "No sha256 tool found; cannot verify nvm checksum"; exit 1
|
||||
fi || { echo "nvm checksum verification failed"; exit 1; }
|
||||
bash "$NVM_SCRIPT"
|
||||
source ~/.nvm/nvm.sh
|
||||
nvm install v18.17.0
|
||||
nvm use v18.17.0
|
||||
|
||||
# copy _enterprise.json from this directory to /ui/litellm-dashboard, and rename it to ui_colors.json
|
||||
cp enterprise/enterprise_ui/enterprise_colors.json ui/litellm-dashboard/ui_colors.json
|
||||
|
||||
# cd in to /ui/litellm-dashboard
|
||||
cd ui/litellm-dashboard
|
||||
|
||||
# ensure have access to build_ui.sh
|
||||
chmod +x ./build_ui.sh
|
||||
# Use a deterministic install when a lockfile is present.
|
||||
if [ -f package-lock.json ]; then
|
||||
npm ci
|
||||
else
|
||||
npm install
|
||||
fi
|
||||
|
||||
# run ./build_ui.sh
|
||||
./build_ui.sh
|
||||
npm run build
|
||||
|
||||
# return to root directory
|
||||
cd ../..
|
||||
destination_dir="../../litellm/proxy/_experimental/out"
|
||||
mkdir -p "$destination_dir"
|
||||
rm -rf "$destination_dir"/*
|
||||
cp -r ./out/. "$destination_dir"/
|
||||
rm -rf ./out
|
||||
|
||||
# Restructure HTML so extensionless routes work (e.g. /ui/login).
|
||||
# Next.js export produces login.html; the proxy expects login/index.html.
|
||||
find "$destination_dir" -name '*.html' ! -name 'index.html' | while read -r htmlfile; do
|
||||
target_dir="${htmlfile%.html}"
|
||||
mkdir -p "$target_dir"
|
||||
mv "$htmlfile" "$target_dir/index.html"
|
||||
done
|
||||
|
||||
cd ../..
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
destination_dir="../../litellm/proxy/_experimental/out"
|
||||
# This script used to rebuild the UI and commit the resulting Next.js
|
||||
# bundle into git so the proxy could ship a pre-built dashboard.
|
||||
#
|
||||
# The bundle is no longer checked into the repository — it is rebuilt
|
||||
# from source by the Dockerfiles and by the PyPI publish workflow. This
|
||||
# script now simply triggers a local build (the canonical entry point is
|
||||
# `docker/build_admin_ui.sh`) for developers who want to preview the
|
||||
# production-flavoured UI without booting a Docker image.
|
||||
|
||||
chmod +x ./build_ui.sh
|
||||
./build_ui.sh
|
||||
|
||||
commit_message="chore: update Next.js build artifacts ($(date -u +"%Y-%m-%d %H:%M UTC"), node $(node -v))"
|
||||
|
||||
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
||||
git add -f "$destination_dir"/
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "$commit_message"
|
||||
echo "Git commit created."
|
||||
else
|
||||
echo "No changes to commit."
|
||||
fi
|
||||
else
|
||||
echo "Not a git repository. Skipping commit."
|
||||
fi
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
exec "$repo_root/docker/build_admin_ui.sh"
|
||||
|
|
|
|||
|
|
@ -42,18 +42,24 @@ npm run build
|
|||
if [ $? -eq 0 ]; then
|
||||
echo "Build successful. Copying files..."
|
||||
|
||||
# echo current dir
|
||||
echo
|
||||
pwd
|
||||
|
||||
# Specify the destination directory
|
||||
destination_dir="../../litellm/proxy/_experimental/out"
|
||||
|
||||
# Remove existing files in the destination directory
|
||||
# The pre-built bundle is no longer checked into the repository, so the
|
||||
# destination may not exist on a fresh checkout. Create it before copying.
|
||||
mkdir -p "$destination_dir"
|
||||
rm -rf "$destination_dir"/*
|
||||
|
||||
# Copy the contents of the output directory to the specified destination
|
||||
cp -r ./out/* "$destination_dir"
|
||||
cp -r ./out/. "$destination_dir"/
|
||||
|
||||
# Restructure HTML so extensionless routes work (login.html -> login/index.html)
|
||||
find "$destination_dir" -name '*.html' ! -name 'index.html' | while read -r htmlfile; do
|
||||
target_dir="${htmlfile%.html}"
|
||||
mkdir -p "$target_dir"
|
||||
mv "$htmlfile" "$target_dir/index.html"
|
||||
done
|
||||
|
||||
rm -rf ./out
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue