import { ContentObject, ImageRenditionFormat } from "@vertesia/common"; import { Button, Spinner, useToast } from "@vertesia/ui/core"; import { useNavigate } from "@vertesia/ui/router"; import { useUserSession } from "@vertesia/ui/session"; import { JSONDisplay, MarkdownRenderer } from "@vertesia/ui/widgets"; import { ChevronRight, Download, FileText, Info, LayoutGrid, Maximize2, X, } from "lucide-react"; import { useEffect, useState } from "react"; import { useUITranslation } from '../../../i18n/index.js'; interface DocumentPreviewPanelProps { objectId: string | null; isOpen: boolean; onClose: () => void; } export function DocumentPreviewPanel({ objectId, isOpen, onClose, }: DocumentPreviewPanelProps) { const navigate = useNavigate(); const { client, store } = useUserSession(); const [object, setObject] = useState(null); const [isLoading, setIsLoading] = useState(false); const [loadingText, setLoadingText] = useState(false); const [text, setText] = useState(); const [imageUrl, setImageUrl] = useState(); const [currentTab, setCurrentTab] = useState("preview"); const { t } = useUITranslation(); const toast = useToast(); useEffect(() => { if (objectId && isOpen) { setIsLoading(true); store.objects .retrieve(objectId, "+embeddings") .then((result) => { setObject(result); // If the object has text, use it if (result.text) { setText(result.text); } else { // Otherwise, fetch text loadObjectText(result.id); } // If it's an image, load the image URL const content = result.content; const isImage = content && content.source && content.type && content.type.startsWith("image/"); if (isImage) { loadImageUrl(result); } }) .catch((error) => { console.error("Error loading object:", error); toast({ title: t('agent.error'), description: t('store.failedToLoadDocument'), status: "error", duration: 3000, }); }) .finally(() => { setIsLoading(false); }); } else { // Reset state when panel closes setObject(null); setText(undefined); setImageUrl(undefined); } }, [objectId, isOpen, store.objects]); const loadObjectText = async (id: string) => { setLoadingText(true); try { const result = await store.objects.getObjectText(id); setText(result.text); } catch (error) { console.error("Error loading text:", error); } finally { setLoadingText(false); } }; const loadImageUrl = async (obj: ContentObject) => { try { // Try to get a rendition first const rendition = await client.objects.getRendition(obj.id, { format: ImageRenditionFormat.jpeg, generate_if_missing: false, }); // Don't use rendition object to avoid type issues if (rendition.status === "found") { console.log("Found rendition"); } // Get download URL const downloadUrlResult = await client.files.getDownloadUrl( obj.content.source!, ); setImageUrl(downloadUrlResult.url); } catch (error) { console.error("Error loading image:", error); } }; const handleViewFullDocument = () => { if (object) { navigate(`/legal/objects/${object.id}`); onClose(); } }; const seemsMarkdown = text && (text.startsWith("#") || text.includes("\n#") || text.includes("\n*")); const isImage = object?.content?.type && object.content.type.startsWith("image/"); const isPdf = object?.content?.type === "application/pdf"; if (!isOpen) return null; return (
{/* Header */}

{isLoading ? t('store.loadingDocument') : object?.name || t('store.documentPreview')}

{/* Content */} {isLoading ? (

{t('store.loadingDocument')}

) : (
{/* Tabs */}
{/* Tab Content */}
{currentTab === "preview" && (
{/* Text/Markdown Content */} {loadingText ? (
) : text ? (
{seemsMarkdown ? (
{text}
) : (
                        {text}
                      
)}
) : null} {/* Image Content */} {isImage && (
{imageUrl ? (
{object?.name}
) : (
)}
)} {/* PDF Content Notice */} {isPdf && (

{t('store.pdfPreviewFullView')}

)} {/* No Content Notice */} {!isImage && !text && !isPdf && !loadingText && (

{t('store.noPreviewAvailable')}

)}
)} {currentTab === "properties" && object && (
{object.properties ? (
) : (

{t('store.noPropertiesAvailable')}

)}

{t('store.documentInformation')}

{t('store.id')}
{object.id}
{t('store.contentType')}
{object.type?.name || t('store.unknown')}
{t('store.contentType')}
{object.content?.type || t('store.na')}
{t('store.size')}
{object.content && "size" in object.content ? formatFileSize(object.content.size as number) : t('store.unknown')}
{t('store.created')}
{object.created_at ? new Date(object.created_at).toLocaleString() : t('store.na')}
{t('store.updated')}
{object.updated_at ? new Date(object.updated_at).toLocaleString() : t('store.na')}
)}
)} {/* Footer actions */}
{object?.content?.source && ( )}
); } function formatFileSize(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }