SPaCial_server_api / static /camera.html
cooldan's picture
Upload 6 files
4d2f9e8 verified
Raw History Blame Contribute Delete
12 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>SPaCial AI - Live Scanner</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
background: #000;
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
#container {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
#videoContainer {
position: relative;
flex: 1;
overflow: hidden;
background: #000;
}
#video {
width: 100%;
height: 100%;
object-fit: cover;
}
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
#overlay {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 15px;
border-radius: 10px;
font-size: 14px;
z-index: 10;
}
#status {
font-weight: bold;
margin-bottom: 5px;
}
.zone-info {
font-size: 12px;
margin: 5px 0;
color: #0f0;
}
#controls {
background: rgba(0, 0, 0, 0.9);
padding: 20px;
display: flex;
justify-content: space-around;
align-items: center;
}
button {
background: #0088cc;
color: white;
border: none;
padding: 15px 30px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
min-width: 120px;
}
button:active {
transform: scale(0.95);
}
button:disabled {
background: #666;
cursor: not-allowed;
}
#captureBtn {
background: #28a745;
font-size: 18px;
padding: 20px 40px;
}
#toggleScanBtn {
background: #ffc107;
}
#toggleScanBtn.scanning {
background: #dc3545;
}
.error {
color: #ff4444;
background: rgba(255, 68, 68, 0.2);
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
.loading {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #fff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
#zoneCount {
font-size: 24px;
color: #0f0;
font-weight: bold;
}
</style>
</head>
<body>
<div id="container">
<div id="videoContainer">
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
<div id="overlay">
<div id="status">πŸ“Έ Camera Ready</div>
<div id="zoneCount">0 zones detected</div>
<div id="details"></div>
</div>
</div>
<div id="controls">
<button id="toggleScanBtn" onclick="toggleScanning()">Start Scan</button>
<button id="captureBtn" onclick="captureResults()" disabled>Capture</button>
</div>
</div>
<script>
const tg = window.Telegram.WebApp;
tg.ready();
tg.expand();
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
const zoneCountEl = document.getElementById('zoneCount');
const detailsEl = document.getElementById('details');
const toggleBtn = document.getElementById('toggleScanBtn');
const captureBtn = document.getElementById('captureBtn');
let isScanning = false;
let scanInterval = null;
let lastResults = null;
let stream = null;
// Get API URL (same host as mini app)
const API_URL = window.location.origin;
// Initialize camera
async function initCamera() {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1920 },
height: { ideal: 1080 }
}
});
video.srcObject = stream;
video.addEventListener('loadedmetadata', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
statusEl.textContent = 'βœ… Camera Ready';
});
} catch (error) {
statusEl.innerHTML = `❌ Camera Error: ${error.message}`;
console.error('Camera error:', error);
}
}
// Toggle scanning
function toggleScanning() {
if (isScanning) {
stopScanning();
} else {
startScanning();
}
}
// Start continuous scanning
function startScanning() {
isScanning = true;
toggleBtn.textContent = 'Stop Scan';
toggleBtn.classList.add('scanning');
captureBtn.disabled = true;
// Scan every 2 seconds
scanInterval = setInterval(scanFrame, 2000);
scanFrame(); // First scan immediately
}
// Stop scanning
function stopScanning() {
isScanning = false;
toggleBtn.textContent = 'Start Scan';
toggleBtn.classList.remove('scanning');
captureBtn.disabled = false;
if (scanInterval) {
clearInterval(scanInterval);
scanInterval = null;
}
}
// Scan current frame
async function scanFrame() {
try {
statusEl.innerHTML = '⏳ Processing... <span class="loading"></span>';
// Draw video frame to canvas
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// Get image as blob
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', 0.95));
// Send to OCR API
const formData = new FormData();
formData.append('file', blob, 'frame.jpg');
formData.append('mode', 'fast');
const response = await fetch(`${API_URL}/ocr/process`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const result = await response.json();
lastResults = result;
// Draw results
drawResults(result);
} catch (error) {
statusEl.innerHTML = `❌ Error: ${error.message}`;
console.error('Scan error:', error);
}
}
// Draw OCR results on canvas
function drawResults(result) {
const zones = result.zones || [];
// Clear previous drawings
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update status
statusEl.textContent = 'βœ… Scanning...';
zoneCountEl.textContent = `${zones.length} zones detected`;
// Draw zones
zones.forEach((zone, index) => {
const x = zone.x;
const y = zone.y;
const w = zone.width;
const h = zone.height;
// Draw green box
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, w, h);
// Draw text label with background
const text = zone.text;
const fontSize = Math.max(16, Math.min(w / text.length * 1.5, 24));
ctx.font = `bold ${fontSize}px Arial`;
const textMetrics = ctx.measureText(text);
const textWidth = textMetrics.width;
const textHeight = fontSize;
// Background for text
ctx.fillStyle = 'rgba(0, 255, 0, 0.8)';
ctx.fillRect(x, y - textHeight - 8, textWidth + 10, textHeight + 8);
// Text
ctx.fillStyle = '#000';
ctx.fillText(text, x + 5, y - 8);
});
// Show first few zones in details
if (zones.length > 0) {
const preview = zones.slice(0, 3).map((z, i) =>
`${i + 1}. ${z.text} (${Math.round(z.confidence * 100)}%)`
).join('<br>');
detailsEl.innerHTML = `<div class="zone-info">${preview}${zones.length > 3 ? '<br>...' : ''}</div>`;
} else {
detailsEl.innerHTML = '';
}
}
// Capture and send results
async function captureResults() {
if (!lastResults) {
tg.showAlert('No results to capture! Start scanning first.');
return;
}
try {
stopScanning();
statusEl.textContent = 'πŸ“€ Sending results...';
// Send results back to Telegram chat
const zones = lastResults.zones || [];
const message = `πŸ“Š OCR Results Captured!\n\nβœ… Found ${zones.length} text zones\n\nProcessing complete!`;
// You can send data back to bot via bot API if needed
// For now, just show confirmation
tg.showAlert(`βœ… Captured ${zones.length} zones!\n\nResults saved.`, () => {
tg.close();
});
} catch (error) {
statusEl.innerHTML = `<div class="error">Error: ${error.message}</div>`;
}
}
// Handle Telegram back button
tg.BackButton.show();
tg.BackButton.onClick(() => {
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
tg.close();
});
// Initialize on load
initCamera();
</script>
</body>
</html>