bolt.diy/app/components/chat/CodeBlock.tsx
2025-06-05 21:23:06 -05:00

99 lines
3.1 KiB
TypeScript

import { memo, useEffect, useState } from 'react';
import { bundledLanguages, codeToHtml, isSpecialLang, type BundledLanguage, type SpecialLanguage } from 'shiki';
import { classNames } from '~/utils/classNames';
import { createScopedLogger } from '~/utils/logger';
import styles from './CodeBlock.module.scss';
const logger = createScopedLogger('CodeBlock');
interface CodeBlockProps {
className?: string;
code: string;
language?: BundledLanguage | SpecialLanguage;
theme?: 'light-plus' | 'dark-plus';
disableCopy?: boolean;
}
export const CodeBlock = memo(
({ className, code, language = 'plaintext', theme = 'dark-plus', disableCopy = false }: CodeBlockProps) => {
const [html, setHTML] = useState<string | undefined>(undefined);
const [copied, setCopied] = useState(false);
const copyToClipboard = () => {
if (copied) {
return;
}
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
};
useEffect(() => {
let effectiveLanguage = language;
if (language && !isSpecialLang(language) && !(language in bundledLanguages)) {
logger.warn(`Unsupported language '${language}', falling back to plaintext`);
effectiveLanguage = 'plaintext';
}
logger.trace(`Language = ${effectiveLanguage}`);
const processCode = async () => {
setHTML(await codeToHtml(code, { lang: effectiveLanguage, theme }));
};
processCode();
}, [code, language, theme]);
return (
<div className={classNames('relative group text-left', className)}>
<div
className={classNames(
styles.CopyButtonContainer,
'bg-transparant absolute top-[10px] right-[10px] rounded-md z-10 text-lg flex items-center justify-center gap-1 opacity-0 group-hover:opacity-100',
{
'rounded-l-0 opacity-100': copied,
},
)}
>
{!disableCopy && (
<button
className={classNames(
'copy-button flex items-center bg-accent-500 p-[6px] justify-center before:bg-white before:rounded-l-md before:text-gray-500 before:border-r before:border-gray-300 rounded-md transition-theme',
{
'before:opacity-0': !copied,
'before:opacity-100': copied,
},
)}
title="Copy Code"
onClick={() => copyToClipboard()}
>
<div className="i-ph:clipboard-text-duotone"></div>
</button>
)}
<button
className="flex items-center bg-accent-500 p-[6px] justify-center rounded-md transition-theme"
title="Ask Bolt about this code"
onClick={() => {
const askFn = (window as any).__BOLT_ASK_SNIPPET__;
if (typeof askFn === 'function') {
askFn(code, language);
}
}}
>
<div className="i-ph:chat-circle-duotone"></div>
</button>
</div>
<div dangerouslySetInnerHTML={{ __html: html ?? '' }}></div>
</div>
);
},
);