|
2 | 2 | CODE_BLOCK_REGEXP, |
3 | 3 | LATEX_MATH_AND_CODE_PATTERN, |
4 | 4 | LATEX_LINEBREAK_REGEXP, |
| 5 | + LATEX_TRIGGER_REGEXP, |
5 | 6 | MHCHEM_PATTERN_MAP |
6 | 7 | } from '$lib/constants'; |
7 | 8 |
|
@@ -148,6 +149,15 @@ export function preprocessLaTeX(content: string): string { |
148 | 149 | // See also: |
149 | 150 | // https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts |
150 | 151 |
|
| 152 | + // Every step below keys off a `$` or a backslash escape (\[ \] \( \) \ce{ \pu{). |
| 153 | + // With neither present the protect/restore passes round-trip the input |
| 154 | + // unchanged, so skip them: the step 2 scan is O(n^2) in line length and costs |
| 155 | + // ~90ms on a 26KB single-line message that contains no math at all. This |
| 156 | + // matters during streaming, where the whole message is reprocessed per frame. |
| 157 | + if (!LATEX_TRIGGER_REGEXP.test(content)) { |
| 158 | + return content; |
| 159 | + } |
| 160 | + |
151 | 161 | // Step 0: Temporarily remove blockquote markers (>) to process LaTeX correctly |
152 | 162 | // Store the structure so we can restore it later |
153 | 163 | const blockquoteMarkers: Map<number, string> = new Map(); |
@@ -175,24 +185,31 @@ export function preprocessLaTeX(content: string): string { |
175 | 185 | const latexExpressions: string[] = []; |
176 | 186 |
|
177 | 187 | // Match \S...\[...\] and protect them and insert a line-break. |
178 | | - content = content.replace(/([\S].*?)\\\[([\s\S]*?)\\\](.*)/g, (match, group1, group2, group3) => { |
179 | | - // Check if there are characters following the formula (display-formula in a table-cell?) |
180 | | - if (group1.endsWith('\\')) { |
181 | | - return match; // Backslash before \[, do nothing. |
182 | | - } |
183 | | - const hasSuffix = /\S/.test(group3); |
184 | | - let optBreak; |
185 | | - |
186 | | - if (hasSuffix) { |
187 | | - latexExpressions.push(`\\(${group2.trim()}\\)`); // Convert into inline. |
188 | | - optBreak = ''; |
189 | | - } else { |
190 | | - latexExpressions.push(`\\[${group2}\\]`); |
191 | | - optBreak = '\n'; |
192 | | - } |
| 188 | + // Guarded: with no `\[` present this pattern still probes every start offset, |
| 189 | + // expanding `.*?` to the end of each line before failing - O(n^2) for nothing. |
| 190 | + if (content.includes('\\[')) { |
| 191 | + content = content.replace( |
| 192 | + /([\S].*?)\\\[([\s\S]*?)\\\](.*)/g, |
| 193 | + (match, group1, group2, group3) => { |
| 194 | + // Check if there are characters following the formula (display-formula in a table-cell?) |
| 195 | + if (group1.endsWith('\\')) { |
| 196 | + return match; // Backslash before \[, do nothing. |
| 197 | + } |
| 198 | + const hasSuffix = /\S/.test(group3); |
| 199 | + let optBreak; |
| 200 | + |
| 201 | + if (hasSuffix) { |
| 202 | + latexExpressions.push(`\\(${group2.trim()}\\)`); // Convert into inline. |
| 203 | + optBreak = ''; |
| 204 | + } else { |
| 205 | + latexExpressions.push(`\\[${group2}\\]`); |
| 206 | + optBreak = '\n'; |
| 207 | + } |
193 | 208 |
|
194 | | - return `${group1}${optBreak}<<LATEX_${latexExpressions.length - 1}>>${optBreak}${group3}`; |
195 | | - }); |
| 209 | + return `${group1}${optBreak}<<LATEX_${latexExpressions.length - 1}>>${optBreak}${group3}`; |
| 210 | + } |
| 211 | + ); |
| 212 | + } |
196 | 213 |
|
197 | 214 | // Match \(...\), \[...\], $$...$$ and protect them |
198 | 215 | content = content.replace( |
|
0 commit comments