Initial: Tesseract Web GUI with Flask + start.bat

This commit is contained in:
Sivarius
2026-07-03 16:59:26 +08:00
commit 330b79288b
6 changed files with 534 additions and 0 deletions
+291
View File
@@ -0,0 +1,291 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tesseract Web GUI</title>
<style>
:root {
--bg: #1a1a2e;
--panel: #16213e;
--accent: #0f3460;
--text: #e0e0e0;
--muted: #a0a0a0;
--border: #2a3f5f;
--ok: #2ecc71;
--err: #e74c3c;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Segoe UI", Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
h1 {
margin: 0 0 10px;
font-size: 26px;
}
.subtitle {
color: var(--muted);
margin-bottom: 20px;
}
.grid {
display: grid;
grid-template-columns: 320px 1fr;
gap: 20px;
}
@media (max-width: 800px) {
.grid { grid-template-columns: 1fr; }
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 18px;
}
.panel h2 {
margin: 0 0 14px;
font-size: 16px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
label {
display: block;
margin: 12px 0 5px;
font-size: 13px;
color: var(--muted);
}
input, select, textarea {
width: 100%;
padding: 8px 10px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
font-size: 14px;
}
input[type="checkbox"] {
width: auto;
margin-right: 6px;
}
.checkbox-row {
display: flex;
align-items: center;
margin-top: 12px;
}
.checkbox-row label {
margin: 0;
}
button {
width: 100%;
margin-top: 16px;
padding: 12px;
background: var(--accent);
border: none;
border-radius: 8px;
color: #fff;
font-size: 15px;
cursor: pointer;
transition: background 0.2s;
}
button:hover { background: #1a4a80; }
button:disabled { opacity: 0.6; cursor: wait; }
.dropzone {
border: 2px dashed var(--border);
border-radius: 10px;
padding: 30px;
text-align: center;
color: var(--muted);
cursor: pointer;
transition: border-color 0.2s;
}
.dropzone:hover, .dropzone.dragover { border-color: var(--accent); color: var(--text); }
.preview {
margin-top: 15px;
text-align: center;
}
.preview img {
max-width: 100%;
max-height: 220px;
border-radius: 8px;
border: 1px solid var(--border);
}
.result textarea {
min-height: 280px;
font-family: Consolas, monospace;
font-size: 13px;
resize: vertical;
}
.meta {
margin-top: 10px;
font-size: 12px;
color: var(--muted);
}
.status {
margin-top: 12px;
padding: 10px;
border-radius: 8px;
font-size: 13px;
display: none;
}
.status.ok { background: rgba(46, 204, 113, 0.15); color: var(--ok); display: block; }
.status.err { background: rgba(231, 76, 60, 0.15); color: var(--err); display: block; }
</style>
</head>
<body>
<div class="container">
<h1>🔍 Tesseract Web GUI</h1>
<div class="subtitle">Загрузи картинку — получи текст. Всё локально, без интернета.</div>
<div class="grid">
<div>
<div class="panel">
<h2>1. Картинка</h2>
<div class="dropzone" id="dropzone">
📁 Перетащи сюда файл или кликни для выбора
<input type="file" id="fileInput" accept="image/*" hidden>
</div>
<div class="preview" id="preview"></div>
</div>
<div class="panel" style="margin-top: 20px;">
<h2>2. Настройки OCR</h2>
<label>Язык</label>
<input type="text" id="lang" value="rus+eng">
<label>PSM режим</label>
<select id="psm">
<option value="3">3 — Авто</option>
<option value="4">4 — Одна колонка</option>
<option value="6" selected>6 — Блок текста (скриншоты)</option>
<option value="7">7 — Одна строка</option>
<option value="8">8 — Одно слово</option>
<option value="11">11 — Разреженный текст</option>
<option value="13">13 — Сырая строка</option>
</select>
<label>Увеличение (scale)</label>
<input type="number" id="scale" value="2" min="1" max="5">
<label>Контраст</label>
<input type="number" id="contrast" value="2.0" step="0.1" min="1" max="5">
<div class="checkbox-row">
<input type="checkbox" id="sharpen" checked>
<label for="sharpen">Резкость</label>
</div>
<div class="checkbox-row">
<input type="checkbox" id="binary">
<label for="binary">Бинаризация</label>
</div>
<label>Порог бинаризации</label>
<input type="number" id="binary_thresh" value="140" min="0" max="255">
<label>Whitelist символов (опционально)</label>
<input type="text" id="whitelist" placeholder="Например: 0123456789">
<button id="runBtn" disabled>🔮 Распознать</button>
<div class="status" id="status"></div>
</div>
</div>
<div class="panel result">
<h2>3. Результат</h2>
<textarea id="result" readonly placeholder="Тут появится распознанный текст..."></textarea>
<div class="meta" id="meta"></div>
</div>
</div>
</div>
<script>
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
const runBtn = document.getElementById('runBtn');
const status = document.getElementById('status');
const resultArea = document.getElementById('result');
const meta = document.getElementById('meta');
let currentFile = null;
dropzone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('dragover');
});
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover'));
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
function handleFiles(files) {
if (!files.length) return;
currentFile = files[0];
const url = URL.createObjectURL(currentFile);
preview.innerHTML = `<img src="${url}" alt="preview">`;
runBtn.disabled = false;
status.className = 'status';
status.textContent = '';
}
runBtn.addEventListener('click', async () => {
if (!currentFile) return;
runBtn.disabled = true;
status.className = 'status';
status.textContent = 'Распознаю...';
const form = new FormData();
form.append('file', currentFile);
form.append('lang', document.getElementById('lang').value);
form.append('psm', document.getElementById('psm').value);
form.append('scale', document.getElementById('scale').value);
form.append('contrast', document.getElementById('contrast').value);
form.append('sharpen', document.getElementById('sharpen').checked);
form.append('binary', document.getElementById('binary').checked);
form.append('binary_thresh', document.getElementById('binary_thresh').value);
form.append('whitelist', document.getElementById('whitelist').value);
try {
const resp = await fetch('/ocr', { method: 'POST', body: form });
const data = await resp.json();
if (data.error) {
status.className = 'status err';
status.textContent = 'Ошибка: ' + data.error;
return;
}
resultArea.value = data.text;
meta.textContent = 'Настройки: ' + data.config_used;
if (data.preview) {
preview.innerHTML = `<img src="data:image/png;base64,${data.preview}" alt="preview">`;
}
status.className = 'status ok';
status.textContent = 'Готово!';
} catch (e) {
status.className = 'status err';
status.textContent = 'Ошибка сети: ' + e.message;
} finally {
runBtn.disabled = false;
}
});
</script>
</body>
</html>