hoàn thành cơ bản các chức năng
This commit is contained in:
+350
-14
@@ -258,6 +258,29 @@
|
||||
margin: 5px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Animations for notifications */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -369,7 +392,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin: 20px 0 15px; color: #667eea;">🛰️ Dữ Liệu Vệ Tinh</h3>
|
||||
<h3 style="margin: 20px 0 15px; color: #667eea;">� Dataset Cache Preset</h3>
|
||||
<div class="form-group">
|
||||
<label>Chọn Dataset đã cache:</label>
|
||||
<select id="cachePreset" style="font-size: 14px;">
|
||||
<option value="">-- Không dùng cache preset --</option>
|
||||
</select>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 5px;">
|
||||
💡 Chọn dataset cache có sẵn để tự động điền các thông số tương ứng
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin: 20px 0 15px; color: #667eea;">�🛰️ Dữ Liệu Vệ Tinh</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Số scenes tối đa:</label>
|
||||
@@ -390,8 +424,27 @@
|
||||
</div>
|
||||
|
||||
<h3 style="margin: 20px 0 15px; color: #667eea;">🤖 Model Parameters</h3>
|
||||
|
||||
<!-- Model Type Selection -->
|
||||
<div class="form-group">
|
||||
<label><strong>🧠 Loại Model:</strong></label>
|
||||
<select id="modelType" required style="font-weight: 600; font-size: 14px;">
|
||||
<option value="xgboost" selected>🚀 XGBoost (Nhanh, Chính xác cao, Hỗ trợ GPU)</option>
|
||||
<option value="random_forest">🌲 Random Forest (Ổn định, Không cần GPU)</option>
|
||||
<option value="decision_tree">🌳 Decision Tree (Đơn giản, Nhanh nhất)</option>
|
||||
<option value="svm">🎯 SVM (Chính xác, Chậm với dữ liệu lớn)</option>
|
||||
<option value="cnn">🧠 CNN - Deep Learning (PyTorch, Tốt với ảnh vệ tinh, Hỗ trợ GPU)</option>
|
||||
</select>
|
||||
<div style="margin-top: 8px; padding: 10px; background: #e7f3ff; border-radius: 5px; font-size: 12px;">
|
||||
<span id="modelTypeDesc" style="color: #1976d2;">
|
||||
✓ XGBoost: Tốt nhất cho dữ liệu satellite, hỗ trợ GPU, training nhanh
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model-specific parameters -->
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<div class="form-group" id="nEstimatorsGroup">
|
||||
<label>N Estimators:</label>
|
||||
<input type="number" id="nEstimators" value="100" min="10" max="1000" required>
|
||||
</div>
|
||||
@@ -399,11 +452,11 @@
|
||||
<label>Max Depth:</label>
|
||||
<input type="number" id="maxDepth" value="20" min="1" max="50" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group" id="learningRateGroup">
|
||||
<label>Learning Rate:</label>
|
||||
<input type="number" step="0.01" id="learningRate" value="0.1" min="0.01" max="1" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group" id="useGpuGroup">
|
||||
<label>Use GPU:</label>
|
||||
<select id="useGpu" required>
|
||||
<option value="true" selected>Có (RTX 4060)</option>
|
||||
@@ -412,6 +465,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cache Option -->
|
||||
<div class="form-group" style="margin-top: 20px; padding: 15px; background: #fff8dc; border-radius: 8px; border: 1px solid #ffc107;">
|
||||
<label style="display: flex; align-items: center; cursor: pointer; margin: 0;">
|
||||
<input type="checkbox" id="useCache" checked style="width: 18px; height: 18px; margin-right: 10px;">
|
||||
<span style="font-weight: 600; color: #856404;">💾 Sử dụng Cache Dataset</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; color: #856404; margin-top: 8px; margin-left: 28px;">
|
||||
✅ <strong>Khuyến nghị:</strong> Bật để test nhanh hơn. Lần đầu load dữ liệu sẽ chậm, nhưng các lần sau rất nhanh (không cần download lại từ satellite).<br>
|
||||
📊 <span id="cacheInfo">Đang kiểm tra cache...</span>
|
||||
</div>
|
||||
<div style="margin-top: 10px; margin-left: 28px;">
|
||||
<button type="button" class="btn btn-secondary" onclick="clearCache()" style="font-size: 12px; padding: 5px 10px; background: #dc3545;">
|
||||
🗑️ Xóa Cache
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="loadCacheInfo()" style="font-size: 12px; padding: 5px 10px; background: #17a2b8; margin-left: 5px;">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; text-align: center;">
|
||||
<button type="submit" class="btn btn-primary" id="startBtn">
|
||||
🚀 Bắt Đầu Training
|
||||
@@ -526,14 +599,15 @@
|
||||
<h3 style="color: #28a745; margin-bottom: 10px;">✅ Kết Quả Dự Đoán</h3>
|
||||
<div style="background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%); padding: 20px; border-radius: 8px; border: 2px solid #28a745;">
|
||||
<div id="predResultText" style="font-size: 14px; line-height: 1.8;"></div>
|
||||
<div style="margin-top: 15px; text-align: center;">
|
||||
<button onclick="downloadPredictionResult()" class="btn btn-secondary" style="background: #28a745;">
|
||||
📥 Tải Kết Quả
|
||||
</button>
|
||||
<button onclick="viewPredictionResult()" class="btn btn-secondary" style="background: #17a2b8; margin-left: 10px;">
|
||||
👁️ Xem Chi Tiết
|
||||
</button>
|
||||
</div>
|
||||
<div id="downloadLinkContainer" style="margin-top: 15px; text-align: center;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Previous Predictions List -->
|
||||
<div id="predictionsListSection" style="margin-top: 20px;">
|
||||
<h3 style="color: #ff6b6b; margin-bottom: 10px;">📂 Các File Dự Đoán Đã Tạo</h3>
|
||||
<div id="predictionsList" style="background: #f8f9fa; padding: 15px; border-radius: 8px; max-height: 200px; overflow-y: auto;">
|
||||
<p style="color: #666; text-align: center;">Đang tải...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -631,10 +705,12 @@
|
||||
max_scenes: parseInt(document.getElementById('maxScenes').value),
|
||||
cloud_cover: parseInt(document.getElementById('cloudCover').value),
|
||||
resolution: parseInt(document.getElementById('resolution').value),
|
||||
model_type: document.getElementById('modelType').value,
|
||||
n_estimators: parseInt(document.getElementById('nEstimators').value),
|
||||
max_depth: parseInt(document.getElementById('maxDepth').value),
|
||||
learning_rate: parseFloat(document.getElementById('learningRate').value),
|
||||
use_gpu: document.getElementById('useGpu').value === 'true'
|
||||
use_gpu: document.getElementById('useGpu').value === 'true',
|
||||
use_cache: document.getElementById('useCache').checked
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -994,10 +1070,209 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Model type change handler
|
||||
function updateModelTypeUI() {
|
||||
const modelType = document.getElementById('modelType').value;
|
||||
const desc = document.getElementById('modelTypeDesc');
|
||||
const nEstimatorsGroup = document.getElementById('nEstimatorsGroup');
|
||||
const learningRateGroup = document.getElementById('learningRateGroup');
|
||||
const useGpuGroup = document.getElementById('useGpuGroup');
|
||||
|
||||
const descriptions = {
|
||||
'xgboost': '✓ XGBoost: Tốt nhất cho dữ liệu satellite, hỗ trợ GPU, training nhanh',
|
||||
'random_forest': '✓ Random Forest: Ổn định, không overfitting, phù hợp mọi kích thước dữ liệu',
|
||||
'decision_tree': '✓ Decision Tree: Đơn giản nhất, nhanh nhất, dễ hiểu, phù hợp để test nhanh',
|
||||
'svm': '✓ SVM: Chính xác cao với dữ liệu nhỏ, chậm với dữ liệu lớn',
|
||||
'cnn': '✓ CNN PyTorch: Mạnh nhất với ảnh vệ tinh, tự học features, tương thích GPU tốt, cần pip install torch'
|
||||
};
|
||||
|
||||
desc.textContent = descriptions[modelType];
|
||||
|
||||
// Show/hide parameters based on model type
|
||||
if (modelType === 'xgboost') {
|
||||
nEstimatorsGroup.style.display = '';
|
||||
learningRateGroup.style.display = '';
|
||||
useGpuGroup.style.display = '';
|
||||
} else if (modelType === 'random_forest') {
|
||||
nEstimatorsGroup.style.display = '';
|
||||
learningRateGroup.style.display = 'none';
|
||||
useGpuGroup.style.display = 'none';
|
||||
} else if (modelType === 'decision_tree') {
|
||||
nEstimatorsGroup.style.display = 'none';
|
||||
learningRateGroup.style.display = 'none';
|
||||
useGpuGroup.style.display = 'none';
|
||||
} else if (modelType === 'svm') {
|
||||
nEstimatorsGroup.style.display = 'none';
|
||||
learningRateGroup.style.display = 'none';
|
||||
useGpuGroup.style.display = 'none';
|
||||
} else if (modelType === 'cnn') {
|
||||
// CNN uses n_estimators as epochs and supports GPU
|
||||
nEstimatorsGroup.style.display = '';
|
||||
document.querySelector('#nEstimatorsGroup label').textContent = 'Epochs (số lần training):';
|
||||
document.getElementById('nEstimators').value = 50;
|
||||
learningRateGroup.style.display = 'none';
|
||||
useGpuGroup.style.display = ''; // Show GPU option for CNN
|
||||
}
|
||||
|
||||
// Reset n_estimators label for non-CNN
|
||||
if (modelType !== 'cnn' && modelType !== 'decision_tree' && modelType !== 'svm') {
|
||||
document.querySelector('#nEstimatorsGroup label').textContent = 'N Estimators:';
|
||||
}
|
||||
}
|
||||
|
||||
// ============== CACHE MANAGEMENT ==============
|
||||
|
||||
let cacheFiles = []; // Store cache files data
|
||||
|
||||
// Load cache info and populate preset dropdown
|
||||
async function loadCacheInfo() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/cache/info`);
|
||||
const data = await response.json();
|
||||
|
||||
// Update cache info display
|
||||
const cacheInfoSpan = document.getElementById('cacheInfo');
|
||||
if (data.exists && data.count > 0) {
|
||||
cacheInfoSpan.innerHTML = `✅ Có <strong>${data.count}</strong> file cache (${data.total_size_mb} MB)`;
|
||||
cacheInfoSpan.style.color = '#28a745';
|
||||
} else {
|
||||
cacheInfoSpan.innerHTML = '❌ Chưa có cache nào';
|
||||
cacheInfoSpan.style.color = '#6c757d';
|
||||
}
|
||||
|
||||
// Populate cache preset dropdown
|
||||
const cachePreset = document.getElementById('cachePreset');
|
||||
cachePreset.innerHTML = '<option value="">-- Không dùng cache preset --</option>';
|
||||
|
||||
if (data.exists && data.files && data.files.length > 0) {
|
||||
cacheFiles = data.files;
|
||||
data.files.forEach((file, index) => {
|
||||
const meta = file.metadata;
|
||||
const option = document.createElement('option');
|
||||
option.value = index;
|
||||
|
||||
// Format display text with metadata
|
||||
let displayText = `Cache #${index + 1}`;
|
||||
if (meta && meta.n_samples) {
|
||||
displayText += ` (${meta.n_samples} mẫu`;
|
||||
if (meta.start_date && meta.end_date) {
|
||||
displayText += `, ${meta.start_date} → ${meta.end_date}`;
|
||||
}
|
||||
if (meta.resolution) {
|
||||
displayText += `, ${meta.resolution}m`;
|
||||
}
|
||||
displayText += ')';
|
||||
} else {
|
||||
displayText += ` (${file.size_mb} MB, ${new Date(file.modified).toLocaleString('vi-VN')})`;
|
||||
}
|
||||
|
||||
option.textContent = displayText;
|
||||
cachePreset.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading cache info:', error);
|
||||
document.getElementById('cacheInfo').innerHTML = '⚠️ Lỗi kiểm tra cache';
|
||||
}
|
||||
}
|
||||
|
||||
// Apply cache preset to form inputs
|
||||
function applyCachePreset() {
|
||||
const selectIndex = document.getElementById('cachePreset').value;
|
||||
|
||||
if (selectIndex === '') {
|
||||
// Clear preset - no auto-fill
|
||||
return;
|
||||
}
|
||||
|
||||
const fileData = cacheFiles[parseInt(selectIndex)];
|
||||
if (!fileData || !fileData.metadata) {
|
||||
alert('Không có metadata cho cache này');
|
||||
return;
|
||||
}
|
||||
|
||||
const meta = fileData.metadata;
|
||||
|
||||
// Fill bbox inputs
|
||||
if (meta.min_lon !== undefined) {
|
||||
document.getElementById('minLon').value = meta.min_lon;
|
||||
document.getElementById('maxLon').value = meta.max_lon;
|
||||
document.getElementById('minLat').value = meta.min_lat;
|
||||
document.getElementById('maxLat').value = meta.max_lat;
|
||||
|
||||
// Update map rectangle
|
||||
if (rectangle) {
|
||||
drawnItems.removeLayer(rectangle);
|
||||
}
|
||||
const bounds = L.latLngBounds(
|
||||
[meta.min_lat, meta.min_lon],
|
||||
[meta.max_lat, meta.max_lon]
|
||||
);
|
||||
rectangle = L.rectangle(bounds, {color: '#3388ff', weight: 3, fillOpacity: 0.2});
|
||||
drawnItems.addLayer(rectangle);
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
|
||||
// Fill time range inputs
|
||||
if (meta.start_date) {
|
||||
document.getElementById('startDate').value = meta.start_date;
|
||||
}
|
||||
if (meta.end_date) {
|
||||
document.getElementById('endDate').value = meta.end_date;
|
||||
}
|
||||
|
||||
// Fill resolution if available
|
||||
if (meta.resolution) {
|
||||
document.getElementById('resolution').value = meta.resolution;
|
||||
}
|
||||
|
||||
// Note to user
|
||||
const presetNote = `📌 Đã áp dụng cache preset: ${meta.n_samples || '?'} mẫu, ${meta.start_date || '?'} → ${meta.end_date || '?'}`;
|
||||
console.log(presetNote);
|
||||
|
||||
// Show notification
|
||||
const notification = document.createElement('div');
|
||||
notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#28a745;color:white;padding:15px 20px;border-radius:8px;box-shadow:0 4px 6px rgba(0,0,0,0.1);z-index:10000;animation:slideIn 0.3s ease-out;';
|
||||
notification.innerHTML = `<strong>✅ Cache Preset Applied</strong><br>${presetNote}`;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = 'slideOut 0.3s ease-out';
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
async function clearCache() {
|
||||
if (!confirm('Bạn có chắc muốn xóa toàn bộ cache?\n\nCache giúp test nhanh hơn bằng cách lưu lại dữ liệu đã download.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/cache/clear`, {
|
||||
method: 'POST'
|
||||
});
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
loadCacheInfo(); // Refresh info
|
||||
} catch (error) {
|
||||
alert('Lỗi xóa cache: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize map when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initMap();
|
||||
initPredictionMap();
|
||||
loadPredictionsList(); // Load predictions list on page load
|
||||
loadCacheInfo(); // Load cache info
|
||||
|
||||
// Add model type change listener
|
||||
document.getElementById('modelType').addEventListener('change', updateModelTypeUI);
|
||||
updateModelTypeUI(); // Initial update
|
||||
|
||||
// Add cache preset change listener
|
||||
document.getElementById('cachePreset').addEventListener('change', applyCachePreset);
|
||||
});
|
||||
|
||||
// ============== PREDICTION FUNCTIONALITY ==============
|
||||
@@ -1207,6 +1482,10 @@
|
||||
// Store result globally for download/view functions
|
||||
window.lastPredictionResult = result;
|
||||
|
||||
// Extract filename from path
|
||||
const filename = result.output_file.split('/').pop();
|
||||
const downloadUrl = `${API_BASE}/predictions/download/${filename}`;
|
||||
|
||||
resultText.innerHTML = `
|
||||
<div style="margin-bottom: 10px;">
|
||||
<strong>📁 File kết quả:</strong><br>
|
||||
@@ -1220,19 +1499,76 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add download link
|
||||
const downloadContainer = document.getElementById('downloadLinkContainer');
|
||||
downloadContainer.innerHTML = `
|
||||
<a href="${downloadUrl}"
|
||||
class="btn btn-primary"
|
||||
style="background: #28a745; padding: 12px 24px; text-decoration: none; display: inline-block; margin-right: 10px;"
|
||||
download="${filename}">
|
||||
📥 Tải GeoTIFF
|
||||
</a>
|
||||
<button onclick="viewPredictionResult()" class="btn btn-secondary" style="background: #17a2b8;">
|
||||
👁️ Xem Chi Tiết
|
||||
</button>
|
||||
<div style="margin-top: 10px; font-size: 12px; color: #666;">
|
||||
Hoặc copy link: <a href="${downloadUrl}" target="_blank" style="color: #28a745;">${downloadUrl}</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
// Refresh predictions list
|
||||
loadPredictionsList();
|
||||
}
|
||||
|
||||
// Download prediction result
|
||||
function downloadPredictionResult() {
|
||||
if (window.lastPredictionResult) {
|
||||
const result = window.lastPredictionResult;
|
||||
alert('File kết quả: ' + result.output_file + '\n\nĐể tải file, vui lòng truy cập thư mục predictions/ trên server.');
|
||||
const filename = result.output_file.split('/').pop();
|
||||
const downloadUrl = `${API_BASE}/predictions/download/${filename}`;
|
||||
window.open(downloadUrl, '_blank');
|
||||
} else {
|
||||
alert('Chưa có kết quả dự đoán nào!');
|
||||
}
|
||||
}
|
||||
|
||||
// Load list of previous predictions
|
||||
async function loadPredictionsList() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/predictions/list`);
|
||||
const data = await response.json();
|
||||
|
||||
const listDiv = document.getElementById('predictionsList');
|
||||
|
||||
if (data.predictions && data.predictions.length > 0) {
|
||||
listDiv.innerHTML = data.predictions.map(pred => `
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 10px; margin-bottom: 8px; background: white; border-radius: 6px; border: 1px solid #ddd;">
|
||||
<div style="flex: 1;">
|
||||
<strong style="color: #333;">📄 ${pred.filename}</strong>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 3px;">
|
||||
📅 ${new Date(pred.created).toLocaleString('vi-VN')} | 💾 ${pred.size_mb} MB
|
||||
</div>
|
||||
</div>
|
||||
<a href="${pred.download_url}"
|
||||
class="btn btn-secondary"
|
||||
style="background: #28a745; padding: 6px 12px; font-size: 12px; text-decoration: none;"
|
||||
download="${pred.filename}">
|
||||
📥 Tải về
|
||||
</a>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
listDiv.innerHTML = '<p style="color: #666; text-align: center;">Chưa có file dự đoán nào.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading predictions list:', error);
|
||||
document.getElementById('predictionsList').innerHTML =
|
||||
'<p style="color: #dc3545; text-align: center;">Lỗi tải danh sách: ' + error.message + '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// View prediction result details
|
||||
function viewPredictionResult() {
|
||||
if (window.lastPredictionResult) {
|
||||
|
||||
Reference in New Issue
Block a user