Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Build

on:
push:
branches:
- main
pull_request:
branches:
- main

concurrency: # Run release builds sequentially, cancel outdated PR builds
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions: # Grant write access to github.token within non-pull_request builds
contents: write
packages: write

env:
PLATFORMS: linux/amd64,linux/arm64

jobs:
build:
name: Build
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false

- id: release
name: Prepare release
uses: mgoltzsche/conventional-release@v1
with:
commit-files: chat.html
# Don't validate git commit messages prior to requiring the convention
ignore-commits-before: 5b106ddeae56211c542ef1d71415dcf4acded4f2

- name: Write release version into chat.html
if: steps.release.outputs.publish
run: |
sed -Ei 's/^(\s+const APP_VERSION = ).+$/\1'"'$RELEASE_VERSION';/" chat.html

- name: Set up QEMU
uses: docker/setup-qemu-action@v4
with:
platforms: ${{ env.PLATFORMS }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log into GitHub registry
if: steps.release.outputs.publish
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Build (and push) container image
uses: docker/build-push-action@v7
with:
context: .
push: ${{ !!steps.release.outputs.publish }}
platforms: ${{ env.PLATFORMS }}
tags: |
ghcr.io/${{ github.actor }}/statelesschatui:latest
ghcr.io/${{ github.actor }}/statelesschatui:${{ steps.release.outputs.version }}

7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM nginxinc/nginx-unprivileged:1.29-alpine3.23
COPY favicon.ico /usr/share/nginx/html/
COPY chat.html /usr/share/nginx/html/index.html
COPY server.conf.template /etc/nginx/templates/default.conf.template
RUN rm /etc/nginx/conf.d/default.conf
ENV OPENAI_BASE_URL=http://127.0.0.1:8080/v1
ENTRYPOINT ["/bin/sh", "-c", "export OPENAI_MODELS_BASE_URL=${OPENAI_MODELS_BASE_URL:-$OPENAI_BASE_URL}; exec /docker-entrypoint.sh nginx -g 'daemon off;'"]
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,38 @@ python -m http.server 8000
npx http-server -p 8000
```

To optionally build and run the docker container to serve the file via an nginx HTTP server, run:
```bash
docker build -t statelesschatui:dev .
docker run --rm -p 8000:8000 \
-e OPENAI_BASE_URL=http://localai:8080/v1 \
-v "`pwd`/chat.html:/usr/share/nginx/html/index.html" \
statelesschatui:dev
```
Once the container started, you can browse the web UI at [http://localhost:8000](http://localhost:8000).
Instead of building the container image yourself you can also use a published release.
For more information as well as the configuration options, see the section "[Run the container](#run-the-container)".

### Run the container

To serve StatelessChatUI via a Linux container, run:
```sh
docker run --rm -e OPENAI_BASE_URL=http://localai:8080/v1 -p 8000:8000 ghcr.io/srware-net/statelesschatui:latest
```
Once the container started, you can browse the web UI at [http://localhost:8000](http://localhost:8000).
To have a reproducible setup, you should replace `latest` with a concrete version ideally.

The container can be configured using the following environment variables:
| Environment variable | Default value | Description |
| ------------------------ | --------------------------- | -------------------------------------- |
| `OPENAI_BASE_URL` | `http://127.0.0.1:8080/v1` | The base URL pointing to the OpenAI API server. |
| `OPENAI_MODELS_BASE_URL` | (same as `OPENAI_BASE_URL`) | The base URL pointing to the server serving the OpenAI models API endpoint. |

Using the Linux container provides the following benefits compared to using the HTML file directly:
* No CORS problems due to reverse-proxied API endpoints.
* API base URL can be configured as environment variable, avoiding the need for manual configuration within the web UI.
* A separate base URL can be configured to fetch the available models, allowing to use a middleware/agent that only serves the chat completion endpoint.

### API Configuration

Set base URL and API key in the header section:
Expand Down
6 changes: 6 additions & 0 deletions chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,12 @@

/* ---------- Welcome ---------- */
addMessage('system', 'Ready. CORS mode enabled. <think>, reasoning and reasoning_content supported. Import/Export + Markdown + JSON editor + auto-scroll enabled. Load models via /v1/models.');

/* ---------- Derive API base URL from server & Load models ---------- */
if (window.location.href.match(/^https?:\/\//)) {
els.apiBase.value = window.location.href.replace(/^(.+?)(\/[^\/]*)?$/, '$1')+'/v1';
loadModels();
}
})();
</script>
</body>
Expand Down
Binary file added favicon.ico
Binary file not shown.
42 changes: 42 additions & 0 deletions server.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
server {
listen 8000;
server_name localhost;

proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 10s;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";
proxy_http_version 1.1;
proxy_redirect off;
proxy_buffering off;
proxy_request_buffering off;
client_max_body_size 0;

# Reverse-proxy the chat completion API endpoint
location = /v1/chat/completions {
alias /v1/chat/completions;
proxy_pass ${OPENAI_BASE_URL}/chat/completions;
}

# Reverse-proxy the models API endpoint
location = /v1/models {
alias /v1/models;
proxy_pass ${OPENAI_MODELS_BASE_URL}/models;
}

# Serve the UI
location / {
root /usr/share/nginx/html;
index index.html;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}