PuneetP16
commited on
Commit
·
43839b1
1
Parent(s):
f0a668b
[fix]: artifact actionlist rendering in chat
Browse files- app/components/chat/Markdown.spec.ts +48 -0
- app/components/chat/Markdown.tsx +45 -1
- app/utils/logger.ts +1 -1
app/components/chat/Markdown.spec.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import { stripCodeFenceFromArtifact } from './Markdown';
|
| 3 |
+
|
| 4 |
+
describe('stripCodeFenceFromArtifact', () => {
|
| 5 |
+
it('should remove code fences around artifact element', () => {
|
| 6 |
+
const input = "```xml\n<div class='__boltArtifact__'></div>\n```";
|
| 7 |
+
const expected = "\n<div class='__boltArtifact__'></div>\n";
|
| 8 |
+
expect(stripCodeFenceFromArtifact(input)).toBe(expected);
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
it('should handle code fence with language specification', () => {
|
| 12 |
+
const input = "```typescript\n<div class='__boltArtifact__'></div>\n```";
|
| 13 |
+
const expected = "\n<div class='__boltArtifact__'></div>\n";
|
| 14 |
+
expect(stripCodeFenceFromArtifact(input)).toBe(expected);
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
it('should not modify content without artifacts', () => {
|
| 18 |
+
const input = '```\nregular code block\n```';
|
| 19 |
+
expect(stripCodeFenceFromArtifact(input)).toBe(input);
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
+
it('should handle empty input', () => {
|
| 23 |
+
expect(stripCodeFenceFromArtifact('')).toBe('');
|
| 24 |
+
});
|
| 25 |
+
|
| 26 |
+
it('should handle artifact without code fences', () => {
|
| 27 |
+
const input = "<div class='__boltArtifact__'></div>";
|
| 28 |
+
expect(stripCodeFenceFromArtifact(input)).toBe(input);
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
it('should handle multiple artifacts but only remove fences around them', () => {
|
| 32 |
+
const input = [
|
| 33 |
+
'Some text',
|
| 34 |
+
'```typescript',
|
| 35 |
+
"<div class='__boltArtifact__'></div>",
|
| 36 |
+
'```',
|
| 37 |
+
'```',
|
| 38 |
+
'regular code',
|
| 39 |
+
'```',
|
| 40 |
+
].join('\n');
|
| 41 |
+
|
| 42 |
+
const expected = ['Some text', '', "<div class='__boltArtifact__'></div>", '', '```', 'regular code', '```'].join(
|
| 43 |
+
'\n',
|
| 44 |
+
);
|
| 45 |
+
|
| 46 |
+
expect(stripCodeFenceFromArtifact(input)).toBe(expected);
|
| 47 |
+
});
|
| 48 |
+
});
|
app/components/chat/Markdown.tsx
CHANGED
|
@@ -68,7 +68,51 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
|
|
| 68 |
remarkPlugins={remarkPlugins(limitedMarkdown)}
|
| 69 |
rehypePlugins={rehypePlugins(html)}
|
| 70 |
>
|
| 71 |
-
{children}
|
| 72 |
</ReactMarkdown>
|
| 73 |
);
|
| 74 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
remarkPlugins={remarkPlugins(limitedMarkdown)}
|
| 69 |
rehypePlugins={rehypePlugins(html)}
|
| 70 |
>
|
| 71 |
+
{stripCodeFenceFromArtifact(children)}
|
| 72 |
</ReactMarkdown>
|
| 73 |
);
|
| 74 |
});
|
| 75 |
+
|
| 76 |
+
/**
|
| 77 |
+
* Removes code fence markers (```) surrounding an artifact element while preserving the artifact content.
|
| 78 |
+
* This is necessary because artifacts should not be wrapped in code blocks when rendered for rendering action list.
|
| 79 |
+
*
|
| 80 |
+
* @param content - The markdown content to process
|
| 81 |
+
* @returns The processed content with code fence markers removed around artifacts
|
| 82 |
+
*
|
| 83 |
+
* @example
|
| 84 |
+
* // Removes code fences around artifact
|
| 85 |
+
* const input = "```xml\n<div class='__boltArtifact__'></div>\n```";
|
| 86 |
+
* stripCodeFenceFromArtifact(input);
|
| 87 |
+
* // Returns: "\n<div class='__boltArtifact__'></div>\n"
|
| 88 |
+
*
|
| 89 |
+
* @remarks
|
| 90 |
+
* - Only removes code fences that directly wrap an artifact (marked with __boltArtifact__ class)
|
| 91 |
+
* - Handles code fences with optional language specifications (e.g. ```xml, ```typescript)
|
| 92 |
+
* - Preserves original content if no artifact is found
|
| 93 |
+
* - Safely handles edge cases like empty input or artifacts at start/end of content
|
| 94 |
+
*/
|
| 95 |
+
export const stripCodeFenceFromArtifact = (content: string) => {
|
| 96 |
+
if (!content || !content.includes('__boltArtifact__')) {
|
| 97 |
+
return content;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
const lines = content.split('\n');
|
| 101 |
+
const artifactLineIndex = lines.findIndex((line) => line.includes('__boltArtifact__'));
|
| 102 |
+
|
| 103 |
+
// Return original content if artifact line not found
|
| 104 |
+
if (artifactLineIndex === -1) {
|
| 105 |
+
return content;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// Check previous line for code fence
|
| 109 |
+
if (artifactLineIndex > 0 && lines[artifactLineIndex - 1]?.trim().match(/^```\w*$/)) {
|
| 110 |
+
lines[artifactLineIndex - 1] = '';
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
if (artifactLineIndex < lines.length - 1 && lines[artifactLineIndex + 1]?.trim().match(/^```$/)) {
|
| 114 |
+
lines[artifactLineIndex + 1] = '';
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
return lines.join('\n');
|
| 118 |
+
};
|
app/utils/logger.ts
CHANGED
|
@@ -11,7 +11,7 @@ interface Logger {
|
|
| 11 |
setLevel: (level: DebugLevel) => void;
|
| 12 |
}
|
| 13 |
|
| 14 |
-
let currentLevel: DebugLevel =
|
| 15 |
|
| 16 |
const isWorker = 'HTMLRewriter' in globalThis;
|
| 17 |
const supportsColor = !isWorker;
|
|
|
|
| 11 |
setLevel: (level: DebugLevel) => void;
|
| 12 |
}
|
| 13 |
|
| 14 |
+
let currentLevel: DebugLevel = import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV ? 'debug' : 'info';
|
| 15 |
|
| 16 |
const isWorker = 'HTMLRewriter' in globalThis;
|
| 17 |
const supportsColor = !isWorker;
|