Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Procrustes Analysis Web Server</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --sidebar-width: 280px; | |
| --primary-color: #2563eb; | |
| --sidebar-bg: #f8fafc; | |
| --header-bg: #f1f5f9; | |
| --border-color: #e2e8f0; | |
| --text-primary: #1e293b; | |
| --text-secondary: #64748b; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| } | |
| .wrapper { | |
| display: flex; | |
| width: 100%; | |
| align-items: stretch; | |
| } | |
| #sidebar { | |
| min-width: var(--sidebar-width); | |
| max-width: var(--sidebar-width); | |
| min-height: 100vh; | |
| transition: all 0.3s ease-out; | |
| background-color: var(--sidebar-bg); | |
| border-right: 1px solid var(--border-color); | |
| } | |
| #sidebar.active { | |
| margin-left: calc(-1 * var(--sidebar-width)); | |
| } | |
| #content { | |
| width: 100%; | |
| padding: 2rem; | |
| min-height: 100vh; | |
| margin-left: 0; | |
| transition: all 0.3s ease-out; | |
| background-color: white; | |
| padding-bottom: 4rem; | |
| } | |
| .sidebar-header { | |
| padding: 1.5rem; | |
| background: var(--header-bg); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| #sidebarCollapse, #showSidebarBtn { | |
| background: transparent; | |
| border: none; | |
| padding: 0.5rem; | |
| color: var(--text-primary); | |
| border-radius: 0.375rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| #sidebarCollapse:hover, #showSidebarBtn:hover { | |
| background: rgba(0, 0, 0, 0.05); | |
| transform: translateX(2px); | |
| } | |
| #sidebarCollapse i { | |
| transition: transform 0.3s ease; | |
| } | |
| #sidebar.active #sidebarCollapse i { | |
| transform: rotate(180deg); | |
| } | |
| #showSidebarBtn { | |
| position: fixed; | |
| left: 1.5rem; | |
| top: 1.5rem; | |
| z-index: 1000; | |
| display: none; | |
| background-color: white; | |
| border: 1px solid var(--border-color); | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.1); | |
| } | |
| #showSidebarBtn.show { | |
| display: flex; | |
| } | |
| .sidebar-title { | |
| margin: 0; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| font-size: 1.125rem; | |
| } | |
| .components { | |
| padding: 1.5rem 0; | |
| } | |
| .components a { | |
| padding: 0.75rem 1.5rem; | |
| display: flex; | |
| align-items: center; | |
| color: var(--text-secondary); | |
| text-decoration: none; | |
| transition: all 0.2s; | |
| font-weight: 500; | |
| } | |
| .components a i { | |
| margin-right: 0.75rem; | |
| font-size: 1.25rem; | |
| } | |
| .components a:hover { | |
| background: var(--header-bg); | |
| color: var(--text-primary); | |
| } | |
| .components a.active { | |
| background: var(--header-bg); | |
| color: var(--primary-color); | |
| border-right: 3px solid var(--primary-color); | |
| } | |
| .card { | |
| border: 1px solid var(--border-color); | |
| border-radius: 0.5rem; | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.1); | |
| } | |
| .card-body { | |
| padding: 1.5rem; | |
| } | |
| .form-label { | |
| font-weight: 500; | |
| color: var(--text-primary); | |
| margin-bottom: 0.5rem; | |
| } | |
| .form-control, .form-select { | |
| border-color: var(--border-color); | |
| border-radius: 0.375rem; | |
| padding: 0.625rem; | |
| } | |
| .form-control:focus, .form-select:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| border: none; | |
| padding: 0.625rem 1.25rem; | |
| font-weight: 500; | |
| } | |
| .btn-primary:hover { | |
| background-color: #1d4ed8; | |
| } | |
| .btn-success, .btn-info { | |
| font-weight: 500; | |
| padding: 0.625rem 1.25rem; | |
| } | |
| .loading { | |
| display: none; | |
| align-items: center; | |
| gap: 0.75rem; | |
| padding: 1rem; | |
| background-color: var(--sidebar-bg); | |
| border-radius: 0.375rem; | |
| } | |
| .result-section { | |
| display: none; | |
| margin-top: 2rem; | |
| } | |
| .bg-light { | |
| background-color: var(--sidebar-bg) ; | |
| border-radius: 0.375rem; | |
| } | |
| h2 { | |
| font-weight: 600; | |
| margin-bottom: 1.5rem; | |
| color: var(--text-primary); | |
| } | |
| h5, h6 { | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| } | |
| .footer { | |
| position: fixed; | |
| bottom: 0; | |
| width: 100%; | |
| background-color: var(--header-bg); | |
| border-top: 1px solid var(--border-color); | |
| padding: 1rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| z-index: 1000; | |
| } | |
| .footer .copyright { | |
| color: var(--text-secondary); | |
| font-size: 0.875rem; | |
| } | |
| .footer .status { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| color: var(--text-secondary); | |
| font-size: 0.875rem; | |
| } | |
| .footer .status .status-indicator { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| } | |
| .footer .status .status-details { | |
| display: none; | |
| position: absolute; | |
| bottom: 100%; | |
| right: 1rem; | |
| background: white; | |
| border: 1px solid var(--border-color); | |
| border-radius: 0.5rem; | |
| padding: 1rem; | |
| box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1); | |
| min-width: 200px; | |
| } | |
| .footer .status:hover .status-details { | |
| display: block; | |
| } | |
| .status-details div { | |
| margin-bottom: 0.5rem; | |
| } | |
| .status-details .component { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .status-details .component-indicator { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| } | |
| .status-details .resources { | |
| margin-top: 0.75rem; | |
| padding-top: 0.75rem; | |
| border-top: 1px solid var(--border-color); | |
| } | |
| /* Markdown content styling */ | |
| .section { | |
| padding: 2rem; | |
| } | |
| .section table { | |
| border-collapse: collapse; | |
| margin: 1rem 0; | |
| width: 100%; | |
| } | |
| .section table th, | |
| .section table td { | |
| border: 1px solid var(--border-color); | |
| padding: 0.75rem; | |
| text-align: left; | |
| } | |
| .section table th { | |
| background-color: var(--header-bg); | |
| font-weight: 600; | |
| } | |
| .section table tr:nth-child(even) { | |
| background-color: var(--header-bg); | |
| } | |
| .section h1, | |
| .section h2, | |
| .section h3 { | |
| color: var(--text-primary); | |
| margin-top: 2rem; | |
| margin-bottom: 1rem; | |
| } | |
| .section h1 { | |
| font-size: 2rem; | |
| border-bottom: 2px solid var(--border-color); | |
| padding-bottom: 0.5rem; | |
| } | |
| .section h2 { | |
| font-size: 1.5rem; | |
| } | |
| .section h3 { | |
| font-size: 1.25rem; | |
| } | |
| .section p { | |
| margin-bottom: 1rem; | |
| line-height: 1.6; | |
| } | |
| .section ul, | |
| .section ol { | |
| margin-bottom: 1rem; | |
| padding-left: 2rem; | |
| } | |
| .section li { | |
| margin-bottom: 0.5rem; | |
| } | |
| .section code { | |
| background-color: var(--header-bg); | |
| padding: 0.2rem 0.4rem; | |
| border-radius: 0.25rem; | |
| font-family: monospace; | |
| } | |
| .section pre { | |
| background-color: var(--header-bg); | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| overflow-x: auto; | |
| margin: 1rem 0; | |
| } | |
| .section a { | |
| color: var(--primary-color); | |
| text-decoration: none; | |
| } | |
| .section a:hover { | |
| text-decoration: underline; | |
| } | |
| /* Math equations */ | |
| .section .MathJax_Display { | |
| overflow-x: auto; | |
| max-width: 100%; | |
| margin: 1em 0; | |
| } | |
| .section .MathJax { | |
| font-size: 1.1em ; | |
| } | |
| .section .math-block { | |
| overflow-x: auto; | |
| margin: 1em 0; | |
| text-align: center; | |
| } | |
| </style> | |
| <!-- MathJax Configuration --> | |
| <script type="text/x-mathjax-config"> | |
| MathJax.Hub.Config({ | |
| tex2jax: { | |
| inlineMath: [['$','$'], ['\\(','\\)']], | |
| displayMath: [['$$','$$'], ['\\[','\\]']], | |
| processEscapes: true, | |
| processEnvironments: true | |
| }, | |
| displayAlign: 'center', | |
| "HTML-CSS": { | |
| styles: {'.MathJax_Display': {"margin": 0}}, | |
| linebreaks: { automatic: true } | |
| } | |
| }); | |
| </script> | |
| <!-- MathJax Loading --> | |
| <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"></script> | |
| </head> | |
| <body> | |
| <button type="button" id="showSidebarBtn" class="btn"> | |
| <i class="bi bi-chevron-right"></i> | |
| </button> | |
| <div class="wrapper"> | |
| <!-- Sidebar --> | |
| <nav id="sidebar"> | |
| <div class="sidebar-header"> | |
| <h5 class="sidebar-title">Procrustes Server</h5> | |
| <button type="button" id="sidebarCollapse" class="btn"> | |
| <i class="bi bi-chevron-left"></i> | |
| </button> | |
| </div> | |
| <div class="components"> | |
| <a href="#about" class="nav-link" data-section="about"> | |
| <i class="bi bi-book"></i> About Procrustes | |
| </a> | |
| <a href="#tools" class="nav-link" data-section="tools"> | |
| <i class="bi bi-tools"></i> Procrustes Tools | |
| </a> | |
| <a href="#contact" class="nav-link" data-section="contact"> | |
| <i class="bi bi-envelope"></i> Contact | |
| </a> | |
| </div> | |
| </nav> | |
| <!-- Page Content --> | |
| <div id="content"> | |
| <!-- About Section --> | |
| <div id="about" class="section" style="display: none;"> | |
| <h2>About Procrustes Analysis</h2> | |
| <div id="about-content">Loading...</div> | |
| </div> | |
| <!-- Tools Section --> | |
| <div id="tools" class="section"> | |
| <h2>Procrustes Analysis Tools</h2> | |
| <div class="card"> | |
| <div class="card-body"> | |
| <form id="uploadForm" enctype="multipart/form-data"> | |
| <div class="mb-3"> | |
| <label for="algorithm" class="form-label">Select Algorithm</label> | |
| <select class="form-select" id="algorithm" name="algorithm" required> | |
| <option value="orthogonal">orthogonal</option> | |
| <option value="rotational">rotational</option> | |
| <option value="permutation">permutation</option> | |
| <!-- <option value="generalized">generalized</option> --> | |
| <option value="generic">generic</option> | |
| <!-- <option value="kopt_heuristic_single">kopt_heuristic_single</option> --> | |
| <!-- <option value="kopt_heuristic_double">kopt_heuristic_double</option> --> | |
| <option value="orthogonal_2sided">orthogonal_2sided</option> | |
| <option value="permutation_2sided">permutation_2sided</option> | |
| <option value="softassign">softassign</option> | |
| <option value="symmetric">symmetric</option> | |
| </select> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="parameters" class="form-label">Additional Parameters (JSON format)</label> | |
| <textarea class="form-control font-monospace" id="parameters" name="parameters" rows="10"></textarea> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="file1" class="form-label">First Matrix (NPZ/Excel/TXT)</label> | |
| <input type="file" class="form-control" id="file1" name="file1" accept=".npz,.xlsx,.xls,.txt" required> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="file2" class="form-label">Second Matrix (NPZ/Excel/TXT)</label> | |
| <input type="file" class="form-control" id="file2" name="file2" accept=".npz,.xlsx,.xls,.txt" required> | |
| </div> | |
| <button type="submit" class="btn btn-primary">Analyze</button> | |
| </form> | |
| <div class="loading mt-3"> | |
| <div class="spinner-border text-primary" role="status"> | |
| <span class="visually-hidden">Loading...</span> | |
| </div> | |
| <span class="ms-2">Processing...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="resultSection" class="result-section"> | |
| <div class="card"> | |
| <div class="card-body"> | |
| <h5 class="card-title">Results</h5> | |
| <p>Procrustes Error: <span id="error" class="fw-bold"></span></p> | |
| <div class="mb-3"> | |
| <h6>Transformation Matrix:</h6> | |
| <div id="transformationMatrix" class="bg-light p-3 mb-3" style="font-family: monospace;"> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <h6>Transformed Array:</h6> | |
| <div id="transformedArray" class="bg-light p-3 mb-3" style="font-family: monospace;"> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label">Download Format</label> | |
| <select class="form-select" id="downloadFormat"> | |
| <option value="npz">NPZ</option> | |
| <option value="xlsx">Excel</option> | |
| <option value="txt">TXT</option> | |
| </select> | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn btn-success" onclick="downloadResult('transformation')"> | |
| Download Transformation Matrix | |
| </button> | |
| <button class="btn btn-info" onclick="downloadResult('new_array')"> | |
| Download Transformed Array | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Contact Section --> | |
| <div id="contact" class="section" style="display: none;"> | |
| <h2>Contact Information</h2> | |
| <div id="contact-content">Loading...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="footer"> | |
| <div class="copyright"> | |
| © Copyright 2017-2024, The QC-Devs Community. | |
| </div> | |
| <div class="status"> | |
| <div class="status-indicator"></div> | |
| <span>Server Status: </span> | |
| <span id="serverStatus">Loading...</span> | |
| <div class="status-details"> | |
| <div><strong>Components:</strong></div> | |
| <div class="component"> | |
| <div class="component-indicator"></div> | |
| <span>Flask</span> | |
| </div> | |
| <div class="component"> | |
| <div class="component-indicator"></div> | |
| <span>Celery</span> | |
| </div> | |
| <div class="component"> | |
| <div class="component-indicator"></div> | |
| <span>Redis</span> | |
| </div> | |
| <div class="resources"> | |
| <div><strong>Resources:</strong></div> | |
| <div>CPU: <span id="cpuStatus">-</span>%</div> | |
| <div>Memory: <span id="memoryStatus">-</span>%</div> | |
| <div>Disk: <span id="diskStatus">-</span>%</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |
| <script> | |
| let resultData = null; | |
| // Sidebar toggle functionality | |
| const sidebar = document.getElementById('sidebar'); | |
| const content = document.getElementById('content'); | |
| const sidebarCollapse = document.getElementById('sidebarCollapse'); | |
| const showSidebarBtn = document.getElementById('showSidebarBtn'); | |
| function toggleSidebar() { | |
| sidebar.classList.toggle('active'); | |
| content.classList.toggle('active'); | |
| showSidebarBtn.classList.toggle('show', sidebar.classList.contains('active')); | |
| } | |
| sidebarCollapse.addEventListener('click', toggleSidebar); | |
| showSidebarBtn.addEventListener('click', toggleSidebar); | |
| function showSection(sectionId) { | |
| // Hide all sections | |
| document.querySelectorAll('.section').forEach(section => { | |
| section.style.display = 'none'; | |
| }); | |
| // Remove active class from all nav links | |
| document.querySelectorAll('.nav-link').forEach(link => { | |
| link.classList.remove('active'); | |
| }); | |
| // Show selected section | |
| const section = document.getElementById(sectionId); | |
| if (section) { | |
| section.style.display = 'block'; | |
| // Add active class to corresponding nav link | |
| const navLink = document.querySelector(`.nav-link[data-section="${sectionId}"]`); | |
| if (navLink) { | |
| navLink.classList.add('active'); | |
| } | |
| } | |
| } | |
| // Handle navigation clicks | |
| document.querySelectorAll('.nav-link').forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const sectionId = link.getAttribute('data-section'); | |
| showSection(sectionId); | |
| }); | |
| }); | |
| // Show tools section by default | |
| document.addEventListener('DOMContentLoaded', function() { | |
| showSection('tools'); | |
| loadMarkdownContent(); | |
| checkServerStatus(); | |
| setInterval(checkServerStatus, 30000); | |
| }); | |
| function formatMatrix(matrix) { | |
| // Return the matrix as is, without formatting | |
| return matrix; | |
| } | |
| async function updateDefaultParams() { | |
| const algorithm = document.getElementById('algorithm').value; | |
| try { | |
| const response = await fetch(`/get_default_params/${algorithm}`); | |
| const data = await response.json(); | |
| document.getElementById('parameters').value = JSON.stringify(data, null, 2); | |
| } catch (error) { | |
| console.error('Error fetching default parameters:', error); | |
| } | |
| } | |
| document.getElementById('algorithm').addEventListener('change', updateDefaultParams); | |
| updateDefaultParams(); | |
| document.getElementById('uploadForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| console.log('Form submitted'); | |
| const formData = new FormData(e.target); | |
| const loading = document.querySelector('.loading'); | |
| const resultSection = document.getElementById('resultSection'); | |
| loading.style.display = 'block'; | |
| resultSection.style.display = 'none'; | |
| try { | |
| console.log('Sending request...'); | |
| const response = await fetch('/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| console.log('Response received'); | |
| const data = await response.json(); | |
| console.log('Response data:', data); | |
| if (response.ok) { | |
| resultData = data; | |
| console.log('Setting error:', data.error); | |
| document.getElementById('error').textContent = data.error.toFixed(6); | |
| console.log('Setting transformation matrix'); | |
| const transformationElem = document.getElementById('transformationMatrix'); | |
| transformationElem.textContent = JSON.stringify(data.transformation, null, 2); | |
| console.log('Setting transformed array'); | |
| const transformedArrayElem = document.getElementById('transformedArray'); | |
| transformedArrayElem.textContent = JSON.stringify(data.new_array, null, 2); | |
| resultSection.style.display = 'block'; | |
| } else { | |
| console.error('Error response:', data); | |
| alert(data.error || 'An error occurred'); | |
| } | |
| } catch (error) { | |
| console.error('Error in request:', error); | |
| alert('An error occurred while processing the request'); | |
| } finally { | |
| loading.style.display = 'none'; | |
| } | |
| }); | |
| async function downloadResult(type) { | |
| if (!resultData) return; | |
| const format = document.getElementById('downloadFormat').value; | |
| const formData = new FormData(); | |
| formData.append('data', JSON.stringify( | |
| type === 'transformation' ? resultData.transformation : resultData.new_array | |
| )); | |
| formData.append('format', format); | |
| try { | |
| const response = await fetch('/download', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (response.ok) { | |
| const blob = await response.blob(); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `procrustes_${type}.${format}`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| a.remove(); | |
| } else { | |
| const data = await response.json(); | |
| alert(data.error || 'An error occurred during download'); | |
| } | |
| } catch (error) { | |
| alert('An error occurred while downloading the file'); | |
| } | |
| } | |
| // Server status check | |
| async function checkServerStatus() { | |
| try { | |
| const response = await fetch('/status'); | |
| const data = await response.json(); | |
| const statusElem = document.getElementById('serverStatus'); | |
| const indicator = document.querySelector('.status-indicator'); | |
| // Update main status | |
| statusElem.textContent = data.status === 'ok' ? 'Running' : | |
| data.status === 'degraded' ? 'Degraded' : 'Error'; | |
| // Update indicator color | |
| indicator.style.backgroundColor = | |
| data.status === 'ok' ? '#22c55e' : // green | |
| data.status === 'degraded' ? '#f59e0b' : // orange | |
| '#ef4444'; // red | |
| // Update components | |
| const components = document.querySelectorAll('.component'); | |
| Object.entries(data.components).forEach((component, index) => { | |
| const [name, status] = component; | |
| components[index].querySelector('.component-indicator').style.backgroundColor = | |
| status ? '#22c55e' : '#ef4444'; | |
| }); | |
| } catch (error) { | |
| const statusElem = document.getElementById('serverStatus'); | |
| const indicator = document.querySelector('.status-indicator'); | |
| statusElem.textContent = 'Offline'; | |
| indicator.style.backgroundColor = '#ef4444'; // red | |
| // Set all components to red when offline | |
| const components = document.querySelectorAll('.component'); | |
| components.forEach(comp => { | |
| comp.querySelector('.component-indicator').style.backgroundColor = '#ef4444'; | |
| }); | |
| } | |
| } | |
| // Load markdown content and add MathJax support | |
| async function loadMarkdownContent() { | |
| try { | |
| // Load about content | |
| const aboutResponse = await fetch('/md/about.md'); | |
| const aboutData = await aboutResponse.json(); | |
| document.getElementById('about-content').innerHTML = aboutData.html; | |
| // Load contact content | |
| const contactResponse = await fetch('/md/contacts.md'); | |
| const contactData = await contactResponse.json(); | |
| document.getElementById('contact-content').innerHTML = contactData.html; | |
| // Retypeset math | |
| if (window.MathJax && window.MathJax.Hub) { | |
| window.MathJax.Hub.Queue(["Typeset", window.MathJax.Hub]); | |
| } | |
| } catch (error) { | |
| console.error('Error loading markdown content:', error); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |