ocr-reader / index.html
khan1289's picture
Update index.html
c0e8fe8 verified
Raw History Blame Contribute Delete
6.29 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OCR Reader</title>
<style>
:root { --bg1:#eef2ff; --bg2:#fdf2f8; --card:#fff; --text:#1e1b4b; --muted:#6b7280; --border:#e5e7eb; --accent:#4f46e5; --accent2:#7c3aed; }
@media (prefers-color-scheme: dark) {
:root { --bg1:#0f172a; --bg2:#1e1b4b; --card:#1e293b; --text:#f1f5f9; --muted:#94a3b8; --border:#334155; --accent:#818cf8; --accent2:#a78bfa; }
}
* { box-sizing: border-box; }
body { margin:0; min-height:100vh; padding:20px; display:flex; align-items:center; justify-content:center;
font-family:"Segoe UI",system-ui,Arial,sans-serif; color:var(--text);
background:linear-gradient(135deg,var(--bg1),var(--bg2)); }
.card { width:100%; max-width:640px; background:var(--card); padding:34px; border-radius:20px; box-shadow:0 20px 50px rgba(79,70,229,.18); }
h1 { margin:0 0 4px; font-size:32px; background:linear-gradient(90deg,var(--accent),var(--accent2)); -webkit-background-clip:text; background-clip:text; color:transparent; }
.sub { margin:0 0 6px; color:var(--muted); }
.tip { margin:0 0 20px; font-size:13px; color:var(--muted); }
#drop { border:2px dashed var(--border); border-radius:14px; padding:18px; text-align:center; cursor:pointer; transition:border-color .2s; }
#drop:hover, #drop.over { border-color:var(--accent); }
#preview { display:none; max-width:100%; max-height:220px; margin:0 auto 10px; border-radius:8px; }
#dropText { color:var(--muted); }
button { font-family:inherit; cursor:pointer; border:none; border-radius:12px; }
#btn { width:100%; margin-top:14px; padding:14px; font-size:17px; font-weight:600; color:#fff; background:linear-gradient(90deg,var(--accent),var(--accent2)); }
#btn:disabled { opacity:.5; cursor:not-allowed; }
#status { margin-top:16px; font-size:14px; color:var(--muted); }
.track { height:8px; margin-top:6px; background:var(--border); border-radius:99px; overflow:hidden; }
#loadbar { height:100%; width:0%; background:linear-gradient(90deg,var(--accent),var(--accent2)); transition:width .3s; }
#out { display:none; margin-top:20px; }
#text { padding:16px; border:2px solid var(--border); border-radius:12px; font-size:20px; font-weight:600; word-break:break-word; min-height:30px; }
#copy { margin-top:10px; padding:8px 16px; font-size:14px; color:var(--text); background:transparent; border:1px solid var(--border); }
#copy:hover { border-color:var(--accent); color:var(--accent); }
#err { margin-top:12px; color:#dc2626; font-size:14px; }
footer { margin-top:22px; text-align:center; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<div class="card">
<h1>OCR Reader</h1>
<p class="sub">Upload a picture of printed text and see the text the AI reads from it.</p>
<p class="tip">Tip: this model reads one line of text at a time, so crop your image to a single line.</p>
<div id="drop">
<img id="preview" alt="Your image">
<div id="dropText">Click here or drop an image</div>
<input id="file" type="file" accept="image/*" hidden>
</div>
<button id="btn" disabled>Read text</button>
<div id="status">Loading AI model, please wait...</div>
<div class="track"><div id="loadbar"></div></div>
<div id="err"></div>
<div id="out">
<div id="text"></div>
<button id="copy">Copy text</button>
</div>
<footer>Runs privately in your browser with Transformers.js. Your image never leaves your device.</footer>
</div>
<script type="module">
import { pipeline, env } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3";
env.allowLocalModels = false;
const $ = (id) => document.getElementById(id);
const drop = $("drop"), file = $("file"), preview = $("preview"), btn = $("btn");
const status = $("status"), loadbar = $("loadbar"), err = $("err"), out = $("out"), text = $("text");
let imageData = null;
function loadFile(f) {
err.textContent = "";
if (!f || !f.type.startsWith("image/")) { err.textContent = "Please choose an image file (JPG, PNG, etc.)."; return; }
const reader = new FileReader();
reader.onload = () => {
imageData = reader.result;
preview.src = imageData; preview.style.display = "block";
$("dropText").textContent = "Click to choose a different image";
out.style.display = "none";
if (ocr) btn.disabled = false;
};
reader.readAsDataURL(f);
}
drop.addEventListener("click", () => file.click());
file.addEventListener("change", () => loadFile(file.files[0]));
drop.addEventListener("dragover", (e) => { e.preventDefault(); drop.classList.add("over"); });
drop.addEventListener("dragleave", () => drop.classList.remove("over"));
drop.addEventListener("drop", (e) => { e.preventDefault(); drop.classList.remove("over"); loadFile(e.dataTransfer.files[0]); });
let ocr = null;
try {
ocr = await pipeline("image-to-text", "Xenova/trocr-small-printed", {
progress_callback: (p) => {
if (p.status === "progress" && typeof p.progress === "number") loadbar.style.width = p.progress.toFixed(0) + "%";
},
});
loadbar.style.width = "100%";
status.textContent = "✅ Model ready. Upload an image of one line of printed text.";
if (imageData) btn.disabled = false;
} catch (e) {
status.textContent = "";
err.textContent = "Could not load the AI model. Check your internet connection and refresh the page.";
}
btn.addEventListener("click", async () => {
err.textContent = "";
btn.disabled = true; btn.textContent = "Reading...";
try {
const result = await ocr(imageData);
text.textContent = result[0].generated_text.trim() || "(no text found)";
out.style.display = "block";
} catch (e) {
err.textContent = "Sorry, the app could not read this image. Try a clearer, cropped one.";
}
btn.disabled = false; btn.textContent = "Read text";
});
$("copy").addEventListener("click", async () => {
try { await navigator.clipboard.writeText(text.textContent); $("copy").textContent = "Copied!"; }
catch (e) { $("copy").textContent = "Select and copy manually"; }
setTimeout(() => { $("copy").textContent = "Copy text"; }, 1500);
});
</script>
</body>
</html>