Spaces:
Paused
Paused
File size: 6,909 Bytes
88bdcff 0699c5f 88bdcff 3b08f11 88bdcff 3b08f11 88bdcff 3b08f11 88bdcff |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
"""Reusable UI components for FDAM AI Pipeline.
Provides helper functions for common Gradio UI patterns.
"""
from typing import Optional
from .state import SessionState, AssessmentHistory
def create_validation_message(
is_valid: bool,
errors: list[str],
success_msg: str = "All required fields are complete."
) -> str:
"""Create a formatted validation message.
Args:
is_valid: Whether validation passed
errors: List of validation errors
success_msg: Message to show on success
Returns:
Formatted message string
"""
if is_valid:
return f"✓ {success_msg}"
else:
error_list = "\n".join(f"• {e}" for e in errors)
return f"⚠ Please fix the following:\n{error_list}"
def create_progress_html(
current_stage: int,
total_stages: int,
stage_name: str,
percentage: Optional[float] = None
) -> str:
"""Create HTML for progress display during processing.
Args:
current_stage: Current stage number (1-indexed)
total_stages: Total number of stages
stage_name: Name of current stage
percentage: Optional percentage override
Returns:
HTML string for progress display
"""
if percentage is None:
percentage = (current_stage / total_stages) * 100
return f"""
<div style="margin: 10px 0;">
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span><strong>Stage {current_stage}/{total_stages}:</strong> {stage_name}</span>
<span>{percentage:.0f}%</span>
</div>
<div style="background: #e0e0e0; border-radius: 4px; height: 20px; overflow: hidden;">
<div style="background: #4CAF50; height: 100%; width: {percentage}%; transition: width 0.3s;"></div>
</div>
</div>
"""
def create_history_dropdown_choices(history: AssessmentHistory) -> list[tuple[str, str]]:
"""Create choices for history dropdown.
Args:
history: Assessment history object
Returns:
List of (label, value) tuples for dropdown
"""
choices = [("-- New Assessment --", "new")]
for item in history.get_history_items():
label = item["name"]
if item["has_results"]:
label += " ✓"
# Format date nicely
try:
from datetime import datetime
dt = datetime.fromisoformat(item["updated"])
date_str = dt.strftime("%m/%d %H:%M")
label += f" ({date_str})"
except Exception:
pass
choices.append((label, item["id"]))
return choices
def create_tab_status_indicator(
tab_number: int,
is_complete: bool,
is_current: bool = False
) -> str:
"""Create a status indicator for tab navigation.
Args:
tab_number: Tab number (1-5)
is_complete: Whether tab is complete
is_current: Whether this is the current tab
Returns:
Status indicator string
"""
if is_complete:
return f"✓ Tab {tab_number}"
elif is_current:
return f"● Tab {tab_number}"
else:
return f"○ Tab {tab_number}"
def create_stats_dict(session: SessionState) -> dict:
"""Create statistics dictionary for display.
Args:
session: Current session state
Returns:
Dictionary of statistics
"""
r = session.room
total_area = r.length_ft * r.width_ft
total_volume = total_area * r.ceiling_height_ft
return {
"room_name": r.name or "Not set",
"images": len(session.images),
"total_floor_area_sf": f"{total_area:,.0f}",
"total_volume_cf": f"{total_volume:,.0f}",
"facility_classification": r.facility_classification or "Not set",
"construction_era": r.construction_era or "Not set",
}
def format_validation_errors_html(errors: list[str]) -> str:
"""Format validation errors as HTML list.
Args:
errors: List of error messages
Returns:
HTML string
"""
if not errors:
return ""
items = "".join(f"<li>{e}</li>" for e in errors)
return f"""
<div style="background: #ffebee; border: 1px solid #ef5350; border-radius: 4px; padding: 10px; margin: 10px 0;">
<strong style="color: #c62828;">Please fix the following issues:</strong>
<ul style="margin: 5px 0 0 0; padding-left: 20px; color: #c62828;">
{items}
</ul>
</div>
"""
def format_success_html(message: str) -> str:
"""Format success message as HTML.
Args:
message: Success message
Returns:
HTML string
"""
return f"""
<div style="background: #e8f5e9; border: 1px solid #66bb6a; border-radius: 4px; padding: 10px; margin: 10px 0;">
<span style="color: #2e7d32;">✓ {message}</span>
</div>
"""
def format_warning_html(message: str) -> str:
"""Format warning message as HTML.
Args:
message: Warning message
Returns:
HTML string
"""
return f"""
<div style="background: #fff3e0; border: 1px solid #ffb74d; border-radius: 4px; padding: 10px; margin: 10px 0;">
<span style="color: #e65100;">⚠ {message}</span>
</div>
"""
def format_info_html(message: str) -> str:
"""Format info message as HTML.
Args:
message: Info message
Returns:
HTML string
"""
return f"""
<div style="background: #e3f2fd; border: 1px solid #64b5f6; border-radius: 4px; padding: 10px; margin: 10px 0;">
<span style="color: #1565c0;">ℹ {message}</span>
</div>
"""
# Image handling helpers (images stored separately from localStorage)
class ImageStore:
"""In-memory store for uploaded images.
Images are too large for localStorage, so they're kept in memory
and referenced by ID. Users are prompted to re-upload when resuming.
"""
def __init__(self):
self._images: dict[str, bytes] = {}
def store(self, image_id: str, image_bytes: bytes) -> None:
"""Store image bytes by ID."""
self._images[image_id] = image_bytes
def get(self, image_id: str) -> Optional[bytes]:
"""Get image bytes by ID."""
return self._images.get(image_id)
def remove(self, image_id: str) -> None:
"""Remove image by ID."""
self._images.pop(image_id, None)
def clear(self) -> None:
"""Clear all stored images."""
self._images.clear()
def get_missing_ids(self, expected_ids: list[str]) -> list[str]:
"""Get list of expected image IDs that are missing."""
return [id for id in expected_ids if id not in self._images]
def has_all(self, expected_ids: list[str]) -> bool:
"""Check if all expected images are present."""
return all(id in self._images for id in expected_ids)
# Global image store instance
image_store = ImageStore()
|