fix(web): prevent LaTeX from being cut at \right] or \big) in agent o… (#13155)

### What problem does this PR solve?

- Use negative lookbehind (?<![a-zA-Z]) so \] and \) inside commands
(e.g. \right], \big)) are not treated as block/inline delimiters
- Use greedy matching to capture up to the last valid delimiter, fixing
truncated formulas (e.g. C_{seq}(y|x) = \frac{1}{|y|} ...)
- Add unit tests for preprocessLaTeX

Closes #13134


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
PandaMan
2026-02-24 12:30:06 +08:00
committed by GitHub
parent 3280772934
commit f462a9d85a
2 changed files with 46 additions and 2 deletions

View File

@@ -0,0 +1,34 @@
import { preprocessLaTeX } from '../chat';
describe('preprocessLaTeX', () => {
it('converts block \\[ \\] to $$ $$', () => {
expect(preprocessLaTeX('\\[ x + y \\]')).toBe('$$x + y$$');
});
it('converts inline \\( \\) to $ $', () => {
expect(preprocessLaTeX('\\( a \\)')).toBe('$a$');
});
it('does not cut block math at \\right] (Closes #13134)', () => {
const content = '\\[ C_{seq}(y|x) = \\frac{1}{|y|} \\sum_{t=1}^{|y|} \\right] \\]';
const result = preprocessLaTeX(content);
expect(result).toContain('\\right]');
expect(result).toContain('\\frac{1}{|y|}');
expect(result).toBe(
'$$ C_{seq}(y|x) = \\frac{1}{|y|} \\sum_{t=1}^{|y|} \\right] $$',
);
});
it('does not cut inline math at \\big) or nested parens', () => {
const content = '\\( f(x) + \\big) \\)';
const result = preprocessLaTeX(content);
expect(result).toContain('\\big)');
expect(result).toBe('$ f(x) + \\big) $');
});
it('handles multiple block equations', () => {
const content = 'First \\[ a \\] then \\[ b \\right] c \\]';
const result = preprocessLaTeX(content);
expect(result).toBe('First $$a$$ then $$ b \\right] c $$');
});
});

View File

@@ -39,14 +39,24 @@ export const buildMessageUuidWithRole = (
// Preprocess LaTeX equations to be rendered by KaTeX
// ref: https://github.com/remarkjs/react-markdown/issues/785
//
// Delimiter matching: we only treat \] and \) as block/inline endings when they
// are not part of a LaTeX command (e.g. \right], \big), \left)). Use a negative
// lookbehind (?<![a-zA-Z]) so that \] or \) preceded by a letter (command name)
// is not considered the closing delimiter. Use greedy matching so we match up to
// the last valid delimiter and avoid cutting at the first \] or \) inside the
// equation (e.g. \frac{1}{|y|} or \right]).
const BLOCK_MATH_RE = /\\\[([\s\S]*)(?<![a-zA-Z])\\\]/g;
const INLINE_MATH_RE = /\\\(([\s\S]*)(?<![a-zA-Z])\\\)/g;
export const preprocessLaTeX = (content: string) => {
const blockProcessedContent = content.replace(
/\\\[([\s\S]*?)\\\]/g,
BLOCK_MATH_RE,
(_, equation) => `$$${equation}$$`,
);
const inlineProcessedContent = blockProcessedContent.replace(
/\\\(([\s\S]*?)\\\)/g,
INLINE_MATH_RE,
(_, equation) => `$${equation}$`,
);
return inlineProcessedContent;