Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ConGr Visualizer</title> | |
| <script type="text/javascript" src="https://unpkg.com/vis-network@9.0.4/standalone/umd/vis-network.min.js"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| color: #333; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| color: white; | |
| } | |
| .header h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 10px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .header p { | |
| font-size: 1.1rem; | |
| opacity: 0.9; | |
| } | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 2fr; | |
| gap: 30px; | |
| background: white; | |
| border-radius: 15px; | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.1); | |
| overflow: hidden; | |
| } | |
| .sidebar { | |
| background: #f8f9fa; | |
| padding: 30px; | |
| border-right: 1px solid #e9ecef; | |
| } | |
| .section { | |
| margin-bottom: 30px; | |
| } | |
| .section h3 { | |
| color: #495057; | |
| margin-bottom: 15px; | |
| font-size: 1.2rem; | |
| border-bottom: 2px solid #667eea; | |
| padding-bottom: 5px; | |
| } | |
| .input-group { | |
| margin-bottom: 20px; | |
| } | |
| .input-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| color: #495057; | |
| } | |
| .input-group textarea { | |
| width: 100%; | |
| min-height: 120px; | |
| padding: 12px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 8px; | |
| font-family: inherit; | |
| font-size: 14px; | |
| resize: vertical; | |
| transition: border-color 0.3s ease; | |
| } | |
| .input-group textarea:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| } | |
| .input-group select { | |
| width: 100%; | |
| padding: 12px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 8px; | |
| font-family: inherit; | |
| font-size: 14px; | |
| background: white; | |
| cursor: pointer; | |
| transition: border-color 0.3s ease; | |
| } | |
| .input-group select:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 600; | |
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |
| width: 100%; | |
| margin-bottom: 10px; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| .btn-secondary { | |
| background: linear-gradient(135deg, #6c757d 0%, #495057 100%); | |
| } | |
| .btn-secondary:hover { | |
| box-shadow: 0 5px 15px rgba(108, 117, 125, 0.4); | |
| } | |
| .graph-container { | |
| padding: 30px; | |
| position: relative; | |
| } | |
| #mynetwork { | |
| width: 100%; | |
| height: 600px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 10px; | |
| background: white; | |
| } | |
| .loading { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(255, 255, 255, 0.9); | |
| padding: 20px; | |
| border-radius: 10px; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| z-index: 1000; | |
| } | |
| .loading.hidden { | |
| display: none; | |
| } | |
| .status { | |
| margin-top: 15px; | |
| padding: 10px; | |
| border-radius: 5px; | |
| font-size: 14px; | |
| } | |
| .status.success { | |
| background: #d4edda; | |
| color: #155724; | |
| border: 1px solid #c3e6cb; | |
| } | |
| .status.error { | |
| background: #f8d7da; | |
| color: #721c24; | |
| border: 1px solid #f5c6cb; | |
| } | |
| .status.info { | |
| background: #d1ecf1; | |
| color: #0c5460; | |
| border: 1px solid #bee5eb; | |
| } | |
| .example-text { | |
| background: #e9ecef; | |
| padding: 15px; | |
| border-radius: 8px; | |
| font-size: 13px; | |
| line-height: 1.4; | |
| margin-top: 10px; | |
| } | |
| .example-text h4 { | |
| margin-bottom: 8px; | |
| color: #495057; | |
| } | |
| .example-text p { | |
| margin-bottom: 8px; | |
| } | |
| .example-text ul { | |
| margin-left: 20px; | |
| } | |
| .example-text li { | |
| margin-bottom: 4px; | |
| } | |
| .entity-list { | |
| max-height: 200px; | |
| overflow-y: auto; | |
| border: 1px solid #e9ecef; | |
| border-radius: 8px; | |
| background: white; | |
| } | |
| .entity-item { | |
| padding: 10px 12px; | |
| border-bottom: 1px solid #f8f9fa; | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| } | |
| .entity-item:hover { | |
| background-color: #f8f9fa; | |
| } | |
| .entity-item:last-child { | |
| border-bottom: none; | |
| } | |
| .entity-name { | |
| font-weight: 600; | |
| color: #495057; | |
| } | |
| .entity-model { | |
| font-size: 12px; | |
| color: #6c757d; | |
| margin-top: 2px; | |
| } | |
| .graph-info { | |
| background: #e9ecef; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-top: 15px; | |
| } | |
| .graph-info h4 { | |
| margin-bottom: 10px; | |
| color: #495057; | |
| } | |
| .graph-info p { | |
| margin-bottom: 5px; | |
| font-size: 14px; | |
| } | |
| .consensus-text { | |
| background: #d4edda; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin-top: 10px; | |
| font-style: italic; | |
| } | |
| .sequences-section { | |
| background: #f8f9fa; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 15px; | |
| border: 1px solid #e9ecef; | |
| } | |
| .sequences-section h4 { | |
| margin-bottom: 10px; | |
| color: #495057; | |
| font-size: 1rem; | |
| } | |
| .sequences-list { | |
| max-height: 150px; | |
| overflow-y: auto; | |
| border: 1px solid #e9ecef; | |
| border-radius: 6px; | |
| background: white; | |
| } | |
| .sequences-list li { | |
| padding: 6px 10px; | |
| border-bottom: 1px solid #f8f9fa; | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| } | |
| .sequences-list li:hover { | |
| background-color: #f8f9fa; | |
| } | |
| .sequences-list li:last-child { | |
| border-bottom: none; | |
| } | |
| .sequences-list .sequence-item { | |
| font-family: 'Courier New', Courier, monospace; | |
| font-size: 12px; | |
| line-height: 1.3; | |
| white-space: pre-wrap; | |
| word-break: break-all; | |
| } | |
| .consensus-highlight { | |
| background-color: #ceeab2; | |
| color: #2d5016; | |
| font-weight: bold; | |
| padding: 1px 2px; | |
| border-radius: 3px; | |
| } | |
| .consensus-section { | |
| background: #f8f9fa; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 15px; | |
| border: 1px solid #e9ecef; | |
| } | |
| .consensus-text { | |
| font-family: 'Courier New', Courier, monospace; | |
| font-size: 14px; | |
| line-height: 1.4; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| background: white; | |
| padding: 10px; | |
| border: 1px solid #e9ecef; | |
| border-radius: 6px; | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| .sidebar { | |
| border-right: none; | |
| border-bottom: 1px solid #e9ecef; | |
| } | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>ConGr Visualizer</h1> | |
| <p>Explore and visualize ConGrs</p> | |
| </div> | |
| <div class="main-content"> | |
| <div class="sidebar"> | |
| <div class="section"> | |
| <h3>Browse Existing Graphs</h3> | |
| <div class="input-group"> | |
| <label for="datasetSelect">Select Dataset:</label> | |
| <select id="datasetSelect" onchange="loadEntities()"> | |
| <option value="">Choose a dataset...</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label for="entitySelect">Select Instance:</label> | |
| <select id="entitySelect" onchange="loadModels()"> | |
| <option value="">Choose an instance...</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label for="modelSelect">Select Model:</label> | |
| <select id="modelSelect" onchange="loadSelectedGraph()"> | |
| <option value="">Choose a model...</option> | |
| </select> | |
| </div> | |
| <div id="graphInfo" class="graph-info hidden"> | |
| <h4>Graph Information</h4> | |
| <div id="graphDetails"></div> | |
| </div> | |
| </div> | |
| <!-- COMMENTED OUT: Create New Graph Section --> | |
| <!-- | |
| <div class="section"> | |
| <h3>Create New Graph</h3> | |
| <div class="input-group"> | |
| <label for="textInput">Enter text sequences (one per line):</label> | |
| <textarea id="textInput" placeholder="Enter your text sequences here..."></textarea> | |
| </div> | |
| <button class="btn" onclick="createGraph()">Create Graph</button> | |
| <div class="input-group"> | |
| <label> | |
| <input type="checkbox" id="computeConsensus" checked> Display consensus response using consensus decoding | |
| </label> | |
| </div> | |
| </div> | |
| --> | |
| <!-- COMMENTED OUT: Graph Options Section --> | |
| <!-- | |
| <div class="section"> | |
| <h3>Graph Options</h3> | |
| <div class="input-group"> | |
| <label for="saveFilename">Save filename:</label> | |
| <input type="text" id="saveFilename" placeholder="graph.pkl" value="graph.pkl"> | |
| </div> | |
| <button class="btn btn-secondary" onclick="saveGraph()">Save Graph</button> | |
| <button class="btn btn-secondary" onclick="clearGraph()">Clear Graph</button> | |
| </div> | |
| --> | |
| <div id="status" class="status hidden"></div> | |
| </div> | |
| <div class="graph-container"> | |
| <div id="loadingProgress" class="loading hidden">Processing...</div> | |
| <div id="originalSequences" class="sequences-section hidden"> | |
| <h4>Original Sequences</h4> | |
| <div id="sequencesList"></div> | |
| </div> | |
| <div id="consensusResponse" class="consensus-section hidden"> | |
| <h4>Consensus Response</h4> | |
| <div id="consensusText"></div> | |
| </div> | |
| <div id="mynetwork"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let network = null; | |
| let currentGraphData = null; | |
| let availableEntities = []; | |
| function showStatus(message, type = 'info') { | |
| const status = document.getElementById('status'); | |
| status.textContent = message; | |
| status.className = `status ${type}`; | |
| status.classList.remove('hidden'); | |
| if (type === 'success') { | |
| setTimeout(() => { | |
| status.classList.add('hidden'); | |
| }, 3000); | |
| } | |
| } | |
| function showLoading() { | |
| document.getElementById('loadingProgress').classList.remove('hidden'); | |
| } | |
| function hideLoading() { | |
| document.getElementById('loadingProgress').classList.add('hidden'); | |
| } | |
| async function loadDatasets() { | |
| try { | |
| const response = await fetch('/api/datasets'); | |
| const data = await response.json(); | |
| const datasetSelect = document.getElementById('datasetSelect'); | |
| datasetSelect.innerHTML = '<option value="">Choose a dataset...</option>'; | |
| if (data.datasets && data.datasets.length > 0) { | |
| data.datasets.forEach(dataset => { | |
| const option = document.createElement('option'); | |
| option.value = dataset.name; | |
| option.textContent = `${dataset.display_name} (${dataset.count} graphs)`; | |
| datasetSelect.appendChild(option); | |
| }); | |
| } | |
| } catch (error) { | |
| showStatus('Error loading datasets: ' + error.message, 'error'); | |
| } | |
| } | |
| async function loadEntities() { | |
| const datasetSelect = document.getElementById('datasetSelect'); | |
| const entitySelect = document.getElementById('entitySelect'); | |
| const modelSelect = document.getElementById('modelSelect'); | |
| const dataset = datasetSelect.value; | |
| if (!dataset) { | |
| entitySelect.innerHTML = '<option value="">Choose an entity...</option>'; | |
| modelSelect.innerHTML = '<option value="">Choose a model...</option>'; | |
| return; | |
| } | |
| showLoading(); | |
| showStatus('Loading entities...', 'info'); | |
| try { | |
| const response = await fetch(`/api/entities?dataset=${dataset}`); | |
| const data = await response.json(); | |
| if (data.error) { | |
| showStatus('Error loading entities: ' + data.error, 'error'); | |
| return; | |
| } | |
| availableEntities = data.entities; | |
| entitySelect.innerHTML = '<option value="">Choose an entity...</option>'; | |
| modelSelect.innerHTML = '<option value="">Choose a model...</option>'; | |
| if (data.entities && data.entities.length > 0) { | |
| // Get unique entity names | |
| const uniqueEntities = [...new Set(data.entities.map(e => e.entity))]; | |
| // Sort numerically for non-bio datasets | |
| if (dataset !== 'bio') { | |
| uniqueEntities.sort((a, b) => { | |
| // Extract numbers from entity names for sorting | |
| const numA = parseInt(a.match(/\d+/)?.[0] || '0'); | |
| const numB = parseInt(b.match(/\d+/)?.[0] || '0'); | |
| return numA - numB; | |
| }); | |
| } else { | |
| // Sort alphabetically for bio dataset | |
| uniqueEntities.sort(); | |
| } | |
| uniqueEntities.forEach(entityName => { | |
| const option = document.createElement('option'); | |
| option.value = entityName; | |
| option.textContent = entityName; | |
| entitySelect.appendChild(option); | |
| }); | |
| } | |
| showStatus(`Loaded ${data.entities.length} entities from ${dataset} dataset`, 'success'); | |
| } catch (error) { | |
| showStatus('Error loading entities: ' + error.message, 'error'); | |
| } finally { | |
| hideLoading(); | |
| } | |
| } | |
| async function loadModels() { | |
| const datasetSelect = document.getElementById('datasetSelect'); | |
| const entitySelect = document.getElementById('entitySelect'); | |
| const modelSelect = document.getElementById('modelSelect'); | |
| const dataset = datasetSelect.value; | |
| const entityName = entitySelect.value; | |
| if (!entityName) { | |
| modelSelect.innerHTML = '<option value="">Choose a model...</option>'; | |
| return; | |
| } | |
| showLoading(); | |
| showStatus('Loading models...', 'info'); | |
| try { | |
| const response = await fetch(`/api/models?dataset=${dataset}&entity=${encodeURIComponent(entityName)}`); | |
| const data = await response.json(); | |
| if (data.error) { | |
| showStatus('Error loading models: ' + data.error, 'error'); | |
| return; | |
| } | |
| modelSelect.innerHTML = '<option value="">Choose a model...</option>'; | |
| if (data.models && data.models.length > 0) { | |
| // Sort models by name for consistency | |
| data.models.sort((a, b) => a.model.localeCompare(b.model)); | |
| data.models.forEach(model => { | |
| const option = document.createElement('option'); | |
| option.value = model.filepath; | |
| option.textContent = model.model; | |
| modelSelect.appendChild(option); | |
| }); | |
| } else { | |
| console.log('No models found for this entity'); | |
| } | |
| showStatus(`Loaded ${data.models.length} models for ${entityName}`, 'success'); | |
| } catch (error) { | |
| showStatus('Error loading models: ' + error.message, 'error'); | |
| } finally { | |
| hideLoading(); | |
| } | |
| } | |
| function displayOriginalSequences(sequences, consensusText = null) { | |
| const sequencesSection = document.getElementById('originalSequences'); | |
| const sequencesList = document.getElementById('sequencesList'); | |
| if (!sequences || sequences.length === 0) { | |
| sequencesSection.classList.add('hidden'); | |
| return; | |
| } | |
| let html = '<ul class="sequences-list">'; | |
| sequences.forEach((sequence, index) => { | |
| let highlightedSequence = sequence; | |
| // Highlight consensus text in green if available | |
| if (consensusText && consensusText.trim()) { | |
| const consensusWords = consensusText.trim().split(/\s+/); | |
| let currentSequence = sequence; | |
| consensusWords.forEach(word => { | |
| if (word.length > 2) { // Only highlight words longer than 2 characters | |
| const regex = new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'gi'); | |
| currentSequence = currentSequence.replace(regex, `<span class="consensus-highlight">${word}</span>`); | |
| } | |
| }); | |
| highlightedSequence = currentSequence; | |
| } | |
| html += `<li><div class="sequence-item"><strong>Sequence ${index + 1}:</strong> ${highlightedSequence}</div></li>`; | |
| }); | |
| html += '</ul>'; | |
| sequencesList.innerHTML = html; | |
| sequencesSection.classList.remove('hidden'); | |
| // Display consensus response in separate box if available | |
| displayConsensusResponse(consensusText); | |
| } | |
| function displayConsensusResponse(consensusText) { | |
| const consensusSection = document.getElementById('consensusResponse'); | |
| const consensusTextDiv = document.getElementById('consensusText'); | |
| if (!consensusText || !consensusText.trim()) { | |
| consensusSection.classList.add('hidden'); | |
| return; | |
| } | |
| consensusTextDiv.innerHTML = `<div class="consensus-text">${consensusText}</div>`; | |
| consensusSection.classList.remove('hidden'); | |
| } | |
| async function loadSelectedGraph() { | |
| const modelSelect = document.getElementById('modelSelect'); | |
| const selectedModel = modelSelect.value; | |
| if (!selectedModel) { | |
| return; | |
| } | |
| showLoading(); | |
| showStatus('Loading graph...', 'info'); | |
| try { | |
| // Note: computeConsensus checkbox removed, using default value | |
| const computeConsensus = false; // or true, depending on your preference | |
| const response = await fetch('/api/load_existing_graph', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| filepath: selectedModel, | |
| compute_consensus: computeConsensus | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| displayGraph(data.nodes, data.edges); | |
| displayOriginalSequences(data.original_sequences, data.consensus_text); | |
| showGraphInfo(data); | |
| showStatus(`Graph loaded successfully! ${data.num_sequences} sequences, ${data.num_nodes} nodes, ${data.num_edges} edges.`, 'success'); | |
| } else { | |
| showStatus('Error loading graph: ' + data.error, 'error'); | |
| } | |
| } catch (error) { | |
| showStatus('Error loading graph: ' + error.message, 'error'); | |
| } finally { | |
| hideLoading(); | |
| } | |
| } | |
| function showGraphInfo(data) { | |
| const graphInfo = document.getElementById('graphInfo'); | |
| const graphDetails = document.getElementById('graphDetails'); | |
| let detailsHtml = ''; | |
| if (data.metadata) { | |
| detailsHtml += ` | |
| <p><strong>Dataset:</strong> ${data.metadata.task}</p> | |
| <p><strong>Entity:</strong> ${data.metadata.entity}</p> | |
| <p><strong>Model:</strong> ${data.metadata.model}</p> | |
| `; | |
| } else { | |
| } | |
| detailsHtml += ` | |
| <p><strong>Sequences:</strong> ${data.num_sequences}</p> | |
| <p><strong>Nodes:</strong> ${data.num_nodes}</p> | |
| <p><strong>Edges:</strong> ${data.num_edges}</p> | |
| `; | |
| graphDetails.innerHTML = detailsHtml; | |
| graphInfo.classList.remove('hidden'); | |
| } | |
| // COMMENTED OUT: createGraph function | |
| /* | |
| async function createGraph() { | |
| const textInput = document.getElementById('textInput').value.trim(); | |
| if (!textInput) { | |
| showStatus('Please enter some text sequences.', 'error'); | |
| return; | |
| } | |
| const sequences = textInput.split('\n').filter(line => line.trim() !== ''); | |
| if (sequences.length < 2) { | |
| showStatus('Please enter at least 2 text sequences.', 'error'); | |
| return; | |
| } | |
| showLoading(); | |
| showStatus('Creating graph...', 'info'); | |
| try { | |
| const computeConsensus = document.getElementById('computeConsensus').checked; | |
| const response = await fetch('/api/create_graph', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| sequences: sequences, | |
| compute_consensus: computeConsensus | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| displayGraph(data.nodes, data.edges); | |
| displayOriginalSequences(data.original_sequences, data.consensus_text); | |
| showStatus(`Graph created with ${data.num_sequences} sequences, ${data.num_nodes} nodes, and ${data.num_edges} edges!`, 'success'); | |
| } else { | |
| showStatus('Error creating graph: ' + data.error, 'error'); | |
| } | |
| } catch (error) { | |
| showStatus('Error creating graph: ' + error.message, 'error'); | |
| } finally { | |
| hideLoading(); | |
| } | |
| } | |
| */ | |
| function displayGraph(nodes, edges) { | |
| const container = document.getElementById('mynetwork'); | |
| if (!nodes || nodes.length === 0) { | |
| console.error('No nodes provided to displayGraph'); | |
| return; | |
| } | |
| // Process nodes without manual level assignment | |
| const processedNodes = nodes.map(node => ({ ...node })); | |
| const data = { | |
| nodes: new vis.DataSet(processedNodes), | |
| edges: new vis.DataSet(edges) | |
| }; | |
| const options = { | |
| width: '100%', | |
| height: '100%', | |
| physics: { | |
| enabled: false, | |
| stabilization: { | |
| updateInterval: 10, | |
| }, | |
| }, | |
| edges: { | |
| color: { | |
| inherit: false | |
| } | |
| }, | |
| layout: { | |
| hierarchical: { | |
| direction: "UD", | |
| sortMethod: "directed", | |
| shakeTowards: "roots", | |
| levelSeparation: 150, | |
| nodeSpacing: 800, | |
| treeSpacing: 200, | |
| parentCentralization: true, | |
| } | |
| } | |
| }; | |
| if (network) { | |
| network.destroy(); | |
| } | |
| try { | |
| network = new vis.Network(container, data, options); | |
| } catch (error) { | |
| console.error('Error creating network:', error); | |
| return; | |
| } | |
| network.on("stabilizationProgress", function (params) { | |
| document.getElementById("loadingProgress").innerText = | |
| "Stabilizing: " + Math.round(params.iterations / params.total * 100) + "%"; | |
| }); | |
| network.once("stabilizationIterationsDone", function () { | |
| document.getElementById("loadingProgress").innerText = "100%"; | |
| setTimeout(function () { | |
| document.getElementById("loadingProgress").classList.add("hidden"); | |
| }, 500); | |
| }); | |
| currentGraphData = { nodes, edges }; | |
| } | |
| // COMMENTED OUT: saveGraph function | |
| /* | |
| async function saveGraph() { | |
| const textInput = document.getElementById('textInput').value.trim(); | |
| if (!textInput) { | |
| showStatus('Please enter some text sequences first.', 'error'); | |
| return; | |
| } | |
| const sequences = textInput.split('\n').filter(line => line.trim() !== ''); | |
| if (sequences.length < 2) { | |
| showStatus('Please enter at least 2 text sequences.', 'error'); | |
| return; | |
| } | |
| const filename = document.getElementById('saveFilename').value || 'graph.pkl'; | |
| showLoading(); | |
| showStatus('Saving graph...', 'info'); | |
| try { | |
| const response = await fetch('/api/save_graph', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| sequences: sequences, | |
| filename: filename | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| showStatus(`Graph saved successfully to ${data.filename}!`, 'success'); | |
| } else { | |
| showStatus('Error saving graph: ' + data.error, 'error'); | |
| } | |
| } catch (error) { | |
| showStatus('Error saving graph: ' + error.message, 'error'); | |
| } finally { | |
| hideLoading(); | |
| } | |
| } | |
| */ | |
| // COMMENTED OUT: clearGraph function (modified to only clear existing graph browsing) | |
| /* | |
| function clearGraph() { | |
| if (network) { | |
| network.destroy(); | |
| network = null; | |
| } | |
| currentGraphData = null; | |
| document.getElementById('textInput').value = ''; | |
| document.getElementById('datasetSelect').value = ''; | |
| document.getElementById('entitySelect').innerHTML = '<option value="">Choose an entity...</option>'; | |
| document.getElementById('modelSelect').innerHTML = '<option value="">Choose a model...</option>'; | |
| document.getElementById('graphInfo').classList.add('hidden'); | |
| document.getElementById('originalSequences').classList.add('hidden'); | |
| showStatus('Graph cleared.', 'info'); | |
| } | |
| */ | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| loadDatasets(); | |
| showStatus('Ready to explore existing graphs!', 'info'); | |
| }); | |
| </script> | |
| </body> | |
| </html> |