diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx index 0b1f447..9bdb4b7 100644 --- a/src/components/ChatMessage.tsx +++ b/src/components/ChatMessage.tsx @@ -4,6 +4,7 @@ import remarkGfm from 'remark-gfm'; import rehypeHighlight from 'rehype-highlight'; import type { ChatMessage as ChatMessageType, MessageBlock } from '../types'; import { ThinkingBlock } from './ThinkingBlock'; +import { CodeBlock } from './CodeBlock'; import { ToolCall } from './ToolCall'; import { Bot, User, Wrench } from 'lucide-react'; import { t, locale } from '../lib/i18n'; @@ -127,10 +128,12 @@ function getInternalBlocks(blocks: MessageBlock[]): MessageBlock[] { return blocks.filter(b => b.type === 'thinking' || b.type === 'tool_use' || b.type === 'tool_result'); } +const markdownComponents = { pre: CodeBlock }; + function renderTextBlocks(blocks: MessageBlock[]) { return getTextBlocks(blocks).map((block, i) => (
renderer for ReactMarkdown. + * Wraps code blocks with a floating copy button. + */ +export function CodeBlock(props: HTMLAttributes) { + const [copied, setCopied] = useState(false); + + const handleCopy = useCallback(() => { + // Extract text from the nested element + const code = (props.children as any)?.props?.children; + if (typeof code === 'string') { + navigator.clipboard.writeText(code).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + } + }, [props.children]); + + return ( ++ + ++ ); +}