style: code styling

This commit is contained in:
Danny Avila 2024-08-23 16:14:14 -04:00
parent 0a54489842
commit b3e3788261
4 changed files with 38 additions and 110 deletions

View file

@ -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(() => {

View file

@ -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} />

View file

@ -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>
);
},

View file

@ -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>);
};