Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -47,24 +47,25 @@ st.markdown("""
|
|
| 47 |
}
|
| 48 |
.car-spec-output {
|
| 49 |
font-family: "Segoe UI", sans-serif;
|
| 50 |
-
font-size:
|
| 51 |
background-color: #ffffff;
|
| 52 |
color: #003B6F;
|
| 53 |
-
padding:
|
| 54 |
border-radius: 10px;
|
| 55 |
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
| 56 |
-
border-left:
|
| 57 |
-
line-height: 1.
|
| 58 |
}
|
| 59 |
.spec-table {
|
| 60 |
width: 100%;
|
| 61 |
border-collapse: collapse;
|
| 62 |
-
margin-top:
|
| 63 |
-
margin-bottom:
|
|
|
|
| 64 |
}
|
| 65 |
.spec-table th, .spec-table td {
|
| 66 |
border: 1px solid #ddd;
|
| 67 |
-
padding:
|
| 68 |
text-align: left;
|
| 69 |
}
|
| 70 |
.spec-table th {
|
|
@@ -75,9 +76,9 @@ st.markdown("""
|
|
| 75 |
background-color: #f7f9fc;
|
| 76 |
}
|
| 77 |
.icon {
|
| 78 |
-
width:
|
| 79 |
vertical-align: middle;
|
| 80 |
-
margin-right:
|
| 81 |
}
|
| 82 |
</style>
|
| 83 |
""", unsafe_allow_html=True)
|
|
@@ -85,13 +86,14 @@ st.markdown("""
|
|
| 85 |
st.markdown("""
|
| 86 |
<div style='text-align: center; margin-top: 20px; margin-bottom: -10px;'>
|
| 87 |
<span style='display: inline-flex; align-items: center; gap: 8px;'>
|
| 88 |
-
<img src='https://www.carfind.co.za/images/Carfind-Icon.svg' width='
|
| 89 |
-
<span style='font-size:
|
| 90 |
</span>
|
| 91 |
</div>
|
| 92 |
""", unsafe_allow_html=True)
|
| 93 |
|
| 94 |
-
#
|
|
|
|
| 95 |
def get_or_create_thread_id():
|
| 96 |
doc_ref = db.collection("users").document(user_id)
|
| 97 |
doc = doc_ref.get()
|
|
@@ -113,33 +115,23 @@ def save_message(role, content):
|
|
| 113 |
})
|
| 114 |
|
| 115 |
def display_chat_history():
|
| 116 |
-
messages = db.collection("users").document(user_id).collection("messages")
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
assistant_icon_html = "<img src='https://www.carfind.co.za/images/Carfind-Icon.svg' width='22' style='vertical-align:middle;'/>"
|
| 120 |
for msg in list(messages)[::-1]:
|
| 121 |
data = msg.to_dict()
|
| 122 |
if data["role"] == "user":
|
| 123 |
-
st.markdown(
|
| 124 |
-
f"<div class='stChatMessage' data-testid='stChatMessage-user'>"
|
| 125 |
-
f"π€ <strong>You:</strong> {data['content']}</div>", unsafe_allow_html=True
|
| 126 |
-
)
|
| 127 |
else:
|
| 128 |
-
st.markdown(
|
| 129 |
-
f"<div class='stChatMessage' data-testid='stChatMessage-assistant'>"
|
| 130 |
-
f"{assistant_icon_html} <strong>Carfind Assistant:</strong> {data['content']}</div>",
|
| 131 |
-
unsafe_allow_html=True
|
| 132 |
-
)
|
| 133 |
|
| 134 |
-
#
|
| 135 |
tab1, tab2 = st.tabs(["AI Chat", "What car is that?"])
|
| 136 |
|
| 137 |
-
#
|
| 138 |
with tab1:
|
| 139 |
input_col, clear_col = st.columns([9, 1])
|
| 140 |
with input_col:
|
| 141 |
user_input = st.chat_input("Type your message here...")
|
| 142 |
-
|
| 143 |
with clear_col:
|
| 144 |
if st.button("ποΈ", key="clear-chat", help="Clear Chat"):
|
| 145 |
try:
|
|
@@ -151,14 +143,12 @@ with tab1:
|
|
| 151 |
st.rerun()
|
| 152 |
except Exception as e:
|
| 153 |
st.error(f"Failed to clear chat: {e}")
|
| 154 |
-
|
| 155 |
thread_id = get_or_create_thread_id()
|
| 156 |
display_chat_history()
|
| 157 |
|
| 158 |
if user_input:
|
| 159 |
client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
|
| 160 |
save_message("user", user_input)
|
| 161 |
-
|
| 162 |
with st.spinner("Thinking and typing... π"):
|
| 163 |
run = client.beta.threads.runs.create(thread_id=thread_id, assistant_id=assistant_id)
|
| 164 |
while True:
|
|
@@ -166,7 +156,6 @@ with tab1:
|
|
| 166 |
if run_status.status == "completed":
|
| 167 |
break
|
| 168 |
time.sleep(1)
|
| 169 |
-
|
| 170 |
messages_response = client.beta.threads.messages.list(thread_id=thread_id)
|
| 171 |
latest_response = sorted(messages_response.data, key=lambda x: x.created_at)[-1]
|
| 172 |
assistant_message = latest_response.content[0].text.value
|
|
@@ -174,78 +163,50 @@ with tab1:
|
|
| 174 |
time.sleep(0.5)
|
| 175 |
st.rerun()
|
| 176 |
|
| 177 |
-
#
|
| 178 |
with tab2:
|
| 179 |
uploaded_image = st.file_uploader("Upload an image of a car and let Ai identify it for you", type=["jpg", "jpeg", "png"])
|
| 180 |
-
|
| 181 |
if uploaded_image:
|
| 182 |
col1, col2 = st.columns([1.2, 1.8])
|
| 183 |
-
|
| 184 |
with col1:
|
| 185 |
image = Image.open(uploaded_image)
|
| 186 |
st.image(image, caption="Uploaded Image", use_container_width=True)
|
| 187 |
-
|
| 188 |
with col2:
|
| 189 |
try:
|
| 190 |
image_thread = client.beta.threads.create()
|
| 191 |
file_response = client.files.create(file=uploaded_image, purpose="assistants")
|
| 192 |
-
|
| 193 |
client.beta.threads.messages.create(
|
| 194 |
thread_id=image_thread.id,
|
| 195 |
role="user",
|
| 196 |
content=[
|
| 197 |
-
{
|
| 198 |
-
|
| 199 |
-
"image_file": {"file_id": file_response.id}
|
| 200 |
-
},
|
| 201 |
-
{
|
| 202 |
-
"type": "text",
|
| 203 |
-
"text": "Please identify this car from the image and include a full vehicle specification table, a short overview of the car, and who it's recommended for."
|
| 204 |
-
}
|
| 205 |
]
|
| 206 |
)
|
| 207 |
-
|
| 208 |
-
run = client.beta.threads.runs.create(
|
| 209 |
-
thread_id=image_thread.id,
|
| 210 |
-
assistant_id=assistant_id
|
| 211 |
-
)
|
| 212 |
-
|
| 213 |
with st.spinner("π Analyzing image and identifying the car..."):
|
| 214 |
while True:
|
| 215 |
-
run_status = client.beta.threads.runs.retrieve(
|
| 216 |
-
thread_id=image_thread.id,
|
| 217 |
-
run_id=run.id
|
| 218 |
-
)
|
| 219 |
if run_status.status == "completed":
|
| 220 |
break
|
| 221 |
time.sleep(1)
|
| 222 |
-
|
| 223 |
messages = client.beta.threads.messages.list(thread_id=image_thread.id)
|
| 224 |
assistant_message = messages.data[0].content[0].text.value
|
| 225 |
-
|
| 226 |
cleaned_message = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', assistant_message)
|
| 227 |
cleaned_message = cleaned_message.replace("###", "<h4 style='margin-bottom: 10px;'>").replace("\n", "<br>")
|
| 228 |
-
|
| 229 |
-
# Extract markdown table (if present)
|
| 230 |
if '|' in cleaned_message:
|
| 231 |
lines = cleaned_message.split('<br>')
|
| 232 |
-
html_table = "
|
| 233 |
for line in lines:
|
| 234 |
-
if '|' in line:
|
| 235 |
cells = line.strip('|').split('|')
|
| 236 |
row_html = '<tr>' + ''.join(f'<td>{cell.strip()}</td>' for cell in cells) + '</tr>'
|
| 237 |
html_table += row_html
|
| 238 |
-
|
| 239 |
html_table += f"</table><p>{line}</p><table class='spec-table'>"
|
| 240 |
html_table += "</table>"
|
| 241 |
cleaned_message = html_table
|
| 242 |
-
|
| 243 |
st.success("β
Identification Complete")
|
| 244 |
-
st.markdown(f""
|
| 245 |
-
<div class='car-spec-output'>
|
| 246 |
-
{cleaned_message}
|
| 247 |
-
</div>
|
| 248 |
-
""", unsafe_allow_html=True)
|
| 249 |
-
|
| 250 |
except Exception as e:
|
| 251 |
st.error(f"β Error during image analysis: {str(e)}")
|
|
|
|
| 47 |
}
|
| 48 |
.car-spec-output {
|
| 49 |
font-family: "Segoe UI", sans-serif;
|
| 50 |
+
font-size: 10px;
|
| 51 |
background-color: #ffffff;
|
| 52 |
color: #003B6F;
|
| 53 |
+
padding: 18px;
|
| 54 |
border-radius: 10px;
|
| 55 |
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
| 56 |
+
border-left: 4px solid #0071BC;
|
| 57 |
+
line-height: 1.5;
|
| 58 |
}
|
| 59 |
.spec-table {
|
| 60 |
width: 100%;
|
| 61 |
border-collapse: collapse;
|
| 62 |
+
margin-top: 12px;
|
| 63 |
+
margin-bottom: 16px;
|
| 64 |
+
font-size: 10px;
|
| 65 |
}
|
| 66 |
.spec-table th, .spec-table td {
|
| 67 |
border: 1px solid #ddd;
|
| 68 |
+
padding: 6px 8px;
|
| 69 |
text-align: left;
|
| 70 |
}
|
| 71 |
.spec-table th {
|
|
|
|
| 76 |
background-color: #f7f9fc;
|
| 77 |
}
|
| 78 |
.icon {
|
| 79 |
+
width: 14px;
|
| 80 |
vertical-align: middle;
|
| 81 |
+
margin-right: 5px;
|
| 82 |
}
|
| 83 |
</style>
|
| 84 |
""", unsafe_allow_html=True)
|
|
|
|
| 86 |
st.markdown("""
|
| 87 |
<div style='text-align: center; margin-top: 20px; margin-bottom: -10px;'>
|
| 88 |
<span style='display: inline-flex; align-items: center; gap: 8px;'>
|
| 89 |
+
<img src='https://www.carfind.co.za/images/Carfind-Icon.svg' width='28' class='carfind-logo'/>
|
| 90 |
+
<span style='font-size: 12px; color: gray;'>Powered by Carfind</span>
|
| 91 |
</span>
|
| 92 |
</div>
|
| 93 |
""", unsafe_allow_html=True)
|
| 94 |
|
| 95 |
+
# Firebase Chat Functions
|
| 96 |
+
|
| 97 |
def get_or_create_thread_id():
|
| 98 |
doc_ref = db.collection("users").document(user_id)
|
| 99 |
doc = doc_ref.get()
|
|
|
|
| 115 |
})
|
| 116 |
|
| 117 |
def display_chat_history():
|
| 118 |
+
messages = db.collection("users").document(user_id).collection("messages").order_by("timestamp").stream()
|
| 119 |
+
assistant_icon_html = "<img src='https://www.carfind.co.za/images/Carfind-Icon.svg' width='20' style='vertical-align:middle;'/>"
|
|
|
|
|
|
|
| 120 |
for msg in list(messages)[::-1]:
|
| 121 |
data = msg.to_dict()
|
| 122 |
if data["role"] == "user":
|
| 123 |
+
st.markdown(f"<div class='stChatMessage' data-testid='stChatMessage-user'>π€ <strong>You:</strong> {data['content']}</div>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
| 124 |
else:
|
| 125 |
+
st.markdown(f"<div class='stChatMessage' data-testid='stChatMessage-assistant'>{assistant_icon_html} <strong>Carfind Assistant:</strong> {data['content']}</div>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
# Tabs: AI Chat | Car Identifier
|
| 128 |
tab1, tab2 = st.tabs(["AI Chat", "What car is that?"])
|
| 129 |
|
| 130 |
+
# AI Chat Tab
|
| 131 |
with tab1:
|
| 132 |
input_col, clear_col = st.columns([9, 1])
|
| 133 |
with input_col:
|
| 134 |
user_input = st.chat_input("Type your message here...")
|
|
|
|
| 135 |
with clear_col:
|
| 136 |
if st.button("ποΈ", key="clear-chat", help="Clear Chat"):
|
| 137 |
try:
|
|
|
|
| 143 |
st.rerun()
|
| 144 |
except Exception as e:
|
| 145 |
st.error(f"Failed to clear chat: {e}")
|
|
|
|
| 146 |
thread_id = get_or_create_thread_id()
|
| 147 |
display_chat_history()
|
| 148 |
|
| 149 |
if user_input:
|
| 150 |
client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
|
| 151 |
save_message("user", user_input)
|
|
|
|
| 152 |
with st.spinner("Thinking and typing... π"):
|
| 153 |
run = client.beta.threads.runs.create(thread_id=thread_id, assistant_id=assistant_id)
|
| 154 |
while True:
|
|
|
|
| 156 |
if run_status.status == "completed":
|
| 157 |
break
|
| 158 |
time.sleep(1)
|
|
|
|
| 159 |
messages_response = client.beta.threads.messages.list(thread_id=thread_id)
|
| 160 |
latest_response = sorted(messages_response.data, key=lambda x: x.created_at)[-1]
|
| 161 |
assistant_message = latest_response.content[0].text.value
|
|
|
|
| 163 |
time.sleep(0.5)
|
| 164 |
st.rerun()
|
| 165 |
|
| 166 |
+
# Car Image Tab
|
| 167 |
with tab2:
|
| 168 |
uploaded_image = st.file_uploader("Upload an image of a car and let Ai identify it for you", type=["jpg", "jpeg", "png"])
|
|
|
|
| 169 |
if uploaded_image:
|
| 170 |
col1, col2 = st.columns([1.2, 1.8])
|
|
|
|
| 171 |
with col1:
|
| 172 |
image = Image.open(uploaded_image)
|
| 173 |
st.image(image, caption="Uploaded Image", use_container_width=True)
|
|
|
|
| 174 |
with col2:
|
| 175 |
try:
|
| 176 |
image_thread = client.beta.threads.create()
|
| 177 |
file_response = client.files.create(file=uploaded_image, purpose="assistants")
|
|
|
|
| 178 |
client.beta.threads.messages.create(
|
| 179 |
thread_id=image_thread.id,
|
| 180 |
role="user",
|
| 181 |
content=[
|
| 182 |
+
{"type": "image_file", "image_file": {"file_id": file_response.id}},
|
| 183 |
+
{"type": "text", "text": "Please identify this car from the image and include a clean HTML-styled specification table without Year or Mileage fields, and avoid markdown or dotted lines."}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
]
|
| 185 |
)
|
| 186 |
+
run = client.beta.threads.runs.create(thread_id=image_thread.id, assistant_id=assistant_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
with st.spinner("π Analyzing image and identifying the car..."):
|
| 188 |
while True:
|
| 189 |
+
run_status = client.beta.threads.runs.retrieve(thread_id=image_thread.id, run_id=run.id)
|
|
|
|
|
|
|
|
|
|
| 190 |
if run_status.status == "completed":
|
| 191 |
break
|
| 192 |
time.sleep(1)
|
|
|
|
| 193 |
messages = client.beta.threads.messages.list(thread_id=image_thread.id)
|
| 194 |
assistant_message = messages.data[0].content[0].text.value
|
|
|
|
| 195 |
cleaned_message = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', assistant_message)
|
| 196 |
cleaned_message = cleaned_message.replace("###", "<h4 style='margin-bottom: 10px;'>").replace("\n", "<br>")
|
|
|
|
|
|
|
| 197 |
if '|' in cleaned_message:
|
| 198 |
lines = cleaned_message.split('<br>')
|
| 199 |
+
html_table = "<table class='spec-table'>"
|
| 200 |
for line in lines:
|
| 201 |
+
if '|' in line and '---' not in line and 'Year' not in line and 'Mileage' not in line:
|
| 202 |
cells = line.strip('|').split('|')
|
| 203 |
row_html = '<tr>' + ''.join(f'<td>{cell.strip()}</td>' for cell in cells) + '</tr>'
|
| 204 |
html_table += row_html
|
| 205 |
+
elif '|' not in line:
|
| 206 |
html_table += f"</table><p>{line}</p><table class='spec-table'>"
|
| 207 |
html_table += "</table>"
|
| 208 |
cleaned_message = html_table
|
|
|
|
| 209 |
st.success("β
Identification Complete")
|
| 210 |
+
st.markdown(f"<div class='car-spec-output'>{cleaned_message}</div>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
except Exception as e:
|
| 212 |
st.error(f"β Error during image analysis: {str(e)}")
|