text-message.tsx•4.8 kB
import { javascript } from '@codemirror/lang-javascript';
import { githubDark, githubLight } from '@uiw/codemirror-theme-github';
import ReactCodeMirror, {
EditorState,
EditorView,
} from '@uiw/react-codemirror';
import { CodeIcon, Copy } from 'lucide-react';
import React from 'react';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { CopyButton } from '@/components/custom/clipboard/copy-button';
import { useTheme } from '@/components/theme-provider';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { FileResponseInterface } from '@activepieces/shared';
interface TextMessageProps {
content: string;
role: 'user' | 'bot';
attachments?: FileResponseInterface[];
}
export const TextMessage: React.FC<TextMessageProps> = React.memo(
({ content, role }) => {
const { theme } = useTheme();
const extensions = [
theme === 'dark' ? githubDark : githubLight,
EditorState.readOnly.of(true),
EditorView.editable.of(false),
javascript({ jsx: false, typescript: true }),
];
return (
<>
<Markdown
remarkPlugins={[remarkGfm]}
className="bg-inherit"
components={{
code({ node, inline, className, children, ...props }: any) {
if (role === 'user') {
return <div className="font-mono text-sm">{children}</div>;
}
const match = /language-(\w+)/.exec(className || '');
return !inline && match && match[1] ? (
<div
className={cn(
'relative border rounded-md p-4 pt-12',
theme === 'dark' ? 'bg-[#0E1117]' : 'bg-background',
)}
>
<ReactCodeMirror
value={String(children).trim()}
className="border-none"
width="100%"
minWidth="100%"
maxWidth="100%"
minHeight="50px"
basicSetup={{
syntaxHighlighting: true,
foldGutter: false,
lineNumbers: false,
searchKeymap: true,
lintKeymap: true,
autocompletion: false,
highlightActiveLine: false,
highlightActiveLineGutter: false,
highlightSpecialChars: false,
indentOnInput: false,
bracketMatching: false,
closeBrackets: false,
}}
lang={match[1]}
theme={theme === 'dark' ? githubDark : githubLight}
readOnly={true}
extensions={extensions}
/>
<div className="absolute top-4 left-5 text-xs text-gray-500">
<div className="flex items-center gap-1">
<CodeIcon className="size-3" />
<span>{match[1]}</span>
</div>
</div>
<CopyCode
textToCopy={String(children).trim()}
className="absolute top-2 right-2 text-xs text-gray-500"
/>
</div>
) : (
<code
className={cn(
className,
'bg-gray-200 px-[6px] py-[2px] rounded-xs font-mono text-sm',
)}
{...props}
>
{String(children).trim()}
</code>
);
},
}}
>
{content}
</Markdown>
{role === 'bot' && (
<CopyButton
textToCopy={content}
tooltipSide="bottom"
className="size-6 p-1 mt-2"
/>
)}
</>
);
},
(prevProps, nextProps) => {
return (
prevProps.content === nextProps.content &&
prevProps.role === nextProps.role
);
},
);
TextMessage.displayName = 'TextMessage';
const CopyCode = ({
textToCopy,
className,
}: {
textToCopy: string;
className?: string;
}) => {
const [isCopied, setIsCopied] = React.useState(false);
return (
<div className={className}>
<Button
variant="ghost"
className="gap-2"
size="xs"
onClick={() => {
setIsCopied(true);
navigator.clipboard.writeText(textToCopy);
setTimeout(() => setIsCopied(false), 1500);
}}
>
<Copy className="size-4" />
<span className="text-xs">{isCopied ? 'Copied!' : 'Copy Code'}</span>
</Button>
</div>
);
};