Initial: Tesseract Web GUI with Flask + start.bat
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user