Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ const handleRuleExtraction = async (guid: string) => {
}
};

const fetchRiskDataWithSources = async (medication: string, source: "include" | "diagnosis" = "include") => {
try {
const response = await api.post(`/v1/api/riskWithSources`, {
drug: medication,
source: source,
});
return response.data;
} catch (error) {
console.error("Error fetching risk data: ", error);
throw error;
}
};

interface StreamCallbacks {
onContent?: (content: string) => void;
Expand Down Expand Up @@ -165,7 +177,6 @@ const handleSendDrugSummaryStream = async (
}
};


// Legacy function for backward compatibility
const handleSendDrugSummaryStreamLegacy = async (
message: string,
Expand Down Expand Up @@ -256,7 +267,6 @@ const updateConversationTitle = async (
}
};


export {
handleSubmitFeedback,
handleSendDrugSummary,
Expand All @@ -268,5 +278,6 @@ export {
deleteConversation,
updateConversationTitle,
handleSendDrugSummaryStream,
handleSendDrugSummaryStreamLegacy
handleSendDrugSummaryStreamLegacy,
fetchRiskDataWithSources
};
139 changes: 78 additions & 61 deletions frontend/src/pages/DrugSummary/PDFViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface DocumentLoadSuccess {
numPages: number;
}

const PAGE_INIT_DELAY = 800;

const PDFViewer = () => {
const [numPages, setNumPages] = useState<number | null>(null);
const [pageNumber, setPageNumber] = useState(1);
Expand All @@ -24,81 +26,96 @@ const PDFViewer = () => {
null
);

const manualScrollInProgress = useRef(false);
const PAGE_INIT_DELAY = 800;

const headerRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const pageRefs = useRef<Record<number, HTMLDivElement | null>>({});
const initializationRef = useRef(false);
const prevGuidRef = useRef<string | null>(null);
const isFetchingRef = useRef(false);

const location = useLocation();
const navigate = useNavigate();
const params = new URLSearchParams(location.search);
const guid = params.get("guid");
const pageParam = params.get("page");

const baseURL = import.meta.env.VITE_API_BASE_URL;
const pdfUrl = useMemo(
() => (guid ? `${baseURL}/v1/api/uploadFile/${guid}` : null),
[guid, baseURL]
);
const baseURL = import.meta.env.VITE_API_BASE_URL as string | undefined;

const pdfUrl = useMemo(() => {
const url = guid && baseURL ? `${baseURL}/v1/api/uploadFile/${guid}` : null;

return url;
}, [guid, baseURL]);

useEffect(() => {
pageRefs.current = {};
setIsDocumentLoaded(false);
initializationRef.current = false;
const nextPage = pageParam ? parseInt(pageParam, 10) : 1;
const guidChanged = guid !== prevGuidRef.current;

if (guidChanged) {
pageRefs.current = {};
setIsDocumentLoaded(false);
setNumPages(null);
setPdfData(null);
setPageNumber(1);
}

if (pageParam) {
const page = parseInt(pageParam, 10);
if (!isNaN(page) && page > 0) setTargetPageAfterLoad(page);
if (!isNaN(nextPage) && nextPage > 0) {
setTargetPageAfterLoad(nextPage);
} else {
setTargetPageAfterLoad(1);
}
}, [guid, pageParam]);

prevGuidRef.current = guid;
}, [guid, pageParam, location.pathname, location.search]);

const scrollToPage = useCallback(
(page: number) => {
if (page < 1 || !numPages || page > numPages) return;
if (!numPages || page < 1 || page > numPages) {
return;
}

const targetRef = pageRefs.current[page];
if (!targetRef) return;

manualScrollInProgress.current = true;
targetRef.scrollIntoView({ behavior: "smooth", block: "start" });

const observer = new IntersectionObserver(
(entries, obs) => {
const entry = entries[0];
if (entry?.isIntersecting) {
manualScrollInProgress.current = false;
obs.disconnect();
}
},
{ threshold: 0.5 }
);
observer.observe(targetRef);
if (!targetRef) {
setTimeout(() => scrollToPage(page), 100);
return;
}

const newParams = new URLSearchParams(location.search);
newParams.set("page", String(page));
navigate(`${location.pathname}?${newParams.toString()}`, {
replace: true,
targetRef.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});

const newParams = new URLSearchParams(location.search);
const oldPage = newParams.get("page");
if (oldPage !== String(page)) {
newParams.set("page", String(page));
const newUrl = `${location.pathname}?${newParams.toString()}`;
navigate(newUrl, { replace: true });
}

setPageNumber(page);
},
[numPages, navigate, location.pathname, location.search]
[numPages, location.pathname, location.search, navigate, pageNumber]
);

// Preload-aware navigation: if not loaded yet, just remember target page.
const goToPage = useCallback(
(page: number) => {
if (typeof page !== "number" || isNaN(page)) return;
if (page < 1) page = 1;
else if (numPages && page > numPages) page = numPages;

setPageNumber(page);
scrollToPage(page);
const clamped = Math.max(1, numPages ? Math.min(page, numPages) : page);

if (!isDocumentLoaded || !numPages) {
setTargetPageAfterLoad(clamped);
return;
}

if (clamped === pageNumber) return;
setPageNumber(clamped);
scrollToPage(clamped);
},
[numPages, scrollToPage]
[isDocumentLoaded, numPages, pageNumber, scrollToPage]
);

useEffect(() => {
Expand All @@ -115,21 +132,16 @@ const PDFViewer = () => {
}, [goToPage]);

useEffect(() => {
if (
isDocumentLoaded &&
numPages &&
targetPageAfterLoad &&
Object.keys(pageRefs.current).length > 0
) {
if (isDocumentLoaded && numPages && targetPageAfterLoad) {
const validPage = Math.min(Math.max(1, targetPageAfterLoad), numPages);
setPageNumber(validPage);

const timeoutId = setTimeout(() => {
const timer = setTimeout(() => {
scrollToPage(validPage);
setTargetPageAfterLoad(null);
}, PAGE_INIT_DELAY);

return () => clearTimeout(timeoutId);
return () => clearTimeout(timer);
}
}, [isDocumentLoaded, numPages, targetPageAfterLoad, scrollToPage]);

Expand Down Expand Up @@ -169,7 +181,13 @@ const PDFViewer = () => {

const fetchPdf = useCallback(async () => {
if (!pdfUrl) return;
if (isFetchingRef.current) {
console.log("⏳ fetchPdf already in progress, skipping duplicate call");
return;
}

try {
isFetchingRef.current = true;
setLoading(true);
setError(null);
const token = localStorage.getItem("access");
Expand All @@ -193,6 +211,7 @@ const PDFViewer = () => {
setPdfData(null);
} finally {
setLoading(false);
isFetchingRef.current = false;
}
}, [pdfUrl, isPDF]);

Expand Down Expand Up @@ -230,13 +249,11 @@ const PDFViewer = () => {
</button>
<span className="text-sm">
Page {pageNumber} of {numPages || "-"}
Page {pageNumber} of {numPages ?? "-"}
</span>
<button
onClick={() =>
goToPage(Math.min(pageNumber + 1, numPages || pageNumber))
}
disabled={!numPages || pageNumber >= numPages}
onClick={() => goToPage(pageNumber + 1)}
disabled={!numPages || pageNumber >= (numPages ?? 1)}
className="px-3 py-1 bg-white border rounded"
>
Expand Down Expand Up @@ -282,6 +299,7 @@ const PDFViewer = () => {
style={{ maxHeight: containerSize.height }}
>
<Document
key={guid ?? "doc"}
file={file}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(err) => setError(err.message)}
Expand All @@ -294,18 +312,17 @@ const PDFViewer = () => {
<div
key={pageNum}
ref={(el) => {
if (el) pageRefs.current[pageNum] = el;
pageRefs.current[pageNum] = el;
}}
className="mb-4 w-full"
data-page={pageNum}
>
<Page
pageNumber={pageNum}
scale={scale}
width={Math.max(0, (containerSize.width || 0) - 50)}
renderTextLayer={true}
renderAnnotationLayer={true}
renderAnnotationLayer={false}
className="shadow-lg"
height={containerSize.height || undefined}
width={(containerSize.width || 0) - 50}
/>
<div className="text-center text-gray-500 text-sm mt-1">
Page {pageNum} of {numPages}
Expand Down
Loading