Files
labweb/public/js/Tools.js
2025-12-16 11:39:15 +08:00

988 lines
39 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Backend API Base URL (使用相对路径,避免跨域问题)
const API_BASE_URL = '/api';
// Tab switching functionality
function switchTab(tabName) {
// Remove active class from all tabs
document.querySelectorAll('.tab-item').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-panel').forEach(panel => {
panel.classList.remove('active');
});
// Add active class to current tab
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
document.querySelector(`#${tabName}-panel`).classList.add('active');
}
// File status update functionality
function updateFileStatus(input, statusElementId = 'file-status') {
const fileStatus = statusElementId ?
document.getElementById(statusElementId) :
document.querySelector('.file-status');
if (input.files && input.files.length > 0) {
const fileCount = input.files.length;
if (fileCount === 1) {
fileStatus.textContent = input.files[0].name;
} else {
fileStatus.textContent = `${fileCount} files selected`;
}
fileStatus.style.color = '#4A90A4';
// Validate email requirement
const predictionType = input.id.replace('-file-input', '');
validateEmailRequirement(predictionType);
// Show email-progress-section and email container if at least 1 file
const emailProgressSection = document.getElementById(`${predictionType}-email-progress`);
const emailContainer = document.getElementById(`${predictionType}-email-container`);
if (emailProgressSection) {
emailProgressSection.style.display = 'block';
}
if (emailContainer) {
emailContainer.style.display = 'block';
}
// Clear previous results when selecting new file
if (currentAnalysisId) {
clearResultContent();
currentAnalysisId = null;
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
const progressTab = document.getElementById(`${predictionType}-progress-tab`);
if (progressTab) {
progressTab.style.display = 'none';
}
// We don't hide email section here anymore as long as files are selected
}
} else {
fileStatus.textContent = 'No file selected';
fileStatus.style.color = '#666';
// Hide email-progress section when no files
const predictionType = input.id.replace('-file-input', '');
const emailProgressSection = document.getElementById(`${predictionType}-email-progress`);
const emailContainer = document.getElementById(`${predictionType}-email-container`);
if (emailProgressSection) {
emailProgressSection.style.display = 'none';
}
if (emailContainer) {
emailContainer.style.display = 'none';
}
}
}
// Validate email requirement
function validateEmailRequirement(predictionType) {
const fileInput = document.getElementById(`${predictionType}-file-input`);
const submitBtn = document.getElementById(`${predictionType}-submit-btn`);
const emailInput = document.getElementById(`${predictionType}-email-input`);
const emailError = document.getElementById(`${predictionType}-email-error`);
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
return true; // No files, validation not needed
}
const fileCount = fileInput.files.length;
const emailValue = emailInput ? emailInput.value.trim() : '';
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (fileCount >= 2) {
// More than 2 files, email is required
if (!emailValue || !emailRegex.test(emailValue)) {
if (emailError) {
emailError.textContent = 'Valid email is required for 2+ files';
emailError.style.display = 'block';
}
submitBtn.disabled = true;
return false;
} else {
if (emailError) {
emailError.style.display = 'none';
}
submitBtn.disabled = false;
return true;
}
} else {
// 1 file, email is optional but must be valid if entered
if (emailValue && !emailRegex.test(emailValue)) {
if (emailError) {
emailError.textContent = 'Please enter a valid email address';
emailError.style.display = 'block';
}
submitBtn.disabled = true;
return false;
} else {
if (emailError) {
emailError.style.display = 'none';
}
submitBtn.disabled = false;
return true;
}
}
}
// Global variables
let currentAnalysisId = null;
let progressInterval = null;
let startTime = null;
let allAnalysisIds = []; // Track all analysis IDs for "analysis all" mode
let allAnalysisResults = {}; // Store results from all analyses
// let currentJobId = null; // Deprecated, unified to currentAnalysisId
// Map frontend prediction types to backend analysis types
function mapAnalysisType(predictionType) {
const typeMap = {
'nutrition': 'nutrition',
'ph': 'ph',
'temperature': 'temperature',
'oxygen': 'oxygen',
'growth': 'growth'
};
return typeMap[predictionType] || 'nutrition';
}
// Handle submit functionality
async function handleSubmit(predictionType) {
const currentTab = document.querySelector('.tab-panel.active');
console.log('predictionType:', predictionType);
const fileInput = currentTab.querySelector('input[type="file"]');
const emailInput = document.getElementById(`${predictionType}-email-input`);
const emailProgressSection = document.getElementById(`${predictionType}-email-progress`);
const submitBtn = document.getElementById(`${predictionType}-submit-btn`);
const analysisAllCheckbox = document.getElementById(`${predictionType}-analysis-all`);
// Check if file is selected
if (!fileInput.files || fileInput.files.length === 0) {
alert('Please select genome file(s) first!');
return;
}
const fileCount = fileInput.files.length;
const emailValue = emailInput ? emailInput.value.trim() : '';
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isAnalysisAll = analysisAllCheckbox && analysisAllCheckbox.checked;
// Validate email logic again before submission
if (fileCount >= 2) {
if (!emailValue || !emailRegex.test(emailValue)) {
alert('Please enter a valid email address. Email is required when uploading 2+ files.');
if (emailInput) emailInput.focus();
return;
}
} else if (fileCount === 1 && emailValue) {
// Optional email validation
if (!emailRegex.test(emailValue)) {
alert('Please enter a valid email address.');
if (emailInput) emailInput.focus();
return;
}
}
// Clean up old state
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
// Clear result content
clearResultContent();
allAnalysisIds = [];
allAnalysisResults = {};
// Disable submit button
if (submitBtn) {
submitBtn.disabled = true;
}
// Ensure email progress section is visible
if (emailProgressSection) {
emailProgressSection.style.display = 'block';
}
const emailContainer = document.getElementById(`${predictionType}-email-container`);
const progressTab = document.getElementById(`${predictionType}-progress-tab`);
const progressFill = document.getElementById(`${predictionType}-progress-fill`);
const progressText = document.getElementById(`${predictionType}-progress-text`);
const progressDetails = document.getElementById(`${predictionType}-progress-details`);
// Show progress tab
if (progressTab) {
progressTab.style.display = 'block';
}
// Show stop button
const stopBtn = document.getElementById(`${predictionType}-stop-btn`);
if (stopBtn) {
stopBtn.style.display = 'block';
}
// Initialize progress
if (progressFill) {
progressFill.style.width = '0%';
}
if (progressText) {
progressText.textContent = isAnalysisAll ? 'Starting all analyses...' : 'Uploading files...';
}
if (progressDetails) {
progressDetails.textContent = '';
}
try {
// If analysis all is selected, submit single request with analysis_type='all'
if (isAnalysisAll) {
await submitAnalysisAll(fileInput, emailValue, predictionType, progressText, progressFill, progressDetails, submitBtn, progressTab);
} else {
// Single analysis submission
await submitSingleAnalysis(predictionType, fileInput, emailValue, progressText, progressFill, submitBtn, progressTab);
}
} catch (error) {
console.error('Upload error:', error);
alert(`Upload failed: ${error.message}`);
// Reset UI
if (submitBtn) {
submitBtn.disabled = false;
}
if (progressTab) {
progressTab.style.display = 'none';
}
if (progressText) {
progressText.textContent = 'Upload failed';
}
}
}
// Submit single analysis
async function submitSingleAnalysis(predictionType, fileInput, emailValue, progressText, progressFill, submitBtn, progressTab) {
// Create FormData for file upload
const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files', fileInput.files[i]);
}
// Add analysis type and email
const backendAnalysisType = mapAnalysisType(predictionType);
formData.append('analysis_type', backendAnalysisType);
console.log('analysis_type:', backendAnalysisType);
if (emailValue) {
formData.append('email', emailValue);
}
// Upload files
const uploadResponse = await fetch(`${API_BASE_URL}/upload`, {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
const errorData = await uploadResponse.json();
throw new Error(errorData.error || 'Upload failed');
}
const uploadResult = await uploadResponse.json();
currentAnalysisId = uploadResult.analysis_id;
startTime = Date.now();
// Update progress
if (progressText) {
progressText.textContent = 'Analysis started...';
}
if (progressFill) {
progressFill.style.width = '10%';
}
// Get fileName for single file analysis
const fileName = fileInput.files.length === 1 ? fileInput.files[0].name : null;
// Start polling for status
startProgressPolling(predictionType, currentAnalysisId, fileName);
}
// Submit analysis all mode - single request with analysis_type='all'
async function submitAnalysisAll(fileInput, emailValue, currentPredictionType, progressText, progressFill, progressDetails, submitBtn, progressTab) {
// Create FormData for file upload
const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files', fileInput.files[i]);
}
// Add analysis type as 'all' and email
formData.append('analysis_type', 'all');
console.log('analysis_type: all (all analyses)');
if (emailValue) {
formData.append('email', emailValue);
}
// Upload files and start all analyses
const uploadResponse = await fetch(`${API_BASE_URL}/upload`, {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
const errorData = await uploadResponse.json();
throw new Error(errorData.error || 'Upload failed');
}
const uploadResult = await uploadResponse.json();
currentAnalysisId = uploadResult.analysis_id;
startTime = Date.now();
// Update progress
if (progressText) {
progressText.textContent = 'All analyses started...';
}
if (progressFill) {
progressFill.style.width = '10%';
}
if (progressDetails) {
progressDetails.textContent = 'Processing all analysis types...';
}
// Get fileName for single file analysis
const fileName = fileInput.files.length === 1 ? fileInput.files[0].name : null;
// Show stop button for current prediction type (not 'all')
const stopBtn = document.getElementById(`${currentPredictionType}-stop-btn`);
if (stopBtn) {
stopBtn.style.display = 'block';
}
// Start polling for status - use 'all' as prediction type for display
startProgressPolling('all', currentAnalysisId, fileName);
}
// Start polling for analysis progress
function startProgressPolling(predictionType, analysisId, fileName = null) {
if (progressInterval) {
clearInterval(progressInterval);
}
// Get current active tab's prediction type for UI elements
// If predictionType is 'all', use the active tab's type for UI elements
const currentTab = document.querySelector('.tab-panel.active');
const activeTabType = currentTab ? currentTab.id.replace('-panel', '') : predictionType;
const uiPredictionType = predictionType === 'all' ? activeTabType : predictionType;
// Get fileName from current file input if not provided
if (!fileName) {
const fileInput = currentTab ? currentTab.querySelector('input[type="file"]') : null;
if (fileInput && fileInput.files && fileInput.files.length === 1) {
fileName = fileInput.files[0].name;
}
}
progressInterval = setInterval(async () => {
try {
const response = await fetch(`${API_BASE_URL}/status/${analysisId}`);
if (!response.ok) {
throw new Error('Failed to get status');
}
const statusData = await response.json();
const progressFill = document.getElementById(`${uiPredictionType}-progress-fill`);
const progressText = document.getElementById(`${uiPredictionType}-progress-text`);
const progressDetails = document.getElementById(`${uiPredictionType}-progress-details`);
const submitBtn = document.getElementById(`${uiPredictionType}-submit-btn`);
const stopBtn = document.getElementById(`${uiPredictionType}-stop-btn`);
// Update progress bar
if (progressFill) {
progressFill.style.width = `${statusData.progress || 0}%`;
}
// Ensure stop button is visible during analysis
if (stopBtn && (statusData.status === 'queued' || statusData.status === 'analyzing' || statusData.status === 'created')) {
stopBtn.style.display = 'block';
}
// Update status text
if (statusData.status === 'completed') {
if (progressText) {
progressText.textContent = 'Analysis completed!';
}
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
if (submitBtn) {
submitBtn.disabled = false;
}
if (stopBtn) {
stopBtn.style.display = 'none';
}
// Display results - pass fileName for single file analysis
if (statusData.result) {
displayResults(statusData.result, predictionType, fileName);
}
} else if (statusData.status === 'failed') {
if (progressText) {
progressText.textContent = `Analysis failed: ${statusData.error || 'Unknown error'}`;
}
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
if (submitBtn) {
submitBtn.disabled = false;
}
if (stopBtn) {
stopBtn.style.display = 'none';
}
} else {
// Update progress details
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const statusMessages = {
'queued': predictionType === 'all' ? 'Waiting in queue for all analyses...' : 'Waiting in queue...',
'analyzing': predictionType === 'all' ? 'Running all analyses...' : 'Analyzing...',
'created': 'Preparing...'
};
if (progressText) {
let statusMsg = statusMessages[statusData.status] || `Processing... (${statusData.progress || 0}%)`;
if (predictionType === 'all' && statusData.status === 'analyzing') {
statusMsg = `Running all analyses... (${statusData.progress || 0}%)`;
}
progressText.textContent = statusMsg;
}
if (progressDetails) {
const detailMsg = predictionType === 'all'
? `All analyses in progress - Elapsed: ${elapsed}s`
: `Elapsed time: ${elapsed}s`;
progressDetails.textContent = detailMsg;
}
}
} catch (error) {
console.error('Status polling error:', error);
}
}, 2000); // Poll every 2 seconds
}
// Extract genome ID from filename
function extractGenomeId(filename) {
// Remove file extension
const nameWithoutExt = filename.replace(/\.(fna|fasta|fa)$/i, '');
// Return the filename without extension as genome ID
return nameWithoutExt;
}
// Display analysis results
function displayResults(result, predictionType, fileName = null) {
const resultContent = document.getElementById('result-content');
const downloadBtn = document.getElementById('download-btn');
if (!resultContent) return;
// Show download button
if (downloadBtn) {
downloadBtn.style.display = 'block';
downloadBtn.onclick = () => {
window.location.href = `${API_BASE_URL}/download/${currentAnalysisId}`;
};
}
// Update result content based on analysis type
const speciesEl = document.getElementById('result-species');
const taxonomyEl = document.getElementById('result-taxonomy');
const phEl = document.getElementById('result-ph');
const tempEl = document.getElementById('result-temperature');
const respEl = document.getElementById('result-respiratory');
const mediumEl = document.getElementById('result-medium');
const growthEl = document.getElementById('result-growth');
// Set species name from filename if single file
if (fileName && speciesEl) {
const genomeId = extractGenomeId(fileName);
speciesEl.textContent = genomeId;
}
// Display results based on analysis type - try to parse CSV data if available
if (result && Object.keys(result).length > 2) { // More than just analysis_id and result_file
// Handle 'all' analysis type - display all results
if (predictionType === 'all') {
// Display all analysis results from merged CSV
const allFields = Object.keys(result).filter(k =>
k !== 'analysis_id' && k !== 'result_file' && k !== 'analysis_types'
);
// pH results
const phFields = allFields.filter(k =>
k.toLowerCase().includes('ph') && !k.toLowerCase().includes('predict')
);
if (phFields.length > 0 && phEl) {
phEl.textContent = result[phFields[0]] || 'See CSV file for details';
} else if (phEl) {
phEl.textContent = 'See CSV file for details';
}
// Temperature results
const tempFields = allFields.filter(k =>
(k.toLowerCase().includes('temp') || k.toLowerCase().includes('temperature')) &&
!k.toLowerCase().includes('predict')
);
if (tempFields.length > 0 && tempEl) {
tempEl.textContent = result[tempFields[0]] || 'See CSV file for details';
} else if (tempEl) {
tempEl.textContent = 'See CSV file for details';
}
// Oxygen/respiratory results
const respFields = allFields.filter(k =>
(k.toLowerCase().includes('oxygen') || k.toLowerCase().includes('respiratory') ||
k.toLowerCase().includes('o2')) && !k.toLowerCase().includes('predict')
);
if (respFields.length > 0 && respEl) {
respEl.textContent = result[respFields[0]] || 'See CSV file for details';
} else if (respEl) {
respEl.textContent = 'See CSV file for details';
}
// Nutrition/medium results
const mediumFields = allFields.filter(k =>
(k.toLowerCase().includes('medium') || k.toLowerCase().includes('nutrition') ||
k.toLowerCase().includes('culture') || k.toLowerCase().includes('compound'))
);
if (mediumFields.length > 0 && mediumEl) {
const mediumValue = mediumFields.map(f => result[f]).filter(v => v && v !== '').join(', ');
mediumEl.textContent = mediumValue || 'See CSV file for details';
} else if (mediumEl) {
mediumEl.textContent = 'See CSV file for details';
}
// Growth results
const growthFields = allFields.filter(k =>
k.toLowerCase().includes('growth') && !k.toLowerCase().includes('predict')
);
if (growthFields.length > 0 && growthEl) {
growthEl.textContent = result[growthFields[0]] || 'See CSV file for details';
} else if (growthEl) {
growthEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'nutrition') {
// Look for medium-related fields
const mediumFields = Object.keys(result).filter(k =>
k.toLowerCase().includes('medium') ||
k.toLowerCase().includes('nutrition') ||
k.toLowerCase().includes('culture')
);
if (mediumFields.length > 0 && mediumEl) {
const mediumValue = mediumFields.map(f => result[f]).filter(v => v && v !== '').join(', ');
mediumEl.textContent = mediumValue || 'See CSV file for details';
} else if (mediumEl) {
mediumEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'ph') {
// Look for pH-related fields
const phFields = Object.keys(result).filter(k =>
k.toLowerCase().includes('ph') ||
k.toLowerCase().includes('ph_')
);
if (phFields.length > 0 && phEl) {
// Only display the value, not the field name
const phValue = result[phFields[0]] || '';
phEl.textContent = phValue || 'See CSV file for details';
} else if (phEl) {
phEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'temperature') {
// Look for temperature-related fields
const tempFields = Object.keys(result).filter(k =>
k.toLowerCase().includes('temp') ||
k.toLowerCase().includes('temperature')
);
if (tempFields.length > 0 && tempEl) {
// Only display the value, not the field name
const tempValue = result[tempFields[0]] || '';
tempEl.textContent = tempValue || 'See CSV file for details';
} else if (tempEl) {
tempEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'oxygen') {
// Look for oxygen/respiratory-related fields
const respFields = Object.keys(result).filter(k =>
k.toLowerCase().includes('oxygen') ||
k.toLowerCase().includes('respiratory') ||
k.toLowerCase().includes('o2')
);
if (respFields.length > 0 && respEl) {
// Only display the value, not the field name
const respValue = result[respFields[0]] || '';
respEl.textContent = respValue || 'See CSV file for details';
} else if (respEl) {
respEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'growth') {
// Look for growth-related fields
const growthFields = Object.keys(result).filter(k =>
k.toLowerCase().includes('growth') ||
k.toLowerCase().includes('rate')
);
if (growthFields.length > 0 && growthEl) {
// Only display the value, not the field name
const growthValue = result[growthFields[0]] || '';
growthEl.textContent = growthValue || 'See CSV file for details';
} else if (growthEl) {
growthEl.textContent = 'See CSV file for details';
}
}
} else {
// Fallback to CSV file message
if (predictionType === 'nutrition' && result.result_file) {
if (mediumEl) {
mediumEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'ph' && result.result_file) {
if (phEl) {
phEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'temperature' && result.result_file) {
if (tempEl) {
tempEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'oxygen' && result.result_file) {
if (respEl) {
respEl.textContent = 'See CSV file for details';
}
} else if (predictionType === 'growth' && result.result_file) {
if (growthEl) {
growthEl.textContent = 'See CSV file for details';
}
}
}
}
// Display all analysis results (for analysis all mode with single file)
function displayAllResults(fileName) {
const speciesEl = document.getElementById('result-species');
const taxonomyEl = document.getElementById('result-taxonomy');
const phEl = document.getElementById('result-ph');
const tempEl = document.getElementById('result-temperature');
const respEl = document.getElementById('result-respiratory');
const mediumEl = document.getElementById('result-medium');
const growthEl = document.getElementById('result-growth');
// Set species name from filename
if (fileName && speciesEl) {
const genomeId = extractGenomeId(fileName);
speciesEl.textContent = genomeId;
}
// Display results from all analyses
if (allAnalysisResults.ph && phEl) {
const phResult = allAnalysisResults.ph;
const phFields = Object.keys(phResult).filter(k =>
k.toLowerCase().includes('ph') && k !== 'analysis_id' && k !== 'result_file'
);
if (phFields.length > 0) {
phEl.textContent = phResult[phFields[0]] || 'See CSV file for details';
} else {
phEl.textContent = 'See CSV file for details';
}
}
if (allAnalysisResults.temperature && tempEl) {
const tempResult = allAnalysisResults.temperature;
const tempFields = Object.keys(tempResult).filter(k =>
k.toLowerCase().includes('temp') && k !== 'analysis_id' && k !== 'result_file'
);
if (tempFields.length > 0) {
tempEl.textContent = tempResult[tempFields[0]] || 'See CSV file for details';
} else {
tempEl.textContent = 'See CSV file for details';
}
}
if (allAnalysisResults.oxygen && respEl) {
const oxygenResult = allAnalysisResults.oxygen;
const respFields = Object.keys(oxygenResult).filter(k =>
(k.toLowerCase().includes('oxygen') || k.toLowerCase().includes('respiratory') || k.toLowerCase().includes('o2')) &&
k !== 'analysis_id' && k !== 'result_file'
);
if (respFields.length > 0) {
respEl.textContent = oxygenResult[respFields[0]] || 'See CSV file for details';
} else {
respEl.textContent = 'See CSV file for details';
}
}
if (allAnalysisResults.nutrition && mediumEl) {
const nutritionResult = allAnalysisResults.nutrition;
const mediumFields = Object.keys(nutritionResult).filter(k =>
(k.toLowerCase().includes('medium') || k.toLowerCase().includes('nutrition')) &&
k !== 'analysis_id' && k !== 'result_file'
);
if (mediumFields.length > 0) {
const mediumValue = mediumFields.map(f => nutritionResult[f]).filter(v => v && v !== '').join(', ');
mediumEl.textContent = mediumValue || 'See CSV file for details';
} else {
mediumEl.textContent = 'See CSV file for details';
}
}
if (allAnalysisResults.growth && growthEl) {
const growthResult = allAnalysisResults.growth;
const growthFields = Object.keys(growthResult).filter(k =>
k.toLowerCase().includes('growth') && k !== 'analysis_id' && k !== 'result_file'
);
if (growthFields.length > 0) {
growthEl.textContent = growthResult[growthFields[0]] || 'See CSV file for details';
} else {
growthEl.textContent = 'See CSV file for details';
}
}
// Show download button for the first analysis
const downloadBtn = document.getElementById('download-btn');
if (downloadBtn && allAnalysisIds.length > 0) {
downloadBtn.style.display = 'block';
downloadBtn.onclick = () => {
// Download the first analysis result (or could be modified to download all)
window.location.href = `${API_BASE_URL}/download/${allAnalysisIds[0].id}`;
};
}
}
// 补充原代码中可能缺失的函数定义(根据上下文推测)
function handleReset() {
// 重置表单逻辑
const activeTab = document.querySelector('.tab-item.active').dataset.tab;
const fileInput = document.getElementById(`${activeTab}-file-input`);
const fileStatus = document.getElementById(`${activeTab}-file-status`);
const emailInput = document.getElementById(`${activeTab}-email-input`);
const emailError = document.getElementById(`${activeTab}-email-error`);
const emailProgressSection = document.getElementById(`${activeTab}-email-progress`);
const analysisAllCheckbox = document.getElementById(`${activeTab}-analysis-all`);
const submitBtn = document.getElementById(`${activeTab}-submit-btn`);
// 重置文件输入
if (fileInput) {
fileInput.value = '';
}
if (fileStatus) {
fileStatus.textContent = 'No file selected';
fileStatus.style.color = '#666';
}
// 重置邮箱输入
if (emailInput) {
emailInput.value = '';
}
if (emailError) {
emailError.style.display = 'none';
}
// 重置analysis all复选框
if (analysisAllCheckbox) {
analysisAllCheckbox.checked = false;
}
// 重置提交按钮
if (submitBtn) {
submitBtn.disabled = false;
}
// 隐藏进度和邮箱区域
if (emailProgressSection) {
emailProgressSection.style.display = 'none';
}
// 清除进度间隔
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
// 清除全局变量
currentAnalysisId = null;
allAnalysisIds = [];
allAnalysisResults = {};
// 清除结果
clearResultContent();
}
function clearResultContent() {
const resultContent = document.getElementById('result-content');
if (resultContent) {
// 可以保留原始结构但清空内容,或恢复默认内容
resultContent.innerHTML = `
<ul>
<li>
<p class="field-name">species name</p>
<p class="field-value" id="result-species"></p>
</li>
<li>
<p class="field-name">taxonomy</p>
<p class="field-value" id="result-taxonomy"></p>
</li>
<li>
<p class="field-name">pH</p>
<p class="field-value" id="result-ph"></p>
</li>
<li>
<p class="field-name">temperature</p>
<p class="field-value" id="result-temperature"></p>
</li>
<li>
<p class="field-name">respiratory type</p>
<p class="field-value" id="result-respiratory"></p>
</li>
<li>
<p class="field-name">culture medium</p>
<p class="field-value" id="result-medium"></p>
</li>
<li>
<p class="field-name">Max growth rate</p>
<p class="field-value" id="result-growth"></p>
</li>
</ul>
`;
}
// 隐藏下载按钮
const downloadBtn = document.getElementById('download-btn');
if (downloadBtn) {
downloadBtn.style.display = 'none';
}
}
function downloadResult() {
// 下载结果逻辑
alert('Download functionality will be implemented here.');
}
// 初始化标签切换事件
document.querySelectorAll('.tab-item').forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.dataset.tab;
switchTab(tabName);
});
});
// 页面加载时检查 URL 参数并切换到对应的 tab
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const tabParam = urlParams.get('tab');
if (tabParam) {
// 验证 tab 参数是否有效
const validTabs = ['nutrition', 'ph', 'temperature', 'oxygen', 'growth'];
if (validTabs.includes(tabParam)) {
// 延迟执行以确保 DOM 完全加载
setTimeout(() => {
switchTab(tabParam);
// 滚动到 tab 导航区域
const tabNav = document.querySelector('.tab-nav');
if (tabNav) {
tabNav.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 100);
}
}
});
// 初始化停止按钮事件
document.querySelectorAll('.stop-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const predictionType = this.id.replace('-stop-btn', '');
if (!currentAnalysisId) {
alert('No active analysis to stop');
return;
}
// 确认停止操作
if (!confirm('Are you sure you want to stop the analysis? This action cannot be undone.')) {
return;
}
// 禁用stop按钮防止重复点击
const stopBtn = this;
stopBtn.disabled = true;
stopBtn.textContent = 'Stopping...';
try {
const response = await fetch(`${API_BASE_URL}/stop/${currentAnalysisId}`, {
method: 'POST'
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || 'Failed to stop analysis');
}
const result = await response.json();
console.log('Stop response:', result);
// Stop polling
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
// 获取当前活动的tab类型处理'all'类型的情况)
const currentTab = document.querySelector('.tab-panel.active');
const activeTabType = currentTab ? currentTab.id.replace('-panel', '') : predictionType;
// Update UI
const progressTab = document.getElementById(`${activeTabType}-progress-tab`);
const progressText = document.getElementById(`${activeTabType}-progress-text`);
const progressDetails = document.getElementById(`${activeTabType}-progress-details`);
const progressFill = document.getElementById(`${activeTabType}-progress-fill`);
const submitBtn = document.getElementById(`${activeTabType}-submit-btn`);
if (progressText) {
progressText.textContent = 'Analysis stopped by user';
}
if (progressDetails) {
progressDetails.textContent = 'The analysis has been terminated.';
}
if (progressFill) {
progressFill.style.width = '0%';
}
if (submitBtn) {
submitBtn.disabled = false;
}
if (stopBtn) {
stopBtn.style.display = 'none';
}
// 清除结果
clearResultContent();
// 重置全局变量
currentAnalysisId = null;
allAnalysisIds = [];
allAnalysisResults = {};
// 显示成功消息
setTimeout(() => {
if (progressTab) {
progressTab.style.display = 'none';
}
}, 3000);
} catch (error) {
console.error('Stop error:', error);
alert(`Failed to stop analysis: ${error.message}`);
// 恢复按钮状态
stopBtn.disabled = false;
stopBtn.textContent = 'Stop Analysis';
}
});
});
// 初始化邮箱输入验证事件
document.querySelectorAll('.email-input').forEach(input => {
input.addEventListener('input', function() {
const predictionType = this.id.replace('-email-input', '');
validateEmailRequirement(predictionType);
});
});