🤝 feat: View Artifacts in Shared Conversations (#10477)

* feat: Integrate logger for MessageIcon component

* feat: Enhance artifact sharing functionality with updated path checks and read-only state management

* feat: Refactor Thinking and Reasoning components for improved structure and styling

* feat: Enhance artifact sharing with context value management and responsive layout

* feat: Enhance ShareView with theme and language management features

* feat: Improve ThinkingButton accessibility and styling for better user interaction

* feat: Introduce isArtifactRoute utility for route validation in Artifact components

* feat: Add latest message text extraction in SharedView for improved message display

* feat: Update locale handling in SharedView for dynamic date formatting

* feat: Refactor ArtifactsContext and SharedView for improved context handling and styling adjustments

* feat: Enhance artifact panel size management with local storage integration

* chore: imports

* refactor: move ShareArtifactsContainer out of ShareView

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-11-13 22:59:46 +01:00 committed by GitHub
parent cabc8afeac
commit c2505d2bc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 443 additions and 73 deletions

View file

@ -6,8 +6,8 @@ import { useLocation } from 'react-router-dom';
import type { Pluggable } from 'unified';
import type { Artifact } from '~/common';
import { useMessageContext, useArtifactContext } from '~/Providers';
import { logger, extractContent, isArtifactRoute } from '~/utils';
import { artifactsState } from '~/store/artifacts';
import { logger, extractContent } from '~/utils';
import ArtifactButton from './ArtifactButton';
export const artifactPlugin: Pluggable = () => {
@ -88,7 +88,7 @@ export function Artifact({
lastUpdateTime: now,
};
if (!location.pathname.includes('/c/')) {
if (!isArtifactRoute(location.pathname)) {
return setArtifact(currentArtifact);
}

View file

@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom';
import { useRecoilState, useSetRecoilState, useResetRecoilState } from 'recoil';
import type { Artifact } from '~/common';
import FilePreview from '~/components/Chat/Input/Files/FilePreview';
import { cn, getFileType, logger } from '~/utils';
import { cn, getFileType, logger, isArtifactRoute } from '~/utils';
import { useLocalize } from '~/hooks';
import store from '~/store';
@ -37,7 +37,7 @@ const ArtifactButton = ({ artifact }: { artifact: Artifact | null }) => {
return;
}
if (!location.pathname.includes('/c/')) {
if (!isArtifactRoute(location.pathname)) {
return;
}
@ -57,8 +57,6 @@ const ArtifactButton = ({ artifact }: { artifact: Artifact | null }) => {
<div className="group relative my-4 rounded-xl text-sm text-text-primary">
{(() => {
const handleClick = () => {
if (!location.pathname.includes('/c/')) return;
if (isSelected) {
resetCurrentArtifactId();
setVisible(false);

View file

@ -157,6 +157,7 @@ export const ArtifactCodeEditor = function ({
artifact,
editorRef,
sharedProps,
readOnly: externalReadOnly,
}: {
fileKey: string;
artifact: Artifact;
@ -164,6 +165,7 @@ export const ArtifactCodeEditor = function ({
template: SandpackProviderProps['template'];
sharedProps: Partial<SandpackProviderProps>;
editorRef: React.MutableRefObject<CodeEditorRef>;
readOnly?: boolean;
}) {
const { data: config } = useGetStartupConfig();
const { isSubmitting } = useArtifactsContext();
@ -177,10 +179,10 @@ export const ArtifactCodeEditor = function ({
bundlerURL: template === 'static' ? config.staticBundlerURL : config.bundlerURL,
};
}, [config, template, fileKey]);
const [readOnly, setReadOnly] = useState(isSubmitting ?? false);
const [readOnly, setReadOnly] = useState(externalReadOnly ?? isSubmitting ?? false);
useEffect(() => {
setReadOnly(isSubmitting ?? false);
}, [isSubmitting]);
setReadOnly(externalReadOnly ?? isSubmitting ?? false);
}, [isSubmitting, externalReadOnly]);
if (Object.keys(files).length === 0) {
return null;

View file

@ -15,10 +15,12 @@ export default function ArtifactTabs({
artifact,
editorRef,
previewRef,
isSharedConvo,
}: {
artifact: Artifact;
editorRef: React.MutableRefObject<CodeEditorRef>;
previewRef: React.MutableRefObject<SandpackPreviewRef>;
isSharedConvo?: boolean;
}) {
const { isSubmitting } = useArtifactsContext();
const { currentCode, setCurrentCode } = useCodeState();
@ -54,6 +56,7 @@ export default function ArtifactTabs({
artifact={artifact}
editorRef={editorRef}
sharedProps={sharedProps}
readOnly={isSharedConvo}
/>
</Tabs.Content>

View file

@ -4,10 +4,10 @@ import { Code, Play, RefreshCw, X } from 'lucide-react';
import { useSetRecoilState, useResetRecoilState } from 'recoil';
import { Button, Spinner, useMediaQuery, Radio } from '@librechat/client';
import type { SandpackPreviewRef, CodeEditorRef } from '@codesandbox/sandpack-react';
import { useShareContext, useMutationState } from '~/Providers';
import useArtifacts from '~/hooks/Artifacts/useArtifacts';
import DownloadArtifact from './DownloadArtifact';
import ArtifactVersion from './ArtifactVersion';
import { useMutationState } from '~/Providers/EditorContext';
import ArtifactTabs from './ArtifactTabs';
import { CopyCodeButton } from './Code';
import { useLocalize } from '~/hooks';
@ -20,6 +20,7 @@ const MAX_BACKDROP_OPACITY = 0.3;
export default function Artifacts() {
const localize = useLocalize();
const { isMutating } = useMutationState();
const { isSharedConvo } = useShareContext();
const isMobile = useMediaQuery('(max-width: 868px)');
const editorRef = useRef<CodeEditorRef>();
const previewRef = useRef<SandpackPreviewRef>();
@ -294,6 +295,7 @@ export default function Artifacts() {
artifact={currentArtifact}
editorRef={editorRef as React.MutableRefObject<CodeEditorRef>}
previewRef={previewRef as React.MutableRefObject<SandpackPreviewRef>}
isSharedConvo={isSharedConvo}
/>
</div>