ConGrs / web_interface /index.html
Shahzaib98's picture
Updated UI to remove customization of responses for now
68bdba1 verified
<!DOCTYPE html>
<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>