build(ports): 本地开发改用 7380/7381 避开端口冲突
Some checks failed
Publish container images / Quality checks (push) Has been cancelled
Publish container images / Resolve build version (push) Has been cancelled
Publish container images / Build open-ai-canvas-backend (amd64) (push) Has been cancelled
Publish container images / Build open-ai-canvas-backend (arm64) (push) Has been cancelled
Publish container images / Build open-ai-canvas-web (multi) (push) Has been cancelled
Publish container images / Publish open-ai-canvas-backend manifest (push) Has been cancelled
Publish container images / Publish open-ai-canvas-web manifest (push) Has been cancelled
Publish container images / Publish release assets (push) Has been cancelled

This commit is contained in:
2026-09-21 14:54:20 +08:00
parent c5b81f696c
commit 3c25511f47
10 changed files with 93 additions and 17 deletions

View File

@@ -4,7 +4,7 @@ CANVAS_REGISTRATION_ENABLED=false
CANVAS_ALLOW_PRIVATE_UPSTREAMS=false
# 多个主机使用英文逗号分隔,例如:gateway.internal,192.168.1.10
CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS=
CANVAS_CORS_ORIGINS=http://localhost:3000
CANVAS_CORS_ORIGINS=http://localhost:7380
# 服务器对模型上游可访问的地址:本地存储的短时签名链接(Agent 看图、生成任务参考图)由它拼出,
# 内网部署还要把该主机放进 CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS。留空则本地存储无法签出链接。
CANVAS_PUBLIC_BASE_URL=
@@ -34,4 +34,5 @@ CANVAS_UPDATER_SOCKET_DIR=/run/open-ai-canvas-updater
# REDIS_URL=redis://redis:6379/0
# CANVAS_POSTGRES_DATA_PATH=./postgres-data
# CANVAS_REDIS_DATA_PATH=./redis-data
# CANVAS_HTTP_PORT=3000
# 宿主机访问端口;本项目使用 7380 以避免与同机其它工程(默认 3000)冲突
# CANVAS_HTTP_PORT=7380

View File

@@ -138,3 +138,73 @@
- Git 提交说明使用 `<type>(<scope>): <业务模块> - <变更摘要>`,`type` 为 `feat|fix|refactor|perf|docs|test|build|ci|chore|revert`。
交付前至少检查:改动是否聚焦、调用方和类型是否同步、错误/权限/数据归属是否完整、必要文档是否同步、验证是否如实说明、是否留下密钥或本地数据。
---
# 本项目附加约定
以上第 1–9 章继承自上游 `ddcat-ai/open-ai-canvas`。以下为本仓库(`ttstd-infinite-canvas`)在底座之上叠加的约定;**与上游冲突时以本章为准**。
## A. 底座与上游同步
本仓库以 `ddcat-ai/open-ai-canvas` 为底座,通过 Git remote 保持可追溯的上游关系:
- `upstream` → `https://github.com/ddcat-ai/open-ai-canvas.git`(开发机上另存有一份克隆,可作离线 fetch 源)。
- 同步新功能:`git fetch upstream && git merge upstream/main`。冲突优先保留上游实现,本仓库的差异应集中在 B~D 三项。
- **功能一律优先使用上游源码实现**,不要在本仓库另起一套。确需偏离上游时,先在本章登记原因再改代码。
- `libtv.html` 与 `libtv-前端技术分析.md` 是竞品运行时快照与分析资料,属于本地参考,不要当作上游代码改动。
- 分支:本仓库工作分支为 `master`,上游为 `main`;`git pull upstream` 后按需合并。
## B. 端口约定
**不要改回上游默认的 3000 / 8080**——同机上 `infinite-canvas`、`open-ai-canvas`、`open-canvas` 都占用 3000,会互相抢占。
| 场景 | 前端 | 后端 |
| --- | --- | --- |
| 本地开发(宿主机) | **7380** | **7381** |
| Docker(宿主机映射) | **7380** → 容器 3000 | 不暴露(容器内仍 8080) |
容器内部端口保持上游原样(nginx `listen 3000`、后端 `:8080`),以减小与上游的差异面。改端口时必须同步:
- `web/package.json` — `dev` / `start` 的 `--port`
- `web/vite.config.ts` — `apiProxyTarget` 默认值
- `backend/cmd/server/main.go` — `CANVAS_BACKEND_ADDR` 默认值
- `scripts/start-local.ps1` — 后端地址、代理目标、提示文案
- `docker-compose.yml`、`docker-compose.local.yml` — 宿主机端口映射与 `CANVAS_CORS_ORIGINS`
- `.env.example` — `CANVAS_CORS_ORIGINS`、`CANVAS_HTTP_PORT`
- `README.md` 快速开始
`docker-compose.deploy.yml` / `docker-compose.server.yml` 与 `scripts/install-server*.sh` 面向独立服务器部署,保留上游的 3000 默认值,不做本地化改动。
`scripts/launch-canvas.ps1` 是上游维护者的个人脚本(硬编码 `D:\yingce\...`、`Administrator`、cloudflared 隧道),在本机不可用,**保持原样不要改**。
## C. 许可与商标红线
- 本仓库与上游均为 **MIT**。复制上游代码必须保留其版权声明与 `NOTICE`(声明 fork 自 `basketikun/infinite-canvas`,上游作者权利保留)。
- **`tldraw` 许可非 MIT**:生产环境商用需购买 license key,否则必须保留 "Made with tldraw" 水印。上游已预留 `VITE_TLDRAW_LICENSE_KEY`(构建期)与 `tldraw_license_key`(运行时设置),对外商用前必须就此明确决策。
- **不得新增他人 logo / 商标**:`web/public/icons/` 下的模型厂商图标、根 README 的赞助商与社群素材仅在上游语境下存在,对外分发前必须清理或替换为纯文字。
- **禁止把带 GPL 组件的 ffmpeg 打进分发镜像**:服务端 ffmpeg 使用 LGPL 构建或由用户挂载(`CANVAS_FFMPEG_PATH`)。
- 面向国内公众提供服务时,需处理 AI 生成内容标识、生成式 AI 服务备案与深度合成管理要求。
## D. 进程清理(强制)
验收或调试结束后清理进程时,**只允许按精确 PID 操作**,严禁用命令名、路径片段或宽正则批量匹配后终止。
正确做法:
1. `netstat -ano | Select-String 'LISTENING' | Select-String ':<端口>'` 拿到占用端口的 PID;
2. `Get-Process -Id <pid>` 确认进程名与可执行文件路径确属本项目;
3. `Stop-Process -Id <pid> -Force`;需要连带子进程时用 `taskkill /PID <pid> /T /F`。
禁止的做法(已有实际事故):
- ❌ `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'server\.exe' } | Stop-Process`
曾匹配到 IDE 的 `embeddings-server.exe`,导致误杀开发环境进程。
- ❌ `-match 'vite'` / `-match 'node'` / `-match 'concurrently'` / `-match 'go'`
这些字符串同样会命中编辑器本体、语言服务器,以及同机上其它工程。
补充要求:
- 需要批量清理时,**先只打印**匹配结果(PID、进程名、完整命令行)供人工确认,确认后另起一步执行终止;不要在同一条命令里「查完即杀」。
- 无法确认归属的进程一律不动,交回用户处理。同机存在多个同类工程时尤其要区分(例如 `infinite-canvas/web/.../vite.js` 与本项目的 `vite.js`)。
- 误杀非本项目进程时,必须立即如实告知用户,说明进程名、可能影响与恢复方式,不要轻描淡写。

View File

@@ -53,7 +53,7 @@ mkdir -p .local/project-workbench-debug .local/cache/go-build .local/cache/go-mo
# 终端一:后端
cd backend
CANVAS_BACKEND_ADDR=127.0.0.1:8080 \
CANVAS_BACKEND_ADDR=127.0.0.1:7381 \
CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug \
GOCACHE=../.local/cache/go-build \
GOMODCACHE=../.local/cache/go-mod \
@@ -65,7 +65,9 @@ bun install --frozen-lockfile
bun run dev
```
打开 <http://localhost:3000>。首次使用时注册管理员账号,并在设置中配置模型渠道。前端默认将 `/api` 代理到 `http://127.0.0.1:8080`;如需修改代理目标,可设置 `VITE_API_PROXY_TARGET`。
打开 <http://localhost:7380>。首次使用时注册管理员账号,并在设置中配置模型渠道。前端默认将 `/api` 代理到 `http://127.0.0.1:7381`;如需修改代理目标,可设置 `VITE_API_PROXY_TARGET`。
> **本项目端口约定**:本地开发使用前端 **7380** / 后端 **7381**,Docker 宿主机端口为 **7380**(容器内仍为上游默认的 3000/8080)。这样可与同机上的 `infinite-canvas`、`open-ai-canvas` 等工程并存。详见 [AGENTS.md](./AGENTS.md) 的「本项目附加约定」。
Windows PowerShell 用户可在仓库根目录执行:

View File

@@ -67,7 +67,8 @@ func run(ctx context.Context) error {
}
repo := repository.New(db)
addr := env("CANVAS_BACKEND_ADDR", ":8080")
// 本项目本地开发端口 7381,避开上游默认的 8080(见 AGENTS.md「本项目附加约定」)。
addr := env("CANVAS_BACKEND_ADDR", ":7381")
svc := service.New(repo, dataDir)
if updaterToken := strings.TrimSpace(os.Getenv("CANVAS_UPDATER_TOKEN")); updaterToken != "" {
svc.ConfigureUpdateManager(updaterclient.New(env("CANVAS_UPDATER_SOCKET", "/run/open-ai-canvas-updater/updater.sock"), updaterToken))

View File

@@ -25,7 +25,7 @@ services:
CANVAS_REGISTRATION_ENABLED: ${CANVAS_REGISTRATION_ENABLED:-false}
CANVAS_ALLOW_PRIVATE_UPSTREAMS: ${CANVAS_ALLOW_PRIVATE_UPSTREAMS:-false}
CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS: ${CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS:-}
CANVAS_CORS_ORIGINS: ${CANVAS_CORS_ORIGINS:-http://localhost:3000}
CANVAS_CORS_ORIGINS: ${CANVAS_CORS_ORIGINS:-http://localhost:7380}
# 服务器访问地址:Agent 看图与生成参考图都靠它签发上游可取到的短时链接。
CANVAS_PUBLIC_BASE_URL: ${CANVAS_PUBLIC_BASE_URL:-}
GIN_MODE: debug

View File

@@ -18,7 +18,7 @@ services:
CANVAS_REGISTRATION_ENABLED: ${CANVAS_REGISTRATION_ENABLED:-false}
CANVAS_ALLOW_PRIVATE_UPSTREAMS: ${CANVAS_ALLOW_PRIVATE_UPSTREAMS:-false}
CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS: ${CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS:-}
CANVAS_CORS_ORIGINS: ${CANVAS_CORS_ORIGINS:-http://localhost:3000}
CANVAS_CORS_ORIGINS: ${CANVAS_CORS_ORIGINS:-http://localhost:7380}
# 服务器访问地址:Agent 看图与生成参考图都靠它签发上游可取到的短时链接。
CANVAS_PUBLIC_BASE_URL: ${CANVAS_PUBLIC_BASE_URL:-}
ENABLE_PROVIDER_PLUGINS: ${ENABLE_PROVIDER_PLUGINS:-false}
@@ -40,7 +40,7 @@ services:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
- "7380:3000"
depends_on:
backend:
condition: service_healthy

View File

@@ -18,7 +18,7 @@ services:
CANVAS_REGISTRATION_ENABLED: ${CANVAS_REGISTRATION_ENABLED:-false}
CANVAS_ALLOW_PRIVATE_UPSTREAMS: ${CANVAS_ALLOW_PRIVATE_UPSTREAMS:-false}
CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS: ${CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS:-}
CANVAS_CORS_ORIGINS: ${CANVAS_CORS_ORIGINS:-http://localhost:3000}
CANVAS_CORS_ORIGINS: ${CANVAS_CORS_ORIGINS:-http://localhost:7380}
# 服务器访问地址:Agent 看图与生成参考图都靠它签发上游可取到的短时链接。
CANVAS_PUBLIC_BASE_URL: ${CANVAS_PUBLIC_BASE_URL:-}
GIN_MODE: release
@@ -33,7 +33,7 @@ services:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
- "7380:3000"
depends_on:
backend:
condition: service_healthy

View File

@@ -70,19 +70,19 @@ $dataDirLiteral = ConvertTo-PowerShellLiteral $dataDir
$backendCommand = @"
`$ErrorActionPreference = 'Stop'
Set-Location -LiteralPath $backendDirLiteral
`$env:CANVAS_BACKEND_ADDR = '127.0.0.1:8080'
`$env:CANVAS_BACKEND_ADDR = '127.0.0.1:7381'
`$env:CANVAS_BACKEND_DATA_DIR = $dataDirLiteral
`$env:GOCACHE = $(ConvertTo-PowerShellLiteral $goBuildCache)
`$env:GOMODCACHE = $(ConvertTo-PowerShellLiteral $goModuleCache)
Write-Host '影策后端:http://127.0.0.1:8080' -ForegroundColor Cyan
Write-Host '影策后端:http://127.0.0.1:7381' -ForegroundColor Cyan
go run ./cmd/server
"@
$webCommand = @"
`$ErrorActionPreference = 'Stop'
Set-Location -LiteralPath $webDirLiteral
`$env:VITE_API_PROXY_TARGET = 'http://127.0.0.1:8080'
Write-Host '影策前端:http://localhost:3000' -ForegroundColor Cyan
`$env:VITE_API_PROXY_TARGET = 'http://127.0.0.1:7381'
Write-Host '影策前端:http://localhost:7380' -ForegroundColor Cyan
bun run dev
"@

View File

@@ -4,14 +4,14 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0 --port 3000",
"dev": "vite --host 0.0.0.0 --port 7380",
"build": "tsc --noEmit && vite build",
"pretest": "bun test test/tiptap-dependency-versions.test.ts test/admin-ui-regressions.test.ts test/announcement-prompt-regressions.test.ts test/password-reset.test.ts test/oss-settings.test.ts test/channel-model-editor-form.test.ts test/channel-model-price-tier-form.test.ts test/admin-logical-models.test.ts && bun test test/canvas-grid-viewport.test.tsx",
"typecheck": "tsc --noEmit",
"test:canvas": "bun test test/canvas-media-performance.test.ts test/canvas-node-title-interaction.test.ts test/canvas-spatial-index.test.ts",
"test": "bun test",
"test:director:e2e": "bun scripts/director-p0-chrome-e2e.mjs",
"start": "vite preview --host 0.0.0.0 --port 3000",
"start": "vite preview --host 0.0.0.0 --port 7380",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint src test"

View File

@@ -7,7 +7,9 @@ import { defineConfig } from "vite";
const webDir = dirname(fileURLToPath(import.meta.url));
const appVersion = process.env.CANVAS_BUILD_VERSION?.trim() || readFileSync(resolve(webDir, "../VERSION"), "utf8").trim();
const appChangelog = readFileSync(resolve(webDir, "../CHANGELOG.md"), "utf8");
const apiProxyTarget = process.env.VITE_API_PROXY_TARGET?.trim() || "http://127.0.0.1:8080";
// 本项目本地开发端口:前端 7380、后端 7381(见 AGENTS.md「本项目附加约定」)。
// 避开上游默认的 3000 / 8080,以便与 infinite-canvas、open-ai-canvas 等同机工程并存。
const apiProxyTarget = process.env.VITE_API_PROXY_TARGET?.trim() || "http://127.0.0.1:7381";
export default defineConfig({
plugins: [react()],