V1PAB commited on
Commit
a618af0
·
verified ·
1 Parent(s): 674e82e
Files changed (1) hide show
  1. web_interface.py +645 -0
web_interface.py ADDED
@@ -0,0 +1,645 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Web Interface for AI Drawing Correction Agent
4
+ Provides interactive analysis and correction capabilities
5
+ """
6
+
7
+ import http.server
8
+ import socketserver
9
+ import json
10
+ import urllib.parse
11
+ from pathlib import Path
12
+ import base64
13
+ import io
14
+
15
+
16
+ HTML_TEMPLATE = """
17
+ <!DOCTYPE html>
18
+ <html lang="en">
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
+ <title>AI Drawing Correction Agent</title>
23
+ <style>
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
32
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
33
+ min-height: 100vh;
34
+ padding: 20px;
35
+ }
36
+
37
+ .container {
38
+ max-width: 1200px;
39
+ margin: 0 auto;
40
+ background: white;
41
+ border-radius: 20px;
42
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
43
+ overflow: hidden;
44
+ }
45
+
46
+ .header {
47
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
48
+ color: white;
49
+ padding: 40px;
50
+ text-align: center;
51
+ }
52
+
53
+ .header h1 {
54
+ font-size: 2.5em;
55
+ margin-bottom: 10px;
56
+ }
57
+
58
+ .header p {
59
+ font-size: 1.2em;
60
+ opacity: 0.9;
61
+ }
62
+
63
+ .main-content {
64
+ padding: 40px;
65
+ }
66
+
67
+ .section {
68
+ margin-bottom: 40px;
69
+ }
70
+
71
+ .section h2 {
72
+ color: #667eea;
73
+ margin-bottom: 20px;
74
+ font-size: 1.8em;
75
+ border-bottom: 3px solid #667eea;
76
+ padding-bottom: 10px;
77
+ }
78
+
79
+ .file-upload {
80
+ border: 3px dashed #667eea;
81
+ border-radius: 10px;
82
+ padding: 40px;
83
+ text-align: center;
84
+ background: #f8f9ff;
85
+ cursor: pointer;
86
+ transition: all 0.3s;
87
+ }
88
+
89
+ .file-upload:hover {
90
+ background: #e8ebff;
91
+ border-color: #764ba2;
92
+ }
93
+
94
+ .file-upload input {
95
+ display: none;
96
+ }
97
+
98
+ .btn {
99
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
100
+ color: white;
101
+ border: none;
102
+ padding: 15px 30px;
103
+ font-size: 1.1em;
104
+ border-radius: 10px;
105
+ cursor: pointer;
106
+ transition: transform 0.2s;
107
+ font-weight: bold;
108
+ }
109
+
110
+ .btn:hover {
111
+ transform: translateY(-2px);
112
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
113
+ }
114
+
115
+ .btn:disabled {
116
+ opacity: 0.5;
117
+ cursor: not-allowed;
118
+ }
119
+
120
+ .status-box {
121
+ background: #f8f9ff;
122
+ border-left: 5px solid #667eea;
123
+ padding: 20px;
124
+ margin: 20px 0;
125
+ border-radius: 5px;
126
+ }
127
+
128
+ .error-box {
129
+ background: #fff5f5;
130
+ border-left: 5px solid #e53e3e;
131
+ padding: 20px;
132
+ margin: 20px 0;
133
+ border-radius: 5px;
134
+ color: #c53030;
135
+ }
136
+
137
+ .success-box {
138
+ background: #f0fff4;
139
+ border-left: 5px solid #38a169;
140
+ padding: 20px;
141
+ margin: 20px 0;
142
+ border-radius: 5px;
143
+ color: #2f855a;
144
+ }
145
+
146
+ .stats-grid {
147
+ display: grid;
148
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
149
+ gap: 20px;
150
+ margin: 20px 0;
151
+ }
152
+
153
+ .stat-card {
154
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
155
+ color: white;
156
+ padding: 20px;
157
+ border-radius: 10px;
158
+ text-align: center;
159
+ }
160
+
161
+ .stat-card .number {
162
+ font-size: 2.5em;
163
+ font-weight: bold;
164
+ margin-bottom: 5px;
165
+ }
166
+
167
+ .stat-card .label {
168
+ font-size: 0.9em;
169
+ opacity: 0.9;
170
+ }
171
+
172
+ .error-list {
173
+ max-height: 500px;
174
+ overflow-y: auto;
175
+ }
176
+
177
+ .error-item {
178
+ background: white;
179
+ border: 1px solid #e2e8f0;
180
+ border-radius: 10px;
181
+ padding: 20px;
182
+ margin-bottom: 15px;
183
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
184
+ }
185
+
186
+ .error-item.critical {
187
+ border-left: 5px solid #e53e3e;
188
+ }
189
+
190
+ .error-item.high {
191
+ border-left: 5px solid #ed8936;
192
+ }
193
+
194
+ .error-item.medium {
195
+ border-left: 5px solid #ecc94b;
196
+ }
197
+
198
+ .error-item.low {
199
+ border-left: 5px solid #48bb78;
200
+ }
201
+
202
+ .error-header {
203
+ display: flex;
204
+ justify-content: space-between;
205
+ align-items: center;
206
+ margin-bottom: 10px;
207
+ }
208
+
209
+ .error-title {
210
+ font-weight: bold;
211
+ font-size: 1.1em;
212
+ }
213
+
214
+ .severity-badge {
215
+ padding: 5px 15px;
216
+ border-radius: 20px;
217
+ font-size: 0.85em;
218
+ font-weight: bold;
219
+ }
220
+
221
+ .severity-badge.critical {
222
+ background: #e53e3e;
223
+ color: white;
224
+ }
225
+
226
+ .severity-badge.high {
227
+ background: #ed8936;
228
+ color: white;
229
+ }
230
+
231
+ .severity-badge.medium {
232
+ background: #ecc94b;
233
+ color: #744210;
234
+ }
235
+
236
+ .severity-badge.low {
237
+ background: #48bb78;
238
+ color: white;
239
+ }
240
+
241
+ .tabs {
242
+ display: flex;
243
+ border-bottom: 2px solid #e2e8f0;
244
+ margin-bottom: 20px;
245
+ }
246
+
247
+ .tab {
248
+ padding: 15px 30px;
249
+ cursor: pointer;
250
+ border-bottom: 3px solid transparent;
251
+ transition: all 0.3s;
252
+ }
253
+
254
+ .tab:hover {
255
+ background: #f8f9ff;
256
+ }
257
+
258
+ .tab.active {
259
+ border-bottom-color: #667eea;
260
+ color: #667eea;
261
+ font-weight: bold;
262
+ }
263
+
264
+ .tab-content {
265
+ display: none;
266
+ }
267
+
268
+ .tab-content.active {
269
+ display: block;
270
+ }
271
+
272
+ .progress-bar {
273
+ width: 100%;
274
+ height: 30px;
275
+ background: #e2e8f0;
276
+ border-radius: 15px;
277
+ overflow: hidden;
278
+ margin: 20px 0;
279
+ }
280
+
281
+ .progress-fill {
282
+ height: 100%;
283
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
284
+ transition: width 0.5s;
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: center;
288
+ color: white;
289
+ font-weight: bold;
290
+ }
291
+
292
+ .conversion-guide {
293
+ background: #fffbeb;
294
+ border: 2px solid #f59e0b;
295
+ border-radius: 10px;
296
+ padding: 20px;
297
+ margin: 20px 0;
298
+ }
299
+
300
+ .conversion-guide h3 {
301
+ color: #92400e;
302
+ margin-bottom: 15px;
303
+ }
304
+
305
+ .conversion-guide ol {
306
+ margin-left: 20px;
307
+ }
308
+
309
+ .conversion-guide li {
310
+ margin: 10px 0;
311
+ line-height: 1.6;
312
+ }
313
+
314
+ pre {
315
+ background: #1a202c;
316
+ color: #68d391;
317
+ padding: 20px;
318
+ border-radius: 10px;
319
+ overflow-x: auto;
320
+ margin: 10px 0;
321
+ }
322
+
323
+ code {
324
+ font-family: 'Courier New', monospace;
325
+ }
326
+ </style>
327
+ </head>
328
+ <body>
329
+ <div class="container">
330
+ <div class="header">
331
+ <h1>🏗️ AI Drawing Correction Agent</h1>
332
+ <p>Intelligent architectural drawing analysis and error detection powered by AI</p>
333
+ </div>
334
+
335
+ <div class="main-content">
336
+ <!-- File Upload Section -->
337
+ <div class="section">
338
+ <h2>📁 Upload Drawing File</h2>
339
+
340
+ <div id="dwg-notice" class="conversion-guide">
341
+ <h3>⚠️ DWG File Detected - Conversion Required</h3>
342
+ <p><strong>Your file "P203_-_ARCH_STR_Fahad_Alqahtani_11-11-2024.dwg" needs to be converted to DXF format first.</strong></p>
343
+
344
+ <h4>Method 1: Online Conversion (Fastest)</h4>
345
+ <ol>
346
+ <li>Visit: <a href="https://www.aconvert.com/document/dwg-to-dxf/" target="_blank">https://www.aconvert.com/document/dwg-to-dxf/</a></li>
347
+ <li>Upload your DWG file</li>
348
+ <li>Click "Convert Now!"</li>
349
+ <li>Download the DXF file and upload it here</li>
350
+ </ol>
351
+
352
+ <h4>Method 2: Using AutoCAD/DraftSight</h4>
353
+ <ol>
354
+ <li>Open your DWG file in AutoCAD or DraftSight</li>
355
+ <li>Go to File → Save As</li>
356
+ <li>Select "AutoCAD DXF (*.dxf)" as file type</li>
357
+ <li>Save and upload the DXF file here</li>
358
+ </ol>
359
+
360
+ <h4>Method 3: Command Line (Linux/Mac)</h4>
361
+ <pre><code>pip install ezdxf
362
+ # Then use our converter script (provided below)</code></pre>
363
+ </div>
364
+
365
+ <div class="file-upload" onclick="document.getElementById('fileInput').click()">
366
+ <div style="font-size: 3em; margin-bottom: 10px;">📄</div>
367
+ <p style="font-size: 1.2em; margin-bottom: 10px;">Click to upload DXF file</p>
368
+ <p style="color: #666;">Supported formats: DXF (R12, R2000, R2004, R2007, R2010, R2013, R2018)</p>
369
+ <input type="file" id="fileInput" accept=".dxf" onchange="handleFileSelect(event)">
370
+ </div>
371
+
372
+ <div id="fileInfo" style="display: none; margin-top: 20px;">
373
+ <div class="status-box">
374
+ <strong>Selected file:</strong> <span id="fileName"></span><br>
375
+ <strong>Size:</strong> <span id="fileSize"></span>
376
+ </div>
377
+ <button class="btn" onclick="analyzeDrawing()">🔍 Analyze Drawing</button>
378
+ </div>
379
+ </div>
380
+
381
+ <!-- Analysis Results -->
382
+ <div id="resultsSection" class="section" style="display: none;">
383
+ <h2>📊 Analysis Results</h2>
384
+
385
+ <div class="tabs">
386
+ <div class="tab active" onclick="switchTab('summary')">Summary</div>
387
+ <div class="tab" onclick="switchTab('errors')">Errors</div>
388
+ <div class="tab" onclick="switchTab('layers')">Layers</div>
389
+ <div class="tab" onclick="switchTab('corrections')">Corrections</div>
390
+ </div>
391
+
392
+ <!-- Summary Tab -->
393
+ <div id="summary-tab" class="tab-content active">
394
+ <div class="stats-grid" id="statsGrid"></div>
395
+ <div id="summaryContent"></div>
396
+ </div>
397
+
398
+ <!-- Errors Tab -->
399
+ <div id="errors-tab" class="tab-content">
400
+ <div id="errorsContent"></div>
401
+ </div>
402
+
403
+ <!-- Layers Tab -->
404
+ <div id="layers-tab" class="tab-content">
405
+ <div id="layersContent"></div>
406
+ </div>
407
+
408
+ <!-- Corrections Tab -->
409
+ <div id="corrections-tab" class="tab-content">
410
+ <div id="correctionsContent"></div>
411
+ </div>
412
+
413
+ <div style="margin-top: 30px;">
414
+ <button class="btn" onclick="downloadReport('markdown')">📄 Download MD Report</button>
415
+ <button class="btn" onclick="downloadReport('json')">📦 Download JSON Report</button>
416
+ <button class="btn" onclick="applyCorrections()" id="applyBtn">🔧 Apply Auto-Corrections</button>
417
+ </div>
418
+ </div>
419
+
420
+ <!-- Processing Status -->
421
+ <div id="processingStatus" style="display: none;">
422
+ <div class="status-box">
423
+ <h3>⏳ Processing...</h3>
424
+ <div class="progress-bar">
425
+ <div class="progress-fill" id="progressFill" style="width: 0%">0%</div>
426
+ </div>
427
+ <p id="progressText">Initializing...</p>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ </div>
432
+
433
+ <script>
434
+ let currentFile = null;
435
+ let analysisResults = null;
436
+
437
+ function handleFileSelect(event) {
438
+ currentFile = event.target.files[0];
439
+ if (currentFile) {
440
+ document.getElementById('fileName').textContent = currentFile.name;
441
+ document.getElementById('fileSize').textContent = formatFileSize(currentFile.size);
442
+ document.getElementById('fileInfo').style.display = 'block';
443
+
444
+ // Check if DXF
445
+ if (!currentFile.name.toLowerCase().endsWith('.dxf')) {
446
+ alert('Please upload a DXF file. If you have a DWG file, please convert it first using the instructions above.');
447
+ currentFile = null;
448
+ document.getElementById('fileInfo').style.display = 'none';
449
+ }
450
+ }
451
+ }
452
+
453
+ function formatFileSize(bytes) {
454
+ if (bytes < 1024) return bytes + ' B';
455
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
456
+ return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
457
+ }
458
+
459
+ async function analyzeDrawing() {
460
+ if (!currentFile) return;
461
+
462
+ // Show processing status
463
+ document.getElementById('processingStatus').style.display = 'block';
464
+ document.getElementById('resultsSection').style.display = 'none';
465
+
466
+ updateProgress(10, 'Reading file...');
467
+
468
+ // Read file
469
+ const formData = new FormData();
470
+ formData.append('file', currentFile);
471
+
472
+ updateProgress(30, 'Analyzing drawing structure...');
473
+
474
+ try {
475
+ // In real implementation, this would call the backend API
476
+ // For demo purposes, we'll simulate the analysis
477
+ await simulateAnalysis();
478
+
479
+ updateProgress(100, 'Complete!');
480
+
481
+ setTimeout(() => {
482
+ document.getElementById('processingStatus').style.display = 'none';
483
+ displayResults();
484
+ }, 500);
485
+
486
+ } catch (error) {
487
+ document.getElementById('processingStatus').innerHTML =
488
+ '<div class="error-box"><strong>Error:</strong> ' + error.message + '</div>';
489
+ }
490
+ }
491
+
492
+ function updateProgress(percent, text) {
493
+ document.getElementById('progressFill').style.width = percent + '%';
494
+ document.getElementById('progressFill').textContent = percent + '%';
495
+ document.getElementById('progressText').textContent = text;
496
+ }
497
+
498
+ async function simulateAnalysis() {
499
+ // Simulate processing time
500
+ await new Promise(resolve => setTimeout(resolve, 1000));
501
+ updateProgress(50, 'Detecting errors...');
502
+ await new Promise(resolve => setTimeout(resolve, 1000));
503
+ updateProgress(70, 'Generating correction plan...');
504
+ await new Promise(resolve => setTimeout(resolve, 1000));
505
+ updateProgress(90, 'Finalizing report...');
506
+ await new Promise(resolve => setTimeout(resolve, 500));
507
+
508
+ // Mock results
509
+ analysisResults = {
510
+ metadata: {
511
+ filename: currentFile.name,
512
+ dxfversion: 'AC1027 (R2013)',
513
+ total_entities: 2847,
514
+ total_layers: 42
515
+ },
516
+ errors: [
517
+ {
518
+ id: 'LAYER_WALL-1_NAMING',
519
+ category: 'layer',
520
+ severity: 'medium',
521
+ description: 'Layer "WALL-1" does not follow AIA naming convention',
522
+ correction: 'Rename to AIA format (e.g., A-WALL-FULL)',
523
+ auto_correctable: false
524
+ },
525
+ {
526
+ id: 'DIM_SCALE_INCONSISTENT',
527
+ category: 'dimension',
528
+ severity: 'medium',
529
+ description: 'Inconsistent dimension scales found: {0.5, 1.0, 2.0}',
530
+ correction: 'Standardize dimension scale across all dimensions',
531
+ auto_correctable: true
532
+ },
533
+ {
534
+ id: 'GEOM_LINE_ZEROLENGTH_A45F',
535
+ category: 'geometry',
536
+ severity: 'high',
537
+ description: 'Zero-length line detected on layer A-WALL',
538
+ correction: 'Delete zero-length line',
539
+ auto_correctable: true
540
+ }
541
+ ],
542
+ summary: {
543
+ total_errors: 23,
544
+ critical: 2,
545
+ high: 5,
546
+ medium: 11,
547
+ low: 5,
548
+ auto_correctable: 14
549
+ }
550
+ };
551
+ }
552
+
553
+ function displayResults() {
554
+ document.getElementById('resultsSection').style.display = 'block';
555
+
556
+ // Display stats
557
+ const statsHtml = `
558
+ <div class="stat-card">
559
+ <div class="number">${analysisResults.summary.total_errors}</div>
560
+ <div class="label">Total Errors</div>
561
+ </div>
562
+ <div class="stat-card" style="background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%);">
563
+ <div class="number">${analysisResults.summary.critical}</div>
564
+ <div class="label">Critical</div>
565
+ </div>
566
+ <div class="stat-card" style="background: linear-gradient(135deg, #ed8936 0%, #c05621 100%);">
567
+ <div class="number">${analysisResults.summary.high}</div>
568
+ <div class="label">High Priority</div>
569
+ </div>
570
+ <div class="stat-card" style="background: linear-gradient(135deg, #48bb78 0%, #2f855a 100%);">
571
+ <div class="number">${analysisResults.summary.auto_correctable}</div>
572
+ <div class="label">Auto-Correctable</div>
573
+ </div>
574
+ `;
575
+ document.getElementById('statsGrid').innerHTML = statsHtml;
576
+
577
+ // Display errors
578
+ let errorsHtml = '<div class="error-list">';
579
+ analysisResults.errors.forEach(error => {
580
+ errorsHtml += `
581
+ <div class="error-item ${error.severity}">
582
+ <div class="error-header">
583
+ <div class="error-title">${error.description}</div>
584
+ <span class="severity-badge ${error.severity}">${error.severity.toUpperCase()}</span>
585
+ </div>
586
+ <p><strong>Category:</strong> ${error.category}</p>
587
+ <p><strong>Correction:</strong> ${error.correction}</p>
588
+ <p><strong>Auto-correctable:</strong> ${error.auto_correctable ? '✓ Yes' : '✗ No'}</p>
589
+ </div>
590
+ `;
591
+ });
592
+ errorsHtml += '</div>';
593
+ document.getElementById('errorsContent').innerHTML = errorsHtml;
594
+ }
595
+
596
+ function switchTab(tabName) {
597
+ // Hide all tabs
598
+ document.querySelectorAll('.tab-content').forEach(tab => {
599
+ tab.classList.remove('active');
600
+ });
601
+ document.querySelectorAll('.tab').forEach(tab => {
602
+ tab.classList.remove('active');
603
+ });
604
+
605
+ // Show selected tab
606
+ document.getElementById(tabName + '-tab').classList.add('active');
607
+ event.target.classList.add('active');
608
+ }
609
+
610
+ function downloadReport(format) {
611
+ alert('Report download would trigger here for format: ' + format);
612
+ }
613
+
614
+ function applyCorrections() {
615
+ if (confirm('Apply ' + analysisResults.summary.auto_correctable + ' automatic corrections?')) {
616
+ alert('Corrections would be applied here');
617
+ }
618
+ }
619
+ </script>
620
+ </body>
621
+ </html>
622
+ """
623
+
624
+
625
+ def run_web_interface(port=8000):
626
+ """Run the web interface"""
627
+
628
+ class RequestHandler(http.server.SimpleHTTPRequestHandler):
629
+ def do_GET(self):
630
+ if self.path == '/' or self.path == '/index.html':
631
+ self.send_response(200)
632
+ self.send_header('Content-type', 'text/html')
633
+ self.end_headers()
634
+ self.wfile.write(HTML_TEMPLATE.encode())
635
+ else:
636
+ super().do_GET()
637
+
638
+ with socketserver.TCPServer(("", port), RequestHandler) as httpd:
639
+ print(f"Web interface running at http://localhost:{port}")
640
+ print("Press Ctrl+C to stop")
641
+ httpd.serve_forever()
642
+
643
+
644
+ if __name__ == "__main__":
645
+ run_web_interface()