AIDA / app /ai /utils /draft_html_generator.py
destinyebuka's picture
fyp
7c7cb8d
# app/ai/utils/draft_html_generator.py
# Generate beautiful HTML UI for listing drafts
def generate_draft_html(draft: dict) -> str:
"""
Generate a beautiful HTML UI for the listing draft.
This HTML is returned to frontend and displayed directly.
Args:
draft: Dictionary with listing data
Returns:
Complete HTML string for the draft preview
"""
# Extract data
title = draft.get("title", "Untitled")
description = draft.get("description", "No description")
location = draft.get("location", "N/A")
bedrooms = draft.get("bedrooms", "?")
bathrooms = draft.get("bathrooms", "?")
price = draft.get("price", "?")
currency = draft.get("currency", "N/A")
price_type = draft.get("price_type", "N/A")
amenities = draft.get("amenities", [])
requirements = draft.get("requirements", "")
images = draft.get("images", [])
# Format amenities with icons
amenity_icons = {
"wifi": "πŸ“Ά",
"parking": "πŸ…ΏοΈ",
"furnished": "πŸ›‹οΈ",
"washing machine": "🧼",
"dryer": "πŸ”„",
"balcony": "πŸŒ†",
"pool": "🏊",
"gym": "πŸ’ͺ",
"garden": "🌿",
"air conditioning": "❄️",
"kitchen": "🍳",
"ac": "❄️",
"washer": "🧼",
}
amenities_html = ""
if amenities:
amenities_html = '<div class="amenities-list">'
for amenity in amenities:
icon = amenity_icons.get(amenity.lower(), "βœ“")
amenities_html += f'<span class="amenity-badge">{icon} {amenity}</span>'
amenities_html += '</div>'
# Format images
images_html = ""
if images:
images_html = '<div class="images-gallery">'
for idx, img_url in enumerate(images[:3]): # Show first 3 images
images_html += f'''
<div class="image-item">
<img src="{img_url}" alt="Property image {idx+1}" loading="lazy">
</div>
'''
if len(images) > 3:
images_html += f'<div class="image-count">+{len(images)-3} more</div>'
images_html += '</div>'
# Requirements section
requirements_html = ""
if requirements:
requirements_html = f'''
<div class="requirements-section">
<h4>πŸ“ Requirements</h4>
<p>{requirements}</p>
</div>
'''
# Complete HTML
html = f'''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title} - Lojiz</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;
}}
.draft-container {{
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
animation: slideIn 0.3s ease-out;
}}
@keyframes slideIn {{
from {{
opacity: 0;
transform: translateY(20px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
.draft-header {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px 20px;
text-align: center;
}}
.draft-header h1 {{
font-size: 24px;
margin-bottom: 10px;
word-wrap: break-word;
}}
.draft-header .status {{
background: rgba(255,255,255,0.3);
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
display: inline-block;
margin-top: 10px;
}}
.draft-content {{
padding: 30px 20px;
}}
.description {{
color: #555;
line-height: 1.6;
margin-bottom: 25px;
font-size: 15px;
text-align: justify;
}}
.key-details {{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 25px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}}
.detail-item {{
display: flex;
align-items: flex-start;
gap: 12px;
}}
.detail-icon {{
font-size: 24px;
min-width: 30px;
}}
.detail-info h4 {{
color: #666;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}}
.detail-info p {{
color: #333;
font-size: 16px;
font-weight: 600;
}}
.amenities-list {{
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 25px;
}}
.amenity-badge {{
background: #e8f0ff;
color: #667eea;
padding: 8px 14px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
}}
.images-gallery {{
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 25px;
}}
.image-item {{
position: relative;
overflow: hidden;
border-radius: 8px;
aspect-ratio: 1;
}}
.image-item img {{
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}}
.image-item:hover img {{
transform: scale(1.05);
}}
.image-count {{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(102, 126, 234, 0.9);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 16px;
}}
.requirements-section {{
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
border-radius: 4px;
margin-bottom: 25px;
}}
.requirements-section h4 {{
color: #856404;
margin-bottom: 8px;
font-size: 14px;
}}
.requirements-section p {{
color: #856404;
font-size: 14px;
line-height: 1.5;
}}
.price-section {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
margin-bottom: 25px;
}}
.price-section .label {{
font-size: 12px;
text-transform: uppercase;
opacity: 0.9;
letter-spacing: 0.5px;
}}
.price-section .amount {{
font-size: 28px;
font-weight: 700;
margin: 8px 0;
}}
.price-section .frequency {{
font-size: 14px;
opacity: 0.9;
}}
.action-buttons {{
display: flex;
gap: 12px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}}
.btn {{
flex: 1;
padding: 12px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}}
.btn-publish {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}}
.btn-publish:hover {{
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}}
.btn-edit {{
background: #f0f0f0;
color: #333;
}}
.btn-edit:hover {{
background: #e0e0e0;
}}
.btn-discard {{
background: #ff6b6b;
color: white;
}}
.btn-discard:hover {{
background: #ee5a52;
}}
.badge {{
display: inline-block;
background: #667eea;
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
margin-left: 10px;
}}
@media (max-width: 480px) {{
.draft-container {{
border-radius: 0;
}}
.key-details {{
grid-template-columns: 1fr;
}}
.price-section .amount {{
font-size: 24px;
}}
.action-buttons {{
flex-direction: column;
}}
}}
</style>
</head>
<body>
<div class="draft-container">
<!-- Header -->
<div class="draft-header">
<h1>{title}</h1>
<span class="status">πŸ“‹ Draft Preview</span>
</div>
<!-- Content -->
<div class="draft-content">
<!-- Description -->
<p class="description">{description}</p>
<!-- Key Details -->
<div class="key-details">
<div class="detail-item">
<div class="detail-icon">πŸ“</div>
<div class="detail-info">
<h4>Location</h4>
<p>{location}</p>
</div>
</div>
<div class="detail-item">
<div class="detail-icon">πŸ›οΈ</div>
<div class="detail-info">
<h4>Bedrooms</h4>
<p>{bedrooms}</p>
</div>
</div>
<div class="detail-item">
<div class="detail-icon">🚿</div>
<div class="detail-info">
<h4>Bathrooms</h4>
<p>{bathrooms}</p>
</div>
</div>
<div class="detail-item">
<div class="detail-icon">🏠</div>
<div class="detail-info">
<h4>Type</h4>
<p>{draft.get('listing_type', 'N/A').title()}</p>
</div>
</div>
</div>
<!-- Amenities -->
{amenities_html}
<!-- Images -->
{images_html}
<!-- Requirements -->
{requirements_html}
<!-- Price Section -->
<div class="price-section">
<div class="label">πŸ’° Price</div>
<div class="amount">{currency} {price}</div>
<div class="frequency">per {price_type}</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<button class="btn btn-publish" onclick="handlePublish()">βœ“ Publish</button>
<button class="btn btn-edit" onclick="handleEdit()">✏️ Edit</button>
<button class="btn btn-discard" onclick="handleDiscard()">βœ• Discard</button>
</div>
</div>
</div>
<script>
function handlePublish() {{
window.parent.postMessage({{
action: 'publish',
type: 'listing-action'
}}, '*');
}}
function handleEdit() {{
window.parent.postMessage({{
action: 'edit',
type: 'listing-action'
}}, '*');
}}
function handleDiscard() {{
window.parent.postMessage({{
action: 'discard',
type: 'listing-action'
}}, '*');
}}
</script>
</body>
</html>
'''
return html