Skip to content

Commit

Permalink
add code preview header and truncate too long examples
Browse files Browse the repository at this point in the history
  • Loading branch information
breeg554 committed Oct 11, 2024
1 parent f00e397 commit ef01d48
Showing 1 changed file with 128 additions and 10 deletions.
138 changes: 128 additions & 10 deletions apps/web-remix/app/components/chat/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { AnchorHTMLAttributes } from 'react';
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useMemo, useRef } from 'react';
import { Check, Copy } from 'lucide-react';
import Markdown from 'markdown-to-jsx';
import type { MarkdownToJSX } from 'markdown-to-jsx';
import mermaid from 'mermaid';
import { z } from 'zod';

import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
import { cn } from '~/utils/cn';

interface ChatMarkdownProps {
Expand Down Expand Up @@ -277,25 +279,61 @@ function Link({
);
}

const MAX_VISIBLE_LENGTH = 1000;

function Pre({
children,
className,
...rest
}: React.ParamHTMLAttributes<HTMLPreElement>) {
const { copy, isCopied } = useCopyToClipboard(getStringContent(children));

useEffect(() => {
mermaid.initialize({});
}, []);

const truncatedChildren = useMemo(() => {
return truncateChildrenContent(children, MAX_VISIBLE_LENGTH);
}, [children]);

const language = useMemo(() => {
if (children && typeof children === 'object' && 'props' in children) {
return getLanguage(children.props.className);
}

return null;
}, [children]);

return (
<pre
className={cn(
'my-1 bg-primary text-primary-foreground break-words whitespace-pre-wrap p-2',
className,
)}
{...rest}
>
<code>{children}</code>
</pre>
<div>
<div className="w-full bg-[#38383b] rounded-tl-[0.375rem] rounded-tr-[0.375rem] relative -bottom-1 px-2 py-1 flex justify-between min-h-[30px] items-center">
<span className="text-muted text-xs">{language ?? ''}</span>
<button
className={cn('text-xs flex gap-1 items-center', {
'text-green-500': isCopied,
'text-white': !isCopied,
})}
onClick={copy}
>
{isCopied ? (
<Check className="w-3 h-3" />
) : (
<Copy className="w-3 h-3" />
)}
<span className="text-muted">Copy code</span>
</button>
</div>

<pre
className={cn(
'my-0 bg-primary text-primary-foreground break-words whitespace-pre-wrap p-2 rounded-tl-0 rounded-tr-0',
className,
)}
{...rest}
>
{truncatedChildren}
</pre>
</div>
);
}

Expand Down Expand Up @@ -331,6 +369,7 @@ function Code({
}
return 'Uploaded files';
}

return (
<code
ref={codeRef}
Expand All @@ -352,3 +391,82 @@ function Image({
}: React.ImgHTMLAttributes<HTMLImageElement>) {
return <img alt={alt} className={cn(className)} {...rest} />;
}

function truncateChildrenContent(
children: React.ReactNode,
maxLength: number = 300,
): React.ReactNode {
return React.Children.map(children, (child) => {
if (typeof child === 'string') {
return truncateString(child, maxLength);
}

if (child && typeof child === 'object') {
if ('props' in child && shouldBeTruncated(child)) {
return {
...child,
props: {
...child.props,
children: truncateChildrenContent(child.props.children, maxLength),
},
};
}
}
return child;
});
}

function getStringContent(children: React.ReactNode): string {
return (
React.Children.map(children, (child) => {
if (typeof child === 'string') {
return child;
}

if (child && typeof child === 'object') {
if ('props' in child && shouldBeTruncated(child)) {
return getStringContent(child.props.children);
}
}
return '';
})?.join('\n') ?? ''
);
}

function shouldBeTruncated(child: { props: Record<string, unknown> }) {
if ('className' in child.props && typeof child.props.className === 'string') {
const language = getLanguage(child.props.className);

if (!language) return false;

return [
'json',
'javascript',
'typescript',
'html',
'css',
'sql',
'python',
'java',
'csharp',
'c',
'cpp',
'bash',
'sh',
'yaml',
'xml',
].includes(language);
}

return false;
}

function getLanguage(className?: string) {
return className?.match(/lang-(\w+)/)?.[1];
}

function truncateString(str: string, maxLength: number) {
return str.length > maxLength
? `${str.slice(0, maxLength / 1.5)}\n\n... rest of code`
: str;
}

0 comments on commit ef01d48

Please sign in to comment.