🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s

A professional workspace for AI coding agents with:
- Real-time streaming chat with SSE
- Session management (fork, clone, tree navigation)
- MCP tools browser & metadata panel
- Skills browser (global, project, package sources)
- Voice input & STT provider configuration
- Integrated terminal & file explorer
- Model selector with capability metadata
- Keyboard-first UI with command palette
- 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi)
- Tauri 2 desktop app
- React 19 + TypeScript + Vite 8 + Tailwind CSS 4
- Pi Agent extension runtime

Originally forked from OpenCodeUI, now an independent project.
License: GPL-3.0-only
This commit is contained in:
Evandro committed 2026-06-01 00:50:06 -03:00
commit b3489e1ede
983 files changed
+158475

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# OpenCode WebUI - Caddy 配置
# 纯静态文件服务
:3000 {
@api path /api /api/*
handle @api {
respond "API is not served by the frontend-only image" 404
}
handle {
root * /srv
file_server
# SPA 路由 - 所有非文件请求返回 index.html
try_files {path} /index.html
}
encode gzip
}
+36
View File
@@ -0,0 +1,36 @@
{
auto_https off
}
# ---- 主入口 :6658 — OpenCode 应用 ----
:6658 {
# API(SSE + WebSocket 自动处理)
handle_path /api/* {
reverse_proxy opencode-backend:4096 {
flush_interval -1
}
}
# Routes UI + Preview API(供面板切换预览端口)
@router path /routes /preview/*
handle @router {
reverse_proxy 127.0.0.1:7070
}
# 前端(兜底)
handle {
reverse_proxy opencode-frontend:3000
}
}
# ---- 开发服务入口 :6659 — 绑独立域名 ----
# 路径穿透 /p/{token}/ + 独占预览(兜底)
:6659 {
# 动态端口路由(由 router 自动生成)
import /etc/caddy/routes.conf
# 独占预览(兜底:未匹配路径穿透的请求走 preview 端口)
handle {
import /etc/caddy/preview.conf
}
}
+21
View File
@@ -0,0 +1,21 @@
# OpenCode WebUI - 纯前端独立部署
# 静态文件 + API 反代到外部 opencode serve
:3000 {
# API 反代(strip /api 前缀,SSE 需要 flush_interval -1)
handle_path /api/* {
reverse_proxy {$BACKEND_URL:host.docker.internal:4096} {
header_up Host {upstream_hostport}
flush_interval -1
}
}
# 前端静态文件
handle {
root * /srv
file_server
try_files {path} /index.html
}
encode gzip
}
+114
View File
@@ -0,0 +1,114 @@
# ============================================
# OpenCode WebUI - Backend
# ============================================
# Debian slim + opencode serve + agent tooling
FROM docker:29-cli AS docker-cli
FROM debian:bookworm-slim
ARG USE_CHINA_MIRROR=0
ARG APT_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/debian
ARG APT_SECURITY_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/debian-security
ARG NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
ARG NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
ARG MISE_INSTALL_URL=https://mise.run
ARG OPENCODE_INSTALL_URL=https://opencode.ai/install
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
TERM=xterm-256color \
COLORTERM=truecolor \
EDITOR=vim \
NPM_CONFIG_REGISTRY=${NPM_CONFIG_REGISTRY} \
NODEJS_ORG_MIRROR=${NODEJS_ORG_MIRROR}
RUN if [ "${USE_CHINA_MIRROR}" = "1" ] && [ -f /etc/apt/sources.list.d/debian.sources ]; then \
sed -i \
-e "s|http://deb.debian.org/debian|${APT_MIRROR}|g" \
-e "s|http://security.debian.org/debian-security|${APT_SECURITY_MIRROR}|g" \
/etc/apt/sources.list.d/debian.sources; \
fi
# Agent 常用基础环境。语言运行时交给 mise,安装到持久化的 /root。
RUN apt-get update && apt-get install -y --no-install-recommends \
bash \
ca-certificates \
curl \
dnsutils \
fd-find \
file \
fonts-dejavu-core \
fonts-liberation \
fonts-noto-cjk \
fonts-noto-color-emoji \
g++ \
git \
git-lfs \
gnupg \
jq \
less \
make \
nano \
openssh-client \
patch \
procps \
python3 \
python3-venv \
ripgrep \
rsync \
tini \
tzdata \
unzip \
vim-tiny \
wget \
xz-utils \
zip \
&& rm -rf /var/lib/apt/lists/*
# Docker CLI + Compose/Buildx plugin,只复制客户端,不安装 dockerd/containerd/runc。
RUN mkdir -p /usr/local/libexec/docker/cli-plugins
COPY --from=docker-cli /usr/local/bin/docker /usr/local/bin/docker
COPY --from=docker-cli /usr/local/libexec/docker/cli-plugins/docker-compose /usr/local/libexec/docker/cli-plugins/docker-compose
COPY --from=docker-cli /usr/local/libexec/docker/cli-plugins/docker-buildx /usr/local/libexec/docker/cli-plugins/docker-buildx
# Debian 的 fd 二进制叫 fdfind,补一个常见命令名。
RUN ln -sf /usr/bin/fdfind /usr/local/bin/fd
# 安装 mise。GitHub Actions 等 CI 环境会在构建时下载,运行时安装的工具仍落在持久化 /root。
RUN curl -fsSL "${MISE_INSTALL_URL}" | sh \
&& MISE_BIN="$(find /root -name mise -type f 2>/dev/null | head -1)" \
&& if [ -z "${MISE_BIN}" ]; then echo "mise binary not found" >&2; exit 1; fi \
&& cp "${MISE_BIN}" /usr/local/bin/mise \
&& chmod +x /usr/local/bin/mise \
&& mise --version
# 安装 opencode。
RUN curl -fsSL "${OPENCODE_INSTALL_URL}" | bash \
&& OPENCODE_BIN="$(find /root -name opencode -type f 2>/dev/null | head -1)" \
&& if [ -z "${OPENCODE_BIN}" ]; then echo "opencode binary not found" >&2; exit 1; fi \
&& cp "${OPENCODE_BIN}" /usr/local/bin/opencode \
&& chmod +x /usr/local/bin/opencode \
&& opencode --version
# 工作目录
RUN mkdir -p /workspace
WORKDIR /workspace
ENV MISE_DATA_DIR=/root/.local/share/mise \
MISE_CONFIG_DIR=/root/.config/mise \
MISE_STATE_DIR=/root/.local/state/mise \
OPENCODE_DISABLE_AUTOUPDATE=true \
OPENCODE_DISABLE_TERMINAL_TITLE=true \
WORKSPACE=/workspace
ENV PATH="/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:/root/.local/share/mise/bin:${PATH}"
COPY ./docker/backend-entrypoint.sh /usr/local/bin/backend-entrypoint.sh
RUN chmod +x /usr/local/bin/backend-entrypoint.sh
EXPOSE 4096
ENTRYPOINT ["tini", "--", "/usr/local/bin/backend-entrypoint.sh"]
CMD ["opencode", "serve", "--port", "4096", "--hostname", "0.0.0.0"]
+48
View File
@@ -0,0 +1,48 @@
# ============================================
# OpenCode WebUI - Frontend
# ============================================
# 纯前端静态文件 + Caddy 反向代理 API
# ---- Stage 1: 构建前端 ----
# 前端产物是静态文件,和目标 CPU 架构无关。
# 多架构镜像构建时让 Node builder 始终跑在构建机原生平台,避免 arm64 通过 QEMU 重跑 npm/vite 构建。
FROM --platform=$BUILDPLATFORM node:22-alpine AS builder
ARG NPM_REGISTRY=https://registry.npmjs.org/
ARG NPM_REGISTRY_FALLBACK=https://registry.npmmirror.com/
WORKDIR /app
COPY package.json package-lock.json ./
RUN set -eux; \
npm config set registry "${NPM_REGISTRY}"; \
npm config set fetch-retries 5; \
npm config set fetch-retry-factor 2; \
npm config set fetch-retry-mintimeout 10000; \
npm config set fetch-retry-maxtimeout 120000; \
npm config set audit false; \
npm config set fund false; \
if npm ci --ignore-scripts; then \
exit 0; \
fi; \
echo "npm ci failed on ${NPM_REGISTRY}, retrying with ${NPM_REGISTRY_FALLBACK}"; \
npm cache clean --force; \
npm config set registry "${NPM_REGISTRY_FALLBACK}"; \
npm ci --ignore-scripts
COPY . .
RUN node scripts/copy-material-icons.mjs
ENV VITE_API_BASE_URL=/api
RUN npm run build
# ---- Stage 2: Caddy 运行时 ----
FROM caddy:2-alpine
# 前端静态文件
COPY --from=builder /app/dist /srv
# Caddy 配置(默认 + standalone)
COPY docker/Caddyfile /etc/caddy/Caddyfile
COPY docker/Caddyfile.standalone /etc/caddy/Caddyfile.standalone
EXPOSE 3000
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]
+27
View File
@@ -0,0 +1,27 @@
FROM rust:1.94-alpine AS router-builder
RUN apk add --no-cache musl-dev
WORKDIR /build
COPY src-router/Cargo.toml src-router/Cargo.lock ./
COPY src-router/src ./src
RUN cargo build --release --locked
FROM caddy:2-alpine
RUN apk add --no-cache docker-cli
WORKDIR /app
COPY --from=router-builder /build/target/release/opencodeui-router /usr/local/bin/opencodeui-router
COPY docker/entrypoint-gateway.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
COPY docker/Caddyfile.gateway /etc/caddy/Caddyfile
RUN touch /etc/caddy/routes.conf && touch /etc/caddy/preview.conf
EXPOSE 6658 6659
CMD ["/app/entrypoint.sh"]
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -eu
ensure_mise() {
if command -v mise >/dev/null 2>&1; then
return
fi
if [ -x /root/.local/bin/mise ]; then
rm -f /usr/local/bin/mise
cp /root/.local/bin/mise /usr/local/bin/mise
chmod +x /usr/local/bin/mise
return
fi
curl -fsSL "${MISE_INSTALL_URL:-https://mise.run}" | sh
MISE_BIN="$(find /root -name mise -type f 2>/dev/null | head -1)"
if [ -z "${MISE_BIN}" ]; then
echo "mise install failed: binary not found" >&2
exit 1
fi
rm -f /usr/local/bin/mise
cp "${MISE_BIN}" /usr/local/bin/mise
chmod +x /usr/local/bin/mise
}
ensure_opencode() {
if opencode --version >/dev/null 2>&1; then
return
fi
OPENCODE_BIN="$(find /root -name opencode -type f 2>/dev/null | head -1)"
if [ -n "${OPENCODE_BIN}" ]; then
rm -f /usr/local/bin/opencode
cp "${OPENCODE_BIN}" /usr/local/bin/opencode
chmod +x /usr/local/bin/opencode
if opencode --version >/dev/null 2>&1; then
return
fi
fi
curl -fsSL "${OPENCODE_INSTALL_URL:-https://opencode.ai/install}" | bash
OPENCODE_BIN="$(find /root -name opencode -type f 2>/dev/null | head -1)"
if [ -z "${OPENCODE_BIN}" ]; then
echo "opencode install failed: binary not found" >&2
exit 1
fi
rm -f /usr/local/bin/opencode
cp "${OPENCODE_BIN}" /usr/local/bin/opencode
chmod +x /usr/local/bin/opencode
}
ensure_package_mirrors() {
mkdir -p /root/.config/pip
if [ ! -f /root/.npmrc ]; then
cat > /root/.npmrc <<EOF
registry=${NPM_CONFIG_REGISTRY:-https://registry.npmmirror.com}
fund=false
audit=false
EOF
fi
if [ ! -f /root/.config/pip/pip.conf ]; then
cat > /root/.config/pip/pip.conf <<EOF
[global]
index-url = ${PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}
trusted-host = ${PIP_TRUSTED_HOST:-pypi.tuna.tsinghua.edu.cn}
timeout = 120
EOF
fi
}
ensure_mise
ensure_opencode
ensure_package_mirrors
if [ "$#" -eq 0 ]; then
set -- opencode serve --port 4096 --hostname 0.0.0.0
fi
exec "$@"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
# Start both processes and exit the container if either one dies.
cleanup() {
kill "$ROUTER_PID" "$CADDY_PID" 2>/dev/null
wait "$ROUTER_PID" "$CADDY_PID" 2>/dev/null
exit 1
}
trap cleanup TERM INT
opencodeui-router &
ROUTER_PID=$!
caddy run --config /etc/caddy/Caddyfile &
CADDY_PID=$!
# Wait for either process to exit, then tear down the other.
# `wait -n` is not available in busybox sh, so poll both.
while kill -0 "$ROUTER_PID" 2>/dev/null && kill -0 "$CADDY_PID" 2>/dev/null; do
wait -n "$ROUTER_PID" "$CADDY_PID" 2>/dev/null || sleep 1
done
echo "One of the processes exited, shutting down..."
cleanup
+46
View File
@@ -0,0 +1,46 @@
# ============================================
# 物理机 Nginx 参考配置
# ============================================
# 放到 /etc/nginx/sites-enabled/ 下
# 将域名请求代理到 Docker 容器
server {
listen 80;
server_name opencode.example.com;
# 前端
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $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-Proto $scheme;
}
# API 反向代理
location /api/ {
proxy_pass http://127.0.0.1:4096/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# SSE
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# WebSocket(PTY 终端)
location /api/pty/ {
proxy_pass http://127.0.0.1:4096/pty/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s;
}
}