-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: improve markdown parsing and add comprehensive tests
- Add comprehensive test suite for markdown utilities - Fix nested code block parsing and language tag collection - Fix line endings in multiple files - Update demo conversation to use consistent port numbers The changes improve the reliability of markdown parsing, especially for nested code blocks and complex messages with multiple content types.
- Loading branch information
Showing
7 changed files
with
258 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,4 +144,4 @@ export const ConversationContent: FC<Props> = ({ conversation }) => { | |
/> | ||
</main> | ||
); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,4 +38,4 @@ export function MessageAvatar({ role, isError, isSuccess, chainType }: MessageAv | |
)} | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
import { processNestedCodeBlocks, transformThinkingTags, parseMarkdownContent } from "../markdownUtils"; | ||
import '@testing-library/jest-dom'; | ||
|
||
describe('processNestedCodeBlocks', () => { | ||
it('should handle nested code blocks', () => { | ||
const input = `\`\`\`markdown | ||
Here's a nested block | ||
\`\`\`python | ||
print("hello") | ||
\`\`\` | ||
\`\`\``; | ||
|
||
const expected = `\`\`\`markdown | ||
Here's a nested block | ||
\`\`\`python | ||
print("hello") | ||
\`\`\` | ||
\`\`\``; | ||
|
||
const result = processNestedCodeBlocks(input); | ||
expect(result.processedContent).toBe(expected); | ||
expect(result.langtags.filter(Boolean)).toEqual(['markdown', 'python']); | ||
}); | ||
|
||
it('should not modify single code blocks', () => { | ||
const input = `\`\`\`python | ||
print("hello") | ||
\`\`\``; | ||
|
||
const result = processNestedCodeBlocks(input); | ||
expect(result.processedContent).toBe(input); | ||
expect(result.langtags.filter(Boolean)).toEqual(['python']); | ||
}); | ||
|
||
it('should handle multiple nested blocks', () => { | ||
const input = `\`\`\`markdown | ||
First block | ||
\`\`\`python | ||
print("hello") | ||
\`\`\` | ||
Second block | ||
\`\`\`javascript | ||
console.log("world") | ||
\`\`\` | ||
\`\`\``; | ||
|
||
const expected = `\`\`\`markdown | ||
First block | ||
\`\`\`python | ||
print("hello") | ||
\`\`\` | ||
Second block | ||
\`\`\`javascript | ||
console.log("world") | ||
\`\`\` | ||
\`\`\``; | ||
|
||
const result = processNestedCodeBlocks(input); | ||
expect(result.processedContent).toBe(expected); | ||
expect(result.langtags.filter(Boolean)).toEqual(['markdown', 'python', 'javascript']); | ||
}); | ||
|
||
it('returns original content when no code blocks', () => { | ||
const input = "Hello world"; | ||
const result = processNestedCodeBlocks(input); | ||
expect(result.processedContent).toBe(input); | ||
expect(result.langtags).toEqual([]); | ||
}); | ||
}); | ||
|
||
describe('transformThinkingTags', () => { | ||
it('should transform thinking tags to details/summary', () => { | ||
const input = 'Before <thinking>Some thoughts</thinking> After'; | ||
const expected = 'Before <details><summary>💭 Thinking</summary>\n\nSome thoughts\n\n</details> After'; | ||
expect(transformThinkingTags(input)).toBe(expected); | ||
}); | ||
|
||
it('should handle multiple thinking tags', () => { | ||
const input = '<thinking>First thought</thinking> Middle <thinking>Second thought</thinking>'; | ||
const expected = '<details><summary>💭 Thinking</summary>\n\nFirst thought\n\n</details> Middle <details><summary>💭 Thinking</summary>\n\nSecond thought\n\n</details>'; | ||
expect(transformThinkingTags(input)).toBe(expected); | ||
}); | ||
|
||
it('should not transform thinking tags within code blocks', () => { | ||
const input = '`<thinking>Code block</thinking>`'; | ||
expect(transformThinkingTags(input)).toBe(input); | ||
}); | ||
|
||
it('preserves content outside thinking tags', () => { | ||
const input = 'Before <thinking>thinking</thinking> after'; | ||
const expected = 'Before <details><summary>💭 Thinking</summary>\n\nthinking\n\n</details> after'; | ||
expect(transformThinkingTags(input)).toBe(expected); | ||
}); | ||
}); | ||
|
||
describe('parseMarkdownContent', () => { | ||
it('parses basic markdown', () => { | ||
const input = "# Hello\n\nThis is a test"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<h1>Hello</h1>"); | ||
expect(result).toContain("<p>This is a test</p>"); | ||
}); | ||
|
||
it('handles code blocks with language tags', () => { | ||
const input = "```python\nprint('hello')\n```"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>💻 python</summary>"); | ||
expect(result).toContain('<span class="hljs-built_in">print</span>'); | ||
expect(result).toContain('<span class="hljs-string">'hello'</span>'); | ||
}); | ||
|
||
it('detects file paths in code blocks', () => { | ||
const input = "```src/test.py\nprint('hello')\n```"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>📄 src/test.py</summary>"); | ||
}); | ||
|
||
it('detects tool commands in code blocks', () => { | ||
const input = "```shell\nls -la\n```"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>🛠️ shell</summary>"); | ||
}); | ||
|
||
it('detects output blocks', () => { | ||
const input = "```stdout\nHello world\n```"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>📤 stdout</summary>"); | ||
}); | ||
|
||
it('detects write operations in code blocks', () => { | ||
const input = "```save test.txt\nHello world\n```"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>📝 save test.txt</summary>"); | ||
}); | ||
|
||
it('handles thinking tags', () => { | ||
const input = "<thinking>Some thought</thinking>"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>💭 Thinking</summary>"); | ||
expect(result).toContain("Some thought"); | ||
}); | ||
|
||
it('handles nested code blocks', () => { | ||
const input = "```markdown\nHere's a nested block\n```python\nprint('hello')\n```\n```"; | ||
const result = parseMarkdownContent(input); | ||
expect(result).toContain("<summary>💻 markdown</summary>"); | ||
expect(result).toContain('<span class="hljs-code">```python'); | ||
expect(result).toContain('print('hello')'); | ||
}); | ||
|
||
it('handles complex message with multiple content types', () => { | ||
const input = `The gptme web UI offers several advantages over the CLI interface: | ||
1. **Rich Message Display**: | ||
- Syntax highlighted code blocks | ||
- Collapsible sections for code and thinking | ||
- Different styles for user/assistant/system messages | ||
- Emoji indicators for different types of content: | ||
- 📄 File paths | ||
- 🛠️ Tool usage | ||
- 📤 Command output | ||
- 💻 Code blocks | ||
2. **Interactive Features**: | ||
- Real-time streaming of responses | ||
- Easy navigation between conversations | ||
- Ability to view and restore conversation history | ||
3. **Integration with gptme-server**: | ||
- Connects to your local gptme instance | ||
- Access to all local tools and capabilities | ||
- Secure local execution of commands | ||
Here's an example showing different types of content: | ||
\`\`\`/path/to/file.py | ||
# This shows as a file path | ||
\`\`\` | ||
\`\`\`shell | ||
# This shows as a tool | ||
ls -la | ||
\`\`\` | ||
\`\`\`stdout | ||
# This shows as command output | ||
total 0 | ||
drwxr-xr-x 2 user user 4096 Jan 29 10:48 . | ||
\`\`\` | ||
<thinking> | ||
Thinking blocks are collapsible and help show my reasoning process | ||
</thinking> | ||
You can try the web UI by: | ||
1. Starting a local gptme-server: \`gptme-server --cors-origin='http://localhost:8080'\` | ||
2. Running the web UI: \`npm run dev\` | ||
3. Opening http://localhost:5173 in your browser`; | ||
|
||
const result = parseMarkdownContent(input); | ||
|
||
// Check markdown formatting is preserved | ||
expect(result).toContain('<p>The gptme web UI offers several advantages over the CLI interface:</p>'); | ||
expect(result).toContain('<li><p><strong>Rich Message Display</strong>:</p>'); | ||
|
||
// Check list items are preserved | ||
expect(result).toContain('<li>Syntax highlighted code blocks</li>'); | ||
expect(result).toContain('<li>📄 File paths</li>'); | ||
|
||
// Check code blocks with correct emoji indicators | ||
expect(result).toContain('<summary>📄 /path/to/file.py</summary>'); | ||
expect(result).toContain('<summary>🛠️ shell</summary>'); | ||
expect(result).toContain('<summary>📤 stdout</summary>'); | ||
|
||
// Check code block content with syntax highlighting | ||
expect(result).toContain('<span class="hljs-comment"># This shows as a file path</span>'); | ||
expect(result).toContain('<span class="language-bash">This shows as a tool</span>'); | ||
expect(result).toContain('# This shows as command output'); | ||
expect(result).toContain('drwxr-xr-x 2 user user'); | ||
|
||
// Check final content is included | ||
expect(result).toContain('You can try the web UI by:'); | ||
expect(result).toContain('<code>gptme-server --cors-origin='); | ||
expect(result).toContain('<code>npm run dev</code>'); | ||
expect(result).toContain('http://localhost:5173'); | ||
|
||
// Check thinking block | ||
expect(result).toContain('<details><summary>💭 Thinking</summary>'); | ||
expect(result).toContain('Thinking blocks are collapsible'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters