🎉 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
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:
commit
b3489e1ede
983 files changed
+158475
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user