| |
| """ |
| 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() |
|
|