blackshadow1 commited on
Commit
2357c09
·
verified ·
1 Parent(s): aafbb5b

updated code UI ✅✅

Browse files
Files changed (1) hide show
  1. mediSync/app.py +362 -290
mediSync/app.py CHANGED
@@ -8,51 +8,7 @@ import gradio as gr
8
  import matplotlib.pyplot as plt
9
  from PIL import Image
10
 
11
- # # Import configuration for end consultation logic
12
- # try:
13
- # from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
14
- # except ImportError:
15
- # def get_flask_urls():
16
- # return [
17
- # "http://127.0.0.1:600/complete_appointment",
18
- # "http://localhost:600/complete_appointment",
19
- # "https://your-flask-app-domain.com/complete_appointment",
20
- # "http://your-flask-app-ip:600/complete_appointment"
21
- # ]
22
- # def get_doctors_page_urls():
23
- # return {
24
- # "local": "http://127.0.0.1:600/doctors",
25
- # "production": "https://your-flask-app-domain.com/doctors"
26
- # }
27
- # TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
28
-
29
- # Add parent directory to path
30
- parent_dir = os.path.dirname(os.path.abspath(__file__))
31
- sys.path.append(parent_dir)
32
-
33
- # Import our modules for model and utility logic
34
- from models.multimodal_fusion import MultimodalFusion
35
- from utils.preprocessing import enhance_xray_image, normalize_report_text
36
- from utils.visualization import (
37
- plot_image_prediction,
38
- plot_multimodal_results,
39
- plot_report_entities,
40
- )
41
-
42
- # Set up logging
43
- logging.basicConfig(
44
- level=logging.INFO,
45
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
46
- handlers=[logging.StreamHandler(), logging.FileHandler("mediSync.log")],
47
- )
48
- logger = logging.getLogger(__name__)
49
-
50
- # Ensure sample data directory exists
51
- os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
52
-
53
-
54
-
55
- # Import configuration
56
  try:
57
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
58
  except ImportError:
@@ -70,9 +26,20 @@ except ImportError:
70
  }
71
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
72
 
 
73
  parent_dir = os.path.dirname(os.path.abspath(__file__))
74
  sys.path.append(parent_dir)
75
 
 
 
 
 
 
 
 
 
 
 
76
  logging.basicConfig(
77
  level=logging.INFO,
78
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@@ -80,224 +47,277 @@ logging.basicConfig(
80
  )
81
  logger = logging.getLogger(__name__)
82
 
 
 
 
83
  class MediSyncApp:
 
 
 
 
84
  def __init__(self):
 
85
  self.logger = logging.getLogger(__name__)
86
  self.logger.info("Initializing MediSync application")
87
- self._temp_files = []
88
  self.fusion_model = None
89
  self.image_model = None
90
  self.text_model = None
91
 
92
- def __del__(self):
93
- self.cleanup_temp_files()
94
-
95
- def cleanup_temp_files(self):
96
- for temp_file in self._temp_files:
97
- try:
98
- if os.path.exists(temp_file):
99
- os.remove(temp_file)
100
- self.logger.debug(f"Cleaned up temporary file: {temp_file}")
101
- except Exception as e:
102
- self.logger.warning(f"Failed to clean up temporary file {temp_file}: {e}")
103
- self._temp_files = []
104
-
105
  def load_models(self):
106
- if self.fusion_model is not None:
107
- return True
 
 
 
 
108
  try:
109
- self.logger.info("Loading models...")
110
- self.logger.info("Models loaded successfully (mock implementation)")
 
 
 
 
111
  return True
112
  except Exception as e:
113
  self.logger.error(f"Error loading models: {e}")
114
  return False
115
 
116
- def enhance_image(self, image):
117
- if image is None:
118
- return None
119
- try:
120
- enhanced_image = image
121
- self.logger.info("Image enhanced successfully")
122
- return enhanced_image
123
- except Exception as e:
124
- self.logger.error(f"Error enhancing image: {e}")
125
- return image
126
-
127
  def analyze_image(self, image):
128
- if image is None:
129
- return None, "Please upload an image first.", None
130
- if not self.load_models():
131
- return image, "Error: Models not loaded properly.", None
 
 
 
 
 
132
  try:
133
- self.logger.info("Analyzing image")
134
- results = {
135
- "primary_finding": "Normal chest X-ray",
136
- "confidence": 0.85,
137
- "has_abnormality": False,
138
- "predictions": [
139
- ("Normal", 0.85),
140
- ("Pneumonia", 0.10),
141
- ("Cardiomegaly", 0.05)
142
- ]
143
- }
144
- fig = self.plot_image_prediction(
 
 
 
 
 
145
  image,
146
  results.get("predictions", []),
147
- f"Primary Finding: {results.get('primary_finding', 'Unknown')}"
148
  )
149
  plot_html = self.fig_to_html(fig)
150
- plt.close(fig)
151
- html_result = self.format_image_results(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  return image, html_result, plot_html
153
  except Exception as e:
154
  self.logger.error(f"Error in image analysis: {e}")
155
  return image, f"Error analyzing image: {str(e)}", None
156
 
157
  def analyze_text(self, text):
158
- if not text or text.strip() == "":
159
- return "", "Please enter medical report text.", None
160
- if not self.load_models():
161
- return text, "Error: Models not loaded properly.", None
 
 
 
 
 
162
  try:
163
- self.logger.info("Analyzing text")
164
- results = {
165
- "entities": [
166
- {"text": "chest X-ray", "type": "PROCEDURE", "confidence": 0.95},
167
- {"text": "55-year-old male", "type": "PATIENT", "confidence": 0.90},
168
- {"text": "cough and fever", "type": "SYMPTOM", "confidence": 0.88}
169
- ],
170
- "sentiment": "neutral",
171
- "key_findings": ["Normal heart size", "Clear lungs", "8mm nodular opacity"]
172
- }
173
- html_result = self.format_text_results(results)
174
- plot_html = self.create_entity_visualization(results["entities"])
175
- return text, html_result, plot_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  except Exception as e:
177
  self.logger.error(f"Error in text analysis: {e}")
178
  return text, f"Error analyzing text: {str(e)}", None
179
 
180
  def analyze_multimodal(self, image, text):
181
- if image is None and (not text or text.strip() == ""):
182
- return "Please provide either an image or text for analysis.", None
183
- if not self.load_models():
184
- return "Error: Models not loaded properly.", None
 
 
 
 
 
 
185
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  self.logger.info("Performing multimodal analysis")
187
- results = {
188
- "combined_finding": "Normal chest X-ray with minor findings",
189
- "confidence": 0.92,
190
- "image_contribution": "Normal cardiac silhouette and clear lung fields",
191
- "text_contribution": "Clinical history supports normal findings",
192
- "recommendations": [
193
- "Follow-up CT for the 8mm nodular opacity",
194
- "Monitor for any changes in symptoms"
195
- ]
196
- }
197
- html_result = self.format_multimodal_results(results)
198
- plot_html = self.create_multimodal_visualization(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  return html_result, plot_html
200
  except Exception as e:
201
  self.logger.error(f"Error in multimodal analysis: {e}")
202
  return f"Error in multimodal analysis: {str(e)}", None
203
 
204
- def format_image_results(self, results):
205
- html_result = f"""
206
- <div class="medisync-card medisync-card-bg">
207
- <h2 class="medisync-title medisync-blue">X-ray Analysis Results</h2>
208
- <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
209
- <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
210
- <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
211
- <h3>Top Predictions:</h3>
212
- <ul>
213
- """
214
- for label, prob in results.get("predictions", [])[:5]:
215
- html_result += f"<li>{label}: {prob:.1%}</li>"
216
- html_result += "</ul></div>"
217
- return html_result
218
-
219
- def format_text_results(self, results):
220
- html_result = f"""
221
- <div class="medisync-card medisync-card-bg">
222
- <h2 class="medisync-title medisync-green">Text Analysis Results</h2>
223
- <p><strong>Sentiment:</strong> {results.get("sentiment", "Unknown").title()}</p>
224
- <h3>Key Findings:</h3>
225
- <ul>
226
  """
227
- for finding in results.get("key_findings", []):
228
- html_result += f"<li>{finding}</li>"
229
- html_result += "</ul>"
230
- html_result += "<h3>Extracted Entities:</h3><ul>"
231
- for entity in results.get("entities", [])[:5]:
232
- html_result += f"<li><strong>{entity['text']}</strong> ({entity['type']}) - {entity['confidence']:.1%}</li>"
233
- html_result += "</ul></div>"
234
- return html_result
235
-
236
- def format_multimodal_results(self, results):
237
- html_result = f"""
238
- <div class="medisync-card medisync-card-bg">
239
- <h2 class="medisync-title medisync-purple">Multimodal Analysis Results</h2>
240
- <p><strong>Combined Finding:</strong> {results.get("combined_finding", "Unknown")}</p>
241
- <p><strong>Overall Confidence:</strong> {results.get("confidence", 0):.1%}</p>
242
- <h3>Image Contribution:</h3>
243
- <p>{results.get("image_contribution", "No image analysis available")}</p>
244
- <h3>Text Contribution:</h3>
245
- <p>{results.get("text_contribution", "No text analysis available")}</p>
246
- <h3>Recommendations:</h3>
247
- <ul>
248
  """
249
- for rec in results.get("recommendations", []):
250
- html_result += f"<li>{rec}</li>"
251
- html_result += "</ul></div>"
252
- return html_result
253
-
254
- def plot_image_prediction(self, image, predictions, title):
255
- fig, ax = plt.subplots(figsize=(10, 6))
256
- ax.imshow(image)
257
- ax.set_title(title, fontsize=14, fontweight='bold', color='#007bff')
258
- ax.axis('off')
259
- return fig
260
-
261
- def create_entity_visualization(self, entities):
262
- if not entities:
263
- return "<p>No entities found in text.</p>"
264
- fig, ax = plt.subplots(figsize=(10, 6))
265
- entity_types = {}
266
- for entity in entities:
267
- entity_type = entity['type']
268
- if entity_type not in entity_types:
269
- entity_types[entity_type] = 0
270
- entity_types[entity_type] += 1
271
- if entity_types:
272
- ax.bar(entity_types.keys(), entity_types.values(), color='#00bfae')
273
- ax.set_title('Entity Types Found in Text', fontsize=14, fontweight='bold', color='#00bfae')
274
- ax.set_ylabel('Count', color='#00bfae')
275
- plt.xticks(rotation=45, color='#222')
276
- plt.yticks(color='#222')
277
- return self.fig_to_html(fig)
278
-
279
- def create_multimodal_visualization(self, results):
280
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
281
- confidence = results.get("confidence", 0)
282
- ax1.pie([confidence, 1-confidence], labels=['Confidence', 'Uncertainty'],
283
- colors=['#00bfae', '#ff7675'], autopct='%1.1f%%', textprops={'color': '#222'})
284
- ax1.set_title('Analysis Confidence', fontweight='bold', color='#00bfae')
285
- recommendations = results.get("recommendations", [])
286
- ax2.bar(['Recommendations'], [len(recommendations)], color='#6c63ff')
287
- ax2.set_title('Number of Recommendations', fontweight='bold', color='#6c63ff')
288
- ax2.set_ylabel('Count', color='#6c63ff')
289
- plt.tight_layout()
290
- return self.fig_to_html(fig)
291
 
292
  def fig_to_html(self, fig):
293
- import io
294
- import base64
295
- buf = io.BytesIO()
296
- fig.savefig(buf, format='png', bbox_inches='tight', dpi=100, facecolor=fig.get_facecolor())
297
- buf.seek(0)
298
- img_str = base64.b64encode(buf.read()).decode()
299
- buf.close()
300
- return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto; background: transparent;"/>'
 
 
 
 
 
301
 
302
  def complete_appointment(appointment_id):
303
  try:
@@ -332,6 +352,7 @@ def complete_appointment(appointment_id):
332
  return {"status": "error", "message": f"Error: {str(e)}"}
333
 
334
  def create_interface():
 
335
  app = MediSyncApp()
336
  example_report = """
337
  CHEST X-RAY EXAMINATION
@@ -357,7 +378,7 @@ def create_interface():
357
 
358
  with gr.Blocks(
359
  title="MediSync: Multi-Modal Medical Analysis System",
360
- theme=gr.themes.Default(), # Use Default for HuggingFace dark/light support
361
  css="""
362
  /* Modern neumorphic card style for all result containers */
363
  .medisync-card {
@@ -373,8 +394,12 @@ def create_interface():
373
  color: var(--body-text-color, #222);
374
  }
375
  .medisync-title {
376
- font-weight: 700;
 
377
  margin-bottom: 0.7em;
 
 
 
378
  }
379
  .medisync-blue { color: #00bfae; }
380
  .medisync-green { color: #28a745; }
@@ -389,7 +414,10 @@ def create_interface():
389
  .gr-button, .end-consultation-btn {
390
  border-radius: 8px !important;
391
  font-weight: 600 !important;
392
- font-size: 1.08rem !important;
 
 
 
393
  transition: background 0.2s, color 0.2s;
394
  }
395
  .end-consultation-btn {
@@ -397,6 +425,10 @@ def create_interface():
397
  border: none !important;
398
  color: #fff !important;
399
  box-shadow: 0 2px 8px 0 rgba(220,53,69,0.10);
 
 
 
 
400
  }
401
  .end-consultation-btn:hover {
402
  background: linear-gradient(90deg, #c82333 60%, #ff7675 100%) !important;
@@ -404,14 +436,17 @@ def create_interface():
404
  /* Responsive tweaks */
405
  @media (max-width: 900px) {
406
  .medisync-card { padding: 16px 8px 12px 8px; }
 
407
  }
408
  /* Ensure text is visible in dark mode */
409
- html[data-theme="dark"] .medisync-card-bg {
 
410
  background: #23272f !important;
411
  color: #f8fafc !important;
412
  }
413
  html[data-theme="dark"] .medisync-title {
414
  color: #00bfae !important;
 
415
  }
416
  html[data-theme="dark"] .medisync-blue { color: #00bfae !important; }
417
  html[data-theme="dark"] .medisync-green { color: #00e676 !important; }
@@ -423,22 +458,72 @@ def create_interface():
423
  html[data-theme="dark"] label, html[data-theme="dark"] .gr-label, html[data-theme="dark"] .gr-text, html[data-theme="dark"] .gr-html, html[data-theme="dark"] .gr-markdown {
424
  color: #f8fafc !important;
425
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  """
427
  ) as interface:
428
  gr.Markdown(
429
  """
430
- <div style="display: flex; align-items: center; gap: 16px; margin-bottom: 0.5em;">
431
- <img src="https://cdn.jsdelivr.net/gh/saqib-ali-buriro/medivance-assets/medivance_logo.png" alt="Medivance Logo" style="height: 38px; border-radius: 8px; background: #fff; box-shadow: 0 2px 8px 0 rgba(26,115,232,0.10);">
432
- <span style="font-size: 2.1rem; font-weight: 700; color: #00bfae;">MediSync</span>
 
433
  </div>
434
- <div style="font-size: 1.18rem; margin-bottom: 1.2em;">
435
- <span style="color: var(--body-text-color, #222);">AI-powered Multi-Modal Medical Analysis System</span>
436
  </div>
437
- <div style="font-size: 1.05rem; margin-bottom: 1.2em;">
438
- <span style="color: var(--body-text-color, #222);">Seamlessly analyze X-ray images and medical reports for comprehensive healthcare insights.</span>
439
  </div>
440
  <div style="margin-bottom: 1.2em;">
441
- <ul style="font-size: 1.01rem; color: var(--body-text-color, #222);">
442
  <li>Upload a chest X-ray image</li>
443
  <li>Enter the corresponding medical report text</li>
444
  <li>Choose the analysis type: <b>Image</b>, <b>Text</b>, or <b>Multimodal</b></li>
@@ -448,24 +533,14 @@ def create_interface():
448
  """,
449
  elem_id="medisync-header"
450
  )
451
-
 
452
  with gr.Row():
453
- import urllib.parse
454
- try:
455
- url_params = {}
456
- if hasattr(gr, 'get_current_url'):
457
- current_url = gr.get_current_url()
458
- if current_url:
459
- parsed = urllib.parse.urlparse(current_url)
460
- url_params = urllib.parse.parse_qs(parsed.query)
461
- default_appointment_id = url_params.get('appointment_id', [''])[0]
462
- except:
463
- default_appointment_id = ""
464
  appointment_id_input = gr.Textbox(
465
  label="Appointment ID",
466
  placeholder="Enter your appointment ID here...",
467
  info="This will be automatically populated if you came from the doctors page",
468
- value=default_appointment_id,
469
  elem_id="appointment_id_input"
470
  )
471
 
@@ -473,7 +548,7 @@ def create_interface():
473
  with gr.Row():
474
  with gr.Column():
475
  multi_img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="multi_img_input")
476
- multi_img_enhance = gr.Button("Enhance Image", icon="✨")
477
  multi_text_input = gr.Textbox(
478
  label="Enter Medical Report Text",
479
  placeholder="Enter the radiologist's report text here...",
@@ -481,7 +556,7 @@ def create_interface():
481
  value=example_report if sample_image_path is None else None,
482
  elem_id="multi_text_input"
483
  )
484
- multi_analyze_btn = gr.Button("Analyze Image & Text", variant="primary", icon="🔎")
485
  with gr.Column():
486
  multi_results = gr.HTML(label="Analysis Results", elem_id="multi_results")
487
  multi_plot = gr.HTML(label="Visualization", elem_id="multi_plot")
@@ -496,8 +571,8 @@ def create_interface():
496
  with gr.Row():
497
  with gr.Column():
498
  img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="img_input")
499
- img_enhance = gr.Button("Enhance Image", icon="✨")
500
- img_analyze_btn = gr.Button("Analyze Image", variant="primary", icon="🔎")
501
  with gr.Column():
502
  img_output = gr.Image(label="Processed Image", elem_id="img_output")
503
  img_results = gr.HTML(label="Analysis Results", elem_id="img_results")
@@ -519,7 +594,7 @@ def create_interface():
519
  value=example_report,
520
  elem_id="text_input"
521
  )
522
- text_analyze_btn = gr.Button("Analyze Text", variant="primary", icon="🔎")
523
  with gr.Column():
524
  text_output = gr.Textbox(label="Processed Text", elem_id="text_output")
525
  text_results = gr.HTML(label="Analysis Results", elem_id="text_results")
@@ -536,16 +611,17 @@ def create_interface():
536
  "End Consultation",
537
  variant="stop",
538
  size="lg",
539
- elem_classes=["end-consultation-btn"],
540
- icon="🛑"
541
  )
542
  end_consultation_status = gr.HTML(label="Status", elem_id="end_consultation_status")
543
 
544
  with gr.Tab("ℹ️ About"):
545
  gr.Markdown(
546
  """
547
- <div class="medisync-card medisync-card-bg">
548
- <h2 class="medisync-title medisync-blue">About MediSync</h2>
 
 
549
  <p>
550
  <b>MediSync</b> is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
551
  </p>
@@ -590,22 +666,23 @@ def create_interface():
590
  )
591
 
592
  def handle_end_consultation(appointment_id):
 
593
  if not appointment_id or appointment_id.strip() == "":
594
- return "<div style='color: #dc3545; padding: 10px; background-color: #ffe6e6; border-radius: 5px;'>Please enter your appointment ID first.</div>"
595
  result = complete_appointment(appointment_id.strip())
596
  if result["status"] == "success":
597
  doctors_urls = get_doctors_page_urls()
598
  html_response = f"""
599
- <div style='color: #28a745; padding: 15px; background-color: #e6ffe6; border-radius: 5px; margin: 10px 0;'>
600
- <h3>✅ Consultation Completed Successfully!</h3>
601
- <p>{result['message']}</p>
602
  <p>Your appointment has been marked as completed.</p>
603
- <button onclick="window.open('{doctors_urls['local']}', '_blank')"
604
- style="background-color: #00bfae; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;">
605
  Return to Doctors Page (Local)
606
  </button>
607
- <button onclick="window.open('{doctors_urls['production']}', '_blank')"
608
- style="background-color: #6c63ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; margin-left: 10px;">
609
  Return to Doctors Page (Production)
610
  </button>
611
  </div>
@@ -613,8 +690,8 @@ def create_interface():
613
  else:
614
  if "Cannot connect to Flask app" in result['message']:
615
  html_response = f"""
616
- <div style='color: #ff9800; padding: 15px; background-color: #fff3cd; border-radius: 5px; margin: 10px 0;'>
617
- <h3>⚠️ Consultation Ready to Complete</h3>
618
  <p>Your consultation analysis is complete! However, we cannot automatically mark your appointment as completed because the Flask app is not accessible from this environment.</p>
619
  <p><strong>Appointment ID:</strong> {appointment_id.strip()}</p>
620
  <p><strong>Next Steps:</strong></p>
@@ -624,16 +701,13 @@ def create_interface():
624
  <li>Manually complete the appointment using the appointment ID</li>
625
  </ol>
626
  <div style="margin-top: 15px;">
627
- <button onclick="window.open('http://127.0.0.1:600/complete_appointment_manual?appointment_id={appointment_id.strip()}', '_blank')"
628
- style="background-color: #00bfae; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
629
  Complete Appointment
630
  </button>
631
- <button onclick="window.open('http://127.0.0.1:600/doctors', '_blank')"
632
- style="background-color: #6c63ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
633
  Return to Doctors Page
634
  </button>
635
- <button onclick="navigator.clipboard.writeText('{appointment_id.strip()}')"
636
- style="background-color: #23272f; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
637
  Copy Appointment ID
638
  </button>
639
  </div>
@@ -641,8 +715,8 @@ def create_interface():
641
  """
642
  else:
643
  html_response = f"""
644
- <div style='color: #dc3545; padding: 15px; background-color: #ffe6e6; border-radius: 5px; margin: 10px 0;'>
645
- <h3>❌ Error Completing Consultation</h3>
646
  <p>{result['message']}</p>
647
  <p>Please try again or contact support if the problem persists.</p>
648
  </div>
@@ -655,38 +729,36 @@ def create_interface():
655
  outputs=[end_consultation_status]
656
  )
657
 
658
- # JavaScript for appointment ID auto-population
659
  gr.HTML("""
660
  <script>
661
  function getUrlParameter(name) {
662
  name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
663
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
664
- var results = regex.exec(location.search);
665
  return results === null ? '' : decodeURIComponent(results[1].replace(/\\+/g, ' '));
666
  }
667
- function populateAppointmentId() {
668
  var appointmentId = getUrlParameter('appointment_id');
669
- if (appointmentId) {
670
- var input = document.getElementById('appointment_id_input');
671
- if (input) {
672
- input.value = appointmentId;
673
- var event = new Event('input', { bubbles: true });
674
- input.dispatchEvent(event);
675
- }
 
676
  }
677
  }
678
- document.addEventListener('DOMContentLoaded', function() {
679
- setTimeout(populateAppointmentId, 800);
680
- });
681
- window.addEventListener('load', function() {
682
- setTimeout(populateAppointmentId, 1200);
683
- });
684
  </script>
685
  """)
686
 
687
  interface.launch()
688
 
689
  if __name__ == "__main__":
690
- create_interface()
691
-
692
- # Some tests on this code
 
8
  import matplotlib.pyplot as plt
9
  from PIL import Image
10
 
11
+ # Import configuration for end consultation logic
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  try:
13
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
14
  except ImportError:
 
26
  }
27
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
28
 
29
+ # Add parent directory to path
30
  parent_dir = os.path.dirname(os.path.abspath(__file__))
31
  sys.path.append(parent_dir)
32
 
33
+ # Import our modules for model and utility logic
34
+ from models.multimodal_fusion import MultimodalFusion
35
+ from utils.preprocessing import enhance_xray_image, normalize_report_text
36
+ from utils.visualization import (
37
+ plot_image_prediction,
38
+ plot_multimodal_results,
39
+ plot_report_entities,
40
+ )
41
+
42
+ # Set up logging
43
  logging.basicConfig(
44
  level=logging.INFO,
45
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 
47
  )
48
  logger = logging.getLogger(__name__)
49
 
50
+ # Ensure sample data directory exists
51
+ os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
52
+
53
  class MediSyncApp:
54
+ """
55
+ Main application class for the MediSync multi-modal medical analysis system.
56
+ """
57
+
58
  def __init__(self):
59
+ """Initialize the application and load models."""
60
  self.logger = logging.getLogger(__name__)
61
  self.logger.info("Initializing MediSync application")
 
62
  self.fusion_model = None
63
  self.image_model = None
64
  self.text_model = None
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  def load_models(self):
67
+ """
68
+ Load models if not already loaded.
69
+
70
+ Returns:
71
+ bool: True if models loaded successfully, False otherwise
72
+ """
73
  try:
74
+ if self.fusion_model is None:
75
+ self.logger.info("Loading models...")
76
+ self.fusion_model = MultimodalFusion()
77
+ self.image_model = self.fusion_model.image_analyzer
78
+ self.text_model = self.fusion_model.text_analyzer
79
+ self.logger.info("Models loaded successfully")
80
  return True
81
  except Exception as e:
82
  self.logger.error(f"Error loading models: {e}")
83
  return False
84
 
 
 
 
 
 
 
 
 
 
 
 
85
  def analyze_image(self, image):
86
+ """
87
+ Analyze a medical image.
88
+
89
+ Args:
90
+ image: Image file uploaded through Gradio
91
+
92
+ Returns:
93
+ tuple: (image, image_results_html, plot_as_html)
94
+ """
95
  try:
96
+ if image is None:
97
+ return None, "Please upload an image first.", None
98
+ if not self.load_models() or self.image_model is None:
99
+ return image, "Error: Models not loaded properly.", None
100
+
101
+ temp_dir = tempfile.mkdtemp()
102
+ temp_path = os.path.join(temp_dir, "upload.png")
103
+ if isinstance(image, str):
104
+ from shutil import copyfile
105
+ copyfile(image, temp_path)
106
+ else:
107
+ image.save(temp_path)
108
+
109
+ self.logger.info(f"Analyzing image: {temp_path}")
110
+ results = self.image_model.analyze(temp_path)
111
+
112
+ fig = plot_image_prediction(
113
  image,
114
  results.get("predictions", []),
115
+ f"Primary Finding: {results.get('primary_finding', 'Unknown')}",
116
  )
117
  plot_html = self.fig_to_html(fig)
118
+
119
+ html_result = f"""
120
+ <div class="medisync-card medisync-card-bg medisync-force-text">
121
+ <h2 class="medisync-title medisync-blue">
122
+ <b>X-ray Analysis Results</b>
123
+ </h2>
124
+ <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
125
+ <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
126
+ <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
127
+ <h3>Top Predictions:</h3>
128
+ <ul>
129
+ """
130
+ for label, prob in results.get("predictions", [])[:5]:
131
+ html_result += f"<li>{label}: {prob:.1%}</li>"
132
+ html_result += "</ul>"
133
+ explanation = self.image_model.get_explanation(results)
134
+ html_result += f"<h3>Analysis Explanation:</h3><p>{explanation}</p>"
135
+ html_result += "</div>"
136
  return image, html_result, plot_html
137
  except Exception as e:
138
  self.logger.error(f"Error in image analysis: {e}")
139
  return image, f"Error analyzing image: {str(e)}", None
140
 
141
  def analyze_text(self, text):
142
+ """
143
+ Analyze a medical report text.
144
+
145
+ Args:
146
+ text: Report text input through Gradio
147
+
148
+ Returns:
149
+ tuple: (text, text_results_html, entities_plot_html)
150
+ """
151
  try:
152
+ if not text or text.strip() == "":
153
+ return "", "Please enter medical report text.", None
154
+ if not self.load_models() or self.text_model is None:
155
+ return text, "Error: Models not loaded properly.", None
156
+ if not text or len(text.strip()) < 10:
157
+ return (
158
+ text,
159
+ "Error: Please enter a valid medical report text (at least 10 characters).",
160
+ None,
161
+ )
162
+ normalized_text = normalize_report_text(text)
163
+ self.logger.info("Analyzing medical report text")
164
+ results = self.text_model.analyze(normalized_text)
165
+ entities = results.get("entities", {})
166
+ fig = plot_report_entities(normalized_text, entities)
167
+ entities_plot_html = self.fig_to_html(fig)
168
+ html_result = f"""
169
+ <div class="medisync-card medisync-card-bg medisync-force-text">
170
+ <h2 class="medisync-title medisync-green">
171
+ <b>Text Analysis Results</b>
172
+ </h2>
173
+ <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
174
+ <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
175
+ <p><strong>Confidence:</strong> {results.get("severity", {}).get("confidence", 0):.1%}</p>
176
+ <h3>Key Findings:</h3>
177
+ <ul>
178
+ """
179
+ findings = results.get("findings", [])
180
+ if findings:
181
+ for finding in findings:
182
+ html_result += f"<li>{finding}</li>"
183
+ else:
184
+ html_result += "<li>No specific findings detailed.</li>"
185
+ html_result += "</ul>"
186
+ html_result += "<h3>Extracted Medical Entities:</h3>"
187
+ for category, items in entities.items():
188
+ if items:
189
+ html_result += f"<p><strong>{category.capitalize()}:</strong> {', '.join(items)}</p>"
190
+ html_result += "<h3>Follow-up Recommendations:</h3><ul>"
191
+ followups = results.get("followup_recommendations", [])
192
+ if followups:
193
+ for rec in followups:
194
+ html_result += f"<li>{rec}</li>"
195
+ else:
196
+ html_result += "<li>No specific follow-up recommendations.</li>"
197
+ html_result += "</ul></div>"
198
+ return text, html_result, entities_plot_html
199
  except Exception as e:
200
  self.logger.error(f"Error in text analysis: {e}")
201
  return text, f"Error analyzing text: {str(e)}", None
202
 
203
  def analyze_multimodal(self, image, text):
204
+ """
205
+ Perform multimodal analysis of image and text.
206
+
207
+ Args:
208
+ image: Image file uploaded through Gradio
209
+ text: Report text input through Gradio
210
+
211
+ Returns:
212
+ tuple: (results_html, multimodal_plot_html)
213
+ """
214
  try:
215
+ if not self.load_models() or self.fusion_model is None:
216
+ return "Error: Models not loaded properly.", None
217
+ if image is None:
218
+ return "Error: Please upload an X-ray image for analysis.", None
219
+ if not text or len(text.strip()) < 10:
220
+ return (
221
+ "Error: Please enter a valid medical report text (at least 10 characters).",
222
+ None,
223
+ )
224
+ temp_dir = tempfile.mkdtemp()
225
+ temp_path = os.path.join(temp_dir, "upload.png")
226
+ if isinstance(image, str):
227
+ from shutil import copyfile
228
+ copyfile(image, temp_path)
229
+ else:
230
+ image.save(temp_path)
231
+ normalized_text = normalize_report_text(text)
232
  self.logger.info("Performing multimodal analysis")
233
+ results = self.fusion_model.analyze(temp_path, normalized_text)
234
+ fig = plot_multimodal_results(results, image, text)
235
+ plot_html = self.fig_to_html(fig)
236
+ explanation = self.fusion_model.get_explanation(results)
237
+ html_result = f"""
238
+ <div class="medisync-card medisync-card-bg medisync-force-text">
239
+ <h2 class="medisync-title medisync-purple">
240
+ <b>Multimodal Analysis Results</b>
241
+ </h2>
242
+ <h3>Overview</h3>
243
+ <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
244
+ <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
245
+ <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
246
+ <p><strong>Agreement Score:</strong> {results.get("agreement_score", 0):.0%}</p>
247
+ <h3>Detailed Findings</h3>
248
+ <ul>
249
+ """
250
+ findings = results.get("findings", [])
251
+ if findings:
252
+ for finding in findings:
253
+ html_result += f"<li>{finding}</li>"
254
+ else:
255
+ html_result += "<li>No specific findings detailed.</li>"
256
+ html_result += "</ul>"
257
+ html_result += "<h3>Recommended Follow-up</h3><ul>"
258
+ followups = results.get("followup_recommendations", [])
259
+ if followups:
260
+ for rec in followups:
261
+ html_result += f"<li>{rec}</li>"
262
+ else:
263
+ html_result += "<li>No specific follow-up recommendations provided.</li>"
264
+ html_result += "</ul>"
265
+ confidence = results.get("severity", {}).get("confidence", 0)
266
+ html_result += f"""
267
+ <p><em>Note: This analysis has a confidence level of {confidence:.0%}.
268
+ Please consult with healthcare professionals for official diagnosis.</em></p>
269
+ <h3>Analysis Explanation:</h3>
270
+ <p>{explanation}</p>
271
+ </div>
272
+ """
273
  return html_result, plot_html
274
  except Exception as e:
275
  self.logger.error(f"Error in multimodal analysis: {e}")
276
  return f"Error in multimodal analysis: {str(e)}", None
277
 
278
+ def enhance_image(self, image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  """
280
+ Enhance X-ray image contrast.
281
+
282
+ Args:
283
+ image: Image file uploaded through Gradio
284
+
285
+ Returns:
286
+ PIL.Image: Enhanced image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  """
288
+ try:
289
+ if image is None:
290
+ return None
291
+ temp_dir = tempfile.mkdtemp()
292
+ temp_path = os.path.join(temp_dir, "upload.png")
293
+ if isinstance(image, str):
294
+ from shutil import copyfile
295
+ copyfile(image, temp_path)
296
+ else:
297
+ image.save(temp_path)
298
+ self.logger.info(f"Enhancing image: {temp_path}")
299
+ output_path = os.path.join(temp_dir, "enhanced.png")
300
+ enhance_xray_image(temp_path, output_path)
301
+ enhanced = Image.open(output_path)
302
+ return enhanced
303
+ except Exception as e:
304
+ self.logger.error(f"Error enhancing image: {e}")
305
+ return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
  def fig_to_html(self, fig):
308
+ """Convert matplotlib figure to HTML for display in Gradio."""
309
+ try:
310
+ import base64
311
+ import io
312
+ buf = io.BytesIO()
313
+ fig.savefig(buf, format="png", bbox_inches="tight", dpi=100, facecolor=fig.get_facecolor())
314
+ buf.seek(0)
315
+ img_str = base64.b64encode(buf.read()).decode("utf-8")
316
+ plt.close(fig)
317
+ return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto; background: transparent;"/>'
318
+ except Exception as e:
319
+ self.logger.error(f"Error converting figure to HTML: {e}")
320
+ return "<p>Error displaying visualization.</p>"
321
 
322
  def complete_appointment(appointment_id):
323
  try:
 
352
  return {"status": "error", "message": f"Error: {str(e)}"}
353
 
354
  def create_interface():
355
+ import urllib.parse
356
  app = MediSyncApp()
357
  example_report = """
358
  CHEST X-RAY EXAMINATION
 
378
 
379
  with gr.Blocks(
380
  title="MediSync: Multi-Modal Medical Analysis System",
381
+ theme=gr.themes.Default(),
382
  css="""
383
  /* Modern neumorphic card style for all result containers */
384
  .medisync-card {
 
394
  color: var(--body-text-color, #222);
395
  }
396
  .medisync-title {
397
+ font-weight: 900;
398
+ font-size: 1.45em;
399
  margin-bottom: 0.7em;
400
+ letter-spacing: 1px;
401
+ text-shadow: 0 2px 8px #00bfae33, 0 1px 0 #fff;
402
+ /* Remove display:flex and gap for simple bold text */
403
  }
404
  .medisync-blue { color: #00bfae; }
405
  .medisync-green { color: #28a745; }
 
414
  .gr-button, .end-consultation-btn {
415
  border-radius: 8px !important;
416
  font-weight: 600 !important;
417
+ font-size: 1rem !important;
418
+ padding: 8px 18px !important;
419
+ min-width: 120px !important;
420
+ min-height: 38px !important;
421
  transition: background 0.2s, color 0.2s;
422
  }
423
  .end-consultation-btn {
 
425
  border: none !important;
426
  color: #fff !important;
427
  box-shadow: 0 2px 8px 0 rgba(220,53,69,0.10);
428
+ font-size: 1.05rem !important;
429
+ padding: 10px 24px !important;
430
+ min-width: 160px !important;
431
+ min-height: 40px !important;
432
  }
433
  .end-consultation-btn:hover {
434
  background: linear-gradient(90deg, #c82333 60%, #ff7675 100%) !important;
 
436
  /* Responsive tweaks */
437
  @media (max-width: 900px) {
438
  .medisync-card { padding: 16px 8px 12px 8px; }
439
+ .medisync-title { font-size: 1.1em; }
440
  }
441
  /* Ensure text is visible in dark mode */
442
+ html[data-theme="dark"] .medisync-card-bg,
443
+ html[data-theme="dark"] .medisync-card-bg.medisync-force-text {
444
  background: #23272f !important;
445
  color: #f8fafc !important;
446
  }
447
  html[data-theme="dark"] .medisync-title {
448
  color: #00bfae !important;
449
+ text-shadow: 0 2px 8px #00bfae33, 0 1px 0 #23272f;
450
  }
451
  html[data-theme="dark"] .medisync-blue { color: #00bfae !important; }
452
  html[data-theme="dark"] .medisync-green { color: #00e676 !important; }
 
458
  html[data-theme="dark"] label, html[data-theme="dark"] .gr-label, html[data-theme="dark"] .gr-text, html[data-theme="dark"] .gr-html, html[data-theme="dark"] .gr-markdown {
459
  color: #f8fafc !important;
460
  }
461
+ /* Force all text in medisync-card and status outputs to be visible in all themes */
462
+ .medisync-force-text, .medisync-force-text * {
463
+ color: var(--body-text-color, #222) !important;
464
+ }
465
+ html[data-theme="dark"] .medisync-force-text, html[data-theme="dark"] .medisync-force-text * {
466
+ color: #f8fafc !important;
467
+ }
468
+ /* End consultation status output: remove color and theme, keep text black and simple */
469
+ #end_consultation_status, #end_consultation_status * {
470
+ color: #000 !important;
471
+ background: #fff !important;
472
+ font-size: 1.12rem !important;
473
+ font-weight: 600 !important;
474
+ }
475
+ /* Style the buttons inside the end consultation status popup */
476
+ #end_consultation_status button {
477
+ font-size: 1rem !important;
478
+ font-weight: 600 !important;
479
+ border-radius: 6px !important;
480
+ padding: 8px 18px !important;
481
+ margin-top: 8px !important;
482
+ margin-bottom: 4px !important;
483
+ min-width: 120px !important;
484
+ min-height: 36px !important;
485
+ box-shadow: 0 1.5px 4px 0 rgba(0,191,174,0.08);
486
+ }
487
+ #end_consultation_status button:active, #end_consultation_status button:focus {
488
+ outline: 2px solid #00bfae !important;
489
+ }
490
+ #end_consultation_status .btn-green {
491
+ background-color: #00bfae !important;
492
+ color: #fff !important;
493
+ }
494
+ #end_consultation_status .btn-purple {
495
+ background-color: #6c63ff !important;
496
+ color: #fff !important;
497
+ }
498
+ #end_consultation_status .btn-dark {
499
+ background-color: #23272f !important;
500
+ color: #fff !important;
501
+ }
502
+ #end_consultation_status .btn-orange {
503
+ background-color: #ff9800 !important;
504
+ color: #fff !important;
505
+ }
506
+ #end_consultation_status .btn-red {
507
+ background-color: #dc3545 !important;
508
+ color: #fff !important;
509
+ }
510
  """
511
  ) as interface:
512
  gr.Markdown(
513
  """
514
+ <div style="margin-bottom: 0.5em;">
515
+ <span style="font-size: 2.4rem; font-weight: bold; letter-spacing: 1.5px;">
516
+ <b>MediSync</b>
517
+ </span>
518
  </div>
519
+ <div style="font-size: 1.22rem; margin-bottom: 1.2em; font-weight: 600;">
520
+ <span>AI-powered Multi-Modal Medical Analysis System</span>
521
  </div>
522
+ <div style="font-size: 1.09rem; margin-bottom: 1.2em;">
523
+ <span>Seamlessly analyze X-ray images and medical reports for comprehensive healthcare insights.</span>
524
  </div>
525
  <div style="margin-bottom: 1.2em;">
526
+ <ul style="font-size: 1.04rem;">
527
  <li>Upload a chest X-ray image</li>
528
  <li>Enter the corresponding medical report text</li>
529
  <li>Choose the analysis type: <b>Image</b>, <b>Text</b>, or <b>Multimodal</b></li>
 
533
  """,
534
  elem_id="medisync-header"
535
  )
536
+
537
+ # --- BRUTAL FIX: Always set appointment id from URL using JS, forcibly, and keep it in sync ---
538
  with gr.Row():
 
 
 
 
 
 
 
 
 
 
 
539
  appointment_id_input = gr.Textbox(
540
  label="Appointment ID",
541
  placeholder="Enter your appointment ID here...",
542
  info="This will be automatically populated if you came from the doctors page",
543
+ value="",
544
  elem_id="appointment_id_input"
545
  )
546
 
 
548
  with gr.Row():
549
  with gr.Column():
550
  multi_img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="multi_img_input")
551
+ multi_img_enhance = gr.Button("Enhance Image")
552
  multi_text_input = gr.Textbox(
553
  label="Enter Medical Report Text",
554
  placeholder="Enter the radiologist's report text here...",
 
556
  value=example_report if sample_image_path is None else None,
557
  elem_id="multi_text_input"
558
  )
559
+ multi_analyze_btn = gr.Button("Analyze Image & Text", variant="primary")
560
  with gr.Column():
561
  multi_results = gr.HTML(label="Analysis Results", elem_id="multi_results")
562
  multi_plot = gr.HTML(label="Visualization", elem_id="multi_plot")
 
571
  with gr.Row():
572
  with gr.Column():
573
  img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="img_input")
574
+ img_enhance = gr.Button("Enhance Image")
575
+ img_analyze_btn = gr.Button("Analyze Image", variant="primary")
576
  with gr.Column():
577
  img_output = gr.Image(label="Processed Image", elem_id="img_output")
578
  img_results = gr.HTML(label="Analysis Results", elem_id="img_results")
 
594
  value=example_report,
595
  elem_id="text_input"
596
  )
597
+ text_analyze_btn = gr.Button("Analyze Text", variant="primary")
598
  with gr.Column():
599
  text_output = gr.Textbox(label="Processed Text", elem_id="text_output")
600
  text_results = gr.HTML(label="Analysis Results", elem_id="text_results")
 
611
  "End Consultation",
612
  variant="stop",
613
  size="lg",
614
+ elem_classes=["end-consultation-btn"]
 
615
  )
616
  end_consultation_status = gr.HTML(label="Status", elem_id="end_consultation_status")
617
 
618
  with gr.Tab("ℹ️ About"):
619
  gr.Markdown(
620
  """
621
+ <div class="medisync-card medisync-card-bg medisync-force-text">
622
+ <h2 class="medisync-title medisync-blue">
623
+ <b>About MediSync</b>
624
+ </h2>
625
  <p>
626
  <b>MediSync</b> is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
627
  </p>
 
666
  )
667
 
668
  def handle_end_consultation(appointment_id):
669
+ # Output status: styled with color for buttons and clear status box, as per template
670
  if not appointment_id or appointment_id.strip() == "":
671
+ return "<div style='color: #000; background: #fff; padding: 10px; border-radius: 5px;'>Please enter your appointment ID first.</div>"
672
  result = complete_appointment(appointment_id.strip())
673
  if result["status"] == "success":
674
  doctors_urls = get_doctors_page_urls()
675
  html_response = f"""
676
+ <div style='color: #000; background: #fff; padding: 15px; border-radius: 5px; margin: 10px 0;'>
677
+ <h3 style="color: #28a745;">✅ Consultation Completed Successfully!</h3>
678
+ <p style="color: #28a745;">✔️ {result['message']}</p>
679
  <p>Your appointment has been marked as completed.</p>
680
+ <button class="btn-green" onclick="window.open('{doctors_urls['local']}', '_blank')"
681
+ style="margin-top: 10px;">
682
  Return to Doctors Page (Local)
683
  </button>
684
+ <button class="btn-purple" onclick="window.open('{doctors_urls['production']}', '_blank')"
685
+ style="margin-top: 10px; margin-left: 10px;">
686
  Return to Doctors Page (Production)
687
  </button>
688
  </div>
 
690
  else:
691
  if "Cannot connect to Flask app" in result['message']:
692
  html_response = f"""
693
+ <div style='color: #000; background: #fff; padding: 15px; border-radius: 5px; margin: 10px 0;'>
694
+ <h3 style="color: #ff9800;">⚠️ Consultation Ready to Complete</h3>
695
  <p>Your consultation analysis is complete! However, we cannot automatically mark your appointment as completed because the Flask app is not accessible from this environment.</p>
696
  <p><strong>Appointment ID:</strong> {appointment_id.strip()}</p>
697
  <p><strong>Next Steps:</strong></p>
 
701
  <li>Manually complete the appointment using the appointment ID</li>
702
  </ol>
703
  <div style="margin-top: 15px;">
704
+ <button class="btn-green" onclick="window.open('http://127.0.0.1:600/complete_appointment_manual?appointment_id={appointment_id.strip()}', '_blank')" style="margin-right: 10px;">
 
705
  Complete Appointment
706
  </button>
707
+ <button class="btn-purple" onclick="window.open('http://127.0.0.1:600/doctors', '_blank')" style="margin-right: 10px;">
 
708
  Return to Doctors Page
709
  </button>
710
+ <button class="btn-dark" onclick="navigator.clipboard.writeText('{appointment_id.strip()}')">
 
711
  Copy Appointment ID
712
  </button>
713
  </div>
 
715
  """
716
  else:
717
  html_response = f"""
718
+ <div style='color: #000; background: #fff; padding: 15px; border-radius: 5px; margin: 10px 0;'>
719
+ <h3 style="color: #dc3545;">❌ Error Completing Consultation</h3>
720
  <p>{result['message']}</p>
721
  <p>Please try again or contact support if the problem persists.</p>
722
  </div>
 
729
  outputs=[end_consultation_status]
730
  )
731
 
732
+ # --- BRUTAL JS: forcibly set the appointment id textbox from URL param, every 500ms, forever ---
733
  gr.HTML("""
734
  <script>
735
  function getUrlParameter(name) {
736
  name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
737
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
738
+ var results = regex.exec(window.location.search);
739
  return results === null ? '' : decodeURIComponent(results[1].replace(/\\+/g, ' '));
740
  }
741
+ function setAppointmentIdBrutal() {
742
  var appointmentId = getUrlParameter('appointment_id');
743
+ var input = document.getElementById('appointment_id_input');
744
+ if (input && appointmentId && input.value !== appointmentId) {
745
+ input.value = appointmentId;
746
+ // For Gradio >=3.41, fire input event and change event
747
+ var event = new Event('input', { bubbles: true });
748
+ input.dispatchEvent(event);
749
+ var event2 = new Event('change', { bubbles: true });
750
+ input.dispatchEvent(event2);
751
  }
752
  }
753
+ // Run every 500ms, forever, to brutally force the value
754
+ setInterval(setAppointmentIdBrutal, 500);
755
+ // Also run immediately on load
756
+ window.addEventListener('DOMContentLoaded', setAppointmentIdBrutal);
757
+ window.addEventListener('load', setAppointmentIdBrutal);
 
758
  </script>
759
  """)
760
 
761
  interface.launch()
762
 
763
  if __name__ == "__main__":
764
+ create_interface()