diff --git a/api/server/services/Artifacts/update.js b/api/server/services/Artifacts/update.js
index d068593f8c..be1644b11c 100644
--- a/api/server/services/Artifacts/update.js
+++ b/api/server/services/Artifacts/update.js
@@ -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
diff --git a/api/server/services/Artifacts/update.spec.js b/api/server/services/Artifacts/update.spec.js
index 2a3e0bbe39..39a4f02863 100644
--- a/api/server/services/Artifacts/update.spec.js
+++ b/api/server/services/Artifacts/update.spec.js
@@ -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 = ``;
+
+ /** 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 = `
+
+
Test
+Hello
+`;
+
+ 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 (
+
+
Count: {count}
+
+
+ );
+}`;
+
+ 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');
+ });
});
});