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
5 changes: 5 additions & 0 deletions .changeset/web-mermaid-workers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Add Mermaid diagram rendering to the web chat. Fenced `mermaid` blocks in assistant responses now render as diagrams. KaTeX math and Mermaid diagram parsing also run in Web Workers to keep the UI responsive during live streaming.
1 change: 1 addition & 0 deletions apps/kimi-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@xterm/xterm": "^6.0.0",
"katex": "^0.17.0",
"markstream-vue": "^1.0.4",
"mermaid": "^11.15.0",
"shiki": "^4.3.0",
"stream-markdown": "^0.0.16",
"vue": "^3.5.35",
Expand Down
46 changes: 44 additions & 2 deletions apps/kimi-web/src/components/chat/Markdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@
<script setup lang="ts">
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { MarkdownRender, enableKatex } from 'markstream-vue';
import {
MarkdownRender,
enableKatex,
enableMermaid,
setKaTeXWorker,
clearKaTeXWorker,
setMermaidWorker,
clearMermaidWorker,
} from 'markstream-vue';
import type { MarkdownIt } from 'markstream-vue';
import { useIsDark } from '../../composables/useIsDark';
import type { FilePreviewRequest } from '../../types';
import { collectFilePathAliases, findFilePathLinks } from '../../lib/filePathLinks';
import { markdownRenderPlan } from '../../lib/markdownPerformance';
import { copyTextToClipboard } from '../../lib/clipboard';
import * as katexWorkerModule from 'markstream-vue/workers/katexRenderer.worker?worker&type=module';
import * as mermaidWorkerModule from 'markstream-vue/workers/mermaidParser.worker?worker&type=module';
// px-based CSS build (our app is px, not rem). Imported here so the styles
// load wherever Markdown is used; scoped overrides below re-skin it to
// Terminal Pro. Importing the same file from multiple components is a no-op
Expand All @@ -23,6 +33,35 @@ import 'markstream-vue/index.px.css';
import 'katex/dist/katex.min.css';
enableKatex();

// Mermaid diagram rendering. enableMermaid() registers the default
// `import('mermaid')` loader — same pattern as enableKatex(). Without a worker,
// mermaid.parse() runs on the main thread; with a worker (set via
// setMermaidWorker), the MermaidBlockNode can validate partial-stream code
// off-thread so the UI stays responsive during live diagram output.
enableMermaid();
Comment thread
Simon-He95 marked this conversation as resolved.

// ---------------------------------------------------------------------------
// Off-main-thread workers for KaTeX and Mermaid
//
// Both katex.renderToString and mermaid.parse are CPU-heavy. markstream-vue
// ships pre-built workers (katexRenderer.worker.js, mermaidParser.worker.js)
// that follow the exact protocol its internal worker clients expect. We import
// them via Vite's `?worker&type=module` so they're built as ES module chunks
// (supporting code-splitting, which mermaid needs for per-diagram dynamic
// imports).
//
// markstream-vue's MermaidBlockNode and MathBlockNode auto-detect the presence
// of a worker: when set, heavy parsing/rendering is dispatched off-thread; when
// absent, everything runs on the main thread.
// ---------------------------------------------------------------------------

// Tear down any previous worker (e.g. from HMR) before setting a new one.
clearKaTeXWorker();
clearMermaidWorker();

setKaTeXWorker(new katexWorkerModule.default());
setMermaidWorker(new mermaidWorkerModule.default());

// Only `$$…$$` display math is rendered; single `$` inline math is disabled so
// prices, env vars, and shell paths (`$5`, `$PATH`, `$HOME/bin`) stay literal
// without any escaping or code-detection gymnastics. `math_block` (the $$ rule)
Expand Down Expand Up @@ -172,7 +211,7 @@ function processFileLinks(): void {
const parent = text.parentElement;
if (
parent &&
!parent.closest('a, pre, .md-file-link') &&
!parent.closest('a, pre, .md-file-link, svg') &&
text.data.trim().length > 0
) {
textNodes.push(text);
Expand Down Expand Up @@ -231,6 +270,9 @@ function processMarkdownLinks(): void {
const links = mdRef.value.querySelectorAll<HTMLAnchorElement>('a[href]');
for (const link of links) {
if (link.dataset.mdLinkHandled === 'true') continue;
// Skip links inside Mermaid SVGs — their hrefs are diagram semantics, not
// workspace file paths.
if (link.closest('svg')) continue;
const href = link.getAttribute('href') ?? '';
if (!isLocalLink(href)) continue;
link.dataset.mdLinkHandled = 'true';
Expand Down
9 changes: 9 additions & 0 deletions apps/kimi-web/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ declare module '*.vue' {
const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>;
export default component;
}

// Vite's `?worker&type=module` imports — not declared in `vite/client`,
// which only covers `?worker`, `?worker&inline`, and `?worker&url` for classic
// workers. ES module workers need this additional declaration so TypeScript
// can resolve the import without errors.
declare module '*?worker&type=module' {
const WorkerFactory: new () => Worker;
export default WorkerFactory;
}
6 changes: 6 additions & 0 deletions apps/kimi-web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@ export default defineConfig({
emptyOutDir: true,
target: 'es2022',
},
// Workers that import modules with code-splitting (e.g. mermaid's dynamic
// diagram imports) need ES format — IIFE cannot split chunks. The app
// already targets ES2022 so all supported browsers handle module workers.
worker: {
format: 'es',
},
});
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"vitepress": "^1.5.0"
},
"dependencies": {
"mermaid": "^11.12.2",
"mermaid": "^11.15.0",
"vitepress-plugin-llms": "^1.10.0",
"vitepress-plugin-mermaid": "^2.0.17"
}
Expand Down
5 changes: 4 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading