DjPapzin commited on
Commit
6b68020
·
1 Parent(s): d81d9b5

Undo last commit

Browse files
Files changed (1) hide show
  1. frontend/app.py +364 -102
frontend/app.py CHANGED
@@ -22,7 +22,7 @@ import re
22
  from nltk.tokenize import word_tokenize
23
  from nltk.corpus import stopwords
24
 
25
- # --- Download NLTK resources if not already present ---
26
  try:
27
  nltk.data.find('tokenizers/punkt')
28
  except LookupError:
@@ -33,7 +33,7 @@ try:
33
  except LookupError:
34
  nltk.download('stopwords')
35
 
36
- # --- Preprocess text function ---
37
  def preprocess_text(text):
38
  # Convert to lowercase
39
  text = text.lower()
@@ -51,104 +51,190 @@ def preprocess_text(text):
51
 
52
  return cleaned_text
53
 
54
- # --- Load necessary data for symptom detection ---
55
- dataset_1 = pd.read_csv("frontend/Symptoms_Detection/training_data.csv")
56
- dataset_1['symptoms_text'] = dataset_1.apply(lambda row: ','.join([col for col in dataset_1.columns if row[col] == 1]), axis=1)
57
- final_dataset = pd.DataFrame(dataset_1[["prognosis", "symptoms_text"]])
58
- final_dataset.columns = ['label', 'text']
59
-
60
- dataset_2 = pd.read_csv("frontend/Symptoms_Detection/Symptom2Disease.csv")
61
- dataset_2 = dataset_2[["label", "text"]]
62
-
63
- df_combined = pd.concat([final_dataset, dataset_2], axis=0, ignore_index=True)
64
- df_combined["cleaned_text"] = df_combined["text"].apply(preprocess_text)
65
-
66
- data = pd.read_csv('frontend/Symptoms_Detection/final_dataset_llms.csv')
67
- X = data['cleaned_text']
68
- y = data['label']
69
-
70
- vectorizer = CountVectorizer()
71
- X_vectorized = vectorizer.fit_transform(X)
72
-
73
- X_train, X_test, y_train, y_test = train_test_split(X_vectorized, y, test_size=0.2, random_state=42)
74
-
75
- model = LogisticRegression()
76
- model.fit(X_train, y_train)
77
-
78
- # --- Function to predict new symptoms ---
79
- def predict_symptoms(new_symptoms):
80
- preprocessed_text = preprocess_text(new_symptoms)
81
-
82
- if isinstance(preprocessed_text, str):
83
- new_symptoms = [preprocessed_text]
84
-
85
- # Vectorize the new symptoms
86
- new_symptoms_vectorized = vectorizer.transform(new_symptoms)
87
- # Make predictions
88
- prediction = model.predict(new_symptoms_vectorized)
89
-
90
- return prediction
91
-
92
- # --- Functions for precaution and occurrence ---
93
- def precaution(label):
94
- dataset_precau = pd.read_csv("frontend/Symptoms_Detection/disease_precaution.csv", encoding='latin1')
95
- label = str(label)
96
- label = label.lower()
97
-
98
- dataset_precau["Disease"] = dataset_precau["Disease"].str.lower()
99
- filtered_precautions = dataset_precau[dataset_precau["Disease"] == label]
100
- precautions = filtered_precautions[["Precaution_1", "Precaution_2", "Precaution_3", "Precaution_4"]]
101
- return precautions.values.tolist()
102
-
103
- def occurance(label):
104
- dataset_occur = pd.read_csv("frontend/Symptoms_Detection/disease_riskFactors.csv", encoding='latin1')
105
- label = str(label)
106
- label = label.lower()
107
-
108
- dataset_occur["DNAME"] = dataset_occur["DNAME"].str.lower()
109
- filtered_occurrence = dataset_occur[dataset_occur["DNAME"] == label]
110
- occurrences = filtered_occurrence["OCCUR"].tolist()
111
- return occurrences
112
-
113
- # --- Streamlit App ---
114
  st.title("Medi Scape Dashboard")
115
 
116
  # --- Session State Initialization ---
117
- # ... (rest of the session state initialization code)
118
-
119
- # --- Sidebar Navigation ---
120
- # ... (sidebar navigation code)
121
-
122
- # --- Main Content ---
123
- if page == "Home":
124
- # ... (home page content)
125
-
126
- elif page == "AI Chatbot Diagnosis":
127
- st.write("Enter your symptoms separated by commas:")
128
- symptoms_input = st.text_area("Symptoms:")
129
-
130
- col1, col2 = st.columns(2)
131
-
132
- with col1:
133
- if st.button("Diagnose (Regression Model)"):
134
- if symptoms_input:
135
- prediction = predict_symptoms(symptoms_input)
136
- st.write("## Regression Model Prediction:")
137
- st.write(prediction)
138
- st.write("## Precautions:")
139
- st.write(precaution(prediction[0]))
140
- st.write("## Occurrence:")
141
- st.write(occurance(prediction[0]))
142
- else:
143
- st.write("Please enter your symptoms.")
144
-
145
- with col2:
146
- if st.button("Diagnose (LLM)"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  if symptoms_input:
148
  # --- Pipeline 1 Implementation ---
149
  # 1. Symptom Input (already done with st.text_area)
150
- # 2. Regression Prediction (using predict_disease function)
151
- regression_prediction = predict_disease(symptoms_input) # Assuming predict_disease function exists
152
 
153
  if regression_prediction is not None:
154
  # 3. LLM Prompt Enhancement
@@ -170,13 +256,189 @@ elif page == "AI Chatbot Diagnosis":
170
  else:
171
  st.write("Please enter your symptoms.")
172
 
173
- elif page == "Drug Identification":
174
- # ... (drug identification page content)
 
 
 
 
 
 
175
 
176
- elif page == "Disease Detection":
177
- # ... (disease detection page content)
 
 
178
 
179
- elif page == "Outbreak Alert":
180
- # ... (outbreak alert page content)
181
-
182
- # ... (rest of the code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  from nltk.tokenize import word_tokenize
23
  from nltk.corpus import stopwords
24
 
25
+ # --- Download NLTK 'punkt' resource if not already present ---
26
  try:
27
  nltk.data.find('tokenizers/punkt')
28
  except LookupError:
 
33
  except LookupError:
34
  nltk.download('stopwords')
35
 
36
+ # --- Preprocess text function (moved outside session state) ---
37
  def preprocess_text(text):
38
  # Convert to lowercase
39
  text = text.lower()
 
51
 
52
  return cleaned_text
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  st.title("Medi Scape Dashboard")
55
 
56
  # --- Session State Initialization ---
57
+ if 'disease_model' not in st.session_state:
58
+ try:
59
+ model_path = 'FINAL_MODEL.keras' # Updated path
60
+ print(f"Attempting to load disease model from: {model_path}")
61
+ print(f"Model file exists: {os.path.exists(model_path)}")
62
+ st.session_state.disease_model = tf.keras.models.load_model(model_path)
63
+ print("Disease model loaded successfully!")
64
+ except FileNotFoundError:
65
+ st.error("Disease classification model not found. Please ensure 'FINAL_MODEL.keras' is in the same directory as this app.")
66
+ st.session_state.disease_model = None
67
+
68
+ # Load the vectorizer
69
+ if 'vectorizer' not in st.session_state:
70
+ try:
71
+ vectorizer_path = "vectorizer.pkl"
72
+ print(f"Attempting to load vectorizer from: {vectorizer_path}")
73
+ print(f"Vectorizer file exists: {os.path.exists(vectorizer_path)}")
74
+ st.session_state.vectorizer = pd.read_pickle(vectorizer_path)
75
+ print("Vectorizer loaded successfully!")
76
+ except FileNotFoundError:
77
+ st.error("Vectorizer file not found. Please ensure 'vectorizer.pkl' is in the same directory as this app.")
78
+ st.session_state.vectorizer = None
79
+ except Exception as e:
80
+ st.error(f"An error occurred while loading the vectorizer: {e}")
81
+ st.session_state.vectorizer = None
82
+
83
+ if 'model_llm' not in st.session_state:
84
+ try:
85
+ llm_model_path = "frontend/logistic_regression_model.pkl" # Updated path
86
+ print(f"Attempting to load LLM model from: {llm_model_path}")
87
+ print(f"LLM Model file exists: {os.path.exists(llm_model_path)}")
88
+ st.session_state.model_llm = pd.read_pickle(llm_model_path)
89
+ print("LLM model loaded successfully!")
90
+ except FileNotFoundError:
91
+ st.error("LLM model file not found. Please ensure 'logistic_regression_model.pkl' is in the 'frontend' directory.")
92
+ st.session_state.model_llm = None
93
+ except Exception as e:
94
+ st.error(f"An error occurred while loading the LLM model: {e}")
95
+ st.session_state.model_llm = None
96
+
97
+ # --- End of Session State Initialization ---
98
+
99
+ # Load the disease classification model
100
+ try:
101
+ disease_model = tf.keras.models.load_model('FINAL_MODEL.keras') # Updated path
102
+ except FileNotFoundError:
103
+ st.error("Disease classification model not found. Please ensure 'FINAL_MODEL.keras' is in the same directory as this app.")
104
+ disease_model = None
105
+
106
+ # Sidebar Navigation
107
+ st.sidebar.title("Navigation")
108
+ page = st.sidebar.radio("Go to", ["Home", "AI Chatbot Diagnosis", "Drug Identification", "Disease Detection", "Outbreak Alert"])
109
+
110
+ # Access secrets using st.secrets
111
+ if "INFERENCE_API_URL" not in st.secrets or "INFERENCE_API_KEY" not in st.secrets:
112
+ st.error("Please make sure to set your secrets in the Streamlit secrets settings.")
113
+ else:
114
+ # Initialize the Inference Client
115
+ CLIENT = InferenceHTTPClient(
116
+ api_url=st.secrets["INFERENCE_API_URL"],
117
+ api_key=st.secrets["INFERENCE_API_KEY"]
118
+ )
119
+
120
+ # Function to preprocess the image
121
+ def preprocess_image(image_path):
122
+ # Load the image
123
+ image = cv2.imread(image_path)
124
+
125
+ # Convert to grayscale
126
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
127
+
128
+ # Remove noise
129
+ blurred = cv2.GaussianBlur(gray, (5, 5), 0)
130
+
131
+ # Thresholding/Binarization
132
+ _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
133
+
134
+ # Dilation and Erosion
135
+ kernel = np.ones((1, 1), np.uint8)
136
+ dilated = cv2.dilate(binary, kernel, iterations=1)
137
+ eroded = cv2.erode(dilated, kernel, iterations=1)
138
+
139
+ # Edge detection
140
+ edges = cv2.Canny(eroded, 100, 200)
141
+
142
+ # Deskewing
143
+ coords = np.column_stack(np.where(edges > 0))
144
+ angle = cv2.minAreaRect(coords)[-1]
145
+ if angle < -45:
146
+ angle = -(90 + angle)
147
+ else:
148
+ angle = -angle
149
+
150
+ (h, w) = edges.shape[:2]
151
+ center = (w // 2, h // 2)
152
+ M = cv2.getRotationMatrix2D(center, angle, 1.0)
153
+ deskewed = cv2.warpAffine(edges, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
154
+
155
+ # Find contours
156
+ contours, _ = cv2.findContours(deskewed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
157
+
158
+ # Draw contours on the original image
159
+ contour_image = image.copy()
160
+ cv2.drawContours(contour_image, contours, -1, (0, 255, 0), 2)
161
+
162
+ return contour_image
163
+
164
+ def get_x1(detection):
165
+ return detection.xyxy[0][0]
166
+
167
+ # --- Prediction function (using session state) ---
168
+ def predict_disease(symptoms):
169
+ if st.session_state.vectorizer is not None and st.session_state.model_llm is not None:
170
+ preprocessed_symptoms = preprocess_text(symptoms)
171
+ symptoms_vectorized = st.session_state.vectorizer.transform([preprocessed_symptoms])
172
+ prediction = st.session_state.model_llm.predict(symptoms_vectorized)
173
+ return prediction[0]
174
+ else:
175
+ st.error("Unable to make prediction. Vectorizer or LLM model is not loaded.")
176
+ return None
177
+
178
+ # --- New function to analyze X-ray with LLM ---
179
+ def analyze_xray_with_llm(predicted_class):
180
+ prompt = f"""
181
+ Based on a chest X-ray analysis, the predicted condition is {predicted_class}.
182
+ Please provide a concise summary of this condition, including:
183
+ - A brief description of the condition.
184
+ - Common symptoms associated with it.
185
+ - Potential causes.
186
+ - General treatment approaches.
187
+ - Any other relevant information for a patient.
188
+ """
189
+ llm_response = get_ai71_response(prompt)
190
+ st.write("## LLM Analysis of X-ray Results:")
191
+ st.write(llm_response)
192
+
193
+ if page == "Home":
194
+ st.markdown("## Welcome to Medi Scape")
195
+ st.write("Medi Scape is an AI-powered healthcare application designed to streamline the process of understanding and managing medical information. It leverages advanced AI models to provide features such as prescription analysis, disease detection from chest X-rays, and symptom-based diagnosis assistance.")
196
+
197
+ st.markdown("## Features")
198
+ st.write("Medi Scape provides various AI-powered tools for remote healthcare, including:")
199
+ features = [
200
+ "**AI Chatbot Diagnosis:** Interact with an AI chatbot for preliminary diagnosis and medical information.",
201
+ "**Drug Identification:** Upload a prescription image to identify medications and access relevant details.",
202
+ "**Doctor's Handwriting Identification:** Our system can accurately recognize and process doctor's handwriting.",
203
+ "**Disease Detection:** Upload a chest X-ray image to detect potential diseases.",
204
+ "**Outbreak Alert:** Stay informed about potential disease outbreaks in your area."
205
+ ]
206
+ for feature in features:
207
+ st.markdown(f"- {feature}")
208
+
209
+ st.markdown("## How it Works")
210
+ steps = [
211
+ "**Upload:** You can upload a prescription image for drug identification or a chest X-ray image for disease detection.",
212
+ "**Process:** Our AI models will analyze the image and extract relevant information.",
213
+ "**Results:** You will receive identified drug names, uses, side effects, and more, or a potential disease diagnosis."
214
+ ]
215
+ for i, step in enumerate(steps, 1):
216
+ st.markdown(f"{i}. {step}")
217
+
218
+ st.markdown("## Key Features")
219
+ key_features = [
220
+ "**AI-Powered:** Leverages advanced AI models for accurate analysis and diagnosis.",
221
+ "**User-Friendly:** Simple and intuitive interface for easy navigation and interaction.",
222
+ "**Secure:** Your data is protected and handled with confidentiality."
223
+ ]
224
+ for feature in key_features:
225
+ st.markdown(f"- {feature}")
226
+
227
+ st.markdown("Please use the sidebar to navigate to different features.")
228
+
229
+ elif page == "AI Chatbot Diagnosis":
230
+ st.write("Enter your symptoms separated by commas:")
231
+ symptoms_input = st.text_area("Symptoms:")
232
+ if st.button("Diagnose"):
233
  if symptoms_input:
234
  # --- Pipeline 1 Implementation ---
235
  # 1. Symptom Input (already done with st.text_area)
236
+ # 2. Regression Prediction
237
+ regression_prediction = predict_disease(symptoms_input)
238
 
239
  if regression_prediction is not None:
240
  # 3. LLM Prompt Enhancement
 
256
  else:
257
  st.write("Please enter your symptoms.")
258
 
259
+ elif page == "Drug Identification":
260
+ st.write("Upload a prescription image for drug identification.")
261
+ uploaded_file = st.file_uploader("Upload prescription", type=["png", "jpg", "jpeg"])
262
+
263
+ if uploaded_file is not None:
264
+ # Display the uploaded image
265
+ image = Image.open(uploaded_file)
266
+ st.image(image, caption="Uploaded Prescription", use_column_width=True)
267
 
268
+ if st.button("Process Prescription"):
269
+ # Save the image to a temporary file
270
+ temp_image_path = "temp_image.jpg"
271
+ image.save(temp_image_path)
272
 
273
+ # Preprocess the image
274
+ preprocessed_image = preprocess_image(temp_image_path)
275
+
276
+ # Perform inference
277
+ result_doch1 = CLIENT.infer(preprocessed_image, model_id="doctor-s-handwriting/1")
278
+
279
+ # Extract labels and detections
280
+ labels = [item["class"] for item in result_doch1["predictions"]]
281
+ detections = sv.Detections.from_inference(result_doch1)
282
+
283
+ # Sort detections and labels
284
+ sorted_indices = sorted(range(len(detections)), key=lambda i: get_x1(detections[i]))
285
+ sorted_detections = [detections[i] for i in sorted_indices]
286
+ sorted_labels = [labels[i] for i in sorted_indices]
287
+
288
+ # Convert list to string
289
+ resulting_string = ''.join(sorted_labels)
290
+
291
+ # Display results
292
+ st.subheader("Processed Prescription")
293
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))
294
+
295
+ # Plot bounding boxes
296
+ image_with_boxes = preprocessed_image.copy()
297
+ for detection in sorted_detections:
298
+ x1, y1, x2, y2 = detection.xyxy[0]
299
+ cv2.rectangle(image_with_boxes, (int(x1), int(y1)), (int(x2), int(y2)), (255, 0, 0), 2)
300
+ ax1.imshow(cv2.cvtColor(image_with_boxes, cv2.COLOR_BGR2RGB))
301
+ ax1.set_title("Bounding Boxes")
302
+ ax1.axis('off')
303
+
304
+ # Plot labels
305
+ image_with_labels = preprocessed_image.copy()
306
+ for i, detection in enumerate(sorted_detections):
307
+ x1, y1, x2, y2 = detection.xyxy[0]
308
+ label = sorted_labels[i]
309
+ cv2.putText(image_with_labels, label, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
310
+ ax2.imshow(cv2.cvtColor(image_with_labels, cv2.COLOR_BGR2RGB))
311
+ ax2.set_title("Labels")
312
+ ax2.axis('off')
313
+
314
+ st.pyplot(fig)
315
+
316
+ st.write("Extracted Text from Prescription:", resulting_string)
317
+
318
+ # Prepare prompt for LLM
319
+ prompt = f"""Analyze the following prescription text:
320
+ {resulting_string}
321
+
322
+ Please provide:
323
+ 1. Identified drug name(s)
324
+ 2. Full name of each identified drug
325
+ 3. Primary uses of each drug
326
+ 4. Common side effects
327
+ 5. Recommended dosage (if identifiable from the text)
328
+ 6. Any warnings or precautions
329
+ 7. Potential interactions with other medications (if multiple drugs are identified)
330
+ 8. Any additional relevant information for the patient
331
+
332
+ If any part of the prescription is unclear or seems incomplete, please mention that and provide information about possible interpretations or matches. Always emphasize the importance of consulting a healthcare professional for accurate interpretation and advice."""
333
+
334
+ # Get LLM response
335
+ llm_response = get_ai71_response(prompt)
336
+
337
+ st.subheader("AI Analysis of the Prescription")
338
+ st.write(llm_response)
339
+
340
+ # Remove the temporary image file
341
+ os.remove(temp_image_path)
342
+
343
+ else:
344
+ st.info("Please upload a prescription image to proceed.")
345
+
346
+ elif page == "Disease Detection":
347
+ st.write("Upload a chest X-ray image for disease detection.")
348
+ uploaded_image = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
349
+
350
+ if uploaded_image is not None and st.session_state.disease_model is not None:
351
+ # Display the image
352
+ img_opened = Image.open(uploaded_image).convert('RGB')
353
+ image_pred = np.array(img_opened)
354
+ image_pred = cv2.resize(image_pred, (150, 150))
355
+
356
+ # Convert the image to a numpy array
357
+ image_pred = np.array(image_pred)
358
+
359
+ # Rescale the image (if the model was trained with rescaling)
360
+ image_pred = image_pred / 255.0
361
+
362
+ # Add an extra dimension to match the input shape (1, 150, 150, 3)
363
+ image_pred = np.expand_dims(image_pred, axis=0)
364
+
365
+ # Predict using the model
366
+ prediction = st.session_state.disease_model.predict(image_pred)
367
+
368
+ # Get the predicted class
369
+ predicted_ = np.argmax(prediction)
370
+
371
+ # Decode the prediction
372
+ if predicted_ == 0:
373
+ predicted_class = "Covid"
374
+ elif predicted_ == 1:
375
+ predicted_class = "Normal Chest X-ray"
376
+ else:
377
+ predicted_class = "Pneumonia"
378
+
379
+ st.image(image_pred, caption='Input image by user', use_column_width=True)
380
+ st.write("Prediction Classes for different types:")
381
+ st.write("COVID: 0")
382
+ st.write("Normal Chest X-ray: 1")
383
+ st.write("Pneumonia: 2")
384
+ st.write("\n")
385
+ st.write("DETECTED DISEASE DISPLAY")
386
+ st.write(f"Predicted Class : {predicted_}")
387
+ st.write(predicted_class)
388
+
389
+ # Analyze X-ray results with LLM
390
+ analyze_xray_with_llm(predicted_class)
391
+ else:
392
+ st.write("Please upload an image file or ensure the disease model is loaded.")
393
+
394
+ elif page == "Outbreak Alert":
395
+ st.markdown("## **Disease Outbreak News (from WHO)**")
396
+
397
+ # Fetch WHO news page
398
+ url = "https://www.who.int/news-room/events"
399
+ response = requests.get(url)
400
+ response.raise_for_status() # Raise an exception for bad status codes
401
+
402
+ soup = BeautifulSoup(response.content, 'html.parser')
403
+
404
+ # Find news articles (adjust selectors if WHO website changes)
405
+ articles = soup.find_all('div', class_='list-view--item')
406
+
407
+ for article in articles[:5]: # Display the top 5 news articles
408
+ title_element = article.find('a', class_='link-container')
409
+ if title_element:
410
+ title = title_element.text.strip()
411
+ link = title_element['href']
412
+ date_element = article.find('span', class_='date')
413
+ date = date_element.text.strip() if date_element else "Date not found"
414
+
415
+ # Format date
416
+ date_parts = date.split()
417
+ if len(date_parts) >= 3:
418
+ try:
419
+ formatted_date = datetime.strptime(date, "%d %B %Y").strftime("%Y-%m-%d")
420
+ except ValueError:
421
+ formatted_date = date # Keep the original date if formatting fails
422
+ else:
423
+ formatted_date = date
424
+
425
+ # Display news item in a card-like container
426
+ with st.container():
427
+ st.markdown(f"**{formatted_date}**")
428
+ st.markdown(f"[{title}]({link})")
429
+ st.markdown("---")
430
+ else:
431
+ st.write("Could not find article details.")
432
+
433
+ # Auto-scroll to the bottom of the chat container
434
+ st.markdown(
435
+ """
436
+ <script>
437
+ const chatContainer = document.querySelector('.st-chat-container');
438
+ if (chatContainer) {
439
+ chatContainer.scrollTop = chatContainer.scrollHeight;
440
+ }
441
+ </script>
442
+ """,
443
+ unsafe_allow_html=True,
444
+ )