Spaces:
Running
Running
Update index.html
Browse files- index.html +112 -82
index.html
CHANGED
|
@@ -690,8 +690,8 @@
|
|
| 690 |
<div class="button-group-wrapper">
|
| 691 |
<div id="processing-time-info">Estimated processing time: around 30 seconds.</div>
|
| 692 |
<div class="button-group">
|
| 693 |
-
<button id="clear-btn">Clear Input</button> <!-- NO onclick attribute here
|
| 694 |
-
<button id="predict-btn">Submit</button> <!-- NO onclick attribute here
|
| 695 |
</div>
|
| 696 |
</div>
|
| 697 |
|
|
@@ -860,7 +860,7 @@
|
|
| 860 |
</li>
|
| 861 |
<li><strong>Solvent Accessibility (SolventAccessibilityD3001):</strong>
|
| 862 |
<ul>
|
| 863 |
-
<li><strong>Description:</
|
| 864 |
<li><strong>Interpretation:</b> Solvent-accessible residues positively contribute to AMP prediction. This might indicate that exposed residues facilitate interaction with bacterial membranes or the surrounding environment.</li>
|
| 865 |
</ul>
|
| 866 |
</li>
|
|
@@ -992,23 +992,21 @@
|
|
| 992 |
</address>
|
| 993 |
</footer>
|
| 994 |
|
| 995 |
-
<!-- IMPORTANT: The script tag must be type="module" to use import statements. -->
|
| 996 |
<script type="module">
|
| 997 |
import { Client } from "https://esm.sh/@gradio/client";
|
| 998 |
|
| 999 |
const GRADIO_HF_SPACE_RAW_URL = "https://huggingface.co/spaces/nonzeroexit/AMP-Classifier";
|
| 1000 |
const GRADIO_PREDICTION_TARGET_ID = "nonzeroexit/AMP-Classifier";
|
| 1001 |
-
|
| 1002 |
-
//
|
| 1003 |
-
|
| 1004 |
-
const GRADIO_EMAIL_TARGET_ID = "nonzeroexit/amp-email-sender"; // Verify this Space ID is correct and PUBLIC!
|
| 1005 |
|
| 1006 |
// --- DOM Elements ---
|
| 1007 |
const sequenceInput = document.getElementById('sequence');
|
| 1008 |
const charCount = document.getElementById('char-count');
|
| 1009 |
const errorDiv = document.getElementById('error');
|
| 1010 |
const predictBtn = document.getElementById('predict-btn');
|
| 1011 |
-
const clearBtn = document.getElementById('clear-btn');
|
| 1012 |
const fileInput = document.getElementById('file-input');
|
| 1013 |
const downloadLink = document.getElementById('download-link');
|
| 1014 |
const ampClassificationOutput = document.getElementById('amp-classification-output');
|
|
@@ -1021,11 +1019,14 @@
|
|
| 1021 |
const emailErrorDiv = document.getElementById('email-error');
|
| 1022 |
const emailStatusDiv = document.getElementById('email-status');
|
| 1023 |
|
| 1024 |
-
let clientInstancePrediction; //
|
| 1025 |
-
let clientInstanceEmail; //
|
| 1026 |
let debounceTimer;
|
| 1027 |
let countUpIntervalId;
|
| 1028 |
|
|
|
|
|
|
|
|
|
|
| 1029 |
// --- Utility Functions for Error Display (Defined at top of module scope) ---
|
| 1030 |
function showError(message) {
|
| 1031 |
if(errorDiv) {
|
|
@@ -1058,26 +1059,27 @@
|
|
| 1058 |
clientInstancePrediction = await Client.connect(GRADIO_PREDICTION_TARGET_ID);
|
| 1059 |
console.log("Gradio client for prediction initialized successfully directly to space ID:", GRADIO_PREDICTION_TARGET_ID);
|
| 1060 |
|
| 1061 |
-
//
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
|
|
|
|
|
|
| 1067 |
updatePredictButtonState();
|
| 1068 |
|
| 1069 |
} catch (error) {
|
| 1070 |
-
console.error("Failed to initialize Gradio client for prediction or email:", error);
|
| 1071 |
-
// Use the locally available showError function
|
| 1072 |
showError(`Failed to connect to a Hugging Face Space. This might be due to incorrect Space IDs, private spaces, or network issues. Error details: ${error.message}`);
|
| 1073 |
-
// If
|
| 1074 |
if (predictBtn) predictBtn.disabled = true;
|
| 1075 |
}
|
| 1076 |
}
|
| 1077 |
|
| 1078 |
// --- Initial Setup on Window Load ---
|
| 1079 |
window.onload = function() {
|
| 1080 |
-
initClient();
|
| 1081 |
|
| 1082 |
if (document.getElementById('info-icon')) {
|
| 1083 |
document.getElementById('info-icon').addEventListener('mouseenter', showTooltip);
|
|
@@ -1085,23 +1087,22 @@
|
|
| 1085 |
}
|
| 1086 |
if (fileInput) fileInput.addEventListener('change', handleFileSelect);
|
| 1087 |
|
| 1088 |
-
// Add event listeners for input fields to update button state
|
| 1089 |
if (sequenceInput) {
|
| 1090 |
sequenceInput.addEventListener('input', updateAllInputsAndButtonState);
|
| 1091 |
sequenceInput.addEventListener('paste', handleSequencePaste);
|
| 1092 |
sequenceInput.addEventListener('keypress', handleSequenceKeyPress);
|
| 1093 |
}
|
|
|
|
| 1094 |
if (userEmailInput) {
|
| 1095 |
userEmailInput.addEventListener('input', updateAllInputsAndButtonState);
|
| 1096 |
}
|
| 1097 |
|
| 1098 |
-
//
|
| 1099 |
-
// This is the fix for the ReferenceError when using type="module".
|
| 1100 |
if (clearBtn) clearBtn.addEventListener('click', clearInput);
|
| 1101 |
if (predictBtn) predictBtn.addEventListener('click', classifySequenceDebounced);
|
| 1102 |
|
| 1103 |
|
| 1104 |
-
clearInput();
|
| 1105 |
};
|
| 1106 |
|
| 1107 |
// --- Tooltip Functions ---
|
|
@@ -1120,14 +1121,19 @@
|
|
| 1120 |
if(sequenceInput) sequenceInput.value = '';
|
| 1121 |
if(fileInput) fileInput.value = '';
|
| 1122 |
if(charCount) charCount.textContent = '0';
|
| 1123 |
-
clearGeneralError();
|
| 1124 |
|
| 1125 |
// Clear email specific fields
|
| 1126 |
if(userEmailInput) userEmailInput.value = '';
|
| 1127 |
if(emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1128 |
if(emailStatusDiv) {
|
| 1129 |
-
|
| 1130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1131 |
}
|
| 1132 |
|
| 1133 |
if(downloadLink) {
|
|
@@ -1155,7 +1161,7 @@
|
|
| 1155 |
countUpIntervalId = null;
|
| 1156 |
}
|
| 1157 |
|
| 1158 |
-
updatePredictButtonState();
|
| 1159 |
if(predictBtn) predictBtn.textContent = 'Submit';
|
| 1160 |
}
|
| 1161 |
|
|
@@ -1182,7 +1188,9 @@
|
|
| 1182 |
}
|
| 1183 |
|
| 1184 |
clearGeneralError();
|
| 1185 |
-
|
|
|
|
|
|
|
| 1186 |
const reader = new FileReader();
|
| 1187 |
reader.onload = function(e) {
|
| 1188 |
try {
|
|
@@ -1193,7 +1201,7 @@
|
|
| 1193 |
sequenceInput.dataset.source = 'file';
|
| 1194 |
}
|
| 1195 |
updateCharacterCount();
|
| 1196 |
-
updateAllInputsAndButtonState();
|
| 1197 |
} else {
|
| 1198 |
showError("No valid sequence found in FASTA file.");
|
| 1199 |
if(fileInput) fileInput.value = '';
|
|
@@ -1262,12 +1270,13 @@
|
|
| 1262 |
}
|
| 1263 |
|
| 1264 |
function validateEmail(email) {
|
|
|
|
| 1265 |
if (!email) {
|
| 1266 |
-
return { isValid: false, message: "Email address is
|
| 1267 |
}
|
| 1268 |
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
| 1269 |
if (!emailRegex.test(email)) {
|
| 1270 |
-
return { isValid: false, message: "Please enter a valid email
|
| 1271 |
}
|
| 1272 |
return { isValid: true, message: "" };
|
| 1273 |
}
|
|
@@ -1287,11 +1296,12 @@
|
|
| 1287 |
const emailValidation = validateEmail(userEmailInput ? userEmailInput.value : '');
|
| 1288 |
|
| 1289 |
if (!sequenceValidation.isValid) {
|
| 1290 |
-
showError(sequenceValidation.message);
|
| 1291 |
} else {
|
| 1292 |
clearGeneralError();
|
| 1293 |
}
|
| 1294 |
|
|
|
|
| 1295 |
if (!emailValidation.isValid && userEmailInput && userEmailInput.value.length > 0) {
|
| 1296 |
if (emailErrorDiv) {
|
| 1297 |
emailErrorDiv.textContent = emailValidation.message;
|
|
@@ -1300,29 +1310,33 @@
|
|
| 1300 |
} else {
|
| 1301 |
if (emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1302 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1303 |
|
| 1304 |
if(micCheckboxes) micCheckboxes.forEach(checkbox => {
|
| 1305 |
checkbox.disabled = !sequenceValidation.isValid;
|
| 1306 |
if (!sequenceValidation.isValid) checkbox.checked = false;
|
| 1307 |
});
|
| 1308 |
|
| 1309 |
-
updatePredictButtonState(sequenceValidation.isValid
|
| 1310 |
}
|
| 1311 |
|
| 1312 |
-
function updatePredictButtonState(sequenceIsValid = false
|
| 1313 |
if (predictBtn) {
|
| 1314 |
-
|
| 1315 |
-
|
| 1316 |
-
|
| 1317 |
-
const seqState = validateSequence(currentSequenceValue).isValid;
|
| 1318 |
-
const emailState = validateEmail(currentEmailValue).isValid;
|
| 1319 |
-
|
| 1320 |
-
// Button is disabled if either seq or email is invalid, OR if Gradio clients aren't ready
|
| 1321 |
-
predictBtn.disabled = !(seqState && emailState) || !clientInstancePrediction || !clientInstanceEmail;
|
| 1322 |
}
|
| 1323 |
}
|
| 1324 |
|
| 1325 |
-
|
| 1326 |
// --- Sequence Input Event Handlers ---
|
| 1327 |
function handleSequencePaste(e) {
|
| 1328 |
e.preventDefault();
|
|
@@ -1362,25 +1376,18 @@
|
|
| 1362 |
}
|
| 1363 |
|
| 1364 |
async function classifySequence() {
|
| 1365 |
-
// IMPORTANT: Ensure BOTH prediction and email clients are ready before proceeding
|
| 1366 |
if (!clientInstancePrediction) {
|
| 1367 |
showError("Classifier service is not ready. Please wait a moment and try again.");
|
| 1368 |
console.error("Gradio client instance for prediction is not initialized when classifySequence called.");
|
| 1369 |
-
updatePredictButtonState();
|
| 1370 |
-
return;
|
| 1371 |
-
}
|
| 1372 |
-
if (!clientInstanceEmail) {
|
| 1373 |
-
showError("Email service is not ready. Please wait a moment and try again.");
|
| 1374 |
-
console.error("Gradio client instance for email is not initialized when classifySequence called.");
|
| 1375 |
-
updatePredictButtonState(); // Re-check button state if an issue is found
|
| 1376 |
return;
|
| 1377 |
}
|
| 1378 |
|
| 1379 |
const currentSequence = sequenceInput ? sequenceInput.value.trim().toUpperCase() : "";
|
| 1380 |
-
const userEmail = userEmailInput ? userEmailInput.value.trim() : "";
|
| 1381 |
|
| 1382 |
const sequenceValidation = validateSequence(currentSequence);
|
| 1383 |
-
const emailValidation = validateEmail(userEmail);
|
| 1384 |
|
| 1385 |
if (!sequenceValidation.isValid) {
|
| 1386 |
showError(sequenceValidation.message);
|
|
@@ -1389,21 +1396,28 @@
|
|
| 1389 |
} else {
|
| 1390 |
clearGeneralError();
|
| 1391 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1392 |
|
| 1393 |
-
if (!emailValidation.isValid) {
|
| 1394 |
-
if (emailErrorDiv) {
|
| 1395 |
-
emailErrorDiv.textContent = emailValidation.message;
|
| 1396 |
-
emailErrorDiv.style.display = 'block';
|
| 1397 |
-
}
|
| 1398 |
-
updatePredictButtonState();
|
| 1399 |
-
return;
|
| 1400 |
-
} else {
|
| 1401 |
-
if (emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1402 |
-
}
|
| 1403 |
|
| 1404 |
if(emailStatusDiv) {
|
| 1405 |
-
|
| 1406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1407 |
}
|
| 1408 |
|
| 1409 |
if (predictBtn) predictBtn.disabled = true;
|
|
@@ -1497,7 +1511,7 @@
|
|
| 1497 |
} catch (pdfGenerationError) {
|
| 1498 |
console.error("Error during PDF report content generation:", pdfGenerationError);
|
| 1499 |
showError(`PDF report generation failed: ${pdfGenerationError.message}`);
|
| 1500 |
-
emailStatusDiv.textContent = 'PDF generation failed,
|
| 1501 |
emailStatusDiv.style.color = 'var(--clear-button-bg)';
|
| 1502 |
pdfDoc = null; // Ensure pdfDoc is null on error
|
| 1503 |
}
|
|
@@ -1520,9 +1534,16 @@
|
|
| 1520 |
additionalDetailsOutput.appendChild(downloadLink);
|
| 1521 |
}
|
| 1522 |
|
| 1523 |
-
// ---
|
| 1524 |
-
|
| 1525 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1526 |
|
| 1527 |
} else {
|
| 1528 |
additionalDetailsOutput.innerHTML = '<p class="info-message" style="color: var(--warning-color);">Could not generate PDF report for download.</p>';
|
|
@@ -1541,7 +1562,7 @@
|
|
| 1541 |
}
|
| 1542 |
|
| 1543 |
} catch (error) {
|
| 1544 |
-
console.error("Error during prediction or processing
|
| 1545 |
showError(`Prediction failed: ${error.message || "Unknown error."} Please check the Hugging Face Gradio Space for any issues or temporary downtime.`);
|
| 1546 |
if(ampClassificationOutput) ampClassificationOutput.innerHTML = '<p style="text-align: center; color: var(--clear-button-bg);">Error in Prediction</p>';
|
| 1547 |
if(additionalDetailsOutput) {
|
|
@@ -1551,19 +1572,31 @@
|
|
| 1551 |
downloadLink.style.display = 'none';
|
| 1552 |
}
|
| 1553 |
}
|
| 1554 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1555 |
emailStatusDiv.style.color = 'var(--clear-button-bg)';
|
| 1556 |
} finally {
|
| 1557 |
if (countUpIntervalId) { clearInterval(countUpIntervalId); countUpIntervalId = null; }
|
| 1558 |
if (predictBtn) { predictBtn.textContent = 'Submit';}
|
| 1559 |
-
updatePredictButtonState();
|
| 1560 |
}
|
| 1561 |
}
|
| 1562 |
|
| 1563 |
-
// --- Send Email Function (
|
| 1564 |
async function sendEmailWithReport(email, pdfBlob, fileName, sequence, prediction, confidence) {
|
| 1565 |
if (!emailStatusDiv) return;
|
| 1566 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1567 |
emailStatusDiv.textContent = 'Sending email...';
|
| 1568 |
emailStatusDiv.style.color = 'var(--info-color)'; // Blue for processing
|
| 1569 |
|
|
@@ -1577,14 +1610,11 @@
|
|
| 1577 |
if (!clientInstanceEmail) {
|
| 1578 |
emailStatusDiv.textContent = 'Email service is not ready. Please try again.';
|
| 1579 |
emailStatusDiv.style.color = 'var(--clear-button-bg)';
|
| 1580 |
-
console.error("Gradio email client instance is not initialized.");
|
| 1581 |
return;
|
| 1582 |
}
|
| 1583 |
|
| 1584 |
try {
|
| 1585 |
-
// IMPORTANT: "/send_email_report" must match the name of the Python function
|
| 1586 |
-
// in your Gradio `app.py` that you wrapped with `gr.Interface`.
|
| 1587 |
-
// e.g., gr.Interface(fn=send_email_report, ...) -> "/send_email_report"
|
| 1588 |
const emailResult = await clientInstanceEmail.predict(
|
| 1589 |
"/send_email_report",
|
| 1590 |
[
|
|
@@ -1661,7 +1691,7 @@
|
|
| 1661 |
async function generatePDFReportContent(sequenceForPdf, predictionForPdf, confidenceForPdf, micResultsForPdf, limeFeaturesForPdf) {
|
| 1662 |
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined' || typeof window.jspdf.jsPDF !== 'function') {
|
| 1663 |
console.error("jsPDF library (window.jspdf.jsPDF) is not loaded or not correctly exposed as a function.");
|
| 1664 |
-
showError("PDF generation library not loaded. Cannot create report.");
|
| 1665 |
return null;
|
| 1666 |
}
|
| 1667 |
const jsPDF = window.jspdf.jsPDF;
|
|
@@ -1671,12 +1701,12 @@
|
|
| 1671 |
pdf = new jsPDF();
|
| 1672 |
if (!pdf || !pdf.internal || !pdf.internal.pageSize || pdf.internal.pageSize.width === undefined || pdf.internal.pageSize.width === null) {
|
| 1673 |
console.error("Critical error: jsPDF instance or its internal properties are invalid.");
|
| 1674 |
-
showError("PDF generation failed: Invalid PDF object.");
|
| 1675 |
return null;
|
| 1676 |
}
|
| 1677 |
} catch (e) {
|
| 1678 |
console.error("Failed to instantiate jsPDF object:", e);
|
| 1679 |
-
showError(`PDF generation failed: Could not initialize PDF library. Error: ${e.message}`);
|
| 1680 |
return null;
|
| 1681 |
}
|
| 1682 |
|
|
|
|
| 690 |
<div class="button-group-wrapper">
|
| 691 |
<div id="processing-time-info">Estimated processing time: around 30 seconds.</div>
|
| 692 |
<div class="button-group">
|
| 693 |
+
<button id="clear-btn">Clear Input</button> <!-- NO onclick attribute here -->
|
| 694 |
+
<button id="predict-btn">Submit</button> <!-- NO onclick attribute here -->
|
| 695 |
</div>
|
| 696 |
</div>
|
| 697 |
|
|
|
|
| 860 |
</li>
|
| 861 |
<li><strong>Solvent Accessibility (SolventAccessibilityD3001):</strong>
|
| 862 |
<ul>
|
| 863 |
+
<li><strong>Description:</b> Reflects how exposed the residues are to the solvent (water).</li>
|
| 864 |
<li><strong>Interpretation:</b> Solvent-accessible residues positively contribute to AMP prediction. This might indicate that exposed residues facilitate interaction with bacterial membranes or the surrounding environment.</li>
|
| 865 |
</ul>
|
| 866 |
</li>
|
|
|
|
| 992 |
</address>
|
| 993 |
</footer>
|
| 994 |
|
|
|
|
| 995 |
<script type="module">
|
| 996 |
import { Client } from "https://esm.sh/@gradio/client";
|
| 997 |
|
| 998 |
const GRADIO_HF_SPACE_RAW_URL = "https://huggingface.co/spaces/nonzeroexit/AMP-Classifier";
|
| 999 |
const GRADIO_PREDICTION_TARGET_ID = "nonzeroexit/AMP-Classifier";
|
| 1000 |
+
// This Space ID is technically still used in initClient but won't be connected
|
| 1001 |
+
// for email sending functionality due to being disabled.
|
| 1002 |
+
const GRADIO_EMAIL_TARGET_ID = "nonzeroexit/amp-email-sender"; // Email backend reference (disabled)
|
|
|
|
| 1003 |
|
| 1004 |
// --- DOM Elements ---
|
| 1005 |
const sequenceInput = document.getElementById('sequence');
|
| 1006 |
const charCount = document.getElementById('char-count');
|
| 1007 |
const errorDiv = document.getElementById('error');
|
| 1008 |
const predictBtn = document.getElementById('predict-btn');
|
| 1009 |
+
const clearBtn = document.getElementById('clear-btn');
|
| 1010 |
const fileInput = document.getElementById('file-input');
|
| 1011 |
const downloadLink = document.getElementById('download-link');
|
| 1012 |
const ampClassificationOutput = document.getElementById('amp-classification-output');
|
|
|
|
| 1019 |
const emailErrorDiv = document.getElementById('email-error');
|
| 1020 |
const emailStatusDiv = document.getElementById('email-status');
|
| 1021 |
|
| 1022 |
+
let clientInstancePrediction; // Only prediction client will be connected
|
| 1023 |
+
let clientInstanceEmail = null; // Email client will NOT be connected (explicitly null)
|
| 1024 |
let debounceTimer;
|
| 1025 |
let countUpIntervalId;
|
| 1026 |
|
| 1027 |
+
// Flag to control email functionality globally
|
| 1028 |
+
const IS_EMAIL_SENDING_ENABLED = false; // <<< SET TO false TO DISABLE EMAIL SENDING
|
| 1029 |
+
|
| 1030 |
// --- Utility Functions for Error Display (Defined at top of module scope) ---
|
| 1031 |
function showError(message) {
|
| 1032 |
if(errorDiv) {
|
|
|
|
| 1059 |
clientInstancePrediction = await Client.connect(GRADIO_PREDICTION_TARGET_ID);
|
| 1060 |
console.log("Gradio client for prediction initialized successfully directly to space ID:", GRADIO_PREDICTION_TARGET_ID);
|
| 1061 |
|
| 1062 |
+
// ONLY CONNECT EMAIL CLIENT IF FEATURE IS ENABLED
|
| 1063 |
+
if (IS_EMAIL_SENDING_ENABLED) {
|
| 1064 |
+
clientInstanceEmail = await Client.connect(GRADIO_EMAIL_TARGET_ID);
|
| 1065 |
+
console.log("Gradio client for email sender initialized successfully directly to space ID:", GRADIO_EMAIL_TARGET_ID);
|
| 1066 |
+
} else {
|
| 1067 |
+
console.log("Email sending functionality is disabled, skipping email client connection.");
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
updatePredictButtonState();
|
| 1071 |
|
| 1072 |
} catch (error) {
|
| 1073 |
+
console.error("Failed to initialize Gradio client for prediction (or email if enabled):", error);
|
|
|
|
| 1074 |
showError(`Failed to connect to a Hugging Face Space. This might be due to incorrect Space IDs, private spaces, or network issues. Error details: ${error.message}`);
|
| 1075 |
+
// If prediction client fails, or if email is enabled and its client fails, disable the button.
|
| 1076 |
if (predictBtn) predictBtn.disabled = true;
|
| 1077 |
}
|
| 1078 |
}
|
| 1079 |
|
| 1080 |
// --- Initial Setup on Window Load ---
|
| 1081 |
window.onload = function() {
|
| 1082 |
+
initClient();
|
| 1083 |
|
| 1084 |
if (document.getElementById('info-icon')) {
|
| 1085 |
document.getElementById('info-icon').addEventListener('mouseenter', showTooltip);
|
|
|
|
| 1087 |
}
|
| 1088 |
if (fileInput) fileInput.addEventListener('change', handleFileSelect);
|
| 1089 |
|
|
|
|
| 1090 |
if (sequenceInput) {
|
| 1091 |
sequenceInput.addEventListener('input', updateAllInputsAndButtonState);
|
| 1092 |
sequenceInput.addEventListener('paste', handleSequencePaste);
|
| 1093 |
sequenceInput.addEventListener('keypress', handleSequenceKeyPress);
|
| 1094 |
}
|
| 1095 |
+
// Email input listeners still active for user input and local visual validation
|
| 1096 |
if (userEmailInput) {
|
| 1097 |
userEmailInput.addEventListener('input', updateAllInputsAndButtonState);
|
| 1098 |
}
|
| 1099 |
|
| 1100 |
+
// Attach event listeners to buttons
|
|
|
|
| 1101 |
if (clearBtn) clearBtn.addEventListener('click', clearInput);
|
| 1102 |
if (predictBtn) predictBtn.addEventListener('click', classifySequenceDebounced);
|
| 1103 |
|
| 1104 |
|
| 1105 |
+
clearInput();
|
| 1106 |
};
|
| 1107 |
|
| 1108 |
// --- Tooltip Functions ---
|
|
|
|
| 1121 |
if(sequenceInput) sequenceInput.value = '';
|
| 1122 |
if(fileInput) fileInput.value = '';
|
| 1123 |
if(charCount) charCount.textContent = '0';
|
| 1124 |
+
clearGeneralError();
|
| 1125 |
|
| 1126 |
// Clear email specific fields
|
| 1127 |
if(userEmailInput) userEmailInput.value = '';
|
| 1128 |
if(emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1129 |
if(emailStatusDiv) {
|
| 1130 |
+
if (IS_EMAIL_SENDING_ENABLED) {
|
| 1131 |
+
emailStatusDiv.textContent = '';
|
| 1132 |
+
emailStatusDiv.style.color = 'var(--text-color)';
|
| 1133 |
+
} else {
|
| 1134 |
+
emailStatusDiv.textContent = 'Email sending is currently disabled by the tool.';
|
| 1135 |
+
emailStatusDiv.style.color = 'var(--info-color)'; // Neutral info color
|
| 1136 |
+
}
|
| 1137 |
}
|
| 1138 |
|
| 1139 |
if(downloadLink) {
|
|
|
|
| 1161 |
countUpIntervalId = null;
|
| 1162 |
}
|
| 1163 |
|
| 1164 |
+
updatePredictButtonState();
|
| 1165 |
if(predictBtn) predictBtn.textContent = 'Submit';
|
| 1166 |
}
|
| 1167 |
|
|
|
|
| 1188 |
}
|
| 1189 |
|
| 1190 |
clearGeneralError();
|
| 1191 |
+
// Clear email validation error, though it won't block submission
|
| 1192 |
+
if(emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1193 |
+
|
| 1194 |
const reader = new FileReader();
|
| 1195 |
reader.onload = function(e) {
|
| 1196 |
try {
|
|
|
|
| 1201 |
sequenceInput.dataset.source = 'file';
|
| 1202 |
}
|
| 1203 |
updateCharacterCount();
|
| 1204 |
+
updateAllInputsAndButtonState();
|
| 1205 |
} else {
|
| 1206 |
showError("No valid sequence found in FASTA file.");
|
| 1207 |
if(fileInput) fileInput.value = '';
|
|
|
|
| 1270 |
}
|
| 1271 |
|
| 1272 |
function validateEmail(email) {
|
| 1273 |
+
// Email validation is now only for local display, not blocking submission
|
| 1274 |
if (!email) {
|
| 1275 |
+
return { isValid: false, message: "Email address is empty." };
|
| 1276 |
}
|
| 1277 |
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
| 1278 |
if (!emailRegex.test(email)) {
|
| 1279 |
+
return { isValid: false, message: "Please enter a valid email format." };
|
| 1280 |
}
|
| 1281 |
return { isValid: true, message: "" };
|
| 1282 |
}
|
|
|
|
| 1296 |
const emailValidation = validateEmail(userEmailInput ? userEmailInput.value : '');
|
| 1297 |
|
| 1298 |
if (!sequenceValidation.isValid) {
|
| 1299 |
+
showError(sequenceValidation.message);
|
| 1300 |
} else {
|
| 1301 |
clearGeneralError();
|
| 1302 |
}
|
| 1303 |
|
| 1304 |
+
// Display email format errors locally, but don't clear prediction error message
|
| 1305 |
if (!emailValidation.isValid && userEmailInput && userEmailInput.value.length > 0) {
|
| 1306 |
if (emailErrorDiv) {
|
| 1307 |
emailErrorDiv.textContent = emailValidation.message;
|
|
|
|
| 1310 |
} else {
|
| 1311 |
if (emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1312 |
}
|
| 1313 |
+
// If email sending is disabled, provide a default status message.
|
| 1314 |
+
if (emailStatusDiv) {
|
| 1315 |
+
if (!IS_EMAIL_SENDING_ENABLED) {
|
| 1316 |
+
emailStatusDiv.textContent = 'Email sending is currently disabled by the tool.';
|
| 1317 |
+
emailStatusDiv.style.color = 'var(--info-color)';
|
| 1318 |
+
} else if (userEmailInput && userEmailInput.value.trim() === '') {
|
| 1319 |
+
// Clear previous status if user input cleared.
|
| 1320 |
+
emailStatusDiv.textContent = '';
|
| 1321 |
+
emailStatusDiv.style.color = 'var(--text-color)';
|
| 1322 |
+
}
|
| 1323 |
+
}
|
| 1324 |
|
| 1325 |
if(micCheckboxes) micCheckboxes.forEach(checkbox => {
|
| 1326 |
checkbox.disabled = !sequenceValidation.isValid;
|
| 1327 |
if (!sequenceValidation.isValid) checkbox.checked = false;
|
| 1328 |
});
|
| 1329 |
|
| 1330 |
+
updatePredictButtonState(sequenceValidation.isValid); // Pass only sequence validity
|
| 1331 |
}
|
| 1332 |
|
| 1333 |
+
function updatePredictButtonState(sequenceIsValid = false) {
|
| 1334 |
if (predictBtn) {
|
| 1335 |
+
// Button enabled solely on valid sequence input AND successful prediction client connection
|
| 1336 |
+
predictBtn.disabled = !sequenceIsValid || !clientInstancePrediction;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1337 |
}
|
| 1338 |
}
|
| 1339 |
|
|
|
|
| 1340 |
// --- Sequence Input Event Handlers ---
|
| 1341 |
function handleSequencePaste(e) {
|
| 1342 |
e.preventDefault();
|
|
|
|
| 1376 |
}
|
| 1377 |
|
| 1378 |
async function classifySequence() {
|
|
|
|
| 1379 |
if (!clientInstancePrediction) {
|
| 1380 |
showError("Classifier service is not ready. Please wait a moment and try again.");
|
| 1381 |
console.error("Gradio client instance for prediction is not initialized when classifySequence called.");
|
| 1382 |
+
updatePredictButtonState();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1383 |
return;
|
| 1384 |
}
|
| 1385 |
|
| 1386 |
const currentSequence = sequenceInput ? sequenceInput.value.trim().toUpperCase() : "";
|
| 1387 |
+
const userEmail = userEmailInput ? userEmailInput.value.trim() : ""; // Still get email, but won't be used for sending
|
| 1388 |
|
| 1389 |
const sequenceValidation = validateSequence(currentSequence);
|
| 1390 |
+
// const emailValidation = validateEmail(userEmail); // Email validation is now only for local display/info
|
| 1391 |
|
| 1392 |
if (!sequenceValidation.isValid) {
|
| 1393 |
showError(sequenceValidation.message);
|
|
|
|
| 1396 |
} else {
|
| 1397 |
clearGeneralError();
|
| 1398 |
}
|
| 1399 |
+
|
| 1400 |
+
// Remove email validation check that blocked submission
|
| 1401 |
+
// if (!emailValidation.isValid && IS_EMAIL_SENDING_ENABLED) {
|
| 1402 |
+
// if (emailErrorDiv) {
|
| 1403 |
+
// emailErrorDiv.textContent = emailValidation.message;
|
| 1404 |
+
// emailErrorDiv.style.display = 'block';
|
| 1405 |
+
// }
|
| 1406 |
+
// updatePredictButtonState();
|
| 1407 |
+
// return;
|
| 1408 |
+
// } else {
|
| 1409 |
+
// if (emailErrorDiv) emailErrorDiv.style.display = 'none';
|
| 1410 |
+
// }
|
| 1411 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
|
| 1413 |
if(emailStatusDiv) {
|
| 1414 |
+
if (IS_EMAIL_SENDING_ENABLED) {
|
| 1415 |
+
emailStatusDiv.textContent = '';
|
| 1416 |
+
emailStatusDiv.style.color = 'var(--text-color)';
|
| 1417 |
+
} else {
|
| 1418 |
+
emailStatusDiv.textContent = 'Email sending is currently disabled by the tool.';
|
| 1419 |
+
emailStatusDiv.style.color = 'var(--info-color)';
|
| 1420 |
+
}
|
| 1421 |
}
|
| 1422 |
|
| 1423 |
if (predictBtn) predictBtn.disabled = true;
|
|
|
|
| 1511 |
} catch (pdfGenerationError) {
|
| 1512 |
console.error("Error during PDF report content generation:", pdfGenerationError);
|
| 1513 |
showError(`PDF report generation failed: ${pdfGenerationError.message}`);
|
| 1514 |
+
emailStatusDiv.textContent = 'PDF generation failed, email not sent (functionality disabled).';
|
| 1515 |
emailStatusDiv.style.color = 'var(--clear-button-bg)';
|
| 1516 |
pdfDoc = null; // Ensure pdfDoc is null on error
|
| 1517 |
}
|
|
|
|
| 1534 |
additionalDetailsOutput.appendChild(downloadLink);
|
| 1535 |
}
|
| 1536 |
|
| 1537 |
+
// --- Email logic based on IS_EMAIL_SENDING_ENABLED flag ---
|
| 1538 |
+
if (IS_EMAIL_SENDING_ENABLED) {
|
| 1539 |
+
await sendEmailWithReport(userEmail, pdfBlob, pdfFileName, currentSequence, ampLabelFromPrediction, ampConfidenceFromPrediction);
|
| 1540 |
+
} else {
|
| 1541 |
+
console.log("Email sending is disabled. Not attempting to send.");
|
| 1542 |
+
if(emailStatusDiv) {
|
| 1543 |
+
emailStatusDiv.textContent = 'Email report generation and sending are currently disabled by the tool.';
|
| 1544 |
+
emailStatusDiv.style.color = 'var(--info-color)';
|
| 1545 |
+
}
|
| 1546 |
+
}
|
| 1547 |
|
| 1548 |
} else {
|
| 1549 |
additionalDetailsOutput.innerHTML = '<p class="info-message" style="color: var(--warning-color);">Could not generate PDF report for download.</p>';
|
|
|
|
| 1562 |
}
|
| 1563 |
|
| 1564 |
} catch (error) {
|
| 1565 |
+
console.error("Error during prediction or processing:", error);
|
| 1566 |
showError(`Prediction failed: ${error.message || "Unknown error."} Please check the Hugging Face Gradio Space for any issues or temporary downtime.`);
|
| 1567 |
if(ampClassificationOutput) ampClassificationOutput.innerHTML = '<p style="text-align: center; color: var(--clear-button-bg);">Error in Prediction</p>';
|
| 1568 |
if(additionalDetailsOutput) {
|
|
|
|
| 1572 |
downloadLink.style.display = 'none';
|
| 1573 |
}
|
| 1574 |
}
|
| 1575 |
+
if (IS_EMAIL_SENDING_ENABLED) {
|
| 1576 |
+
emailStatusDiv.textContent = `Email not sent due to prediction error: ${error.message}`;
|
| 1577 |
+
} else {
|
| 1578 |
+
emailStatusDiv.textContent = `Email sending is disabled. Prediction failed: ${error.message}`;
|
| 1579 |
+
}
|
| 1580 |
emailStatusDiv.style.color = 'var(--clear-button-bg)';
|
| 1581 |
} finally {
|
| 1582 |
if (countUpIntervalId) { clearInterval(countUpIntervalId); countUpIntervalId = null; }
|
| 1583 |
if (predictBtn) { predictBtn.textContent = 'Submit';}
|
| 1584 |
+
updatePredictButtonState(); // Ensure button is re-enabled/disabled based on final prediction input state
|
| 1585 |
}
|
| 1586 |
}
|
| 1587 |
|
| 1588 |
+
// --- Dummy Send Email Function (when disabled) / Original logic (when enabled) ---
|
| 1589 |
async function sendEmailWithReport(email, pdfBlob, fileName, sequence, prediction, confidence) {
|
| 1590 |
if (!emailStatusDiv) return;
|
| 1591 |
|
| 1592 |
+
if (!IS_EMAIL_SENDING_ENABLED) {
|
| 1593 |
+
console.log("Email sending is currently disabled. Not proceeding with backend call.");
|
| 1594 |
+
emailStatusDiv.textContent = 'Email report was NOT sent: Functionality is disabled.';
|
| 1595 |
+
emailStatusDiv.style.color = 'var(--info-color)';
|
| 1596 |
+
return;
|
| 1597 |
+
}
|
| 1598 |
+
|
| 1599 |
+
// --- Original email sending logic (ONLY runs if IS_EMAIL_SENDING_ENABLED is true) ---
|
| 1600 |
emailStatusDiv.textContent = 'Sending email...';
|
| 1601 |
emailStatusDiv.style.color = 'var(--info-color)'; // Blue for processing
|
| 1602 |
|
|
|
|
| 1610 |
if (!clientInstanceEmail) {
|
| 1611 |
emailStatusDiv.textContent = 'Email service is not ready. Please try again.';
|
| 1612 |
emailStatusDiv.style.color = 'var(--clear-button-bg)';
|
| 1613 |
+
console.error("Gradio email client instance is not initialized, even though IS_EMAIL_SENDING_ENABLED is true. Check initClient for errors.");
|
| 1614 |
return;
|
| 1615 |
}
|
| 1616 |
|
| 1617 |
try {
|
|
|
|
|
|
|
|
|
|
| 1618 |
const emailResult = await clientInstanceEmail.predict(
|
| 1619 |
"/send_email_report",
|
| 1620 |
[
|
|
|
|
| 1691 |
async function generatePDFReportContent(sequenceForPdf, predictionForPdf, confidenceForPdf, micResultsForPdf, limeFeaturesForPdf) {
|
| 1692 |
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined' || typeof window.jspdf.jsPDF !== 'function') {
|
| 1693 |
console.error("jsPDF library (window.jspdf.jsPDF) is not loaded or not correctly exposed as a function.");
|
| 1694 |
+
showError("PDF generation library not loaded. Cannot create report.");
|
| 1695 |
return null;
|
| 1696 |
}
|
| 1697 |
const jsPDF = window.jspdf.jsPDF;
|
|
|
|
| 1701 |
pdf = new jsPDF();
|
| 1702 |
if (!pdf || !pdf.internal || !pdf.internal.pageSize || pdf.internal.pageSize.width === undefined || pdf.internal.pageSize.width === null) {
|
| 1703 |
console.error("Critical error: jsPDF instance or its internal properties are invalid.");
|
| 1704 |
+
showError("PDF generation failed: Invalid PDF object.");
|
| 1705 |
return null;
|
| 1706 |
}
|
| 1707 |
} catch (e) {
|
| 1708 |
console.error("Failed to instantiate jsPDF object:", e);
|
| 1709 |
+
showError(`PDF generation failed: Could not initialize PDF library. Error: ${e.message}`);
|
| 1710 |
return null;
|
| 1711 |
}
|
| 1712 |
|