helloya20's picture
Upload 2345 files
f0743f4 verified
import React, { useRef, useState, useMemo, useEffect } from 'react';
import copy from 'copy-to-clipboard';
import { InfoIcon } from 'lucide-react';
import { Tools } from 'librechat-data-provider';
import { Clipboard, CheckMark } from '@librechat/client';
import type { CodeBarProps } from '~/common';
import ResultSwitcher from '~/components/Messages/Content/ResultSwitcher';
import { useToolCallsMapContext, useMessageContext } from '~/Providers';
import { LogContent } from '~/components/Chat/Messages/Content/Parts';
import RunCode from '~/components/Messages/Content/RunCode';
import { useLocalize } from '~/hooks';
import cn from '~/utils/cn';
type CodeBlockProps = Pick<
CodeBarProps,
'lang' | 'plugin' | 'error' | 'allowExecution' | 'blockIndex'
> & {
codeChildren: React.ReactNode;
classProp?: string;
};
const CodeBar: React.FC<CodeBarProps> = React.memo(
({ lang, error, codeRef, blockIndex, plugin = null, allowExecution = true }) => {
const localize = useLocalize();
const [isCopied, setIsCopied] = useState(false);
return (
<div className="relative flex items-center justify-between rounded-tl-md rounded-tr-md bg-gray-700 px-4 py-2 font-sans text-xs text-gray-200 dark:bg-gray-700">
<span className="">{lang}</span>
{plugin === true ? (
<InfoIcon className="ml-auto flex h-4 w-4 gap-2 text-white/50" />
) : (
<div className="flex items-center justify-center gap-4">
{allowExecution === true && (
<RunCode lang={lang} codeRef={codeRef} blockIndex={blockIndex} />
)}
<button
type="button"
className={cn(
'ml-auto flex gap-2',
error === true ? 'h-4 w-4 items-start text-white/50' : '',
)}
onClick={async () => {
const codeString = codeRef.current?.textContent;
if (codeString != null) {
setIsCopied(true);
copy(codeString.trim(), { format: 'text/plain' });
setTimeout(() => {
setIsCopied(false);
}, 3000);
}
}}
>
{isCopied ? (
<>
<CheckMark className="h-[18px] w-[18px]" />
{error === true ? '' : localize('com_ui_copied')}
</>
) : (
<>
<Clipboard />
{error === true ? '' : localize('com_ui_copy_code')}
</>
)}
</button>
</div>
)}
</div>
);
},
);
const CodeBlock: React.FC<CodeBlockProps> = ({
lang,
blockIndex,
codeChildren,
classProp = '',
allowExecution = true,
plugin = null,
error,
}) => {
const codeRef = useRef<HTMLElement>(null);
const toolCallsMap = useToolCallsMapContext();
const { messageId, partIndex } = useMessageContext();
const key = allowExecution
? `${messageId}_${partIndex ?? 0}_${blockIndex ?? 0}_${Tools.execute_code}`
: '';
const [currentIndex, setCurrentIndex] = useState(0);
const fetchedToolCalls = toolCallsMap?.[key];
const [toolCalls, setToolCalls] = useState(toolCallsMap?.[key] ?? null);
useEffect(() => {
if (fetchedToolCalls) {
setToolCalls(fetchedToolCalls);
setCurrentIndex(fetchedToolCalls.length - 1);
}
}, [fetchedToolCalls]);
const currentToolCall = useMemo(() => toolCalls?.[currentIndex], [toolCalls, currentIndex]);
const next = () => {
if (!toolCalls) {
return;
}
if (currentIndex < toolCalls.length - 1) {
setCurrentIndex(currentIndex + 1);
}
};
const previous = () => {
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
};
const isNonCode = !!(plugin === true || error === true);
const language = isNonCode ? 'json' : lang;
return (
<div className="w-full rounded-md bg-gray-900 text-xs text-white/80">
<CodeBar
lang={lang}
error={error}
codeRef={codeRef}
blockIndex={blockIndex}
plugin={plugin === true}
allowExecution={allowExecution}
/>
<div className={cn(classProp, 'overflow-y-auto p-4')}>
<code
ref={codeRef}
className={cn(
isNonCode ? '!whitespace-pre-wrap' : `hljs language-${language} !whitespace-pre`,
)}
>
{codeChildren}
</code>
</div>
{allowExecution === true && toolCalls && toolCalls.length > 0 && (
<>
<div className="bg-gray-700 p-4 text-xs">
<div
className="prose flex flex-col-reverse text-white"
style={{
color: 'white',
}}
>
<pre className="shrink-0">
<LogContent
output={(currentToolCall?.result as string | undefined) ?? ''}
attachments={currentToolCall?.attachments ?? []}
renderImages={true}
/>
</pre>
</div>
</div>
{toolCalls.length > 1 && (
<ResultSwitcher
currentIndex={currentIndex}
totalCount={toolCalls.length}
onPrevious={previous}
onNext={next}
/>
)}
</>
)}
</div>
);
};
export default CodeBlock;