Files
Agent-System-Documents/docs/proposals/2026-09-30-file-preview-and-reserved-capabilities.md
T
weichen 4f3b22d601 Record the user adjudication of the preview decision points
Why: D1-D5 were settled by the user (deps in, full-fidelity pptx, OG
card plus optional embed, dual placement); the proposal status must
reflect that before implementation starts.

Evidence: decision section rewritten with the settled choices and the
accepted trade-off on D2 (fall back to outline level with evidence only
if no Vite-compatible renderer builds).

Impact: documentation only.

Refs: task feature/file-preview-caps.
2026-09-30 16:15:41 +00:00

8.8 KiB
Raw Blame History

文件预览与保留能力(工件删除 / fs 工具)方案

日期:2026-09-30;状态:已裁定(§5),实施任务 feature/file-preview-caps;作者:weichen + Agent 关联:2026-09-30-ui-redesign-and-auth.md §9(上轮"下载为主"的非目标,本提案补齐) 任务:feature/file-preview-proposal(本仓 PR 见任务卡)

1. 背景与诉求(用户原话归纳)

  1. 文件预览补齐:PDF、Word、PowerPoint、Excel、图像、markdown、代码文件等要有预览;可考虑做网页链接的预览;落位可以考虑右侧栏,或系统提供的弹出窗口。
  2. 工件删除:接口和功能加入,但系统先不提供此类操作——保留能力,不暴露入口。
  3. fs 类工具:同样实现,但不提供(默认不可用)。

上轮交付后现状(chat-order-artifact-files,PR#71):

  • DocViewer(OverlayHost 单层):markdown 渲染、图片、pdf iframe、csv/json 等宽、二进制信息卡+下载;docx/xlsx/pptx 仅下载(当时定为非目标)。
  • DELETE /api/artifacts/{id} 后端端点已存在,前端工件面板有行内删除按钮(二次确认)。
  • 后端无任何 fs 类工具;文件面唯一入口是 artifacts(上传 + write_artifact)。

2. 合理性评估(逐条)

2.1 文件预览 —— 合理,方向正确;两点边界须裁定

赞同:

  • 预览是上轮"工作区文件管理"的自然完成项,闭环"浏览/显示/下载/回看"中的"显示"。
  • 落位思路与系统架构一致:预览应做成 @ui 能力组件(FilePreview),右栏轻预览与弹窗深预览复用同一组件——契合能力平台原则(系统基本功能做完善,插件直接复用)。

边界与风险:

  • docx/xlsx 的浏览器端解析必须引入前端依赖(mammoth / SheetJS),突破此前"无新依赖"约定——需 D1 裁定。缓解:全部 dynamic import() 按需加载,不进主 bundle,不预览不下载解析器。
  • pptx 没有轻量高保真方案(社区库重且还原度差)。推荐降级为文本大纲级预览(JSZip 解 slide XML,逐页标题+正文),全保真不做(D2)。
  • 网页链接预览:iframe 直嵌会被多数站点的 X-Frame-Options/CSP 拒绝,不可靠。推荐 OG 元数据卡为主 + 可选"尝试内嵌"(D3)。服务端抓取属 SSRF 高危面,硬边界:仅 http/https、解析后拒绝 localhost/环回/私有/保留地址、重定向逐跳同校验、超时与响应体上限。

2.2 工件删除:保留能力、不暴露入口 —— 合理,成本极低

  • 后端端点已存在(无需新接口);动作是回退上轮加的行内删除按钮,保留 api.deleteArtifact 前端方法(插件可用)。
  • 与 API-first 原则完全一致:能力在公开 API,UI 只是第一个客户端;将来要暴露可走设置开关或管理端,不需要任何后端改动。
  • 文档与代码注释标注"保留能力,默认不暴露",防止后续误当死代码清理。

2.3 fs 工具:实现但不提供 —— 合理,契合能力预留;三点设计约束

赞同:实现合入(有测试、有沙箱、有门控),默认关闭=模型不可见、不进 /api/tools,插件/配置可开启。这正是"为插件系统预留能力"的形态。

约束(缺一不可):

  1. 门控:设置 runtime.fsTools(默认 false)。关闭时不注册工具——/api/tools 列表、模型可见工具集均不含;开启后仍受画像 tools.allow/deny/confirm 约束(现有机制)。
  2. 沙箱:根目录 <DataDir>/workspaces/<owner>/<conversationId>/;路径规范化(filepath.Clean + Rel 校验)后必须仍在根内,拒绝 ..、绝对路径与符号链接逃逸;单文件读写上限(如 2MB/1MB);list 深度与条数上限。
  3. 不变式:write_file 落盘后同步登记 artifact(kind file,内容指针指向沙箱文件)——保住上轮确立的"工作区文件 = 工件面板可见可下载"不变式;read/list 仅限沙箱内。
  4. 测试:路径穿越负例(../、绝对路径、URL 编码变体、符号链接)、上限、门控正负例。

3. 设计方案

3.1 FilePreview 能力组件(modules/ui)

FilePreview({ source, compact? })
source = { kind:'artifact', id, name, mime?, contentUrl, downloadUrl }
       | { kind:'url', href }
  • 按扩展名/mime 分派内部渲染器;渲染器注册表 registerFilePreview(ext, renderer) 预留插件自定义预览能力。
  • 格式矩阵(v1):
格式 方案 新依赖
md/txt marked 渲染(现成) 无
图像 <img> + 点击放大 无
pdf 同源 iframe(现成) 无
csv/json/日志 等宽 pre(现成) 无
代码文件(go/py/ts/js/rs/java/sh/sql/yaml/toml/html/css 等) 等宽 + 行号(不做语法高亮;高亮若做需 highlight.js,另行裁定) 无
docx mammoth → HTML(样式收敛进 tokens) mammoth(动态)
xlsx/xls SheetJS → 表格(sheet 切换,默认首 200 行分页) xlsx(动态)
pptx JSZip 解 slide XML → 逐页文本大纲卡 jszip(动态)
url OG 卡(标题/描述/站点/图标/首图)+「尝试内嵌」按钮(iframe 加载失败回退提示) 服务端 /api/url-preview
  • 双落位(D4):右栏新增"预览"页签(compact 模式:窄栏排版,边聊边看,可随消息工件 chip 一键换内容)+ OverlayHost 深预览(现 DocViewer 收敛为 FilePreview 的容器壳:工具栏=编辑/保存/下载/关闭)。两处同一组件、同一注册表。

3.2 后端(modules/backend)

  • GET /api/url-preview?url=:SSRF 硬边界(见 §2.1);返回 {title, description, siteName, favicon, image, finalUrl};解析 og:title/og:description/og:image/og:site_name,回退 <title>/meta description。5s 超时、256KB 截断、Content-Type 须 text/html。缓存(内存 LRU,15 分钟)。
  • fs 工具族 internal/tools/fs.go:fs_read_file / fs_write_file / fs_list_dir,参数与返回结构走既有工具契约;门控与沙箱见 §2.3。
  • 工件删除:零后端改动。

3.3 前端(modules/frontend)

  • DocViewer 收敛为 FilePreview 容器(删自有格式分支);右栏 Workbench 增"预览"页签 + store.previewSource。
  • 消息工件 chip 与 ArtifactsPanel 行提供两个入口:默认弹窗深预览,"右栏"按钮轻预览;移除删除按钮(保留 api 方法与注释说明)。
  • URL 入口(P3):会话输入框粘贴纯 URL → 消息侧生成链接预览卡(点击进右栏/弹窗预览);插件卡 doc-ref 同样可指向 url 工件。

3.4 依赖与体积

mammoth + xlsx + jszip 全部动态 import,仅对应格式首次预览时加载;主 bundle 不变。版本随 frontend package-lock 锁定。

4. 分期与验收

期 内容 验收
P1 预览基座 FilePreview 组件+注册表;双落位;md/图片/pdf/csv/json/代码文件迁移收敛;删除按钮回退 @ui vitest(组件矩阵);头链:右栏预览页签+弹窗各断言;面板无删除按钮、API 仍在
P2 Office docx/xlsx/pptx 渲染器(动态加载) 样例文件矩阵头链断言(docx 出正文、xlsx 出表格、pptx 出大纲);懒加载不进主 bundle(构建产物体积对比)
P3 URL 预览 /api/url-preview + LinkPreview + 输入框 URL 卡 go 测试:OG 解析正例 + SSRF 负例(localhost/私网/非 http 协议/超时);头链:链接卡→右栏预览
P4 fs 工具 实现+门控+沙箱+测试(默认关) go 测试:穿越负例全红、门控关时不注册;开启后 write_file 产物出现在工件面板

5. 决策点(2026-09-30 用户已裁定)

  • D1 Office 依赖:✅ 引入 mammoth/SheetJS(动态加载,不进主包)。
  • D2 pptx 深度:✅ 全保真渲染(用户选择;接受重型依赖与还原度折衷——实施时在 pptx-preview / pptx2html 候选中取与 Vite 兼容者,若均不可构建则带回证据退回文本大纲级再裁定)。
  • D3 URL 预览形态:✅ OG 卡 + 可选尝试内嵌。
  • D4 预览落位:✅ 右栏 + 弹窗双落位。
  • D5 fs 开关可见性:按推荐默认执行——仅配置/环境可开(runtime.fsTools 默认 false),设置页不显示开关。
  • 实施任务:feature/file-preview-caps(P1–P4 一体交付)。

6. 边界与不做

  • 不做 Office 文件的在线编辑(预览只读;md/txt/csv/json 编辑能力维持现状)。
  • 不做语法高亮(如需引入 highlight.js 另行裁定)。
  • 不做服务端格式转换(LibreOffice/onlyoffice 等违反单二进制非容器化部署约束)。
  • 删除能力不做确认流之外的新入口形态;fs 工具不做 shell/exec 类操作(只做文件读写列目录)。