From 0a54489842ee9cf2d3b94117cc6c96cfed68771e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 23 Aug 2024 08:03:38 -0400 Subject: [PATCH] feat: second pass --- client/src/common/artifacts.ts | 11 ++- client/src/components/Artifacts/Artifact.tsx | 89 ++++++++++++------ client/src/components/Artifacts/Artifacts.tsx | 18 ++-- client/src/components/Artifacts/Code.tsx | 91 ++++++++----------- .../src/components/Artifacts/CodePreview.tsx | 13 ++- .../Chat/Messages/Content/Markdown.tsx | 4 +- client/src/components/Chat/Presentation.tsx | 9 +- client/src/store/artifacts.ts | 7 +- client/src/utils/artifacts.ts | 42 ++++++--- 9 files changed, 163 insertions(+), 121 deletions(-) diff --git a/client/src/common/artifacts.ts b/client/src/common/artifacts.ts index e472af5b64..0f628922f6 100644 --- a/client/src/common/artifacts.ts +++ b/client/src/common/artifacts.ts @@ -2,4 +2,13 @@ export interface CodeBlock { id: string; language: string; content: string; -} \ No newline at end of file +} + +export interface Artifact { + id: string; + identifier?: string; + language?: string; + content?: string; + title?: string; + type?: string; +} diff --git a/client/src/components/Artifacts/Artifact.tsx b/client/src/components/Artifacts/Artifact.tsx index 972c18a0ce..4612e93580 100644 --- a/client/src/components/Artifacts/Artifact.tsx +++ b/client/src/components/Artifacts/Artifact.tsx @@ -1,9 +1,11 @@ -import { useEffect, useCallback, useRef } from 'react'; +import React, { useEffect, useCallback, useRef, useState } from 'react'; +import throttle from 'lodash/throttle'; import { visit } from 'unist-util-visit'; import { useSetRecoilState } from 'recoil'; -import { artifactsState, artifactIdsState } from '~/store/artifacts'; import type { Pluggable } from 'unified'; -import throttle from 'lodash/throttle'; +import type { Artifact } from '~/common'; +import { artifactsState, artifactIdsState } from '~/store/artifacts'; +import CodePreview from './CodePreview'; export const artifactPlugin: Pluggable = () => { return (tree) => { @@ -18,9 +20,32 @@ export const artifactPlugin: Pluggable = () => { }; }; -export function Artifact({ node, ...props }) { +const extractContent = ( + children: React.ReactNode | { props: { children: React.ReactNode } } | string, +): string => { + if (typeof children === 'string') { + return children; + } + if (React.isValidElement(children)) { + return extractContent((children.props as { children?: React.ReactNode }).children); + } + if (Array.isArray(children)) { + return children.map(extractContent).join(''); + } + return ''; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function Artifact({ + node, + ...props +}: Artifact & { + children: React.ReactNode | { props: { children: React.ReactNode } }; + node: unknown; +}) { const setArtifacts = useSetRecoilState(artifactsState); const setArtifactIds = useSetRecoilState(artifactIdsState); + const [artifact, setArtifact] = useState(null); const throttledUpdateRef = useRef( throttle((updateFn: () => void) => { @@ -29,31 +54,34 @@ export function Artifact({ node, ...props }) { ); const updateArtifact = useCallback(() => { - const content = - props.children && typeof props.children === 'string' - ? props.children - : props.children?.props?.children || ''; + const content = extractContent(props.children); + console.log('Content:', content); - const title = props.title || 'Untitled Artifact'; - const type = props.type || 'unknown'; - const identifier = props.identifier || 'no-identifier'; + const title = props.title ?? 'Untitled Artifact'; + const type = props.type ?? 'unknown'; + const identifier = props.identifier ?? 'no-identifier'; const artifactKey = `${identifier}_${type}_${title}`.replace(/\s+/g, '_').toLowerCase(); throttledUpdateRef.current(() => { + const currentArtifact = { + id: artifactKey, + identifier, + title, + type, + content, + }; + setArtifacts((prevArtifacts) => { - if (prevArtifacts[artifactKey] && prevArtifacts[artifactKey].content === content) { + if ( + (prevArtifacts as Record)[artifactKey] && + prevArtifacts[artifactKey].content === content + ) { return prevArtifacts; } return { ...prevArtifacts, - [artifactKey]: { - id: artifactKey, - identifier, - title, - type, - content, - }, + [artifactKey]: currentArtifact, }; }); @@ -63,19 +91,28 @@ export function Artifact({ node, ...props }) { } return prevIds; }); + + setArtifact(currentArtifact); }); - }, [props, setArtifacts, setArtifactIds]); + + console.log('Artifact updated:', artifactKey); + }, [props.children, props.title, props.type, props.identifier, setArtifacts, setArtifactIds]); useEffect(() => { updateArtifact(); }, [updateArtifact]); return ( -
- {props.title || 'Untitled Artifact'} -

Type: {props.type || 'unknown'}

-

Identifier: {props.identifier || 'No identifier'}

- {props.children} -
+ <> + + {/* {props.children} */} + ); } + +//
+// {props.title ?? 'Untitled Artifact'} +//

Type: {props.type ?? 'unknown'}

+//

Identifier: {props.identifier ?? 'No identifier'}

+// {props.children as React.ReactNode} +//
diff --git a/client/src/components/Artifacts/Artifacts.tsx b/client/src/components/Artifacts/Artifacts.tsx index d5778ce571..35b0f465cf 100644 --- a/client/src/components/Artifacts/Artifacts.tsx +++ b/client/src/components/Artifacts/Artifacts.tsx @@ -2,18 +2,22 @@ import React, { useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; 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 } from '~/utils/artifacts'; +import { sharedOptions, sharedFiles, sharedProps, getArtifactFilename } from '~/utils/artifacts'; +import type { Artifact } from '~/common'; import store from '~/store'; -export function CodeViewer({ +export function ArtifactPreview({ showEditor = false, - content, + artifact, }: { showEditor?: boolean; - content: string; + artifact: Artifact; }) { - const files = { '/App.js': content }; + const files = useMemo(() => { + return removeNullishValues({ [getArtifactFilename(artifact.type ?? '')]: artifact.content }); + }, [artifact.type, artifact.content]); if (Object.keys(files).length === 0) { return null; @@ -128,11 +132,11 @@ export default function Artifacts() { {/* Content */}
-              {currentArtifact.content}
+              {currentArtifact.content}
             
- + {/* Footer */} diff --git a/client/src/components/Artifacts/Code.tsx b/client/src/components/Artifacts/Code.tsx index e83c355d00..9cc9c17b20 100644 --- a/client/src/components/Artifacts/Code.tsx +++ b/client/src/components/Artifacts/Code.tsx @@ -35,7 +35,7 @@ const CodeBar: React.FC = React.memo(({ lang, codeRef }) => { {lang}