@llamaindex/ui
Version:
A comprehensive UI component library built with React, TypeScript, and Tailwind CSS for LlamaIndex applications
378 lines (374 loc) • 14.1 kB
JavaScript
'use strict';
var chunkAETTG7GC_js = require('./chunk-AETTG7GC.js');
require('./chunk-NJMLJSEA.js');
require('./chunk-BOVUEAAQ.js');
require('./chunk-U3IWFKKJ.js');
require('./chunk-HK7TFVDA.js');
var chunk4E3IDRQJ_js = require('./chunk-4E3IDRQJ.js');
var react = require('react');
var reactPdf = require('react-pdf');
var jsxRuntime = require('react/jsx-runtime');
if (typeof window !== "undefined") {
reactPdf.pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${reactPdf.pdfjs.version}/build/pdf.worker.min.mjs`;
}
import('react-pdf/dist/Page/AnnotationLayer.css');
import('react-pdf/dist/Page/TextLayer.css');
var pdfOptions = {
cMapUrl: `https://unpkg.com/pdfjs-dist@${reactPdf.pdfjs.version}/cmaps/`,
wasmUrl: `https://unpkg.com/pdfjs-dist@${reactPdf.pdfjs.version}/wasm/`
};
var FILE_SIZE_THRESHOLD = 10 * 1024 * 1024;
var PdfPreviewImpl = ({
fileName,
url,
onDownload,
onRemove,
highlights,
toolbarClassName,
maxPages,
maxPagesWarning
}) => {
const [numPages, setNumPages] = react.useState();
const [currentPage, setCurrentPage] = react.useState(1);
const [scale, setScale] = react.useState(1);
const [file, setFile] = react.useState(null);
const [isLoading, setIsLoading] = react.useState(false);
const [renderedPages, setRenderedPages] = react.useState(0);
const [isRendering, setIsRendering] = react.useState(true);
const containerRef = react.useRef(null);
const pageRefs = react.useRef({});
const isInitialScaleSet = react.useRef(false);
const [pageBaseDims, setPageBaseDims] = react.useState({});
const [showHighlights, setShowHighlights] = react.useState(true);
const hasPageLimit = typeof maxPages === "number" && Number.isFinite(maxPages) && maxPages > 0;
const effectiveNumPages = react.useMemo(() => {
if (!numPages) return numPages;
if (!hasPageLimit) return numPages;
return Math.min(numPages, maxPages != null ? maxPages : numPages);
}, [numPages, hasPageLimit, maxPages]);
const showMaxPagesWarning = hasPageLimit && !!maxPagesWarning && !!numPages && numPages > (maxPages != null ? maxPages : 0);
const highlightsByPage = react.useMemo(() => {
if (!highlights || highlights.length === 0) {
return {};
}
const grouped = {};
highlights.forEach((highlight, idx) => {
if (!grouped[highlight.page]) {
grouped[highlight.page] = [];
}
grouped[highlight.page].push({
id: `highlight-${highlight.page}-${idx}`,
x: highlight.x,
y: highlight.y,
width: highlight.width,
height: highlight.height,
color: "rgba(255, 215, 0, 0.25)"
});
});
return grouped;
}, [highlights]);
function onDocumentLoadSuccess({ numPages: numPages2 }) {
setNumPages(numPages2);
setRenderedPages(0);
setIsRendering(true);
}
function handlePageRenderSuccess() {
setRenderedPages((prev) => {
const next = prev + 1;
if (effectiveNumPages && next === effectiveNumPages) {
setIsRendering(false);
}
return next;
});
}
const goToPage = react.useCallback(
(pageNumber) => {
const maxPage = effectiveNumPages != null ? effectiveNumPages : 1;
const targetPage = Math.min(Math.max(pageNumber, 1), maxPage);
setCurrentPage(targetPage);
const pageElement = pageRefs.current[targetPage];
if (pageElement && containerRef.current) {
pageElement.scrollIntoView({
behavior: "instant",
block: "center"
});
}
},
[effectiveNumPages]
);
react.useEffect(() => {
if (!highlights || highlights.length === 0) return;
if (!effectiveNumPages) return;
const firstHighlight = highlights[0];
if (firstHighlight.page > effectiveNumPages) return;
const pageEl = pageRefs.current[firstHighlight.page];
if (pageEl) {
goToPage(firstHighlight.page);
setShowHighlights(true);
}
}, [highlights, effectiveNumPages, goToPage]);
react.useEffect(() => {
if (!effectiveNumPages) return;
setCurrentPage((prev) => Math.min(prev, effectiveNumPages));
}, [effectiveNumPages]);
const handleLoadPage = (page) => {
const viewport = page.getViewport({ scale: 1 });
setPageBaseDims((prev) => chunk4E3IDRQJ_js.__spreadProps(chunk4E3IDRQJ_js.__spreadValues({}, prev), {
[page.pageNumber]: {
width: viewport.width,
height: viewport.height
}
}));
if (!isInitialScaleSet.current && page.pageNumber === 1 && containerRef.current) {
const containerWidth = containerRef.current.clientWidth;
const padding = 16;
const availableWidth = containerWidth - padding;
const newScale = availableWidth / viewport.width;
setScale(newScale);
isInitialScaleSet.current = true;
}
};
const handleClickOnPage = () => {
if (showHighlights) {
setShowHighlights(false);
}
};
react.useEffect(() => {
const handleScroll = () => {
if (!containerRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
const containerCenter = containerRect.top + containerRect.height / 2;
let closestPage = 1;
let closestDistance = Infinity;
Object.entries(pageRefs.current).forEach(([pageNumber, element]) => {
if (element) {
const rect = element.getBoundingClientRect();
const pageCenter = rect.top + rect.height / 2;
const distance = Math.abs(pageCenter - containerCenter);
if (distance < closestDistance) {
closestDistance = distance;
closestPage = parseInt(pageNumber);
}
}
});
const totalPages = effectiveNumPages != null ? effectiveNumPages : closestPage;
const clampedPage = Math.min(Math.max(closestPage, 1), totalPages);
setCurrentPage(clampedPage);
};
const container = containerRef.current;
if (container) {
container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}
}, [effectiveNumPages]);
const lastLoadedUrl = react.useRef(null);
react.useEffect(() => {
if (lastLoadedUrl.current === url) {
return;
}
lastLoadedUrl.current = url;
const fetchFile = async () => {
setIsLoading(true);
const response = await fetch(url);
const blob = await response.blob();
setFile(
new File([blob], fileName != null ? fileName : "document.pdf", {
type: "application/pdf"
})
);
setIsLoading(false);
};
fetchFile();
return () => {
setFile(null);
};
}, [url, fileName]);
react.useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleKeyDown = (event) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
if (currentPage > 1) {
goToPage(currentPage - 1);
}
} else if (event.key === "ArrowRight") {
event.preventDefault();
const totalPages = effectiveNumPages != null ? effectiveNumPages : currentPage;
if (currentPage < totalPages) {
goToPage(currentPage + 1);
}
} else if (event.key === "=" || event.key === "+") {
event.preventDefault();
setScale((prev) => Math.min(prev + 0.25, 3));
} else if (event.key === "-") {
event.preventDefault();
setScale((prev) => Math.max(prev - 0.25, 0.5));
}
};
container.addEventListener("keydown", handleKeyDown);
container.tabIndex = 0;
return () => {
container.removeEventListener("keydown", handleKeyDown);
};
}, [currentPage, effectiveNumPages, goToPage]);
const handleDownload = () => {
if (onDownload) {
onDownload();
} else {
if (file) {
const blobUrl = URL.createObjectURL(file);
const link = document.createElement("a");
link.href = blobUrl;
link.download = file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
} else {
window.open(url, "_blank");
}
}
};
const handleReset = () => {
setCurrentPage(1);
goToPage(1);
};
const toggleFullscreen = () => {
var _a;
if (!document.fullscreenElement) {
(_a = containerRef.current) == null ? void 0 : _a.requestFullscreen();
} else {
document.exitFullscreen();
}
};
if (isLoading) {
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full flex flex-col", children: [
/* @__PURE__ */ jsxRuntime.jsx(
chunkAETTG7GC_js.FileToolbar,
{
fileName,
onRemove,
className: toolbarClassName
}
),
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 bg-[#F3F3F3]" }),
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex-1 flex items-center justify-center bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-2" }),
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600", children: "Loading PDF..." })
] }) })
] });
}
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full flex flex-col", children: [
isRendering && file && file.size > FILE_SIZE_THRESHOLD && /* @__PURE__ */ jsxRuntime.jsx(
PdfRenderingProgress,
{
renderedPages,
numPages: effectiveNumPages
}
),
effectiveNumPages && effectiveNumPages > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsx(
chunkAETTG7GC_js.FileToolbar,
{
fileName,
currentPage,
totalPages: effectiveNumPages,
scale,
onPageChange: goToPage,
onScaleChange: setScale,
onDownload: handleDownload,
onRemove,
onReset: handleReset,
onFullscreen: toggleFullscreen,
className: toolbarClassName,
isOverlay: true
}
),
showMaxPagesWarning && /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
role: "alert",
className: "bg-amber-50 border border-amber-200 text-amber-900 px-4 py-2 text-sm",
children: maxPagesWarning != null ? maxPagesWarning : ""
}
),
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 bg-[#F3F3F3]" })
] }),
/* @__PURE__ */ jsxRuntime.jsx(
"div",
{
ref: containerRef,
className: "overflow-auto h-full bg-[#F3F3F3] rounded-lg flex-1 min-h-0",
children: /* @__PURE__ */ jsxRuntime.jsx(
reactPdf.Document,
{
file,
onLoadSuccess: onDocumentLoadSuccess,
loading: isLoading,
options: pdfOptions,
children: Array.from({ length: effectiveNumPages != null ? effectiveNumPages : 0 }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
ref: (el) => {
pageRefs.current[index + 1] = el;
},
className: "mb-4 flex justify-center",
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-block", children: [
/* @__PURE__ */ jsxRuntime.jsx(
reactPdf.Page,
{
pageNumber: index + 1,
scale,
renderTextLayer: true,
renderAnnotationLayer: true,
onLoadSuccess: handleLoadPage,
onClick: handleClickOnPage,
onRenderSuccess: handlePageRenderSuccess
}
),
showHighlights && highlightsByPage[index + 1] && pageBaseDims[index + 1] && /* @__PURE__ */ jsxRuntime.jsx(
chunkAETTG7GC_js.BoundingBoxOverlay,
{
boundingBoxes: highlightsByPage[index + 1],
zoom: scale,
containerWidth: pageBaseDims[index + 1].width,
containerHeight: pageBaseDims[index + 1].height
}
)
] })
},
`page_${index + 1}`
))
}
)
}
)
] });
};
function PdfRenderingProgress({
renderedPages,
numPages
}) {
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-50 bg-opacity-90 z-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white shadow-lg rounded-2xl p-6 w-80 text-center", children: [
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-10 w-10 border-4 border-primary border-t-transparent" }) }),
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-800 mb-2", children: "Rendering PDF\u2026" }),
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-600 mb-3", children: [
"Page ",
renderedPages,
" of ",
numPages != null ? numPages : "?"
] }),
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-2.5 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "bg-primary h-2.5 rounded-full transition-all duration-300",
style: {
width: numPages && numPages > 0 ? `${Math.round(renderedPages / numPages * 100)}%` : "0%"
}
}
) }),
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Large PDFs or those with heavy images may take longer to render." })
] }) });
}
exports.PdfPreviewImpl = PdfPreviewImpl;