| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="utf-8" /> |
| | <title>Plagiarism Check β TrueWrite Scan</title> |
| | <meta name="viewport" content="width=device-width,initial-scale=1" /> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| | </head> |
| | <body class="bg-gradient-to-br from-slate-950 via-slate-900 to-violet-950 text-white min-h-screen flex flex-col"> |
| | <main class="flex-1 max-w-6xl mx-auto px-4 py-8"> |
| | |
| | <div class="flex items-center justify-between mb-4"> |
| | <div class="flex items-center gap-3"> |
| | <div> |
| | <img src="logo.png" alt="TrueWrite Scan Logo" class="w-10 h-10 rounded-xl shadow-lg shadow-indigo-500/40"> |
| | </div> |
| | <div> |
| | <h1 class="text-2xl md:text-3xl font-extrabold">TrueWrite <span class="text-blue-400">Scan</span></h1> |
| | <p class="text-[11px] text-slate-300 uppercase tracking-[0.25em]"> |
| | Plagiarism Checker |
| | </p> |
| | </div> |
| | </div> |
| | <a href="dashboard.html" |
| | class="text-xs text-slate-200 px-3 py-1.5 rounded-full border border-slate-600/70 bg-slate-900/40 backdrop-blur hover:bg-slate-800/70 transition"> |
| | β Back to dashboard |
| | </a> |
| | </div> |
| |
|
| | |
| | <nav class="mb-6"> |
| | <div class="inline-flex items-center gap-1 rounded-full bg-slate-900/80 border border-slate-800 p-1 text-xs"> |
| | <a href="grammar.html" |
| | class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800"> |
| | Grammar |
| | </a> |
| | <a href="plagiarism.html" |
| | class="px-3 py-1.5 rounded-full bg-blue-500 text-white font-medium shadow shadow-blue-500/40" |
| | aria-current="page"> |
| | Plagiarism |
| | </a> |
| | <a href="ai-check.html" |
| | class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800"> |
| | AI Content |
| | </a> |
| | </div> |
| | </nav> |
| |
|
| | |
| | <div class="grid md:grid-cols-2 gap-6"> |
| | |
| | <section class="rounded-3xl border border-white/10 bg-slate-900/40 backdrop-blur-xl shadow-2xl shadow-black/50 p-5 md:p-6 flex flex-col"> |
| | <header class="mb-4"> |
| | <h2 class="text-lg md:text-xl font-semibold">Analyze your content</h2> |
| | <p class="text-xs text-slate-300 mt-1"> |
| | Paste text or drop a <span class="font-semibold">.txt / .pdf / .docx</span> file. This gives an idea of ββhow much Plagiarized content there is. |
| | </p> |
| | </header> |
| | |
| | <div id="dropZone" |
| | class="mb-3 border-2 border-dashed border-slate-600/80 rounded-2xl bg-slate-900/50 backdrop-blur-md px-4 py-3 text-xs flex items-center justify-between transition hover:border-blue-400/80 hover:bg-slate-900/70"> |
| | <div class="flex items-center gap-3"> |
| | <div class="w-8 h-8 rounded-full bg-slate-800/80 flex items-center justify-center"> |
| | <span class="text-lg">π</span> |
| | </div> |
| | <div> |
| | <div class="font-semibold text-sm">Drag & drop file</div> |
| | <div class="text-xs text-slate-400">Supported: .txt, .pdf, .docx (max 15MB)</div> |
| | </div> |
| | </div> |
| | <div class="text-right"> |
| | <label class="px-3 py-1 rounded-full border border-slate-600 cursor-pointer bg-slate-800/60 text-xs"> |
| | Browse <input id="fileInput" type="file" accept=".txt,.pdf,.docx" class="hidden" /> |
| | </label> |
| | <div id="fileName" class="text-xs text-slate-400 mt-1 max-w-[160px] truncate"></div> |
| | </div> |
| | </div> |
| |
|
| | <div class="mt-3 text-xs text-slate-400"> |
| | Tip: .txt files preview locally; .pdf/.docx will be uploaded and parsed by the server. |
| | </div> |
| |
|
| | <div class="mt-3 flex items-center justify-between gap-3"> |
| | <button id="pasteBtn" |
| | class="text-xs px-3 py-1 rounded-full border border-slate-600 hover:bg-slate-800"> |
| | Paste text |
| | </button> |
| | <span id="statusTiny" class="text-xs text-slate-400">Ready</span> |
| | </div> |
| |
|
| | <textarea id="inputText" rows="12" |
| | class="w-full mt-3 p-3 bg-slate-950/60 border border-slate-800 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#0487D9] |
| | placeholder="Paste text here or load a .txt file (preview)."></textarea> |
| |
|
| | <div class="mt-4 flex flex-wrap gap-2 items-center"> |
| | <button id="checkBtn" |
| | class="px-5 py-2 rounded-xl bg-[#0487D9] hover:bg-[#0487D9] text-sm font-medium flex items-center gap-2"> |
| | <svg id="spinner" class="hidden animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none"> |
| | <circle cx="12" cy="12" r="10" stroke="white" stroke-opacity="0.25" stroke-width="4"></circle> |
| | <path d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" fill="white" fill-opacity="0.8"></path> |
| | </svg> |
| | <span id="checkLabel">Check Plagiarism</span> |
| | </button> |
| |
|
| | <button id="downloadBtn" |
| | class="px-5 py-2 rounded-xl bg-slate-800 border border-slate-700 text-sm font-medium"> |
| | Download report as PDF |
| | </button> |
| | </div> |
| | </section> |
| |
|
| | |
| | <section class="p-5 rounded-3xl bg-slate-900/40 border border-white/5 backdrop-blur text-sm"> |
| | <h2 class="font-semibold text-lg mb-2">Result</h2> |
| |
|
| | |
| | <div class="h-2.5 w-full rounded-full bg-slate-800 overflow-hidden mb-2"> |
| | <div id="progressBar" class="h-full w-0 bg-[#0487D9] transition-all"></div> |
| | </div> |
| | <div id="progressText" class="text-xs text-slate-400 mb-3">Idle</div> |
| |
|
| | |
| | <p id="score" class="text-lg font-bold mb-1 text-[#0487D9]">No scan yet.</p> |
| |
|
| | |
| | <div class="flex items-center gap-4 mb-4"> |
| | <div class="relative w-24 h-24"> |
| | <svg viewBox="0 0 100 100" class="w-24 h-24 transform -rotate-90"> |
| | |
| | <circle cx="50" cy="50" r="40" |
| | stroke="rgba(148, 163, 184, 0.35)" |
| | stroke-width="8" |
| | fill="none" /> |
| | |
| | <circle id="gaugeCircle" cx="50" cy="50" r="40" |
| | stroke="rgb(59, 130, 246)" |
| | stroke-width="8" |
| | fill="none" |
| | stroke-linecap="round" |
| | stroke-dasharray="251.2" |
| | stroke-dashoffset="251.2" /> |
| | </svg> |
| | <div class="absolute inset-0 flex items-center justify-center"> |
| | <span id="gaugeLabel" class="text-xs font-semibold text-slate-100">0%</span> |
| | </div> |
| | </div> |
| |
|
| | <div> |
| | <span id="riskBadge" |
| | class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-slate-800 text-slate-200 border border-slate-600"> |
| | No risk yet |
| | </span> |
| | <p class="mt-1 text-[11px] text-slate-400"> |
| | Risk level updates after each scan based on plagiarism percentage. |
| | </p> |
| | </div> |
| | </div> |
| |
|
| | <p id="summary" class="text-sm text-slate-200 mb-3"> |
| | Run a scan to see estimated plagiarism percentage based on the demo corpus. |
| | </p> |
| |
|
| | <div class="text-xs text-slate-400 mb-3"> |
| | <ul class="list-disc list-inside space-y-1"> |
| | <li>Educational demo β not connected to real academic databases.</li> |
| | <li>Backend uses TF-IDF and set-based similarity against local corpus files.</li> |
| | </ul> |
| | </div> |
| |
|
| | <h3 class="font-semibold text-sm mt-2 mb-1">Top matches in corpus</h3> |
| | <div id="matches" class="mt-1 text-xs text-slate-300 space-y-2"> |
| | |
| | </div> |
| | </section> |
| | </div> |
| |
|
| | |
| | <section class="mt-10 space-y-4"> |
| | <h2 class="text-xl md:text-2xl font-semibold">Why choose this plagiarism checker?</h2> |
| | <p class="text-sm text-slate-300"> |
| | TrueWrite Scanβs plagiarism page is built to help you understand how similarity checks work, |
| | from input text to percentage score and PDF report. |
| | </p> |
| |
|
| | <div class="grid md:grid-cols-3 gap-5 mt-3"> |
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center"> |
| | <span class="text-lg">π</span> |
| | </div> |
| | <p class="font-semibold text-sm">Clear percentage score</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | See an easy-to-understand plagiarism percentage instead of raw technical metrics. |
| | </p> |
| | </div> |
| |
|
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center"> |
| | <span class="text-lg">π</span> |
| | </div> |
| | <p class="font-semibold text-sm">Backend similarity engine</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | The backend uses TF-IDF and set-based similarity to compare your text with a demo corpus. |
| | </p> |
| | </div> |
| |
|
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center"> |
| | <span class="text-lg">π</span> |
| | </div> |
| | <p class="font-semibold text-sm">Instant PDF reports</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | Export a mini plagiarism report so you can attach it with your draft or keep it for records. |
| | </p> |
| | </div> |
| | </div> |
| | </section> |
| |
|
| | <section class="mt-10 space-y-4"> |
| | <h2 class="text-xl md:text-2xl font-semibold">Who can use this tool?</h2> |
| | <p class="text-sm text-slate-300"> |
| | Anyone who wants a first originality check before using heavy enterprise tools. |
| | </p> |
| |
|
| | <div class="grid md:grid-cols-3 gap-5 mt-3"> |
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-sky-500/20 flex items-center justify-center"> |
| | <span class="text-lg">π</span> |
| | </div> |
| | <p class="font-semibold text-sm">Students & project teams</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | Check reports, abstracts, and essays for overlap with sample academic-style text. |
| | </p> |
| | </div> |
| |
|
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-amber-500/20 flex items-center justify-center"> |
| | <span class="text-lg">π©βπ«</span> |
| | </div> |
| | <p class="font-semibold text-sm">Educators</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | Demonstrate the concept of similarity checking in class with a clear, visual UI. |
| | </p> |
| | </div> |
| |
|
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-rose-500/20 flex items-center justify-center"> |
| | <span class="text-lg">βοΈ</span> |
| | </div> |
| | <p class="font-semibold text-sm">Bloggers & writers</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | Quickly see if short articles look too similar to built-in reference styles. |
| | </p> |
| | </div> |
| | </div> |
| | </section> |
| |
|
| | <section class="mt-10 space-y-4"> |
| | <h2 class="text-xl md:text-2xl font-semibold">How does this plagiarism checker work?</h2> |
| | <p class="text-sm text-slate-300"> |
| | The logic is intentionally simple and transparent so you can follow and later extend it with |
| | more advanced databases or APIs. |
| | </p> |
| |
|
| | <div class="grid md:grid-cols-3 gap-5 mt-3"> |
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center"> |
| | <span class="text-lg">1οΈβ£</span> |
| | </div> |
| | <p class="font-semibold text-sm">Clean and vectorise</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | Your text is cleaned and transformed into TF-IDF vectors on the server. |
| | </p> |
| | </div> |
| |
|
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center"> |
| | <span class="text-lg">2οΈβ£</span> |
| | </div> |
| | <p class="font-semibold text-sm">Compare with corpus</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | The backend compares your text with stored documents using cosine and set-based similarity. |
| | </p> |
| | </div> |
| |
|
| | <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4"> |
| | <div class="flex items-center gap-3 mb-2"> |
| | <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center"> |
| | <span class="text-lg">3οΈβ£</span> |
| | </div> |
| | <p class="font-semibold text-sm">Return a percentage</p> |
| | </div> |
| | <p class="text-xs text-slate-300"> |
| | The highest overlap is converted into a simple percentage and returned to this page with top matches. |
| | </p> |
| | </div> |
| | </div> |
| | </section> |
| |
|
| | <section class="mt-12"> |
| | <h2 class="text-xl md:text-2xl font-semibold mb-3">Top reviews for this plagiarism tool</h2> |
| | <div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 md:p-6 relative overflow-hidden"> |
| | <div id="reviewCard" class="transition-all duration-500"></div> |
| |
|
| | <div class="flex items-center justify-between mt-4"> |
| | <div id="reviewDots" class="flex gap-1.5"></div> |
| | <div class="flex gap-2"> |
| | <button id="prevReview" |
| | class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">βΉ</button> |
| | <button id="nextReview" |
| | class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">βΊ</button> |
| | </div> |
| | </div> |
| | </div> |
| | </section> |
| | </main> |
| |
|
| | |
| | <footer class="border-t border-slate-800"> |
| | <div class="max-w-6xl mx-auto px-4 py-4 text-[11px] text-slate-400 flex flex-col md:flex-row items-center justify-between gap-2"> |
| | <p>Β© 2025 TrueWrite Scan. All rights reserved.</p> |
| | <p>Designed & developed by <span class="text-blue-300 font-medium">Gopal Krushna Mahapatra</span>.</p> |
| | </div> |
| | </footer> |
| |
|
| | |
| | <script> |
| | const BACKEND_URL = "https://gopalkrushnamahapatra-truewrite-scan-backend.hf.space"; |
| | const token = localStorage.getItem("truewriteToken"); |
| | const user = localStorage.getItem("truewriteUser"); |
| | if (!token || !user) { |
| | window.location.href = "login.html"; |
| | } |
| | |
| | |
| | function getJsPDF() { |
| | if (window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF; |
| | if (window.jsPDF) return window.jsPDF; |
| | alert("PDF library (jsPDF) did not load. Check your internet connection or CDN access."); |
| | return null; |
| | } |
| | |
| | |
| | const dropZone = document.getElementById("dropZone"); |
| | const fileInput = document.getElementById("fileInput"); |
| | const fileName = document.getElementById("fileName"); |
| | const textarea = document.getElementById("inputText"); |
| | const checkBtn = document.getElementById("checkBtn"); |
| | const spinner = document.getElementById("spinner"); |
| | const checkLabel = document.getElementById("checkLabel"); |
| | const statusTiny = document.getElementById("statusTiny"); |
| | const progressBar = document.getElementById("progressBar"); |
| | const progressText = document.getElementById("progressText"); |
| | const summaryP = document.getElementById("summary"); |
| | const matchesDiv = document.getElementById("matches"); |
| | const downloadBtn = document.getElementById("downloadBtn"); |
| | const scoreP = document.getElementById("score"); |
| | const pasteBtn = document.getElementById("pasteBtn"); |
| | |
| | const gaugeCircle = document.getElementById("gaugeCircle"); |
| | const gaugeLabel = document.getElementById("gaugeLabel"); |
| | const riskBadge = document.getElementById("riskBadge"); |
| | const GAUGE_CIRCUMFERENCE = 251.2; |
| | |
| | let lastResult = null; |
| | let progressTimer = null; |
| | |
| | |
| | function updateGauge(percent) { |
| | const p = Math.max(0, Math.min(100, Number(percent) || 0)); |
| | const offset = GAUGE_CIRCUMFERENCE * (1 - p / 100); |
| | gaugeCircle.style.strokeDashoffset = offset; |
| | gaugeLabel.textContent = p.toFixed(0) + "%"; |
| | } |
| | |
| | function updateRiskBadge(percent) { |
| | const p = Math.max(0, Math.min(100, Number(percent) || 0)); |
| | let label, extraClasses; |
| | |
| | if (p <= 20) { |
| | label = "Safe"; |
| | extraClasses = "bg-blue-500/20 text-blue-300 border border-blue-500/60"; |
| | } else if (p <= 50) { |
| | label = "Medium risk"; |
| | extraClasses = "bg-amber-500/20 text-amber-300 border border-amber-500/60"; |
| | } else { |
| | label = "High risk"; |
| | extraClasses = "bg-rose-500/20 text-rose-300 border border-rose-500/60"; |
| | } |
| | |
| | riskBadge.className = |
| | "inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold " + extraClasses; |
| | riskBadge.textContent = label; |
| | } |
| | |
| | |
| | function setLoading(on) { |
| | if (on) { |
| | spinner.classList.remove("hidden"); |
| | checkLabel.textContent = "Checking..."; |
| | checkBtn.disabled = true; |
| | checkBtn.classList.add("opacity-80"); |
| | statusTiny.textContent = "Working..."; |
| | progressBar.style.width = "5%"; |
| | progressText.textContent = "Uploading/processing..."; |
| | progressTimer = setInterval(() => { |
| | let cur = parseFloat(progressBar.style.width) || 5; |
| | cur = Math.min(90, cur + Math.random() * 8); |
| | progressBar.style.width = cur + "%"; |
| | }, 250); |
| | } else { |
| | spinner.classList.add("hidden"); |
| | checkLabel.textContent = "Check Plagiarism"; |
| | checkBtn.disabled = false; |
| | checkBtn.classList.remove("opacity-80"); |
| | if (progressTimer) clearInterval(progressTimer); |
| | progressBar.style.width = "100%"; |
| | progressText.textContent = "Done"; |
| | setTimeout(() => { |
| | progressBar.style.width = "0%"; |
| | progressText.textContent = "Idle"; |
| | }, 700); |
| | } |
| | } |
| | |
| | |
| | ["dragenter", "dragover"].forEach(e => |
| | dropZone.addEventListener(e, ev => { |
| | ev.preventDefault(); |
| | dropZone.classList.add("ring-2", "ring-[#0487D9]"); |
| | }) |
| | ); |
| | ["dragleave", "drop"].forEach(e => |
| | dropZone.addEventListener(e, ev => { |
| | ev.preventDefault(); |
| | dropZone.classList.remove("ring-2", "ring-[#0487D9]"); |
| | }) |
| | ); |
| | |
| | dropZone.addEventListener("drop", ev => { |
| | ev.preventDefault(); |
| | const f = ev.dataTransfer.files[0]; |
| | if (!f) return; |
| | fileInput.files = ev.dataTransfer.files; |
| | fileName.textContent = f.name; |
| | handleSelectedFile(f); |
| | }); |
| | |
| | fileInput.addEventListener("change", ev => { |
| | const f = ev.target.files[0]; |
| | if (!f) return; |
| | fileName.textContent = f.name; |
| | handleSelectedFile(f); |
| | }); |
| | |
| | function handleSelectedFile(file) { |
| | const fname = file.name || "file"; |
| | if (file.type === "text/plain" || fname.toLowerCase().endsWith(".txt")) { |
| | const reader = new FileReader(); |
| | reader.onload = () => { |
| | textarea.value = reader.result; |
| | statusTiny.textContent = `${fname} loaded for preview`; |
| | }; |
| | reader.readAsText(file); |
| | } else { |
| | textarea.value = ""; |
| | statusTiny.textContent = `${fname} selected β will be uploaded and parsed on server when you click Check`; |
| | } |
| | } |
| | |
| | |
| | pasteBtn.addEventListener("click", async () => { |
| | try { |
| | const text = await navigator.clipboard.readText(); |
| | textarea.value = text; |
| | statusTiny.textContent = "Pasted from clipboard."; |
| | } catch (err) { |
| | alert("Clipboard access blocked by browser."); |
| | } |
| | }); |
| | |
| | |
| | async function callPlagiarismText(text) { |
| | const res = await fetch(`${BACKEND_URL}/api/plagiarism-check`, { |
| | method: "POST", |
| | headers: { |
| | "Content-Type": "application/json", |
| | "Authorization": `Bearer ${token}` |
| | }, |
| | body: JSON.stringify({ text }) |
| | }); |
| | if (!res.ok) { |
| | const txt = await res.text(); |
| | throw new Error(txt || `Server responded ${res.status}`); |
| | } |
| | return res.json(); |
| | } |
| | |
| | async function callPlagiarismFile(file) { |
| | const form = new FormData(); |
| | form.append("file", file, file.name); |
| | const res = await fetch(`${BACKEND_URL}/api/plagiarism-check-file`, { |
| | method: "POST", |
| | headers: { "Authorization": `Bearer ${token}` }, |
| | body: form |
| | }); |
| | if (!res.ok) { |
| | const txt = await res.text(); |
| | throw new Error(txt || `Server responded ${res.status}`); |
| | } |
| | return res.json(); |
| | } |
| | |
| | |
| | checkBtn.addEventListener("click", async () => { |
| | const file = fileInput.files[0]; |
| | const text = (textarea.value || "").trim(); |
| | |
| | if (!file && !text) { |
| | alert("Please paste text or select a file."); |
| | return; |
| | } |
| | |
| | setLoading(true); |
| | summaryP.textContent = "Checking..."; |
| | scoreP.textContent = "Scanning..."; |
| | matchesDiv.innerHTML = ""; |
| | lastResult = null; |
| | |
| | try { |
| | let data; |
| | if (file && !(file.type === "text/plain" || file.name.toLowerCase().endsWith(".txt"))) { |
| | data = await callPlagiarismFile(file); |
| | } else { |
| | const payloadText = text || (file ? await file.text() : ""); |
| | data = await callPlagiarismText(payloadText); |
| | } |
| | |
| | if (!data) throw new Error("Empty response from server"); |
| | if (data.plagiarism_percent === undefined) { |
| | throw new Error(data.detail || data.error || "Unexpected response"); |
| | } |
| | |
| | const percent = data.plagiarism_percent; |
| | scoreP.textContent = `Estimated Plagiarism: ${percent}%`; |
| | summaryP.textContent = data.summary || `Plagiarism estimate: ${percent}%`; |
| | |
| | updateGauge(percent); |
| | updateRiskBadge(percent); |
| | |
| | if (data.matches && data.matches.length) { |
| | matchesDiv.innerHTML = data.matches.map(m => ` |
| | <div class="p-2 rounded bg-slate-800/40 flex items-center justify-between gap-2"> |
| | <div> |
| | <div class="font-semibold">${m.title}</div> |
| | <div class="text-[11px] text-slate-400">Similarity score: ${m.score}%</div> |
| | </div> |
| | </div> |
| | `).join(""); |
| | } else { |
| | matchesDiv.innerHTML = |
| | "<div class='text-xs text-slate-400'>No close matches found in the current demo corpus.</div>"; |
| | } |
| | |
| | lastResult = { |
| | input: text || (file ? "[uploaded file]" : ""), |
| | result: data |
| | }; |
| | statusTiny.textContent = "Done"; |
| | } catch (err) { |
| | console.error(err); |
| | summaryP.textContent = "Error: " + (err.message || err); |
| | scoreP.textContent = "Scan failed."; |
| | statusTiny.textContent = "Error"; |
| | } finally { |
| | setLoading(false); |
| | } |
| | }); |
| | |
| | |
| | downloadBtn.addEventListener("click", () => { |
| | if (!lastResult) { |
| | alert("Run a check first."); |
| | return; |
| | } |
| | |
| | const jsPDF = getJsPDF(); |
| | if (!jsPDF) return; |
| | |
| | const doc = new jsPDF({ unit: "pt", format: "a4" }); |
| | let y = 40; |
| | |
| | doc.setFontSize(16); |
| | doc.text("TrueWrite Scan β Plagiarism Report", 40, y); y += 22; |
| | |
| | doc.setFontSize(11); |
| | doc.text("Generated: " + new Date().toLocaleString(), 40, y); y += 18; |
| | doc.text("User: " + (user || "N/A"), 40, y); y += 18; |
| | |
| | const pr = lastResult.result.plagiarism_percent ?? "N/A"; |
| | doc.setFontSize(12); |
| | doc.text(`Plagiarism: ${pr}%`, 40, y); y += 16; |
| | |
| | if (lastResult.result.summary) { |
| | doc.setFontSize(10); |
| | const summaryLines = doc.splitTextToSize("Summary: " + lastResult.result.summary, 520); |
| | doc.text(summaryLines, 40, y); |
| | y += summaryLines.length * 12 + 10; |
| | } |
| | |
| | if (lastResult.result.matches && lastResult.result.matches.length) { |
| | doc.setFontSize(11); |
| | doc.text("Top matches:", 40, y); y += 14; |
| | lastResult.result.matches.forEach(m => { |
| | const line = `β’ ${m.title} β ${m.score}%`; |
| | doc.text(line, 48, y); |
| | y += 12; |
| | }); |
| | } |
| | |
| | y += 10; |
| | doc.setFontSize(11); |
| | doc.text("--- Original text (truncated) ---", 40, y); y += 16; |
| | doc.setFontSize(9); |
| | const raw = lastResult.input || ""; |
| | const truncated = raw.length > 3000 ? raw.slice(0, 3000) + "\n\n[TRUNCATED]" : raw; |
| | const textLines = doc.splitTextToSize(truncated, 520); |
| | doc.text(textLines, 40, y); |
| | |
| | doc.save("plagiarism-report.pdf"); |
| | }); |
| | |
| | |
| | const reviews = [ |
| | { |
| | name: "Aarav S.", |
| | role: "B.Tech Student", |
| | text: "Great for a quick originality check before using heavy tools like Turnitin.", |
| | stars: 5 |
| | }, |
| | { |
| | name: "Priya K.", |
| | role: "Research Scholar", |
| | text: "Helps me understand how similarity scores work in a simple, visible way.", |
| | stars: 5 |
| | }, |
| | { |
| | name: "Rahul M.", |
| | role: "Content Writer", |
| | text: "Nice to get an approximate plagiarism percentage while drafting blog posts.", |
| | stars: 4 |
| | }, |
| | { |
| | name: "Sneha R.", |
| | role: "M.Sc. Student", |
| | text: "Perfect educational example to show classmates how plagiarism detection is implemented.", |
| | stars: 5 |
| | }, |
| | { |
| | name: "Vikram J.", |
| | role: "Developer", |
| | text: "Clean UI and easy to tweak the logic for my own experiments.", |
| | stars: 4 |
| | } |
| | ]; |
| | |
| | let currentReview = 0; |
| | const reviewCard = document.getElementById("reviewCard"); |
| | const reviewDots = document.getElementById("reviewDots"); |
| | |
| | function starRow(stars) { |
| | let html = ""; |
| | for (let i = 0; i < 5; i++) { |
| | html += `<span class="${i < stars ? "text-yellow-400" : "text-slate-600"} text-sm">β
</span>`; |
| | } |
| | return html; |
| | } |
| | |
| | function renderReview() { |
| | const r = reviews[currentReview]; |
| | reviewCard.innerHTML = ` |
| | <div class="flex items-center gap-2 mb-2"> |
| | ${starRow(r.stars)} |
| | </div> |
| | <p class="text-sm text-slate-200 mb-3">"${r.text}"</p> |
| | <p class="text-sm font-semibold">${r.name}</p> |
| | <p class="text-xs text-slate-400">${r.role}</p> |
| | `; |
| | reviewDots.innerHTML = reviews.map((_, i) => |
| | `<span class="w-2 h-2 rounded-full ${i === currentReview ? "bg-[#0487D9]" : "bg-slate-600"}"></span>` |
| | ).join(""); |
| | } |
| | |
| | function nextReview() { |
| | currentReview = (currentReview + 1) % reviews.length; |
| | renderReview(); |
| | } |
| | |
| | function prevReview() { |
| | currentReview = (currentReview - 1 + reviews.length) % reviews.length; |
| | renderReview(); |
| | } |
| | |
| | document.getElementById("nextReview").onclick = () => { |
| | clearInterval(reviewTimer); |
| | nextReview(); |
| | reviewTimer = setInterval(nextReview, 6000); |
| | }; |
| | document.getElementById("prevReview").onclick = () => { |
| | clearInterval(reviewTimer); |
| | prevReview(); |
| | reviewTimer = setInterval(nextReview, 6000); |
| | }; |
| | |
| | let reviewTimer = setInterval(nextReview, 6000); |
| | renderReview(); |
| | |
| | |
| | updateGauge(0); |
| | updateRiskBadge(0); |
| | </script> |
| | </body> |
| | </html> |
| |
|