748 lines
28 KiB
JavaScript
748 lines
28 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';
|
|
|
|
// Check for person indices in events
|
|
var personIndices = [];
|
|
if (img.events && img.events.length > 0) {
|
|
img.events.forEach(function(event) {
|
|
var match = event.description.match(/#(\d+)/g);
|
|
if (match) {
|
|
match.forEach(function(m) {
|
|
if (personIndices.indexOf(m) === -1) {
|
|
personIndices.push(m);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
var indicesDisplay = personIndices.length > 0 ?
|
|
'<span class="image-person-indices">' + personIndices.slice(0, 3).join(' ') + '</span>' : '';
|
|
|
|
item.innerHTML = '<span class="image-number">#' + img.id + '</span>' +
|
|
'<span class="image-time">' + time + '</span>' +
|
|
'<span class="image-status">' + status + '</span>' +
|
|
indicesDisplay +
|
|
'<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) {
|
|
// 分组显示
|
|
var localEvents = [];
|
|
var aiEvents = [];
|
|
|
|
data.events.forEach(function(event) {
|
|
if (event.event_type.indexOf('(本地)') >= 0) {
|
|
localEvents.push(event);
|
|
} else if (event.event_type.indexOf('(AI)') >= 0) {
|
|
aiEvents.push(event);
|
|
} else {
|
|
localEvents.push(event);
|
|
}
|
|
});
|
|
|
|
// 本地分析结果
|
|
if (localEvents.length > 0) {
|
|
var localSection = document.createElement('div');
|
|
localSection.className = 'modal-events-section';
|
|
|
|
// 显示人员序号
|
|
var personIndices = [];
|
|
localEvents.forEach(function(event) {
|
|
if (event.person_index) {
|
|
personIndices.push('#' + event.person_index);
|
|
}
|
|
});
|
|
|
|
var indicesDisplay = personIndices.length > 0 ?
|
|
' <span class="person-indices">[' + personIndices.join(', ') + ']</span>' : '';
|
|
|
|
localSection.innerHTML = '<h4 class="section-title local">Local Analysis (' + localEvents.length + ')' + indicesDisplay + '</h4>';
|
|
|
|
localEvents.forEach(function(event) {
|
|
var div = document.createElement('div');
|
|
div.className = 'modal-event source-local';
|
|
var eventType = event.event_type.replace('(本地)', '').trim();
|
|
div.innerHTML = '<span class="event-type">' + eventType + '</span> ' +
|
|
'<span class="event-confidence">' + event.confidence + '</span>' +
|
|
'<div>' + event.description + '</div>';
|
|
localSection.appendChild(div);
|
|
});
|
|
|
|
eventsDiv.appendChild(localSection);
|
|
}
|
|
|
|
// AI分析结果
|
|
if (aiEvents.length > 0) {
|
|
var aiSection = document.createElement('div');
|
|
aiSection.className = 'modal-events-section';
|
|
aiSection.innerHTML = '<h4 class="section-title ai">AI Analysis (' + aiEvents.length + ') </h4>';
|
|
|
|
aiEvents.forEach(function(event) {
|
|
var div = document.createElement('div');
|
|
div.className = 'modal-event source-ai';
|
|
var eventType = event.event_type.replace('(AI)', '').trim();
|
|
div.innerHTML = '<span class="event-type">' + eventType + '</span> ' +
|
|
'<span class="event-confidence">' + event.confidence + '</span>' +
|
|
'<div>' + event.description + '</div>';
|
|
aiSection.appendChild(div);
|
|
});
|
|
|
|
eventsDiv.appendChild(aiSection);
|
|
}
|
|
} else {
|
|
eventsDiv.innerHTML = '<p style="color:#888;">No events recorded</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;
|
|
}
|
|
|
|
// 分组显示:本地分析和AI分析
|
|
var localEvents = [];
|
|
var aiEvents = [];
|
|
|
|
events.forEach(function(event) {
|
|
if (event.event_type.indexOf('(本地)') >= 0 || event.event_type.indexOf('(local)') >= 0) {
|
|
localEvents.push(event);
|
|
} else if (event.event_type.indexOf('(AI)') >= 0) {
|
|
aiEvents.push(event);
|
|
} else {
|
|
localEvents.push(event);
|
|
}
|
|
});
|
|
|
|
// 显示本地分析结果
|
|
if (localEvents.length > 0) {
|
|
var localHeader = document.createElement('div');
|
|
localHeader.className = 'events-header';
|
|
localHeader.innerHTML = '<span class="header-label local">Local Analysis</span><span class="header-count">' + localEvents.length + ' events</span>';
|
|
list.appendChild(localHeader);
|
|
|
|
localEvents.forEach(function(event) {
|
|
var card = createEventCard(event, 'local');
|
|
list.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// 显示AI分析结果
|
|
if (aiEvents.length > 0) {
|
|
var aiHeader = document.createElement('div');
|
|
aiHeader.className = 'events-header';
|
|
aiHeader.innerHTML = '<span class="header-label ai">AI Analysis</span><span class="header-count">' + aiEvents.length + ' events</span>';
|
|
list.appendChild(aiHeader);
|
|
|
|
aiEvents.forEach(function(event) {
|
|
var card = createEventCard(event, 'ai');
|
|
list.appendChild(card);
|
|
});
|
|
}
|
|
}
|
|
|
|
function createEventCard(event, source) {
|
|
var card = document.createElement('div');
|
|
card.className = 'event-card type-' + event.event_type.replace('(本地)', '').replace('(AI)', '').trim() + ' source-' + source;
|
|
card.onclick = function() { openImageModal(event.image_id); };
|
|
|
|
var time = new Date(event.timestamp).toLocaleString();
|
|
var eventType = event.event_type.replace('(本地)', '').replace('(AI)', '').trim();
|
|
|
|
card.innerHTML = '<div class="event-header">' +
|
|
'<span class="event-type">' + eventType + '</span>' +
|
|
'<span class="event-source">' + (source === 'local' ? 'Local' : 'AI') + '</span>' +
|
|
'<span class="event-time">' + time + '</span></div>' +
|
|
'<div class="event-desc">' + event.description + '</div>' +
|
|
'<div class="event-confidence">' + event.confidence + '</div>';
|
|
|
|
return 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');
|
|
}
|
|
|
|
// Stats Management
|
|
function openStatsModal() {
|
|
loadDailyStats();
|
|
loadHistoryStats();
|
|
document.getElementById('stats-modal').classList.add('active');
|
|
}
|
|
|
|
function closeStatsModal() {
|
|
document.getElementById('stats-modal').classList.remove('active');
|
|
}
|
|
|
|
function loadDailyStats() {
|
|
var dateSelect = document.getElementById('stats-date-select');
|
|
var dateValue = dateSelect.value;
|
|
|
|
var targetDate;
|
|
if (dateValue === 'yesterday') {
|
|
var yesterday = new Date();
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
targetDate = yesterday.toISOString().split('T')[0];
|
|
} else {
|
|
targetDate = new Date().toISOString().split('T')[0];
|
|
}
|
|
|
|
fetch(API_BASE + '/api/stats/daily?date=' + targetDate)
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
renderDailyStats(data);
|
|
})
|
|
.catch(function(e) { console.error('Load stats failed:', e); });
|
|
}
|
|
|
|
function renderDailyStats(data) {
|
|
// Summary
|
|
var summaryDiv = document.getElementById('stats-summary');
|
|
summaryDiv.innerHTML = '<div class="stats-summary">' +
|
|
'<span>Date: ' + data.date + '</span>' +
|
|
'<span>Images: ' + data.total_images + '</span>' +
|
|
'<span>Events: ' + data.total_events + '</span>' +
|
|
'</div>';
|
|
|
|
// Person count timeline chart (using simple CSS bars)
|
|
var timeline = data.timeline;
|
|
var chartCanvas = document.getElementById('person-chart');
|
|
|
|
if (timeline.length === 0) {
|
|
chartCanvas.innerHTML = '<p style="color:#888;text-align:center;">No data</p>';
|
|
return;
|
|
}
|
|
|
|
// Build simple bar chart
|
|
var chartHtml = '<div class="timeline-chart">';
|
|
|
|
var maxCount = Math.max.apply(Math, timeline.map(function(t) { return t.person_count || 0 }));
|
|
maxCount = Math.max(maxCount, 1);
|
|
|
|
timeline.slice(-20).forEach(function(t) {
|
|
var timeLabel = new Date(t.time).toLocaleTimeString('en-US', {hour: '2-digit', minute: '2-digit'});
|
|
var count = t.person_count || 0;
|
|
var height = (count / maxCount) * 100;
|
|
|
|
chartHtml += '<div class="timeline-bar">' +
|
|
'<div class="bar-fill" style="height:' + height + '%"></div>' +
|
|
'<div class="bar-label">' + timeLabel + '</div>' +
|
|
'<div class="bar-value">' + count + '</div>' +
|
|
'</div>';
|
|
});
|
|
|
|
chartHtml += '</div>';
|
|
chartCanvas.innerHTML = chartHtml;
|
|
|
|
// Event types distribution
|
|
var eventTypesDiv = document.getElementById('event-types-chart');
|
|
eventTypesDiv.innerHTML = '';
|
|
|
|
if (data.event_types.length === 0) {
|
|
eventTypesDiv.innerHTML = '<p style="color:#888;">No events</p>';
|
|
return;
|
|
}
|
|
|
|
var maxEventCount = Math.max.apply(Math, data.event_types.map(function(e) { return e.count }));
|
|
|
|
data.event_types.forEach(function(et) {
|
|
var width = (et.count / maxEventCount) * 100;
|
|
var barHtml = '<div class="event-type-bar">' +
|
|
'<span class="event-type-label">' + et.type + '</span>' +
|
|
'<div class="event-type-fill" style="width:' + width + '%">' +
|
|
'<span class="event-type-count">' + et.count + '</span>' +
|
|
'</div></div>';
|
|
eventTypesDiv.innerHTML += barHtml;
|
|
});
|
|
}
|
|
|
|
function loadHistoryStats() {
|
|
fetch(API_BASE + '/api/stats/history?days=7')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
renderHistoryStats(data);
|
|
})
|
|
.catch(function(e) { console.error('Load history failed:', e); });
|
|
}
|
|
|
|
function renderHistoryStats(data) {
|
|
var historyDiv = document.getElementById('history-chart');
|
|
|
|
if (data.history.length === 0) {
|
|
historyDiv.innerHTML = '<p style="color:#888;">No history data</p>';
|
|
return;
|
|
}
|
|
|
|
var maxImages = Math.max.apply(Math, data.history.map(function(h) { return h.total_images }));
|
|
maxImages = Math.max(maxImages, 1);
|
|
|
|
var html = '<div class="history-chart">';
|
|
|
|
data.history.reverse().forEach(function(h) {
|
|
var dateLabel = h.date.slice(5); // MM-DD
|
|
var height = (h.total_images / maxImages) * 100;
|
|
|
|
html += '<div class="history-bar">' +
|
|
'<div class="bar-fill images" style="height:' + height + '%"></div>' +
|
|
'<div class="bar-label">' + dateLabel + '</div>' +
|
|
'<div class="bar-value">' + h.total_images + '</div>' +
|
|
'</div>';
|
|
});
|
|
|
|
html += '</div>';
|
|
historyDiv.innerHTML = html;
|
|
}
|
|
|
|
// Persons Management
|
|
function openPersonsModal() {
|
|
loadPersonsList();
|
|
document.getElementById('persons-modal').classList.add('active');
|
|
}
|
|
|
|
function closePersonsModal() {
|
|
document.getElementById('persons-modal').classList.remove('active');
|
|
}
|
|
|
|
function loadPersonsList() {
|
|
fetch(API_BASE + '/api/persons')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
var statsDiv = document.getElementById('persons-stats');
|
|
var listDiv = document.getElementById('persons-list');
|
|
|
|
// 统计信息
|
|
statsDiv.innerHTML = '<div class="stats-summary">' +
|
|
'<span>Total: ' + data.stats.total_persons + '</span>' +
|
|
'<span>Detected: ' + data.stats.total_detections + '</span>' +
|
|
'<span>Known: ' + data.stats.known_persons_detected + '</span>' +
|
|
'<span>New: ' + data.stats.new_persons_added + '</span>' +
|
|
'</div>';
|
|
|
|
// 人员列表
|
|
if (data.persons.length === 0) {
|
|
listDiv.innerHTML = '<p style="color:#888;text-align:center;">No persons recorded yet</p>';
|
|
return;
|
|
}
|
|
|
|
listDiv.innerHTML = '';
|
|
data.persons.forEach(function(person) {
|
|
var item = document.createElement('div');
|
|
item.className = 'person-item';
|
|
|
|
var firstSeen = new Date(person.first_seen).toLocaleString();
|
|
var lastSeen = new Date(person.last_seen).toLocaleString();
|
|
|
|
item.innerHTML = '<div class="person-info">' +
|
|
'<span class="person-name">' + person.name + '</span>' +
|
|
'<span class="person-id">' + person.person_id + '</span>' +
|
|
'</div>' +
|
|
'<div class="person-stats">' +
|
|
'<span>Visits: ' + person.visit_count + '</span>' +
|
|
'<span>First: ' + firstSeen + '</span>' +
|
|
'<span>Last: ' + lastSeen + '</span>' +
|
|
'</div>' +
|
|
'<div class="person-actions">' +
|
|
'<button onclick="renamePerson(\'' + person.person_id + '\')" class="btn-small">Rename</button>' +
|
|
'<button onclick="deletePerson(\'' + person.person_id + '\')" class="btn-small btn-danger">Delete</button>' +
|
|
'</div>';
|
|
|
|
listDiv.appendChild(item);
|
|
});
|
|
})
|
|
.catch(function(e) { console.error('Load persons failed:', e); });
|
|
}
|
|
|
|
function renamePerson(personId) {
|
|
var newName = prompt('Enter new name:');
|
|
if (!newName) return;
|
|
|
|
fetch(API_BASE + '/api/persons/' + personId + '/rename?name=' + encodeURIComponent(newName), {
|
|
method: 'POST'
|
|
})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
showToast('Renamed to ' + newName, 1500);
|
|
loadPersonsList();
|
|
}
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
function deletePerson(personId) {
|
|
if (!confirm('Delete this person?')) return;
|
|
|
|
fetch(API_BASE + '/api/persons/' + personId, {method: 'DELETE'})
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
showToast('Person deleted', 1500);
|
|
loadPersonsList();
|
|
}
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
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;
|
|
|
|
// Person Detection (YOLO)
|
|
document.getElementById('setting-use-yolo').checked = config.use_yolo !== false;
|
|
document.getElementById('setting-yolo-confidence').value = config.yolo_min_confidence || 0.3;
|
|
|
|
// Person Identification
|
|
document.getElementById('setting-use-face-rec').checked = config.use_face_recognition !== false;
|
|
document.getElementById('setting-use-mediapipe').checked = config.use_mediapipe_face !== false;
|
|
document.getElementById('setting-use-color-hist').checked = config.use_color_histogram !== false;
|
|
document.getElementById('setting-match-threshold').value = config.face_match_threshold || 0.6;
|
|
|
|
// Confirmation Settings
|
|
document.getElementById('setting-confirm-frames').value = config.confirm_frames || 3;
|
|
document.getElementById('setting-leave-frames').value = config.leave_frames || 2;
|
|
|
|
// Vision API settings
|
|
document.getElementById('setting-use-vision-api').checked = config.use_vision_api === true;
|
|
document.getElementById('setting-vision-trigger').value = config.vision_api_trigger || 'person_change';
|
|
|
|
// API config
|
|
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),
|
|
|
|
// Person Detection (YOLO)
|
|
use_yolo: document.getElementById('setting-use-yolo').checked,
|
|
yolo_min_confidence: parseFloat(document.getElementById('setting-yolo-confidence').value),
|
|
|
|
// Person Identification
|
|
use_face_recognition: document.getElementById('setting-use-face-rec').checked,
|
|
use_mediapipe_face: document.getElementById('setting-use-mediapipe').checked,
|
|
use_color_histogram: document.getElementById('setting-use-color-hist').checked,
|
|
face_match_threshold: parseFloat(document.getElementById('setting-match-threshold').value),
|
|
|
|
// Confirmation Settings
|
|
confirm_frames: parseInt(document.getElementById('setting-confirm-frames').value),
|
|
leave_frames: parseInt(document.getElementById('setting-leave-frames').value),
|
|
|
|
// Vision API
|
|
use_vision_api: document.getElementById('setting-use-vision-api').checked,
|
|
vision_api_trigger: document.getElementById('setting-vision-trigger').value,
|
|
|
|
// API config
|
|
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) {
|
|
showToast('Settings saved!', 1500);
|
|
closeSettingsModal();
|
|
loadConfig();
|
|
refreshAll();
|
|
}
|
|
})
|
|
.catch(function(e) { showToast('Error: ' + e.message, 3000); });
|
|
}
|
|
|
|
function browseImagesDir() {
|
|
alert('Please enter the path manually.\nExample: D:\\vision-images\nImages will be saved to date subfolders automatically.');
|
|
} |