SmartHeal commited on
Commit
49f9560
Β·
verified Β·
1 Parent(s): 981c773

Update src/ui_components_original.py

Browse files
Files changed (1) hide show
  1. src/ui_components_original.py +135 -284
src/ui_components_original.py CHANGED
@@ -2,12 +2,10 @@ import gradio as gr
2
  import os
3
  import re
4
  import logging
5
- import tempfile
6
- import shutil
7
  import base64
8
  from datetime import datetime
9
  from PIL import Image
10
- import html # Import the html module for escaping
11
 
12
  from .patient_history import PatientHistoryManager, ReportGenerator
13
 
@@ -45,7 +43,7 @@ class UIComponents:
45
  # Ensure uploads directory exists
46
  if not os.path.exists("uploads"):
47
  os.makedirs("uploads", exist_ok=True)
48
-
49
  def image_to_base64(self, image_path):
50
  """Convert image to base64 data URL for embedding in HTML"""
51
  if not image_path or not os.path.exists(image_path):
@@ -70,7 +68,7 @@ class UIComponents:
70
  except Exception as e:
71
  logging.error(f"Error converting image to base64: {e}")
72
  return None
73
-
74
  def markdown_to_html(self, markdown_text):
75
  """Convert markdown text to proper HTML format with enhanced support"""
76
  if not markdown_text:
@@ -104,7 +102,7 @@ class UIComponents:
104
  # Convert horizontal rules
105
  html_text = re.sub(r"^\s*[-*_]{3,}\s*$", r"<hr>", html_text, flags=re.MULTILINE)
106
 
107
- # Convert bullet points and handle nested lists (simplified for example)
108
  lines = html_text.split("\n")
109
  in_list = False
110
  result_lines = []
@@ -129,7 +127,7 @@ class UIComponents:
129
  result_lines.append("</ul>")
130
 
131
  return "\n".join(result_lines)
132
-
133
  def get_organizations_dropdown(self):
134
  """Get list of organizations for dropdown"""
135
  try:
@@ -138,10 +136,10 @@ class UIComponents:
138
  except Exception as e:
139
  logging.error(f"Error getting organizations: {e}")
140
  return ["Default Hospital - Location"]
141
-
142
  def get_custom_css(self):
143
  return """
144
- /* =================== ORIGINAL SMARTHEAL CSS =================== */
145
  /* Global Styling */
146
  body, html {
147
  margin: 0 !important;
@@ -168,23 +166,6 @@ body, html {
168
  overflow: hidden !important;
169
  }
170
 
171
- .medical-header::before {
172
- content: '' !important;
173
- position: absolute !important;
174
- top: 0 !important;
175
- left: 0 !important;
176
- right: 0 !important;
177
- bottom: 0 !important;
178
- background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)" /></svg>') !important;
179
- opacity: 0.1 !important;
180
- z-index: 1 !important;
181
- }
182
-
183
- .medical-header > * {
184
- position: relative !important;
185
- z-index: 2 !important;
186
- }
187
-
188
  .logo {
189
  width: 80px !important;
190
  height: 80px !important;
@@ -229,17 +210,6 @@ body, html {
229
  overflow: hidden !important;
230
  }
231
 
232
- .gr-form::before {
233
- content: '' !important;
234
- position: absolute !important;
235
- top: 0 !important;
236
- left: 0 !important;
237
- right: 0 !important;
238
- height: 4px !important;
239
- background: linear-gradient(90deg, #e53e3e 0%, #f56565 50%, #e53e3e 100%) !important;
240
- z-index: 1 !important;
241
- }
242
-
243
  /* Professional Input Fields */
244
  .gr-textbox, .gr-number {
245
  border-radius: 12px !important;
@@ -252,10 +222,7 @@ body, html {
252
  padding: 16px 20px !important;
253
  }
254
 
255
- .gr-textbox:focus,
256
- .gr-number:focus,
257
- .gr-textbox input:focus,
258
- .gr-number input:focus {
259
  border-color: #E53E3E !important;
260
  box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important;
261
  background: #FFFFFF !important;
@@ -263,32 +230,8 @@ body, html {
263
  transform: translateY(-1px) !important;
264
  }
265
 
266
- .gr-textbox input,
267
- .gr-number input {
268
- background: transparent !important;
269
- border: none !important;
270
- outline: none !important;
271
- color: #1A202C !important;
272
- font-size: 1rem !important;
273
- width: 100% !important;
274
- padding: 0 !important;
275
- }
276
-
277
- .gr-textbox label,
278
- .gr-number label,
279
- .gr-dropdown label,
280
- .gr-radio label,
281
- .gr-checkbox label {
282
- font-weight: 600 !important;
283
- color: #2D3748 !important;
284
- font-size: 1rem !important;
285
- margin-bottom: 8px !important;
286
- display: block !important;
287
- }
288
-
289
  /* Enhanced Button Styling */
290
- button.gr-button,
291
- button.gr-button-primary {
292
  background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important;
293
  color: #FFFFFF !important;
294
  border: none !important;
@@ -306,42 +249,12 @@ button.gr-button-primary {
306
  cursor: pointer !important;
307
  }
308
 
309
- button.gr-button::before {
310
- content: '' !important;
311
- position: absolute !important;
312
- top: 0 !important;
313
- left: -100% !important;
314
- width: 100% !important;
315
- height: 100% !important;
316
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent) !important;
317
- transition: left 0.5s !important;
318
- }
319
-
320
- button.gr-button:hover::before {
321
- left: 100% !important;
322
- }
323
-
324
- button.gr-button:hover,
325
- button.gr-button-primary:hover {
326
  background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important;
327
  box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important;
328
  transform: translateY(-3px) !important;
329
  }
330
 
331
- button.gr-button:active,
332
- button.gr-button-primary:active {
333
- transform: translateY(-1px) !important;
334
- box-shadow: 0 4px 16px rgba(229, 62, 62, 0.5) !important;
335
- }
336
-
337
- button.gr-button:disabled {
338
- background: #A0AEC0 !important;
339
- color: #718096 !important;
340
- cursor: not-allowed !important;
341
- box-shadow: none !important;
342
- transform: none !important;
343
- }
344
-
345
  /* Professional Status Messages */
346
  .status-success {
347
  background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important;
@@ -379,150 +292,6 @@ button.gr-button:disabled {
379
  backdrop-filter: blur(10px) !important;
380
  }
381
 
382
- /* Professional Card Layout */
383
- .medical-card {
384
- background: linear-gradient(145deg, #FFFFFF 0%, #F7FAFC 100%) !important;
385
- border-radius: 20px !important;
386
- padding: 32px !important;
387
- margin: 24px 0 !important;
388
- box-shadow: 0 16px 48px rgba(0, 0, 0, 0.08) !important;
389
- border: 1px solid rgba(229, 62, 62, 0.1) !important;
390
- backdrop-filter: blur(10px) !important;
391
- position: relative !important;
392
- overflow: hidden !important;
393
- }
394
-
395
- .medical-card::before {
396
- content: '' !important;
397
- position: absolute !important;
398
- top: 0 !important;
399
- left: 0 !important;
400
- right: 0 !important;
401
- height: 4px !important;
402
- background: linear-gradient(90deg, #E53E3E 0%, #F56565 50%, #E53E3E 100%) !important;
403
- }
404
-
405
- .medical-card-title {
406
- font-size: 1.75rem !important;
407
- font-weight: 700 !important;
408
- color: #1A202C !important;
409
- margin-bottom: 24px !important;
410
- padding-bottom: 16px !important;
411
- border-bottom: 2px solid #E53E3E !important;
412
- text-align: center !important;
413
- position: relative !important;
414
- }
415
-
416
- .medical-card-title::after {
417
- content: '' !important;
418
- position: absolute !important;
419
- bottom: -2px !important;
420
- left: 50% !important;
421
- transform: translateX(-50%) !important;
422
- width: 60px !important;
423
- height: 4px !important;
424
- background: linear-gradient(90deg, transparent, #E53E3E, transparent) !important;
425
- border-radius: 2px !important;
426
- }
427
-
428
- /* Professional Dropdown Styling */
429
- .gr-dropdown {
430
- border-radius: 12px !important;
431
- border: 2px solid #E2E8F0 !important;
432
- background: #FFFFFF !important;
433
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
434
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
435
- }
436
-
437
- .gr-dropdown:focus,
438
- .gr-dropdown select:focus {
439
- border-color: #E53E3E !important;
440
- box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important;
441
- outline: none !important;
442
- }
443
-
444
- .gr-dropdown select {
445
- background: transparent !important;
446
- border: none !important;
447
- color: #1A202C !important;
448
- font-size: 1rem !important;
449
- padding: 16px 20px !important;
450
- border-radius: 12px !important;
451
- }
452
-
453
- /* Radio button styling */
454
- .gr-radio input[type="radio"] {
455
- margin-right: 8px !important;
456
- transform: scale(1.2) !important;
457
- }
458
-
459
- .gr-radio label {
460
- display: flex !important;
461
- align-items: center !important;
462
- padding: 8px 0 !important;
463
- font-size: 1rem !important;
464
- line-height: 1.5 !important;
465
- cursor: pointer !important;
466
- color: #1A202C !important;
467
- }
468
-
469
- /* Tab styling */
470
- .gr-tab {
471
- color: #1A202C !important;
472
- font-weight: 500 !important;
473
- font-size: 1rem !important;
474
- padding: 12px 20px !important;
475
- background-color: #F7FAFC !important;
476
- }
477
-
478
- .gr-tab.selected {
479
- color: #E53E3E !important;
480
- font-weight: 600 !important;
481
- border-bottom: 2px solid #E53E3E !important;
482
- background-color: #FFFFFF !important;
483
- }
484
-
485
- /* Image upload styling */
486
- .gr-image {
487
- border: 3px dashed #CBD5E0 !important;
488
- border-radius: 16px !important;
489
- background-color: #F7FAFC !important;
490
- transition: all 0.2s ease !important;
491
- }
492
-
493
- .gr-image:hover {
494
- border-color: #E53E3E !important;
495
- background-color: #FFF5F5 !important;
496
- }
497
-
498
- /* Analyze button special styling */
499
- #analyze-btn {
500
- background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important;
501
- color: #FFFFFF !important;
502
- border: none !important;
503
- border-radius: 8px !important;
504
- font-weight: 700 !important;
505
- padding: 14px 28px !important;
506
- font-size: 1.1rem !important;
507
- letter-spacing: 0.5px !important;
508
- text-align: center !important;
509
- transition: all 0.2s ease-in-out !important;
510
- }
511
-
512
- #analyze-btn:hover {
513
- background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important;
514
- box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important;
515
- transform: translateY(-2px) !important;
516
- }
517
-
518
- #analyze-btn:disabled {
519
- background: #A0AEC0 !important;
520
- color: #1A202C !important;
521
- cursor: not-allowed !important;
522
- box-shadow: none !important;
523
- transform: none !important;
524
- }
525
-
526
  /* Image gallery styling for better visualization */
527
  .image-gallery {
528
  display: grid;
@@ -558,6 +327,26 @@ button.gr-button:disabled {
558
  font-size: 0.9em;
559
  }
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  /* Responsive design */
562
  @media (max-width: 768px) {
563
  .medical-header {
@@ -580,12 +369,6 @@ button.gr-button:disabled {
580
  margin: 8px 0 !important;
581
  }
582
 
583
- button.gr-button,
584
- button.gr-button-primary {
585
- padding: 14px 20px !important;
586
- font-size: 14px !important;
587
- }
588
-
589
  .image-gallery {
590
  grid-template-columns: 1fr;
591
  }
@@ -593,10 +376,10 @@ button.gr-button:disabled {
593
  """
594
 
595
  def create_interface(self):
596
- """Create the main Gradio interface with original styling and base64 image embedding"""
597
  with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app:
598
 
599
- # Header with SmartHeal logo (from original)
600
  logo_url = "https://scontent.fccu31-2.fna.fbcdn.net/v/t39.30808-6/275933824_102121829111657_3325198727201325354_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=6ee11a&_nc_ohc=45krrEUpcSUQ7kNvwGVdiMW&_nc_oc=AdkTdxEC_TkYGiyDkEtTJZ_DFZELW17XKFmWpswmFqGB7JSdvTyWtnrQyLS0USngEiY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=ufAA4Hj5gTRwON5POYzz0Q&oh=00_AfW1-jLEN5RGeggqOvGgEaK_gdg0EDgxf_VhKbZwFLUO0Q&oe=6897A98B"
601
 
602
  gr.HTML(f"""
@@ -609,7 +392,7 @@ button.gr-button:disabled {
609
  </div>
610
  """)
611
 
612
- # Professional disclaimer (from original)
613
  gr.HTML("""
614
  <div style="border: 2px solid #FF6B6B; background-color: #FFE5E5; padding: 15px; border-radius: 12px; margin: 10px 0;">
615
  <h3 style="color: #D63031; margin-top: 0;">⚠️ IMPORTANT DISCLAIMER</h3>
@@ -619,9 +402,9 @@ button.gr-button:disabled {
619
  </div>
620
  """)
621
 
622
- # Main interface with conditional visibility (ORIGINAL STRUCTURE)
623
  with gr.Row():
624
- # Professional Authentication Panel (visible when not logged in)
625
  with gr.Column(visible=True) as auth_panel:
626
  gr.HTML("""
627
  <div style="text-align: center; margin: 40px 0;">
@@ -728,7 +511,7 @@ button.gr-button:disabled {
728
  gr.HTML('<div class="medical-card-title">πŸ‘©β€βš•οΈ Practitioner Dashboard</div>')
729
 
730
  user_info = gr.HTML("")
731
- logout_btn_prac = gr.Button("πŸšͺ Logout", variant="secondary", elem_classes=["logout-btn"])
732
 
733
  # Main tabs for different functions
734
  with gr.Tabs():
@@ -774,8 +557,7 @@ button.gr-button:disabled {
774
  gr.HTML("<h3>πŸ“Έ Wound Image Upload</h3>")
775
  wound_image = gr.Image(
776
  label="Upload Wound Image",
777
- type="filepath",
778
- elem_classes=["image-upload"]
779
  )
780
 
781
  gr.HTML("<h3>πŸ“ Medical History</h3>")
@@ -892,27 +674,53 @@ button.gr-button:disabled {
892
  if not wound_image:
893
  return "<div class='status-error'>❌ Please upload a wound image for analysis.</div>"
894
 
895
- # Show loading state
896
  loading_html = """
897
- <div style="text-align:center; padding: 30px;">
898
- <div style="display:inline-block; border:4px solid #3182ce; border-radius:50%; border-top-color:transparent; width:40px; height:40px; animation:spin 1s linear infinite;"></div>
899
- <p style="margin-top:15px; color:#3182ce; font-weight:600;">Processing wound analysis...</p>
 
 
 
 
900
  <style>@keyframes spin {0% {transform:rotate(0deg)} 100% {transform:rotate(360deg)}}</style>
901
  </div>
902
  """
903
 
904
- # 1. Construct questionnaire dictionary for AIProcessor
905
- questionnaire_data = {
906
- 'user_id': self.current_user.get('id'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
  'age': patient_age,
908
  'diabetic': 'Yes' if diabetic_status != 'Non-diabetic' else 'No',
909
  'allergies': allergies,
910
  'date_of_injury': 'Unknown', # Not collected in this form
911
  'professional_care': 'Yes', # Assumed since using professional interface
912
- 'oozing_bleeding': 'None', # Could be mapped from infection_signs
913
  'infection': 'Yes' if infection_signs != 'None' else 'No',
914
  'moisture': moisture_level,
915
- # Additional fields for comprehensive analysis
916
  'patient_name': patient_name,
917
  'patient_gender': patient_gender,
918
  'wound_location': wound_location,
@@ -923,44 +731,87 @@ button.gr-button:disabled {
923
  'medications': medications,
924
  'additional_notes': additional_notes
925
  }
926
-
927
- # 2. Save questionnaire in DB first
928
- questionnaire_id = self.database_manager.save_questionnaire(questionnaire_data)
929
 
930
  # 3. Run AI analysis using the AIProcessor class
931
  try:
932
- logging.info(f"Starting AI analysis for image: {wound_image}")
933
 
934
  # Use the AIProcessor analyze_wound method
935
- analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data)
936
 
937
  if not analysis_result.get('success', False):
938
- raise Exception(analysis_result.get('error', 'Analysis failed for unknown reason'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
 
940
- # 4. Save AI analysis result to database
941
- self.database_manager.save_analysis_result(questionnaire_id, analysis_result)
 
 
 
 
 
 
 
 
942
 
943
  # 5. Format comprehensive analysis results with all images
944
  formatted_analysis = self._format_comprehensive_analysis_results(
945
- analysis_result, wound_image, questionnaire_data
946
  )
947
 
948
  return formatted_analysis
949
 
950
  except Exception as analysis_error:
951
- logging.error(f"AI analysis error: {analysis_error}")
952
  return f"""
953
- <div class='status-error' style='padding: 20px; background: #ffeeee; border-left: 5px solid #ff5555; margin: 20px 0;'>
954
- <h3>❌ Analysis Error</h3>
955
- <p>There was an error analyzing the wound image:</p>
956
- <pre style='background: #f5f5f5; padding: 10px; border-radius: 5px;'>{str(analysis_error)}</pre>
957
- <p>Please try again with a different image or contact support.</p>
 
 
 
 
 
 
 
 
958
  </div>
959
  """
960
 
961
  except Exception as e:
962
- logging.error(f"Analysis handler error: {e}")
963
- return f"<div class='status-error'>❌ Analysis failed: {str(e)}</div>"
 
 
 
 
 
 
 
 
 
964
 
965
  def handle_logout():
966
  self.current_user = {}
@@ -983,7 +834,7 @@ button.gr-button:disabled {
983
 
984
  def load_patient_history():
985
  try:
986
- user_id = self.current_user.get('id')
987
  if not user_id:
988
  return "<div class='status-error'>❌ Please login first.</div>"
989
 
@@ -996,7 +847,7 @@ button.gr-button:disabled {
996
 
997
  def search_specific_patient(patient_name):
998
  try:
999
- user_id = self.current_user.get('id')
1000
  if not user_id:
1001
  return "<div class='status-error'>❌ Please login first.</div>"
1002
 
@@ -1116,7 +967,7 @@ button.gr-button:disabled {
1116
  risk_class = "low"
1117
  if risk_level.lower() == "moderate":
1118
  risk_class = "moderate"
1119
- elif risk_level.lower() == "high":
1120
  risk_class = "high"
1121
 
1122
  # Format risk factors
 
2
  import os
3
  import re
4
  import logging
 
 
5
  import base64
6
  from datetime import datetime
7
  from PIL import Image
8
+ import html
9
 
10
  from .patient_history import PatientHistoryManager, ReportGenerator
11
 
 
43
  # Ensure uploads directory exists
44
  if not os.path.exists("uploads"):
45
  os.makedirs("uploads", exist_ok=True)
46
+
47
  def image_to_base64(self, image_path):
48
  """Convert image to base64 data URL for embedding in HTML"""
49
  if not image_path or not os.path.exists(image_path):
 
68
  except Exception as e:
69
  logging.error(f"Error converting image to base64: {e}")
70
  return None
71
+
72
  def markdown_to_html(self, markdown_text):
73
  """Convert markdown text to proper HTML format with enhanced support"""
74
  if not markdown_text:
 
102
  # Convert horizontal rules
103
  html_text = re.sub(r"^\s*[-*_]{3,}\s*$", r"<hr>", html_text, flags=re.MULTILINE)
104
 
105
+ # Convert bullet points
106
  lines = html_text.split("\n")
107
  in_list = False
108
  result_lines = []
 
127
  result_lines.append("</ul>")
128
 
129
  return "\n".join(result_lines)
130
+
131
  def get_organizations_dropdown(self):
132
  """Get list of organizations for dropdown"""
133
  try:
 
136
  except Exception as e:
137
  logging.error(f"Error getting organizations: {e}")
138
  return ["Default Hospital - Location"]
139
+
140
  def get_custom_css(self):
141
  return """
142
+ /* =================== SMARTHEAL CSS =================== */
143
  /* Global Styling */
144
  body, html {
145
  margin: 0 !important;
 
166
  overflow: hidden !important;
167
  }
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  .logo {
170
  width: 80px !important;
171
  height: 80px !important;
 
210
  overflow: hidden !important;
211
  }
212
 
 
 
 
 
 
 
 
 
 
 
 
213
  /* Professional Input Fields */
214
  .gr-textbox, .gr-number {
215
  border-radius: 12px !important;
 
222
  padding: 16px 20px !important;
223
  }
224
 
225
+ .gr-textbox:focus, .gr-number:focus, .gr-textbox input:focus, .gr-number input:focus {
 
 
 
226
  border-color: #E53E3E !important;
227
  box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important;
228
  background: #FFFFFF !important;
 
230
  transform: translateY(-1px) !important;
231
  }
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  /* Enhanced Button Styling */
234
+ button.gr-button, button.gr-button-primary {
 
235
  background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important;
236
  color: #FFFFFF !important;
237
  border: none !important;
 
249
  cursor: pointer !important;
250
  }
251
 
252
+ button.gr-button:hover, button.gr-button-primary:hover {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important;
254
  box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important;
255
  transform: translateY(-3px) !important;
256
  }
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  /* Professional Status Messages */
259
  .status-success {
260
  background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important;
 
292
  backdrop-filter: blur(10px) !important;
293
  }
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  /* Image gallery styling for better visualization */
296
  .image-gallery {
297
  display: grid;
 
327
  font-size: 0.9em;
328
  }
329
 
330
+ /* Analyze button special styling */
331
+ #analyze-btn {
332
+ background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important;
333
+ color: #FFFFFF !important;
334
+ border: none !important;
335
+ border-radius: 8px !important;
336
+ font-weight: 700 !important;
337
+ padding: 14px 28px !important;
338
+ font-size: 1.1rem !important;
339
+ letter-spacing: 0.5px !important;
340
+ text-align: center !important;
341
+ transition: all 0.2s ease-in-out !important;
342
+ }
343
+
344
+ #analyze-btn:hover {
345
+ background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important;
346
+ box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important;
347
+ transform: translateY(-2px) !important;
348
+ }
349
+
350
  /* Responsive design */
351
  @media (max-width: 768px) {
352
  .medical-header {
 
369
  margin: 8px 0 !important;
370
  }
371
 
 
 
 
 
 
 
372
  .image-gallery {
373
  grid-template-columns: 1fr;
374
  }
 
376
  """
377
 
378
  def create_interface(self):
379
+ """Create the main Gradio interface with comprehensive analysis display"""
380
  with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app:
381
 
382
+ # Header with SmartHeal logo
383
  logo_url = "https://scontent.fccu31-2.fna.fbcdn.net/v/t39.30808-6/275933824_102121829111657_3325198727201325354_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=6ee11a&_nc_ohc=45krrEUpcSUQ7kNvwGVdiMW&_nc_oc=AdkTdxEC_TkYGiyDkEtTJZ_DFZELW17XKFmWpswmFqGB7JSdvTyWtnrQyLS0USngEiY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=ufAA4Hj5gTRwON5POYzz0Q&oh=00_AfW1-jLEN5RGeggqOvGgEaK_gdg0EDgxf_VhKbZwFLUO0Q&oe=6897A98B"
384
 
385
  gr.HTML(f"""
 
392
  </div>
393
  """)
394
 
395
+ # Professional disclaimer
396
  gr.HTML("""
397
  <div style="border: 2px solid #FF6B6B; background-color: #FFE5E5; padding: 15px; border-radius: 12px; margin: 10px 0;">
398
  <h3 style="color: #D63031; margin-top: 0;">⚠️ IMPORTANT DISCLAIMER</h3>
 
402
  </div>
403
  """)
404
 
405
+ # Main interface with conditional visibility
406
  with gr.Row():
407
+ # Authentication Panel (visible when not logged in)
408
  with gr.Column(visible=True) as auth_panel:
409
  gr.HTML("""
410
  <div style="text-align: center; margin: 40px 0;">
 
511
  gr.HTML('<div class="medical-card-title">πŸ‘©β€βš•οΈ Practitioner Dashboard</div>')
512
 
513
  user_info = gr.HTML("")
514
+ logout_btn_prac = gr.Button("πŸšͺ Logout", variant="secondary")
515
 
516
  # Main tabs for different functions
517
  with gr.Tabs():
 
557
  gr.HTML("<h3>πŸ“Έ Wound Image Upload</h3>")
558
  wound_image = gr.Image(
559
  label="Upload Wound Image",
560
+ type="filepath"
 
561
  )
562
 
563
  gr.HTML("<h3>πŸ“ Medical History</h3>")
 
674
  if not wound_image:
675
  return "<div class='status-error'>❌ Please upload a wound image for analysis.</div>"
676
 
677
+ # Show loading state first
678
  loading_html = """
679
+ <div style="text-align:center; padding: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; color: white; margin: 20px 0;">
680
+ <div style="display:inline-block; border:4px solid rgba(255,255,255,0.3); border-radius:50%; border-top-color:white; width:50px; height:50px; animation:spin 1s linear infinite; margin-bottom: 20px;"></div>
681
+ <h3 style="margin: 0; font-size: 1.5rem;">πŸ”¬ SmartHeal AI Processing</h3>
682
+ <p style="margin: 10px 0 0 0; font-size: 1rem; opacity: 0.9;">Analyzing wound image with advanced computer vision...</p>
683
+ <div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 10px; margin-top: 15px;">
684
+ <p style="margin: 0; font-size: 0.9rem;">⚑ Detection β†’ πŸ“ Segmentation β†’ πŸ€– AI Report</p>
685
+ </div>
686
  <style>@keyframes spin {0% {transform:rotate(0deg)} 100% {transform:rotate(360deg)}}</style>
687
  </div>
688
  """
689
 
690
+ # 1. Save questionnaire FIRST to get valid ID
691
+ questionnaire_data_for_db = {
692
+ 'user_id': self.current_user.get('id', 1), # Default to 1 if no user logged in
693
+ 'patient_name': patient_name,
694
+ 'patient_age': patient_age,
695
+ 'patient_gender': patient_gender,
696
+ 'wound_location': wound_location,
697
+ 'wound_duration': wound_duration,
698
+ 'pain_level': pain_level,
699
+ 'moisture_level': moisture_level,
700
+ 'infection_signs': infection_signs,
701
+ 'diabetic_status': diabetic_status,
702
+ 'previous_treatment': previous_treatment,
703
+ 'medical_history': medical_history,
704
+ 'medications': medications,
705
+ 'allergies': allergies,
706
+ 'additional_notes': additional_notes
707
+ }
708
+
709
+ # Save questionnaire FIRST to get the ID
710
+ questionnaire_response_id = self.database_manager.save_questionnaire(questionnaire_data_for_db)
711
+ logging.info(f"βœ… Questionnaire saved with response ID: {questionnaire_response_id}")
712
+
713
+ # 2. Create AIProcessor questionnaire format
714
+ questionnaire_data_for_ai = {
715
  'age': patient_age,
716
  'diabetic': 'Yes' if diabetic_status != 'Non-diabetic' else 'No',
717
  'allergies': allergies,
718
  'date_of_injury': 'Unknown', # Not collected in this form
719
  'professional_care': 'Yes', # Assumed since using professional interface
720
+ 'oozing_bleeding': 'Minor Oozing' if infection_signs != 'None' else 'None',
721
  'infection': 'Yes' if infection_signs != 'None' else 'No',
722
  'moisture': moisture_level,
723
+ # Additional comprehensive fields
724
  'patient_name': patient_name,
725
  'patient_gender': patient_gender,
726
  'wound_location': wound_location,
 
731
  'medications': medications,
732
  'additional_notes': additional_notes
733
  }
 
 
 
734
 
735
  # 3. Run AI analysis using the AIProcessor class
736
  try:
737
+ logging.info(f"πŸ”¬ Starting AI analysis for image: {wound_image}")
738
 
739
  # Use the AIProcessor analyze_wound method
740
+ analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data_for_ai)
741
 
742
  if not analysis_result.get('success', False):
743
+ error_msg = analysis_result.get('error', 'Analysis failed for unknown reason')
744
+ logging.error(f"❌ AI Analysis failed: {error_msg}")
745
+
746
+ return f"""
747
+ <div class='status-error' style='padding: 30px; background: #fff5f5; border-left: 5px solid #e53e3e; margin: 20px 0; border-radius: 12px;'>
748
+ <h3 style="color: #c53030; margin-top: 0;">❌ AI Analysis Error</h3>
749
+ <p style="color: #742a2a; font-size: 1.1rem;"><strong>Error Details:</strong></p>
750
+ <div style='background: #fed7d7; padding: 15px; border-radius: 8px; margin: 10px 0;'>
751
+ <code style='color: #742a2a; font-size: 0.9rem;'>{error_msg}</code>
752
+ </div>
753
+ <p style="color: #742a2a;">Please try again with a different image or contact technical support.</p>
754
+ <div style="margin-top: 15px; padding: 15px; background: #fff; border-radius: 8px; border: 1px solid #feb2b2;">
755
+ <strong>Troubleshooting Tips:</strong>
756
+ <ul style="margin: 10px 0; color: #742a2a;">
757
+ <li>Ensure image is clear and well-lit</li>
758
+ <li>Wound should be clearly visible in the image</li>
759
+ <li>Try a different image format (PNG, JPG)</li>
760
+ <li>Check image file size (max 10MB recommended)</li>
761
+ </ul>
762
+ </div>
763
+ </div>
764
+ """
765
 
766
+ logging.info("βœ… AI Analysis completed successfully")
767
+
768
+ # 4. Save analysis result with valid questionnaire_response_id
769
+ try:
770
+ if questionnaire_response_id:
771
+ self.database_manager.save_analysis_result(questionnaire_response_id, analysis_result)
772
+ logging.info("βœ… Analysis result saved to database")
773
+ except Exception as db_error:
774
+ logging.error(f"❌ Database save error: {db_error}")
775
+ # Continue with display even if DB save fails
776
 
777
  # 5. Format comprehensive analysis results with all images
778
  formatted_analysis = self._format_comprehensive_analysis_results(
779
+ analysis_result, wound_image, questionnaire_data_for_ai
780
  )
781
 
782
  return formatted_analysis
783
 
784
  except Exception as analysis_error:
785
+ logging.error(f"❌ AI analysis exception: {analysis_error}", exc_info=True)
786
  return f"""
787
+ <div class='status-error' style='padding: 30px; background: #fff5f5; border-left: 5px solid #e53e3e; margin: 20px 0; border-radius: 12px;'>
788
+ <h3 style="color: #c53030; margin-top: 0;">❌ Analysis Processing Error</h3>
789
+ <p style="color: #742a2a;">There was an unexpected error during wound analysis:</p>
790
+ <div style='background: #fed7d7; padding: 15px; border-radius: 8px; margin: 15px 0;'>
791
+ <code style='color: #742a2a; font-size: 0.9rem; word-break: break-word;'>{str(analysis_error)}</code>
792
+ </div>
793
+ <p style="color: #742a2a;"><strong>Next Steps:</strong></p>
794
+ <ul style="color: #742a2a; margin: 10px 0;">
795
+ <li>Refresh the page and try again</li>
796
+ <li>Check your internet connection</li>
797
+ <li>Try with a different wound image</li>
798
+ <li>Contact system administrator if the problem persists</li>
799
+ </ul>
800
  </div>
801
  """
802
 
803
  except Exception as e:
804
+ logging.error(f"❌ Analysis handler error: {e}", exc_info=True)
805
+ return f"""
806
+ <div class='status-error' style='padding: 30px; background: #fff5f5; border-left: 5px solid #e53e3e; margin: 20px 0; border-radius: 12px;'>
807
+ <h3 style="color: #c53030; margin-top: 0;">❌ System Error</h3>
808
+ <p style="color: #742a2a;">A system error occurred while processing your request:</p>
809
+ <div style='background: #fed7d7; padding: 15px; border-radius: 8px; margin: 15px 0;'>
810
+ <code style='color: #742a2a; font-size: 0.9rem;'>{str(e)}</code>
811
+ </div>
812
+ <p style="color: #742a2a;">Please contact technical support if this issue continues.</p>
813
+ </div>
814
+ """
815
 
816
  def handle_logout():
817
  self.current_user = {}
 
834
 
835
  def load_patient_history():
836
  try:
837
+ user_id = self.current_user.get('id', 1)
838
  if not user_id:
839
  return "<div class='status-error'>❌ Please login first.</div>"
840
 
 
847
 
848
  def search_specific_patient(patient_name):
849
  try:
850
+ user_id = self.current_user.get('id', 1)
851
  if not user_id:
852
  return "<div class='status-error'>❌ Please login first.</div>"
853
 
 
967
  risk_class = "low"
968
  if risk_level.lower() == "moderate":
969
  risk_class = "moderate"
970
+ elif risk_level.lower() in ["high", "very high"]:
971
  risk_class = "high"
972
 
973
  # Format risk factors