Skip to content

Commit

Permalink
🐛 fix: Artifact Parsing and Rendering Bug Fix for Gemini 2.0 Flash (l…
Browse files Browse the repository at this point in the history
…obehub#5633)

* ✨ feat: Enhance LobeArtifact processing and rehype plugin

- Add test cases for artifact processing with adjacent lobeThinking tags
- Modify utils to insert empty line between lobeThinking and lobeArtifact
- Implement rehype plugin for transforming LobeArtifact tags in markdown

* ✨ feat: Improve LobeArtifact processing with advanced code block removal

- Add comprehensive test cases for artifact processing with various code block scenarios
- Enhance utils to handle fenced code blocks within and around lobeArtifact tags
- Support removing code blocks for HTML and other artifact types

* ✨ feat: Enhance LobeArtifact code block removal tests

- Add comprehensive test cases for processWithArtifact function
- Cover scenarios with HTML and tool_code code blocks
- Test handling of code blocks with content before and after
- Verify processing of artifacts with and without surrounding code blocks

* ✨ feat: Improve LobeArtifact code block processing regex

- Enhance regex in processWithArtifact to handle more complex code block scenarios
- Support better extraction of content before, within, and after code blocks
- Improve handling of artifacts with surrounding text and multiple tags

* ✨ feat: Add artifact processing and selector tests

- Enhance `processWithArtifact` with debug logging and improved code block handling
- Add comprehensive test cases for artifact-related selectors in chat store
- Implement tests for message content, artifact code extraction, and tag closure detection

* ✨ feat: Improve artifact code block extraction in selectors

- Add support for removing markdown code block wrapping in artifact content
- Update `artifactCode` selector to handle HTML and other code block scenarios
- Enhance test coverage for artifact code extraction with markdown-wrapped content

* 🔇 refactor: Remove debug console logs from processWithArtifact

- Clean up unnecessary console.log statements in artifact processing utility
- Improve code readability and performance by removing debug logging
- Maintain existing logic for artifact tag and code block processing

---------

Co-authored-by: yale <[email protected]>
  • Loading branch information
yaleh and yale authored Feb 5, 2025
1 parent a90a75e commit 7d782b1
Show file tree
Hide file tree
Showing 6 changed files with 624 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ public/swe-worker*
*.patch
*.pdf
vertex-ai-key.json
.pnpm-store
284 changes: 284 additions & 0 deletions src/features/Conversation/components/ChatItem/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,288 @@ describe('processWithArtifact', () => {

expect(output).toEqual(`<lobeThinking>这个词汇涉及了`);
});

it('should handle no empty line between lobeThinking and lobeArtifact', () => {
const input = `<lobeThinking>这是一个思考过程。</lobeThinking>
<lobeArtifact identifier="test" type="image/svg+xml" title="测试">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" fill="blue"/>
</svg>
</lobeArtifact>`;

const output = processWithArtifact(input);

expect(output).toEqual(`<lobeThinking>这是一个思考过程。</lobeThinking>
<lobeArtifact identifier="test" type="image/svg+xml" title="测试"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <rect width="100" height="100" fill="blue"/></svg></lobeArtifact>`);
});

it('should remove fenced code block between lobeArtifact and HTML content', () => {
const input = `<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器">
\`\`\`html
<!DOCTYPE html>
<html lang="zh">
<head>
<title>计算器</title>
</head>
<body>
<div>计算器</div>
</body>
</html>
\`\`\`
</lobeArtifact>`;

const output = processWithArtifact(input);

expect(output).toEqual(
`<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器"><!DOCTYPE html><html lang="zh"><head> <title>计算器</title></head><body> <div>计算器</div></body></html></lobeArtifact>`,
);
});

it('should remove fenced code block between lobeArtifact and HTML content without doctype', () => {
const input = `<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器">
\`\`\`html
<html lang="zh">
<head>
<title>计算器</title>
</head>
<body>
<div>计算器</div>
</body>
</html>
\`\`\`
</lobeArtifact>`;

const output = processWithArtifact(input);

expect(output).toEqual(
`<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器"><html lang="zh"><head> <title>计算器</title></head><body> <div>计算器</div></body></html></lobeArtifact>`,
);
});

it('should remove outer fenced code block wrapping lobeThinking and lobeArtifact', () => {
const input =
'```tool_code\n<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试">\n<div>测试内容</div>\n</lobeArtifact>\n```';

const output = processWithArtifact(input);

expect(output).toEqual(
'<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试"><div>测试内容</div></lobeArtifact>',
);
});

it('should handle both outer code block and inner HTML code block', () => {
const input =
'```tool_code\n<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试">\n```html\n<!DOCTYPE html>\n<html>\n<body>\n<div>测试内容</div>\n</body>\n</html>\n```\n</lobeArtifact>\n```';

const output = processWithArtifact(input);

expect(output).toEqual(
'<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试"><!DOCTYPE html><html><body><div>测试内容</div></body></html></lobeArtifact>',
);
});

it('should handle complete conversation with text and tags', () => {
const input = `Sure, I can help you with that! Here is a basic calculator built using HTML, CSS, and JavaScript.
<lobeThinking>A web calculator is a substantial piece of code and a good candidate for an artifact. It's self-contained, and it's likely that the user will want to modify it. This is a new request, so I will create a new artifact.</lobeThinking>
<lobeArtifact identifier="web-calculator" type="text/html" title="Web Calculator">
\`\`\`html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple Calculator</title>
</head>
<body>
<div>Calculator</div>
</body>
</html>
\`\`\`
</lobeArtifact>
This code provides a basic calculator that can perform addition, subtraction, multiplication, and division.`;

const output = processWithArtifact(input);

expect(output)
.toEqual(`Sure, I can help you with that! Here is a basic calculator built using HTML, CSS, and JavaScript.
<lobeThinking>A web calculator is a substantial piece of code and a good candidate for an artifact. It's self-contained, and it's likely that the user will want to modify it. This is a new request, so I will create a new artifact.</lobeThinking>
<lobeArtifact identifier="web-calculator" type="text/html" title="Web Calculator"><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Simple Calculator</title></head><body> <div>Calculator</div></body></html></lobeArtifact>
This code provides a basic calculator that can perform addition, subtraction, multiplication, and division.`);
});
});

describe('outer code block removal', () => {
it('should remove outer html code block', () => {
const input = `\`\`\`html
<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test">
<!DOCTYPE html>
<html>
<body>Test</body>
</html>
</lobeArtifact>
\`\`\``;

const output = processWithArtifact(input);

expect(output).toEqual(`<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`);
});

it('should remove outer tool_code code block', () => {
const input = `\`\`\`tool_code
<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test">
<!DOCTYPE html>
<html>
<body>Test</body>
</html>
</lobeArtifact>
\`\`\``;

const output = processWithArtifact(input);

expect(output).toEqual(`<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`);
});

it('should handle input without outer code block', () => {
const input = `<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test">
<!DOCTYPE html>
<html>
<body>Test</body>
</html>
</lobeArtifact>`;

const output = processWithArtifact(input);

expect(output).toEqual(`<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`);
});

it('should handle code block with content before and after', () => {
const input = `Some text before
\`\`\`html
<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test">
<!DOCTYPE html>
<html>
<body>Test</body>
</html>
</lobeArtifact>
\`\`\`
Some text after`;

const output = processWithArtifact(input);

expect(output).toEqual(`Some text before
<lobeThinking>Test thinking</lobeThinking>
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>
Some text after`);
});

it('should handle code block with only lobeArtifact tag', () => {
const input = `\`\`\`html
<lobeArtifact identifier="test" type="text/html" title="Test">
<!DOCTYPE html>
<html>
<body>Test</body>
</html>
</lobeArtifact>
\`\`\``;

const output = processWithArtifact(input);

expect(output).toEqual(
`<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`,
);
});

it('should handle code block with surrounding text and both lobeThinking and lobeArtifact', () => {
const input = `---
\`\`\`tool_code
<lobeThinking>The user reported a \`SyntaxError\` in the browser console, indicating a problem with the JavaScript code in the calculator artifact. The error message "Identifier 'display' has already been declared" suggests a variable naming conflict. I need to review the JavaScript code and correct the issue. This is an update to the existing "calculator-web-artifact" artifact.</lobeThinking>
<lobeArtifact identifier="calculator-web-artifact" type="text/html" title="Simple Calculator">
<!DOCTYPE html>
<html lang="en">
...
</html>
</lobeArtifact>
\`\`\`
I've updated the calculator artifact. The issue was a naming conflict with the \`display\` variable. I've renamed the input element's ID to \`calc-display\` and the JavaScript variable to \`displayElement\` to avoid the conflict. The calculator should now function correctly.
---`;

const output = processWithArtifact(input);

expect(output).toEqual(`---
<lobeThinking>The user reported a \`SyntaxError\` in the browser console, indicating a problem with the JavaScript code in the calculator artifact. The error message "Identifier 'display' has already been declared" suggests a variable naming conflict. I need to review the JavaScript code and correct the issue. This is an update to the existing "calculator-web-artifact" artifact.</lobeThinking>
<lobeArtifact identifier="calculator-web-artifact" type="text/html" title="Simple Calculator"><!DOCTYPE html><html lang="en">...</html></lobeArtifact>
I've updated the calculator artifact. The issue was a naming conflict with the \`display\` variable. I've renamed the input element's ID to \`calc-display\` and the JavaScript variable to \`displayElement\` to avoid the conflict. The calculator should now function correctly.
---`);
});

it('should handle code block before lobeThinking and lobeArtifact', () => {
const input = `
Okay, I'll create a temperature converter with the logic wrapped in an IIFE and event listeners attached in Javascript.
\`\`\`html
<!DOCTYPE html>
<html lang="en">
...
</html>
\`\`\`
<lobeThinking>This is a good candidate for an artifact. It's a self-contained HTML document with embedded JavaScript that provides a functional temperature converter. It's more than a simple code snippet and can be reused or modified. This is a new request, so I'll create a new artifact with the identifier "temperature-converter".</lobeThinking>
<lobeArtifact identifier="temperature-converter" type="text/html" title="Temperature Converter">
\`\`\`html
<!DOCTYPE html>
<html lang="en">
...
</html>
\`\`\`
</lobeArtifact>
This HTML document includes the temperature converter with the requested features: the logic is wrapped in an IIFE, and event listeners are attached in JavaScript.
`;

const output = processWithArtifact(input);

expect(output)
.toEqual(`Okay, I'll create a temperature converter with the logic wrapped in an IIFE and event listeners attached in Javascript.
\`\`\`html
<!DOCTYPE html>
<html lang="en">
...
</html>
\`\`\`
<lobeThinking>This is a good candidate for an artifact. It's a self-contained HTML document with embedded JavaScript that provides a functional temperature converter. It's more than a simple code snippet and can be reused or modified. This is a new request, so I'll create a new artifact with the identifier "temperature-converter".</lobeThinking>
<lobeArtifact identifier="temperature-converter" type="text/html" title="Temperature Converter"><!DOCTYPE html><html lang="en">...</html></lobeArtifact>
This HTML document includes the temperature converter with the requested features: the logic is wrapped in an IIFE, and event listeners are attached in JavaScript.`);
});
});
47 changes: 39 additions & 8 deletions src/features/Conversation/components/ChatItem/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,55 @@ import { ARTIFACT_TAG_REGEX, ARTIFACT_THINKING_TAG_REGEX } from '@/const/plugin'
* Replace all line breaks in the matched `lobeArtifact` tag with an empty string
*/
export const processWithArtifact = (input: string = '') => {
let output = input;
const thinkMatch = ARTIFACT_THINKING_TAG_REGEX.exec(input);
// First remove outer fenced code block if it exists
let output = input.replace(
/^([\S\s]*?)\s*```[^\n]*\n((?:<lobeThinking>[\S\s]*?<\/lobeThinking>\s*\n\s*)?<lobeArtifact[\S\s]*?<\/lobeArtifact>\s*)\n```\s*([\S\s]*?)$/,
(_, before = '', content, after = '') => {
return [before.trim(), content.trim(), after.trim()].filter(Boolean).join('\n\n');
},
);

const thinkMatch = ARTIFACT_THINKING_TAG_REGEX.exec(output);

// If the input contains the `lobeThinking` tag, replace all line breaks with an empty string
if (thinkMatch)
output = input.replace(ARTIFACT_THINKING_TAG_REGEX, (match) =>
if (thinkMatch) {
output = output.replace(ARTIFACT_THINKING_TAG_REGEX, (match) =>
match.replaceAll(/\r?\n|\r/g, ''),
);
}

const match = ARTIFACT_TAG_REGEX.exec(input);
// Add empty line between lobeThinking and lobeArtifact if they are adjacent
output = output.replace(/(<\/lobeThinking>)\r?\n(<lobeArtifact)/, '$1\n\n$2');

// Remove fenced code block between lobeArtifact and HTML content
output = output.replace(
/(<lobeArtifact[^>]*>)\s*```[^\n]*\n([\S\s]*?)(```\n)?(<\/lobeArtifact>)/,
(_, start, content, __, end) => {
if (content.trim().startsWith('<!DOCTYPE html') || content.trim().startsWith('<html')) {
return start + content.trim() + end;
}
return start + content + (__ || '') + end;
},
);

// Keep existing code blocks that are not part of lobeArtifact
output = output.replace(
/^([\S\s]*?)(<lobeThinking>[\S\s]*?<\/lobeThinking>\s*\n\s*<lobeArtifact[\S\s]*?<\/lobeArtifact>)([\S\s]*?)$/,
(_, before, content, after) => {
return [before.trim(), content.trim(), after.trim()].filter(Boolean).join('\n\n');
},
);

const match = ARTIFACT_TAG_REGEX.exec(output);
// If the input contains the `lobeArtifact` tag, replace all line breaks with an empty string
if (match)
return output.replace(ARTIFACT_TAG_REGEX, (match) => match.replaceAll(/\r?\n|\r/g, ''));
if (match) {
output = output.replace(ARTIFACT_TAG_REGEX, (match) => match.replaceAll(/\r?\n|\r/g, ''));
}

// if not match, check if it's start with <lobeArtifact but not closed
const regex = /<lobeArtifact\b(?:(?!\/?>)[\S\s])*$/;
if (regex.test(output)) {
return output.replace(regex, '<lobeArtifact>');
output = output.replace(regex, '<lobeArtifact>');
}

return output;
Expand Down
Loading

0 comments on commit 7d782b1

Please sign in to comment.