Spaces:
Sleeping
Sleeping
File size: 8,025 Bytes
8e0dd55 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import Mermaid from './Mermaid';
interface MarkdownProps {
content: string;
}
const Markdown: React.FC<MarkdownProps> = ({ content }) => {
// Define markdown components
const MarkdownComponents: React.ComponentProps<typeof ReactMarkdown>['components'] = {
p({ children, ...props }: { children?: React.ReactNode }) {
return <p className="mb-3 text-sm leading-relaxed dark:text-white" {...props}>{children}</p>;
},
h1({ children, ...props }: { children?: React.ReactNode }) {
return <h1 className="text-xl font-bold mt-6 mb-3 dark:text-white" {...props}>{children}</h1>;
},
h2({ children, ...props }: { children?: React.ReactNode }) {
// Special styling for ReAct headings
if (children && typeof children === 'string') {
const text = children.toString();
if (text.includes('Thought') || text.includes('Action') || text.includes('Observation') || text.includes('Answer')) {
return (
<h2
className={`text-base font-bold mt-5 mb-3 p-2 rounded ${
text.includes('Thought') ? 'bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300' :
text.includes('Action') ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300' :
text.includes('Observation') ? 'bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300' :
text.includes('Answer') ? 'bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300' :
'dark:text-white'
}`}
{...props}
>
{children}
</h2>
);
}
}
return <h2 className="text-lg font-bold mt-5 mb-3 dark:text-white" {...props}>{children}</h2>;
},
h3({ children, ...props }: { children?: React.ReactNode }) {
return <h3 className="text-base font-semibold mt-4 mb-2 dark:text-white" {...props}>{children}</h3>;
},
h4({ children, ...props }: { children?: React.ReactNode }) {
return <h4 className="text-sm font-semibold mt-3 mb-2 dark:text-white" {...props}>{children}</h4>;
},
ul({ children, ...props }: { children?: React.ReactNode }) {
return <ul className="list-disc pl-6 mb-4 text-sm dark:text-white space-y-2" {...props}>{children}</ul>;
},
ol({ children, ...props }: { children?: React.ReactNode }) {
return <ol className="list-decimal pl-6 mb-4 text-sm dark:text-white space-y-2" {...props}>{children}</ol>;
},
li({ children, ...props }: { children?: React.ReactNode }) {
return <li className="mb-2 text-sm leading-relaxed dark:text-white" {...props}>{children}</li>;
},
a({ children, href, ...props }: { children?: React.ReactNode; href?: string }) {
return (
<a
href={href}
className="text-purple-600 dark:text-purple-400 hover:underline font-medium"
target="_blank"
rel="noopener noreferrer"
{...props}
>
{children}
</a>
);
},
blockquote({ children, ...props }: { children?: React.ReactNode }) {
return (
<blockquote
className="border-l-4 border-gray-300 dark:border-gray-700 pl-4 py-1 text-gray-700 dark:text-gray-300 italic my-4 text-sm"
{...props}
>
{children}
</blockquote>
);
},
table({ children, ...props }: { children?: React.ReactNode }) {
return (
<div className="overflow-x-auto my-6 rounded-md">
<table className="min-w-full text-sm border-collapse" {...props}>
{children}
</table>
</div>
);
},
thead({ children, ...props }: { children?: React.ReactNode }) {
return <thead className="bg-gray-100 dark:bg-gray-800" {...props}>{children}</thead>;
},
tbody({ children, ...props }: { children?: React.ReactNode }) {
return <tbody className="divide-y divide-gray-200 dark:divide-gray-700" {...props}>{children}</tbody>;
},
tr({ children, ...props }: { children?: React.ReactNode }) {
return <tr className="hover:bg-gray-50 dark:hover:bg-gray-900" {...props}>{children}</tr>;
},
th({ children, ...props }: { children?: React.ReactNode }) {
return (
<th
className="px-4 py-3 text-left font-medium text-gray-700 dark:text-gray-300"
{...props}
>
{children}
</th>
);
},
td({ children, ...props }: { children?: React.ReactNode }) {
return <td className="px-4 py-3 border-t border-gray-200 dark:border-gray-700" {...props}>{children}</td>;
},
code(props: {
inline?: boolean;
className?: string;
children?: React.ReactNode;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any; // Using any here as it's required for ReactMarkdown components
}) {
const { inline, className, children, ...otherProps } = props;
const match = /language-(\w+)/.exec(className || '');
const codeContent = children ? String(children).replace(/\n$/, '') : '';
// Handle Mermaid diagrams
if (!inline && match && match[1] === 'mermaid') {
return (
<div className="my-8 bg-gray-50 dark:bg-gray-800 rounded-md overflow-hidden shadow-sm">
<Mermaid
chart={codeContent}
className="w-full max-w-full"
zoomingEnabled={true}
/>
</div>
);
}
// Handle code blocks
if (!inline && match) {
return (
<div className="my-6 rounded-md overflow-hidden text-sm shadow-sm">
<div className="bg-gray-800 text-gray-200 px-5 py-2 text-sm flex justify-between items-center">
<span>{match[1]}</span>
<button
onClick={() => {
navigator.clipboard.writeText(codeContent);
}}
className="text-gray-400 hover:text-white"
title="Copy code"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</button>
</div>
<SyntaxHighlighter
language={match[1]}
style={tomorrow}
className="!text-sm"
customStyle={{ margin: 0, borderRadius: '0 0 0.375rem 0.375rem', padding: '1rem' }}
showLineNumbers={true}
wrapLines={true}
wrapLongLines={true}
{...otherProps}
>
{codeContent}
</SyntaxHighlighter>
</div>
);
}
// Handle inline code
return (
<code
className={`${className} font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-pink-500 dark:text-pink-400 text-sm`}
{...otherProps}
>
{children}
</code>
);
},
};
return (
<div className="prose prose-base dark:prose-invert max-w-none px-2 py-4">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
components={MarkdownComponents}
>
{content}
</ReactMarkdown>
</div>
);
};
export default Markdown; |