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)}")
|