ResearchMate / src /templates /projects.html
Ananthakr1shnan's picture
Updated files
eed2f95
{% extends "base.html" %}
{% block title %}ResearchMate - Projects{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<!-- Projects Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2 text-primary-custom">
<i class="fas fa-project-diagram me-2 text-success"></i>Research Projects
</h1>
<button class="btn btn-success shadow-sm" data-bs-toggle="modal" data-bs-target="#createProjectModal">
<i class="fas fa-plus me-2"></i>Create New Project
</button>
</div>
<!-- Projects List -->
<div id="projects-container">
<div class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading projects...</span>
</div>
<p class="mt-3 text-muted">Loading projects...</p>
</div>
</div>
</div>
</div>
<!-- Create Project Modal -->
<div class="modal fade" id="createProjectModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content shadow-lg" style="color: #222;">
<div class="modal-header" style="background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: #222;">
<h5 class="modal-title">
<i class="fas fa-plus me-2"></i>Create New Project
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="create-project-form">
<div class="mb-3">
<label for="project-name" class="form-label fw-bold">Project Name</label>
<input type="text" class="form-control" id="project-name" name="name"
placeholder="e.g., Transformer Models in NLP" required>
<div class="form-text">
<i class="fas fa-info-circle me-1 text-info"></i>
Choose a descriptive name for your research project
</div>
</div>
<div class="mb-3">
<label for="research-question" class="form-label fw-bold">Research Question</label>
<textarea class="form-control" id="research-question" name="research_question" rows="3"
placeholder="e.g., How do transformer models improve performance in natural language processing tasks?"
required></textarea>
</div>
<div class="mb-3">
<label for="keywords" class="form-label">Keywords (comma-separated)</label>
<input type="text" class="form-control" id="keywords" name="keywords"
placeholder="e.g., transformer, attention, NLP, neural networks" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="create-project-form" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Create Project
</button>
</div>
</div>
</div>
</div>
<!-- Project Details Modal -->
<div class="modal fade" id="projectDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-project-diagram me-2"></i>Project Details
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="project-details-content">
<!-- Project details will be loaded here -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const createProjectForm = document.getElementById('create-project-form');
const projectsContainer = document.getElementById('projects-container');
// Load projects on page load
loadProjects();
// Create project form handler
createProjectForm.addEventListener('submit', function(e) {
e.preventDefault();
const name = document.getElementById('project-name').value;
const researchQuestion = document.getElementById('research-question').value;
const keywords = document.getElementById('keywords').value.split(',').map(k => k.trim());
createProject(name, researchQuestion, keywords);
});
function loadProjects() {
apiRequest('/api/projects', { credentials: 'include' })
.then(data => {
console.log('Projects API response:', data); // Debug log
if (data.success) {
displayProjects(data.projects);
} else {
displayError(data.error || 'Failed to load projects');
}
})
.catch(error => {
console.error('Projects loading error:', error); // Debug log
displayError('Network error: ' + error.message);
});
}
function createProject(name, researchQuestion, keywords) {
const submitBtn = document.querySelector('button[form="create-project-form"]') ||
document.querySelector('#create-project-form button[type="submit"]') ||
document.querySelector('.modal-footer button[type="submit"]');
const originalText = submitBtn ? submitBtn.innerHTML : '';
if (submitBtn) {
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Creating...';
submitBtn.disabled = true;
}
apiRequest('/api/projects', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
name: name,
research_question: researchQuestion,
keywords: keywords
})
})
.then(data => {
if (data.success) {
// Close modal and reset form
const modal = bootstrap.Modal.getInstance(document.getElementById('createProjectModal'));
modal.hide();
createProjectForm.reset();
// Reload projects
loadProjects();
// Show success message
showAlert('success', 'Project created successfully!');
} else {
showAlert('danger', data.error || 'Failed to create project');
}
})
.catch(error => {
showAlert('danger', 'Network error: ' + error.message);
})
.finally(() => {
if (submitBtn) {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
});
}
function displayProjects(projects) {
console.log('Displaying projects:', projects); // Debug log
if (!projects || projects.length === 0) {
projectsContainer.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-project-diagram fa-3x text-muted mb-3"></i>
<h4 class="text-muted">No projects yet</h4>
<p class="text-muted">Create your first research project to get started!</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createProjectModal">
<i class="fas fa-plus me-2"></i>Create Project
</button>
</div>
`;
return;
}
const html = `
<div class="row">
${projects.map(project => `
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">${escapeHtml(project.name || 'Untitled Project')}</h5>
<p class="card-text">
<small class="text-muted">${escapeHtml(project.research_question || 'No research question')}</small>
</p>
<div class="mb-2">
<span class="badge bg-${project.status === 'active' ? 'success' : 'secondary'} me-2">
${project.status || 'unknown'}
</span>
<span class="badge bg-info">${(project.papers || []).length} papers</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
Created: ${new Date(project.created_at).toLocaleDateString()}
</small>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary" onclick="viewProject('${project.id}')">
<i class="fas fa-eye me-1"></i>View
</button>
<button class="btn btn-sm btn-outline-success" onclick="searchLiterature('${project.id}')">
<i class="fas fa-search me-1"></i>Search
</button>
</div>
</div>
</div>
</div>
</div>
`).join('')}
</div>
`;
projectsContainer.innerHTML = html;
}
function displayError(message) {
projectsContainer.innerHTML = `
<div class="alert alert-danger" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Error:</strong> ${message}
</div>
`;
}
function showAlert(type, message, duration = 10000) {
const alert = document.createElement('div');
alert.className = `alert alert-${type} alert-dismissible fade show`;
alert.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check' : 'exclamation-triangle'} me-2"></i>
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.querySelector('.container').insertBefore(alert, document.querySelector('.container').firstChild);
// Store reference to current alert
window.currentActiveAlert = alert;
// Only auto-dismiss if duration > 0
if (duration > 0) {
setTimeout(() => {
if (alert.parentNode) {
alert.remove();
if (window.currentActiveAlert === alert) {
window.currentActiveAlert = null;
}
}
}, duration);
}
}
// Global functions for project actions
window.viewProject = function(projectId) {
apiRequest(`/api/projects/${projectId}`, { credentials: 'include' })
.then(data => {
if (data.success) {
displayProjectDetails(data.project);
} else {
showAlert('danger', data.error || 'Failed to load project');
}
})
.catch(error => {
showAlert('danger', 'Network error: ' + error.message);
});
};
window.searchLiterature = function(projectId) {
// Find the button that was clicked and show loading state
const button = event.target.closest('button');
const originalText = button ? button.innerHTML : '';
if (button) {
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Searching...';
button.disabled = true;
button.classList.add('btn-warning');
button.classList.remove('btn-success', 'btn-outline-success');
}
// Show persistent loading message (no auto-dismiss)
showAlert('info', 'Literature search in progress... This may take a few minutes.', 0); // 0 = no auto-dismiss
apiRequest(`/api/projects/${projectId}/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(data => {
// Remove loading alert if present
if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
window.currentActiveAlert.remove();
window.currentActiveAlert = null;
}
if (data.success) {
showAlert('success', `Found ${data.papers_found || 0} papers for the project!`);
loadProjects(); // Reload to show updated paper count
// Display search results if available
if (data.papers && data.papers.length > 0) {
displaySearchResults(data.papers, projectId);
}
} else {
showAlert('danger', data.error || 'Literature search failed');
}
})
.catch(error => {
showAlert('danger', 'Network error: ' + error.message);
})
.finally(() => {
// Restore button state
if (button) {
button.innerHTML = originalText;
button.disabled = false;
button.classList.remove('btn-warning');
button.classList.add('btn-success');
}
});
};
function displayProjectDetails(project) {
const detailsContent = document.getElementById('project-details-content');
detailsContent.innerHTML = `
<div class="mb-3">
<h6 class="text-primary">Project Name:</h6>
<p>${project.name}</p>
</div>
<div class="mb-3">
<h6 class="text-primary">Research Question:</h6>
<p>${project.research_question}</p>
</div>
<div class="mb-3">
<h6 class="text-primary">Keywords:</h6>
<div>
${project.keywords.map(keyword => `<span class="badge bg-secondary me-1">${keyword}</span>`).join('')}
</div>
</div>
<div class="mb-3">
<h6 class="text-primary">Status:</h6>
<span class="badge bg-${project.status === 'active' ? 'success' : 'secondary'}">${project.status}</span>
</div>
<div class="mb-3">
<h6 class="text-primary">Papers:</h6>
<p>${project.papers ? project.papers.length : 0} papers collected</p>
</div>
<div class="mb-3">
<h6 class="text-primary">Created:</h6>
<p>${new Date(project.created_at).toLocaleString()}</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-success" onclick="searchLiterature('${project.id}')">
<i class="fas fa-search me-2"></i>Search Literature
</button>
<button class="btn btn-info" onclick="analyzeProject('${project.id}')">
<i class="fas fa-chart-bar me-2"></i>Analyze
</button>
<button class="btn btn-warning" onclick="generateReview('${project.id}')">
<i class="fas fa-file-alt me-2"></i>Generate Review
</button>
</div>
`;
const modal = new bootstrap.Modal(document.getElementById('projectDetailsModal'));
modal.show();
}
window.analyzeProject = function(projectId) {
// Find the button that was clicked and show loading state
const button = event.target.closest('button');
const originalText = button ? button.innerHTML : '';
if (button) {
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Analyzing...';
button.disabled = true;
button.classList.add('btn-warning');
button.classList.remove('btn-info', 'btn-outline-info');
}
showAlert('info', 'Analyzing project data... This may take a few minutes.', 0);
apiRequest(`/api/projects/${projectId}/analyze`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(data => {
// Remove loading alert if present
if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
window.currentActiveAlert.remove();
window.currentActiveAlert = null;
}
if (data.success) {
showAlert('success', 'Project analysis completed!');
// Display analysis results
if (data.analysis) {
displayAnalysisResults(data.analysis, projectId);
}
} else {
showAlert('danger', data.error || 'Analysis failed');
}
})
.catch(error => {
showAlert('danger', 'Network error: ' + error.message);
})
.finally(() => {
// Restore button state
if (button) {
button.innerHTML = originalText;
button.disabled = false;
button.classList.remove('btn-warning');
button.classList.add('btn-info');
}
});
};
window.generateReview = function(projectId) {
// Find the button that was clicked and show loading state
const button = event.target.closest('button');
const originalText = button ? button.innerHTML : '';
if (button) {
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Generating...';
button.disabled = true;
button.classList.add('btn-info');
button.classList.remove('btn-warning', 'btn-outline-warning');
}
showAlert('info', 'Generating literature review... This may take several minutes.', 0); // 0 = no auto-dismiss
apiRequest(`/api/projects/${projectId}/review`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(data => {
console.log('Review response:', data); // Debug log
// Remove loading alert if present
if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
window.currentActiveAlert.remove();
window.currentActiveAlert = null;
}
if (data.success) {
showAlert('success', 'Literature review generated!');
// Display the generated review
if (data.review) {
console.log('Review data:', data.review); // Debug log
displayLiteratureReview(data.review, projectId);
} else {
showAlert('warning', 'Review generated but no content received');
}
} else {
showAlert('danger', data.error || 'Review generation failed');
}
})
.catch(error => {
showAlert('danger', 'Network error: ' + error.message);
})
.finally(() => {
// Restore button state
if (button) {
button.innerHTML = originalText;
button.disabled = false;
button.classList.remove('btn-info');
button.classList.add('btn-warning');
}
});
};
// Functions to display results
function displaySearchResults(papers, projectId) {
// Defensive: avoid nested template literals and ensure all dynamic content is escaped
const papersHtml = papers.map(function(paper) {
const title = escapeHtml(paper.title || 'Untitled');
const authors = escapeHtml(paper.authors || 'Unknown authors');
const year = escapeHtml(paper.year || 'Unknown year');
const abstract = escapeHtml((paper.abstract || 'No abstract available').substring(0, 150));
let urlLink = '';
if (paper.url) {
urlLink = '<a href="' + escapeHtml(paper.url) + '" target="_blank" class="btn btn-sm btn-outline-primary">' +
'<i class="fas fa-external-link-alt me-1"></i>View Paper' +
'</a>';
}
return [
'<div class="col-md-6 mb-3">',
' <div class="card h-100">',
' <div class="card-body">',
' <h6 class="card-title">' + title + '</h6>',
' <p class="text-muted small mb-2">',
' <i class="fas fa-users me-1"></i>' + authors,
' </p>',
' <p class="text-muted small mb-2">',
' <i class="fas fa-calendar me-1"></i>' + year,
' </p>',
' <p class="card-text small">' + abstract + '...</p>',
' ' + urlLink,
' </div>',
' </div>',
'</div>'
].join('');
}).join('');
const modalContent = [
'<div class="mb-3">',
' <h6 class="text-primary">Found ' + papers.length + ' papers:</h6>',
'</div>',
'<div class="row">',
papersHtml,
'</div>'
].join('');
const modal = createResultModal('Literature Search Results', modalContent);
modal.show();
}
function displayAnalysisResults(analysis, projectId) {
// Store analysis data globally for download
window.currentAnalysis = analysis;
const modal = createResultModal('Project Analysis Results', `
<div class="row">
<div class="col-md-6 mb-3">
<div class="card border-primary">
<div class="card-header bg-primary text-white">
<h6 class="mb-0"><i class="fas fa-chart-bar me-2"></i>Overview</h6>
</div>
<div class="card-body">
<p><strong>Total Papers:</strong> ${analysis.total_papers || 'N/A'}</p>
<p><strong>Key Topics:</strong> ${analysis.key_topics ? analysis.key_topics.join(', ') : 'N/A'}</p>
<p><strong>Research Trends:</strong> ${analysis.trends || 'N/A'}</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-success">
<div class="card-header bg-success text-white">
<h6 class="mb-0"><i class="fas fa-lightbulb me-2"></i>Key Insights</h6>
</div>
<div class="card-body">
<div style="max-height: 200px; overflow-y: auto;">
${analysis.insights ? marked.parse(analysis.insights) : 'No insights available'}
</div>
</div>
</div>
</div>
<div class="col-12 mb-3">
<div class="card border-info">
<div class="card-header bg-info text-white">
<h6 class="mb-0"><i class="fas fa-brain me-2"></i>Summary</h6>
</div>
<div class="card-body">
<div style="max-height: 300px; overflow-y: auto;">
${analysis.summary ? marked.parse(analysis.summary) : 'No summary available'}
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end">
<button class="btn btn-primary me-2" onclick="downloadAnalysis('${projectId}')">
<i class="fas fa-download me-1"></i>Download Analysis
</button>
</div>
`);
modal.show();
}
function displayLiteratureReview(review, projectId) {
// Store review data globally for download
window.currentReview = review;
const modal = createResultModal('Literature Review', `
<div class="card">
<div class="card-body">
<div style="max-height: 600px; overflow-y: auto;">
${review.content ? marked.parse(review.content) : 'No review content available'}
</div>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button class="btn btn-success me-2" onclick="downloadReview('${projectId}')">
<i class="fas fa-download me-1"></i>Download Review
</button>
<button class="btn btn-info" onclick="copyReview('${projectId}')">
<i class="fas fa-copy me-1"></i>Copy to Clipboard
</button>
</div>
`);
modal.show();
}
function createResultModal(title, content) {
// Remove existing result modal if it exists
const existingModal = document.getElementById('resultModal');
if (existingModal) {
existingModal.remove();
}
// Create new modal
const modalHtml = `
<div class="modal fade" id="resultModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${title}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
${content}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
return new bootstrap.Modal(document.getElementById('resultModal'));
}
// Global storage for results
window.currentAnalysis = null;
window.currentReview = null;
// Utility function to escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Analysis-specific functions (global scope)
window.downloadAnalysis = function(projectId) {
if (window.currentAnalysis) {
const analysisText = JSON.stringify(window.currentAnalysis, null, 2);
window.downloadText(analysisText, `analysis_${projectId}.json`);
} else {
showAlert('danger', 'No analysis data available to download', 3000);
}
};
// Review-specific functions (global scope)
window.downloadReview = function(projectId) {
if (window.currentReview && window.currentReview.content) {
window.downloadText(window.currentReview.content, `review_${projectId}.md`);
} else {
showAlert('danger', 'No review content available to download', 3000);
}
};
window.copyReview = function(projectId) {
if (window.currentReview && window.currentReview.content) {
window.copyToClipboard(window.currentReview.content);
} else {
showAlert('danger', 'No review content available to copy', 3000);
}
};
});
</script>
{% endblock %}