🪄 fix: Code Block handling in Artifact Updates (#11417)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions

* Improved detection of code blocks to support both language identifiers and plain code fences.
* Updated tests to cover various scenarios, including edge cases with different language identifiers and multiline content.
* Ensured proper handling of code blocks with trailing whitespace and complex syntax.
This commit is contained in:
Danny Avila 2026-01-20 08:45:43 -05:00 committed by GitHub
parent 4a1d2b0d94
commit e509ba5be0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 277 additions and 4 deletions

View file

@ -73,15 +73,25 @@ const replaceArtifactContent = (originalText, artifact, original, updated) => {
return null;
}
// Check if there are code blocks
const codeBlockStart = artifactContent.indexOf('```\n', contentStart);
// Check if there are code blocks - handle both ```\n and ```lang\n formats
let codeBlockStart = artifactContent.indexOf('```', contentStart);
const codeBlockEnd = artifactContent.lastIndexOf('\n```', contentEnd);
// If we found opening backticks, find the actual newline (skipping any language identifier)
if (codeBlockStart !== -1) {
const newlineAfterBackticks = artifactContent.indexOf('\n', codeBlockStart);
if (newlineAfterBackticks !== -1 && newlineAfterBackticks < contentEnd) {
codeBlockStart = newlineAfterBackticks;
} else {
codeBlockStart = -1;
}
}
// Determine where to look for the original content
let searchStart, searchEnd;
if (codeBlockStart !== -1) {
// Code block starts
searchStart = codeBlockStart + 4; // after ```\n
// Code block starts - searchStart is right after the newline following ```[lang]
searchStart = codeBlockStart + 1; // after the newline
if (codeBlockEnd !== -1 && codeBlockEnd > codeBlockStart) {
// Code block has proper ending

View file

@ -494,5 +494,268 @@ ${original}`;
/```\n {2}function test\(\) \{\n {4}return \{\n {6}value: 100\n {4}\};\n {2}\}\n```/,
);
});
test('should handle code blocks with language identifiers (```svg, ```html, etc.)', () => {
const svgContent = `<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" fill="#4A90A4"/>
<rect x="50" y="50" width="100" height="100" fill="#FFFFFF"/>
</svg>`;
/** Artifact with language identifier in code block */
const artifactText = `${ARTIFACT_START}{identifier="test-svg" type="image/svg+xml" title="Test SVG"}
\`\`\`svg
${svgContent}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
expect(artifacts).toHaveLength(1);
const updatedSvg = svgContent.replace('#FFFFFF', '#131313');
const result = replaceArtifactContent(artifactText, artifacts[0], svgContent, updatedSvg);
expect(result).not.toBeNull();
expect(result).toContain('#131313');
expect(result).not.toContain('#FFFFFF');
expect(result).toMatch(/```svg\n/);
});
test('should handle code blocks with complex language identifiers', () => {
const htmlContent = `<!DOCTYPE html>
<html>
<head><title>Test</title></head>
<body>Hello</body>
</html>`;
const artifactText = `${ARTIFACT_START}{identifier="test-html" type="text/html" title="Test HTML"}
\`\`\`html
${htmlContent}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const updatedHtml = htmlContent.replace('Hello', 'Updated');
const result = replaceArtifactContent(artifactText, artifacts[0], htmlContent, updatedHtml);
expect(result).not.toBeNull();
expect(result).toContain('Updated');
expect(result).toMatch(/```html\n/);
});
});
describe('code block edge cases', () => {
test('should handle code block without language identifier (```\\n)', () => {
const content = 'const x = 1;\nconst y = 2;';
const artifactText = `${ARTIFACT_START}{identifier="test" type="text/plain" title="Test"}
\`\`\`
${content}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const result = replaceArtifactContent(artifactText, artifacts[0], content, 'updated');
expect(result).not.toBeNull();
expect(result).toContain('updated');
expect(result).toMatch(/```\nupdated\n```/);
});
test('should handle various language identifiers', () => {
const languages = [
'javascript',
'typescript',
'python',
'jsx',
'tsx',
'css',
'json',
'xml',
'markdown',
'md',
];
for (const lang of languages) {
const content = `test content for ${lang}`;
const artifactText = `${ARTIFACT_START}{identifier="test-${lang}" type="text/plain" title="Test"}
\`\`\`${lang}
${content}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
expect(artifacts).toHaveLength(1);
const result = replaceArtifactContent(artifactText, artifacts[0], content, 'updated');
expect(result).not.toBeNull();
expect(result).toContain('updated');
expect(result).toMatch(new RegExp(`\`\`\`${lang}\\n`));
}
});
test('should handle single character language identifier', () => {
const content = 'single char lang';
const artifactText = `${ARTIFACT_START}{identifier="test" type="text/plain" title="Test"}
\`\`\`r
${content}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const result = replaceArtifactContent(artifactText, artifacts[0], content, 'updated');
expect(result).not.toBeNull();
expect(result).toContain('updated');
expect(result).toMatch(/```r\n/);
});
test('should handle code block with content that looks like code fence', () => {
const content = 'Line 1\nSome text with ``` backticks in middle\nLine 3';
const artifactText = `${ARTIFACT_START}{identifier="test" type="text/plain" title="Test"}
\`\`\`text
${content}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const result = replaceArtifactContent(artifactText, artifacts[0], content, 'updated');
expect(result).not.toBeNull();
expect(result).toContain('updated');
});
test('should handle code block with trailing whitespace in language line', () => {
const content = 'whitespace test';
/** Note: trailing spaces after 'python' */
const artifactText = `${ARTIFACT_START}{identifier="test" type="text/plain" title="Test"}
\`\`\`python
${content}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const result = replaceArtifactContent(artifactText, artifacts[0], content, 'updated');
expect(result).not.toBeNull();
expect(result).toContain('updated');
});
test('should handle react/jsx content with complex syntax', () => {
const jsxContent = `function App() {
const [count, setCount] = useState(0);
return (
<div className="app">
<h1>Count: {count}</h1>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}`;
const artifactText = `${ARTIFACT_START}{identifier="react-app" type="application/vnd.react" title="React App"}
\`\`\`jsx
${jsxContent}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const updatedJsx = jsxContent.replace('Increment', 'Click me');
const result = replaceArtifactContent(artifactText, artifacts[0], jsxContent, updatedJsx);
expect(result).not.toBeNull();
expect(result).toContain('Click me');
expect(result).not.toContain('Increment');
expect(result).toMatch(/```jsx\n/);
});
test('should handle mermaid diagram content', () => {
const mermaidContent = `graph TD
A[Start] --> B{Is it?}
B -->|Yes| C[OK]
B -->|No| D[End]`;
const artifactText = `${ARTIFACT_START}{identifier="diagram" type="application/vnd.mermaid" title="Flow"}
\`\`\`mermaid
${mermaidContent}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const updatedMermaid = mermaidContent.replace('Start', 'Begin');
const result = replaceArtifactContent(
artifactText,
artifacts[0],
mermaidContent,
updatedMermaid,
);
expect(result).not.toBeNull();
expect(result).toContain('Begin');
expect(result).toMatch(/```mermaid\n/);
});
test('should handle artifact without code block (plain text)', () => {
const content = 'Just plain text without code fences';
const artifactText = `${ARTIFACT_START}{identifier="plain" type="text/plain" title="Plain"}
${content}
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const result = replaceArtifactContent(
artifactText,
artifacts[0],
content,
'updated plain text',
);
expect(result).not.toBeNull();
expect(result).toContain('updated plain text');
expect(result).not.toContain('```');
});
test('should handle multiline content with various newline patterns', () => {
const content = `Line 1
Line 2
Line 4 after empty line
Indented line
Double indented`;
const artifactText = `${ARTIFACT_START}{identifier="test" type="text/plain" title="Test"}
\`\`\`
${content}
\`\`\`
${ARTIFACT_END}`;
const message = { text: artifactText };
const artifacts = findAllArtifacts(message);
const updated = content.replace('Line 1', 'First Line');
const result = replaceArtifactContent(artifactText, artifacts[0], content, updated);
expect(result).not.toBeNull();
expect(result).toContain('First Line');
expect(result).toContain(' Indented line');
expect(result).toContain(' Double indented');
});
});
});