Portfolio/script.js
Max 6b27c0d4b7 Update stats to show real metrics
- Changed 'Jobs Processed Today' to 'Jobs Queued' (tracks session jobs)
- Changed 'Avg Response Time' to 'Results Received' (tracks SSE results)
- Added client-side counters that increment on actual events
- Removed fake random number generation
- Stats now show meaningful, real-time data
2025-11-16 18:08:16 +00:00

200 lines
6.3 KiB
JavaScript

const API_URL = 'https://alpenqueue.maxtheweb.com';
const SSE_URL = 'https://maxtheweb.com/events';
let eventSource = null;
let currentJobId = null;
let jobsQueued = 0;
let resultsReceived = 0;
// Initialize SSE connection
function initSSE() {
if (eventSource) {
eventSource.close();
}
eventSource = new EventSource(SSE_URL);
eventSource.onopen = function() {
console.log('SSE connection established');
updateConnectionStatus('connected');
};
eventSource.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
if (data.type === 'connected') {
console.log('SSE handshake complete');
return;
}
if (data.type === 'result') {
displayResult(data);
}
} catch (error) {
console.error('Error parsing SSE message:', error);
}
};
eventSource.onerror = function(error) {
console.error('SSE error:', error);
updateConnectionStatus('disconnected');
// Reconnect after 5 seconds
setTimeout(() => {
if (eventSource.readyState === EventSource.CLOSED) {
initSSE();
}
}, 5000);
};
}
function updateConnectionStatus(status) {
const indicator = document.getElementById('connection-status');
if (!indicator) return;
indicator.className = 'connection-status ' + status;
indicator.textContent = status === 'connected' ? '● Live' : '○ Reconnecting...';
}
function displayResult(data) {
const resultsContainer = document.getElementById('live-results');
if (!resultsContainer) return;
// Remove the "no results" message if it exists
const noResults = resultsContainer.querySelector('.no-results');
if (noResults) {
noResults.remove();
}
// Increment results received counter
resultsReceived++;
document.getElementById('results-received').textContent = resultsReceived;
const resultDiv = document.createElement('div');
resultDiv.className = 'result-item ' + (data.status || 'ok');
const timestamp = new Date(data.timestamp).toLocaleTimeString();
resultDiv.innerHTML = `
<div class="result-header">
<span class="result-status ${data.status || 'ok'}">${(data.status || 'OK').toUpperCase()}</span>
<span class="result-time">${timestamp} · ${data.took || '0s'}</span>
</div>
<div class="result-url">${data.url}</div>
<div class="result-content">
${data.content ? `<pre>${escapeHtml(data.content.substring(0, 500))}${data.content.length > 500 ? '...' : ''}</pre>` : '<em>No content extracted</em>'}
</div>
`;
// Add to top of results
resultsContainer.insertBefore(resultDiv, resultsContainer.firstChild);
// Limit to 10 results
while (resultsContainer.children.length > 10) {
resultsContainer.removeChild(resultsContainer.lastChild);
}
// Highlight animation
resultDiv.style.animation = 'slideIn 0.3s ease-out';
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Handle form submission
document.getElementById('queue-form').addEventListener('submit', async (e) => {
e.preventDefault();
const submitBtn = document.getElementById('submit-btn');
const responseDiv = document.getElementById('response');
// Get form values
const url = document.getElementById('url').value;
const selector = document.getElementById('selector').value;
// Use our own webhook endpoint
const webhook = 'https://maxtheweb.com/webhook';
// Update UI
submitBtn.disabled = true;
submitBtn.textContent = 'Queueing...';
responseDiv.className = 'response-box loading';
responseDiv.innerHTML = '<div class="spinner"></div> Sending to AlpenQueue...';
try {
// Build the job payload
const jobData = {
url: url,
selector: selector, // AlpenQueue expects a single string
webhook_url: webhook // AlpenQueue expects webhook_url, not callback_url
};
// Send to AlpenQueue API
const response = await fetch(`${API_URL}/jobs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(jobData)
});
if (response.ok) {
// AlpenQueue returns plain text like "Job 7 created"
const resultText = await response.text();
const jobIdMatch = resultText.match(/Job (\d+) created/);
const jobId = jobIdMatch ? jobIdMatch[1] : 'Processing';
currentJobId = jobId;
// Increment jobs queued counter
jobsQueued++;
document.getElementById('jobs-queued').textContent = jobsQueued;
responseDiv.className = 'response-box success';
responseDiv.innerHTML = `
<div class="success-icon">✓</div>
<div class="response-content">
<strong>Job Queued!</strong><br>
Job ID: <code>${jobId}</code><br>
Results will appear below in real-time...
</div>
`;
} else {
throw new Error(`Server responded with ${response.status}`);
}
} catch (error) {
responseDiv.className = 'response-box error';
responseDiv.innerHTML = `
<div class="error-icon">✕</div>
<div class="response-content">
<strong>Error:</strong><br>
${error.message}<br>
<small>Check the console for details</small>
</div>
`;
console.error('AlpenQueue Error:', error);
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Queue it →';
}
});
// Initialize on load
window.addEventListener('load', () => {
// Pre-fill with a demo example
document.getElementById('url').value = 'https://news.ycombinator.com';
document.getElementById('selector').value = '.athing .titleline';
// Start SSE connection
initSSE();
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (eventSource) {
eventSource.close();
}
});