|
|
""" |
|
|
Visualization Display Component |
|
|
Displays AI-generated architectural sketches with metadata and download functionality |
|
|
""" |
|
|
|
|
|
import base64 |
|
|
import logging |
|
|
import tempfile |
|
|
import os |
|
|
from pathlib import Path |
|
|
from typing import Optional, Tuple |
|
|
from datetime import datetime |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
def format_visualization_display(visualization_data) -> Tuple[str, Optional[str], Optional[str]]: |
|
|
""" |
|
|
Format visualization data for display with image and metadata |
|
|
|
|
|
Args: |
|
|
visualization_data: Visualization data (dict or VisualizationData object) |
|
|
|
|
|
Returns: |
|
|
Tuple of (HTML string, image path for gr.Image component, download path) |
|
|
""" |
|
|
|
|
|
if not visualization_data: |
|
|
return _format_no_visualization(), None, None |
|
|
|
|
|
|
|
|
if isinstance(visualization_data, dict): |
|
|
success = visualization_data.get('success', True) |
|
|
if not success: |
|
|
error = visualization_data.get('error', {}) |
|
|
return _format_visualization_error(error), None, None |
|
|
|
|
|
image_base64 = visualization_data.get('image_base64', '') |
|
|
prompt_used = visualization_data.get('prompt_used', '') |
|
|
model_version = visualization_data.get('model_version', 'Unknown') |
|
|
generation_timestamp = visualization_data.get('generation_timestamp', 'Unknown') |
|
|
image_format = visualization_data.get('image_format', 'PNG') |
|
|
resolution = visualization_data.get('resolution', 'Unknown') |
|
|
features_included = visualization_data.get('features_included', []) |
|
|
else: |
|
|
image_base64 = getattr(visualization_data, 'image_base64', '') |
|
|
prompt_used = getattr(visualization_data, 'prompt_used', '') |
|
|
model_version = getattr(visualization_data, 'model_version', 'Unknown') |
|
|
generation_timestamp = getattr(visualization_data, 'generation_timestamp', 'Unknown') |
|
|
image_format = getattr(visualization_data, 'image_format', 'PNG') |
|
|
resolution = getattr(visualization_data, 'resolution', 'Unknown') |
|
|
features_included = getattr(visualization_data, 'features_included', []) |
|
|
|
|
|
|
|
|
if not image_base64: |
|
|
return _format_no_visualization(), None, None |
|
|
|
|
|
|
|
|
image_path = None |
|
|
download_path = None |
|
|
try: |
|
|
|
|
|
image_bytes = base64.b64decode(image_base64) |
|
|
|
|
|
|
|
|
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png', mode='wb') |
|
|
temp_file.write(image_bytes) |
|
|
temp_file.close() |
|
|
image_path = temp_file.name |
|
|
|
|
|
|
|
|
download_dir = Path("gradio-ui/downloads") |
|
|
download_dir.mkdir(exist_ok=True) |
|
|
|
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") |
|
|
download_filename = f"construction_visualization_{timestamp}.png" |
|
|
download_path = download_dir / download_filename |
|
|
|
|
|
|
|
|
with open(download_path, 'wb') as f: |
|
|
f.write(image_bytes) |
|
|
|
|
|
logger.info(f"Created temporary image file: {image_path}") |
|
|
logger.info(f"Created downloadable image file: {download_path}") |
|
|
except Exception as e: |
|
|
logger.error(f"Error converting base64 to image file: {e}") |
|
|
return _format_no_visualization(), None, None |
|
|
|
|
|
|
|
|
html = f""" |
|
|
<div style="padding: 20px;"> |
|
|
<h2 style="margin-top: 0; color: #ffffff !important; font-weight: 700;">π¨ Architectural Visualization</h2> |
|
|
|
|
|
<div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border: 1px solid #e2e8f0;"> |
|
|
<h3 style="color: #1e40af !important; margin-top: 0; font-weight: 600;">π Generation Details</h3> |
|
|
<p style="color: #0f172a !important; margin: 8px 0;"><strong style="color: #0f172a !important;">Model:</strong> {model_version}</p> |
|
|
<p style="color: #0f172a !important; margin: 8px 0;"><strong style="color: #0f172a !important;">Generated:</strong> {generation_timestamp}</p> |
|
|
<p style="color: #0f172a !important; margin: 8px 0;"><strong style="color: #0f172a !important;">Format:</strong> {image_format}</p> |
|
|
<p style="color: #0f172a !important; margin: 8px 0;"><strong style="color: #0f172a !important;">Resolution:</strong> {resolution}</p> |
|
|
<p style="color: #0f172a !important; margin: 8px 0;"><strong style="color: #0f172a !important;">Download:</strong> Use the download button below the image to save</p> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
if features_included: |
|
|
html += """ |
|
|
<div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; border-left: 5px solid #10b981; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border: 1px solid #d1fae5;"> |
|
|
<h3 style="color: #059669 !important; margin-top: 0; font-weight: 600;">π‘οΈ Disaster-Resistant Features</h3> |
|
|
<ul style="line-height: 1.8; color: #0f172a !important;"> |
|
|
""" |
|
|
for feature in features_included: |
|
|
html += f"<li style='color: #0f172a !important;'>{feature}</li>" |
|
|
html += """ |
|
|
</ul> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
if prompt_used: |
|
|
html += f""" |
|
|
<div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border: 1px solid #e2e8f0;"> |
|
|
<h3 style="color: #6366f1 !important; margin-top: 0; font-weight: 600;">π¬ Generation Prompt</h3> |
|
|
<p style="color: #0f172a !important; line-height: 1.6; font-style: italic; background: #f8fafc; padding: 15px; border-radius: 4px; border-left: 3px solid #6366f1;"> |
|
|
{prompt_used} |
|
|
</p> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
html += """ |
|
|
<div style="background: white; padding: 15px; border-radius: 8px; margin-top: 20px; font-size: 0.9em; border: 1px solid #e2e8f0;"> |
|
|
<p style="margin: 5px 0; color: #6b7280 !important; font-style: italic;"> |
|
|
βΉοΈ This visualization is AI-generated and represents a conceptual design. |
|
|
Consult with licensed architects and engineers for actual construction plans. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
return html, image_path, str(download_path) if download_path else None |
|
|
|
|
|
|
|
|
def _format_no_visualization() -> str: |
|
|
"""Format message when no visualization is available""" |
|
|
return """ |
|
|
<div style="padding: 20px;"> |
|
|
<div style="background: white; padding: 30px; border-radius: 8px; text-align: center; border: 2px dashed #cbd5e1;"> |
|
|
<h3 style="color: #6b7280 !important; margin-top: 0;">π¨ No Visualization Available</h3> |
|
|
<p style="color: #6b7280 !important;"> |
|
|
The visualization agent did not generate an image for this construction plan. |
|
|
This may occur if the visualization service is unavailable or if there was an error during generation. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
def _format_visualization_error(error) -> str: |
|
|
"""Format error message for visualization failures""" |
|
|
if isinstance(error, dict): |
|
|
code = error.get('code', 'UNKNOWN') |
|
|
message = error.get('message', 'An unknown error occurred') |
|
|
retry_possible = error.get('retry_possible', False) |
|
|
else: |
|
|
code = getattr(error, 'code', 'UNKNOWN') |
|
|
message = getattr(error, 'message', 'An unknown error occurred') |
|
|
retry_possible = getattr(error, 'retry_possible', False) |
|
|
|
|
|
return f""" |
|
|
<div style="padding: 20px;"> |
|
|
<div style="background: #fef2f2; padding: 20px; border-radius: 8px; border: 2px solid #dc2626;"> |
|
|
<h3 style="color: #dc2626 !important; margin-top: 0;">β Visualization Generation Failed</h3> |
|
|
<p style="color: #0f172a !important;"><strong style="color: #0f172a !important;">Error Code:</strong> {code}</p> |
|
|
<p style="color: #0f172a !important;"><strong style="color: #0f172a !important;">Message:</strong> {message}</p> |
|
|
{f"<p style='color: #0f172a !important;'><strong style='color: #0f172a !important;'>Retry Possible:</strong> {'Yes' if retry_possible else 'No'}</p>" if retry_possible is not None else ''} |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|