Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -28,20 +28,20 @@ st.markdown(
|
|
| 28 |
unsafe_allow_html=True
|
| 29 |
)
|
| 30 |
|
| 31 |
-
# --- Configuration ---
|
| 32 |
API_URL = "https://api.openai.com/v1/chat/completions"
|
| 33 |
|
| 34 |
-
# ---
|
| 35 |
@st.cache_data
|
| 36 |
def encode_image_to_b64(image: Image.Image) -> str:
|
| 37 |
# Convert to RGB if needed
|
| 38 |
if image.mode == 'RGBA':
|
| 39 |
-
bg = Image.new('RGB', image.size, (255,255,255))
|
| 40 |
bg.paste(image, mask=image.split()[3])
|
| 41 |
image = bg
|
| 42 |
elif image.mode not in ['RGB', 'L']:
|
| 43 |
image = image.convert('RGB')
|
| 44 |
-
# Resize
|
| 45 |
max_size = (1024, 1024)
|
| 46 |
if image.width > max_size[0] or image.height > max_size[1]:
|
| 47 |
image.thumbnail(max_size, Image.LANCZOS)
|
|
@@ -49,23 +49,27 @@ def encode_image_to_b64(image: Image.Image) -> str:
|
|
| 49 |
image.save(buf, format='JPEG', quality=85)
|
| 50 |
return base64.b64encode(buf.getvalue()).decode('utf-8')
|
| 51 |
|
|
|
|
| 52 |
def fix_formatting(text: str) -> str:
|
| 53 |
text = re.sub(r'(\d+)([A-Za-z])', r'\1 \2', text)
|
| 54 |
text = re.sub(r'([.,])([A-Za-z0-9])', r'\1 \2', text)
|
| 55 |
text = re.sub(r':([A-Za-z0-9])', r': \1', text)
|
| 56 |
return text
|
| 57 |
|
| 58 |
-
# ---
|
| 59 |
def analyze_home_photos(images, timeframe, details, api_key):
|
| 60 |
if not api_key:
|
| 61 |
return "Error: API key is required."
|
| 62 |
-
headers = {
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
| 64 |
system_prompt = (
|
| 65 |
f"You are a real estate advisor. Today's date is {datetime.now().strftime('%B %d, %Y')}. "
|
| 66 |
-
"Analyze only visible areas
|
| 67 |
)
|
| 68 |
-
#
|
| 69 |
user_content = f"Sell within {timeframe}. {details}\n\n"
|
| 70 |
for img in images:
|
| 71 |
b64 = encode_image_to_b64(img)
|
|
@@ -81,29 +85,34 @@ def analyze_home_photos(images, timeframe, details, api_key):
|
|
| 81 |
resp = requests.post(API_URL, headers=headers, json=payload, timeout=90)
|
| 82 |
if resp.status_code == 200:
|
| 83 |
data = resp.json()
|
| 84 |
-
|
| 85 |
-
return fix_formatting(text)
|
| 86 |
return f"API Error {resp.status_code}: {resp.text[:200]}"
|
| 87 |
|
| 88 |
# --- Cost calculation ---
|
| 89 |
def calculate_cost(usage):
|
| 90 |
if not usage:
|
| 91 |
return 0.0
|
| 92 |
-
in_tok = usage.get('prompt_tokens',0)
|
| 93 |
-
out_tok = usage.get('completion_tokens',0)
|
| 94 |
-
|
| 95 |
-
return cost
|
| 96 |
|
| 97 |
-
# ---
|
| 98 |
st.title("Home Value Maximizer π‘")
|
| 99 |
|
| 100 |
# Sidebar inputs
|
| 101 |
api_key = st.sidebar.text_input("OpenAI API Key", type="password")
|
| 102 |
-
uploaded = st.sidebar.file_uploader(
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
| 105 |
budget = st.sidebar.slider("Max improvement budget ($)", 1000, 50000, 10000, 1000)
|
| 106 |
-
focus = st.sidebar.multiselect(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
if st.sidebar.button("π Analyze My Home"):
|
| 109 |
if not api_key:
|
|
@@ -113,14 +122,23 @@ if st.sidebar.button("π Analyze My Home"):
|
|
| 113 |
else:
|
| 114 |
images = []
|
| 115 |
for f in uploaded:
|
| 116 |
-
try:
|
| 117 |
-
|
|
|
|
|
|
|
| 118 |
with st.spinner("Analyzing..."):
|
| 119 |
-
result = analyze_home_photos(
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
cost = calculate_cost(usage)
|
| 125 |
st.metric("π² Estimated cost", f"${cost:.2f}")
|
| 126 |
-
```
|
|
|
|
| 28 |
unsafe_allow_html=True
|
| 29 |
)
|
| 30 |
|
| 31 |
+
# --- API Configuration ---
|
| 32 |
API_URL = "https://api.openai.com/v1/chat/completions"
|
| 33 |
|
| 34 |
+
# --- Caching helpers ---
|
| 35 |
@st.cache_data
|
| 36 |
def encode_image_to_b64(image: Image.Image) -> str:
|
| 37 |
# Convert to RGB if needed
|
| 38 |
if image.mode == 'RGBA':
|
| 39 |
+
bg = Image.new('RGB', image.size, (255, 255, 255))
|
| 40 |
bg.paste(image, mask=image.split()[3])
|
| 41 |
image = bg
|
| 42 |
elif image.mode not in ['RGB', 'L']:
|
| 43 |
image = image.convert('RGB')
|
| 44 |
+
# Resize large images
|
| 45 |
max_size = (1024, 1024)
|
| 46 |
if image.width > max_size[0] or image.height > max_size[1]:
|
| 47 |
image.thumbnail(max_size, Image.LANCZOS)
|
|
|
|
| 49 |
image.save(buf, format='JPEG', quality=85)
|
| 50 |
return base64.b64encode(buf.getvalue()).decode('utf-8')
|
| 51 |
|
| 52 |
+
|
| 53 |
def fix_formatting(text: str) -> str:
|
| 54 |
text = re.sub(r'(\d+)([A-Za-z])', r'\1 \2', text)
|
| 55 |
text = re.sub(r'([.,])([A-Za-z0-9])', r'\1 \2', text)
|
| 56 |
text = re.sub(r':([A-Za-z0-9])', r': \1', text)
|
| 57 |
return text
|
| 58 |
|
| 59 |
+
# --- Core analysis ---
|
| 60 |
def analyze_home_photos(images, timeframe, details, api_key):
|
| 61 |
if not api_key:
|
| 62 |
return "Error: API key is required."
|
| 63 |
+
headers = {
|
| 64 |
+
"Authorization": f"Bearer {api_key}",
|
| 65 |
+
"Content-Type": "application/json"
|
| 66 |
+
}
|
| 67 |
+
# System prompt
|
| 68 |
system_prompt = (
|
| 69 |
f"You are a real estate advisor. Today's date is {datetime.now().strftime('%B %d, %Y')}. "
|
| 70 |
+
"Analyze only visible areas and give hyper-specific recommendations."
|
| 71 |
)
|
| 72 |
+
# User content with embedded images
|
| 73 |
user_content = f"Sell within {timeframe}. {details}\n\n"
|
| 74 |
for img in images:
|
| 75 |
b64 = encode_image_to_b64(img)
|
|
|
|
| 85 |
resp = requests.post(API_URL, headers=headers, json=payload, timeout=90)
|
| 86 |
if resp.status_code == 200:
|
| 87 |
data = resp.json()
|
| 88 |
+
return fix_formatting(data['choices'][0]['message']['content'])
|
|
|
|
| 89 |
return f"API Error {resp.status_code}: {resp.text[:200]}"
|
| 90 |
|
| 91 |
# --- Cost calculation ---
|
| 92 |
def calculate_cost(usage):
|
| 93 |
if not usage:
|
| 94 |
return 0.0
|
| 95 |
+
in_tok = usage.get('prompt_tokens', 0)
|
| 96 |
+
out_tok = usage.get('completion_tokens', 0)
|
| 97 |
+
return in_tok/1e6*3 + out_tok/1e6*10
|
|
|
|
| 98 |
|
| 99 |
+
# --- UI layout ---
|
| 100 |
st.title("Home Value Maximizer π‘")
|
| 101 |
|
| 102 |
# Sidebar inputs
|
| 103 |
api_key = st.sidebar.text_input("OpenAI API Key", type="password")
|
| 104 |
+
uploaded = st.sidebar.file_uploader(
|
| 105 |
+
"Upload home photos", accept_multiple_files=True, type=["jpg", "jpeg", "png"]
|
| 106 |
+
)
|
| 107 |
+
timeframe = st.sidebar.selectbox(
|
| 108 |
+
"Selling timeframe", ["Within 1 month", "1β3 months", "3β6 months", "6β12 months", ">12 months"]
|
| 109 |
+
)
|
| 110 |
budget = st.sidebar.slider("Max improvement budget ($)", 1000, 50000, 10000, 1000)
|
| 111 |
+
focus = st.sidebar.multiselect(
|
| 112 |
+
"Focus areas",
|
| 113 |
+
["Curb appeal", "Kitchen", "Bathroom", "Living Spaces", "Outdoor", "Storage"],
|
| 114 |
+
default=["Kitchen", "Bathroom"]
|
| 115 |
+
)
|
| 116 |
|
| 117 |
if st.sidebar.button("π Analyze My Home"):
|
| 118 |
if not api_key:
|
|
|
|
| 122 |
else:
|
| 123 |
images = []
|
| 124 |
for f in uploaded:
|
| 125 |
+
try:
|
| 126 |
+
images.append(Image.open(f))
|
| 127 |
+
except:
|
| 128 |
+
pass
|
| 129 |
with st.spinner("Analyzing..."):
|
| 130 |
+
result = analyze_home_photos(
|
| 131 |
+
images,
|
| 132 |
+
timeframe,
|
| 133 |
+
f"Budget ${budget}. Focus: {', '.join(focus)}",
|
| 134 |
+
api_key
|
| 135 |
+
)
|
| 136 |
+
# Render result with styled container
|
| 137 |
+
st.markdown(
|
| 138 |
+
f"<div style='background:#e3f2fd;padding:1rem;border-left:4px solid #1f77b4;'>{result}</div>",
|
| 139 |
+
unsafe_allow_html=True
|
| 140 |
+
)
|
| 141 |
+
# Display cost (usage info not returned in HF)
|
| 142 |
+
usage = {}
|
| 143 |
cost = calculate_cost(usage)
|
| 144 |
st.metric("π² Estimated cost", f"${cost:.2f}")
|
|
|