VA1 / web_interface.py
V1PAB's picture
N
a618af0 verified
#!/usr/bin/env python3
"""
Web Interface for AI Drawing Correction Agent
Provides interactive analysis and correction capabilities
"""
import http.server
import socketserver
import json
import urllib.parse
from pathlib import Path
import base64
import io
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Drawing Correction Agent</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;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
font-size: 1.2em;
opacity: 0.9;
}
.main-content {
padding: 40px;
}
.section {
margin-bottom: 40px;
}
.section h2 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.8em;
border-bottom: 3px solid #667eea;
padding-bottom: 10px;
}
.file-upload {
border: 3px dashed #667eea;
border-radius: 10px;
padding: 40px;
text-align: center;
background: #f8f9ff;
cursor: pointer;
transition: all 0.3s;
}
.file-upload:hover {
background: #e8ebff;
border-color: #764ba2;
}
.file-upload input {
display: none;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 30px;
font-size: 1.1em;
border-radius: 10px;
cursor: pointer;
transition: transform 0.2s;
font-weight: bold;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status-box {
background: #f8f9ff;
border-left: 5px solid #667eea;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
}
.error-box {
background: #fff5f5;
border-left: 5px solid #e53e3e;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
color: #c53030;
}
.success-box {
background: #f0fff4;
border-left: 5px solid #38a169;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
color: #2f855a;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 20px 0;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.stat-card .number {
font-size: 2.5em;
font-weight: bold;
margin-bottom: 5px;
}
.stat-card .label {
font-size: 0.9em;
opacity: 0.9;
}
.error-list {
max-height: 500px;
overflow-y: auto;
}
.error-item {
background: white;
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.error-item.critical {
border-left: 5px solid #e53e3e;
}
.error-item.high {
border-left: 5px solid #ed8936;
}
.error-item.medium {
border-left: 5px solid #ecc94b;
}
.error-item.low {
border-left: 5px solid #48bb78;
}
.error-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.error-title {
font-weight: bold;
font-size: 1.1em;
}
.severity-badge {
padding: 5px 15px;
border-radius: 20px;
font-size: 0.85em;
font-weight: bold;
}
.severity-badge.critical {
background: #e53e3e;
color: white;
}
.severity-badge.high {
background: #ed8936;
color: white;
}
.severity-badge.medium {
background: #ecc94b;
color: #744210;
}
.severity-badge.low {
background: #48bb78;
color: white;
}
.tabs {
display: flex;
border-bottom: 2px solid #e2e8f0;
margin-bottom: 20px;
}
.tab {
padding: 15px 30px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s;
}
.tab:hover {
background: #f8f9ff;
}
.tab.active {
border-bottom-color: #667eea;
color: #667eea;
font-weight: bold;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.progress-bar {
width: 100%;
height: 30px;
background: #e2e8f0;
border-radius: 15px;
overflow: hidden;
margin: 20px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.5s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.conversion-guide {
background: #fffbeb;
border: 2px solid #f59e0b;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.conversion-guide h3 {
color: #92400e;
margin-bottom: 15px;
}
.conversion-guide ol {
margin-left: 20px;
}
.conversion-guide li {
margin: 10px 0;
line-height: 1.6;
}
pre {
background: #1a202c;
color: #68d391;
padding: 20px;
border-radius: 10px;
overflow-x: auto;
margin: 10px 0;
}
code {
font-family: 'Courier New', monospace;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏗️ AI Drawing Correction Agent</h1>
<p>Intelligent architectural drawing analysis and error detection powered by AI</p>
</div>
<div class="main-content">
<!-- File Upload Section -->
<div class="section">
<h2>📁 Upload Drawing File</h2>
<div id="dwg-notice" class="conversion-guide">
<h3>⚠️ DWG File Detected - Conversion Required</h3>
<p><strong>Your file "P203_-_ARCH_STR_Fahad_Alqahtani_11-11-2024.dwg" needs to be converted to DXF format first.</strong></p>
<h4>Method 1: Online Conversion (Fastest)</h4>
<ol>
<li>Visit: <a href="https://www.aconvert.com/document/dwg-to-dxf/" target="_blank">https://www.aconvert.com/document/dwg-to-dxf/</a></li>
<li>Upload your DWG file</li>
<li>Click "Convert Now!"</li>
<li>Download the DXF file and upload it here</li>
</ol>
<h4>Method 2: Using AutoCAD/DraftSight</h4>
<ol>
<li>Open your DWG file in AutoCAD or DraftSight</li>
<li>Go to File → Save As</li>
<li>Select "AutoCAD DXF (*.dxf)" as file type</li>
<li>Save and upload the DXF file here</li>
</ol>
<h4>Method 3: Command Line (Linux/Mac)</h4>
<pre><code>pip install ezdxf
# Then use our converter script (provided below)</code></pre>
</div>
<div class="file-upload" onclick="document.getElementById('fileInput').click()">
<div style="font-size: 3em; margin-bottom: 10px;">📄</div>
<p style="font-size: 1.2em; margin-bottom: 10px;">Click to upload DXF file</p>
<p style="color: #666;">Supported formats: DXF (R12, R2000, R2004, R2007, R2010, R2013, R2018)</p>
<input type="file" id="fileInput" accept=".dxf" onchange="handleFileSelect(event)">
</div>
<div id="fileInfo" style="display: none; margin-top: 20px;">
<div class="status-box">
<strong>Selected file:</strong> <span id="fileName"></span><br>
<strong>Size:</strong> <span id="fileSize"></span>
</div>
<button class="btn" onclick="analyzeDrawing()">🔍 Analyze Drawing</button>
</div>
</div>
<!-- Analysis Results -->
<div id="resultsSection" class="section" style="display: none;">
<h2>📊 Analysis Results</h2>
<div class="tabs">
<div class="tab active" onclick="switchTab('summary')">Summary</div>
<div class="tab" onclick="switchTab('errors')">Errors</div>
<div class="tab" onclick="switchTab('layers')">Layers</div>
<div class="tab" onclick="switchTab('corrections')">Corrections</div>
</div>
<!-- Summary Tab -->
<div id="summary-tab" class="tab-content active">
<div class="stats-grid" id="statsGrid"></div>
<div id="summaryContent"></div>
</div>
<!-- Errors Tab -->
<div id="errors-tab" class="tab-content">
<div id="errorsContent"></div>
</div>
<!-- Layers Tab -->
<div id="layers-tab" class="tab-content">
<div id="layersContent"></div>
</div>
<!-- Corrections Tab -->
<div id="corrections-tab" class="tab-content">
<div id="correctionsContent"></div>
</div>
<div style="margin-top: 30px;">
<button class="btn" onclick="downloadReport('markdown')">📄 Download MD Report</button>
<button class="btn" onclick="downloadReport('json')">📦 Download JSON Report</button>
<button class="btn" onclick="applyCorrections()" id="applyBtn">🔧 Apply Auto-Corrections</button>
</div>
</div>
<!-- Processing Status -->
<div id="processingStatus" style="display: none;">
<div class="status-box">
<h3>⏳ Processing...</h3>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%">0%</div>
</div>
<p id="progressText">Initializing...</p>
</div>
</div>
</div>
</div>
<script>
let currentFile = null;
let analysisResults = null;
function handleFileSelect(event) {
currentFile = event.target.files[0];
if (currentFile) {
document.getElementById('fileName').textContent = currentFile.name;
document.getElementById('fileSize').textContent = formatFileSize(currentFile.size);
document.getElementById('fileInfo').style.display = 'block';
// Check if DXF
if (!currentFile.name.toLowerCase().endsWith('.dxf')) {
alert('Please upload a DXF file. If you have a DWG file, please convert it first using the instructions above.');
currentFile = null;
document.getElementById('fileInfo').style.display = 'none';
}
}
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
}
async function analyzeDrawing() {
if (!currentFile) return;
// Show processing status
document.getElementById('processingStatus').style.display = 'block';
document.getElementById('resultsSection').style.display = 'none';
updateProgress(10, 'Reading file...');
// Read file
const formData = new FormData();
formData.append('file', currentFile);
updateProgress(30, 'Analyzing drawing structure...');
try {
// In real implementation, this would call the backend API
// For demo purposes, we'll simulate the analysis
await simulateAnalysis();
updateProgress(100, 'Complete!');
setTimeout(() => {
document.getElementById('processingStatus').style.display = 'none';
displayResults();
}, 500);
} catch (error) {
document.getElementById('processingStatus').innerHTML =
'<div class="error-box"><strong>Error:</strong> ' + error.message + '</div>';
}
}
function updateProgress(percent, text) {
document.getElementById('progressFill').style.width = percent + '%';
document.getElementById('progressFill').textContent = percent + '%';
document.getElementById('progressText').textContent = text;
}
async function simulateAnalysis() {
// Simulate processing time
await new Promise(resolve => setTimeout(resolve, 1000));
updateProgress(50, 'Detecting errors...');
await new Promise(resolve => setTimeout(resolve, 1000));
updateProgress(70, 'Generating correction plan...');
await new Promise(resolve => setTimeout(resolve, 1000));
updateProgress(90, 'Finalizing report...');
await new Promise(resolve => setTimeout(resolve, 500));
// Mock results
analysisResults = {
metadata: {
filename: currentFile.name,
dxfversion: 'AC1027 (R2013)',
total_entities: 2847,
total_layers: 42
},
errors: [
{
id: 'LAYER_WALL-1_NAMING',
category: 'layer',
severity: 'medium',
description: 'Layer "WALL-1" does not follow AIA naming convention',
correction: 'Rename to AIA format (e.g., A-WALL-FULL)',
auto_correctable: false
},
{
id: 'DIM_SCALE_INCONSISTENT',
category: 'dimension',
severity: 'medium',
description: 'Inconsistent dimension scales found: {0.5, 1.0, 2.0}',
correction: 'Standardize dimension scale across all dimensions',
auto_correctable: true
},
{
id: 'GEOM_LINE_ZEROLENGTH_A45F',
category: 'geometry',
severity: 'high',
description: 'Zero-length line detected on layer A-WALL',
correction: 'Delete zero-length line',
auto_correctable: true
}
],
summary: {
total_errors: 23,
critical: 2,
high: 5,
medium: 11,
low: 5,
auto_correctable: 14
}
};
}
function displayResults() {
document.getElementById('resultsSection').style.display = 'block';
// Display stats
const statsHtml = `
<div class="stat-card">
<div class="number">${analysisResults.summary.total_errors}</div>
<div class="label">Total Errors</div>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%);">
<div class="number">${analysisResults.summary.critical}</div>
<div class="label">Critical</div>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #ed8936 0%, #c05621 100%);">
<div class="number">${analysisResults.summary.high}</div>
<div class="label">High Priority</div>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #48bb78 0%, #2f855a 100%);">
<div class="number">${analysisResults.summary.auto_correctable}</div>
<div class="label">Auto-Correctable</div>
</div>
`;
document.getElementById('statsGrid').innerHTML = statsHtml;
// Display errors
let errorsHtml = '<div class="error-list">';
analysisResults.errors.forEach(error => {
errorsHtml += `
<div class="error-item ${error.severity}">
<div class="error-header">
<div class="error-title">${error.description}</div>
<span class="severity-badge ${error.severity}">${error.severity.toUpperCase()}</span>
</div>
<p><strong>Category:</strong> ${error.category}</p>
<p><strong>Correction:</strong> ${error.correction}</p>
<p><strong>Auto-correctable:</strong> ${error.auto_correctable ? '✓ Yes' : '✗ No'}</p>
</div>
`;
});
errorsHtml += '</div>';
document.getElementById('errorsContent').innerHTML = errorsHtml;
}
function switchTab(tabName) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected tab
document.getElementById(tabName + '-tab').classList.add('active');
event.target.classList.add('active');
}
function downloadReport(format) {
alert('Report download would trigger here for format: ' + format);
}
function applyCorrections() {
if (confirm('Apply ' + analysisResults.summary.auto_correctable + ' automatic corrections?')) {
alert('Corrections would be applied here');
}
}
</script>
</body>
</html>
"""
def run_web_interface(port=8000):
"""Run the web interface"""
class RequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/' or self.path == '/index.html':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(HTML_TEMPLATE.encode())
else:
super().do_GET()
with socketserver.TCPServer(("", port), RequestHandler) as httpd:
print(f"Web interface running at http://localhost:{port}")
print("Press Ctrl+C to stop")
httpd.serve_forever()
if __name__ == "__main__":
run_web_interface()