import { useState, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { useMediaQuery, ResizablePanel, ResizableHandleAlt, ResizablePanelGroup, } from '@librechat/client'; import type { TMessage } from 'librechat-data-provider'; import type { ArtifactsContextValue } from '~/Providers'; import { ArtifactsProvider, EditorProvider } from '~/Providers'; import Artifacts from '~/components/Artifacts/Artifacts'; import { getLatestText } from '~/utils'; import store from '~/store'; const DEFAULT_ARTIFACT_PANEL_SIZE = 40; const SHARE_ARTIFACT_PANEL_STORAGE_KEY = 'share:artifacts-panel-size'; const SHARE_ARTIFACT_PANEL_DEFAULT_KEY = 'share:artifacts-panel-size-default'; /** * Gets the initial artifact panel size from localStorage or returns default */ const getInitialArtifactPanelSize = () => { if (typeof window === 'undefined') { return DEFAULT_ARTIFACT_PANEL_SIZE; } const defaultSizeString = String(DEFAULT_ARTIFACT_PANEL_SIZE); const storedDefault = window.localStorage.getItem(SHARE_ARTIFACT_PANEL_DEFAULT_KEY); if (storedDefault !== defaultSizeString) { window.localStorage.setItem(SHARE_ARTIFACT_PANEL_DEFAULT_KEY, defaultSizeString); window.localStorage.removeItem(SHARE_ARTIFACT_PANEL_STORAGE_KEY); return DEFAULT_ARTIFACT_PANEL_SIZE; } const stored = window.localStorage.getItem(SHARE_ARTIFACT_PANEL_STORAGE_KEY); const parsed = Number(stored); return Number.isFinite(parsed) ? parsed : DEFAULT_ARTIFACT_PANEL_SIZE; }; interface ShareArtifactsContainerProps { messages: TMessage[]; conversationId: string; mainContent: React.ReactNode; } /** * Container component that manages artifact visibility and layout for shared conversations */ export function ShareArtifactsContainer({ messages, conversationId, mainContent, }: ShareArtifactsContainerProps) { const artifacts = useRecoilValue(store.artifactsState); const artifactsVisibility = useRecoilValue(store.artifactsVisibility); const isSmallScreen = useMediaQuery('(max-width: 1023px)'); const [artifactPanelSize, setArtifactPanelSize] = useState(getInitialArtifactPanelSize); const artifactsContextValue = useMemo(() => { const latestMessage = Array.isArray(messages) && messages.length > 0 ? messages[messages.length - 1] : null; if (!latestMessage) { return null; } const latestMessageText = getLatestText(latestMessage); return { isSubmitting: false, latestMessageId: latestMessage.messageId ?? null, latestMessageText, conversationId: conversationId ?? null, }; }, [messages, conversationId]); const shouldRenderArtifacts = artifactsVisibility === true && artifactsContextValue != null && Object.keys(artifacts ?? {}).length > 0; const normalizedArtifactSize = Math.min(60, Math.max(20, artifactPanelSize)); /** * Handles artifact panel resize and persists size to localStorage */ const handleLayoutChange = (sizes: number[]) => { if (sizes.length < 2) { return; } const newSize = sizes[1]; if (!Number.isFinite(newSize)) { return; } setArtifactPanelSize(newSize); if (typeof window !== 'undefined') { window.localStorage.setItem(SHARE_ARTIFACT_PANEL_STORAGE_KEY, newSize.toString()); } }; if (!shouldRenderArtifacts || !artifactsContextValue) { return <>{mainContent}; } if (isSmallScreen) { return ( <> {mainContent} ); } return ( {mainContent} ); } interface ShareArtifactsPanelProps { contextValue: ArtifactsContextValue; } /** * Panel that renders the artifacts UI within a resizable container */ function ShareArtifactsPanel({ contextValue }: ShareArtifactsPanelProps) { return (
); } /** * Mobile overlay that displays artifacts in a fixed position */ function ShareArtifactsOverlay({ contextValue }: ShareArtifactsPanelProps) { return (
); }