Skip to content

Commit 85d73af

Browse files
hannesrudolphclaudedaniel-lxs
authored
feat: add LaTeX math equation rendering in chat window (#4258) (#4786)
Co-authored-by: Claude <[email protected]> Co-authored-by: Daniel Riccio <[email protected]>
1 parent d598fc3 commit 85d73af

File tree

8 files changed

+530
-138
lines changed

8 files changed

+530
-138
lines changed

pnpm-lock.yaml

Lines changed: 477 additions & 132 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/.vscodeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
!webview-ui/build/assets/*.js
2020
!webview-ui/build/assets/*.ttf
2121
!webview-ui/build/assets/*.css
22+
!webview-ui/build/assets/fonts/*.woff
23+
!webview-ui/build/assets/fonts/*.woff2
24+
!webview-ui/build/assets/fonts/*.ttf
2225

2326
# Include default themes JSON files used in getTheme
2427
!integrations/theme/default-themes/**

src/core/webview/ClineProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ export class ClineProvider
672672

673673
const csp = [
674674
"default-src 'none'",
675-
`font-src ${webview.cspSource}`,
675+
`font-src ${webview.cspSource} data:`,
676676
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
677677
`img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:`,
678678
`media-src ${webview.cspSource}`,
@@ -759,7 +759,7 @@ export class ClineProvider
759759
<meta charset="utf-8">
760760
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
761761
<meta name="theme-color" content="#000000">
762-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:; media-src ${webview.cspSource}; script-src ${webview.cspSource} 'wasm-unsafe-eval' 'nonce-${nonce}' https://us-assets.i.posthog.com 'strict-dynamic'; connect-src https://openrouter.ai https://api.requesty.ai https://us.i.posthog.com https://us-assets.i.posthog.com;">
762+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource} data:; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:; media-src ${webview.cspSource}; script-src ${webview.cspSource} 'wasm-unsafe-eval' 'nonce-${nonce}' https://us-assets.i.posthog.com 'strict-dynamic'; connect-src https://openrouter.ai https://api.requesty.ai https://us.i.posthog.com https://us-assets.i.posthog.com;">
763763
<link rel="stylesheet" type="text/css" href="${stylesUri}">
764764
<link href="${codiconsUri}" rel="stylesheet" />
765765
<script nonce="${nonce}">

webview-ui/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"fzf": "^0.5.2",
4545
"i18next": "^25.0.0",
4646
"i18next-http-backend": "^3.0.2",
47+
"katex": "^0.16.11",
4748
"knuth-shuffle-seeded": "^1.0.6",
4849
"lru-cache": "^11.1.0",
4950
"lucide-react": "^0.518.0",
@@ -59,7 +60,9 @@
5960
"react-use": "^17.5.1",
6061
"react-virtuoso": "^4.7.13",
6162
"rehype-highlight": "^7.0.0",
63+
"rehype-katex": "^7.0.1",
6264
"remark-gfm": "^4.0.1",
65+
"remark-math": "^6.0.0",
6366
"remove-markdown": "^0.6.0",
6467
"shell-quote": "^1.8.2",
6568
"shiki": "^3.2.1",
@@ -80,6 +83,8 @@
8083
"@testing-library/jest-dom": "^6.6.3",
8184
"@testing-library/react": "^16.2.0",
8285
"@testing-library/user-event": "^14.6.1",
86+
"@types/jest": "^29.0.0",
87+
"@types/katex": "^0.16.7",
8388
"@types/node": "20.x",
8489
"@types/react": "^18.3.23",
8590
"@types/react-dom": "^18.3.5",

webview-ui/src/components/common/MarkdownBlock.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React, { memo, useEffect } from "react"
22
import { useRemark } from "react-remark"
33
import styled from "styled-components"
44
import { visit } from "unist-util-visit"
5+
import rehypeKatex from "rehype-katex"
6+
import remarkMath from "remark-math"
57

68
import { vscode } from "@src/utils/vscode"
79
import { useExtensionState } from "@src/context/ExtensionStateContext"
@@ -75,6 +77,31 @@ const StyledMarkdown = styled.div`
7577
);
7678
}
7779
80+
/* KaTeX styling */
81+
.katex {
82+
font-size: 1.1em;
83+
color: var(--vscode-editor-foreground);
84+
font-family: KaTeX_Main, "Times New Roman", serif;
85+
line-height: 1.2;
86+
white-space: normal;
87+
text-indent: 0;
88+
}
89+
90+
.katex-display {
91+
display: block;
92+
margin: 1em 0;
93+
text-align: center;
94+
padding: 0.5em;
95+
overflow-x: auto;
96+
overflow-y: hidden;
97+
background-color: var(--vscode-textCodeBlock-background);
98+
border-radius: 3px;
99+
}
100+
101+
.katex-error {
102+
color: var(--vscode-errorForeground);
103+
}
104+
78105
font-family:
79106
var(--vscode-font-family),
80107
system-ui,
@@ -126,6 +153,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
126153
const [reactContent, setMarkdown] = useRemark({
127154
remarkPlugins: [
128155
remarkUrlToLink,
156+
remarkMath,
129157
() => {
130158
return (tree) => {
131159
visit(tree, "code", (node: any) => {
@@ -138,7 +166,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
138166
}
139167
},
140168
],
141-
rehypePlugins: [],
169+
rehypePlugins: [rehypeKatex as any],
142170
rehypeReactOptions: {
143171
components: {
144172
a: ({ href, children }: any) => {

webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ describe("mergeExtensionState", () => {
221221
experiments: {
222222
powerSteering: true,
223223
marketplace: false,
224-
concurrentFileReads: true,
225224
disableCompletionCommand: false,
225+
concurrentFileReads: true,
226226
multiFileApplyDiff: true,
227227
} as Record<ExperimentId, boolean>,
228228
}
@@ -237,8 +237,8 @@ describe("mergeExtensionState", () => {
237237
expect(result.experiments).toEqual({
238238
powerSteering: true,
239239
marketplace: false,
240-
concurrentFileReads: true,
241240
disableCompletionCommand: false,
241+
concurrentFileReads: true,
242242
multiFileApplyDiff: true,
243243
})
244244
})

webview-ui/src/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
@import "tailwindcss/theme.css" layer(theme);
1919
@import "./preflight.css" layer(base);
2020
@import "tailwindcss/utilities.css" layer(utilities);
21+
@import "katex/dist/katex.min.css";
2122

2223
@plugin "tailwindcss-animate";
2324

webview-ui/vite.config.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,17 @@ export default defineConfig(({ mode }) => {
105105
// Default naming for other chunks, ensuring uniqueness from entry
106106
return `assets/chunk-[hash].js`
107107
},
108-
assetFileNames: `assets/[name].[ext]`,
108+
assetFileNames: (assetInfo) => {
109+
if (
110+
assetInfo.name &&
111+
(assetInfo.name.endsWith(".woff2") ||
112+
assetInfo.name.endsWith(".woff") ||
113+
assetInfo.name.endsWith(".ttf"))
114+
) {
115+
return "assets/fonts/[name][extname]"
116+
}
117+
return "assets/[name][extname]"
118+
},
109119
manualChunks: (id, { getModuleInfo }) => {
110120
// Consolidate all mermaid code and its direct large dependencies (like dagre)
111121
// into a single chunk. The 'channel.js' error often points to dagre.

0 commit comments

Comments
 (0)