Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/webui.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,38 @@ implementation accident:
The `mermaid` dependency (11.12.1, MIT) is recorded in
`release/dependency-licenses.json`.

## Math formulas in Markdown (KaTeX)

Assistant messages and Markdown file previews also render math formulas,
in the same pipeline as Mermaid diagrams. Three input shapes are math;
every other use of the dollar sign stays prose:

| Shape | Written as | Rendered as | Backed by |
| --- | --- | --- | --- |
| Inline | `$E=mc^2$` | KaTeX markup inside the paragraph | `webapp/lib/math-renderer.ts` (the marked `webuiMath` inline extension) |
| Display | `$$\frac{a}{b}$$` | A centred block (`.katex-display`) | the same tokenizer, `displayMode: true` |
| Fence | ```` ```math ```` | A centred block, dispatched through the language→renderer registry — the same seam `mermaid` uses, so neither fence language can shadow the other | `webapp/lib/math-renderer.ts` (`registerMathRenderer`) |

| Aspect | Contract | Backed by |
| --- | --- | --- |
| False positives | A single `$` is math only when a closing `$` exists, the body stays on one line, and the body does not start with a digit: `costs $5 and $10`, `$HOME`, an unclosed `$\frac{` all stay prose | `webapp/lib/math-renderer.ts` (tokenizer guard) |
| Invalid formula | An input KaTeX cannot parse degrades to the **original source as code** — inline/display shapes become `<code class="inline-code">raw</code>`, a ```math fence falls back to the plain codeblock shell (the registry's existing throw path). The rest of the document is unaffected; the page never blanks | `webapp/lib/math-renderer.ts`, `webapp/lib/markdown.ts` (`safeLanguageRenderer`) |
| Sanitiser surface | KaTeX runs with `output: "html"` and emits only `span`, `svg`, `path`. The allowlist admits exactly those tags; `svg` keeps a fixed attribute set (`xmlns`, `width`, `height`, `viewBox`, `preserveAspectRatio`, `class`) with no `href`-like attribute, and `<math>`/MathML stays a DROP tag — which is precisely why HTML-only output is configured. Inline `style` survives only on `span` and only when the value clears `isSafeStyleValue`: no parentheses rules out `url(...)`/`expression(...)`, and `position`/`background`/`behavior` are refused outright | `webapp/lib/markdown.ts` (`ALLOWED_TAGS`, `ALLOWED_ATTRS`, `isSafeStyleValue`) |
| React tree | The style attribute reaches React as a parsed object (`parseInlineStyle`), because React rejects a string `style` prop outright — passing it through would silently drop all KaTeX layout | `webapp/lib/markdown.ts` (`parseInlineStyle`), `components/markdown-html.tsx` |
| Trust | KaTeX `trust` stays `false`: `\href` renders as a red warning text node, never a link, so no URL can enter the DOM through a formula | `webapp/lib/math-renderer.ts` (`KATEX_OPTIONS`) |
| Theme | Formulas are inheriting text plus CSS transforms; they need no per-theme re-render (unlike Mermaid, which repaints on the theme flip) and pick up both themes' text colours from the design tokens | `webapp/styles/katex.css` |
| CSS + fonts | `webapp/styles/katex.css` is vendored from `katex/dist/katex.min.css` (the same version as the `katex` devDependency) with the `@font-face` sources repointed at the vendored fonts in `webapp/public/fonts/katex/` (60 font files + the MIT license notice). The stylesheet is loaded unconditionally from `app/layout.tsx` (~24 KB); fonts are served from `/fonts/katex/…` | `app/layout.tsx`, `webapp/styles/katex.css`, `webapp/public/fonts/katex/` |
| Bundle cost | `katex` JS is in the client bundle, not lazy-loaded the way Mermaid is: the math pipeline is synchronous string rendering (`renderToString`), and an inline `$…$` can appear mid-sentence. Accepted as a known cost; the lazy-load lever exists only if bundle budgets demand it | `webapp/lib/math-renderer.ts` |

Upgrading `katex` regenerates both halves in the same commit: replace the
files in `webapp/public/fonts/katex/` from the new `dist/fonts/`, and
regenerate the stylesheet with
`sed 's|url(fonts/|url(/fonts/katex/|g' node_modules/katex/dist/katex.min.css
> webapp/styles/katex.css`.

The `katex` dependency (0.18.7, MIT) is recorded in
`release/dependency-licenses.json`.

## File preview toolbar and Markdown outline (slice 27)

The preview component's header carries three controls, and the Markdown
Expand Down
45 changes: 45 additions & 0 deletions docs/webui.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,51 @@ flowchart LR

依赖:`mermaid` 11.12.1(MIT),已登记于 `release/dependency-licenses.json`。

## Markdown 里的数学公式(KaTeX)

助手回复和 Markdown 文件预览现在能渲染数学公式,与 Mermaid 图走同一条
渲染管线。三种写法会被当作公式,其余出现的美元符号一律按普通文本处理:

| 写法 | 示例 | 效果 |
| --- | --- | --- |
| 行内公式 | `$E=mc^2$` | 段落内排版成公式,随正文流动 |
| 块级公式 | `$$\frac{a}{b}$$` | 居中独立成块显示 |
| 代码块 | 语言位置写 `math` 的代码围栏 | 同块级公式,与 `mermaid` 围栏走同一个分发注册表,互不影响 |

**你会得到什么**

- 上述三种写法都排版成真正的数学公式(分数、根号、求和号、矩阵等),
渲染引擎是 KaTeX(`webapp/lib/math-renderer.ts`)。
- 公式跟随界面浅色/深色主题,两种主题下对比度都正常——公式就是普通
继承文字色的内容,切换主题不需要重画。
- 排版字体(KaTeX 字体)随应用自带,不依赖系统装没装数学字体。

**什么不会误伤**

- `成本 $5 and $10`、`$HOME`、没写闭合 `$` 的片段——都按普通文本原样
显示。单个 `$` 只有在存在闭合 `$`、内容不跨行、且不以数字开头时才
被当作公式。

**公式写错了会怎样**

- 该公式**降级为代码样式显示原始写法**(行内公式显示为行内代码,`math`
代码块显示为普通代码块),原文一个字符都不丢,页面照常渲染,不会
白屏(`webapp/lib/markdown.ts` 的降级路径)。
- 公式里无法夹带链接:`\href` 之类的可信功能默认关闭,只会显示成红色
警示文字,不会变成可点的 URL。

**限制**

- 公式排版样式与字体文件随应用打包(样式表约 24 KB,按需加载字体重
量很小);JS 渲染库进入前端主包(gzip 约 90 KB),不像 Mermaid 那样
懒加载——行内公式可能出现在句子中间,同步渲染是正确性前提。这是
记录在案的成本,若前端体积预算吃紧再评估懒加载方案。
- 升级 KaTeX 版本时需要同步更新两处:`webapp/public/fonts/katex/` 下的
字体文件与 `webapp/styles/katex.css`(由 `katex/dist/katex.min.css` 改写
字体路径生成,方法记录在英文文档同节)。

依赖:`katex` 0.18.7(MIT),已登记于 `release/dependency-licenses.json`。

## 文件预览工具栏与 Markdown 大纲(slice 27)

预览组件顶栏新增三个控件,Markdown 预览增加大纲面板。这也是
Expand Down
1 change: 1 addition & 0 deletions packages/webui/docs/CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ single index that satisfies the check.
| Markdown rendering (headings, lists, code) | ✅ | `webapp/lib/markdown.ts` wrapping the workspace `marked` package (`packages/tui` already depends on it; no CDN) |
| Syntax highlighting in code blocks | ✅ | `marked` code renderer (`webapp/lib/markdown.ts`) + CSS classes from `webapp/styles/official-utilities.css` |
| Mermaid diagrams in markdown (slice 23) | ✅ | a fenced code block whose language token is `mermaid` renders as a diagram instead of a code block. Fence languages dispatch through the language→renderer registry (`webapp/lib/markdown.ts#registerLanguageRenderer`, lines 107-113); mermaid self-registers on import (`webapp/lib/mermaid-renderer.ts:64-71`), and a renderer for any other fence language plugs into the same seam — the markdown main flow never branches on a language name. The chart library is `import()`-ed on the first diagram of a page (`webapp/components/mermaid-block.tsx:62-66`) and its chunk is served with a one-year immutable cache (`server/lib/static.js:66`); a page without a mermaid fence never requests it. Diagrams follow the light/dark theme (`components/markdown-html.tsx:52-66` re-renders on the `<html>` class flip; `components/mermaid-block.tsx:156-166,223-229` re-initialises mermaid per theme). A syntax error renders a legible failure card — error text plus the original source in a copyable `<pre>` (`components/mermaid-block.tsx:285-307`) — while the rest of the document renders normally. Limits: diagrams scale to the column width and scroll when wider (`webapp/styles/mermaid.css:62-80`); CJK labels render via the font stack at `components/mermaid-block.tsx:109`; a diagram is never a heading, so it appears in no heading-derived outline (the fence emits a `<pre>`/`<div>` pair, not `h1`-`h6` — `lib/mermaid-renderer.ts:44-57`). Dependency `mermaid` 11.12.1 (MIT) is recorded in `release/dependency-licenses.json`. |
| Math formulas via KaTeX (inline `$…$`, display `$$…$$`, ```` ```math ```` fences) | ✅ | the same pipeline as Mermaid: an inline marked extension (`webuiMath`) tokenises `$…$`/`$$…$$` and the ```math fence dispatches through the language→renderer registry (`webapp/lib/math-renderer.ts`, self-registered on import), so neither fence language can shadow the other. A single `$` is math only with a closing delimiter on one line and a non-digit start — `costs $5 and $10`, `$HOME`, unclosed `$` stay prose. A formula KaTeX cannot parse degrades to the original source as code (inline `<code class="inline-code">`, fence → the plain codeblock shell) — the page never blanks. KaTeX runs with `output: "html"` (emits only `span`/`svg`/`path`, which the sanitiser allowlist admits with a fixed attribute set; `<math>`/MathML stays a DROP tag) and `trust: false` (`\href` renders as red warning text, never a link). Inline `style` survives only on `span` and only when the value clears `isSafeStyleValue` — no parentheses (no `url()`/`expression()`), and `position`/`background`/`behavior` are refused; `components/markdown-html.tsx` converts the style attribute to a React style object (`parseInlineStyle`), since React rejects a string style prop. CSS is vendored at `webapp/styles/katex.css` (from `katex/dist/katex.min.css`, `@font-face` repointed to `/fonts/katex/…`) and loaded from `app/layout.tsx`; fonts (60 files + MIT notice) are vendored at `webapp/public/fonts/katex/`. Formulas are inheriting text — both themes work without a re-render. Known cost: `katex` JS ships in the client bundle rather than lazy-loading like mermaid (the math pipeline is synchronous `renderToString`). Dependency `katex` 0.18.7 (MIT) is recorded in `release/dependency-licenses.json`. Tests: `webapp/test/markdown-math.test.ts`. |
| Preview toolbar: refresh / edit / save (slice 27) | ✅ | the preview header carries ↻ refresh (re-read from disk, scroll position restored; a deleted/renamed file keeps the last content and shows an explicit banner — `components/file-preview.tsx` refresh handler), a 预览/编辑 toggle (text previews only, `lib/preview-edit.ts#canEditPreview`; credential-shaped paths must pass an explicit confirmation card first — `editRequiresCredentialConfirm` on the slice-16 predicate), and ✓ save (never automatic; success shows "saved at HH:MM", failure keeps the buffer and states the server's reason). Saves go through `POST /api/fs/write` (`api.ts#saveFsFile`) carrying the `(expectedMtime, expectedSize)` baseline from the load; a drifted baseline answers a conflict card (overwrite / reload) rather than a silent overwrite. The write answers with the **realpath-normalised absolute path** (the shared gate resolves symlinks before anything else — the same slice-16 form every `/api/fs/*` route returns; on macOS a `/var/...` fixture therefore answers `/private/var/...`). |
| Markdown outline panel (slice 27) | ✅ | `webapp/components/markdown-toc.tsx` + `webapp/lib/markdown-toc.ts`. The outline is extracted from the RENDERED DOM (`querySelectorAll("h1,…,h6")`), never a second markdown parse; heading ids are assigned onto those nodes (stable slugs, `-2`/`-3` dedupe suffixes — `headingSlug`/`extractOutline`, pinned by `webapp/test/markdown-toc.test.ts`). A click `preventDefault`s the anchor and smooth-scrolls the heading into view via `scrollIntoView`; the panel is sticky within the scroll viewport (max-height pinned to the viewport's height) and the active entry follows the scroll position. Documents without headings render no panel; a heading nested inside a `.mermaid-block` is excluded defensively (`isOutlineHeading`); entries are readable in both themes via design tokens. Below ~300px of content width the outline hides (observing the stable `.file-preview-body` width, not the markdown host — watching the host creates a show/hide feedback loop) rather than squeezing the document. |
| Cancel mid-run | ✅ | acp `session/cancel` is sent as a notification, pinned on the cid's active child (`/api/protocol/cancel` → `server/lib/mcode-rpc.js#cancelSession`). The hard-kill fallback (`/api/stop` → SIGTERM/SIGKILL) is only used when the notification cannot be delivered. The acp session may emit a few extra events before draining. |
Expand Down
1 change: 1 addition & 0 deletions packages/webui/docs/CAPABILITIES.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ CI 会对上述每一个名称是否出现在本文档中进行断言
| Markdown 渲染(标题、列表、代码) | ✅ | `lib/marked.min.js` 本地内置(不走 CDN) |
| 代码块语法高亮 | ✅ | highlight.js(本地副本) |
| Markdown 内 Mermaid 图渲染(slice 23) | ✅ | 代码围栏语言写 `mermaid` 即渲染为图,而非代码块。围栏语言经"语言→渲染器"注册表分发(`webapp/lib/markdown.ts` 的 `registerLanguageRenderer`,107-113 行);mermaid 在模块导入时自注册(`webapp/lib/mermaid-renderer.ts:64-71`),其他语言的渲染器可经同一接缝接入——markdown 主流程不针对语言名写分支。图表库在页面首张图出现时才动态加载(`components/mermaid-block.tsx:62-66`),该文件带一年 immutable 强缓存(`server/lib/static.js:66`);没有 mermaid 围栏的页面完全不加载。图跟随浅色/深色主题(`components/markdown-html.tsx:52-66` 监听 `<html>` class;`components/mermaid-block.tsx:156-166、223-229` 按主题重新初始化)。语法错误时渲染可读的失败卡片——错误信息 + 可复制的原始源码(`components/mermaid-block.tsx:285-307`)——文档其余部分照常渲染。限制:图按列宽缩放、超宽时在卡片内横向滚动(`webapp/styles/mermaid.css:62-80`);中文标签经字体栈正常显示(`components/mermaid-block.tsx:109`);图不产生标题,因此不会进入任何按标题组织的大纲(围栏只产出 `<pre>`/`<div>` 占位对,不产出 `h1`-`h6`,见 `lib/mermaid-renderer.ts:44-57`)。依赖 `mermaid` 11.12.1(MIT)已登记于 `release/dependency-licenses.json`。 |
| Markdown 数学公式(KaTeX,行内 `$…$`、块级 `$$…$$`、```` ```math ```` 代码块) | ✅ | 与 Mermaid 同一条管线:行内经 marked 扩展 `webuiMath` 识别 `$…$`/`$$…$$`,`math` 代码块经语言→渲染器注册表分发(`webapp/lib/math-renderer.ts`,导入时自注册),两类围栏互不干扰。单个 `$` 仅在存在同 行闭合定界符、内容不以数字开头时才视为公式——`成本 $5 and $10`、`$HOME`、未闭合的 `$` 均按普通文本显示。公式无法解析时降级为原始写法的代码样式(行内降级 `<code class="inline-code">`,代码块降级为普通代码块),页面绝不白屏。KaTeX 以 `output: "html"` 运行(只产出 `span`/`svg`/`path`,清洗白名单按固定属性集放行;`<math>`/MathML 仍为整体丢弃标签),`trust: false`(`\href` 只显示红色警示文字,不会成为链接)。内联 `style` 仅在 `span` 上保留且值须通过 `isSafeStyleValue` 校验——禁止括号(杜绝 `url()`/`expression()`),`position`/`background`/`behavior` 直接拒绝;`components/markdown-html.tsx` 将 style 属性解析为 React 样式对象(`parseInlineStyle`,React 不接受字符串 style)。样式表 vendored 于 `webapp/styles/katex.css`(源自 `katex/dist/katex.min.css`,`@font-face` 指向 `/fonts/katex/…`),由 `app/layout.tsx` 加载;字体(60 个文件 + MIT 许可声明)vendored 于 `webapp/public/fonts/katex/`。公式为继承文字色的内容,深浅主题均正常、无需重渲染。已知成本:`katex` JS 随前端主包加载,不像 mermaid 懒加载(数学管线是同步 `renderToString`)。依赖 `katex` 0.18.7(MIT)已登记于 `release/dependency-licenses.json`。测试:`webapp/test/markdown-math.test.ts`。 |
| 运行中取消 | ✅ | acp `session/cancel` 以 notification 形式发送,并钉在该 cid 的活动子进程上(`/api/protocol/cancel` → `server/lib/mcode-rpc.js#cancelSession`)。只有当 notification 无法投递时,才会走硬杀兜底(`/api/stop` → SIGTERM/SIGKILL)。acp 会话在排空前可能还会再发出几个事件。 |
| 回退 / 分叉某条消息 | ⚠ | 引擎已实现 `session/fork` 和 `session/resume`(`MCODE_ACP_CAPABILITIES.fork / .resume = true`),但目前 webui 还没有路由暴露它们——参见 [§13](CAPABILITIES.zh-CN.md#13-要启用--行-mcode-需要增加什么)。 |
| 编辑已发送的消息并重新发送 | ❌ | acp 协议未暴露 |
Expand Down
3 changes: 2 additions & 1 deletion packages/webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"antd": "5.29.3",
"autoprefixer": "10.6.1",
"highlight.js": "10.7.3",
"katex": "0.18.7",
"marked": "18.0.12",
"mermaid": "11.12.1",
"next": "14.2.35",
Expand Down Expand Up @@ -90,7 +91,7 @@
},
{
"name": "lan-sharing",
"description": "Binds loopback `127.0.0.1:18090` by default; LAN exposure is an explicit opt-in \u2014 the `HOST` env var or the persisted `lanBind` setting (`POST /api/settings {lanBind: true}`, effective on restart). A runtime toggle (`POST /api/settings {lanBroadcast: false}`) additionally closes the LAN gate with a bilingual 403 page. The settings snapshot discloses the live exposure via `lanExposed` / `bindRestartPending` / `lanExposureNotice`."
"description": "Binds loopback `127.0.0.1:18090` by default; LAN exposure is an explicit opt-in — the `HOST` env var or the persisted `lanBind` setting (`POST /api/settings {lanBind: true}`, effective on restart). A runtime toggle (`POST /api/settings {lanBroadcast: false}`) additionally closes the LAN gate with a bilingual 403 page. The settings snapshot discloses the live exposure via `lanExposed` / `bindRestartPending` / `lanExposureNotice`."
},
{
"name": "token-auth",
Expand Down
6 changes: 6 additions & 0 deletions packages/webui/webapp/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import "../styles/mavis-dropdown.css";
import "../styles/desktop-typography.css";
import "../styles/code-preview.css";
import "../styles/mermaid.css";
// KaTeX stylesheet, vendored from `katex/dist/katex.min.css` (same version as
// the `katex` devDependency) with the @font-face sources repointed at the
// vendored fonts in `public/fonts/katex/`. Loaded unconditionally: it is
// ~24 KB and the formulas' geometry classes must exist before any message
// with math renders.
import "../styles/katex.css";

export const metadata: Metadata = {
title: "MiniMax Code",
Expand Down
1 change: 1 addition & 0 deletions packages/webui/webapp/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as api from "@/lib/api";
import { renderMarkdown } from "@/lib/markdown";
import { MarkdownHtml } from "./markdown-html";
import "../lib/mermaid-renderer"; // registers the mermaid language renderer
import "../lib/math-renderer"; // registers KaTeX (inline $…$, $$…$$, ```math fences)
import { reportActionError } from "@/lib/action-errors";
import { findSubagentForBlock } from "@/lib/agent-team-lookup";
import { badgeLabelAndGlyph, agentLabel } from "@/lib/i18n-agent-team";
Expand Down
1 change: 1 addition & 0 deletions packages/webui/webapp/components/file-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "@/lib/api";
import { renderMarkdown } from "@/lib/markdown";
import "@/lib/mermaid-renderer"; // registers the mermaid language renderer
import "@/lib/math-renderer"; // registers KaTeX (inline $…$, $$…$$, ```math fences)
import { MarkdownHtml } from "@/components/markdown-html";
import { CodeView as IdeCodeView } from "@/components/code-view";
import {
Expand Down
Loading
Loading