mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-30 15:18:50 +01:00
style: code styling
This commit is contained in:
parent
0a54489842
commit
b3e3788261
4 changed files with 38 additions and 110 deletions
|
|
@ -55,7 +55,6 @@ export function Artifact({
|
|||
|
||||
const updateArtifact = useCallback(() => {
|
||||
const content = extractContent(props.children);
|
||||
console.log('Content:', content);
|
||||
|
||||
const title = props.title ?? 'Untitled Artifact';
|
||||
const type = props.type ?? 'unknown';
|
||||
|
|
@ -94,8 +93,6 @@ export function Artifact({
|
|||
|
||||
setArtifact(currentArtifact);
|
||||
});
|
||||
|
||||
console.log('Artifact updated:', artifactKey);
|
||||
}, [props.children, props.title, props.type, props.identifier, setArtifacts, setArtifactIds]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,15 @@ import * as Tabs from '@radix-ui/react-tabs';
|
|||
import { Sandpack } from '@codesandbox/sandpack-react';
|
||||
import { removeNullishValues } from 'librechat-data-provider';
|
||||
import { SandpackPreview, SandpackProvider } from '@codesandbox/sandpack-react/unstyled';
|
||||
import { sharedOptions, sharedFiles, sharedProps, getArtifactFilename } from '~/utils/artifacts';
|
||||
import type { Artifact } from '~/common';
|
||||
import {
|
||||
sharedFiles,
|
||||
sharedProps,
|
||||
sharedOptions,
|
||||
getFileExtension,
|
||||
getArtifactFilename,
|
||||
} from '~/utils/artifacts';
|
||||
import { CodeMarkdown } from './Code';
|
||||
import store from '~/store';
|
||||
|
||||
export function ArtifactPreview({
|
||||
|
|
@ -53,8 +60,8 @@ export function ArtifactPreview({
|
|||
|
||||
export default function Artifacts() {
|
||||
const [activeTab, setActiveTab] = useState('code');
|
||||
const artifactIds = useRecoilValue(store.artifactIdsState);
|
||||
const artifacts = useRecoilValue(store.artifactsState);
|
||||
const artifactIds = useRecoilValue(store.artifactIdsState);
|
||||
|
||||
const [currentArtifactIndex, setCurrentArtifactIndex] = useState(artifactIds.length - 1);
|
||||
|
||||
|
|
@ -82,7 +89,9 @@ export default function Artifacts() {
|
|||
|
||||
return (
|
||||
<Tabs.Root value={activeTab} onValueChange={setActiveTab} asChild>
|
||||
{/* Main Parent */}
|
||||
<div className="flex h-full w-full items-center justify-center py-2">
|
||||
{/* Main Container */}
|
||||
<div className="flex h-[97%] w-[97%] flex-col overflow-hidden rounded-xl border border-border-medium bg-surface-primary text-xl text-text-primary shadow-xl">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border-medium bg-surface-primary-alt p-2">
|
||||
|
|
@ -128,12 +137,13 @@ export default function Artifacts() {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<Tabs.Content value="code" className="flex-grow overflow-auto bg-surface-secondary">
|
||||
<pre className="h-full w-full overflow-auto rounded bg-surface-primary-alt p-2 text-sm">
|
||||
{currentArtifact.content}
|
||||
</pre>
|
||||
<Tabs.Content value="code" className="flex-grow overflow-auto bg-gray-900">
|
||||
<CodeMarkdown
|
||||
content={`\`\`\`${getFileExtension(currentArtifact.type)}\n${
|
||||
currentArtifact.content ?? ''
|
||||
}\`\`\``}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="preview" className="flex-grow overflow-auto bg-surface-secondary">
|
||||
<ArtifactPreview artifact={currentArtifact} />
|
||||
|
|
|
|||
|
|
@ -1,74 +1,23 @@
|
|||
import React, { useRef, useState, RefObject, memo, useEffect } from 'react';
|
||||
|
||||
import copy from 'copy-to-clipboard';
|
||||
import React, { useRef, RefObject, memo } from 'react';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import type { PluggableList } from 'unified';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import { useDebounceCodeBlock } from './useDebounceCodeBlock';
|
||||
import { handleDoubleClick, cn, langSubset } from '~/utils';
|
||||
import Clipboard from '~/components/svg/Clipboard';
|
||||
import CheckMark from '~/components/svg/CheckMark';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import CodePreview from './CodePreview';
|
||||
|
||||
type CodeBarProps = {
|
||||
lang: string;
|
||||
codeRef: RefObject<HTMLElement>;
|
||||
};
|
||||
|
||||
interface CodeBlockArtifactProps {
|
||||
lang: string;
|
||||
codeString: string;
|
||||
artifactId: string;
|
||||
}
|
||||
type CodeBlockProps = Pick<CodeBarProps, 'lang'> & {
|
||||
codeChildren: React.ReactNode;
|
||||
classProp?: string;
|
||||
};
|
||||
|
||||
const CodeBar: React.FC<CodeBarProps> = React.memo(({ lang, codeRef }) => {
|
||||
const localize = useLocalize();
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
return (
|
||||
<div className="relative flex items-center 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>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-auto flex gap-2"
|
||||
onClick={async () => {
|
||||
const codeString = codeRef.current?.textContent;
|
||||
if (codeString != null) {
|
||||
setIsCopied(true);
|
||||
copy(codeString, { format: 'text/plain' });
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 3000);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckMark className="h-[18px] w-[18px]" />
|
||||
{localize('com_ui_copied')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Clipboard />
|
||||
{localize('com_ui_copy_code')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const CodeBlock: React.FC<CodeBlockProps> = ({ lang, codeChildren, classProp = '' }) => {
|
||||
const codeRef = useRef<HTMLElement>(null);
|
||||
return (
|
||||
<div className="w-full rounded-md bg-gray-900 text-xs text-white/80">
|
||||
<CodeBar lang={lang} codeRef={codeRef} />
|
||||
<div className={cn(classProp, 'overflow-y-auto p-4')}>
|
||||
<code ref={codeRef} className={`hljs language-${lang} !whitespace-pre`}>
|
||||
{codeChildren}
|
||||
|
|
@ -101,17 +50,9 @@ export const code: React.ElementType = memo(({ inline, className, children }: TC
|
|||
|
||||
const cursor = ' ';
|
||||
export const CodeMarkdown = memo(
|
||||
({
|
||||
content = '',
|
||||
showCursor,
|
||||
isLatestMessage,
|
||||
}: {
|
||||
content: string;
|
||||
showCursor?: boolean;
|
||||
isLatestMessage: boolean;
|
||||
}) => {
|
||||
({ content = '', showCursor }: { content: string; showCursor?: boolean }) => {
|
||||
const currentContent = content;
|
||||
const rehypePlugins: PluggableList = [
|
||||
const rehypePlugins = [
|
||||
[rehypeKatex, { output: 'mathml' }],
|
||||
[
|
||||
rehypeHighlight,
|
||||
|
|
@ -125,11 +66,16 @@ export const CodeMarkdown = memo(
|
|||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
/* @ts-ignore */
|
||||
rehypePlugins={rehypePlugins}
|
||||
// linkTarget="_new"
|
||||
components={{ code }}
|
||||
components={
|
||||
{ code } as {
|
||||
[key: string]: React.ElementType;
|
||||
}
|
||||
}
|
||||
>
|
||||
{isLatestMessage && showCursor === true ? currentContent + cursor : currentContent}
|
||||
{showCursor === true ? currentContent + cursor : currentContent}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,20 +15,20 @@ export function getArtifactFilename(type: string): string {
|
|||
return artifactFilename[type] ?? 'App.tsx';
|
||||
}
|
||||
|
||||
export function getFileExtension(language: string): string {
|
||||
export function getFileExtension(language?: string): string {
|
||||
switch (language) {
|
||||
case 'javascript':
|
||||
return 'jsx';
|
||||
case 'typescript':
|
||||
case 'application/vnd.react':
|
||||
return 'tsx';
|
||||
case 'jsx':
|
||||
return 'jsx';
|
||||
case 'tsx':
|
||||
return 'tsx';
|
||||
case 'html':
|
||||
case 'text/html':
|
||||
return 'html';
|
||||
case 'css':
|
||||
return 'css';
|
||||
// case 'jsx':
|
||||
// return 'jsx';
|
||||
// case 'tsx':
|
||||
// return 'tsx';
|
||||
// case 'html':
|
||||
// return 'html';
|
||||
// case 'css':
|
||||
// return 'css';
|
||||
default:
|
||||
return 'txt';
|
||||
}
|
||||
|
|
@ -99,28 +99,3 @@ export const sharedFiles = {
|
|||
</html>
|
||||
`,
|
||||
};
|
||||
|
||||
export const filenameMap = {
|
||||
tsx: 'App',
|
||||
css: 'styles',
|
||||
html: 'index',
|
||||
jsx: 'App',
|
||||
js: 'App',
|
||||
ts: 'App',
|
||||
typescript: 'App',
|
||||
javascript: 'App',
|
||||
};
|
||||
|
||||
export const mapCodeFiles = (
|
||||
codeBlockIds: string[],
|
||||
codeBlocks: Record<string, CodeBlock | undefined>,
|
||||
) => {
|
||||
return codeBlockIds.reduce((acc, id) => {
|
||||
const block = codeBlocks[id];
|
||||
if (block) {
|
||||
const fileName = `${filenameMap[block.language]}.${getFileExtension(block.language)}`;
|
||||
acc[fileName] = typeof block.content === 'string' ? block.content : '';
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue