23 KiB
AGENTS.md
本文件是影策仓库中 AI、自动化工具和协作者的工作约定。用户当前任务优先于本文件;本文件优先于个人习惯。所有结论应能回溯到代码、配置、测试、日志或文档,不用历史印象替代现状。
1. 项目边界
影策(ddcat-ai/open-ai-canvas)是面向 AI 影视与短剧创作的工作台,当前仍在快速开发。公开接口、数据结构和部署配置可能直接调整;除非任务明确要求,不为旧字段、旧 API 或旧数据增加兼容层。
仓库由几个边界清晰但可独立运行的单元组成:
| 单元 | 技术栈 | 入口 | 责任 |
|---|---|---|---|
web/ |
Vite、React 19、TypeScript、React Router、Ant Design、Tailwind、Zustand、TanStack Query | web/src/application.tsx、web/src/router.tsx |
工作区 UI、画布交互、浏览器缓存、API 调用和模型协议适配 |
backend/ |
Go 1.25、Gin、GORM、SQLite/PostgreSQL、Redis 协调 | backend/cmd/server/main.go |
登录、权限、业务 API、任务队列、资源、模型中转和后台管理 |
docs/ |
Next.js、Fumadocs、MDX | docs/content/docs/ |
面向用户和开发者的专题文档;构建配置见 docs/source.config.ts |
根目录的 Dockerfile 构建前端静态镜像;nginx.conf 托管 SPA 并代理后端。docker-compose.dev.yml 是源码热更新开发编排,docker-compose.local.yml 是本地构建运行,docker-compose.deploy.yml 是 PostgreSQL + Redis 部署编排。
2. 开始工作前
Agent 本地开发
详细说明请参考 docs/AGENT_LOCAL_DEVELOPMENT.md。
快速启动:
# 1. 安装 Agent runtime 依赖
cd backend/agent-runtime/pi && npm install && cd ../../..
# 2. 启动后端(自动使用嵌入模式)
cd backend
CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug go run ./cmd/server
# 3. 启动前端
cd web && bun run dev
本地开发不设置 YINGCE_AGENT_URL 时,后端在本进程内启动 Node,无需单独启动 yingce-agent。
生产 Compose 设置 YINGCE_AGENT_URL 后只调用 yingce-agent 容器。远程服务失败时不要回退到内嵌进程,否则同一步可能执行两次。不要删除 yingce-agent/ 或把它从发布流程里撤掉。
其他本地开发说明
- 先读取任务涉及的入口、调用方、配置、锁文件和相邻测试;先理解现状,再决定是否抽象或重构。
- 使用
rg/rg --files搜索,优先并行读取相关文件。不要为了“统一风格”改动无关模块、依赖、格式或用户已有修改。 - 先形成目标边界:页面负责什么、service 负责什么、handler/service/repository 如何分层、数据和错误如何流动。新增 helper 必须消除真实重复或隔离明确协议,不能只透传参数。
- 检查
git status --short。不覆盖、不回滚、不清理非本次产生的变更;不使用git reset --hard、git checkout --或宽范围删除。 - 手工编辑使用
apply_patch;默认使用 ASCII,业务中文或已有 Unicode 文件除外。注释只解释非直观算法、核心入口、安全边界和降级原因。
3. 目录职责和依赖方向
前端
web/src/pages/:路由页面及页面私有 hook/组件;页面协调流程,不直接拼装后端协议。web/src/layouts/:路由级布局、全局浮层和页面壳;不要在页面重复设置全局 body 状态。web/src/components/:真实跨页面复用的 UI 或交互能力;页面私有组件留在页面目录。web/src/services/api/:业务 API、模型渠道协议、资源 API;不依赖 JSX、路由或 AntD 提示。web/src/services/:文件、媒体、同步、缓存和生成任务等跨页面副作用。web/src/stores/:跨页面状态和持久化配置;页面临时状态留在页面,媒体大对象不进localStorage。web/src/lib/:纯函数、画布算法、协议转换、设计 token 和可独立测试的基础能力。web/src/styles/globals.css:变量、重置和必要的第三方覆盖;页面样式优先使用现有 token 或页面样式。
后端
backend/internal/handler/:HTTP 入参、鉴权上下文、调用 service、返回统一响应;不放业务判断和数据库查询。backend/internal/service/:稳定导入面。只再导出internal/app的类型、常量和包级函数(aliases_*.go)。handler/cmd 继续 import 本包。backend/internal/app/:HTTP 组合根和尚未拆出的业务实现。校验、权限、跨域编排从这里进入。画布生成调度仍主要在provider.go;文本 / 图片 / 视频遗留 / 音频 / HTTP / 声明式协议分别在provider_text.go、provider_image.go、provider_video.go、provider_audio.go、provider_http_client.go、provider_protocol.go。技能库在internal/skills,提示词与风格在internal/prompts,登录注册在internal/auth,画布分享在internal/canvas,资源引用解析在internal/assets,运行时策略/限流/worker 在internal/platform,错误码与通用工具在internal/kernel,出站 SSRF 在internal/outbound,Agent 上下文检查点契约在internal/agentcontext。域包不得 importinternal/service或internal/app。backend/internal/repository/:GORM 查询和持久化;不承载业务策略。backend/internal/model/:结构、枚举和简单模型方法;不调用外部服务。backend/internal/provider/:模型供应商能力和协议实现。backend/internal/database/:数据库连接、迁移和连接池。backend/cmd/:可执行入口、迁移和启动配置;启动参数不得绕过数据目录约束。
调用链应保持为:HTTP -> handler -> service(别名)-> app(组合根)-> 业务域包 -> repository/model -> database/resource;需要模型上游时由域包或 app 进入 protocol / generation / outbound。跨层调用必须有明确理由并补测试。
Agent、插件和文档
- 修改
docs/前确认内容属于专题文档,而不是把长篇说明重新复制到根 README。目录索引见docs/index.md。
4. 前端 API 和状态合同
后端业务 JSON
业务 API 的唯一调用入口是 web/src/services/api/request.ts 导出的 http:
- 模块写
http.get/post/put/patch/delete,由http解包{ code, data, msg, reason }。不要再写request(apiClient.*),也不要再axios.create。 - 二进制或非信封响应用
http.raw(CSV、诊断包 zip)。 apiClient只留给拦截器和http内部;默认VITE_CANVAS_BACKEND_URL || "/api"、withCredentials: true,登录 Cookie 不放进 URL。request()仍可用于测试信封解包,不是业务模块的调用面。- 后端成功响应为
{ code: 0, data: T, msg: string };HTTP 200 不等于业务成功,code !== 0必须抛错。失败时用ApiError.reason/ApiError.code判断类型,不要解析msg。错误码见web/src/services/api/error-codes.ts与docs/content/docs/backend/http-api.mdx。 - OpenAPI 3.0 在
GET /api/openapi.yaml。不要从规范生成 TypeScript 客户端来替换http模块。 - API 模块定义并导出接口类型;页面和 React Query 直接接收解包后的
data,不重复访问.data.data。 - 查询参数使用
compactApiParams/serializeApiParams;取消请求传递AbortSignal并保留取消语义。 FormData不手动设置Content-Type,让 Axios 生成 boundary。写路径失败必须向上抛出,不能catch { return defaultValue }。
模型渠道和流式请求
- 自定义渠道 URL/Header 仍由
custom-channel-relay.ts的channelRequest解析;实际发出走channel-transport.ts的createChannelTransport。image / video / audio 只组协议 payload,不再各自axios + channelRequest。 - 自定义渠道必须由登录态后端
/api/ai/custom中转;重建 headers 时清除x-goog-api-key和旧的X-Canvas-Upstream-Headers,不得把第三方密钥放入浏览器 URL。 - Provider 特有 payload、响应解包和状态机留在对应
image.ts、video.ts、audio.ts;不要塞进通用request.ts。 - 原始
fetch仅用于媒体 blob/data URL、资源、Worker 或 SSE;必须检查response.ok,传递正确的credentials和signal。 - 文本任务 SSE 是
GET /api/tasks/:id/text-events,游标是递增事件id;断线使用Last-Event-ID或?after=,不能把任务 ID 当游标。 - 代理只对文本任务和明确的系统模型事件流路径关闭缓冲/缓存/gzip;不要给所有
/api/请求复制长超时和proxy_buffering off。
数据、缓存和写路径
- 画布、项目、任务、素材和大 JSON 使用带用户 scope 的
localforage;localStorage只保存小型配置、当前 scope 或 UI 偏好。 - 用户切换时隔离 React Query、localforage 和资源缓存;不能让账号之间串数据。
- 后端不可用时的本地缓存是降级,不代表服务端已保存。UI 必须区分“本地缓存成功”和“服务端持久化成功”。
- 生成、激活、审批、权限、删除、上传、配额、账务和密钥相关操作属于强校验写路径;不使用空 ID、默认用户、默认权限或默认额度兜底。
- 素材删除必须先检查项目、画布、任务和其他业务引用;有引用则保留并返回来源,无引用才清理物理对象。物理删除失败不得删除素材记录。
5. 后端响应、权限和安全
- Gin 接口统一返回
{ code, data, msg, reason };失败时 HTTP status 和业务code都应表达真实失败,不把所有错误包装成 200。机器可读原因放在reason,见docs/content/docs/backend/http-api.mdx。 - 所有对象读取、更新、删除都在 service 校验当前用户和资源归属;管理员权限在 service 校验,不依赖前端隐藏按钮。
- 默认拒绝本机、私网和链路本地上游。可信开发主机只能通过
CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS精确放行;不要设置“允许全部私网”来绕过 SSRF 防护。 - 用户 API Key 保存在浏览器本地,任务创建时可能提交给自部署后端;只在可信部署和 HTTPS 下使用真实密钥。日志、错误上报、URL、localStorage 和持久任务正文不得写入敏感 URL、Cookie 或 API Key。
- 生产必须配置明确的
CANVAS_CORS_ORIGINS,保持 HTTPS,限制数据库、备份、数据目录和.settings-key权限;默认关闭公开注册。 - 数据库字段或表变化时同步更新
docs/content/docs/backend/backend-database.mdx,不能只改 GORM model。
6. 画布、UI 和设计系统
- 画布组件、状态、算法分别放在
web/src/components/canvas/、web/src/stores/canvas/、web/src/lib/canvas/。事件忽略选择器必须覆盖 modal、popover、dropdown 等浮层。 - 画布拖拽、连接、缩放和快捷键要考虑 pointer capture、滚轮冒泡、焦点以及
data-canvas-no-zoom/data-canvas-wheel-scroll边界。 - 节点和对象名称要有可发现的铅笔入口并支持单击编辑;双击或右键不能是唯一入口。图片节点保持原始比例,面板不能长期遮挡主要画布空间。
- Ant Design 共性主题和控件状态集中在
web/src/lib/app-theme.ts/AppProviders。自带外壳的产品弹窗用AppModal flush,侧栏用AppDrawer;不要再复制padding: 0的 Modal styles。内容外壳仍是.ant-modal-container。 - 第三方覆盖限定在具体组件,不新增全局
.ant-modal-*、.dark .ant-switch-*、.ant-checkbox-*或 Segmented 状态补丁。新增 CSS 前先搜索同名选择器,回到唯一源规则修改。 - 遵循
docs/ui-design-system.md及项目三层 token:Primitive → Semantic → Component。inline style 优先引用var(--token-name),不要散落颜色、圆角、阴影和层级字面值。 - 主操作、普通选中、Checkbox/Radio、Switch 是不同颜色角色;持久切换使用
aria-pressed,type="primary"只表示当前主要命令。尊重prefers-reduced-motion,键盘导航保留:focus-visible。 - 修改既有页面时直接修改真实组件、样式和路由。不得创建独立 HTML 来代替真实页面验证;只有用户明确要求原型或隔离设计稿时才可生成,并放在临时目录或用户指定位置,不得放入
web/public/或产品构建目录。
7. 本地开发、部署和数据目录
- 先阅读
.env.example和对应 Compose 文件。宿主机后端开发必须使用 Git 忽略的.local/project-workbench-debug,通过CANVAS_BACKEND_DATA_DIR显式指定;不要把backend/data当作开发账号数据库。 - 本地缓存放
.local/cache;不要提交数据库、上传文件、.env、真实密钥、构建产物或编辑器配置。 - 宿主机开发:
backend/运行CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug go run ./cmd/server,web/使用 Bun 和 Vite;不要用 pnpm/npm 覆盖同一套node_modules,也不要提交pnpm-lock.yaml或package-lock.json。Docker 热更新使用docker-compose.dev.yml;本地构建运行使用docker-compose.local.yml。 - 生产 Compose 使用
docker-compose.deploy.yml(PostgreSQL、Redis、backend、web),源码构建可叠加docker-compose.build.yml。公网只暴露 web 的3000,backend8080留在 Compose 网络内。 - 默认不启动 dev server;只有用户明确要求浏览器预览或联调时才启动,并先确认端口、数据目录和现有进程。
- 健康检查只能证明入口可用,不能替代登录、SSE、任务生成和资源访问验证。
8. 验证纪律
项目当前默认不自动运行语法检查、类型检查、测试或构建。用户明确要求验证,或改动风险需要验证时,按范围选择最小充分命令,并在交付中如实记录:
- 前端:
cd web && bun run build;专项测试用bun test ...。UI 退场规则用bun run lint(只禁 antd Empty 和静态Modal.confirm,不是风格检查)。 - 后端:
cd backend && go test ./...;涉及 PostgreSQL、资源、任务或权限时补对应集成/冒烟路径。 - 云端 Agent:前端事件流和审批路径依赖后端
/api/agent接口;在该接口实现后补充后端冒烟、Worker 运行和 SSE 断线重连验证。 - 文档站:
cd docs && bun run types:check或bun run build。 - UI 变更能浏览器验证时,检查关键路由、明暗主题、滚动、弹窗、空态和核心交互;不能验证时说明替代依据,不把静态阅读或
git diff写成运行验证。 - 验证必须针对真实实现:优先专项测试、类型检查、项目 build 或真实路由浏览器验收。独立 HTML、静态仿制页面和脱离应用状态的截图不能作为真实页面通过的证据。
同类失败连续三次时停止盲试,记录现象、已排除项和新假设,再切换路径或请求用户决策。
9. 文档与交付
- 根
README.md只保留项目定位、能力概览、快速开始、部署、安全和文档入口;详细专题写入docs/content/docs/。 - 功能、代码地图、待办、待测试分别维护在
docs/content/docs/overview/features.mdx、docs/content/docs/backend/code-map.mdx、docs/content/docs/progress/todo.mdx、docs/content/docs/progress/pending-test.mdx。已实现但未由用户确认的变化先写入pending-test.mdx。 - API、数据表、SSE、资源存储、部署或安全边界变化时同步对应专题文档;不要只改代码和根 README。
- 文档默认中文,不写过期日期,不公开密码、Token、Cookie、真实账号或机器敏感路径。命令、端口、环境变量必须以当前脚本和 Compose 为准。
- 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是竞品运行时快照与分析资料,属于本地参考,不要当作上游代码改动。- 分支:本仓库工作分支为
main,上游分支也是main(位于upstreamremote);同步用git fetch upstream && git merge upstream/main。
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的--portweb/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_PORTREADME.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 操作,严禁用命令名、路径片段或宽正则批量匹配后终止。
正确做法:
netstat -ano | Select-String 'LISTENING' | Select-String ':<端口>'拿到占用端口的 PID;Get-Process -Id <pid>确认进程名与可执行文件路径确属本项目;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)。 - 误杀非本项目进程时,必须立即如实告知用户,说明进程名、可能影响与恢复方式,不要轻描淡写。
E. 上游同步与 backend/server 大文件
上游在 9727656b(2026-09-14)提交了 backend/server —— 一个 62.7 MB 的可执行构建产物(blob cfba15a0),且至今仍保留在 upstream/main 中。
本仓库曾用 git filter-repo --path backend/server --invert-paths 把它从全部历史移除,代价是本地历史与上游彻底断开血缘(不能用 git merge,只能「指定基线 + 快照提交」)。2026-10-13 起改为以上游为基底、保留上游血缘:main 重建为 upstream/main@e70fe1b9 的子提交(d6d6eab5,内容取当时工作树),随后正常合并 upstream/main。旧的「快照同步」规则随之作废。
现状与约束:
-
main是upstream/main的后代,可以正常git merge upstream/main。日常同步:git fetch upstream git merge upstream/main # 常规三方合并;冲突优先保留上游实现,本仓库差异应集中在 B~D 三项 git ls-files backend/server # 必须为空;若因上游改动而重新出现,执行 git rm backend/server 后再提交 git push origin main -
backend/server的 blob 常驻历史(因为它在上游祖先里),克隆与推送体积会包含它;这是换取上游血缘的必然代价。它已被.gitignore的/backend/server忽略,也不在main的工作树与索引中(git ls-files backend/server为空),因此本地不会被检出。上游将来清理该文件后,直接正常合并即可两全。 -
不要再次运行
git filter-repo或任何历史重写:那会重新切断与上游的血缘,回到旧的快照同步模式。 -
每次合并后检查
git ls-files backend/server;只有上游确实改动该文件时它才会重新进入索引,用git rm backend/server处理并提交,不要改回.gitignore的忽略规则。 -
不要用
git diff --binary生成差异补丁:二进制补丁会把该 blob 整段写进补丁文件(实测 43.8 MB)。 -
迁移用的临时分支、bundle 以及 filter-repo 重写前的备份均已在本地清理;本地不再保留旧(filter-repo)历史,需要追溯只能看
origin(Gitea)或upstream。