373 lines
13 KiB
JavaScript
373 lines
13 KiB
JavaScript
// Vision Record System - Frontend Logic
|
|
|
|
var API_BASE = '';
|
|
var currentImageId = null;
|
|
var refreshTimer = null;
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadConfig();
|
|
refreshAll();
|
|
});
|
|
|
|
// Toast 提示(自动消失)
|
|
function showToast(message, duration) {
|
|
duration = duration || 2000;
|
|
var toast = document.createElement('div');
|
|
toast.className = 'toast';
|
|
toast.textContent = message;
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(function() {
|
|
toast.classList.add('fade-out');
|
|
setTimeout(function() {
|
|
toast.remove();
|
|
}, 300);
|
|
}, duration);
|
|
}
|
|
|
|
// 点击模态框背景关闭
|
|
function closeModalOnBackground(event, modalId) {
|
|
if (event.target.id === modalId) {
|
|
document.getElementById(modalId).classList.remove('active');
|
|
}
|
|
}
|
|
|
|
function loadConfig() {
|
|
fetch(API_BASE + '/api/config')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(config) {
|
|
var interval = config.refresh_interval || 5;
|
|
startAutoRefresh(interval);
|
|
document.getElementById('refresh-info').textContent = 'Refresh: ' + interval + 's';
|
|
})
|
|
.catch(function(e) {
|
|
console.error('Load config failed:', e);
|
|
startAutoRefresh(5);
|
|
});
|
|
}
|
|
|
|
function startAutoRefresh(intervalSeconds) {
|
|
if (refreshTimer) {
|
|
clearInterval(refreshTimer);
|
|
}
|
|
refreshTimer = setInterval(refreshAll, intervalSeconds * 1000);
|
|
}
|
|
|
|
function refreshAll() {
|
|
loadStatus();
|
|
loadImages();
|
|
loadEvents();
|
|
}
|
|
|
|
// Status
|
|
function loadStatus() {
|
|
fetch(API_BASE + '/api/status')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
updateUI(data);
|
|
})
|
|
.catch(function(e) {
|
|
console.error('Load status failed:', e);
|
|
});
|
|
}
|
|
|
|
function updateUI(data) {
|
|
var startBtn = document.getElementById('start-btn');
|
|
var stopBtn = document.getElementById('stop-btn');
|
|
|
|
if (data.scheduler.running) {
|
|
startBtn.disabled = true;
|
|
stopBtn.disabled = false;
|
|
document.getElementById('scheduler-status').innerHTML = 'Status: Running';
|
|
} else {
|
|
startBtn.disabled = false;
|
|
stopBtn.disabled = true;
|
|
document.getElementById('scheduler-status').innerHTML = 'Status: Stopped';
|
|
}
|
|
|
|
document.getElementById('capture-count').textContent = 'Capture count: ' + data.scheduler.capture_count;
|
|
document.getElementById('total-images').textContent = data.stats.total_images;
|
|
document.getElementById('analyzed-images').textContent = data.stats.analyzed_images;
|
|
document.getElementById('total-events').textContent = data.stats.total_events;
|
|
|
|
// Event type filter
|
|
var eventFilter = document.getElementById('event-filter');
|
|
eventFilter.innerHTML = '<option value="all">All types</option>';
|
|
data.stats.event_types.forEach(function(item) {
|
|
var opt = document.createElement('option');
|
|
opt.value = item.type;
|
|
opt.textContent = item.type + ' (' + item.count + ')';
|
|
eventFilter.appendChild(opt);
|
|
});
|
|
}
|
|
|
|
// Control
|
|
function startScheduler() {
|
|
fetch(API_BASE + '/api/scheduler/start', {method: 'POST'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
showToast('Started!', 1500);
|
|
refreshAll();
|
|
} else {
|
|
showToast('Failed: ' + data.error, 3000);
|
|
}
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
function stopScheduler() {
|
|
fetch(API_BASE + '/api/scheduler/stop', {method: 'POST'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
showToast('Stopped!', 1500);
|
|
refreshAll();
|
|
}
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
function captureNow() {
|
|
fetch(API_BASE + '/api/capture', {method: 'POST'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
showToast('Captured! ID: ' + data.image_id, 1500);
|
|
refreshAll();
|
|
} else {
|
|
showToast('Failed: ' + data.error, 3000);
|
|
}
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
function analyzeUnanalyzed() {
|
|
fetch(API_BASE + '/api/analyze/unanalyzed', {method: 'POST'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
showToast('Analyzed ' + data.results.length + ' images', 1500);
|
|
refreshAll();
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
// Images
|
|
function loadImages() {
|
|
var filter = document.getElementById('image-filter').value;
|
|
var analyzedOnly = filter === 'analyzed';
|
|
|
|
fetch(API_BASE + '/api/images?limit=50&analyzed_only=' + analyzedOnly)
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
renderImages(data.images);
|
|
})
|
|
.catch(function(e) { console.error('Load images failed:', e); });
|
|
}
|
|
|
|
function renderImages(images) {
|
|
var list = document.getElementById('images-list');
|
|
list.innerHTML = '';
|
|
|
|
if (images.length === 0) {
|
|
list.innerHTML = '<p style="color:#888;text-align:center;">No images</p>';
|
|
return;
|
|
}
|
|
|
|
images.forEach(function(img) {
|
|
var item = document.createElement('div');
|
|
item.className = 'image-item ' + (img.analyzed ? 'analyzed' : 'unanalyzed');
|
|
item.onclick = function() { openImageModal(img.id); };
|
|
|
|
var time = new Date(img.timestamp).toLocaleString();
|
|
var status = img.analyzed ? 'Analyzed' : 'Unanalyzed';
|
|
var events = img.events_summary || 'No events';
|
|
|
|
item.innerHTML = '<span class="image-number">#' + img.id + '</span>' +
|
|
'<span class="image-time">' + time + '</span>' +
|
|
'<span class="image-status">' + status + '</span>' +
|
|
'<span class="image-events-summary">' + events + '</span>';
|
|
|
|
list.appendChild(item);
|
|
});
|
|
}
|
|
|
|
function openImageModal(imageId) {
|
|
currentImageId = imageId;
|
|
|
|
fetch(API_BASE + '/api/images/' + imageId)
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
var filename = data.path.split('/').pop().split('\\').pop();
|
|
document.getElementById('modal-image').src = '/images/' + filename;
|
|
document.getElementById('modal-title').textContent = 'Image #' + data.id;
|
|
document.getElementById('modal-time').textContent = 'Time: ' + new Date(data.timestamp).toLocaleString();
|
|
|
|
var eventsDiv = document.getElementById('modal-events');
|
|
eventsDiv.innerHTML = '';
|
|
|
|
if (data.events && data.events.length > 0) {
|
|
data.events.forEach(function(event) {
|
|
var div = document.createElement('div');
|
|
div.className = 'modal-event';
|
|
div.innerHTML = '<strong>' + event.event_type + '</strong> ' +
|
|
'<span class="event-confidence">' + event.confidence + '</span>' +
|
|
'<div>' + event.description + '</div>';
|
|
eventsDiv.appendChild(div);
|
|
});
|
|
} else {
|
|
eventsDiv.innerHTML = '<p style="color:#888;">No events</p>';
|
|
}
|
|
|
|
document.getElementById('image-modal').classList.add('active');
|
|
})
|
|
.catch(function(e) { console.error('Load image failed:', e); });
|
|
}
|
|
|
|
function closeImageModal() {
|
|
document.getElementById('image-modal').classList.remove('active');
|
|
currentImageId = null;
|
|
}
|
|
|
|
function analyzeCurrentImage() {
|
|
if (!currentImageId) return;
|
|
|
|
fetch(API_BASE + '/api/analyze/' + currentImageId, {method: 'POST'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
alert('Analyzed!');
|
|
openImageModal(currentImageId);
|
|
refreshAll();
|
|
} else {
|
|
alert('Failed: ' + data.error);
|
|
}
|
|
})
|
|
.catch(function(e) { alert('Error: ' + e.message); });
|
|
}
|
|
|
|
function deleteCurrentImage() {
|
|
if (!currentImageId) return;
|
|
if (!confirm('Delete this image?')) return;
|
|
|
|
fetch(API_BASE + '/api/images/' + currentImageId, {method: 'DELETE'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
closeImageModal();
|
|
refreshAll();
|
|
}
|
|
})
|
|
.catch(function(e) { alert('Error: ' + e.message); });
|
|
}
|
|
|
|
// Events
|
|
function loadEvents() {
|
|
var filter = document.getElementById('event-filter').value;
|
|
var eventType = filter === 'all' ? '' : filter;
|
|
|
|
var url = API_BASE + '/api/events?limit=50';
|
|
if (eventType) {
|
|
url += '&event_type=' + encodeURIComponent(eventType);
|
|
}
|
|
|
|
fetch(url)
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
renderEvents(data.events);
|
|
})
|
|
.catch(function(e) { console.error('Load events failed:', e); });
|
|
}
|
|
|
|
function renderEvents(events) {
|
|
var list = document.getElementById('events-list');
|
|
list.innerHTML = '';
|
|
|
|
if (events.length === 0) {
|
|
list.innerHTML = '<p style="color:#888;text-align:center;">No events</p>';
|
|
return;
|
|
}
|
|
|
|
events.forEach(function(event) {
|
|
var card = document.createElement('div');
|
|
card.className = 'event-card type-' + event.event_type;
|
|
card.onclick = function() { openImageModal(event.image_id); };
|
|
|
|
var time = new Date(event.timestamp).toLocaleString();
|
|
|
|
card.innerHTML = '<div class="event-header">' +
|
|
'<span class="event-type">' + event.event_type + '</span>' +
|
|
'<span class="event-time">' + time + '</span></div>' +
|
|
'<div class="event-desc">' + event.description + '</div>' +
|
|
'<div class="event-confidence">' + event.confidence + '</div>';
|
|
|
|
list.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Filter listeners
|
|
document.getElementById('image-filter').addEventListener('change', loadImages);
|
|
document.getElementById('event-filter').addEventListener('change', loadEvents);
|
|
|
|
// Settings
|
|
function openSettingsModal() {
|
|
loadSettingsForm();
|
|
document.getElementById('settings-modal').classList.add('active');
|
|
}
|
|
|
|
function closeSettingsModal() {
|
|
document.getElementById('settings-modal').classList.remove('active');
|
|
}
|
|
|
|
function loadSettingsForm() {
|
|
fetch(API_BASE + '/api/config')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(config) {
|
|
document.getElementById('setting-images-dir').value = config.images_dir || '';
|
|
document.getElementById('setting-interval').value = config.capture_interval || 60;
|
|
document.getElementById('setting-camera-index').value = config.camera_index || 0;
|
|
document.getElementById('setting-auto-analyze').checked = config.auto_analyze !== false;
|
|
document.getElementById('setting-display-limit').value = config.display_limit || 20;
|
|
document.getElementById('setting-refresh-interval').value = config.refresh_interval || 5;
|
|
document.getElementById('setting-api-url').value = config.vision_api_url || '';
|
|
document.getElementById('setting-api-key').value = config.vision_api_key || '';
|
|
document.getElementById('setting-model').value = config.vision_model || '';
|
|
})
|
|
.catch(function(e) { console.error('Load settings failed:', e); });
|
|
}
|
|
|
|
function saveSettings() {
|
|
var settings = {
|
|
images_dir: document.getElementById('setting-images-dir').value,
|
|
capture_interval: parseInt(document.getElementById('setting-interval').value),
|
|
camera_index: parseInt(document.getElementById('setting-camera-index').value),
|
|
auto_analyze: document.getElementById('setting-auto-analyze').checked,
|
|
display_limit: parseInt(document.getElementById('setting-display-limit').value),
|
|
refresh_interval: parseInt(document.getElementById('setting-refresh-interval').value),
|
|
vision_api_url: document.getElementById('setting-api-url').value,
|
|
vision_api_key: document.getElementById('setting-api-key').value,
|
|
vision_model: document.getElementById('setting-model').value
|
|
};
|
|
|
|
fetch(API_BASE + '/api/config', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(settings)
|
|
})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
alert('Settings saved!');
|
|
closeSettingsModal();
|
|
// Reload config to apply new refresh interval
|
|
loadConfig();
|
|
refreshAll();
|
|
}
|
|
})
|
|
.catch(function(e) { alert('Error: ' + e.message); });
|
|
}
|
|
|
|
function browseImagesDir() {
|
|
alert('Please enter the path manually.\nExample: D:\\vision-images\nImages will be saved to date subfolders automatically.');
|
|
} |