Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Document Analyzer | CTRL + ALT + HEAL</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| :root { | |
| --tropical-indigo: rgb(120, 187, 242); | |
| --wisteria: rgb(197, 217, 251); | |
| --latte-cream: #ffffff; | |
| } | |
| body { | |
| font-family: "Rubik", sans-serif; | |
| background-color: var(--latte-cream); | |
| color: #333; | |
| } | |
| nav a { | |
| color: #333; | |
| transition: color 0.3s ease; | |
| } | |
| nav a:hover { | |
| color: var(--tropical-indigo); | |
| } | |
| .rec-card { | |
| background-color: var(--latte-cream); | |
| border: 1px solid var(--wisteria); | |
| border-radius: 1rem; | |
| padding: 1.25rem; | |
| margin-bottom: 1.5rem; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| transition: all 0.3s ease; | |
| } | |
| .rec-card:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); | |
| } | |
| .rec-title { | |
| font-size: 1.125rem; | |
| font-weight: 700; | |
| color: var(--tropical-indigo); | |
| margin-bottom: 0.5rem; | |
| } | |
| .rec-badge { | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 9999px; | |
| } | |
| .badge-high { | |
| background-color: #fee2e2; | |
| color: #b91c1c; | |
| } | |
| .badge-medium { | |
| background-color: #fef3c7; | |
| color: #92400e; | |
| } | |
| .badge-low { | |
| background-color: #d1fae5; | |
| color: #065f46; | |
| } | |
| .rec-content { | |
| list-style-type: disc; | |
| margin-left: 1.25rem; | |
| font-size: 0.875rem; | |
| color: #374151; | |
| } | |
| .rec-link { | |
| color: var(--tropical-indigo); | |
| text-decoration: underline; | |
| } | |
| .rec-link:hover { | |
| color: #4b7bbd; | |
| } | |
| .btn-primary { | |
| background-color: var(--tropical-indigo); | |
| color: white; | |
| transition: background-color 0.3s ease; | |
| } | |
| .btn-primary:hover { | |
| background-color: #5ba5dd; | |
| } | |
| input, | |
| textarea { | |
| border: 1px solid var(--wisteria); | |
| background-color: #fafbfc; | |
| } | |
| input:focus, | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--tropical-indigo); | |
| box-shadow: 0 0 0 2px rgba(120, 187, 242, 0.3); | |
| } | |
| h2, | |
| h3 { | |
| color: var(--tropical-indigo); | |
| } | |
| #chat-output { | |
| background-color: #f7f9fc; | |
| border: 1px solid var(--wisteria); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-[var(--latte-cream)] font-sans text-gray-800 min-h-screen"> | |
| <!-- NAVBAR --> | |
| <nav | |
| class="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-white/20 border-b border-white/30 shadow-md" | |
| > | |
| <div class="flex justify-between items-center w-full px-6 py-4"> | |
| <!-- Logo --> | |
| <a | |
| href="index.html" | |
| class="text-2xl font-bold text-black hover:text-[var(--tropical-indigo)] transition" | |
| > | |
| CTRL + ALT + HEAL | |
| </a> | |
| <ul class="hidden md:flex space-x-6 font-medium text-gray-800"> | |
| <li><a href="index.html" class="nav-link">Home</a></li> | |
| <li><a href="profile.html" class="nav-link">Profile</a></li> | |
| <li><a href="analyzer.html" class="nav-link">Analyzer</a></li> | |
| <li><a href="past_data.html" class="nav-link">Past Reports</a></li> | |
| <li><a href="login.html" class="nav-link">Login</a></li> | |
| </ul> | |
| <!-- Hamburger Menu --> | |
| <button | |
| id="hamburger" | |
| class="md:hidden text-[var(--latte-cream)] text-2xl" | |
| > | |
| ☰ | |
| </button> | |
| </div> | |
| <!-- Mobile Menu --> | |
| <ul | |
| id="mobile-menu" | |
| class="hidden flex-col space-y-4 bg-white/30 backdrop-blur-lg border border-white/20 rounded-xl shadow-lg mt-2 p-4 mx-6 md:hidden" | |
| > | |
| <li> | |
| <a | |
| href="index.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Home</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="analyzer.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Analyzer</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="profile.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Profile</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="login.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Login</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="about.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >About</a | |
| > | |
| </li> | |
| > | |
| </li> | |
| </ul> | |
| </nav> | |
| <!--Shared helpers (API base + query params) --> | |
| <script src="script.js"></script> | |
| <script> | |
| const hamburger = document.getElementById("hamburger"); | |
| const mobileMenu = document.getElementById("mobile-menu"); | |
| hamburger.addEventListener("click", () => { | |
| mobileMenu.classList.toggle("hidden"); | |
| }); | |
| // Underline the active page only | |
| const currentPath = window.location.pathname.split("/").pop(); | |
| document.querySelectorAll(".nav-link").forEach((link) => { | |
| if (link.getAttribute("href") === currentPath) { | |
| link.classList.add("active-page"); // we'll style this in CSS | |
| } | |
| }); | |
| document.querySelectorAll("#mobile-menu a").forEach((link) => { | |
| if (link.getAttribute("href") === currentPath) { | |
| link.classList.add("active-page"); | |
| } | |
| }); | |
| </script> | |
| <!-- CHANGE MARKER TO USE THIS --> | |
| <!-- secound change marker--> | |
| <main class="max-w-5xl mx-auto px-4 mb-16 pt-24"> | |
| <!-- Upload Section --> | |
| <div | |
| class="bg-[var(--latte-cream)] border border-[var(--wisteria)] rounded-lg p-6 mb-8" | |
| > | |
| <h2 class="text-xl font-semibold mb-4"> | |
| Upload & Analyze Your Medical PDF or Image | |
| </h2> | |
| <input | |
| type="file" | |
| id="pdf-upload" | |
| accept=".pdf, image/*" | |
| class="w-full mb-4 rounded px-3 py-2" | |
| /> | |
| <input | |
| type="date" | |
| id="report-date" | |
| class="w-full mb-4 rounded px-3 py-2" | |
| /> | |
| <button id="analyze-btn" class="btn-primary px-4 py-2 rounded"> | |
| Analyze with AI | |
| </button> | |
| <p id="loading" class="text-gray-600 mt-2">No file uploaded yet.</p> | |
| <p id="auth-status" class="text-sm text-gray-500 mt-1"> | |
| Sign in to save and view past analyses. | |
| </p> | |
| </div> | |
| <!-- Extracted Text --> | |
| <div | |
| class="bg-[var(--latte-cream)] border border-[var(--wisteria)] rounded-lg p-6 mb-8" | |
| > | |
| <h3 class="text-lg font-semibold mb-3">Extracted Text</h3> | |
| <div | |
| id="text-output" | |
| class="whitespace-pre-wrap h-60 overflow-auto bg-[#FAFBFC] text-sm border border-[var(--wisteria)] rounded p-4" | |
| > | |
| OCR results will appear here. | |
| </div> | |
| </div> | |
| <!-- AI Findings --> | |
| <div | |
| class="bg-[var(--latte-cream)] border border-[var(--wisteria)] rounded-lg p-6 mb-8" | |
| > | |
| <h3 class="text-lg font-semibold mb-3">AI Findings</h3> | |
| <div | |
| id="recommendations-output" | |
| class="bg-[#F9FAFB] text-sm border border-[var(--wisteria)] rounded p-4 space-y-4" | |
| > | |
| Findings and Recommendations will appear here. | |
| </div> | |
| </div> | |
| <!-- Chatbot --> | |
| <div | |
| class="bg-[var(--latte-cream)] border border-[var(--wisteria)] rounded-lg p-6 mb-8" | |
| > | |
| <h3 class="text-lg font-semibold mb-3">Ask Chatbot</h3> | |
| <div | |
| id="chat-output" | |
| class="space-y-2 h-48 overflow-auto text-sm rounded p-4" | |
| > | |
| <p><strong>Chatbot:</strong> Ask me something about your report</p> | |
| </div> | |
| <div class="flex mt-4 gap-2"> | |
| <input | |
| type="text" | |
| id="user-question" | |
| placeholder="Ask a question..." | |
| class="flex-1 rounded px-3 py-2 focus:outline-none" | |
| /> | |
| <button id="ask-btn" class="btn-primary px-4 py-2 rounded"> | |
| Ask | |
| </button> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Firebase --> | |
| <script type="module"> | |
| import { | |
| getFirestore, | |
| collection, | |
| doc, | |
| addDoc, | |
| serverTimestamp, | |
| } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js"; | |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js"; | |
| import { | |
| getAuth, | |
| onAuthStateChanged, | |
| signOut, | |
| } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-auth.js"; | |
| const firebaseConfig = { | |
| apiKey: "AIzaSyAPhM_Ee7cLzyKHs5zyFy8g5ZOk9-pubRI", | |
| authDomain: "login-tutorial-7a9e1.firebaseapp.com", | |
| projectId: "login-tutorial-7a9e1", | |
| storageBucket: "login-tutorial-7a9e1.firebasestorage.app", | |
| messagingSenderId: "491093197824", | |
| appId: "1:491093197824:web:9f866...", | |
| }; | |
| const app = initializeApp(firebaseConfig); | |
| const auth = getAuth(app); | |
| const db = getFirestore(app); | |
| window.firebaseAuth = auth; | |
| window.onAuthStateChanged = onAuthStateChanged; | |
| window.firestoreDb = db; | |
| window.firestoreHelpers = { collection, doc, addDoc, serverTimestamp }; | |
| onAuthStateChanged(auth, (user) => { | |
| const authNavItem = document.getElementById("authNavItem"); | |
| if (authNavItem) { | |
| if (user) { | |
| authNavItem.innerHTML = | |
| '<button onclick="logout()" class="hover:text-[#6B9080] text-red-600">Logout</button>'; | |
| } else { | |
| authNavItem.innerHTML = | |
| '<a href="login.html" class="hover:text-[#6B9080]">Login</a>'; | |
| } | |
| } | |
| }); | |
| window.logout = async () => { | |
| try { | |
| await signOut(auth); | |
| localStorage.clear(); | |
| window.location.href = "login.html"; | |
| } catch (error) { | |
| console.error("Error signing out:", error); | |
| } | |
| }; | |
| </script> | |
| <script type="module"> | |
| import { | |
| pipeline, | |
| env, | |
| } from "https://cdn.jsdelivr.net/npm/@xenova/[email protected]"; | |
| const loadingEl = document.getElementById("loading"); | |
| const textOutput = document.getElementById("text-output"); | |
| const recsOutput = document.getElementById("recommendations-output"); | |
| const findingsOutput = document.getElementById( | |
| "detected-measurement-results" | |
| ); | |
| const authStatus = document.getElementById("auth-status"); | |
| let extractedText = ""; | |
| let currentUser = null; | |
| document | |
| .getElementById("pdf-upload") | |
| .addEventListener("change", function () { | |
| loadingEl.textContent = this.files.length | |
| ? `File selected: ${this.files[0].name}` | |
| : "No file uploaded yet."; | |
| }); | |
| const saveAnalysis = async (uid, payload) => { | |
| try { | |
| const docRef = await firebase | |
| .firestore() | |
| .collection("users") | |
| .doc(uid) | |
| .collection("analyses") | |
| .add({ | |
| ...payload, | |
| createdAt: serverTimestamp(), | |
| }); | |
| return docRef.id; | |
| } catch (e) { | |
| console.error("Failed to save analysis:", e); | |
| } | |
| }; | |
| const renderRecCard = (rec, idx) => { | |
| const sev = (rec.severity || "").toLowerCase(); | |
| const sevClass = sev.includes("severe") | |
| ? "badge-high" | |
| : sev.includes("moderate") | |
| ? "badge-medium" | |
| : "badge-low"; | |
| return ` | |
| <div class="rec-card"> | |
| <div class="flex items-center justify-between"> | |
| <h4 class="rec-title">Finding ${idx + 1}: ${ | |
| rec.findings || "N/A" | |
| }</h4> | |
| <span class="rec-badge ${sevClass}">${rec.severity || "—"}</span> | |
| </div> | |
| <ul class="rec-content space-y-1"> | |
| <li><em>Recommendations:</em> | |
| <ul class="list-disc list-inside ml-6"> | |
| ${(rec.recommendations || []) | |
| .map((r) => `<li>${r}</li>`) | |
| .join("")} | |
| </ul> | |
| </li> | |
| <li><em>Treatment:</em> ${ | |
| rec.treatment_suggestions || "Not available" | |
| }</li> | |
| <li><em>Home Care:</em> | |
| <ul class="list-disc list-inside ml-6"> | |
| ${(rec.home_care_guidance || []) | |
| .map((r) => `<li>${r}</li>`) | |
| .join("")} | |
| </ul> | |
| </li> | |
| ${ | |
| rec.info_link | |
| ? `<li><a href="${rec.info_link}" target="_blank" class="rec-link">Learn more</a></li>` | |
| : "" | |
| } | |
| </ul> | |
| </div> | |
| `; | |
| }; | |
| window.onAuthStateChanged(window.firebaseAuth, (user) => { | |
| currentUser = user; | |
| if (user) { | |
| authStatus.textContent = `Signed in as ${user.email || user.uid}`; | |
| } else { | |
| authStatus.textContent = | |
| "Not signed in. Sign in to save and view past analyses."; | |
| } | |
| }); | |
| document | |
| .getElementById("analyze-btn") | |
| .addEventListener("click", async () => { | |
| const file = document.getElementById("pdf-upload").files[0]; | |
| const date = document.getElementById("report-date").value; | |
| if (!file) { | |
| loadingEl.textContent = "Please upload a file first."; | |
| return; | |
| } | |
| if (!date) { | |
| loadingEl.textContent = "Please select the report date."; | |
| return; | |
| } | |
| loadingEl.textContent = "Processing with AI..."; | |
| textOutput.textContent = ""; | |
| recsOutput.textContent = ""; | |
| const formData = new FormData(); | |
| formData.append("file", file); | |
| formData.append("model", "bert"); | |
| let data; | |
| try { | |
| const res = await fetch(api("analyze/"), { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| if (!res.ok) throw new Error(await res.text()); | |
| data = await res.json(); | |
| } catch (err) { | |
| console.error(err); | |
| loadingEl.textContent = "Error during analysis: " + err.message; | |
| return; | |
| } | |
| extractedText = data.ocr_text || ""; | |
| textOutput.textContent = extractedText; | |
| const recs = Array.isArray(data.Detected_Anomolies) | |
| ? data.Detected_Anomolies | |
| : data.Detected_Anomolies | |
| ? [data.Detected_Anomolies] | |
| : []; | |
| if (data.Detected_Anomolies) { | |
| recsOutput.innerHTML = recs | |
| .map((rec, i) => renderRecCard(rec, i)) | |
| .join(""); | |
| } else { | |
| recsOutput.textContent = "No recommendations found."; | |
| } | |
| const findings = Array.isArray(data.Detected_Measurement_Values) | |
| ? data.Detected_Measurement_Values | |
| : data.Detected_Measurement_Values | |
| ? [data.Detected_Measurement_Values] | |
| : []; | |
| if (data.Detected_Measurement_Values) { | |
| findingsOutput.innerHTML = findings | |
| .map((finding, i) => renderRecCard(finding, i)) | |
| .join(""); | |
| } | |
| if (currentUser) { | |
| /*await saveAnalysis(currentUser.uid, { | |
| reportDate: date, | |
| ocr_text: extractedText, | |
| anomalies: recs, | |
| measurements: findings, | |
| });*/ | |
| await postReportToBackend({ | |
| user_id: currentUser.email, | |
| report_date: date, | |
| ocr_text: extractedText, | |
| anomalies: JSON.stringify(recs), | |
| measurements: JSON.stringify(findings), | |
| }); | |
| } | |
| loadingEl.textContent = "Analysis complete."; | |
| }); | |
| async function postReportToBackend(report) { | |
| try { | |
| const response = await fetch(api('save_report/'), { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(report), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| console.log('Report successfully sent to backend:', data); | |
| } catch (error) { | |
| console.error('Error sending report to backend:', error); | |
| } | |
| } | |
| document.getElementById("ask-btn").onclick = async () => { | |
| const q = document.getElementById("user-question").value.trim(); | |
| if (!q) return; | |
| const chat = document.getElementById("chat-output"); | |
| chat.innerHTML += `<p><strong>You:</strong> ${q}</p>`; | |
| chat.scrollTop = chat.scrollHeight; | |
| env.allowLocalModels = false; | |
| const qa = await pipeline( | |
| "question-answering", | |
| "Xenova/distilbert-base-uncased-distilled-squad" | |
| ); | |
| const out = await qa(q, extractedText); | |
| chat.innerHTML += `<p><strong>Chatbot:</strong> ${out.answer}</p>`; | |
| document.getElementById("user-question").value = ""; | |
| chat.scrollTop = chat.scrollHeight; | |
| }; | |
| </script> | |
| </body> | |
| </html> |