surya-ocr / index.html
roque99cheroA's picture
Upload 4 files
1732887 verified
Raw
History Blame Contribute Delete
15.3 kB
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Surya OCR - Reconocimiento de Manuscritos</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
color: #fff;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
/* Header */
.header {
text-align: center;
padding: 40px 0;
}
.header h1 {
font-size: 2.5rem;
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
}
.header .version {
color: #888;
font-size: 0.9rem;
}
.header .subtitle {
color: #aaa;
margin-top: 10px;
}
/* Status */
.status-bar {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: rgba(255,255,255,0.1);
border-radius: 20px;
font-size: 0.85rem;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #ff4444;
}
.status-dot.online {
background: #00ff88;
box-shadow: 0 0 10px #00ff88;
}
/* Upload Area */
.upload-section {
background: rgba(255,255,255,0.05);
border: 2px dashed rgba(255,255,255,0.2);
border-radius: 20px;
padding: 40px;
text-align: center;
transition: all 0.3s;
cursor: pointer;
}
.upload-section:hover {
border-color: #00d4ff;
background: rgba(0, 212, 255, 0.05);
}
.upload-section.dragover {
border-color: #00ff88;
background: rgba(0, 255, 136, 0.1);
}
.upload-icon {
font-size: 4rem;
margin-bottom: 20px;
}
.upload-section h3 {
font-size: 1.3rem;
margin-bottom: 10px;
}
.upload-section p {
color: #888;
font-size: 0.9rem;
}
#fileInput {
display: none;
}
.btn {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
border: none;
padding: 15px 40px;
border-radius: 30px;
color: #fff;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
margin-top: 20px;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(0, 212, 255, 0.3);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Preview */
.preview-section {
margin-top: 30px;
display: none;
}
.preview-section.show {
display: block;
}
.preview-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.preview-image {
flex: 1;
min-width: 300px;
background: rgba(255,255,255,0.05);
border-radius: 15px;
padding: 20px;
}
.preview-image img {
max-width: 100%;
border-radius: 10px;
}
.preview-image h4 {
margin-bottom: 15px;
color: #00d4ff;
}
/* Results */
.results-section {
flex: 1;
min-width: 300px;
background: rgba(255,255,255,0.05);
border-radius: 15px;
padding: 20px;
}
.results-section h4 {
margin-bottom: 15px;
color: #00ff88;
}
.result-text {
background: rgba(0,0,0,0.3);
padding: 20px;
border-radius: 10px;
font-size: 1.2rem;
line-height: 1.6;
min-height: 100px;
}
.confidence-bar {
margin-top: 20px;
}
.confidence-bar label {
display: block;
margin-bottom: 8px;
font-size: 0.9rem;
color: #aaa;
}
.progress {
height: 10px;
background: rgba(255,255,255,0.1);
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #00ff88, #00d4ff);
border-radius: 5px;
transition: width 0.5s;
}
.confidence-value {
text-align: right;
font-size: 0.85rem;
margin-top: 5px;
color: #00ff88;
}
/* Lines Detail */
.lines-detail {
margin-top: 20px;
}
.lines-detail h5 {
color: #888;
margin-bottom: 10px;
font-size: 0.9rem;
}
.line-item {
display: flex;
justify-content: space-between;
padding: 10px;
background: rgba(0,0,0,0.2);
border-radius: 8px;
margin-bottom: 8px;
font-size: 0.9rem;
}
.line-text {
flex: 1;
}
.line-conf {
color: #00d4ff;
margin-left: 10px;
}
/* Loading */
.loading {
display: none;
text-align: center;
padding: 40px;
}
.loading.show {
display: block;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255,255,255,0.1);
border-top-color: #00d4ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Error */
.error {
background: rgba(255, 68, 68, 0.2);
border: 1px solid #ff4444;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
display: none;
}
.error.show {
display: block;
}
/* Footer */
.footer {
text-align: center;
padding: 40px 0;
color: #666;
font-size: 0.85rem;
}
.footer a {
color: #00d4ff;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>Surya OCR</h1>
<div class="version">v1.0.0</div>
<p class="subtitle">Reconocimiento de Manuscritos con IA</p>
</div>
<!-- Status Bar -->
<div class="status-bar">
<div class="status-item">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Verificando servidor...</span>
</div>
<div class="status-item">
<span>Modelo: Surya OCR 2024</span>
</div>
</div>
<!-- Upload Section -->
<div class="upload-section" id="uploadArea">
<div class="upload-icon">📄</div>
<h3>Arrastra tu imagen aquí</h3>
<p>o haz clic para seleccionar un archivo</p>
<p style="margin-top: 10px; font-size: 0.8rem;">JPG, PNG, JPEG</p>
<input type="file" id="fileInput" accept="image/*">
<button class="btn" id="selectBtn">Seleccionar Imagen</button>
</div>
<!-- Loading -->
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Procesando imagen con Surya OCR...</p>
<p style="color: #888; font-size: 0.85rem; margin-top: 10px;">Esto puede tomar unos segundos</p>
</div>
<!-- Error -->
<div class="error" id="error">
<strong>Error:</strong> <span id="errorText"></span>
</div>
<!-- Preview & Results -->
<div class="preview-section" id="previewSection">
<div class="preview-container">
<div class="preview-image">
<h4>📷 Imagen</h4>
<img id="previewImg" src="" alt="Preview">
</div>
<div class="results-section">
<h4>✨ Texto Reconocido</h4>
<div class="result-text" id="resultText">-</div>
<div class="confidence-bar">
<label>Nivel de Confianza</label>
<div class="progress">
<div class="progress-bar" id="confidenceBar" style="width: 0%"></div>
</div>
<div class="confidence-value" id="confidenceValue">0%</div>
</div>
<div class="lines-detail" id="linesDetail">
<h5>Detalle por línea:</h5>
<div id="linesList"></div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="footer">
<p>Surya OCR API | <a href="http://localhost:7860/docs" target="_blank">Ver documentación API</a></p>
</div>
</div>
<script>
const API_URL = 'http://localhost:7860';
// Elements
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const selectBtn = document.getElementById('selectBtn');
const loading = document.getElementById('loading');
const error = document.getElementById('error');
const errorText = document.getElementById('errorText');
const previewSection = document.getElementById('previewSection');
const previewImg = document.getElementById('previewImg');
const resultText = document.getElementById('resultText');
const confidenceBar = document.getElementById('confidenceBar');
const confidenceValue = document.getElementById('confidenceValue');
const linesList = document.getElementById('linesList');
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
// Check API Status
async function checkStatus() {
try {
const response = await fetch(`${API_URL}/health`);
if (response.ok) {
statusDot.classList.add('online');
statusText.textContent = 'Servidor activo';
} else {
throw new Error();
}
} catch (e) {
statusDot.classList.remove('online');
statusText.textContent = 'Servidor desconectado';
}
}
// Check status on load and every 5 seconds
checkStatus();
setInterval(checkStatus, 5000);
// Click to select
selectBtn.addEventListener('click', (e) => {
e.stopPropagation();
fileInput.click();
});
uploadArea.addEventListener('click', () => {
fileInput.click();
});
// Drag and drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
processFile(file);
}
});
// File input change
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
processFile(file);
}
});
// Process file
async function processFile(file) {
// Show preview
const reader = new FileReader();
reader.onload = (e) => {
previewImg.src = e.target.result;
};
reader.readAsDataURL(file);
// Show loading
loading.classList.add('show');
error.classList.remove('show');
previewSection.classList.remove('show');
try {
// Send to API
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${API_URL}/recognize`, {
method: 'POST',
body: formData
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.detail || 'Error en el servidor');
}
const data = await response.json();
// Hide loading, show results
loading.classList.remove('show');
previewSection.classList.add('show');
// Display results
resultText.textContent = data.texto || 'No se detectó texto';
const conf = Math.round(data.confianza * 100);
confidenceBar.style.width = conf + '%';
confidenceValue.textContent = conf + '%';
// Lines
linesList.innerHTML = '';
if (data.lineas && data.lineas.length > 0) {
data.lineas.forEach((line, i) => {
const div = document.createElement('div');
div.className = 'line-item';
div.innerHTML = `
<span class="line-text">${line.texto}</span>
<span class="line-conf">${Math.round(line.confianza * 100)}%</span>
`;
linesList.appendChild(div);
});
}
} catch (e) {
loading.classList.remove('show');
error.classList.add('show');
errorText.textContent = e.message;
previewSection.classList.add('show');
resultText.textContent = 'Error al procesar';
}
}
</script>
</body>
</html>