Skip to content
Merged
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
171 changes: 131 additions & 40 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ name: Docker (GHCR)

on:
push:
branches: [main]
branches: [main, release]
workflow_dispatch:

concurrency:
group: docker-${{ github.ref }}
cancel-in-progress: false
cancel-in-progress: true

env:
IMAGE: ghcr.io/${{ github.repository }}
Expand Down Expand Up @@ -63,9 +63,19 @@ jobs:
echo "No version change detected"
fi

build-and-push-dev:
build-dev:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -80,30 +90,75 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (dev tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE }}
tags: |
type=raw,value=dev
type=sha,prefix=dev-

- name: Build and push dev image
- name: Build and push ${{ matrix.platform }} dev image by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

build-and-push-release:
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=dev-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=dev-${{ matrix.arch }}

- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-dev-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge-dev:
if: github.ref == 'refs/heads/main'
needs: build-dev
runs-on: ubuntu-latest
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-dev-*
merge-multiple: true

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create dev multi-arch manifest
working-directory: /tmp/digests
run: |
SHORT_SHA="${GITHUB_SHA::7}"
docker buildx imagetools create \
-t "${{ env.IMAGE }}:dev" \
-t "${{ env.IMAGE }}:dev-${SHORT_SHA}" \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)

build-release:
needs: detect-version-change
if: github.ref == 'refs/heads/release' && needs.detect-version-change.outputs.version_changed == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -118,23 +173,59 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (release tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE }}
tags: |
type=raw,value=latest
type=raw,value=${{ needs.detect-version-change.outputs.version }}
type=semver,pattern={{major}}.{{minor}},value=v${{ needs.detect-version-change.outputs.version }}

- name: Build and push release image
- name: Build and push ${{ matrix.platform }} release image by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=release-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=release-${{ matrix.arch }}

- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-release-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge-release:
needs: [detect-version-change, build-release]
if: github.ref == 'refs/heads/release' && needs.detect-version-change.outputs.version_changed == 'true'
runs-on: ubuntu-latest
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-release-*
merge-multiple: true

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create release multi-arch manifest
working-directory: /tmp/digests
run: |
VERSION="${{ needs.detect-version-change.outputs.version }}"
MAJOR_MINOR="${VERSION%.*}"
docker buildx imagetools create \
-t "${{ env.IMAGE }}:latest" \
-t "${{ env.IMAGE }}:${VERSION}" \
-t "${{ env.IMAGE }}:${MAJOR_MINOR}" \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ── Stage 1: Build the SvelteKit frontend ──────────────────
FROM node:22-slim AS frontend-builder
FROM --platform=$BUILDPLATFORM node:22-slim AS frontend-builder

WORKDIR /build/frontend
COPY cptr/frontend/package.json cptr/frontend/package-lock.json ./
Expand All @@ -9,7 +9,7 @@ RUN npm run build


# ── Stage 2: Install Python dependencies & build wheel ─────
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS backend-builder
FROM --platform=$BUILDPLATFORM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS backend-builder

WORKDIR /build
COPY pyproject.toml uv.lock LICENSE README.md CHANGELOG.md ./
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ Opens in your browser. From other devices:
cptr run --host 0.0.0.0
```

## Docker

Run cptr with Docker:

```bash
docker run --rm -it \
-p 8000:8000 \
-v cptr-data:/data \
-v "$PWD:/workspace" \
-w /workspace \
ghcr.io/open-webui/computer:latest
```

Then open the URL printed in the logs, usually `http://localhost:8000/?token=...`.

`cptr` stores its state in `/data`. Mount your project into the container, like `-v "$PWD:/workspace"`, so cptr can access it.

The `:dev` image is also available and tracks the `main` branch.

## License

Open Use License. Source available. All rights reserved. See [LICENSE](LICENSE). [Enterprise licenses available](mailto:sales@openwebui.com).
2 changes: 2 additions & 0 deletions cptr/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
ENABLE_CHAT_RECONCILE_ON_STARTUP: bool = os.environ.get(
"ENABLE_CHAT_RECONCILE_ON_STARTUP", "true"
).lower() in ("true", "1", "yes")
CHAT_TOOL_MAX_CHARS = int(os.environ.get("CHAT_TOOL_MAX_CHARS", "50000"))
CHAT_TOOL_COMMAND_MAX_CHARS = int(os.environ.get("CHAT_TOOL_COMMAND_MAX_CHARS", "8000"))

# ── AI stream settings ──────────────────────────────────────
STREAM_CONNECT_TIMEOUT_SECONDS = float(os.environ.get("CPTR_STREAM_CONNECT_TIMEOUT", "30"))
Expand Down
2 changes: 1 addition & 1 deletion cptr/frontend/src/lib/apis/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const sendMessage = (
parentId?: string | null,
params: { tool_approval_mode?: string } = {},
regenerationPrompt?: string,
files?: string[]
files?: { id: string; name: string; url: string; type: string }[]
) =>
fetchJSON<SendMessageResult>(
'/api/chats',
Expand Down
3 changes: 3 additions & 0 deletions cptr/frontend/src/lib/apis/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export const createEntry = (path: string, type: 'file' | 'directory') =>
export const uploadFiles = (path: string, form: FormData) =>
fetchHandler('/api/workspace/files/upload', { method: 'POST', body: form });

export const uploadFile = (form: FormData) =>
fetchJSON<{ id: string; url: string }>('/api/files', { method: 'POST', body: form });

export const downloadArchive = (paths: string[]) =>
fetchHandler('/api/workspace/files/archive', { method: 'POST', ...jsonBody({ paths }) });

Expand Down
10 changes: 5 additions & 5 deletions cptr/frontend/src/lib/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@
d="M16 12H17.4C17.7314 12 18 12.2686 18 12.6V19.4C18 19.7314 17.7314 20 17.4 20H6.6C6.26863 20 6 19.7314 6 19.4V12.6C6 12.2686 6.26863 12 6.6 12H8M16 12V8C16 6.66667 15.2 4 12 4C8.8 4 8 6.66667 8 8V12M16 12H8"
/>
{:else if name === 'docker'}
<path d="M15.5 5.5H18.5V8.5H22C22 8.5 22 11.5 19 13C18.5 13.2778 18 13.5 17.5 13.5" />
<path d="M1 11.5C1 11.5 2.5 8 6 8H8M3.5 8.5H6.5V11.5H0.5" />
<path d="M6.5 5.5H9.5V8.5" /><path d="M9.5 5.5H12.5V8.5" />
<path d="M12.5 5.5H15.5V8.5" /><path d="M9.5 8.5H12.5V11.5" />
<path d="M12.5 8.5H15.5V11.5" />
<path
fill="currentColor"
stroke="none"
d="M13.983 11.078h2.119a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.119a.185.185 0 0 0-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 0 0 .186-.186V3.574a.186.186 0 0 0-.186-.185h-2.118a.185.185 0 0 0-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 0 0 .186-.186V6.29a.186.186 0 0 0-.186-.185h-2.118a.185.185 0 0 0-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 0 0 .184-.186V6.29a.185.185 0 0 0-.185-.185H8.1a.185.185 0 0 0-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 0 0 .185-.186V6.29a.185.185 0 0 0-.185-.185H5.136a.186.186 0 0 0-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.118a.185.185 0 0 0-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 0 0 .184-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.185.185 0 0 0-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 0 0 .185-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 0 0 .184-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.185.185 0 0 0-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 0 0-.75.748 11.376 11.376 0 0 0 .692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 0 0 3.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"
/>
{:else if name === 'empty-page'}
<path
d="M4 21.4V2.6C4 2.26863 4.26863 2 4.6 2H16.2515C16.4106 2 16.5632 2.06321 16.6757 2.17574L19.8243 5.32426C19.9368 5.43679 20 5.5894 20 5.74853V21.4C20 21.7314 19.7314 22 19.4 22H4.6C4.26863 22 4 21.7314 4 21.4Z"
Expand Down
Loading
Loading