Files
vision-record/web/static/app.js

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.');
}