- PHP-based web interface for image upscaling using Topaz Labs API - Multiple image upload with batch processing support - AI model selection (Standard V2, Low Resolution V2, CGI, etc.) - Output resolution options from 2K to 8K - Face enhancement feature - Real-time job tracking and status monitoring - Bulk download functionality - Dark mode toggle - Microsoft authentication integration - Comprehensive README with installation instructions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
7.6 KiB
JavaScript
Executable file
217 lines
7.6 KiB
JavaScript
Executable file
const POLL_INTERVAL = 5000; // 5 seconds
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadJobHistory();
|
|
|
|
document.getElementById('uploadForm').addEventListener('submit', handleFormSubmit);
|
|
document.getElementById('bulkDownload').addEventListener('click', bulkDownload);
|
|
|
|
// Add event listener for file input change
|
|
document.querySelector('input[type="file"]').addEventListener('change', updateSelectedFiles);
|
|
|
|
addSelectAllButton();
|
|
});
|
|
|
|
function handleFormSubmit(e) {
|
|
e.preventDefault();
|
|
const formData = new FormData(this);
|
|
const files = formData.getAll('images[]');
|
|
|
|
files.forEach(file => {
|
|
const individualFormData = new FormData();
|
|
individualFormData.append('image', file);
|
|
individualFormData.append('output_height', formData.get('output_height'));
|
|
individualFormData.append('model', formData.get('model'));
|
|
individualFormData.append('face_enhancement', formData.get('face_enhancement'));
|
|
|
|
uploadFile(individualFormData);
|
|
});
|
|
}
|
|
|
|
function uploadFile(formData) {
|
|
fetch('process.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.process_id) {
|
|
addJobToList(data.process_id, formData.get('image').name);
|
|
pollJobStatus(data.process_id);
|
|
addToJobHistory(data.process_id, formData.get('image').name);
|
|
} else if (data.error) {
|
|
console.error('Error:', data.error);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
function addJobToList(processId, fileName) {
|
|
const jobList = document.getElementById('jobList');
|
|
const jobItem = document.createElement('div');
|
|
jobItem.id = `job-${processId}`;
|
|
jobItem.innerHTML = `
|
|
<input type="checkbox" id="check-${processId}">
|
|
<label for="check-${processId}">${fileName} - Process ID: ${processId} - Status: Processing</label>
|
|
<button onclick="downloadJob('${processId}')" style="display:none;">Download</button>
|
|
`;
|
|
jobList.appendChild(jobItem);
|
|
|
|
if (!document.getElementById('selectAll')) {
|
|
addSelectAllButton();
|
|
}
|
|
document.getElementById('bulkDownload').style.display = 'block';
|
|
}
|
|
|
|
function pollJobStatus(processId) {
|
|
const pollInterval = setInterval(() => {
|
|
fetch(`status.php?process_id=${processId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
updateJobStatus(processId, data.status, data.progress);
|
|
if (data.status === 'Completed') {
|
|
clearInterval(pollInterval);
|
|
updateJobHistory(processId, 'Completed');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}, POLL_INTERVAL);
|
|
}
|
|
|
|
function updateJobStatus(processId, status, progress) {
|
|
const jobItem = document.getElementById(`job-${processId}`);
|
|
if (jobItem) {
|
|
const label = jobItem.querySelector('label');
|
|
label.textContent = `${label.textContent.split(' - ')[0]} - Status: ${status} - Progress: ${progress}%`;
|
|
|
|
if (status === 'Completed') {
|
|
jobItem.querySelector('button').style.display = 'inline-block';
|
|
}
|
|
}
|
|
}
|
|
|
|
function downloadJob(processId) {
|
|
let history = JSON.parse(localStorage.getItem('jobHistory')) || [];
|
|
const job = history.find(job => job.processId === processId);
|
|
|
|
if (job && (Date.now() - job.timestamp) <= (2 * 60 * 60 * 1000)) {
|
|
window.location.href = `download.php?process_id=${processId}`;
|
|
} else {
|
|
alert('This job is no longer available for download.');
|
|
// Remove the job from history if it's too old
|
|
history = history.filter(j => j.processId !== processId);
|
|
localStorage.setItem('jobHistory', JSON.stringify(history));
|
|
updateJobHistoryDisplay();
|
|
}
|
|
}
|
|
|
|
function addToJobHistory(processId, fileName) {
|
|
let history = JSON.parse(localStorage.getItem('jobHistory')) || [];
|
|
history.push({ processId, fileName, status: 'Processing', timestamp: Date.now() });
|
|
localStorage.setItem('jobHistory', JSON.stringify(history));
|
|
updateJobHistoryDisplay();
|
|
}
|
|
|
|
function updateJobHistory(processId, status) {
|
|
let history = JSON.parse(localStorage.getItem('jobHistory')) || [];
|
|
const index = history.findIndex(job => job.processId === processId);
|
|
if (index !== -1) {
|
|
history[index].status = status;
|
|
localStorage.setItem('jobHistory', JSON.stringify(history));
|
|
updateJobHistoryDisplay();
|
|
}
|
|
}
|
|
|
|
function loadJobHistory() {
|
|
updateJobHistoryDisplay();
|
|
}
|
|
|
|
function updateJobHistoryDisplay() {
|
|
cleanupOldJobs();
|
|
|
|
const historyContainer = document.getElementById('jobHistory');
|
|
const history = JSON.parse(localStorage.getItem('jobHistory')) || [];
|
|
|
|
let historyHTML = '<h2>Job History</h2>';
|
|
history.forEach(job => {
|
|
const date = new Date(job.timestamp);
|
|
historyHTML += `
|
|
<div>
|
|
${job.fileName} - ${job.status} - ${date.toLocaleString()}
|
|
${job.status === 'Completed' ?
|
|
`<button onclick="downloadJob('${job.processId}')">Download</button>` :
|
|
''}
|
|
</div>`;
|
|
});
|
|
|
|
historyHTML += '<button id="clearHistory">Clear History</button>';
|
|
|
|
historyContainer.innerHTML = historyHTML;
|
|
|
|
document.getElementById('clearHistory').addEventListener('click', clearJobHistory);
|
|
}
|
|
|
|
function clearJobHistory() {
|
|
localStorage.removeItem('jobHistory');
|
|
updateJobHistoryDisplay();
|
|
}
|
|
|
|
function bulkDownload() {
|
|
const checkedJobs = document.querySelectorAll('#jobList input[type="checkbox"]:checked');
|
|
checkedJobs.forEach(checkbox => {
|
|
const processId = checkbox.id.replace('check-', '');
|
|
downloadJob(processId);
|
|
});
|
|
}
|
|
|
|
function updateSelectedFiles() {
|
|
const fileInput = document.querySelector('input[type="file"]');
|
|
const fileList = document.getElementById('selectedFiles');
|
|
|
|
if (!fileList) {
|
|
const newFileList = document.createElement('div');
|
|
newFileList.id = 'selectedFiles';
|
|
fileInput.parentNode.insertBefore(newFileList, fileInput.nextSibling);
|
|
}
|
|
|
|
const files = fileInput.files;
|
|
let fileListHTML = '<strong>Selected Files:</strong><ul>';
|
|
for (let i = 0; i < files.length; i++) {
|
|
fileListHTML += `<li>${files[i].name}</li>`;
|
|
}
|
|
fileListHTML += '</ul>';
|
|
document.getElementById('selectedFiles').innerHTML = fileListHTML;
|
|
}
|
|
|
|
function addSelectAllButton() {
|
|
const jobList = document.getElementById('jobList');
|
|
if (!document.getElementById('selectAll')) {
|
|
const selectAllButton = document.createElement('button');
|
|
selectAllButton.id = 'selectAll';
|
|
selectAllButton.textContent = 'Select All';
|
|
selectAllButton.onclick = toggleSelectAll;
|
|
jobList.insertBefore(selectAllButton, jobList.firstChild);
|
|
}
|
|
}
|
|
|
|
function toggleSelectAll() {
|
|
const checkboxes = document.querySelectorAll('#jobList input[type="checkbox"]');
|
|
const selectAllButton = document.getElementById('selectAll');
|
|
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
|
|
|
checkboxes.forEach(cb => cb.checked = !allChecked);
|
|
selectAllButton.textContent = allChecked ? 'Select All' : 'Deselect All';
|
|
}
|
|
|
|
function cleanupOldJobs() {
|
|
let history = JSON.parse(localStorage.getItem('jobHistory')) || [];
|
|
const twoHoursAgo = Date.now() - (2 * 60 * 60 * 1000); // 2 hours in milliseconds
|
|
|
|
history = history.filter(job => job.timestamp > twoHoursAgo);
|
|
|
|
localStorage.setItem('jobHistory', JSON.stringify(history));
|
|
}
|