|
|
<!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> |
|
|
|