Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -39,20 +39,39 @@ if "token_usage" not in st.session_state:
|
|
| 39 |
|
| 40 |
# Function to encode image to base64
|
| 41 |
def encode_image(image):
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
# Function to fix formatting issues in text
|
| 58 |
def fix_formatting(text):
|
|
@@ -72,6 +91,9 @@ def analyze_home_photos(images, timeframe, additional_details, api_key):
|
|
| 72 |
if not api_key:
|
| 73 |
return "Error: API Key is required for analysis."
|
| 74 |
|
|
|
|
|
|
|
|
|
|
| 75 |
headers = {
|
| 76 |
"Content-Type": "application/json",
|
| 77 |
"Authorization": f"Bearer {api_key}"
|
|
@@ -90,15 +112,22 @@ def analyze_home_photos(images, timeframe, additional_details, api_key):
|
|
| 90 |
]
|
| 91 |
|
| 92 |
# Add images to the message with proper format for vision API
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
# Enhanced system prompt for more specific and actionable recommendations
|
| 104 |
system_prompt = """You are an expert real estate advisor with deep knowledge of home selling strategies. You analyze home photos to provide EXTREMELY SPECIFIC, actionable recommendations that will maximize the property's selling price.
|
|
@@ -224,7 +253,7 @@ For example, if selling within 1-3 months:
|
|
| 224 |
error_text += f" - {response.text[:200]}"
|
| 225 |
return f"Error: {error_text}. Please check your API key and try again."
|
| 226 |
except Exception as e:
|
| 227 |
-
return f"Error: {str(e)}"
|
| 228 |
|
| 229 |
# Calculate estimated cost
|
| 230 |
def calculate_cost(token_usage):
|
|
@@ -476,26 +505,52 @@ with col1:
|
|
| 476 |
# Process uploaded images
|
| 477 |
if uploaded_files:
|
| 478 |
images = []
|
|
|
|
|
|
|
|
|
|
| 479 |
for file in uploaded_files:
|
| 480 |
try:
|
| 481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
# Resize large images to reduce token usage
|
| 483 |
max_size = (1024, 1024)
|
| 484 |
if image.width > max_size[0] or image.height > max_size[1]:
|
| 485 |
image.thumbnail(max_size, Image.LANCZOS)
|
|
|
|
| 486 |
images.append(image)
|
|
|
|
| 487 |
except Exception as e:
|
| 488 |
-
|
| 489 |
|
|
|
|
| 490 |
st.session_state["uploaded_images"] = images
|
| 491 |
|
| 492 |
-
# Display uploaded images
|
| 493 |
if images:
|
| 494 |
-
st.write(f"**{len(images)} images uploaded**")
|
| 495 |
image_cols = st.columns(min(3, len(images)))
|
| 496 |
for i, img in enumerate(images):
|
| 497 |
with image_cols[i % min(3, len(images))]:
|
| 498 |
-
st.image(img, width=150, caption=f"Image {i+1}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
# Custom subheader with background
|
| 501 |
st.markdown('<div class="custom-subheader">🏠 Selling Timeline & Details</div>', unsafe_allow_html=True)
|
|
@@ -548,42 +603,50 @@ with col2:
|
|
| 548 |
if analyze_button or "analysis_result" in st.session_state:
|
| 549 |
# Process analysis if button pressed
|
| 550 |
if analyze_button:
|
| 551 |
-
#
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
|
| 588 |
# Display results
|
| 589 |
if "analysis_result" in st.session_state:
|
|
|
|
| 39 |
|
| 40 |
# Function to encode image to base64
|
| 41 |
def encode_image(image):
|
| 42 |
+
try:
|
| 43 |
+
# Convert RGBA to RGB if needed
|
| 44 |
+
if image.mode == 'RGBA':
|
| 45 |
+
background = Image.new('RGB', image.size, (255, 255, 255))
|
| 46 |
+
background.paste(image, (0, 0), image)
|
| 47 |
+
image = background
|
| 48 |
+
elif image.mode not in ['RGB', 'L']:
|
| 49 |
+
# Convert any other mode to RGB
|
| 50 |
+
image = image.convert('RGB')
|
| 51 |
+
|
| 52 |
+
# Verify the image has valid dimensions
|
| 53 |
+
if image.width <= 0 or image.height <= 0:
|
| 54 |
+
raise ValueError("Image has invalid dimensions: width or height ≤ 0")
|
| 55 |
+
|
| 56 |
+
# Resize large images to reduce token usage
|
| 57 |
+
max_size = (1024, 1024)
|
| 58 |
+
if image.width > max_size[0] or image.height > max_size[1]:
|
| 59 |
+
image.thumbnail(max_size, Image.LANCZOS)
|
| 60 |
+
|
| 61 |
+
# Save to buffer with error handling
|
| 62 |
+
buffered = BytesIO()
|
| 63 |
+
image.save(buffered, format="JPEG", quality=85)
|
| 64 |
+
if buffered.getvalue() == b'':
|
| 65 |
+
raise ValueError("Generated empty image data")
|
| 66 |
+
|
| 67 |
+
# Encode to base64
|
| 68 |
+
encoded = base64.b64encode(buffered.getvalue()).decode('utf-8')
|
| 69 |
+
if not encoded:
|
| 70 |
+
raise ValueError("Base64 encoding produced empty result")
|
| 71 |
+
|
| 72 |
+
return encoded
|
| 73 |
+
except Exception as e:
|
| 74 |
+
raise Exception(f"Image encoding failed: {str(e)}")
|
| 75 |
|
| 76 |
# Function to fix formatting issues in text
|
| 77 |
def fix_formatting(text):
|
|
|
|
| 91 |
if not api_key:
|
| 92 |
return "Error: API Key is required for analysis."
|
| 93 |
|
| 94 |
+
if not images or len(images) == 0:
|
| 95 |
+
return "Error: No valid images provided for analysis. Please upload at least one image of your home."
|
| 96 |
+
|
| 97 |
headers = {
|
| 98 |
"Content-Type": "application/json",
|
| 99 |
"Authorization": f"Bearer {api_key}"
|
|
|
|
| 112 |
]
|
| 113 |
|
| 114 |
# Add images to the message with proper format for vision API
|
| 115 |
+
try:
|
| 116 |
+
for i, img in enumerate(images):
|
| 117 |
+
try:
|
| 118 |
+
base64_img = encode_image(img)
|
| 119 |
+
content.append({
|
| 120 |
+
"type": "image_url",
|
| 121 |
+
"image_url": {
|
| 122 |
+
"url": f"data:image/jpeg;base64,{base64_img}",
|
| 123 |
+
"detail": "low" # Use low detail to reduce token usage
|
| 124 |
+
}
|
| 125 |
+
})
|
| 126 |
+
except Exception as img_error:
|
| 127 |
+
# Log error but continue with other images
|
| 128 |
+
print(f"Error processing image {i}: {str(img_error)}")
|
| 129 |
+
except Exception as e:
|
| 130 |
+
return f"Error preparing images for analysis: {str(e)}"
|
| 131 |
|
| 132 |
# Enhanced system prompt for more specific and actionable recommendations
|
| 133 |
system_prompt = """You are an expert real estate advisor with deep knowledge of home selling strategies. You analyze home photos to provide EXTREMELY SPECIFIC, actionable recommendations that will maximize the property's selling price.
|
|
|
|
| 253 |
error_text += f" - {response.text[:200]}"
|
| 254 |
return f"Error: {error_text}. Please check your API key and try again."
|
| 255 |
except Exception as e:
|
| 256 |
+
return f"Error communicating with OpenAI API: {str(e)}. Please check your internet connection and try again."
|
| 257 |
|
| 258 |
# Calculate estimated cost
|
| 259 |
def calculate_cost(token_usage):
|
|
|
|
| 505 |
# Process uploaded images
|
| 506 |
if uploaded_files:
|
| 507 |
images = []
|
| 508 |
+
valid_images = []
|
| 509 |
+
error_messages = []
|
| 510 |
+
|
| 511 |
for file in uploaded_files:
|
| 512 |
try:
|
| 513 |
+
# Read file content first to check if it's valid
|
| 514 |
+
file_bytes = file.getvalue()
|
| 515 |
+
if len(file_bytes) == 0:
|
| 516 |
+
error_messages.append(f"Error: {file.name} appears to be empty.")
|
| 517 |
+
continue
|
| 518 |
+
|
| 519 |
+
# Try to open the image with PIL
|
| 520 |
+
image = Image.open(BytesIO(file_bytes))
|
| 521 |
+
|
| 522 |
+
# Validate image by trying to get its format
|
| 523 |
+
image_format = image.format
|
| 524 |
+
if not image_format:
|
| 525 |
+
error_messages.append(f"Error: {file.name} doesn't appear to be a valid image format.")
|
| 526 |
+
continue
|
| 527 |
+
|
| 528 |
# Resize large images to reduce token usage
|
| 529 |
max_size = (1024, 1024)
|
| 530 |
if image.width > max_size[0] or image.height > max_size[1]:
|
| 531 |
image.thumbnail(max_size, Image.LANCZOS)
|
| 532 |
+
|
| 533 |
images.append(image)
|
| 534 |
+
valid_images.append(file.name)
|
| 535 |
except Exception as e:
|
| 536 |
+
error_messages.append(f"Error processing {file.name}: {str(e)}")
|
| 537 |
|
| 538 |
+
# Update session state with valid images
|
| 539 |
st.session_state["uploaded_images"] = images
|
| 540 |
|
| 541 |
+
# Display uploaded images and errors
|
| 542 |
if images:
|
| 543 |
+
st.write(f"**{len(images)} valid images uploaded**")
|
| 544 |
image_cols = st.columns(min(3, len(images)))
|
| 545 |
for i, img in enumerate(images):
|
| 546 |
with image_cols[i % min(3, len(images))]:
|
| 547 |
+
st.image(img, width=150, caption=f"Image {i+1}: {valid_images[i]}")
|
| 548 |
+
|
| 549 |
+
# Display error messages if any
|
| 550 |
+
if error_messages:
|
| 551 |
+
with st.expander(f"⚠️ {len(error_messages)} image upload issues detected. Click to view details."):
|
| 552 |
+
for error in error_messages:
|
| 553 |
+
st.error(error)
|
| 554 |
|
| 555 |
# Custom subheader with background
|
| 556 |
st.markdown('<div class="custom-subheader">🏠 Selling Timeline & Details</div>', unsafe_allow_html=True)
|
|
|
|
| 603 |
if analyze_button or "analysis_result" in st.session_state:
|
| 604 |
# Process analysis if button pressed
|
| 605 |
if analyze_button:
|
| 606 |
+
# Check if there are valid images
|
| 607 |
+
if len(st.session_state["uploaded_images"]) == 0:
|
| 608 |
+
st.error("No valid images to analyze. Please upload at least one home photo.")
|
| 609 |
+
else:
|
| 610 |
+
# Simple loading indicator using pure Streamlit - no HTML, JS or CSS
|
| 611 |
+
with st.spinner("🏡 Analyzing your home photos..."):
|
| 612 |
+
# Show a progress bar
|
| 613 |
+
progress_bar = st.progress(0)
|
| 614 |
+
|
| 615 |
+
# Show the analysis steps with native Streamlit components
|
| 616 |
+
steps_placeholder = st.empty()
|
| 617 |
+
steps_placeholder.info("Step 1: Identifying property features and conditions...")
|
| 618 |
+
progress_bar.progress(25)
|
| 619 |
+
time.sleep(0.5) # Reduced delay for faster response
|
| 620 |
+
|
| 621 |
+
steps_placeholder.info("Step 2: Evaluating improvement opportunities and researching specific recommendations...")
|
| 622 |
+
progress_bar.progress(50)
|
| 623 |
+
time.sleep(0.5) # Reduced delay for faster response
|
| 624 |
+
|
| 625 |
+
steps_placeholder.info("Step 3: Calculating ROI potential and precise cost estimates...")
|
| 626 |
+
progress_bar.progress(75)
|
| 627 |
+
time.sleep(0.5) # Reduced delay for faster response
|
| 628 |
+
|
| 629 |
+
steps_placeholder.info("Step 4: Creating detailed timeline and finalizing recommendations...")
|
| 630 |
+
|
| 631 |
+
# Make the actual API call with enhanced error handling
|
| 632 |
+
try:
|
| 633 |
+
analysis_text = analyze_home_photos(
|
| 634 |
+
st.session_state["uploaded_images"],
|
| 635 |
+
timeframe,
|
| 636 |
+
f"Budget: ${max_budget}. Focus areas: {', '.join(improvement_focus)}. DIY preference: {diy_preference}. {additional_details}",
|
| 637 |
+
st.session_state["api_key"]
|
| 638 |
+
)
|
| 639 |
+
|
| 640 |
+
# Store the result
|
| 641 |
+
st.session_state["analysis_result"] = analysis_text
|
| 642 |
+
except Exception as e:
|
| 643 |
+
st.error(f"Error during analysis: {str(e)}")
|
| 644 |
+
st.session_state["analysis_result"] = f"An error occurred during analysis: {str(e)}. Please try again with different images or check your API key."
|
| 645 |
+
|
| 646 |
+
# Complete the progress bar and clear the loading indicators
|
| 647 |
+
progress_bar.progress(100)
|
| 648 |
+
steps_placeholder.empty()
|
| 649 |
+
progress_bar.empty()
|
| 650 |
|
| 651 |
# Display results
|
| 652 |
if "analysis_result" in st.session_state:
|