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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn
MISE_INSTALL_URL=https://mise.run
OPENCODE_INSTALL_URL=https://opencode.ai/install
# opencode 版本(本 UI 只支持 V2,不要改成 v1.x)。改这里即可整体升级/降级后端版本。
OPENCODE_VERSION=2.0.19
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ dist
node_modules
public/material-icons
src-tauri/target
# cargo check / tauri-build 生成的 schema(src-tauri/.gitignore 已忽略,非源码)
src-tauri/gen/schemas
coverage
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@
- fix(settings): apply mobile viewport height to config editor (d442e66)
- fix(titlebar): preserve decorum window controls (1892621)
- fix: remove touchAction:none from FileTreeItem button that broke mobile scrolling (a619d72)
- fix: replace 92vh with calc(var(--app-height) * 0.92) in mobile SettingsDialog to fix address bar hiding issue (f3558fc)
- fix: replace 92vh with calc(var(--app-height) \* 0.92) in mobile SettingsDialog to fix address bar hiding issue (f3558fc)

## [v0.6.16] - 2026-06-07

Expand Down Expand Up @@ -630,7 +630,7 @@

## [v0.5.5] - 2026-04-18

- fix: remove redundant *Single i18n keys — let i18next handle count=1 natively (9385ef8)
- fix: remove redundant \*Single i18n keys — let i18next handle count=1 natively (9385ef8)
- style: match pane drop highlight radius to pane shell (rounded-lg) (f8acac2)
- feat: enable drag-to-split on active session list items (ac20b7b)

Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@

## 快速体验

> ⚠️ **需要 OpenCode v2**(如 `v2.0.19`)。**本 UI 只支持 V2,不支持 v1.x** ——
> 如果后端还是 v1,界面能打开但一发消息就会失败。
> 用 `opencode --version` 确认;输出的版本号应以 `v2.` 开头。

无需部署,在本地启动 OpenCode 后端后直接访问托管版前端:

```bash
Expand Down Expand Up @@ -83,7 +87,9 @@ BACKEND_URL=your-server.com:4096 PORT=8080 docker compose -f docker-compose.stan
若你使用自定义 Caddyfile(或遇到 401 认证失败),在 `reverse_proxy` 块中添加:

```caddyfile
handle_path /api/* {
# 🔴 OpenCode V2:用 handle(不要用 handle_path)——V2 的端点本身带 /api 前缀,
# handle_path 会削掉它,请求会打到不存在的路径上(后端只返回 SPA 兜底 HTML)。
handle /api/* {
reverse_proxy your-opencode-serve:4096 {
header_up Host {upstream_hostport}
header_up Authorization {http.request.header.Authorization}
Expand Down Expand Up @@ -314,7 +320,8 @@ preview.example.com {

## 本地开发

需要一个运行中的 [OpenCode](https://github.com/anomalyco/opencode) 后端。
需要一个运行中的 [OpenCode](https://github.com/anomalyco/opencode) **v2** 后端
(**本 UI 只支持 V2,不支持 v1.x**;用 `opencode --version` 确认版本号以 `v2.` 开头)。

```bash
opencode serve
Expand Down
12 changes: 10 additions & 2 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ A third-party Web frontend for [OpenCode](https://github.com/anomalyco/opencode)

## Quick Start

> ⚠️ **OpenCode v2 required** (e.g. `v2.0.19`). **This UI only supports V2 and does not work with v1.x** —
> if the backend is still v1, the UI will load but sending a message will fail.
> Run `opencode --version` to check; the version number should start with `v2.`.

No deployment needed — after starting the OpenCode backend locally, access the hosted frontend directly:

```bash
Expand Down Expand Up @@ -83,7 +87,10 @@ After you enter the username/password in the frontend's server connection dialog
If you use a custom Caddyfile (or hit 401 auth failures), add this to the `reverse_proxy` block:

```caddyfile
handle_path /api/* {
# 🔴 OpenCode V2: use `handle` (NOT `handle_path`) — V2 endpoints already include
# the /api prefix; handle_path strips it and requests hit non-existent paths
# (the backend returns the SPA fallback HTML instead of JSON).
handle /api/* {
reverse_proxy your-opencode-serve:4096 {
header_up Host {upstream_hostport}
header_up Authorization {http.request.header.Authorization}
Expand Down Expand Up @@ -263,7 +270,8 @@ preview.example.com {

## Local Development

Requires a running [OpenCode](https://github.com/anomalyco/opencode) backend.
Requires a running [OpenCode](https://github.com/anomalyco/opencode) **v2** backend
(**this UI only supports V2 and does not work with v1.x**; run `opencode --version` and make sure it starts with `v2.`).

```bash
opencode serve
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ services:
NPM_CONFIG_REGISTRY: ${NPM_CONFIG_REGISTRY:-https://registry.npmmirror.com}
NODEJS_ORG_MIRROR: ${NODEJS_ORG_MIRROR:-https://npmmirror.com/mirrors/node}
MISE_INSTALL_URL: ${MISE_INSTALL_URL:-https://mise.run}
OPENCODE_INSTALL_URL: ${OPENCODE_INSTALL_URL:-https://opencode.ai/install}
# opencode 版本(本 UI 只支持 V2)。Dockerfile 用它拼下载地址并校验哈希。
OPENCODE_VERSION: ${OPENCODE_VERSION:-2.0.19}
5 changes: 4 additions & 1 deletion docker/Caddyfile.gateway
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
# ---- 主入口 :6658 — OpenCode 应用 ----
:6658 {
# API(SSE + WebSocket 自动处理)
handle_path /api/* {
# 🔴 OpenCode V2:端点带 /api 前缀 → 原样透传,不能削(见 Caddyfile.standalone 注释)。
# 同时显式透传 Authorization:后端开启 OPENCODE_SERVER_PASSWORD 时必需(否则全部 401)。
handle /api/* {
reverse_proxy opencode-backend:4096 {
header_up Authorization {http.request.header.Authorization}
flush_interval -1
}
}
Expand Down
7 changes: 5 additions & 2 deletions docker/Caddyfile.standalone
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
# 静态文件 + API 反代到外部 opencode serve

:3000 {
# API 反代(strip /api 前缀,SSE 需要 flush_interval -1)
handle_path /api/* {
# API 反代(SSE 需要 flush_interval -1)
# 🔴 OpenCode V2:端点本身带 /api 前缀(/api/session、/api/info…)→ 必须原样透传。
# V1 时代用 `handle_path` 削掉 /api 才能命中后端;V2 这么做会把请求打到不存在的
# 路径上,后端只返回 SPA 兜底 HTML(前端拿 HTML 当 JSON 解析 → 整条链路坏)。
handle /api/* {
reverse_proxy {$BACKEND_URL:host.docker.internal:4096} {
header_up Host {upstream_hostport}
# 透传客户端 Authorization 头(后端开启 OPENCODE_SERVER_PASSWORD 认证时必需)
Expand Down
36 changes: 29 additions & 7 deletions docker/Dockerfile.backend
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ 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
# 🔴 opencode 版本**必须钉死**:本 UI 只支持 V2,装成 V1 会直接跑不起来。
# 升级步骤:改这里的默认值 + 重算下方 amd64/arm64 两个 OC_SHA256(见安装段的注释)。
ARG OPENCODE_VERSION=2.0.19

ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
Expand Down Expand Up @@ -89,17 +92,35 @@ RUN case "${TARGETARCH}" in \
| MISE_INSTALL_PATH=/usr/local/bin/mise MISE_INSTALL_FROM_GITHUB=1 MISE_INSTALL_ARCH="${MISE_ARCH}" sh \
&& mise --version

# 安装 opencode。直接从 GitHub Releases 拉 tarball 解压,绕开 opencode.ai/install 脚本:
# 该脚本为拼一个版本号字符串去调 github.com/ghapi(未鉴权,60 次/小时/IP),
# GitHub Actions 共享 runner IP 极易触发限流而 exit 1;而 releases/latest/download
# 本身是 302 跳转,不需要版本号。tarball 内仅含单个 opencode 二进制。
# 安装 opencode(**钉死版本 + 校验哈希**)。
#
# 🔴 为什么必须换渠道(阶段 4 实测结论):
# GitHub Releases 的 `latest` 已经停在 **v1.18.33(V1)** —— 全仓库 500 个 release 里
# 零个 v2,`v2.0.x` 也没有 release 资源(tarball 直链 404)。npm 的 `opencode-ai`
# 同样没有 v2。也就是说:**照旧写法拉 `releases/latest/download` 一定装成 V1**,
# 而本 UI 只支持 V2 → 容器起来后必然连不上/报错。
#
# V2 的真实分发渠道是官方更新服务 `update.opencode.ai` 指向的静态文件服务:
# https://opencode.ai/files/bin/<版本>/opencode-linux-<架构>.tar.gz
# (版本索引:`https://update.opencode.ai/api/dev`(v2 分支快照)/ `/api/beta`)
# tarball 内仍是**单个 `opencode` 文件**,解压命令与旧 GitHub tarball 一致。
#
# ✅ 顺带收益:新渠道不经过 GitHub,**彻底摆脱 GitHub 限流**(旧注释里
# 「共享 runner IP 极易触发 60 次/小时 限流」的问题不复存在)。
#
# 🔒 sha256 校验:防止上游文件被替换 / 下载被中间人篡改 / 半截包。
# 升级版本时:① 改 ARG OPENCODE_VERSION;② 用
# `curl -fsSL -o t.tar.gz <新URL> && sha256sum t.tar.gz` 重算两个架构的哈希。
RUN case "${TARGETARCH}" in \
amd64) OC_ARCH=x64 ;; \
arm64) OC_ARCH=arm64 ;; \
amd64) OC_ARCH=x64 \
OC_SHA256=1a9f7184292035a56b6cf22439762930b3a3ed13812f7360c33bb7ae07ef4075 ;; \
arm64) OC_ARCH=arm64 \
OC_SHA256=d0138dd9b43910c28166cfc4da4a53a1b99b07931a37afa749bde812cd02e5a4 ;; \
*) echo "unsupported arch: ${TARGETARCH}" >&2; exit 1 ;; \
esac \
&& curl -fsSL -o /tmp/opencode.tar.gz \
"https://github.com/anomalyco/opencode/releases/latest/download/opencode-linux-${OC_ARCH}.tar.gz" \
"https://opencode.ai/files/bin/${OPENCODE_VERSION}/opencode-linux-${OC_ARCH}.tar.gz" \
&& echo "${OC_SHA256} /tmp/opencode.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/opencode.tar.gz -C /usr/local/bin opencode \
&& rm -f /tmp/opencode.tar.gz \
&& chmod +x /usr/local/bin/opencode \
Expand All @@ -114,6 +135,7 @@ ENV MISE_DATA_DIR=/root/.local/share/mise \
MISE_STATE_DIR=/root/.local/state/mise \
OPENCODE_DISABLE_AUTOUPDATE=true \
OPENCODE_DISABLE_TERMINAL_TITLE=true \
OPENCODE_VERSION=${OPENCODE_VERSION} \
WORKSPACE=/workspace

ENV PATH="/root/.local/bin:/root/.cargo/bin:/root/.local/share/mise/shims:/root/.local/share/mise/bin:${PATH}"
Expand Down
2 changes: 2 additions & 0 deletions docker/Dockerfile.frontend
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ RUN set -eux; \
npm ci --ignore-scripts
COPY . .
RUN node scripts/copy-material-icons.mjs
# 相对路径 = 「同源反代」语义。V2 下由前端解析为**当前页面 origin**
# (见 src/constants/api.ts 的 V2 修正注释;V1 时代它靠反代削 /api 前缀,V2 不再削)。
ENV VITE_API_BASE_URL=/api
RUN npm run build

Expand Down
26 changes: 23 additions & 3 deletions docker/backend-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,30 @@ ensure_mise() {
}

ensure_opencode() {
# 镜像里已经装好了(Dockerfile 构建期安装 + sha256 校验),这里只是兜底:
# 只有当 /root 卷被换掉、二进制丢失时才重新下载。
if opencode --version >/dev/null 2>&1; then
return
fi

# 直接从 GitHub Releases 拉 tarball,绕开 opencode.ai/install 脚本对
# github.com/ghapi 的依赖(共享 IP 易触发限流)。
# 🔴 必须走 opencode.ai/files 渠道,**不能用 GitHub Releases 的 latest**:
# GitHub 的 `latest` 已经停在 v1.18.33(V1),而本 UI 只支持 V2
# (V2 不在 GitHub Releases;npm 上的 v2 在 `@opencode/cli` 包,不是 `opencode-ai`)。
# 详见 docker/Dockerfile.backend 的注释。
# 版本号由 Dockerfile 的 ENV OPENCODE_VERSION 传进来。
case "$(uname -m)" in
x86_64) OC_ARCH=x64 ;;
aarch64|arm64) OC_ARCH=arm64 ;;
*) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac

curl -fsSL -o /tmp/opencode.tar.gz \
"https://github.com/anomalyco/opencode/releases/latest/download/opencode-linux-${OC_ARCH}.tar.gz"
"https://opencode.ai/files/bin/${OPENCODE_VERSION:-2.0.19}/opencode-linux-${OC_ARCH}.tar.gz"
tar -xzf /tmp/opencode.tar.gz -C /usr/local/bin opencode
rm -f /tmp/opencode.tar.gz
chmod +x /usr/local/bin/opencode
# 兜底下载后做一次版本确认:装成 V1 时立刻失败,而不是等到界面连不上才排查。
opencode --version
}

ensure_package_mirrors() {
Expand Down Expand Up @@ -62,6 +69,19 @@ ensure_mise
ensure_opencode
ensure_package_mirrors

# 🔴 版本守卫(阶段 4 新增):本 UI 只支持 opencode **V2**。
# 如果容器里的二进制是 V1(典型场景:还在用改造前构建的旧镜像),
# 表现是「界面能打开、一发消息就连不上」,排查成本极高 —— 所以在这里直接喊出来。
# 注意是**警告不是退出**:万一 --version 输出格式变了,不应该让容器起不来。
OC_VERSION_OUTPUT="$(opencode --version 2>/dev/null || echo unknown)"
case "${OC_VERSION_OUTPUT}" in
*v2.*) ;;
*)
echo "WARNING: opencode 版本不是 V2(实测输出:${OC_VERSION_OUTPUT})。" >&2
echo "WARNING: 本 UI 只支持 V2。请更新或重建后端镜像(见 docker/Dockerfile.backend)。" >&2
;;
esac

if [ "$#" -eq 0 ]; then
set -- opencode serve --port 4096 --hostname 0.0.0.0
fi
Expand Down
7 changes: 5 additions & 2 deletions docker/nginx.host.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ server {
}

# API 反向代理
# 🔴 OpenCode V2:端点本身带 /api 前缀 → proxy_pass 不能带尾斜杠
# (带尾斜杠会削掉 /api/,请求打到不存在的路径上,后端只返回 SPA 兜底 HTML)。
location /api/ {
proxy_pass http://127.0.0.1:4096/;
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;
Expand All @@ -36,7 +38,8 @@ server {

# WebSocket(PTY 终端)
location /api/pty/ {
proxy_pass http://127.0.0.1:4096/pty/;
# V2:同样不能带尾斜杠(保持 /api/pty/… 原样透传)
proxy_pass http://127.0.0.1:4096;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
Expand Down
Loading