dsh-latex
让 harness 直接调用本机 LaTeX,并让 DeepSeek 的回复以公式块展示数学。
装好之后有两件事发生:
- 聊天里的公式直接渲染成排版好的公式块。 助手回复中的
$...$(行内)和$$...$$(独立成块)由聊天界面内置的 KaTeX 渲染,并带有\(...\)/\[...\]两种等价写法。这部分不需要插件做任何事,插件负责的是 下面的第 2 点,以及用一段 system prompt 让模型知道"该这样写"。 - 多出一个
latex工具,真正调用本机 TeX Live 编译:公式 → SVG/PNG/PDF, 整篇文档 → PDF。它是"要真产物"的路径(嵌进论文、贴进 Keynote、生成 PDF), 而第 1 点是"看着舒服"的路径。

上面这张图就是 latex 工具的产物
(\frac{\partial L}{\partial \theta}=\frac{1}{n}\sum_{i=1}^{n}\nabla_\theta\ell(x_i,y_i;\theta)),
矢量版见 assets/example-formula.svg。
依赖
- TeX Live / MacTeX。macOS 上
/Library/TeX/texbin里的latex、dvisvgm、dvipng、dvipdfmx、pdfcrop、xelatex。 - 没装也没关系:Web UI 会在输入框上方提示,并给一个一键安装按钮(见下文)。
- 不需要 client 构建,也不需要 npm 依赖。
插件自己会在 PATH、/usr/local/texlive/<年>/bin/<arch>、/Library/TeX/texbin
里找这些程序,并在子进程里重排 PATH——桌面端 Electron 主进程经常拿不到
/Library/TeX/texbin,这一步不能省。
安装
DSH 里安装 bundle 统一走插件管理(不是 shell 里的 pnpm,也不要手改 profile 的
package.json):
plugin_manager action=install_bundle target=<这个目录的绝对路径>
从 GitHub 装:
git clone https://github.com/Accelerater-tech/dsh-latex.git
然后在 DSH 里对着克隆出来的目录跑同一条 install_bundle。它会把包装进 profile
的 node_modules 并写入 bundle patch,返回里 application: "applied" 就说明已经
生效——纯 host 插件,不用构建,也不用重启。
装完可以直接验证:新的一轮系统提示里会出现 ## LaTeX 段落,工具表里会多出
latex。
包里沿用本地私有名
@local/dsh-latex(不会发布到 npm)。要换成自己的名字, 同时改package.json和cordis.patch.yml里的name即可。 卸载:plugin_manager action=remove_bundle target=@local/dsh-latex。
用法
1. 聊天里写公式
行内:$e^{i\pi}+1=0$
独立:$$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$
多行:$$\begin{aligned} (a+b)^2 &= a^2+2ab+b^2 \\ &= b^2+2ab+a^2 \end{aligned}$$
KaTeX 渲染失败时会退回红色错误文本,不会把整条消息搞坏。
2. 调工具编译
// 公式 → 矢量 SVG(默认)
{ "source": "\\frac{\\partial L}{\\partial \\theta} = \\nabla_\\theta L" }
// 公式 → 300dpi PNG,文件名自己定
{ "source": "\\sum_{k=1}^{n} k = \\frac{n(n+1)}{2}", "format": "png", "output": "figures/sum.png" }
// 深色背景用的浅色墨
{ "source": "E = mc^2", "color": "#e5e7eb" }
// 整篇文档 → PDF(正文片段,自动套 preamble)
{ "mode": "document", "source": "\\section{结论}\n由上式可得 $x>0$ 时 $\\sqrt{x^2}=x$。" }
// 整篇文档 → PDF(已经是完整 .tex,原样编译)
{ "mode": "document", "source": "\\documentclass{ctexart}\n..." }
返回:
{
"ok": true,
"mode": "math",
"format": "svg",
"engine": "latex",
"files": [{ "path": "/abs/path/.dsh-latex/xxx.svg", "relative": ".dsh-latex/xxx.svg", "format": "svg", "bytes": 5603 }],
"cwd": "/abs/path",
"markdown": "",
"texSource": "完整可复现的 .tex",
"durationMs": 417
}
markdown 是可直接粘进回复的相对路径引用:图片在聊天里会内联显示
(api/file?path=...),文档则是一个打开侧栏的文件链接。相对路径按
当前会话的工作目录解析。
参数
| 参数 | 适用范围 | 说明 |
|---|---|---|
source |
必填 | mode:"math" 时是公式体(外层 $$/\[\] 会被剥掉);mode:"document" 时是完整 .tex 或正文片段 |
mode |
math(默认)/ document |
|
format |
math | svg(默认,矢量)/ png / pdf |
engine |
document | pdflatex / xelatex / lualatex;缺省时按是否含中文自动选 |
preamble |
插入 \begin{document} 之前的额外宏包行,例如 \usepackage{bm} |
|
displayMode |
math | 默认 true;false 时按行内公式排版 |
color |
math | 墨色,#rrggbb 或颜色名,默认黑色 |
dpi |
math png | 96–1200,默认 300 |
output |
输出路径(绝对或相对会话目录) | |
documentClass / classOptions |
document | 包裹正文片段时用的文档类与选项 |
maxPasses |
document | 最多 5 遍,只在日志要求重跑时继续 |
force |
math | 忽略缓存重新编译 |
配置
在 bundle patch 的行里可以传:
- insert:
- id: dsh-latex
name: '@local/dsh-latex'
config:
defaultFormat: svg # svg | png | pdf
defaultEngine: xelatex # pdflatex | xelatex | lualatex
dpi: 300
timeoutMs: 120000 # 单次编译超时
promptSection: true # 是否注入 LaTeX 写法提示
tool: true # 是否注册 latex 工具
环境检测与一键安装
插件启动时会扫一遍本机的 TeX 工具链。没找到 latex / dvisvgm 时,Web UI 会在
输入框上方浮出一条提示,带一个直接可点的安装按钮;TeX 正常时这条提示不会出现,
不留任何常驻 UI。
| 方式 | 适用 | 行为 |
|---|---|---|
| MacTeX (BasicTeX) | macOS,推荐 | 从 CTAN 镜像下 BasicTeX.pkg,再用 osascript ... with administrator privileges 弹系统密码框安装,不需要开终端 |
| TinyTeX(免密码) | macOS / Linux | 跑官方 install-bin-unix.sh,装到 ~/Library/TinyTeX,不碰系统目录 |
| Homebrew: basictex | 已装 Homebrew | brew install --cask basictex |
点下去以后界面会显示进度条和命令输出的最后几行,可以取消。装完插件会重新扫描一遍;
如果仍然找不到 latex,通常会提示重开一次 DSH(新装的 /Library/TeX/texbin
要进入应用进程的 PATH)。
想在本机已经装好 TeX 的情况下预览这个横幅,给页面 URL 加 ?latex-env=preview 即可。
浏览器端通过同源 HTTP 路由和宿主通信(由 webServer 服务注册,不需要 RPC):
GET /dsh-latex/env # 检测报告
GET /dsh-latex/install/status # 安装进度
POST /dsh-latex/install # {"strategy":"basictex"} 开始安装
POST /dsh-latex/install/cancel # 取消
GET /dsh-latex/client/alive # 浏览器半自检信标(只写诊断日志,返回 204)
除自检信标外,其余路由都校验 Origin,跨站请求直接 403。
宿主代码改完由 HMR 直接热加载,不用重开 DSH;client 半的改动需要刷新一次窗口。
排查"面板不出现"先看诊断日志:
<tmpdir>/dsh-latex-routes.log(macOS 上是/var/folders/.../T/dsh-latex-routes.log)。里面会依次出现apply start、inject fired (webServer=present)、每条ok /dsh-latex/...,以及浏览器半跑起来后 打的client half alive (tag=...)。哪一行缺,问题就在哪一步。
实现要点
- 产物裁白:公式用普通
article排版(\pagestyle{empty}),再由dvisvgm --bbox=min --exact-bbox、dvipng -T tight、pdfcrop裁到内容尺寸。 没有用standalone类——它在 DVI 模式下把正文塞进\hbox,display math 会报Missing $ inserted,align也排不了。 - 字形转路径:
dvisvgm --no-fonts把字形转成<path>,SVG 不依赖字体,任何 查看器里都一致(代价是文件稍大)。 - 缓存:源码 + 格式 + dpi + 墨色 的 sha256 作为作业目录名,重复调用直接复用。
- 失败信息:从
.log里只抽!、l.12、file:line:开头的诊断块,最多 24 行, 避免把几千行日志塞回上下文。 - 注册方式:工具与 prompt 段落的 disposer 没有交给
ctx.effect——loader 在apply()返回后会立刻提交 entry fiber 并执行 disposer,那样会把刚注册的工具静默注销 (这个坑在dsh-search-boost的注释里有记录)。 - 聊天的公式渲染不靠本插件:助手 markdown 的 KaTeX 是聊天界面自带的,插件只负责
把编译产物写成图片/文件引用。client 半(
client/client.js)只做环境横幅,平时不渲染。 - client 与宿主之间不用 RPC:
window.__ModuleLoader__里的host只对动态包开放, 这里改为宿主用webServer.register()注册同源路由,浏览器直接fetch。 注册挂在ctx.inject(['webServer'], ...)的局部作用域里——把webServer写进对象级inject会让整个插件在 TUI 等没有 Web 服务的宿主里永远不 apply。 webServer.register()必须包在child.effect()里:直接在inject回调里调register(),路由会在那次回调的 fiber 提交后被回收,表现是"注册时没报错、一访问就 404"。 正确写法是ctx.inject(['webServer'], child => child.effect(() => server.register(route)))。
已知限制
- 公式图片默认黑色墨迹;深色主题下要么用
color指定浅色,要么直接用聊天里的 KaTeX 渲染(自动跟随主题)。 pdfcrop走的是pdftex,首次调用会略慢。- 需要
\usepackage{...}的公式请写进preamble,不要塞在source里。
开发
node test/smoke.mjs # 不经过 harness,直接验证 TeX 管线
pnpm pack # 生成可分发 tarball
CI:每次 push 到
main都会在 GitHub Actions(Ubuntu + TeX Live)上跑同一条冒烟测试,见.github/workflows/ci.yml。
目录结构:
index.js # 插件入口:注册 latex 工具 + system prompt 段落
lib/tex.js # 引擎发现、编译管线、日志裁剪、缓存
cordis.patch.yml # bundle patch:往 profile 里插一行
locale/{en,zh}.json # 插件管理里的标题/描述
icon.svg # 插件图标
test/smoke.mjs # 冒烟测试(直接用 lib/tex.js,不依赖 harness)
No comments yet. Be the first to write one.