mirror of
https://github.com/harivansh-afk/asap.it.git
synced 2026-04-17 05:00:16 +00:00
first commit
This commit is contained in:
commit
1cdbffff09
200 changed files with 30007 additions and 0 deletions
118
app/components/chat/Markdown.tsx
Normal file
118
app/components/chat/Markdown.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { memo, useMemo } from 'react';
|
||||
import ReactMarkdown, { type Components } from 'react-markdown';
|
||||
import type { BundledLanguage } from 'shiki';
|
||||
import { createScopedLogger } from '~/utils/logger';
|
||||
import { rehypePlugins, remarkPlugins, allowedHTMLElements } from '~/utils/markdown';
|
||||
import { Artifact } from './Artifact';
|
||||
import { CodeBlock } from './CodeBlock';
|
||||
|
||||
import styles from './Markdown.module.scss';
|
||||
|
||||
const logger = createScopedLogger('MarkdownComponent');
|
||||
|
||||
interface MarkdownProps {
|
||||
children: string;
|
||||
html?: boolean;
|
||||
limitedMarkdown?: boolean;
|
||||
}
|
||||
|
||||
export const Markdown = memo(({ children, html = false, limitedMarkdown = false }: MarkdownProps) => {
|
||||
logger.trace('Render');
|
||||
|
||||
const components = useMemo(() => {
|
||||
return {
|
||||
div: ({ className, children, node, ...props }) => {
|
||||
if (className?.includes('__boltArtifact__')) {
|
||||
const messageId = node?.properties.dataMessageId as string;
|
||||
|
||||
if (!messageId) {
|
||||
logger.error(`Invalid message id ${messageId}`);
|
||||
}
|
||||
|
||||
return <Artifact messageId={messageId} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
pre: (props) => {
|
||||
const { children, node, ...rest } = props;
|
||||
|
||||
const [firstChild] = node?.children ?? [];
|
||||
|
||||
if (
|
||||
firstChild &&
|
||||
firstChild.type === 'element' &&
|
||||
firstChild.tagName === 'code' &&
|
||||
firstChild.children[0].type === 'text'
|
||||
) {
|
||||
const { className, ...rest } = firstChild.properties;
|
||||
const [, language = 'plaintext'] = /language-(\w+)/.exec(String(className) || '') ?? [];
|
||||
|
||||
return <CodeBlock code={firstChild.children[0].value} language={language as BundledLanguage} {...rest} />;
|
||||
}
|
||||
|
||||
return <pre {...rest}>{children}</pre>;
|
||||
},
|
||||
} satisfies Components;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
allowedElements={allowedHTMLElements}
|
||||
className={styles.MarkdownContent}
|
||||
components={components}
|
||||
remarkPlugins={remarkPlugins(limitedMarkdown)}
|
||||
rehypePlugins={rehypePlugins(html)}
|
||||
>
|
||||
{stripCodeFenceFromArtifact(children)}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Removes code fence markers (```) surrounding an artifact element while preserving the artifact content.
|
||||
* This is necessary because artifacts should not be wrapped in code blocks when rendered for rendering action list.
|
||||
*
|
||||
* @param content - The markdown content to process
|
||||
* @returns The processed content with code fence markers removed around artifacts
|
||||
*
|
||||
* @example
|
||||
* // Removes code fences around artifact
|
||||
* const input = "```xml\n<div class='__boltArtifact__'></div>\n```";
|
||||
* stripCodeFenceFromArtifact(input);
|
||||
* // Returns: "\n<div class='__boltArtifact__'></div>\n"
|
||||
*
|
||||
* @remarks
|
||||
* - Only removes code fences that directly wrap an artifact (marked with __boltArtifact__ class)
|
||||
* - Handles code fences with optional language specifications (e.g. ```xml, ```typescript)
|
||||
* - Preserves original content if no artifact is found
|
||||
* - Safely handles edge cases like empty input or artifacts at start/end of content
|
||||
*/
|
||||
export const stripCodeFenceFromArtifact = (content: string) => {
|
||||
if (!content || !content.includes('__boltArtifact__')) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
const artifactLineIndex = lines.findIndex((line) => line.includes('__boltArtifact__'));
|
||||
|
||||
// Return original content if artifact line not found
|
||||
if (artifactLineIndex === -1) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Check previous line for code fence
|
||||
if (artifactLineIndex > 0 && lines[artifactLineIndex - 1]?.trim().match(/^```\w*$/)) {
|
||||
lines[artifactLineIndex - 1] = '';
|
||||
}
|
||||
|
||||
if (artifactLineIndex < lines.length - 1 && lines[artifactLineIndex + 1]?.trim().match(/^```$/)) {
|
||||
lines[artifactLineIndex + 1] = '';
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue