'use client'; import { memo, MouseEvent, useCallback, useEffect, useMemo, useRef, } from 'react'; import { BlockKind, UIBlock } from './block'; import { FileIcon, FullscreenIcon, ImageIcon, LoaderIcon } from './icons'; import { cn, fetcher } from '@/lib/utils'; import { Document } from '@/lib/db/schema'; import { InlineDocumentSkeleton } from './document-skeleton'; import useSWR from 'swr'; import { Editor } from './editor'; import { DocumentToolCall, DocumentToolResult } from './document'; import { CodeEditor } from './code-editor'; import { useBlock } from '@/hooks/use-block'; import equal from 'fast-deep-equal'; import { ImageEditor } from './image-editor'; interface DocumentPreviewProps { isReadonly: boolean; result?: any; args?: any; } export function DocumentPreview({ isReadonly, result, args, }: DocumentPreviewProps) { const { block, setBlock } = useBlock(); const { data: documents, isLoading: isDocumentsFetching } = useSWR< Array >(result ? `/api/document?id=${result.id}` : null, fetcher); const previewDocument = useMemo(() => documents?.[0], [documents]); const hitboxRef = useRef(null); useEffect(() => { const boundingBox = hitboxRef.current?.getBoundingClientRect(); if (block.documentId && boundingBox) { setBlock((block) => ({ ...block, boundingBox: { left: boundingBox.x, top: boundingBox.y, width: boundingBox.width, height: boundingBox.height, }, })); } }, [block.documentId, setBlock]); if (block.isVisible) { if (result) { return ( ); } if (args) { return ( ); } } if (isDocumentsFetching) { return ; } const document: Document | null = previewDocument ? previewDocument : block.status === 'streaming' ? { title: block.title, kind: block.kind, content: block.content, id: block.documentId, createdAt: new Date(), userId: 'noop', } : null; if (!document) return ; return (
); } const LoadingSkeleton = ({ blockKind }: { blockKind: BlockKind }) => (
{blockKind === 'image' ? (
) : (
)}
); const PureHitboxLayer = ({ hitboxRef, result, setBlock, }: { hitboxRef: React.RefObject; result: any; setBlock: (updaterFn: UIBlock | ((currentBlock: UIBlock) => UIBlock)) => void; }) => { const handleClick = useCallback( (event: MouseEvent) => { const boundingBox = event.currentTarget.getBoundingClientRect(); setBlock((block) => block.status === 'streaming' ? { ...block, isVisible: true } : { ...block, title: result.title, documentId: result.id, kind: result.kind, isVisible: true, boundingBox: { left: boundingBox.x, top: boundingBox.y, width: boundingBox.width, height: boundingBox.height, }, }, ); }, [setBlock, result], ); return ( ); }; const HitboxLayer = memo(PureHitboxLayer, (prevProps, nextProps) => { if (!equal(prevProps.result, nextProps.result)) return false; return true; }); const PureDocumentHeader = ({ title, kind, isStreaming, }: { title: string; kind: BlockKind; isStreaming: boolean; }) => (
{isStreaming ? (
) : kind === 'image' ? ( ) : ( )}
{title}
); const DocumentHeader = memo(PureDocumentHeader, (prevProps, nextProps) => { if (prevProps.title !== nextProps.title) return false; if (prevProps.isStreaming !== nextProps.isStreaming) return false; return true; }); const DocumentContent = ({ document }: { document: Document }) => { const { block } = useBlock(); const containerClassName = cn( 'h-[257px] overflow-y-scroll border rounded-b-2xl dark:bg-muted border-t-0 dark:border-zinc-700', { 'p-4 sm:px-14 sm:py-16': document.kind === 'text', 'p-0': document.kind === 'code', }, ); const commonProps = { content: document.content ?? '', isCurrentVersion: true, currentVersionIndex: 0, status: block.status, saveContent: () => {}, suggestions: [], }; return (
{document.kind === 'text' ? ( {}} /> ) : document.kind === 'code' ? (
{}} />
) : document.kind === 'image' ? ( ) : null}
); };