|
|
"""
|
|
|
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
|
|
|
|
|
|
|
|
|
from .server import job_manager, initialize_server
|
|
|
|
|
|
|
|
|
app = FastAPI(
|
|
|
title="Docking@HOME",
|
|
|
description="Distributed Molecular Docking Platform",
|
|
|
version="1.0.0"
|
|
|
)
|
|
|
|
|
|
|
|
|
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_websockets: List[WebSocket] = []
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
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
|
|
|
)
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
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)
|
|
|
|
|
|
job = job_manager.get_job(job_id)
|
|
|
|
|
|
if not job:
|
|
|
break
|
|
|
|
|
|
|
|
|
for ws in active_websockets[:]:
|
|
|
try:
|
|
|
await ws.send_json({
|
|
|
"type": "job_update",
|
|
|
"job_id": job_id,
|
|
|
"status": job["status"],
|
|
|
"progress": job["progress"]
|
|
|
})
|
|
|
except Exception:
|
|
|
|
|
|
if ws in active_websockets:
|
|
|
active_websockets.remove(ws)
|
|
|
|
|
|
|
|
|
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()
|
|
|
|