Mentors4EDU's picture
Upload 42 files
35aaa09 verified
"""
GUI Interface for Docking@HOME
A modern web-based GUI using FastAPI and HTML/JavaScript for molecular docking.
Integrates with AutoDock backend for real molecular docking simulations.
Authors: OpenPeer AI, Riemann Computing Inc., Bleunomics, Andrew Magdy Kamal
"""
import os
import json
import asyncio
from pathlib import Path
from typing import Optional, List, Dict
from datetime import datetime
from fastapi import FastAPI, File, UploadFile, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import uvicorn
# Import the docking server components
from .server import job_manager, initialize_server
# Initialize FastAPI app
app = FastAPI(
title="Docking@HOME",
description="Distributed Molecular Docking Platform",
version="1.0.0"
)
# Data models
class DockingJobRequest(BaseModel):
num_runs: int = 100
use_gpu: bool = True
job_name: Optional[str] = None
class JobStatus(BaseModel):
job_id: str
status: str
progress: float
message: str
# Active WebSocket connections
active_websockets: List[WebSocket] = []
# File upload directory
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
RESULTS_DIR = Path("results")
RESULTS_DIR.mkdir(exist_ok=True)
@app.get("/", response_class=HTMLResponse)
async def root():
"""Serve the main GUI page"""
html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docking@HOME - Molecular Docking Platform</title>
<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;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
margin-bottom: 30px;
text-align: center;
}
.header h1 {
color: #667eea;
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 1.1em;
}
.authors {
color: #888;
font-size: 0.9em;
margin-top: 10px;
}
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.card {
background: white;
padding: 25px;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
}
.card h2 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.5em;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 600;
}
input[type="file"],
input[type="number"],
select {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: border-color 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: #667eea;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
}
input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 30px;
border: none;
border-radius: 8px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
width: 100%;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.job-list {
max-height: 400px;
overflow-y: auto;
}
.job-item {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
border-left: 4px solid #667eea;
}
.job-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.job-id {
font-weight: 600;
color: #333;
}
.status-badge {
padding: 5px 15px;
border-radius: 20px;
font-size: 0.85em;
font-weight: 600;
}
.status-pending {
background: #ffeaa7;
color: #d63031;
}
.status-running {
background: #74b9ff;
color: #0984e3;
}
.status-completed {
background: #55efc4;
color: #00b894;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-top: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s ease;
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 20px;
}
.stat-card {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-value {
font-size: 2em;
font-weight: 700;
color: #667eea;
}
.stat-label {
color: #666;
font-size: 0.9em;
margin-top: 5px;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
display: none;
min-width: 300px;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.notification.success {
border-left: 4px solid #00b894;
}
.notification.error {
border-left: 4px solid #d63031;
}
.footer {
text-align: center;
color: white;
margin-top: 30px;
padding: 20px;
}
.full-width {
grid-column: 1 / -1;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🧬 Docking@HOME</h1>
<p>Distributed Molecular Docking Platform</p>
<div class="authors">
OpenPeer AI Β· Riemann Computing Inc. Β· Bleunomics Β· Andrew Magdy Kamal
</div>
</div>
<div class="main-content">
<div class="card">
<h2>πŸ“€ Submit Docking Job</h2>
<form id="dockingForm">
<div class="form-group">
<label>Ligand File (PDBQT)</label>
<input type="file" id="ligandFile" accept=".pdbqt,.pdb" required>
</div>
<div class="form-group">
<label>Receptor File (PDBQT)</label>
<input type="file" id="receptorFile" accept=".pdbqt,.pdb" required>
</div>
<div class="form-group">
<label>Number of Runs</label>
<input type="number" id="numRuns" value="100" min="1" max="1000" required>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="useGPU" checked>
<label for="useGPU">Use GPU Acceleration</label>
</div>
<button type="submit" class="btn" id="submitBtn">
πŸš€ Start Docking
</button>
</form>
</div>
<div class="card">
<h2>πŸ“Š Active Jobs</h2>
<div class="job-list" id="jobList">
<p style="text-align: center; color: #999; padding: 20px;">
No jobs yet. Submit a docking job to get started!
</p>
</div>
</div>
<div class="card full-width">
<h2>πŸ“ˆ System Statistics</h2>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="totalJobs">0</div>
<div class="stat-label">Total Jobs</div>
</div>
<div class="stat-card">
<div class="stat-value" id="completedJobs">0</div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-card">
<div class="stat-value" id="avgTime">0s</div>
<div class="stat-label">Avg. Time</div>
</div>
</div>
</div>
</div>
<div class="footer">
<p>Support: andrew@bleunomics.com | Issues: <a href="https://huggingface.co/OpenPeerAI/DockingAtHOME/discussions" style="color: white;">HuggingFace</a></p>
</div>
</div>
<div class="notification" id="notification">
<div id="notificationMessage"></div>
</div>
<script>
const API_BASE = window.location.origin;
// WebSocket connection for real-time updates
let ws = null;
function connectWebSocket() {
ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateJobList();
updateStats();
};
ws.onerror = () => {
setTimeout(connectWebSocket, 5000);
};
}
// Submit docking job
document.getElementById('dockingForm').addEventListener('submit', async (e) => {
e.preventDefault();
const ligandFile = document.getElementById('ligandFile').files[0];
const receptorFile = document.getElementById('receptorFile').files[0];
const numRuns = document.getElementById('numRuns').value;
const useGPU = document.getElementById('useGPU').checked;
if (!ligandFile || !receptorFile) {
showNotification('Please select both ligand and receptor files', 'error');
return;
}
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = true;
submitBtn.textContent = '⏳ Uploading...';
try {
// Upload ligand
const ligandFormData = new FormData();
ligandFormData.append('file', ligandFile);
const ligandResponse = await fetch(`${API_BASE}/upload`, {
method: 'POST',
body: ligandFormData
});
const ligandData = await ligandResponse.json();
// Upload receptor
const receptorFormData = new FormData();
receptorFormData.append('file', receptorFile);
const receptorResponse = await fetch(`${API_BASE}/upload`, {
method: 'POST',
body: receptorFormData
});
const receptorData = await receptorResponse.json();
// Submit job
const jobResponse = await fetch(`${API_BASE}/api/jobs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ligand_file: ligandData.filename,
receptor_file: receptorData.filename,
num_runs: parseInt(numRuns),
use_gpu: useGPU
})
});
const jobData = await jobResponse.json();
showNotification(`Job submitted successfully! ID: ${jobData.job_id}`, 'success');
// Reset form
document.getElementById('dockingForm').reset();
// Update job list
updateJobList();
updateStats();
} catch (error) {
showNotification('Error submitting job: ' + error.message, 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'πŸš€ Start Docking';
}
});
// Update job list
async function updateJobList() {
try {
const response = await fetch(`${API_BASE}/api/jobs`);
const jobs = await response.json();
const jobList = document.getElementById('jobList');
if (jobs.length === 0) {
jobList.innerHTML = '<p style="text-align: center; color: #999; padding: 20px;">No jobs yet. Submit a docking job to get started!</p>';
return;
}
jobList.innerHTML = jobs.map(job => `
<div class="job-item">
<div class="job-header">
<span class="job-id">${job.job_id}</span>
<span class="status-badge status-${job.status}">${job.status.toUpperCase()}</span>
</div>
<div style="font-size: 0.9em; color: #666;">
<div>Ligand: ${job.ligand_file}</div>
<div>Receptor: ${job.receptor_file}</div>
<div>Runs: ${job.num_runs} | GPU: ${job.use_gpu ? 'Yes' : 'No'}</div>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${job.progress * 100}%"></div>
</div>
<div style="margin-top: 5px; font-size: 0.85em; color: #666;">
Progress: ${(job.progress * 100).toFixed(1)}%
</div>
</div>
`).join('');
} catch (error) {
console.error('Error updating job list:', error);
}
}
// Update statistics
async function updateStats() {
try {
const response = await fetch(`${API_BASE}/api/stats`);
const stats = await response.json();
document.getElementById('totalJobs').textContent = stats.total_jobs;
document.getElementById('completedJobs').textContent = stats.completed_jobs;
document.getElementById('avgTime').textContent = stats.avg_time + 's';
} catch (error) {
console.error('Error updating stats:', error);
}
}
// Show notification
function showNotification(message, type) {
const notification = document.getElementById('notification');
const messageElement = document.getElementById('notificationMessage');
messageElement.textContent = message;
notification.className = `notification ${type}`;
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 5000);
}
// Initialize
connectWebSocket();
updateJobList();
updateStats();
// Refresh job list every 2 seconds
setInterval(() => {
updateJobList();
updateStats();
}, 2000);
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
"""Upload ligand or receptor file"""
try:
file_path = UPLOAD_DIR / file.filename
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
return {"filename": file.filename, "path": str(file_path)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/jobs")
async def create_job(
ligand_file: str,
receptor_file: str,
num_runs: int = 100,
use_gpu: bool = True,
job_name: Optional[str] = None
):
"""Create a new docking job with real AutoDock integration"""
try:
# Submit job to the docking server
job_id = await job_manager.submit_job(
ligand_file=ligand_file,
receptor_file=receptor_file,
num_runs=num_runs,
use_gpu=use_gpu,
job_name=job_name
)
# Start broadcasting updates
asyncio.create_task(broadcast_job_updates(job_id))
return job_manager.get_job(job_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/jobs")
async def get_jobs():
"""Get all jobs"""
return job_manager.get_all_jobs()
@app.get("/api/jobs/{job_id}")
async def get_job(job_id: str):
"""Get specific job"""
job = job_manager.get_job(job_id)
if not job:
raise HTTPException(status_code=404, detail="Job not found")
return job
@app.get("/api/stats")
async def get_stats():
"""Get system statistics"""
return job_manager.get_stats()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket for real-time updates"""
await websocket.accept()
active_websockets.append(websocket)
try:
while True:
data = await websocket.receive_text()
# Echo back or handle commands
await websocket.send_json({"status": "connected"})
except WebSocketDisconnect:
active_websockets.remove(websocket)
except Exception as e:
if websocket in active_websockets:
active_websockets.remove(websocket)
async def broadcast_job_updates(job_id: str):
"""Broadcast job progress to all connected WebSocket clients"""
while True:
await asyncio.sleep(0.5) # Update every 500ms
job = job_manager.get_job(job_id)
if not job:
break
# Send update to all connected clients
for ws in active_websockets[:]: # Copy list to avoid modification during iteration
try:
await ws.send_json({
"type": "job_update",
"job_id": job_id,
"status": job["status"],
"progress": job["progress"]
})
except Exception:
# Remove disconnected clients
if ws in active_websockets:
active_websockets.remove(ws)
# Stop broadcasting if job is complete or failed
if job["status"] in ["completed", "failed"]:
break
@app.on_event("startup")
async def startup_event():
"""Initialize the server on startup"""
await initialize_server()
def start_gui(host: str = "localhost", port: int = 8080):
"""Start the GUI server with AutoDock integration"""
print(f"""
╔═══════════════════════════════════════════════════════════════╗
β•‘ Docking@HOME GUI Server β•‘
β•‘ Real AutoDock Integration with GPU Support β•‘
β•‘ β•‘
β•‘ 🌐 Server: http://{host}:{port} β•‘
β•‘ 🧬 AutoDock: Enabled (GPU acceleration supported) β•‘
β•‘ πŸ“§ Support: andrew@bleunomics.com β•‘
β•‘ πŸ€— Issues: https://huggingface.co/OpenPeerAI/DockingAtHOME β•‘
β•‘ β•‘
β•‘ Open your browser to start docking! β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
""")
uvicorn.run(app, host=host, port=port, log_level="info")
if __name__ == "__main__":
start_gui()