Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
|
| 3 |
+
# Set page config to minimize default margins and padding - MUST BE FIRST STREAMLIT COMMAND
|
| 4 |
+
st.set_page_config(
|
| 5 |
+
page_title="Home Value Maximizer",
|
| 6 |
+
page_icon="π‘",
|
| 7 |
+
layout="wide",
|
| 8 |
+
initial_sidebar_state="collapsed"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
import requests
|
| 12 |
+
from PIL import Image
|
| 13 |
+
import base64
|
| 14 |
+
from io import BytesIO
|
| 15 |
+
import time
|
| 16 |
+
from datetime import datetime, timedelta
|
| 17 |
+
import re
|
| 18 |
+
|
| 19 |
+
# Configure OpenAI API settings
|
| 20 |
+
API_URL = "https://api.openai.com/v1/chat/completions"
|
| 21 |
+
MODEL_NAME = "gpt-4o" # Updated to use GPT-4o which includes vision capabilities
|
| 22 |
+
|
| 23 |
+
# Get current date
|
| 24 |
+
current_date = datetime.now().strftime("%B %d, %Y")
|
| 25 |
+
|
| 26 |
+
# Set API key from Hugging Face secrets
|
| 27 |
+
try:
|
| 28 |
+
OPENAI_API_KEY = st.secrets["OPENAI_API_KEY"]
|
| 29 |
+
except:
|
| 30 |
+
OPENAI_API_KEY = "" # Will be prompted to enter API key if not in secrets
|
| 31 |
+
|
| 32 |
+
# Session state initialization
|
| 33 |
+
if "uploaded_images" not in st.session_state:
|
| 34 |
+
st.session_state["uploaded_images"] = []
|
| 35 |
+
if "api_key" not in st.session_state:
|
| 36 |
+
st.session_state["api_key"] = OPENAI_API_KEY
|
| 37 |
+
if "token_usage" not in st.session_state:
|
| 38 |
+
st.session_state["token_usage"] = {}
|
| 39 |
+
|
| 40 |
+
# Function to encode image to base64
|
| 41 |
+
def encode_image(image):
|
| 42 |
+
# Convert RGBA to RGB if needed
|
| 43 |
+
if image.mode == 'RGBA':
|
| 44 |
+
background = Image.new('RGB', image.size, (255, 255, 255))
|
| 45 |
+
background.paste(image, (0, 0), image)
|
| 46 |
+
image = background
|
| 47 |
+
|
| 48 |
+
# Resize large images to reduce token usage
|
| 49 |
+
max_size = (1024, 1024)
|
| 50 |
+
if image.width > max_size[0] or image.height > max_size[1]:
|
| 51 |
+
image.thumbnail(max_size, Image.LANCZOS)
|
| 52 |
+
|
| 53 |
+
buffered = BytesIO()
|
| 54 |
+
image.save(buffered, format="JPEG", quality=85)
|
| 55 |
+
return base64.b64encode(buffered.getvalue()).decode('utf-8')
|
| 56 |
+
|
| 57 |
+
# Function to fix formatting issues in text
|
| 58 |
+
def fix_formatting(text):
|
| 59 |
+
# Fix issues with numbers and text run together
|
| 60 |
+
text = re.sub(r'(\d+)([a-zA-Z])', r'\1 \2', text)
|
| 61 |
+
|
| 62 |
+
# Fix issues with missing spaces after commas and periods
|
| 63 |
+
text = re.sub(r'([.,])([a-zA-Z0-9])', r'\1 \2', text)
|
| 64 |
+
|
| 65 |
+
# Fix issues with missing spaces after colons
|
| 66 |
+
text = re.sub(r':([a-zA-Z0-9])', r': \1', text)
|
| 67 |
+
|
| 68 |
+
return text
|
| 69 |
+
|
| 70 |
+
# Function to analyze home photos with OpenAI's API
|
| 71 |
+
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}"
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
# Construct the message content
|
| 81 |
+
content = [
|
| 82 |
+
{"type": "text", "text": f"""Analyze these home photos and provide EXTREMELY SPECIFIC strategies to maximize the selling price.
|
| 83 |
+
The owner plans to sell within {timeframe}. Today's date is {current_date}. {additional_details}
|
| 84 |
+
|
| 85 |
+
Be incredibly detailed and specific about what you see in each image. Reference exact features, colors, materials, and conditions.
|
| 86 |
+
Provide precise recommendations with specific products, materials, colors, and techniques.
|
| 87 |
+
|
| 88 |
+
IMPORTANT: Ensure all your text is properly formatted with spaces between numbers and words, and proper spacing after punctuation.
|
| 89 |
+
"""}
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
# Add images to the message with proper format for vision API
|
| 93 |
+
for img in images:
|
| 94 |
+
base64_img = encode_image(img)
|
| 95 |
+
content.append({
|
| 96 |
+
"type": "image_url",
|
| 97 |
+
"image_url": {
|
| 98 |
+
"url": f"data:image/jpeg;base64,{base64_img}",
|
| 99 |
+
"detail": "low" # Use low detail to reduce token usage
|
| 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.
|
| 105 |
+
|
| 106 |
+
## FORMAT YOUR RESPONSE LIKE THIS:
|
| 107 |
+
|
| 108 |
+
### π KEY RECOMMENDATIONS SUMMARY
|
| 109 |
+
Begin with this exact text: "**HOME VALUE MAXIMIZER SUMMARY**"
|
| 110 |
+
|
| 111 |
+
Then add this line: "Below are the top improvements to maximize your home's value. See detailed sections for specific costs, instructions, and timeline."
|
| 112 |
+
|
| 113 |
+
Then, list 5 bullet points of the MOST IMPACTFUL improvements that will increase the property's value. For each bullet:
|
| 114 |
+
- Make the first sentence **bold**
|
| 115 |
+
- Include the specific issue identified
|
| 116 |
+
- Include the recommended solution
|
| 117 |
+
- Include the estimated value increase or ROI
|
| 118 |
+
|
| 119 |
+
### π TOP PRICE-MAXIMIZING PRIORITIES
|
| 120 |
+
Present the 3-4 MOST IMPACTFUL improvements first, formatted as a clear list. Each item MUST include:
|
| 121 |
+
- **Exact issue you see**: Be incredibly specific about what you observe in the photo (e.g., "The beige laminate countertops in the kitchen are visibly worn with scratches near the sink area")
|
| 122 |
+
- **Precise recommendation**: Specify EXACT materials, colors, products, or contractors (e.g., "Replace with Calacatta Quartz countertops in white with subtle gray veining")
|
| 123 |
+
- **Specific cost estimate**: Give narrow ranges (e.g., "$2,800-$3,200")
|
| 124 |
+
- **Precise value impact**: Quantify the increase (e.g., "Estimated to add $8,000-$10,000 to home value")
|
| 125 |
+
- **Timeline**: Exact number of days needed and mention if permits are required
|
| 126 |
+
|
| 127 |
+
### π¨ QUICK WINS (1-3 days)
|
| 128 |
+
List 5-6 fast, high-ROI improvements with EXACT details:
|
| 129 |
+
- **Kitchen**: Name specific issues, specific solutions, specific products/colors/materials
|
| 130 |
+
- **Bathroom**: Name specific issues, specific solutions, specific products/colors/materials
|
| 131 |
+
- **Living Spaces**: Name specific issues, specific solutions, specific products/colors/materials
|
| 132 |
+
- **Exterior**: Name specific issues, specific solutions, specific products/colors/materials
|
| 133 |
+
Include PRECISE cost estimates, DIY or contractor recommendations, and EXACT product suggestions.
|
| 134 |
+
|
| 135 |
+
### π SPECIFIC PRICING STRATEGY
|
| 136 |
+
Provide EXACT details on:
|
| 137 |
+
- **Precise price point**: Suggest a specific price point or narrow range based on visible features (e.g., "$399,900" not "$399,900toappealpsychologically")
|
| 138 |
+
- **Specific pricing psychology**: e.g., "$399,900 rather than $400,000" - MAKE SURE TO INCLUDE SPACES BETWEEN NUMBERS AND TEXT
|
| 139 |
+
- **Exact timing**: Specific days of week and dates based on current date and selling timeframe
|
| 140 |
+
- **Market-specific advice**: Relate to current market conditions with specific details
|
| 141 |
+
|
| 142 |
+
IMPORTANT: ALWAYS use proper spacing in your text. For example:
|
| 143 |
+
- CORRECT: "List at $399,900 to appeal psychologically to buyers under $400k."
|
| 144 |
+
- INCORRECT: "List at $399,900toappealpsychologicallytobuyers."
|
| 145 |
+
|
| 146 |
+
### πΈ DETAILED MARKETING PLAN
|
| 147 |
+
Provide SPECIFIC advice on:
|
| 148 |
+
- **Exactly which room features**: Name specific architectural elements or features to highlight
|
| 149 |
+
- **Specific photography angles**: Exact camera positions and times of day for optimal lighting
|
| 150 |
+
- **Specific staging items**: Name exact furniture pieces, decor items, or color accents to add/remove
|
| 151 |
+
- **Exact virtual tools**: Name specific apps or services for virtual tours/staging
|
| 152 |
+
|
| 153 |
+
### π SPECIFIC NEGOTIATION TACTICS
|
| 154 |
+
Recommend PRECISE approaches:
|
| 155 |
+
- **Exact language**: Provide specific scripts for countering common objections
|
| 156 |
+
- **Specific inspection strategy**: Name exact items to fix pre-inspection
|
| 157 |
+
- **Precise contingency planning**: Specific strategies for common issues based on the home's visible condition
|
| 158 |
+
|
| 159 |
+
### β οΈ CRITICAL ISSUES TO ADDRESS
|
| 160 |
+
List 3 SPECIFIC problems visible in the photos that must be fixed, with EXACT solutions.
|
| 161 |
+
|
| 162 |
+
### π
DETAILED TIMELINE WITH DATES
|
| 163 |
+
Create a PRECISE calendar using EXACT DATES starting from TODAY (${current_date}). Factor in the user's stated selling timeframe of ${timeframe}.
|
| 164 |
+
|
| 165 |
+
For example, if selling within 1-3 months:
|
| 166 |
+
- By [EXACT DATE]: Complete specific kitchen updates (list precisely what)
|
| 167 |
+
- By [EXACT DATE]: Finish specific bathroom improvements (list precisely what)
|
| 168 |
+
- By [EXACT DATE]: Address specific exterior issues (list precisely what)
|
| 169 |
+
- [EXACT DATE]: Schedule professional photography
|
| 170 |
+
- [EXACT DATE]: List the home on the market
|
| 171 |
+
|
| 172 |
+
## IMPORTANT GUIDELINES:
|
| 173 |
+
1. Be EXTREMELY SPECIFIC about what you see in each photo - reference exact details, colors, materials
|
| 174 |
+
2. Give PRECISE product recommendations when possible - name brands, models, colors
|
| 175 |
+
3. Provide EXACT cost estimates in narrow ranges
|
| 176 |
+
4. Specify CLEAR timelines with CALENDAR DATES based on today's date (${current_date})
|
| 177 |
+
5. Make all advice HYPER-SPECIFIC to the actual property in the photos
|
| 178 |
+
6. NEVER give generic advice - every recommendation should directly reference visible elements
|
| 179 |
+
7. ALWAYS use proper spacing in text - add spaces between numbers and words, after punctuation, etc.
|
| 180 |
+
"""
|
| 181 |
+
|
| 182 |
+
# Replace placeholders in the system prompt
|
| 183 |
+
formatted_system_prompt = system_prompt.replace("${current_date}", current_date).replace("${timeframe}", timeframe)
|
| 184 |
+
|
| 185 |
+
# Create the API payload with enhanced system prompt
|
| 186 |
+
payload = {
|
| 187 |
+
"model": MODEL_NAME,
|
| 188 |
+
"messages": [
|
| 189 |
+
{
|
| 190 |
+
"role": "system",
|
| 191 |
+
"content": formatted_system_prompt
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"role": "user",
|
| 195 |
+
"content": content
|
| 196 |
+
}
|
| 197 |
+
],
|
| 198 |
+
"max_tokens": 2500
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
try:
|
| 202 |
+
response = requests.post(API_URL, headers=headers, json=payload, timeout=90)
|
| 203 |
+
|
| 204 |
+
if response.status_code == 200:
|
| 205 |
+
response_data = response.json()
|
| 206 |
+
|
| 207 |
+
# Store token usage data
|
| 208 |
+
if "usage" in response_data:
|
| 209 |
+
st.session_state["token_usage"] = response_data["usage"]
|
| 210 |
+
|
| 211 |
+
# Get the content and fix any formatting issues
|
| 212 |
+
content = response_data["choices"][0]["message"]["content"]
|
| 213 |
+
content = fix_formatting(content)
|
| 214 |
+
|
| 215 |
+
return content
|
| 216 |
+
else:
|
| 217 |
+
error_text = f"API Error: {response.status_code}"
|
| 218 |
+
if response.text:
|
| 219 |
+
try:
|
| 220 |
+
error_json = response.json()
|
| 221 |
+
if "error" in error_json:
|
| 222 |
+
error_text += f" - {error_json['error']['message']}"
|
| 223 |
+
except:
|
| 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):
|
| 231 |
+
if not token_usage:
|
| 232 |
+
return {"total": "Unknown"}
|
| 233 |
+
|
| 234 |
+
# Latest pricing as of April 2024 for GPT-4o
|
| 235 |
+
input_cost_per_1M = 3 # $3 per 1M input tokens for GPT-4o
|
| 236 |
+
output_cost_per_1M = 10 # $10 per 1M output tokens for GPT-4o
|
| 237 |
+
|
| 238 |
+
prompt_tokens = token_usage.get("prompt_tokens", 0)
|
| 239 |
+
completion_tokens = token_usage.get("completion_tokens", 0)
|
| 240 |
+
|
| 241 |
+
input_cost = (prompt_tokens / 1000000) * input_cost_per_1M
|
| 242 |
+
output_cost = (completion_tokens / 1000000) * output_cost_per_1M
|
| 243 |
+
total_cost = input_cost + output_cost
|
| 244 |
+
|
| 245 |
+
return {
|
| 246 |
+
"input_tokens": prompt_tokens,
|
| 247 |
+
"output_tokens": completion_tokens,
|
| 248 |
+
"input_cost": input_cost,
|
| 249 |
+
"output_cost": output_cost,
|
| 250 |
+
"total_cost": total_cost
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
# Custom CSS to fix all styling issues including text visibility and header spacing
|
| 254 |
+
st.markdown("""
|
| 255 |
+
<style>
|
| 256 |
+
/* Override streamlit's default padding to prevent header cutoff */
|
| 257 |
+
.main .block-container {
|
| 258 |
+
padding-top: 2rem !important; /* Increased from 1rem to give more space */
|
| 259 |
+
padding-bottom: 1rem !important;
|
| 260 |
+
max-width: 100% !important;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
/* Fix for the header area to ensure it's fully visible in Hugging Face Spaces */
|
| 264 |
+
.stApp {
|
| 265 |
+
margin-top: 0.5rem !important;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
/* Fix text visibility issues - ensure good contrast */
|
| 269 |
+
.upload-text {
|
| 270 |
+
color: #262730 !important;
|
| 271 |
+
background-color: transparent !important;
|
| 272 |
+
font-weight: 500 !important;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/* Make the title and header area more compact but visible */
|
| 276 |
+
.title-area {
|
| 277 |
+
margin: 0 !important;
|
| 278 |
+
padding: 0.5rem 0 !important; /* Added top/bottom padding */
|
| 279 |
+
margin-bottom: 0.5rem !important;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
/* Make header more compact but ensure visibility */
|
| 283 |
+
.stMarkdown h1 {
|
| 284 |
+
margin-top: 0.5rem !important; /* Added margin to prevent cutoff */
|
| 285 |
+
margin-bottom: 0.5rem !important;
|
| 286 |
+
font-size: 1.8rem !important;
|
| 287 |
+
padding: 0 !important;
|
| 288 |
+
line-height: 1.3 !important; /* Improved line height */
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
/* Ensure the logo and title are visible */
|
| 292 |
+
.title-section {
|
| 293 |
+
display: block !important;
|
| 294 |
+
padding-top: 0.5rem !important;
|
| 295 |
+
margin-bottom: 0.75rem !important;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
/* Steps styling - improved visibility and spacing */
|
| 299 |
+
.steps-container {
|
| 300 |
+
display: flex;
|
| 301 |
+
justify-content: space-between;
|
| 302 |
+
margin: 0.75rem 0 1rem 0 !important; /* Added top margin */
|
| 303 |
+
padding: 0 !important;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.step-item {
|
| 307 |
+
flex: 1;
|
| 308 |
+
padding: 0.5rem !important; /* Increased padding */
|
| 309 |
+
font-size: 0.85rem;
|
| 310 |
+
color: #262730;
|
| 311 |
+
background-color: #f0f2f6;
|
| 312 |
+
border-radius: 4px;
|
| 313 |
+
margin-right: 0.5rem;
|
| 314 |
+
text-align: center;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
/* Enhanced section styling */
|
| 318 |
+
.stMarkdown h3 {
|
| 319 |
+
margin-top: 1.5rem;
|
| 320 |
+
padding: 0.5rem;
|
| 321 |
+
border-radius: 0.5rem;
|
| 322 |
+
font-weight: 600;
|
| 323 |
+
color: white;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
/* Style for summary section */
|
| 327 |
+
.stMarkdown h3:contains("KEY RECOMMENDATIONS SUMMARY") {
|
| 328 |
+
background-color: #2196f3;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
/* Summary content styling */
|
| 332 |
+
.summary-title {
|
| 333 |
+
font-size: 1.2rem;
|
| 334 |
+
font-weight: bold;
|
| 335 |
+
margin-bottom: 0.5rem;
|
| 336 |
+
color: #2196f3;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.summary-subtitle {
|
| 340 |
+
font-size: 0.9rem;
|
| 341 |
+
font-weight: 500;
|
| 342 |
+
margin-bottom: 1rem;
|
| 343 |
+
color: #424242;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
/* Priority recommendations */
|
| 347 |
+
.stMarkdown h3:contains("TOP PRICE") {
|
| 348 |
+
background-color: #1e88e5;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
/* Quick improvements section */
|
| 352 |
+
.stMarkdown h3:contains("QUICK WINS") {
|
| 353 |
+
background-color: #43a047;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
/* Strategic pricing section */
|
| 357 |
+
.stMarkdown h3:contains("SPECIFIC PRICING") {
|
| 358 |
+
background-color: #5e35b1;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
/* Marketing section */
|
| 362 |
+
.stMarkdown h3:contains("DETAILED MARKETING") {
|
| 363 |
+
background-color: #fb8c00;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
/* Negotiation section */
|
| 367 |
+
.stMarkdown h3:contains("SPECIFIC NEGOTIATION") {
|
| 368 |
+
background-color: #00897b;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
/* Critical issues section */
|
| 372 |
+
.stMarkdown h3:contains("CRITICAL ISSUES") {
|
| 373 |
+
background-color: #e53935;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
/* Timeline section */
|
| 377 |
+
.stMarkdown h3:contains("DETAILED TIMELINE") {
|
| 378 |
+
background-color: #8e24aa;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
/* Style for bullet points in summary */
|
| 382 |
+
.stMarkdown ul li {
|
| 383 |
+
margin-bottom: 0.75rem;
|
| 384 |
+
line-height: 1.6;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
/* Bold text */
|
| 388 |
+
.stMarkdown strong {
|
| 389 |
+
color: #262730;
|
| 390 |
+
font-weight: 700;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
/* Results container */
|
| 394 |
+
.results-container {
|
| 395 |
+
padding: 1.5rem;
|
| 396 |
+
background-color: white;
|
| 397 |
+
border-radius: 0.5rem;
|
| 398 |
+
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
| 399 |
+
margin-bottom: 1.5rem;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
/* Cost information */
|
| 403 |
+
.cost-info {
|
| 404 |
+
background-color: #f8f9fa;
|
| 405 |
+
padding: 0.75rem;
|
| 406 |
+
border-radius: 0.5rem;
|
| 407 |
+
font-size: 0.85rem;
|
| 408 |
+
margin-top: 1rem;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
/* Blue background for summary section */
|
| 412 |
+
.summary-section {
|
| 413 |
+
background-color: #e3f2fd;
|
| 414 |
+
border-radius: 8px;
|
| 415 |
+
padding: 1rem;
|
| 416 |
+
margin-bottom: 1.5rem;
|
| 417 |
+
border-left: 4px solid #2196f3;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
/* Custom header style for subheaders */
|
| 421 |
+
.custom-subheader {
|
| 422 |
+
background-color: #f5f5f5;
|
| 423 |
+
padding: 10px;
|
| 424 |
+
border-radius: 5px;
|
| 425 |
+
margin-bottom: 10px;
|
| 426 |
+
color: #262730;
|
| 427 |
+
font-weight: 600;
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
/* Fix for code blocks and pre-formatted text */
|
| 431 |
+
pre, code {
|
| 432 |
+
white-space: pre-wrap !important;
|
| 433 |
+
word-break: break-word !important;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
/* Fix for Hugging Face Spaces iframe header issues */
|
| 437 |
+
@media screen and (max-width: 1200px) {
|
| 438 |
+
.main .block-container {
|
| 439 |
+
padding-top: 3rem !important; /* Even more padding for smaller screens */
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
</style>
|
| 443 |
+
""", unsafe_allow_html=True)
|
| 444 |
+
|
| 445 |
+
# Page config was moved to the top of the file as the first Streamlit command
|
| 446 |
+
|
| 447 |
+
# Custom compact header - modified for better visibility
|
| 448 |
+
st.markdown("""
|
| 449 |
+
<div class="title-section">
|
| 450 |
+
<h1>Home Value Maximizer π‘</h1>
|
| 451 |
+
<div class="upload-text">πΈ Upload photos β π° Get specific improvements β π‘ Maximize selling price</div>
|
| 452 |
+
</div>
|
| 453 |
+
|
| 454 |
+
<div class="steps-container">
|
| 455 |
+
<div class="step-item">1. Upload multiple photos of your home</div>
|
| 456 |
+
<div class="step-item">2. Enter your selling timeline & preferences</div>
|
| 457 |
+
<div class="step-item">3. Click 'Analyze My Home' for detailed strategies</div>
|
| 458 |
+
</div>
|
| 459 |
+
""", unsafe_allow_html=True)
|
| 460 |
+
|
| 461 |
+
# API Key input if not in secrets
|
| 462 |
+
if not st.session_state["api_key"]:
|
| 463 |
+
st.session_state["api_key"] = st.text_input("Enter your OpenAI API Key", type="password")
|
| 464 |
+
st.info("Your API key is required to analyze the images. It will not be stored permanently.")
|
| 465 |
+
|
| 466 |
+
# Main content columns
|
| 467 |
+
col1, col2 = st.columns(2)
|
| 468 |
+
|
| 469 |
+
with col1:
|
| 470 |
+
# Custom subheader with background
|
| 471 |
+
st.markdown('<div class="custom-subheader">πΈ Upload Home Photos</div>', unsafe_allow_html=True)
|
| 472 |
+
uploaded_files = st.file_uploader("Upload photos of different areas of your home",
|
| 473 |
+
accept_multiple_files=True,
|
| 474 |
+
type=["jpg", "jpeg", "png"])
|
| 475 |
+
|
| 476 |
+
# Process uploaded images
|
| 477 |
+
if uploaded_files:
|
| 478 |
+
images = []
|
| 479 |
+
for file in uploaded_files:
|
| 480 |
+
try:
|
| 481 |
+
image = Image.open(file)
|
| 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 |
+
st.error(f"Error opening image {file.name}: {e}")
|
| 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)
|
| 502 |
+
|
| 503 |
+
# Expanded selling timeline options
|
| 504 |
+
timeframe = st.radio("When do you plan to sell your home?",
|
| 505 |
+
["Within 1 month", "1-3 months", "3-6 months", "6-12 months", "More than 12 months"])
|
| 506 |
+
|
| 507 |
+
additional_details = st.text_area("Additional Details",
|
| 508 |
+
placeholder="Describe your home, local market conditions, target buyers, or any specific concerns you have about the selling process.",
|
| 509 |
+
height=100)
|
| 510 |
+
|
| 511 |
+
# Additional preferences with custom subheader
|
| 512 |
+
st.markdown('<div class="custom-subheader">π° Budget & Preferences</div>', unsafe_allow_html=True)
|
| 513 |
+
|
| 514 |
+
max_budget = st.slider("Maximum budget for improvements ($)",
|
| 515 |
+
min_value=1000,
|
| 516 |
+
max_value=50000,
|
| 517 |
+
value=10000,
|
| 518 |
+
step=1000)
|
| 519 |
+
|
| 520 |
+
improvement_focus = st.multiselect(
|
| 521 |
+
"Areas to focus on (optional)",
|
| 522 |
+
["Curb appeal", "Kitchen", "Bathroom", "Living spaces", "Outdoor areas", "Storage solutions", "Energy efficiency", "Marketing", "Pricing strategy", "Negotiation", "Staging"],
|
| 523 |
+
default=["Curb appeal", "Kitchen", "Bathroom", "Pricing strategy"]
|
| 524 |
+
)
|
| 525 |
+
|
| 526 |
+
diy_preference = st.select_slider(
|
| 527 |
+
"DIY Preference",
|
| 528 |
+
options=["Professional work only", "Mix of DIY and professional", "Mostly DIY if possible"],
|
| 529 |
+
value="Mix of DIY and professional"
|
| 530 |
+
)
|
| 531 |
+
|
| 532 |
+
# Analysis button
|
| 533 |
+
analyze_button = st.button('π Analyze My Home',
|
| 534 |
+
use_container_width=True,
|
| 535 |
+
disabled=len(st.session_state["uploaded_images"]) == 0 or not st.session_state["api_key"])
|
| 536 |
+
|
| 537 |
+
if len(st.session_state["uploaded_images"]) == 0:
|
| 538 |
+
st.warning("Please upload at least one photo of your home to receive recommendations.")
|
| 539 |
+
|
| 540 |
+
if not st.session_state["api_key"]:
|
| 541 |
+
st.warning("Please enter your OpenAI API key to enable analysis.")
|
| 542 |
+
|
| 543 |
+
with col2:
|
| 544 |
+
# Custom subheader with background
|
| 545 |
+
st.markdown('<div class="custom-subheader">π‘ Comprehensive Selling Strategies</div>', unsafe_allow_html=True)
|
| 546 |
+
|
| 547 |
+
# Process and display analysis results
|
| 548 |
+
if analyze_button or "analysis_result" in st.session_state:
|
| 549 |
+
# Process analysis if button pressed
|
| 550 |
+
if analyze_button:
|
| 551 |
+
# Simple loading indicator using pure Streamlit - no HTML, JS or CSS
|
| 552 |
+
with st.spinner("π‘ Analyzing your home photos..."):
|
| 553 |
+
# Show a progress bar
|
| 554 |
+
progress_bar = st.progress(0)
|
| 555 |
+
|
| 556 |
+
# Show the analysis steps with native Streamlit components
|
| 557 |
+
steps_placeholder = st.empty()
|
| 558 |
+
steps_placeholder.info("Step 1: Identifying property features and conditions...")
|
| 559 |
+
progress_bar.progress(25)
|
| 560 |
+
time.sleep(1) # Small delay for visual effect
|
| 561 |
+
|
| 562 |
+
steps_placeholder.info("Step 2: Evaluating improvement opportunities and researching specific recommendations...")
|
| 563 |
+
progress_bar.progress(50)
|
| 564 |
+
time.sleep(1) # Small delay for visual effect
|
| 565 |
+
|
| 566 |
+
steps_placeholder.info("Step 3: Calculating ROI potential and precise cost estimates...")
|
| 567 |
+
progress_bar.progress(75)
|
| 568 |
+
time.sleep(1) # Small delay for visual effect
|
| 569 |
+
|
| 570 |
+
steps_placeholder.info("Step 4: Creating detailed timeline and finalizing recommendations...")
|
| 571 |
+
|
| 572 |
+
# Make the actual API call
|
| 573 |
+
analysis_text = analyze_home_photos(
|
| 574 |
+
st.session_state["uploaded_images"],
|
| 575 |
+
timeframe,
|
| 576 |
+
f"Budget: ${max_budget}. Focus areas: {', '.join(improvement_focus)}. DIY preference: {diy_preference}. {additional_details}",
|
| 577 |
+
st.session_state["api_key"]
|
| 578 |
+
)
|
| 579 |
+
|
| 580 |
+
# Complete the progress bar
|
| 581 |
+
progress_bar.progress(100)
|
| 582 |
+
|
| 583 |
+
# Store the result and clear the loading indicators
|
| 584 |
+
st.session_state["analysis_result"] = analysis_text
|
| 585 |
+
steps_placeholder.empty()
|
| 586 |
+
progress_bar.empty()
|
| 587 |
+
|
| 588 |
+
# Display results
|
| 589 |
+
if "analysis_result" in st.session_state:
|
| 590 |
+
# Process the output to enhance summary formatting
|
| 591 |
+
result = st.session_state["analysis_result"]
|
| 592 |
+
|
| 593 |
+
# Check if we need to style the summary section
|
| 594 |
+
if "**HOME VALUE MAXIMIZER SUMMARY**" in result:
|
| 595 |
+
# Split the result to find the summary section
|
| 596 |
+
parts = result.split("### π KEY RECOMMENDATIONS SUMMARY", 1)
|
| 597 |
+
if len(parts) > 1:
|
| 598 |
+
before_summary = parts[0]
|
| 599 |
+
rest_parts = parts[1].split("### π", 1)
|
| 600 |
+
if len(rest_parts) > 1:
|
| 601 |
+
summary_content = rest_parts[0]
|
| 602 |
+
after_summary = "### π" + rest_parts[1]
|
| 603 |
+
|
| 604 |
+
# Apply custom formatting to the summary
|
| 605 |
+
result = before_summary + '<div class="summary-section">' + summary_content + '</div>' + after_summary
|
| 606 |
+
|
| 607 |
+
# Display the enhanced result
|
| 608 |
+
st.markdown(f'<div class="results-container">{result}</div>', unsafe_allow_html=True)
|
| 609 |
+
|
| 610 |
+
# Add a download button for the report
|
| 611 |
+
st.download_button(
|
| 612 |
+
label="Download Selling Strategies as Text",
|
| 613 |
+
data=st.session_state["analysis_result"],
|
| 614 |
+
file_name="home_selling_strategies.txt",
|
| 615 |
+
mime="text/plain",
|
| 616 |
+
use_container_width=True
|
| 617 |
+
)
|
| 618 |
+
else:
|
| 619 |
+
st.markdown('<div class="results-container">**Personalized selling strategies will appear here after you submit your home photos and details**</div>', unsafe_allow_html=True)
|
| 620 |
+
|
| 621 |
+
# Disclaimer section
|
| 622 |
+
st.markdown("---")
|
| 623 |
+
with st.expander("π **Disclaimer**"):
|
| 624 |
+
st.markdown("""
|
| 625 |
+
**Disclaimer:**
|
| 626 |
+
This Home Value Maximizer tool provides recommendations based on AI analysis of your home photos.
|
| 627 |
+
Results are for **informational purposes only** and may not be accurate for your specific market or situation.
|
| 628 |
+
|
| 629 |
+
- **Price impact estimates** are approximate and depend on local market conditions
|
| 630 |
+
- **Pricing strategies** should be verified with a local real estate professional familiar with your specific market
|
| 631 |
+
- **Improvement recommendations** and costs may vary based on your location and local labor costs
|
| 632 |
+
- **Product recommendations** are suggestions only; please research compatibility with your home
|
| 633 |
+
|
| 634 |
+
For the best results, consult with experienced real estate professionals in your area before making significant decisions.
|
| 635 |
+
""")
|