31aa1608af
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4.1 KiB
4.1 KiB
HTML 文件 Preview/Source 切换 — 设计
日期:2026-08-08
背景
Workspace 文件对话框(WorkspaceFileDialog.tsx)打开 .html 文件时只显示源码(SyntaxHighlighter)。服务端有意以 text/plain + Content-Security-Policy: sandbox + nosniff 返回 HTML(src/app/api/workspace/file/route.ts:53),防止直接打开时渲染执行。
需求
- HTML 文件增加 Preview | Source 切换,默认 Preview。
- Preview 渲染页面(允许 JavaScript,plotly/echarts 交互图表可用)。
- Source 为现有源码高亮视图;现有 Edit(textarea 编辑 + Save)保持不变。
- 同目录相对资源(CSS/JS/图片)在 Preview 中可解析。
设计
1. 服务端:?render=1 渲染模式
修改 src/app/api/workspace/file/route.ts 的 GET:
- 新参数
render=1,仅当解析出的扩展名为html时生效;其他扩展名忽略该参数。 - render 模式下:
Content-Type: text/html; charset=utf-8Content-Security-Policy: sandbox allow-scripts(允许脚本;不授予allow-same-origin,脚本运行在 opaque origin,无法读取应用 cookie/DOM,其 fetch 会被 API 的跨源与会话检查拒绝)Content-Disposition: inline- 保留
Cache-Control: no-store;不加X-Content-Type-Options: nosniff以外的变化
?download=1优先于render=1(仍为 attachment、text/plain)。- 非 render 模式行为完全不变(HTML 仍 text/plain +
sandbox)。
相对资源 MIME 修正:同目录 .css/.js 当前以 text/plain + nosniff 返回,浏览器在渲染页面中会拒绝应用/执行。CONTENT_TYPES 中:
css:text/plain→text/css; charset=utf-8js:text/plain→text/javascript; charset=utf-8
这两个扩展仍受 CSP sandbox(直接打开时脚本不执行)与既有同源检查保护;图片扩展(png/jpg/…)已是正确 MIME,无需改动。
2. 对话框 UI(src/app/components/WorkspaceFileDialog.tsx)
- 仅对
ext === "html"且kind === "text"且未超大小限制的文件启用。 - 新 state:
htmlView: "preview" | "source",默认"preview";useEffect在path/threadId变化时重置为"preview"。 - 头部工具栏(现有 Edit/Download/Delete 之前)增加分段切换:Preview | Source 两个 ghost Button,激活态用
bg-accent类区分;进入编辑模式时隐藏该切换(编辑已有 Cancel/Save)。 - Preview 视图:
<iframe src={workspaceFileUrl(threadId, path) + "&render=1"} sandbox="allow-scripts" title={name} className="h-full w-full rounded-md border border-border" /> - Source 视图:现有 SyntaxHighlighter 分支(对 html 文件从默认改为仅
htmlView === "source"时显示)。 - 保存成功后
setEditing(false)且htmlView回到"preview",iframe 以key={content}(或保存计数)强制重新加载以显示新内容。 - 切换状态不持久化,每次打开文件重置为 Preview。
3. 安全边界
render=1仅限 GET、仅.html;isCrossOrigin检查已有。- iframe
sandbox="allow-scripts"(不含allow-same-origin/allow-forms/allow-modals/allow-popups),与服务端 CSP 双重限制。 - 渲染的 HTML 来自用户/agent 自己的工作区文件,与现有图片/PDF iframe 预览的信任级别一致。
错误处理
- render 模式读取失败沿用现有错误 JSON(400/403/404)。
- Preview iframe 加载失败(文件被删等)由浏览器显示空 iframe;Source 视图的错误展示不变。
测试
src/app/api/workspace/file/route.test.ts(新建或扩展,参照现有路由测试模式):render=1+.html→ 200、text/html、CSPsandbox allow-scripts、inlinerender=1+.md→ 忽略 render,行为不变render=1&download=1+.html→ download 优先(attachment、text/plain)- 不带 render 的
.html→ 仍text/plain+ CSPsandbox .css/.js新 MIME
- UI 手动验证:打开含 plotly 的 HTML → 默认渲染、交互可用;切 Source → 源码;Edit 保存 → 回到 Preview 且内容更新;相对 CSS/JS/图片正常加载。