11 KiB
11 KiB
Web App 产物模式:多页面站点 / 流程图 / 地图(可降级 + 独立页面风格)
总体架构
让智能体在沙箱里产出完整多页面站点(site.json 清单 + src/pages + public 数据),平台统一构建(预置 node_modules,永不 npm install)与受控静态托管,前端新注册一个风格独立的「站点预览」路由页。
两条铁律(本次新增的核心原则):
- 零侵入:新页面不复用现有工作台风格/布局组件,走独立路由 + 独立样式表;对已有代码的修改仅限「一个带开关的入口按钮」。
- 可降级:沙箱/模板/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、Windowsmklink /Jjunction 免管理员 →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-secret0600)。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(独立风格见第一节;iframesandbox="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/ 出现在产物目录扫描(文件噪音)——一期保持现状,二期优化。