Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
from PIL import Image
|
| 3 |
from io import BytesIO
|
| 4 |
-
import
|
| 5 |
import base64
|
| 6 |
import time
|
| 7 |
from datetime import datetime
|
|
@@ -28,13 +28,10 @@ st.markdown(
|
|
| 28 |
unsafe_allow_html=True
|
| 29 |
)
|
| 30 |
|
| 31 |
-
# ---
|
| 32 |
-
|
| 33 |
-
def get_openai_client(api_key: str):
|
| 34 |
-
openai.api_key = api_key
|
| 35 |
-
return openai
|
| 36 |
|
| 37 |
-
# ---
|
| 38 |
@st.cache_data
|
| 39 |
def encode_image_to_b64(image: Image.Image) -> str:
|
| 40 |
# Convert to RGB if needed
|
|
@@ -52,94 +49,78 @@ def encode_image_to_b64(image: Image.Image) -> str:
|
|
| 52 |
image.save(buf, format='JPEG', quality=85)
|
| 53 |
return base64.b64encode(buf.getvalue()).decode('utf-8')
|
| 54 |
|
| 55 |
-
# --- Simple formatting fixes ---
|
| 56 |
def fix_formatting(text: str) -> str:
|
| 57 |
text = re.sub(r'(\d+)([A-Za-z])', r'\1 \2', text)
|
| 58 |
text = re.sub(r'([.,])([A-Za-z0-9])', r'\1 \2', text)
|
| 59 |
text = re.sub(r':([A-Za-z0-9])', r': \1', text)
|
| 60 |
return text
|
| 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 |
-
# --- Streamlit layout ---
|
| 104 |
st.title("Home Value Maximizer π‘")
|
| 105 |
-
st.write("Upload photos, enter your timeline & budget, then click Analyze for detailed, hyper-specific advice.")
|
| 106 |
|
| 107 |
-
#
|
| 108 |
api_key = st.sidebar.text_input("OpenAI API Key", type="password")
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
# Upload images
|
| 112 |
-
st.sidebar.header("πΈ Upload Home Photos")
|
| 113 |
-
uploaded = st.sidebar.file_uploader("Images", type=["jpg","png","jpeg"], accept_multiple_files=True)
|
| 114 |
-
images = []
|
| 115 |
-
for file in uploaded:
|
| 116 |
-
try:
|
| 117 |
-
img = Image.open(file)
|
| 118 |
-
images.append(img)
|
| 119 |
-
except:
|
| 120 |
-
st.sidebar.error(f"Failed to load {file.name}")
|
| 121 |
|
| 122 |
-
# Inputs
|
| 123 |
timeframe = st.sidebar.selectbox("Selling timeframe", ["Within 1 month","1β3 months","3β6 months","6β12 months",">12 months"])
|
| 124 |
budget = st.sidebar.slider("Max improvement budget ($)", 1000, 50000, 10000, 1000)
|
| 125 |
-
|
| 126 |
|
| 127 |
-
|
| 128 |
-
if
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
st.error("Upload at least one photo.")
|
| 133 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
with st.spinner("Analyzing..."):
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
else:
|
| 145 |
-
st.info("Submit images & API key to generate recommendations.")
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
from PIL import Image
|
| 3 |
from io import BytesIO
|
| 4 |
+
import requests
|
| 5 |
import base64
|
| 6 |
import time
|
| 7 |
from datetime import datetime
|
|
|
|
| 28 |
unsafe_allow_html=True
|
| 29 |
)
|
| 30 |
|
| 31 |
+
# --- Configuration ---
|
| 32 |
+
API_URL = "https://api.openai.com/v1/chat/completions"
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
# --- Cache utilities ---
|
| 35 |
@st.cache_data
|
| 36 |
def encode_image_to_b64(image: Image.Image) -> str:
|
| 37 |
# Convert to RGB if needed
|
|
|
|
| 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 |
+
# --- Analysis function via requests ---
|
| 59 |
+
def analyze_home_photos(images, timeframe, details, api_key):
|
| 60 |
+
if not api_key:
|
| 61 |
+
return "Error: API key is required."
|
| 62 |
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
| 63 |
+
# Build system prompt
|
| 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 in the images and give hyper-specific recommendations."
|
| 67 |
+
)
|
| 68 |
+
# Build user content with embedded images
|
| 69 |
+
user_content = f"Sell within {timeframe}. {details}\n\n"
|
| 70 |
+
for img in images:
|
| 71 |
+
b64 = encode_image_to_b64(img)
|
| 72 |
+
user_content += f"\n\n"
|
| 73 |
+
payload = {
|
| 74 |
+
"model": "gpt-4o",
|
| 75 |
+
"messages": [
|
| 76 |
+
{"role": "system", "content": system_prompt},
|
| 77 |
+
{"role": "user", "content": user_content}
|
| 78 |
+
],
|
| 79 |
+
"max_tokens": 2000
|
| 80 |
+
}
|
| 81 |
+
resp = requests.post(API_URL, headers=headers, json=payload, timeout=90)
|
| 82 |
+
if resp.status_code == 200:
|
| 83 |
+
data = resp.json()
|
| 84 |
+
text = data['choices'][0]['message']['content']
|
| 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 |
+
cost = in_tok/1e6*3 + out_tok/1e6*10
|
| 95 |
+
return cost
|
| 96 |
+
|
| 97 |
+
# --- App layout ---
|
|
|
|
|
|
|
| 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("Upload home photos", accept_multiple_files=True, type=["jpg","png","jpeg"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
|
|
|
| 104 |
timeframe = st.sidebar.selectbox("Selling timeframe", ["Within 1 month","1β3 months","3β6 months","6β12 months",">12 months"])
|
| 105 |
budget = st.sidebar.slider("Max improvement budget ($)", 1000, 50000, 10000, 1000)
|
| 106 |
+
focus = st.sidebar.multiselect("Focus areas", ["Curb appeal","Kitchen","Bathroom","Living Spaces","Outdoor","Storage"], default=["Kitchen","Bathroom"])
|
| 107 |
|
| 108 |
+
if st.sidebar.button("π Analyze My Home"):
|
| 109 |
+
if not api_key:
|
| 110 |
+
st.sidebar.error("Enter API key.")
|
| 111 |
+
elif not uploaded:
|
| 112 |
+
st.sidebar.error("Upload at least one photo.")
|
|
|
|
| 113 |
else:
|
| 114 |
+
images = []
|
| 115 |
+
for f in uploaded:
|
| 116 |
+
try: images.append(Image.open(f))
|
| 117 |
+
except: pass
|
| 118 |
with st.spinner("Analyzing..."):
|
| 119 |
+
result = analyze_home_photos(images, timeframe, f"Budget ${budget}. Focus: {', '.join(focus)}", api_key)
|
| 120 |
+
st.markdown(f"<div style='background:#e3f2fd;padding:1rem;border-left:4px solid #1f77b4;'>{result}</div>", unsafe_allow_html=True)
|
| 121 |
+
# Cost display
|
| 122 |
+
# Note: HF Spaces does not return usage, so this may be blank
|
| 123 |
+
usage = {} # placeholder
|
| 124 |
+
cost = calculate_cost(usage)
|
| 125 |
+
st.metric("π² Estimated cost", f"${cost:.2f}")
|
| 126 |
+
```
|
|
|
|
|
|
|
|
|