ginipick's picture
Update index.html
3453b9b verified
raw
history blame
16.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Screen Monitor Pro</title>
<script src="https://unpkg.com/[email protected]/dist/tesseract.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, system-ui, sans-serif;
}
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f8fafc;
min-height: 100vh;
}
.header {
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status-indicator {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #cbd5e1;
}
.status-dot.active {
background: #22c55e;
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.controls {
display: flex;
gap: 10px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.start-btn {
background: #22c55e;
color: white;
}
.stop-btn {
background: #ef4444;
color: white;
}
.preview-container {
background: black;
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: relative;
}
#preview {
width: 100%;
aspect-ratio: 16/9;
object-fit: contain;
}
.logs {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.log-header {
background: #1e293b;
color: white;
padding: 15px;
font-weight: 500;
}
.log-entry {
padding: 20px;
border-bottom: 1px solid #e2e8f0;
display: grid;
grid-template-columns: 300px 1fr;
gap: 20px;
}
.screenshot-container {
position: relative;
}
.screenshot {
width: 100%;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.change-highlight {
position: absolute;
border: 2px solid #ef4444;
background: rgba(239, 68, 68, 0.2);
pointer-events: none;
transition: all 0.3s;
}
.info-panel {
display: flex;
flex-direction: column;
gap: 15px;
}
.timestamp {
color: #64748b;
font-size: 0.9rem;
}
.analysis {
background: #f8fafc;
padding: 15px;
border-radius: 6px;
font-size: 0.95rem;
line-height: 1.5;
}
.analysis h4 {
margin-bottom: 8px;
color: #1e293b;
}
.ocr-text {
font-family: monospace;
white-space: pre-wrap;
background: #f1f5f9;
padding: 10px;
border-radius: 4px;
margin-top: 8px;
}
.actions {
display: flex;
gap: 10px;
margin-top: auto;
}
.download-btn {
padding: 8px 16px;
background: #3b82f6;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 6px;
}
.error-message {
background: #fef2f2;
color: #ef4444;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
display: none;
}
@media (max-width: 768px) {
.log-entry {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<h1>Screen Monitor Pro</h1>
<p>Advanced screen capture and analysis</p>
</div>
<div class="status-bar">
<div class="status-indicator">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Ready</span>
</div>
<div class="controls">
<button class="start-btn" id="startBtn">Start Capture</button>
<button class="stop-btn" id="stopBtn" disabled>Stop</button>
</div>
</div>
<div class="error-message" id="errorMessage"></div>
<div class="preview-container">
<video id="preview" autoplay></video>
</div>
<div class="logs">
<div class="log-header">Change Log</div>
<div id="logContainer"></div>
</div>
</div>
<script>
class ScreenAnalyzer {
constructor() {
this.mediaStream = null;
this.captureInterval = null;
this.lastImageData = null;
this.worker = null;
this.isProcessing = false;
this.initializeOCR();
this.bindElements();
this.bindEvents();
}
async initializeOCR() {
try {
this.worker = await Tesseract.createWorker();
await this.worker.loadLanguage('eng');
await this.worker.initialize('eng');
} catch (error) {
this.showError('Failed to initialize OCR');
}
}
bindElements() {
this.elements = {
startBtn: document.getElementById('startBtn'),
stopBtn: document.getElementById('stopBtn'),
preview: document.getElementById('preview'),
logContainer: document.getElementById('logContainer'),
statusDot: document.getElementById('statusDot'),
statusText: document.getElementById('statusText'),
errorMessage: document.getElementById('errorMessage')
};
}
bindEvents() {
this.elements.startBtn.addEventListener('click', () => this.start());
this.elements.stopBtn.addEventListener('click', () => this.stop());
}
updateStatus(status, isError = false) {
this.elements.statusDot.className = `status-dot ${status === 'recording' ? 'active' : ''}`;
this.elements.statusText.textContent = status.charAt(0).toUpperCase() + status.slice(1);
if (isError) {
this.elements.errorMessage.textContent = status;
this.elements.errorMessage.style.display = 'block';
} else {
this.elements.errorMessage.style.display = 'none';
}
}
async start() {
try {
this.mediaStream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: "always" }
});
this.elements.preview.srcObject = this.mediaStream;
this.elements.startBtn.disabled = true;
this.elements.stopBtn.disabled = false;
this.updateStatus('recording');
this.captureInterval = setInterval(() => this.capture(), 1000);
this.mediaStream.getVideoTracks()[0].onended = () => this.stop();
} catch (error) {
this.showError('Failed to start screen capture');
}
}
stop() {
if (this.mediaStream) {
this.mediaStream.getTracks().forEach(track => track.stop());
this.elements.preview.srcObject = null;
}
clearInterval(this.captureInterval);
this.elements.startBtn.disabled = false;
this.elements.stopBtn.disabled = true;
this.lastImageData = null;
this.updateStatus('ready');
}
async capture() {
if (this.isProcessing) return;
this.isProcessing = true;
try {
const canvas = document.createElement('canvas');
canvas.width = this.elements.preview.videoWidth;
canvas.height = this.elements.preview.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(this.elements.preview, 0, 0);
const currentImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const changes = this.detectChanges(currentImageData);
if (changes.length > 0 || !this.lastImageData) {
const imageUrl = canvas.toDataURL('image/jpeg', 0.8);
const ocrResults = await this.analyzeChanges(canvas, changes);
this.addLogEntry(imageUrl, changes, ocrResults);
}
this.lastImageData = currentImageData;
} catch (error) {
this.showError('Failed to process capture');
} finally {
this.isProcessing = false;
}
}
detectChanges(currentImageData) {
if (!this.lastImageData) return [];
const changes = [];
const blockSize = 20;
const threshold = 30;
const width = currentImageData.width;
const height = currentImageData.height;
for (let y = 0; y < height; y += blockSize) {
for (let x = 0; x < width; x += blockSize) {
let diffCount = 0;
const maxY = Math.min(y + blockSize, height);
const maxX = Math.min(x + blockSize, width);
for (let py = y; py < maxY; py++) {
for (let px = x; px < maxX; px++) {
const i = (py * width + px) * 4;
if (Math.abs(currentImageData.data[i] - this.lastImageData.data[i]) > threshold ||
Math.abs(currentImageData.data[i + 1] - this.lastImageData.data[i + 1]) > threshold ||
Math.abs(currentImageData.data[i + 2] - this.lastImageData.data[i + 2]) > threshold) {
diffCount++;
}
}
}
if (diffCount > (blockSize * blockSize * 0.3)) {
changes.push({
x: x / width * 100,
y: y / height * 100,
width: Math.min(blockSize / width * 100, 100 - x / width * 100),
height: Math.min(blockSize / height * 100, 100 - y / height * 100),
pixels: {x, y, width: maxX - x, height: maxY - y}
});
}
}
}
return changes;
}
async analyzeChanges(canvas, changes) {
const results = [];
for (const change of changes) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = change.pixels.width;
tempCanvas.height = change.pixels.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(
canvas,
change.pixels.x, change.pixels.y,
change.pixels.width, change.pixels.height,
0, 0,
change.pixels.width, change.pixels.height
);
try {
const result = await this.worker.recognize(tempCanvas);
if (result.data.text.trim()) {
results.push({
text: result.data.text.trim(),
confidence: result.data.confidence,
region: change
});
}
} catch (error) {
console.error('OCR Error:', error);
}
}
return results;
}
addLogEntry(imageUrl, changes, ocrResults) {
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
const timestamp = new Date().toLocaleString();
logEntry.innerHTML = `
<div class="screenshot-container">
<img class="screenshot" src="${imageUrl}" alt="Screenshot">
${changes.map((change, index) => `
<div class="change-highlight" style="
left: ${change.x}%;
top: ${change.y}%;
width: ${change.width}%;
height: ${change.height}%;
"></div>
`).join('')}
</div>
<div class="info-panel">
<div class="timestamp">📅 ${timestamp}</div>
<div class="analysis">
<h4>Changes Detected</h4>
<p>${changes.length} regions changed</p>
${ocrResults.length > 0 ? `
<h4>Text Content</h4>
<div class="ocr-text">
${ocrResults.map(result =>
`[Region ${Math.round(result.confidence)}% confidence]\n${result.text}`
).join('\n\n')}
</div>
` : ''}
</div>
<div class="actions">
<a href="${imageUrl}" download="screenshot-${Date.now()}.jpg"
class="download-btn">
📸 Download Screenshot
</a>
</div>
</div>
`;
this.elements.logContainer.insertBefore(logEntry, this.elements.logContainer.firstChild);
}
showError(message) {
this.updateStatus(message, true);
console.error(message);
}
async cleanup() {
if (this.worker) {
await this.worker.terminate();
}
this.stop();
}
}
// Initialize the application
const analyzer = new ScreenAnalyzer();
window.addEventListener('beforeunload', () => analyzer.cleanup());
</script>
</body>
</html>