sync: complete remaining files (source ji)

This commit is contained in:
gozeros committed 2026-10-21 15:15:16 +08:00
1 parent e602d88695
commit b8ad8fda94
80 files changed
+3420 -924

No files matched your search

@@ -0,0 +1,113 @@
# Web App 产物模式:多页面站点 / 流程图 / 地图(可降级 + 独立页面风格)
## 总体架构
让智能体在沙箱里产出**完整多页面站点**(site.json 清单 + src/pages + public 数据),平台统一构建(预置 node_modules,永不 npm install)与受控静态托管,前端新注册一个**风格独立**的「站点预览」路由页。
**两条铁律(本次新增的核心原则)**:
1. **零侵入**:新页面不复用现有工作台风格/布局组件,走独立路由 + 独立样式表;对已有代码的修改仅限「一个带开关的入口按钮」。
2. **可降级**:沙箱/模板/node/构建链任何一环部署失败,Web App 功能整体退回"不可用"状态并优雅提示,智能体回退到现有单页 HTML 模式,**现有功能零影响**。
```
智能体(技能 web-app)
0. 检查可用性:/mnt/web-app-template 缺失 → 回退现有单页 HTML 模式(行为与今天完全一致)
1. scaffold → outputs/my-site/(排除 node_modules/dist)
2. 写 src/pages/*.tsx、public/data.geojson、编辑 site.json
3. build_site.py build → 软链/junction 预置 node_modules → vite build → dist/(失败自修复 ≤3 次)
4. present_files site.json
平台(Gateway,全部能力探测 + 开关保护)
GET /api/web-app/capability # 可用性探测(配置/模板/node_modules/node 四项检查)
GET /api/threads/{tid}/sites # 站点列表 + 构建状态
POST /api/threads/{tid}/sites/{name}/build # 触发构建
GET /api/threads/{tid}/sites/{name}/build-status
POST /api/threads/{tid}/sites/{name}/preview-url → 签发短时效签名 URL
GET /api/public/site-preview/{token}/{path} # 静态托管 dist/
前端(独立路由 + 独立风格)
#/sites/:threadId/:siteName # SiteStudioPage:全新视觉风格,无现有 chrome
产物面板 site.json →「打开站点预览」按钮(仅 capability 可用时渲染)
```
已确认决策:混合构建(技能自修复 + 平台按钮兜底);预览工作台先行;GeoJSON 优先 + 可配置底图。
---
## 一、页面风格:独立设计系统(不用现有风格)
新页面 `SiteStudioPage` 做成**深色"预览工作室"**风格,视觉上与现有工作台完全区隔:
- **独立路由**:直接注册在 `App.tsx` 顶层(`/share/:shareCode`、`/login` 同款先例),**不进** `PageLayout`/`WorkspaceLayout`/`Header`/`PageSidebar`——零现有 chrome 继承,零布局耦合。
- **独立样式**:新建 `src/pages/site-studio/site-studio.css`(自建 design tokens:深空灰底 `#0f1117` 系、靛蓝/青渐变强调色、玻璃拟态面板、细腻边框与阴影、系统字体栈),所有类名 `site-studio-` 前缀,**只被该页面 import**,不碰 tailwind 配置、全局 CSS、主题 provider。
- **布局**:
- 顶部悬浮工具栏:站点标题 + 构建状态胶囊(成功/构建中/失败三态,带呼吸灯动画)+ 设备切换分段控件(桌面/平板/手机)+ 刷新/重新构建/新窗口打开;
- 左侧窄导航栏:site.json 的页面列表(图标 + 标题,点击设 iframe hash 切页),可折叠成纯图标模式;
- 中央:**浏览器壳**造型(圆角 + 顶部仿 URL 地址条 + 红黄绿窗控点缀)内嵌 iframe,设备切换时宽度平滑过渡动画;
- 底部可折叠**构建控制台**(等宽字体日志、错误红色高亮、构建时长);
- 加载/失败/未启用态都有设计过的插画式空状态(纯 CSS/SVG,不引组件库新依赖)。
- 登录守卫:轻量检查 stored auth,缺失跳 `#/login`(复用既有跳转工具,不动鉴权体系)。
## 二、降级与故障隔离矩阵(部署失败不影响已有功能)
| 故障点 | 行为 | 对现有功能影响 |
|---|---|---|
| config `web_app.enabled: false`(默认关) | capability 返回不可用;入口按钮不渲染;技能第 0 步探测失败即回退单页 HTML | 无 |
| 模板目录/node_modules 未预置(发版遗漏) | 同上:capability 四项检查(配置/模板/依赖/node 二进制)逐项报告原因 | 无 |
| sandbox.mounts 加了模板挂载但目录缺失 | 已核实 `local_sandbox_provider.py:81-90` 对不存在宿主路径**静默跳过仅告警**;Aio provider 同样按"存在才挂"实现 | 无(沿用既有容错语义) |
| vite build 失败/超时 | 子进程隔离(`asyncio.to_thread` + 超时强杀 + 每站点并发锁),错误收进 JSON;预览页显示日志;智能体读报错自修复 | 无(不进 gateway 请求主链路) |
| 签名 URL 过期/伪造 | 403 JSON,页面自动重签 | 无 |
| 前端新页面任何异常 | 独立路由独立样式,最坏就是该页白屏,error boundary 兜底 | 无 |
| 产物面板入口按钮 | 唯一改动点:capability 请求失败/不可用 → 按钮不渲染,**原 UI 一字不变** | 无 |
| 新 router 注册 | 纯增量 include_router,异常全部在路由内转结构化响应 | 无 |
技能回退语义(关键):SKILL.md 第 0 步强制探测 `ls /mnt/web-app-template/scripts/build_site.py`——探测失败时明确指示"改用现有方式产出单个 xxx.html",保证部署不完整时智能体产物质量与今天一致。
## P1 站点模板(`offline-backend-20260512/backend/templates/web-app-template/`,源码入库,node_modules gitignore 由发版仓库预置)
- Vite + React + react-router-dom(**HashRouter**:静态托管路径下无需服务端 fallback)+ **纯 CSS design tokens**(不用 Tailwind v4,规避 oklch 在 chrome100 老内核失效)+ build target es2018。
- 依赖:react 系、@xyflow/react、maplibre-gl、echarts、mermaid、pmtiles、vite/@vitejs/plugin-react(lockfile 锁死版本)。
- 内置组件:`AppShell`(读 site.json 导航)、`FlowCanvas`(xyflow 流程图封装,自动分层布局)、`GeoMap`(**默认无底图模式**:背景 + GeoJSON 点线面离线开箱即用;site.json 可配 tilesUrl/PMTiles)、`ChartPanel`(echarts)、`MermaidBlock`(静态图)。
- `router.tsx` 用 `import.meta.glob('../pages/*.tsx')` 自动注册路由——智能体丢页面文件即生效,零手动接线。
- `site.json`:`{ title, description, nav[], map:{tilesUrl?, tilesType?, pmtiles?}, theme? }`。
- Makefile 加 `make webapp-template`(开发机一次性 pnpm install);发版仓库构建脚本负责镜像内预置 node_modules(文档写明)。
- config.yaml `sandbox.mounts` 增模板只读挂载(`templates/web-app-template → /mnt/web-app-template`,缺失自动跳过已验证);AioSandboxProvider `_get_thread_mounts` 小扩展:同样"存在才挂"地补读 config 自定义 mounts。
## P2 构建脚本 + 技能包(`skills/public/web-app/`)
- `SKILL.md`:触发词(多页面网站/站点/web app/带流程图或地图的网页);工作流 = ①可用性探测(失败即回退单页 HTML)②scaffold ③配置 site.json ④写页面 ⑤数据放 public/ ⑥build(失败读 JSON 报错修复重试 ≤3)⑦present_files site.json ⑧引导点「站点预览」;硬约束:不手写 dist、一律 `/mnt/user-data/...` 虚拟路径。
- `scripts/build_site.py`(纯标准库):`check`(能力探测 JSON)/ `scaffold`(copytree 忽略 node_modules、dist)/ `build`(定位 node → 软链 node_modules 进站点:Linux symlink、Windows `mklink /J` junction 免管理员 → `node vite.js build` → 结构化 JSON 输出,180s 超时)/ `status`。
- 模板定位顺序:env `DEERFLOW_WEBAPP_TEMPLATE` → `/mnt/web-app-template`(沙箱内)→ 后端相对路径(宿主侧)。
## P3 后端:纯引擎 + 路由(全部能力开关保护)
- `packages/harness/deerflow/webapp/`(纯引擎,不 import app.*,对齐 `address_scan.py` 先例):站点发现(outputs/* 下 site.json + dist 状态)、构建执行(子进程 + 锁 + 超时)、HMAC token(`tid|site|exp`,密钥首用生成持久化 `{base_dir}/.web-app-secret` 0600)。
- `app/gateway/routers/sites.py`(app.py 注册):
- `GET /api/web-app/capability`:enabled + 模板 + node_modules + node 四项检查,逐项给原因(前端与技能共用);
- 列表/build/build-status/preview-url:`@require_permission("threads", "read", owner_check=True)`,能力不可用时返回结构化 `{"available": false, "reason"}` 而非 5xx;
- `GET /api/public/site-preview/{token}/{path}`:验签 + 过期 → `aresolve_thread_virtual_path` 且**限定站点 dist/ 根**(复用遍历防护)→ 正确 MIME(.js/.mjs→text/javascript、.geojson→application/json、.wasm)→ **HTML inline** + `CSP: default-src 'self' data: blob:; worker-src 'self' blob:`(maplibre worker 需要)+ `X-Content-Type-Options: nosniff` + `Cache-Control: no-cache` + ETag(mtime)。
- config.yaml + `deerflow/config/configuration.py` 加 `web_app:`(enabled **默认 false**、build_timeout、token_ttl、template_dir、node 覆盖)——与 `workflows` 同款总开关模式。
- 测试 `tests/test_web_app_sites.py`:capability 降级路径、token 签发/过期/伪造、路径穿越拒绝、MIME、站点发现(构建子进程 mock)。
## P4 前端
- `src/core/sites/api.ts`:capability/sites/build/status/mintPreviewUrl + TanStack Query hooks(照 `light-apps.ts` 模式)。
- `src/pages/site-studio/SiteStudioPage.tsx` + `site-studio.css` + `App.tsx` 注册顶层路由 `/sites/:threadId/:siteName`(独立风格见第一节;iframe `sandbox="allow-scripts allow-forms allow-popups"` 无 allow-same-origin,延续既有安全契约;token 过期自动重签;设备切换;重新构建 → 轮询 build-status → cache-bust 刷新)。
- `artifact-file-detail.tsx` **唯一既有文件改动**:产物路径以 site.json 结尾且 capability 可用时渲染「打开站点预览」按钮(navigate 到新路由);capability 请求失败/超时一律不渲染,原 UI 不变。
- token URL 构造等纯函数补单测;`pnpm typecheck` 通过。
## P5 测试与文档
- 后端 `make test` + ruff;前端 typecheck + 单测;`build_site.py` 本机真实跑通 scaffold→build→静态服务器自测 dist。
- 文档:AGENTS.md 增「Web App 产物」小节(含降级矩阵);发版文档补 node_modules 预置说明。
## 安全边界
短时效签名 URL(默认 2h)只覆盖该站点 dist/ 静态读;dist/ 根限定 + 复用 `aresolve_thread_virtual_path` 遍历防护;HTML inline 仅在 site-preview 路径开放(不影响 artifacts 接口既有强制 attachment);CSP + nosniff + iframe sandbox 三重纵深。
## 二期(本期不做)
源码编辑 + 保存即重建;跨对话站点 gallery;PMTiles 底图资产打包;`visualizations/` 内联图表(进行中,互补不冲突)的底图配置统一(其 map-renderer.tsx 硬编码 Google 瓦片属离线隐患,另行处理)。
## 风险
- Windows 开发机 junction 链接免管理员,node 前端机必有;生产镜像已含 nodejs+npm。
- Aio provider 自定义 mounts 扩展是小改动,按"存在才挂"实现即可,不影响现有挂载。
- dist/ 出现在产物目录扫描(文件噪音)——一期保持现状,二期优化。