Bundle
dsh-excel-viewer
Spreadsheet preview for the DeepSeek Harness web client: .xlsx, .xlsm, .xls, .csv and .tsv files open as a read-only grid in the right Sidebar instead of the unsupported-file placeholder
- Source
- HaydenSmith1121
- License
- MIT
- Updated
- Updated 20 hours ago
Readme
# dsh-excel-viewer
让 **Excel 文件在 DeepSeek Harness 里直接打开**:在右侧 Sidebar 的文件树里点一个
`.xlsx` / `.xlsm` / `.xls` / `.csv` / `.tsv`,不再看到「不支持预览」的空态,而是
一张**只读表格** —— 多工作表切换、行列号、格式化后的数值与日期、合并单元格、双向滚动。
- 适配 dsh 运行时:**`0.1.6-alpha.1`**
- 形态:**纯客户端渲染器 + 空 host 半**(host 侧不读文件、不注册工具)
- 许可:MIT(内联了一份 Apache-2.0 的 SheetJS,见 [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md))
---
## 一、它是怎么接进去的
Harness 的右侧文档预览本来就为「按后缀接管渲染」留了公开扩展点:
`@deepseek-ai/dsh-client-ui-sidebar-documentpreview` 提供一个注册表
(`ctx.documentPreviews`)和一个键控子 slot(`sidebar.right.tab.document`)。
本插件只回答那个包提出的两个问题:
```js
// 1) 认领哪些后缀 —— extension 档位,比兜底的纯文本类型优先
ctx.documentPreviews.register({
id: 'dsh-excel-viewer/sheet',
extensions: ['xlsx', 'xlsm', 'xls', 'csv', 'tsv'],
binaryExtensions: ['xlsx', 'xlsm', 'xls'], // 取消这些后缀的「纯文本」兜底
priority: 'extension',
title: () => t('title'),
loading: 'bytes-complete', // 工作表必须先拿到完整字节才能排版
wrap: false,
})
// 2) 画什么 —— 与元数据同 id 的 keyed body
ctx.slots.register({ name: 'sidebar.right.tab.document', key: 'dsh-excel-viewer/sheet', locale: NS }, ExcelBody)
```
**其它一切都不归本插件管**:tab 怎么开、地址怎么解析、文件怎么读、失败行怎么画、
查看器下拉里有哪些候选,全部仍由上游那个包拥有。本插件既不碰 Sidebar 的 store,
也不新增读取路径 —— 字节是 owner 自己经 `workspaceFiles` Remote 读完递进来的,
所以「这个会话能不能读这个文件」只有一个答案(也就没有第二个权限判断)。
> 这也是 host 半是空的原因:没有第二个读路径,就没有 host 侧的事可做。那个模块存在
> 只为让 Loader 有一行可 import 的入口 —— 客户端的 bundle 是靠扫描宿主 Loader 行里
> 声明了 `dsh.client` 的包才被发现的。
---
## 二、支持的格式与各自的读法
| 后缀 | 容器 / 编码 | 读法 | 纯文本兜底 |
|---|---|---|---|
| `.xlsx` | OOXML(ZIP + XML) | SheetJS `read(type:'array')` | 关闭(二进制) |
| `.xlsm` | 同上 + VBA part | 同上 | 关闭(二进制) |
| `.xls` | OLE2 / BIFF8 | 同上(代码页表已随 bundle 内联) | 关闭(二进制) |
| `.csv` | 文本 | 严格 UTF-8 → 失败则 GB18030 → 再失败则代码页 936 | **保留**(可切回纯文本) |
| `.tsv` | 文本(制表符分隔) | 同上,`FS: '\t'` | **保留** |
两条与「看得见」直接相关的实现选择:
- **二进制后缀先验文件头**。SheetJS 对认不出的内容会退回文本解析 —— 一个改了后缀的
`.txt` 会被它当成单列 CSV,于是「一张看着像表格的错答案」。所以 `.xlsx/.xlsm/.xls`
先检查 ZIP / OLE2 / BIFF8 文件头,不匹配就直接给出「这不是一个有效的工作簿文件」。
- **中文 CSV 走平台解码器**。Excel 在中文 Windows 上默认存 GBK:严格 UTF-8 解码失败后
用平台的 `TextDecoder('gb18030')` 再解一次,因此不会出现 `ÇøÓò` 这种mojibake。
---
## 三、安装与使用
```bash
dsh plugin --profile web add dsh-excel-viewer-0.1.0.tgz
# 重启该 profile(dsh web 不能同时起两次)
```
重启后**不需要任何设置**:打开会话右侧 Sidebar 的 **文件** 页,展开目录,点一个
表格文件即可。
- 多工作表 → 顶部出现工作表标签,点击切换;
- 数值按工作簿自带格式显示(`2,340.00`、`35.12%`、`2024-01-31`);
- 合并单元格按 `rowspan` / `colspan` 呈现;
- 长文本按列宽裁切并带省略号,鼠标悬停有完整 `title`;
- 想换回纯文本看原始内容 → 用 tab 头部的查看器下拉(仅 `.csv` / `.tsv` 提供)。
---
## 四、两个渲染上限(都会写在页脚,不静默截断)
| 上限 | 值 | 为什么 |
|---|---|---|
| 模型层:每张表解析的行 × 列 | 2000 × 200 | 表格不是文本:没有「下一页」可读,排一次版就要决定留下多少 |
| 视图层:首次挂载行数 / 每次追加 | 300 行 / +500 行 | 一个 `<td>` 就是一个 DOM 节点,2000 行的宽表是 20 万节点;预览面板不该冻住整个窗口 |
页脚始终显示 `共 N 行 × M 列`,被截断时追加 `仅渲染前 X 行、前 Y 列`,
行还没挂完时给一个 `再显示 500 行` 按钮。
---
## 五、开发
```bash
npm install # 会从 SheetJS 官方 CDN 取 xlsx(见 THIRD_PARTY_NOTICES.md)
node scripts/build.mjs # src/ → lib/(host 半直拷;client 半 esbuild 打包 + 内联 SheetJS)
node scripts/make-fixtures.mjs # 生成 fixtures/ 下的测试样本
node scripts/test-model.mjs # 解析/排版层:83 条断言
node scripts/test-client.mjs # 客户端 bundle:57 条断言(含 react-dom/server 实渲染)
node scripts/pack.mjs # 产出 dist/dsh-excel-viewer-0.1.0.tgz
```
入库登记不是手改:`node scripts/register-in-repo.mjs <dsh-plugins 仓库路径>` 会一次性补齐
`compatibility.json`、`README.md`(六处)与 `profile-config/profile-bundles.yaml`,**然后自查**
(bundle 数组必须等于 in-box bundles 按序接上插件列表、每个 tarball 必须真实存在),
并保持各文件原有的行尾与缩进;`--check` 只报告不落盘。手改这四个地方正是本仓库最容易出的那类错。
构建脚本自带两道闸门,因为这两类错误在 GUI 里表现为「什么都不发生」:
- 打包后扫描 `require(...)`,出现平台模块表提供不了的 specifier 就**构建失败**
(浏览器只认 `react` 与 `@deepseek-ai/*`,其余一律必须内联);
- 注册 id 与包名对不上时直接拒绝产出。
`scripts/test-client.mjs` 按浏览器的方式加载 `lib/client.js`(`window.__ModuleLoader__.load`),
用一个只会答 `react` 的 `require` 把 bundle 实例化,再用桩 ctx 调 `apply`,最后用
`react-dom/server` 把每个 fixture 渲染成 HTML 逐格断言 —— 因此「注册错 id」「少内联了
东西」「表格画错」都在 Node 里就暴露,不用等到开浏览器。
---
## 六、目录
```none
dsh-excel-viewer/
├─ src/
│ ├─ index.js # host 半(空 —— 见第一节)
│ ├─ client.js # 客户端半:注册渲染器元数据 + keyed body
│ ├─ body.js # 表格视图(纯函数:字节进,HTML 出)
│ ├─ sheet-model.js # 解析与排版(纯 JS,Node 与浏览器共用同一份)
│ └─ styles.js # 样式表(作为 <style data-plugin-css> 注入,卸载时移除)
├─ scripts/ # build / fixtures / tests / pack
├─ fixtures/ # 生成的测试样本(不入 tarball)
└─ lib/ # 构建产物:index.js + client.js(含内联 SheetJS)
```
Install
dsh plugin --profile web add github:HaydenSmith1121/dsh-excel-viewer
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-excel-viewer from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.