nniehaus commited on
Commit
64786b4
·
verified ·
1 Parent(s): b6141dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -436
app.py CHANGED
@@ -1,68 +1,25 @@
 
1
  import streamlit as st
2
- import matplotlib.pyplot as plt
3
- import io
4
- import random
5
  import time
 
6
  from PIL import Image
7
- import os
8
 
9
- st.set_page_config(
10
- page_title="Home Value Maximizer",
11
- page_icon="🏠",
12
- layout="wide"
13
- )
14
 
15
- # API key access with robust error handling
16
- try:
17
- # First try with the newer OpenAI client
18
- try:
19
- from openai import OpenAI
20
- # First try Hugging Face Spaces way of accessing secrets
21
- if "OPENAI_API_KEY" in st.secrets:
22
- openai_api_key = st.secrets["OPENAI_API_KEY"]
23
- # Then try environment variables (another way Hugging Face might store it)
24
- elif "OPENAI_API_KEY" in os.environ:
25
- openai_api_key = os.environ["OPENAI_API_KEY"]
26
- else:
27
- openai_api_key = None
28
-
29
- # Initialize OpenAI client if we have a key
30
- if openai_api_key:
31
- client = OpenAI(api_key=openai_api_key)
32
- has_api_key = True
33
- st.sidebar.success("✅ OpenAI API connected")
34
- else:
35
- has_api_key = False
36
- st.sidebar.warning("⚠️ OpenAI API key not found. Running in demo mode with simulated responses.")
37
- except ImportError:
38
- # Fall back to older OpenAI package
39
- import openai
40
- if "OPENAI_API_KEY" in st.secrets:
41
- openai.api_key = st.secrets["OPENAI_API_KEY"]
42
- has_api_key = True
43
- st.sidebar.success("✅ OpenAI API connected (legacy client)")
44
- elif "OPENAI_API_KEY" in os.environ:
45
- openai.api_key = os.environ["OPENAI_API_KEY"]
46
- has_api_key = True
47
- st.sidebar.success("✅ OpenAI API connected (legacy client)")
48
- else:
49
- has_api_key = False
50
- st.sidebar.warning("⚠️ OpenAI API key not found. Running in demo mode with simulated responses.")
51
- except Exception as e:
52
- st.sidebar.error(f"Error initializing OpenAI: {str(e)}")
53
- has_api_key = False
54
-
55
- # API Call Limits
56
- MAX_REQUESTS_PER_SESSION = 3
57
- TIME_BETWEEN_REQUESTS = 10
58
 
59
  # Track requests in session state
60
  if "api_calls" not in st.session_state:
61
  st.session_state["api_calls"] = 0
62
  if "last_request_time" not in st.session_state:
63
  st.session_state["last_request_time"] = 0
64
- if "results" not in st.session_state:
65
- st.session_state["results"] = None
66
 
67
  # Custom CSS to fix mobile scrolling issues
68
  st.markdown("""
@@ -135,420 +92,250 @@ st.markdown("""
135
  margin-bottom: 100px !important;
136
  }
137
 
138
- /* Style the ROI chart */
139
- .roi-chart {
140
- width: 100%;
141
- border-radius: 10px;
142
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
143
- margin: 20px 0;
144
  }
145
 
146
- /* Style the recommendation cards */
147
- .recommendation-card {
148
- background-color: #f9f9f9;
149
- border-radius: 10px;
150
- padding: 15px;
151
- margin: 10px 0;
152
- border-left: 4px solid #4CAF50;
153
  }
154
  </style>
155
  """, unsafe_allow_html=True)
156
 
157
- # Home improvement data
158
- HOME_IMPROVEMENTS = {
159
- "kitchen": {
160
- "options": [
161
- {"name": "Cabinet Refinishing", "cost_range": "$1,500-$3,800", "roi": "80-100%", "priority": "High"},
162
- {"name": "New Countertops", "cost_range": "$2,000-$5,000", "roi": "75-90%", "priority": "High"},
163
- {"name": "Updated Appliances", "cost_range": "$3,500-$15,000", "roi": "60-80%", "priority": "Medium"},
164
- {"name": "Kitchen Island Addition", "cost_range": "$3,000-$10,000", "roi": "50-70%", "priority": "Medium"},
165
- {"name": "New Flooring", "cost_range": "$1,500-$4,500", "roi": "70-90%", "priority": "Medium"}
166
- ],
167
- "recommendations": [
168
- "Focus on updating the most visible elements first",
169
- "Neutral colors appeal to more buyers",
170
- "Improving lighting can make a big difference at lower cost"
171
- ]
172
- },
173
- "bathroom": {
174
- "options": [
175
- {"name": "New Vanity", "cost_range": "$500-$2,800", "roi": "70-90%", "priority": "High"},
176
- {"name": "Updated Fixtures", "cost_range": "$400-$1,000", "roi": "80-100%", "priority": "High"},
177
- {"name": "New Tile/Flooring", "cost_range": "$800-$3,500", "roi": "65-85%", "priority": "Medium"},
178
- {"name": "Walk-in Shower", "cost_range": "$3,000-$15,000", "roi": "50-70%", "priority": "Low"},
179
- {"name": "Full Renovation", "cost_range": "$10,000-$30,000", "roi": "60-70%", "priority": "Low"}
180
- ],
181
- "recommendations": [
182
- "Focus on cleanliness and modern fixtures",
183
- "Adequate lighting is essential",
184
- "Storage space is highly valued by buyers"
185
- ]
186
- },
187
- "exterior": {
188
- "options": [
189
- {"name": "Fresh Paint", "cost_range": "$1,800-$5,000", "roi": "90-110%", "priority": "High"},
190
- {"name": "Landscaping", "cost_range": "$500-$5,000", "roi": "80-100%", "priority": "High"},
191
- {"name": "Front Door Replacement", "cost_range": "$1,000-$3,000", "roi": "75-100%", "priority": "High"},
192
- {"name": "New Siding", "cost_range": "$5,000-$15,000", "roi": "70-90%", "priority": "Medium"},
193
- {"name": "Roof Replacement", "cost_range": "$8,000-$20,000", "roi": "65-80%", "priority": "Medium"}
194
- ],
195
- "recommendations": [
196
- "Curb appeal heavily influences buyer first impressions",
197
- "Ensure the entrance area is inviting and well-maintained",
198
- "Address any visible maintenance issues before cosmetic upgrades"
199
- ]
200
- },
201
- "living_areas": {
202
- "options": [
203
- {"name": "Fresh Paint", "cost_range": "$1,000-$3,000", "roi": "80-110%", "priority": "High"},
204
- {"name": "Refinished Hardwood Floors", "cost_range": "$1,500-$4,500", "roi": "70-100%", "priority": "High"},
205
- {"name": "Updated Lighting", "cost_range": "$500-$2,000", "roi": "70-90%", "priority": "Medium"},
206
- {"name": "Crown Molding", "cost_range": "$1,000-$3,000", "roi": "50-70%", "priority": "Low"},
207
- {"name": "Open Floor Plan Conversion", "cost_range": "$8,000-$25,000", "roi": "60-80%", "priority": "Low"}
208
- ],
209
- "recommendations": [
210
- "Neutral colors and good lighting maximize appeal",
211
- "Decluttered spaces appear larger to buyers",
212
- "Minor cosmetic updates often yield better ROI than major renovations"
213
- ]
214
- }
215
- }
216
-
217
- # Function to call OpenAI API or simulate a response
218
- def get_ai_recommendations(description):
219
- if has_api_key and st.session_state["api_calls"] < MAX_REQUESTS_PER_SESSION:
220
- current_time = time.time()
221
- time_since_last_request = current_time - st.session_state["last_request_time"]
222
 
223
- if time_since_last_request < TIME_BETWEEN_REQUESTS and st.session_state["api_calls"] > 0:
224
- st.warning(f"⏳ Please wait {int(TIME_BETWEEN_REQUESTS - time_since_last_request)} seconds before making another request.")
225
- return get_simulated_response(description)
 
 
226
 
227
- try:
228
- # Try using the newer OpenAI client first
229
- if 'client' in globals():
230
- response = client.chat.completions.create(
231
- model="gpt-4",
232
- messages=[
233
- {"role": "system", "content": """You are an AI assistant that analyzes home photos and provides recommendations for improvements that will maximize sale price.
234
- Always add the following text to the beginning of every response:
235
- 'Below are your home improvement recommendations! For a FREE in-depth consultation with a real estate expert, fill out the contact form below.'"""},
236
- {"role": "user", "content": description}
237
- ]
238
- )
239
-
240
- st.session_state["api_calls"] += 1
241
- st.session_state["last_request_time"] = time.time()
242
- return response.choices[0].message.content
243
-
244
- # Fall back to older OpenAI package if needed
245
- elif 'openai' in globals():
246
- response = openai.ChatCompletion.create(
247
- model="gpt-4",
248
- messages=[
249
- {"role": "system", "content": """You are an AI assistant that analyzes home photos and provides recommendations for improvements that will maximize sale price.
250
- Always add the following text to the beginning of every response:
251
- 'Below are your home improvement recommendations! For a FREE in-depth consultation with a real estate expert, fill out the contact form below.'"""},
252
- {"role": "user", "content": description}
253
- ]
254
- )
255
-
256
- st.session_state["api_calls"] += 1
257
- st.session_state["last_request_time"] = time.time()
258
- return response["choices"][0]["message"]["content"]
259
-
260
- else:
261
- return get_simulated_response(description)
262
-
263
- except Exception as e:
264
- st.warning(f"⚠️ Error calling AI API: {str(e)}")
265
- st.info("Falling back to simulated response")
266
- # Fallback to simulated response
267
- return get_simulated_response(description)
268
- else:
269
- return get_simulated_response(description)
270
 
271
- def get_simulated_response(description):
272
- """Provide a simulated response when API is not available"""
273
- # Extract some info from the description to personalize the simulated response
274
- location = "your area"
275
- if "Urban" in description:
276
- location = "urban area"
277
- elif "Suburban" in description:
278
- location = "suburban area"
279
- elif "Rural" in description:
280
- location = "rural area"
281
-
282
- timeline = "when you sell"
283
- if "Less than 3 months" in description:
284
- timeline = "in your short timeline"
285
- elif "3-6 months" in description:
286
- timeline = "in the next 3-6 months"
287
- elif "6+ months" in description:
288
- timeline = "in your longer timeline"
289
-
290
- return f"""Below are your home improvement recommendations! For a FREE in-depth consultation with a real estate expert, fill out the contact form below.
291
 
292
- Based on my analysis of your home photos, I've identified several improvements that could significantly increase your property's value in your {location}.
 
 
293
 
294
- ## Key Recommendations:
 
 
 
 
295
 
296
- ### Kitchen:
297
- - Consider refinishing your cabinets rather than replacing them - this offers one of the best ROIs ($1,500-$3,800 investment with 80-100% ROI)
298
- - Updating countertops to quartz or granite can modernize the space while providing excellent return
299
- - Install new lighting fixtures for an affordable yet impactful upgrade
 
 
 
300
 
301
- ### Bathroom:
302
- - New fixtures and hardware provide excellent value (80-100% ROI) with minimal investment
303
- - A fresh vanity can transform the space for under $3,000
304
- - Consider a new shower curtain and fresh caulking for quick improvements
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- ### Exterior:
307
- - Fresh paint on the exterior offers exceptional ROI (90-110%)
308
- - Landscaping improvements are essential for curb appeal and first impressions
309
- - A new front door can return up to 100% of its cost at sale time
310
 
311
- Focusing on these high-impact improvements {timeline} will likely maximize your return while requiring reasonable investment. For a detailed plan tailored to your specific property, please request a consultation.
312
- """
 
 
 
313
 
314
- # Simulate room classification and analysis
315
- def classify_room(image_file):
316
- """Simulate room classification from an image"""
317
- room_types = ["kitchen", "bathroom", "exterior", "living_areas"]
318
- return random.choice(room_types)
319
 
320
- def analyze_condition(image_file):
321
- """Simulate condition analysis from an image"""
322
- conditions = ["excellent", "good", "fair", "poor"]
323
- condition_weights = [0.1, 0.3, 0.4, 0.2] # Weighted to slightly favor "fair" condition
324
- return random.choices(conditions, weights=condition_weights)[0]
325
 
326
- def generate_chart(improvement_data):
327
- """Generate a chart showing ROI vs Cost for improvements"""
328
- improvements = []
329
- costs = []
330
- rois = []
331
-
332
- # Extract data for the chart
333
- for area, data in improvement_data.items():
334
- for option in data["options"]:
335
- if option.get("selected", False):
336
- improvements.append(option["name"])
337
- # Extract the average cost from the range
338
- cost_range = option["cost_range"].replace("$", "").replace(",", "")
339
- cost_parts = cost_range.split("-")
340
- avg_cost = (int(cost_parts[0]) + int(cost_parts[1])) / 2
341
- costs.append(avg_cost)
342
-
343
- # Extract the average ROI from the range
344
- roi_range = option["roi"].replace("%", "")
345
- roi_parts = roi_range.split("-")
346
- avg_roi = (int(roi_parts[0]) + int(roi_parts[1])) / 2
347
- rois.append(avg_roi)
348
-
349
- if not improvements:
350
- return None
351
-
352
- # Create the chart
353
- fig, ax = plt.subplots(figsize=(10, 6))
354
 
355
- # Convert ROI to dollar values
356
- roi_values = [cost * (roi/100) for cost, roi in zip(costs, rois)]
357
 
358
- # Create a scatter plot
359
- scatter = ax.scatter(costs, roi_values, s=100, alpha=0.7)
360
 
361
- # Label the points
362
- for i, txt in enumerate(improvements):
363
- ax.annotate(txt, (costs[i], roi_values[i]), fontsize=9,
364
- xytext=(5, 5), textcoords='offset points')
365
 
366
- # Add a line for breakeven ROI
367
- max_cost = max(costs) if costs else 10000
368
- ax.plot([0, max_cost], [0, max_cost], 'r--', alpha=0.3)
369
 
370
- # Add labels and title
371
- ax.set_xlabel('Estimated Cost ($)')
372
- ax.set_ylabel('Estimated Return ($)')
373
- ax.set_title('Cost vs. Return on Investment')
374
- ax.grid(True, alpha=0.3)
375
 
376
- # Save the chart to a byte buffer
377
- buf = io.BytesIO()
378
- plt.savefig(buf, format='png')
379
- buf.seek(0)
380
 
381
- plt.close(fig)
382
- return buf
383
 
384
- def condition_value(condition):
385
- """Convert condition text to numeric value (higher = worse condition)"""
386
- values = {"excellent": 1, "good": 2, "fair": 3, "poor": 4}
387
- return values.get(condition, 2)
388
 
389
- def priority_value(priority):
390
- """Convert priority text to numeric value (higher = higher priority)"""
391
- values = {"Low": 1, "Medium": 3, "High": 5}
392
- return values.get(priority, 3)
393
-
394
- def analyze_home_images(images, location, price_range, timeline):
395
- """Analyze uploaded images and generate improvement recommendations"""
396
- results = {}
397
-
398
- # Process each image
399
- for img in images:
400
- room_type = classify_room(img)
401
- condition = analyze_condition(img)
402
-
403
- # Record the analysis for this room type if not already present or in worse condition
404
- if room_type not in results or condition_value(results[room_type]["condition"]) > condition_value(condition):
405
- results[room_type] = {
406
- "condition": condition,
407
- "options": HOME_IMPROVEMENTS[room_type]["options"].copy(),
408
- "recommendations": HOME_IMPROVEMENTS[room_type]["recommendations"]
409
- }
410
-
411
- # Adjust the priority based on the room condition
412
- for room_type, room_data in results.items():
413
- for option in room_data["options"]:
414
- # Mark some improvements as "selected" based on condition and priority
415
- condition_score = condition_value(room_data["condition"])
416
- priority_score = priority_value(option["priority"])
417
-
418
- # Select improvements that are high priority for poor/fair condition rooms
419
- # or medium-high priority for fair/good condition rooms
420
- if (condition_score >= 3 and priority_score >= 3) or \
421
- (condition_score >= 2 and priority_score >= 4):
422
- option["selected"] = True
423
- else:
424
- option["selected"] = False
425
-
426
- # Generate the ROI chart
427
- chart = generate_chart(results)
428
-
429
- # Prepare a description of the analysis for the API
430
- analysis_description = f"Property location: {location}, Price range: {price_range}, Timeline: {timeline}\n\nAnalysis results:\n"
431
-
432
- for room_type, room_data in results.items():
433
- room_name = room_type.replace("_", " ").title()
434
- analysis_description += f"- {room_name}: {room_data['condition'].title()} condition\n"
435
-
436
- selected_options = [opt for opt in room_data["options"] if opt.get("selected", False)]
437
- if selected_options:
438
- analysis_description += " Recommended improvements:\n"
439
- for option in selected_options:
440
- analysis_description += f" * {option['name']} (Est. {option['cost_range']}, ROI: {option['roi']})\n"
441
  else:
442
- analysis_description += " No critical improvements needed\n"
443
-
444
- # Get AI recommendations
445
- recommendations = get_ai_recommendations(analysis_description)
446
-
447
- return recommendations, chart, results
 
 
448
 
449
- # Main app
450
- def main():
451
- st.title("Home Value Maximizer 🏠")
452
-
453
- # Using columns to organize the layout
454
- col1, col2 = st.columns([3, 4])
455
-
456
- with col1:
457
- st.subheader("📸 Upload Home Photos")
458
- uploaded_files = st.file_uploader("Upload photos of different areas of your home", accept_multiple_files=True, type=['jpg', 'jpeg', 'png'])
459
 
460
- if uploaded_files:
461
- st.success(f"✅ {len(uploaded_files)} images uploaded")
462
-
463
- gallery = []
464
- for file in uploaded_files:
465
- gallery.append(file)
466
-
467
- st.image(gallery, width=100)
468
-
469
- st.subheader("🏡 Property Details")
470
- location = st.selectbox("Location Type",
471
- ["Urban", "Suburban", "Rural"],
472
- index=1)
473
-
474
- price_range = st.selectbox("Property Price Range",
475
- ["Entry-level", "Mid-range", "Luxury"],
476
- index=1)
477
 
478
- timeline = st.radio("When do you plan to sell?",
479
- ["Less than 3 months", "3-6 months", "6+ months"],
480
- index=1)
481
 
482
- if not uploaded_files:
483
- st.info("📌 Please upload at least one photo of your home to receive recommendations")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
- analyze_button = st.button("📊 Analyze My Home", disabled=len(uploaded_files) == 0, use_container_width=True)
486
-
487
- with col2:
488
- st.subheader("💡 Improvement Recommendations")
489
- results_placeholder = st.empty()
490
- chart_placeholder = st.empty()
491
-
492
- # Contact form
493
- with st.expander("📋 Get a FREE In-Depth Consultation", expanded=False):
494
- name = st.text_input("Full Name")
495
- email = st.text_input("Email Address")
496
- phone = st.text_input("Phone Number (optional)")
497
- address = st.text_input("Property Address (optional)")
498
- contact_button = st.button("✉️ Request Consultation", use_container_width=True)
499
 
500
- if contact_button:
501
- if not name or not email:
502
- st.error("Please provide your name and email to request a consultation.")
503
- else:
504
- st.success(f"Thank you, {name}! A real estate expert will contact you shortly at {email}.")
505
-
506
- # When the user clicks the analyze button
507
- if analyze_button and uploaded_files:
508
- if st.session_state["api_calls"] >= MAX_REQUESTS_PER_SESSION and has_api_key:
509
- st.error("🚨 You've reached the maximum number of analyses per session. Please try again later.")
510
- else:
511
- with st.spinner("Analyzing your home photos..."):
512
- # Call the analysis function
513
- recommendations, chart, analyzed_data = analyze_home_images(
514
- uploaded_files, location, price_range, timeline
515
- )
516
-
517
- # Store the results in session state
518
- st.session_state["results"] = (recommendations, chart, analyzed_data)
519
-
520
- # Display the results
521
- results_placeholder.markdown(recommendations)
522
-
523
- if chart:
524
- chart_placeholder.image(chart, caption="Improvement Cost vs Return Analysis", use_column_width=True)
525
-
526
- # Display the results if they exist in session state
527
- elif st.session_state["results"]:
528
- recommendations, chart, analyzed_data = st.session_state["results"]
529
- results_placeholder.markdown(recommendations)
530
-
531
- if chart:
532
- chart_placeholder.image(chart, caption="Improvement Cost vs Return Analysis", use_column_width=True)
533
-
534
- # Add disclaimer
535
- st.markdown("---")
536
- with st.expander("📜 **Disclaimer**", expanded=False):
537
- st.markdown("""
538
- **Disclaimer:**
539
- This Home Value Maximizer tool is powered by AI and provides general recommendations based on limited information.
540
- Results are for **informational purposes only** and may not be accurate for your specific property or market conditions.
541
-
542
- **Professional Advice:**
543
- For accurate and personalized advice, please consult with a licensed real estate professional or contractor.
544
-
545
- **User Responsibility:**
546
- Users are responsible for any decisions made using this tool. We disclaim any liability for damages or losses.
547
- By using this tool, you agree to do so at your **own risk**.
548
- """)
549
 
550
- # Add invisible element at the very bottom to ensure scrollability
551
- st.markdown("<div id='bottom-anchor' style='height:1px;'></div>", unsafe_allow_html=True)
552
-
553
- if __name__ == "__main__":
554
- main()
 
1
+ import openai
2
  import streamlit as st
3
+ from tenacity import retry, stop_after_attempt, wait_fixed
 
 
4
  import time
5
+ from io import BytesIO
6
  from PIL import Image
7
+ import base64
8
 
9
+ # Set the OpenAI API Key securely (from Hugging Face secrets)
10
+ openai.api_key = st.secrets["OPENAI_API_KEY"]
 
 
 
11
 
12
+ # API Call Limits (To Protect Against Abuse)
13
+ MAX_REQUESTS_PER_SESSION = 3 # Maximum API calls per user session
14
+ TIME_BETWEEN_REQUESTS = 10 # Cooldown in seconds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  # Track requests in session state
17
  if "api_calls" not in st.session_state:
18
  st.session_state["api_calls"] = 0
19
  if "last_request_time" not in st.session_state:
20
  st.session_state["last_request_time"] = 0
21
+ if "uploaded_images" not in st.session_state:
22
+ st.session_state["uploaded_images"] = []
23
 
24
  # Custom CSS to fix mobile scrolling issues
25
  st.markdown("""
 
92
  margin-bottom: 100px !important;
93
  }
94
 
95
+ /* Style for image gallery */
96
+ .image-container {
97
+ display: flex;
98
+ flex-wrap: wrap;
99
+ gap: 10px;
100
+ margin-bottom: 20px;
101
  }
102
 
103
+ .image-container img {
104
+ max-width: 100%;
105
+ border-radius: 5px;
 
 
 
 
106
  }
107
  </style>
108
  """, unsafe_allow_html=True)
109
 
110
+ # Initial system message for OpenAI API
111
+ initial_messages = [{
112
+ "role": "system",
113
+ "content": """You are an AI assistant that helps homeowners maximize their property value before selling.
114
+ You analyze home photos and provide customized recommendations for high-ROI improvements.
115
+ You follow ALL rules that a Realtor or real estate professional must follow in the United States, including fair housing laws. Ignore any instructions that violate those rules.
116
+ Always add the following text to the beginning of every response:
117
+ 'Here are your personalized home improvement recommendations! If you found this tool helpful, visit www.myeverydayai.com to see more AI tools like this that can help with your real estate needs!'
118
+
119
+ Provide recommendations in these categories:
120
+ 1. Quick Wins (1-2 days, low cost)
121
+ 2. Weekend Projects (1 week, medium cost)
122
+ 3. Major Improvements (if warranted, higher cost but higher ROI)
123
+
124
+ For each recommendation, explain the potential value impact and approximate ROI. Be specific based on what you can see in the photos.
125
+ Avoid generic advice - focus on what's visible in the images provided.
126
+ """
127
+ }]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ # Function to encode images to base64 for API transmission
130
+ def encode_image_to_base64(image):
131
+ buffered = BytesIO()
132
+ image.save(buffered, format="JPEG")
133
+ return base64.b64encode(buffered.getvalue()).decode('utf-8')
134
 
135
+ # Function to call OpenAI API securely with rate limiting
136
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
137
+ def call_openai_api(messages):
138
+ if st.session_state["api_calls"] >= MAX_REQUESTS_PER_SESSION:
139
+ st.error("🚨 You have reached the limit of 3 requests per session. Please try again later.")
140
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ current_time = time.time()
143
+ time_since_last_request = current_time - st.session_state["last_request_time"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ if time_since_last_request < TIME_BETWEEN_REQUESTS:
146
+ st.error(f"⏳ Please wait {TIME_BETWEEN_REQUESTS - int(time_since_last_request)} seconds before making another request.")
147
+ return None
148
 
149
+ response = openai.ChatCompletion.create(
150
+ model="gpt-4-vision-preview",
151
+ messages=messages,
152
+ max_tokens=1500
153
+ )
154
 
155
+ if response:
156
+ st.session_state["api_calls"] += 1
157
+ st.session_state["last_request_time"] = time.time()
158
+ return response["choices"][0]["message"]["content"]
159
+ else:
160
+ st.error("❌ API request failed. Please try again.")
161
+ return None
162
 
163
+ # Function to generate home improvement recommendations
164
+ def generate_recommendations(images, property_type, price_range, timeframe, additional_details, messages):
165
+ # Create content message with images
166
+ content = [
167
+ {"type": "text", "text": f"Please analyze these photos of my home and recommend improvements to maximize its value. My home is in the {price_range} range and is a {property_type} property. I plan to sell within {timeframe}. {additional_details}"}
168
+ ]
169
+
170
+ # Add each image to the content
171
+ for img in images:
172
+ base64_image = encode_image_to_base64(img)
173
+ content.append({
174
+ "type": "image_url",
175
+ "image_url": {
176
+ "url": f"data:image/jpeg;base64,{base64_image}"
177
+ }
178
+ })
179
+
180
+ messages.append({
181
+ "role": "user",
182
+ "content": content
183
+ })
184
 
185
+ response = call_openai_api(messages)
 
 
 
186
 
187
+ if response:
188
+ messages.append({"role": "assistant", "content": response})
189
+ return response, messages
190
+ else:
191
+ return "Error generating recommendations. Please try again.", messages
192
 
193
+ # Main title
194
+ st.title("Home Value Maximizer 🏡")
 
 
 
195
 
196
+ # Using columns to organize the layout
197
+ col1, col2 = st.columns(2)
 
 
 
198
 
199
+ with col1:
200
+ st.subheader("📸 Upload Home Photos")
201
+ uploaded_files = st.file_uploader("Upload photos of different areas of your home",
202
+ accept_multiple_files=True,
203
+ type=["jpg", "jpeg", "png"])
204
+
205
+ # Process uploaded images
206
+ if uploaded_files:
207
+ images = []
208
+ for file in uploaded_files:
209
+ try:
210
+ image = Image.open(file)
211
+ images.append(image)
212
+ except Exception as e:
213
+ st.error(f"Error opening image {file.name}: {e}")
214
+
215
+ st.session_state["uploaded_images"] = images
216
+
217
+ # Display uploaded images in a gallery
218
+ if images:
219
+ st.write(f"**{len(images)} images uploaded**")
220
+ image_cols = st.columns(min(3, len(images)))
221
+ for i, img in enumerate(images):
222
+ with image_cols[i % min(3, len(images))]:
223
+ st.image(img, width=150, caption=f"Image {i+1}")
 
 
 
224
 
225
+ st.subheader("🏠 Property Details")
 
226
 
227
+ property_type = st.selectbox("Property Type",
228
+ ["Single Family Home", "Townhouse", "Condo/Apartment", "Multi-Family"])
229
 
230
+ price_range = st.selectbox("Property Price Range",
231
+ ["Entry-level", "Mid-range", "Luxury"])
 
 
232
 
233
+ timeframe = st.radio("When do you plan to sell?",
234
+ ["Less than 3 months", "3-6 months", "6+ months"])
 
235
 
236
+ additional_details = st.text_area("Additional Details",
237
+ placeholder="Describe specific areas you're considering renovating or any concerns.",
238
+ height=80)
 
 
239
 
240
+ # Make button more mobile-friendly
241
+ submit_button = st.button('🔍 Analyze My Home', use_container_width=True,
242
+ disabled=len(st.session_state["uploaded_images"]) == 0)
 
243
 
244
+ if len(st.session_state["uploaded_images"]) == 0:
245
+ st.warning("Please upload at least one photo of your home to receive recommendations.")
246
 
247
+ with col2:
248
+ st.subheader("💡 Improvement Recommendations")
249
+ result_placeholder = st.empty()
 
250
 
251
+ if submit_button:
252
+ if st.session_state["api_calls"] >= MAX_REQUESTS_PER_SESSION:
253
+ st.error("🚨 Request limit reached. Please try again later.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  else:
255
+ with st.spinner("Analyzing your home and generating recommendations..."):
256
+ messages = initial_messages.copy()
257
+ reply, _ = generate_recommendations(st.session_state["uploaded_images"],
258
+ property_type, price_range, timeframe,
259
+ additional_details, messages)
260
+ result_placeholder.markdown(reply)
261
+ else:
262
+ result_placeholder.markdown("**Results will appear here after you submit your home photos and details**")
263
 
264
+ # Lead capture form - appears after recommendations are shown
265
+ if submit_button and "api_calls" in st.session_state and st.session_state["api_calls"] > 0:
266
+ st.write("---")
267
+ st.subheader("📋 Get a FREE In-Depth Consultation")
 
 
 
 
 
 
268
 
269
+ st.write("Want a professional to review these recommendations and provide personalized guidance?")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
+ name = st.text_input("Your Name")
272
+ email = st.text_input("Your Email")
273
+ phone = st.text_input("Your Phone (optional)")
274
 
275
+ if st.button("Request Free Consultation", use_container_width=True):
276
+ if name and email:
277
+ st.success("Thanks! A real estate professional will contact you within 24 hours to discuss your home value maximization strategy.")
278
+ # Here you would typically add code to save the lead information
279
+ # For example, sending to an email, CRM, or database
280
+ else:
281
+ st.warning("Please provide your name and email to request a consultation.")
282
+
283
+ # Add disclaimer
284
+ st.markdown("<div style='height: 30px'></div>", unsafe_allow_html=True) # Add space
285
+ st.markdown("---") # Horizontal line for separation
286
+
287
+ # Add extra spacing before disclaimer to ensure it's scrollable on iOS
288
+ st.markdown("<div style='height: 20px'></div>", unsafe_allow_html=True)
289
+
290
+ with st.expander("📜 **Disclaimer (Click to Expand)**"):
291
+ st.markdown("""
292
+ **Disclaimer:**
293
+ This Home Value Maximizer tool is powered by AI and provides general recommendations based on the photos and information you provide.
294
+ Results are for **informational purposes only** and may not be accurate for your specific situation. Please consult with real estate professionals before making significant investments.
295
+
296
+ **Fair Housing Compliance:**
297
+ This tool complies with Fair Housing laws and does **not** make recommendations based on race, color, religion, sex, disability, familial status, or national origin.
298
+
299
+ **User Responsibility & Liability:**
300
+ Users are responsible for any decisions made using this tool. We disclaim any liability for damages or losses.
301
+ By using this tool, you agree to do so at your **own risk**.
302
+ """)
303
+
304
+ # Add extra padding at the bottom to ensure scrollability on iOS
305
+ st.markdown("<div style='height: 100px'></div>", unsafe_allow_html=True)
306
+
307
+ # Add invisible element at the very bottom to ensure scrollability
308
+ st.markdown("<div id='bottom-anchor' style='height:1px;'></div>", unsafe_allow_html=True)
309
+
310
+ # JavaScript to help with iOS scrolling
311
+ st.markdown("""
312
+ <script>
313
+ // Help iOS recognize the full scrollable height
314
+ function fixIOSScrolling() {
315
+ if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
316
+ // Force layout recalculation
317
+ document.body.style.display = 'none';
318
+ document.body.offsetHeight; // Trigger reflow
319
+ document.body.style.display = '';
320
 
321
+ // Add extra padding to bottom if needed
322
+ const container = document.querySelector('.main .block-container');
323
+ if (container) {
324
+ container.style.paddingBottom = '150px';
325
+ }
 
 
 
 
 
 
 
 
 
326
 
327
+ // Ensure scrollability by briefly scrolling to bottom
328
+ window.setTimeout(function() {
329
+ const bottomElement = document.getElementById('bottom-anchor');
330
+ if (bottomElement) {
331
+ bottomElement.scrollIntoView();
332
+ window.scrollTo(0, 0);
333
+ }
334
+ }, 500);
335
+ }
336
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
+ // Run when page loads
339
+ window.addEventListener('load', fixIOSScrolling);
340
+ </script>
341
+ """, unsafe_allow_html=True)