| <!DOCTYPE html> |
| <html lang="en" data-theme="dark"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Heart Health Analysis System</title> |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> |
| <style> |
| :root[data-theme="dark"] { |
| --primary-color: #f03e5f; |
| --secondary-color: #f5768d; |
| --accent-color: #d1062a; |
| --text-color: #ffffff; |
| --text-light: #fccad3; |
| --background-start: #1a1a2e; |
| --background-end: #16213e; |
| --card-color: rgba(26, 26, 46, 0.8); |
| --border-radius: 12px; |
| --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); |
| --transition: all 0.3s ease; |
| --placeholder-color: rgba(255, 255, 255, 0.7); |
| } |
| |
| :root[data-theme="light"] { |
| --primary-color: #f03e5f; |
| --secondary-color: #f5768d; |
| --accent-color: #d1062a; |
| --text-color: #333333; |
| --text-light: #8a0018; |
| --background-start: #f5f5f5; |
| --background-end: #ffffff; |
| --card-color: rgba(255, 255, 255, 0.9); |
| --border-radius: 12px; |
| --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| --transition: all 0.3s ease; |
| --placeholder-color: rgba(138, 0, 24, 0.5); |
| } |
| |
| body { |
| background: linear-gradient(135deg, |
| rgba(26, 26, 46, 0.95) 0%, |
| rgba(22, 33, 62, 0.95) 50%, |
| rgba(209, 6, 42, 0.2) 100% |
| ); |
| color: var(--text-color); |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| padding: 20px; |
| min-height: 100vh; |
| height: 100vh; |
| position: relative; |
| overflow: hidden; |
| width: 100%; |
| max-width: 100vw; |
| } |
| |
| body::before { |
| content: ''; |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: radial-gradient( |
| circle at top left, |
| rgba(114, 9, 183, 0.2) 0%, |
| transparent 50% |
| ), |
| radial-gradient( |
| circle at top right, |
| rgba(76, 201, 240, 0.1) 0%, |
| transparent 50% |
| ); |
| z-index: -1; |
| } |
| |
| .container { |
| background-color: var(--card-color); |
| border-radius: var(--border-radius); |
| padding: 30px; |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| max-width: 100%; |
| max-height: calc(100vh - 40px); |
| overflow-y: auto; |
| overflow-x: hidden; |
| } |
| |
| .header { |
| text-align: center; |
| margin-bottom: 40px; |
| animation: fadeInDown 1s ease; |
| padding: 20px; |
| } |
| |
| .header h1 { |
| background: linear-gradient(135deg, var(--accent-color), var(--primary-color)); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| font-weight: 700; |
| margin-bottom: 10px; |
| } |
| |
| .header p { |
| color: var(--text-light); |
| font-size: 1.1rem; |
| } |
| |
| .nav-tabs { |
| border: none; |
| margin-bottom: 30px; |
| background: rgba(26, 26, 46, 0.6); |
| padding: 10px; |
| border-radius: var(--border-radius); |
| box-shadow: var(--box-shadow); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .nav-tabs .nav-link { |
| border: none; |
| color: var(--text-light); |
| padding: 12px 24px; |
| border-radius: var(--border-radius); |
| transition: var(--transition); |
| font-weight: 500; |
| } |
| |
| .nav-tabs .nav-link:hover { |
| background: linear-gradient(135deg, rgba(255, 77, 109, 0.2), rgba(114, 9, 183, 0.1)); |
| color: var(--primary-color); |
| } |
| |
| .nav-tabs .nav-link.active { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| color: white; |
| box-shadow: 0 4px 8px rgba(255, 77, 109, 0.3); |
| } |
| |
| .tab-content { |
| animation: fadeIn 0.5s ease; |
| } |
| |
| .ecg-plot { |
| max-width: 100%; |
| width: 100%; |
| height: auto; |
| margin-top: 20px; |
| border-radius: var(--border-radius); |
| box-shadow: var(--box-shadow); |
| animation: fadeIn 1s ease; |
| display: none; |
| } |
| |
| #ecgPlotContainer { |
| width: 100%; |
| margin-top: 20px; |
| overflow: visible; |
| } |
| |
| .analysis-section { |
| background-color: rgba(26, 26, 46, 0.6); |
| border-radius: var(--border-radius); |
| padding: 30px; |
| margin-bottom: 30px; |
| box-shadow: var(--box-shadow); |
| transition: var(--transition); |
| animation: fadeInUp 0.8s ease; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| max-width: 100%; |
| overflow: visible; |
| } |
| |
| .analysis-section:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); |
| } |
| |
| .form-control { |
| border-radius: var(--border-radius); |
| padding: 12px; |
| border: 2px solid rgba(255, 255, 255, 0.1); |
| transition: var(--transition); |
| background-color: rgba(26, 26, 46, 0.8); |
| color: var(--text-color); |
| } |
| |
| .form-control:focus { |
| background-color: rgba(26, 26, 46, 0.9); |
| border-color: var(--primary-color); |
| color: var(--text-color); |
| box-shadow: 0 0 0 0.2rem rgba(255, 77, 109, 0.25); |
| } |
| |
| .form-control::placeholder { |
| color: var(--placeholder-color); |
| } |
| |
| .btn-primary { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| border: none; |
| padding: 12px 24px; |
| border-radius: var(--border-radius); |
| font-weight: 500; |
| transition: var(--transition); |
| box-shadow: 0 4px 8px rgba(255, 77, 109, 0.3); |
| } |
| |
| .btn-primary:hover { |
| background: linear-gradient(135deg, var(--accent-color), var(--primary-color)); |
| } |
| |
| .result-box { |
| border: none; |
| border-radius: var(--border-radius); |
| padding: 20px; |
| margin-top: 20px; |
| background: linear-gradient(135deg, |
| rgba(76, 201, 240, 0.1), |
| rgba(114, 9, 183, 0.05) |
| ); |
| animation: fadeIn 0.8s ease; |
| border: 1px solid rgba(76, 201, 240, 0.2); |
| } |
| |
| .result-box.anomaly { |
| background: linear-gradient(135deg, |
| rgba(240, 62, 95, 0.1), |
| rgba(209, 6, 42, 0.05) |
| ); |
| border: 1px solid rgba(240, 62, 95, 0.2); |
| } |
| |
| .result-box.normal { |
| background: linear-gradient(135deg, |
| rgba(76, 201, 240, 0.1), |
| rgba(76, 201, 240, 0.05) |
| ); |
| border: 1px solid rgba(76, 201, 240, 0.2); |
| } |
| |
| .progress { |
| height: 25px; |
| border-radius: var(--border-radius); |
| background-color: rgba(0, 0, 0, 0.3); |
| margin: 15px 0; |
| overflow: hidden; |
| } |
| |
| .progress-bar { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| transition: width 1s ease; |
| } |
| |
| .input-guidelines { |
| font-size: 0.85rem; |
| color: var(--text-light); |
| margin-top: 0.5rem; |
| padding-left: 5px; |
| } |
| |
| .form-label { |
| font-weight: 600; |
| color: var(--text-color); |
| margin-bottom: 0.5rem; |
| } |
| |
| .loading { |
| display: none; |
| text-align: center; |
| margin: 20px 0; |
| } |
| |
| .loading i { |
| color: var(--primary-color); |
| font-size: 2rem; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| |
| @keyframes fadeInUp { |
| from { |
| opacity: 0; |
| transform: translateY(20px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes fadeInDown { |
| from { |
| opacity: 0; |
| transform: translateY(-20px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| .alert { |
| border-radius: var(--border-radius); |
| padding: 15px; |
| margin-top: 20px; |
| animation: fadeIn 0.5s ease; |
| } |
| |
| |
| ::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| ::-webkit-scrollbar-track { |
| background: rgba(0, 0, 0, 0.2); |
| } |
| |
| ::-webkit-scrollbar-thumb { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| border-radius: 4px; |
| } |
| |
| ::-webkit-scrollbar-thumb:hover { |
| background: linear-gradient(135deg, var(--accent-color), var(--primary-color)); |
| } |
| |
| |
| select.form-control { |
| appearance: none; |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23ffffff' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); |
| background-repeat: no-repeat; |
| background-position: right 12px center; |
| padding-right: 35px; |
| } |
| |
| select.form-control option { |
| background-color: rgba(26, 26, 46, 0.95); |
| color: var(--text-color); |
| padding: 10px; |
| } |
| |
| |
| input[type="file"].form-control { |
| padding: 8px; |
| cursor: pointer; |
| } |
| |
| input[type="file"].form-control::-webkit-file-upload-button { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| border-radius: var(--border-radius); |
| margin-right: 10px; |
| cursor: pointer; |
| transition: var(--transition); |
| } |
| |
| input[type="file"].form-control::-webkit-file-upload-button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 8px rgba(255, 77, 109, 0.3); |
| } |
| |
| |
| input[type="file"].form-control::file-selector-button { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| border-radius: var(--border-radius); |
| margin-right: 10px; |
| cursor: pointer; |
| transition: var(--transition); |
| } |
| |
| input[type="file"].form-control::file-selector-button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 8px rgba(255, 77, 109, 0.3); |
| } |
| |
| |
| .dropdown-menu { |
| background-color: rgba(26, 26, 46, 0.95); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); |
| } |
| |
| .dropdown-item { |
| color: var(--text-color); |
| padding: 10px 20px; |
| } |
| |
| .dropdown-item:hover { |
| background-color: rgba(255, 77, 109, 0.2); |
| color: var(--primary-color); |
| } |
| |
| .dropdown-item.active { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| color: white; |
| } |
| |
| .emergency-button { |
| position: fixed; |
| bottom: 30px; |
| right: 30px; |
| background: linear-gradient(135deg, var(--accent-color), #8a0018); |
| color: white; |
| border: none; |
| padding: 15px 30px; |
| border-radius: 50px; |
| font-size: 1.2rem; |
| font-weight: bold; |
| cursor: pointer; |
| transition: var(--transition); |
| box-shadow: 0 4px 15px rgba(255, 0, 0, 0.3); |
| z-index: 1000; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .emergency-button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 20px rgba(255, 0, 0, 0.4); |
| } |
| |
| .emergency-button i { |
| font-size: 1.4rem; |
| } |
| |
| .theme-toggle { |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| background: var(--card-color); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| color: var(--text-color); |
| padding: 10px 20px; |
| border-radius: var(--border-radius); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| transition: var(--transition); |
| z-index: 1000; |
| } |
| |
| .theme-toggle:hover { |
| transform: translateY(-2px); |
| box-shadow: var(--box-shadow); |
| } |
| |
| .theme-toggle i { |
| font-size: 1.2rem; |
| } |
| |
| [data-theme="light"] .container { |
| background-color: var(--card-color); |
| border: 1px solid rgba(0, 0, 0, 0.1); |
| } |
| |
| [data-theme="light"] .form-control { |
| background-color: rgba(255, 255, 255, 0.9); |
| border: 2px solid rgba(0, 0, 0, 0.1); |
| color: var(--text-color); |
| } |
| |
| [data-theme="light"] .form-control:focus { |
| background-color: #ffffff; |
| border-color: var(--primary-color); |
| color: var(--text-color); |
| } |
| |
| [data-theme="light"] .nav-tabs { |
| background: rgba(255, 255, 255, 0.9); |
| border: 1px solid rgba(0, 0, 0, 0.1); |
| } |
| |
| [data-theme="light"] .analysis-section { |
| background-color: rgba(255, 255, 255, 0.9); |
| border: 1px solid rgba(0, 0, 0, 0.1); |
| } |
| |
| [data-theme="light"] .result-box { |
| background: linear-gradient(135deg, |
| rgba(76, 201, 240, 0.1), |
| rgba(114, 9, 183, 0.05) |
| ); |
| border: 1px solid rgba(76, 201, 240, 0.2); |
| } |
| |
| [data-theme="light"] .result-box.anomaly { |
| background: linear-gradient(135deg, |
| rgba(240, 62, 95, 0.15), |
| rgba(209, 6, 42, 0.1) |
| ); |
| border: 1px solid rgba(240, 62, 95, 0.3); |
| } |
| |
| [data-theme="light"] .result-box.normal { |
| background: linear-gradient(135deg, |
| rgba(76, 201, 240, 0.15), |
| rgba(76, 201, 240, 0.1) |
| ); |
| border: 1px solid rgba(76, 201, 240, 0.3); |
| } |
| |
| |
| #ai-doctor .analysis-section { |
| padding: 0; |
| background: transparent; |
| box-shadow: none; |
| height: calc(100vh - 100px); |
| display: flex; |
| flex-direction: column; |
| position: relative; |
| margin-bottom: 0; |
| } |
| |
| #ai-doctor .header { |
| margin-bottom: 0; |
| padding: 15px 20px; |
| background: rgba(26, 26, 46, 0.9); |
| border-radius: var(--border-radius) var(--border-radius) 0 0; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| border-bottom: none; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 15px; |
| z-index: 2; |
| text-align: center; |
| } |
| |
| #ai-doctor .header h3 { |
| margin: 0; |
| color: var(--primary-color); |
| font-size: 1.2rem; |
| } |
| |
| #ai-doctor .header p { |
| margin: 0; |
| opacity: 0.8; |
| font-size: 0.9rem; |
| } |
| |
| #ai-doctor .header i { |
| font-size: 1.5rem; |
| color: var(--primary-color); |
| } |
| |
| .chat-container { |
| display: flex; |
| flex-direction: column; |
| flex: 1; |
| background: rgba(26, 26, 46, 0.6); |
| border-radius: 0 0 var(--border-radius) var(--border-radius); |
| overflow: hidden; |
| position: relative; |
| border: 1px solid rgba(252, 202, 211, 0.1); |
| border-top: none; |
| margin: 0; |
| height: calc(100% - 60px); |
| } |
| |
| .chat-messages { |
| flex: 1; |
| overflow-y: auto; |
| overflow-x: hidden; |
| padding: 20px; |
| padding-bottom: 80px; |
| display: flex; |
| flex-direction: column; |
| gap: 15px; |
| background: linear-gradient(135deg, |
| rgba(26, 26, 46, 0.95) 0%, |
| rgba(22, 33, 62, 0.95) 50%, |
| rgba(209, 6, 42, 0.1) 100% |
| ); |
| } |
| |
| .message { |
| max-width: 75%; |
| padding: 12px 16px; |
| border-radius: 12px; |
| position: relative; |
| word-wrap: break-word; |
| overflow-wrap: break-word; |
| box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); |
| margin: 2px 0; |
| } |
| |
| .message.user { |
| align-self: flex-end; |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| color: white; |
| border-bottom-right-radius: 4px; |
| margin-left: 15%; |
| } |
| |
| .message.doctor { |
| align-self: flex-start; |
| background: rgba(252, 202, 211, 0.1); |
| color: var(--text-color); |
| border-bottom-left-radius: 4px; |
| margin-right: 15%; |
| } |
| |
| .chat-input-container { |
| position: absolute; |
| bottom: 0; |
| left: 20px; |
| right: auto; |
| width: calc(100% - 200px); |
| display: flex; |
| padding: 12px; |
| background: rgba(26, 26, 46, 0.95); |
| border-top: 1px solid rgba(252, 202, 211, 0.1); |
| backdrop-filter: blur(10px); |
| z-index: 1; |
| border-radius: 0 0 var(--border-radius) var(--border-radius); |
| } |
| |
| .chat-input { |
| flex: 1; |
| background: rgba(252, 202, 211, 0.1); |
| border: 1px solid rgba(252, 202, 211, 0.2); |
| border-radius: 20px; |
| color: var(--text-color); |
| padding: 12px 16px; |
| resize: none; |
| margin-right: 10px; |
| min-height: 45px; |
| transition: all 0.3s ease; |
| font-size: 1rem; |
| max-width: calc(100% - 60px); |
| } |
| |
| .chat-input:focus { |
| background: rgba(252, 202, 211, 0.15); |
| border-color: var(--primary-color); |
| outline: none; |
| box-shadow: 0 0 0 2px rgba(240, 62, 95, 0.2); |
| } |
| |
| #sendMessage { |
| align-self: flex-end; |
| padding: 12px 20px; |
| min-width: 45px; |
| font-weight: 500; |
| letter-spacing: 0.5px; |
| border-radius: 20px; |
| flex-shrink: 0; |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| } |
| |
| #sendMessage:hover { |
| background: linear-gradient(135deg, var(--accent-color), var(--primary-color)); |
| } |
| |
| .loading { |
| position: absolute; |
| bottom: 70px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: rgba(26, 26, 46, 0.9); |
| padding: 10px 20px; |
| border-radius: 20px; |
| display: none; |
| align-items: center; |
| gap: 10px; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); |
| z-index: 1; |
| border: 1px solid rgba(252, 202, 211, 0.1); |
| } |
| |
| .loading i { |
| color: var(--primary-color); |
| font-size: 1.2rem; |
| animation: spin 1s linear infinite; |
| } |
| |
| .loading p { |
| margin: 0; |
| font-size: 0.9rem; |
| color: var(--text-light); |
| } |
| |
| [data-theme="light"] select.form-control { |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23333333' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); |
| } |
| |
| [data-theme="light"] select.form-control option { |
| background-color: rgba(255, 255, 255, 0.95); |
| color: var(--text-color); |
| } |
| |
| [data-theme="light"] .chat-container { |
| background: rgba(252, 202, 211, 0.1); |
| border: 1px solid rgba(138, 0, 24, 0.1); |
| } |
| |
| [data-theme="light"] .chat-messages { |
| background: linear-gradient(135deg, |
| rgba(252, 202, 211, 0.1) 0%, |
| rgba(245, 118, 141, 0.1) 50%, |
| rgba(240, 62, 95, 0.05) 100% |
| ); |
| } |
| |
| [data-theme="light"] .chat-input-container { |
| background: rgba(255, 255, 255, 0.95); |
| border-top: 1px solid rgba(138, 0, 24, 0.1); |
| } |
| |
| [data-theme="light"] .chat-input { |
| background: rgba(255, 255, 255, 0.95); |
| border: 2px solid var(--accent-color); |
| color: var(--text-color); |
| } |
| |
| [data-theme="light"] .chat-input:focus { |
| background: rgba(255, 255, 255, 0.95); |
| border-color: var(--accent-color); |
| box-shadow: 0 0 0 2px rgba(209, 6, 42, 0.2); |
| } |
| |
| [data-theme="light"] .chat-input::placeholder { |
| color: rgba(138, 0, 24, 0.6); |
| } |
| |
| [data-theme="light"] .loading { |
| background: rgba(255, 255, 255, 0.95); |
| border: 1px solid rgba(138, 0, 24, 0.1); |
| } |
| |
| [data-theme="light"] .message.doctor { |
| background: rgba(252, 202, 211, 0.2); |
| } |
| |
| [data-theme="light"] #ai-doctor .header { |
| background: rgba(252, 202, 211, 0.2); |
| border: 1px solid rgba(138, 0, 24, 0.1); |
| border-bottom: none; |
| } |
| |
| [data-theme="light"] #ai-doctor .header h3 { |
| color: var(--accent-color); |
| } |
| |
| [data-theme="light"] #ai-doctor .header p { |
| color: var(--text-light); |
| } |
| |
| [data-theme="light"] #ai-doctor .header i { |
| color: var(--accent-color); |
| } |
| |
| .header-content { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| width: 100%; |
| padding: 0 20px; |
| } |
| |
| .btn-logout { |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| border: none; |
| color: white; |
| padding: 8px 16px; |
| border-radius: var(--border-radius); |
| font-weight: 500; |
| cursor: pointer; |
| transition: var(--transition); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .btn-logout:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 12px rgba(240, 62, 95, 0.3); |
| } |
| |
| .btn-logout i { |
| font-size: 1.1em; |
| } |
| |
| .analysis-container { |
| padding: 20px; |
| } |
| |
| .analysis-header { |
| text-align: center; |
| margin-bottom: 30px; |
| } |
| |
| .analysis-header h3 { |
| color: var(--primary-color); |
| margin-bottom: 10px; |
| } |
| |
| .analysis-inputs { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| gap: 20px; |
| margin-bottom: 30px; |
| } |
| |
| .input-section { |
| background: rgba(255, 255, 255, 0.05); |
| padding: 15px; |
| border-radius: var(--border-radius); |
| } |
| |
| .input-section h4 { |
| color: var(--text-light); |
| margin-bottom: 10px; |
| } |
| |
| .result-box { |
| background: rgba(0, 0, 0, 0.2); |
| padding: 10px; |
| border-radius: var(--border-radius); |
| min-height: 100px; |
| max-height: 200px; |
| overflow-y: auto; |
| } |
| |
| #generateAnalysis { |
| width: 100%; |
| padding: 15px; |
| margin: 20px 0; |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); |
| border: none; |
| border-radius: var(--border-radius); |
| color: white; |
| font-size: 1.1rem; |
| cursor: pointer; |
| transition: var(--transition); |
| } |
| |
| #generateAnalysis:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 8px rgba(255, 77, 109, 0.3); |
| } |
| |
| .analysis-result { |
| background: rgba(255, 255, 255, 0.05); |
| padding: 20px; |
| border-radius: var(--border-radius); |
| margin-top: 20px; |
| display: none; |
| } |
| |
| .analysis-result.show { |
| display: block; |
| } |
| </style> |
| </head> |
| <body> |
| <button class="theme-toggle" onclick="toggleTheme()"> |
| <i class="fas fa-moon"></i> |
| <span>Dark Mode</span> |
| </button> |
|
|
| <div class="container"> |
| <div class="header"> |
| <div class="header-content"> |
| <h1>Heart Health Analysis System</h1> |
| <button onclick="logout()" class="btn-logout"> |
| <i class="fas fa-sign-out-alt"></i> Logout |
| </button> |
| </div> |
| </div> |
| |
| <ul class="nav nav-tabs" id="analysisTabs" role="tablist"> |
| <li class="nav-item" role="presentation"> |
| <button class="nav-link active" id="heart-risk-tab" data-bs-toggle="tab" data-bs-target="#heart-risk" type="button" role="tab"> |
| <i class="fas fa-heart"></i> Heart Disease Risk |
| </button> |
| </li> |
| <li class="nav-item" role="presentation"> |
| <button class="nav-link" id="ecg-tab" data-bs-toggle="tab" data-bs-target="#ecg" type="button" role="tab"> |
| <i class="fas fa-heartbeat"></i> ECG Analysis |
| </button> |
| </li> |
| <li class="nav-item" role="presentation"> |
| <button class="nav-link" id="heart-sound-tab" data-bs-toggle="tab" data-bs-target="#heart-sound" type="button" role="tab"> |
| <i class="fas fa-stethoscope"></i> Heart Sound Analysis |
| </button> |
| </li> |
| <li class="nav-item" role="presentation"> |
| <button class="nav-link" id="ai-doctor-tab" data-bs-toggle="tab" data-bs-target="#ai-doctor" type="button" role="tab"> |
| <i class="fas fa-robot"></i> AI Doctor |
| </button> |
| </li> |
| </ul> |
|
|
| <div class="tab-content" id="analysisTabContent"> |
| |
| <div class="tab-pane fade show active" id="heart-risk" role="tabpanel"> |
| <div class="analysis-section"> |
| <h3><i class="fas fa-chart-line"></i> Heart Disease Risk Analysis</h3> |
| <form id="heartRiskForm"> |
| <div class="row"> |
| <div class="col-md-6 mb-3"> |
| <label for="age" class="form-label">Age of Patient</label> |
| <input type="number" class="form-control" id="age" min="25" max="75" placeholder="Enter age (25-75)" required> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="sex" class="form-label">Gender</label> |
| <select class="form-control" id="sex" required> |
| <option value="1">Male</option> |
| <option value="0">Female</option> |
| </select> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="cp" class="form-label">Category of Chest Pain</label> |
| <select class="form-control" id="cp" required> |
| <option value="1">Atypical Angina</option> |
| <option value="0">Typical Angina</option> |
| <option value="3">Asymptomatic</option> |
| <option value="2">Non-Angina</option> |
| </select> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="trestbps" class="form-label">Resting Blood Pressure</label> |
| <input type="number" class="form-control" id="trestbps" min="94" max="200" placeholder="Enter blood pressure (94-200 mmHg)" required> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="chol" class="form-label">Cholesterol</label> |
| <input type="number" class="form-control" id="chol" min="126" max="564" placeholder="Enter cholesterol level (126-564 mg/dL)" required> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="fbs" class="form-label">Fasting Blood Sugar</label> |
| <select class="form-control" id="fbs" required> |
| <option value="0">Less than 120 mg/dL (Normal)</option> |
| <option value="1">120 mg/dL or more (High)</option> |
| </select> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="restecg" class="form-label">Resting ECG Result</label> |
| <select class="form-control" id="restecg" required> |
| <option value="0">Normal</option> |
| <option value="1">ST-T Wave Abnormalities</option> |
| <option value="2">Left Ventricular Hypertrophy</option> |
| </select> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="thalach" class="form-label">Maximum Heart Rate</label> |
| <input type="number" class="form-control" id="thalach" min="71" max="202" placeholder="Enter heart rate (71-202 bpm)" required> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="exang" class="form-label">Exercise Induced Angina</label> |
| <select class="form-control" id="exang" required> |
| <option value="1">Yes</option> |
| <option value="0">No</option> |
| </select> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="oldpeak" class="form-label">ST Depression Induced by Exercise</label> |
| <input type="number" step="0.1" class="form-control" id="oldpeak" min="0" max="6.2" placeholder="Enter ST Depression value (0-6.2)" required> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="slope" class="form-label">Slope of Peak Exercise ST Segment</label> |
| <select class="form-control" id="slope" required> |
| <option value="0">Upsloping</option> |
| <option value="1">Flat</option> |
| <option value="2">Downsloping</option> |
| </select> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="ca" class="form-label">Number of Major Vessels Colored by Fluoroscopy</label> |
| <input type="number" class="form-control" id="ca" min="0" max="3" placeholder="Enter number of vessels (0-3)" required> |
| </div> |
| <div class="col-md-6 mb-3"> |
| <label for="thal" class="form-label">Thalassemia</label> |
| <select class="form-control" id="thal" required> |
| <option value="0">Absent / Normal</option> |
| <option value="1">Mild Thalassemia</option> |
| <option value="2">Moderate Thalassemia</option> |
| <option value="3">Severe Thalassemia</option> |
| </select> |
| </div> |
| </div> |
| <button type="submit" class="btn btn-primary"> |
| <i class="fas fa-chart-pie"></i> Analyze Heart Disease Risk |
| </button> |
| </form> |
| <div class="loading" id="heartLoading"> |
| <i class="fas fa-spinner"></i> |
| <p>Analyzing heart disease risk...</p> |
| </div> |
| <div id="heartRiskResult" class="result-box"> |
| <h4><i class="fas fa-clipboard-list"></i> Analysis Results</h4> |
| <div class="progress mb-3"> |
| <div id="riskProgress" class="progress-bar" role="progressbar"></div> |
| </div> |
| <p id="riskPrediction"></p> |
| <p id="riskIndicators"></p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="tab-pane fade" id="ecg" role="tabpanel"> |
| <div class="analysis-section"> |
| <h3><i class="fas fa-heartbeat"></i> ECG Analysis</h3> |
| <form id="ecgForm"> |
| <div class="mb-3"> |
| <label for="ecgFile" class="form-label">Upload ECG Data (JSON file)</label> |
| <input type="file" class="form-control" id="ecgFile" accept=".json" required> |
| <div class="input-guidelines">Upload a JSON file containing 141 ECG values in the format: {"ecg_values": [value1, value2, ...]}</div> |
| </div> |
| <button type="submit" class="btn btn-primary"> |
| <i class="fas fa-chart-line"></i> Analyze ECG |
| </button> |
| </form> |
| <div class="loading" id="ecgLoading"> |
| <i class="fas fa-spinner"></i> |
| <p>Analyzing ECG data...</p> |
| </div> |
| <div id="ecgResult" class="result-box"> |
| <h4><i class="fas fa-clipboard-list"></i> ECG Analysis Results</h4> |
| <p id="ecgPrediction"></p> |
| </div> |
| <div id="ecgPlotContainer" style="width: 100%; margin-top: 20px;"> |
| <img id="ecgPlot" class="ecg-plot" src="" alt="ECG Plot"> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="tab-pane fade" id="heart-sound" role="tabpanel"> |
| <div class="analysis-section"> |
| <h3><i class="fas fa-stethoscope"></i> Heart Sound Analysis</h3> |
| <form id="heartSoundForm"> |
| <div class="mb-3"> |
| <label for="audioFile" class="form-label">Upload Heart Sound (WAV format)</label> |
| <input type="file" class="form-control" id="audioFile" accept=".wav" required> |
| <div class="input-guidelines">Upload a WAV file containing heart sound recording</div> |
| </div> |
| <button type="submit" class="btn btn-primary"> |
| <i class="fas fa-wave-square"></i> Analyze Heart Sound |
| </button> |
| </form> |
| <div class="loading" id="soundLoading"> |
| <i class="fas fa-spinner"></i> |
| <p>Analyzing heart sound...</p> |
| </div> |
| <div id="heartSoundResult" class="result-box"> |
| <h4><i class="fas fa-clipboard-list"></i> Heart Sound Analysis Results</h4> |
| <p id="soundPrediction"></p> |
| <p id="soundConfidence"></p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="tab-pane fade" id="ai-doctor" role="tabpanel"> |
| <div class="analysis-section"> |
| <div class="header"> |
| <i class="fas fa-robot"></i> |
| <div> |
| <h3>AI Doctor</h3> |
| <p>Your personal cardiologist assistant</p> |
| </div> |
| </div> |
| |
| <div class="chat-container"> |
| <div class="chat-messages" id="chatMessages"> |
| <div class="message doctor"> |
| <div class="message-content"> |
| <p>Hello! I'm your AI cardiologist. How can I help you with your heart health today?</p> |
| </div> |
| <div class="message-time">Just now</div> |
| </div> |
| </div> |
| |
| <div class="chat-input-container"> |
| <textarea id="userMessage" class="chat-input" placeholder="Type your message..." rows="1"></textarea> |
| <button id="sendMessage" class="btn btn-primary"> |
| <i class="fas fa-paper-plane"></i> |
| </button> |
| </div> |
| </div> |
| |
| <div class="loading" id="aiDoctorLoading"> |
| <i class="fas fa-spinner"></i> |
| <p>AI Doctor is typing...</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="tab-pane" id="ai-analysis"> |
| <div class="analysis-container"> |
| <div class="analysis-header"> |
| <h3>AI Analysis Report</h3> |
| <p>Comprehensive analysis of your heart health based on all test results</p> |
| </div> |
| <div class="analysis-content"> |
| <div class="analysis-inputs"> |
| <div class="input-section"> |
| <h4>Heart Disease Risk Results</h4> |
| <div id="heartDiseaseResults" class="result-box"></div> |
| </div> |
| <div class="input-section"> |
| <h4>ECG Analysis Results</h4> |
| <div id="ecgResults" class="result-box"></div> |
| </div> |
| <div class="input-section"> |
| <h4>Heart Sound Analysis Results</h4> |
| <div id="heartSoundResults" class="result-box"></div> |
| </div> |
| </div> |
| <button id="generateAnalysis" class="btn btn-primary"> |
| <i class="fas fa-robot"></i> Generate Analysis Report |
| </button> |
| <div id="aiAnalysisResult" class="analysis-result"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <a href="/emergency" class="emergency-button"> |
| <i class="fas fa-exclamation-triangle"></i> EMERGENCY |
| </a> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> |
| <script src="https://www.gstatic.com/firebasejs/9.6.0/firebase-app-compat.js"></script> |
| <script src="https://www.gstatic.com/firebasejs/9.6.0/firebase-auth-compat.js"></script> |
| <script> |
| |
| const firebaseConfig = {{ firebase_config|tojson|safe }}; |
| |
| |
| firebase.initializeApp(firebaseConfig); |
| |
| |
| firebase.auth().onAuthStateChanged((user) => { |
| if (!user) { |
| window.location.href = '/login'; |
| } |
| }); |
| |
| |
| function logout() { |
| |
| firebase.auth().signOut() |
| .then(() => { |
| |
| return fetch('/logout', { |
| method: 'GET', |
| headers: { |
| 'Accept': 'application/json', |
| 'Content-Type': 'application/json' |
| } |
| }); |
| }) |
| .then(response => { |
| |
| localStorage.clear(); |
| sessionStorage.clear(); |
| |
| |
| window.location.href = '/login'; |
| }) |
| .catch(error => { |
| console.error('Logout error:', error); |
| |
| window.location.href = '/login'; |
| }); |
| } |
| |
| |
| document.querySelectorAll('form').forEach(form => { |
| form.addEventListener('submit', function() { |
| const loadingId = this.id.replace('Form', 'Loading'); |
| const resultBox = document.getElementById(this.id.replace('Form', 'Result')); |
| document.getElementById(loadingId).style.display = 'block'; |
| resultBox.style.display = 'none'; |
| |
| |
| if (this.id === 'ecgForm') { |
| document.getElementById('ecgPlot').style.display = 'none'; |
| } |
| }); |
| }); |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| const sendButton = document.getElementById('sendMessage'); |
| const userMessageInput = document.getElementById('userMessage'); |
| const chatMessages = document.getElementById('chatMessages'); |
| const aiDoctorLoading = document.getElementById('aiDoctorLoading'); |
| |
| |
| sendButton.addEventListener('click', sendMessage); |
| |
| |
| userMessageInput.addEventListener('keydown', function(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendMessage(); |
| } |
| }); |
| |
| function sendMessage() { |
| const message = userMessageInput.value.trim(); |
| if (!message) return; |
| |
| |
| addMessage(message, 'user'); |
| |
| |
| userMessageInput.value = ''; |
| |
| |
| aiDoctorLoading.style.display = 'block'; |
| |
| |
| fetch('/ai_doctor', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ query: message }) |
| }) |
| .then(response => response.json()) |
| .then(data => { |
| |
| aiDoctorLoading.style.display = 'none'; |
| |
| |
| if (data.response) { |
| addMessage(data.response, 'doctor'); |
| } else { |
| addMessage('I apologize, but I encountered an error processing your request. Please try again.', 'doctor'); |
| } |
| }) |
| .catch(error => { |
| console.error('Error:', error); |
| aiDoctorLoading.style.display = 'none'; |
| addMessage('I apologize, but I encountered an error processing your request. Please try again.', 'doctor'); |
| }); |
| } |
| |
| function addMessage(text, sender) { |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message ${sender}`; |
| |
| const contentDiv = document.createElement('div'); |
| contentDiv.className = 'message-content'; |
| |
| const paragraph = document.createElement('p'); |
| paragraph.textContent = text; |
| contentDiv.appendChild(paragraph); |
| |
| const timeDiv = document.createElement('div'); |
| timeDiv.className = 'message-time'; |
| timeDiv.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); |
| |
| messageDiv.appendChild(contentDiv); |
| messageDiv.appendChild(timeDiv); |
| |
| chatMessages.appendChild(messageDiv); |
| |
| |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| } |
| }); |
| |
| |
| document.getElementById('heartRiskForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const loading = document.getElementById('heartLoading'); |
| const resultBox = document.getElementById('heartRiskResult'); |
| |
| try { |
| loading.style.display = 'block'; |
| resultBox.style.display = 'none'; |
| |
| const data = { |
| age: document.getElementById('age').value, |
| sex: document.getElementById('sex').value, |
| cp: document.getElementById('cp').value, |
| trestbps: document.getElementById('trestbps').value, |
| chol: document.getElementById('chol').value, |
| fbs: document.getElementById('fbs').value, |
| restecg: document.getElementById('restecg').value, |
| thalach: document.getElementById('thalach').value, |
| exang: document.getElementById('exang').value, |
| oldpeak: document.getElementById('oldpeak').value, |
| slope: document.getElementById('slope').value, |
| ca: document.getElementById('ca').value, |
| thal: document.getElementById('thal').value |
| }; |
| |
| const response = await fetch('/analyze_heart', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify(data) |
| }); |
| |
| const result = await response.json(); |
| |
| loading.style.display = 'none'; |
| resultBox.style.display = 'block'; |
| |
| const probability = result.probability * 100; |
| const progressBar = document.getElementById('riskProgress'); |
| progressBar.style.width = `${probability}%`; |
| progressBar.textContent = `${probability.toFixed(1)}%`; |
| progressBar.className = `progress-bar ${probability > 50 ? 'bg-danger' : 'bg-success'}`; |
| |
| document.getElementById('riskPrediction').textContent = |
| `Prediction: ${result.prediction ? 'High Risk' : 'Low Risk'}`; |
| document.getElementById('riskIndicators').textContent = |
| `High Risk Indicators: ${result.high_risk_indicators}, Low Risk Indicators: ${result.low_risk_indicators}`; |
| |
| |
| const heartDiseaseResultsBox = document.getElementById('heartDiseaseResults'); |
| if (heartDiseaseResultsBox) { |
| heartDiseaseResultsBox.innerHTML = ` |
| <div class="result-content"> |
| <p><strong>Risk Level:</strong> ${result.prediction ? 'High Risk' : 'Low Risk'}</p> |
| <p><strong>Probability:</strong> ${probability.toFixed(1)}%</p> |
| <p><strong>High Risk Indicators:</strong> ${result.high_risk_indicators}</p> |
| <p><strong>Low Risk Indicators:</strong> ${result.low_risk_indicators}</p> |
| </div> |
| `; |
| } |
| } catch (error) { |
| console.error('Error:', error); |
| loading.style.display = 'none'; |
| resultBox.style.display = 'block'; |
| resultBox.innerHTML = `<div class="alert alert-danger">An error occurred during analysis</div>`; |
| } |
| }); |
| |
| |
| document.getElementById('ecgForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const loading = document.getElementById('ecgLoading'); |
| const resultBox = document.getElementById('ecgResult'); |
| const file = document.getElementById('ecgFile').files[0]; |
| |
| if (!file) { |
| alert('Please select a JSON file'); |
| return; |
| } |
| |
| try { |
| loading.style.display = 'block'; |
| resultBox.style.display = 'none'; |
| |
| const text = await file.text(); |
| const data = JSON.parse(text); |
| |
| const response = await fetch('/analyze_ecg', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify(data) |
| }); |
| |
| const result = await response.json(); |
| |
| loading.style.display = 'none'; |
| resultBox.style.display = 'block'; |
| |
| |
| resultBox.className = `result-box ${result.is_anomaly ? 'anomaly' : 'normal'}`; |
| |
| document.getElementById('ecgPrediction').textContent = |
| `ECG Analysis: ${result.is_anomaly ? 'Anomaly Detected' : 'Normal ECG'}`; |
| |
| if (result.status === 'success' && result.plot_url) { |
| const plotImage = document.getElementById('ecgPlot'); |
| plotImage.style.display = 'block'; |
| plotImage.src = `data:image/png;base64,${result.plot_url}`; |
| } else { |
| document.getElementById('ecgPlot').style.display = 'none'; |
| console.error('No plot URL received from server'); |
| } |
| |
| |
| const ecgResultsBox = document.getElementById('ecgResults'); |
| if (ecgResultsBox) { |
| ecgResultsBox.innerHTML = ` |
| <div class="result-content"> |
| <p><strong>ECG Analysis:</strong> ${result.is_anomaly ? 'Anomaly Detected' : 'Normal ECG'}</p> |
| <p><strong>Status:</strong> ${result.status}</p> |
| ${result.plot_url ? `<img src="data:image/png;base64,${result.plot_url}" alt="ECG Plot" style="max-width: 100%; margin-top: 10px;">` : ''} |
| </div> |
| `; |
| } |
| } catch (error) { |
| console.error('Error:', error); |
| loading.style.display = 'none'; |
| resultBox.style.display = 'block'; |
| resultBox.innerHTML = `<div class="alert alert-danger">An error occurred during analysis. Please ensure the JSON file is properly formatted.</div>`; |
| } |
| }); |
| |
| |
| document.getElementById('heartSoundForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const loading = document.getElementById('soundLoading'); |
| const resultBox = document.getElementById('heartSoundResult'); |
| const audioFile = document.getElementById('audioFile').files[0]; |
| |
| if (!audioFile) { |
| alert('Please select an audio file'); |
| return; |
| } |
| |
| try { |
| loading.style.display = 'block'; |
| resultBox.style.display = 'none'; |
| |
| const formData = new FormData(); |
| formData.append('audio', audioFile); |
| |
| const response = await fetch('/analyze_audio', { |
| method: 'POST', |
| body: formData |
| }); |
| |
| const result = await response.json(); |
| |
| loading.style.display = 'none'; |
| resultBox.style.display = 'block'; |
| |
| document.getElementById('soundPrediction').textContent = |
| `Detected Condition: ${result.disease}`; |
| document.getElementById('soundConfidence').textContent = |
| `Confidence: ${result.confidence}%`; |
| |
| |
| const heartSoundResultsBox = document.getElementById('heartSoundResults'); |
| if (heartSoundResultsBox) { |
| heartSoundResultsBox.innerHTML = ` |
| <div class="result-content"> |
| <p><strong>Detected Condition:</strong> ${result.disease}</p> |
| <p><strong>Confidence Level:</strong> ${result.confidence}%</p> |
| <p><strong>Analysis Status:</strong> ${result.status || 'Completed'}</p> |
| </div> |
| `; |
| } |
| } catch (error) { |
| console.error('Error:', error); |
| loading.style.display = 'none'; |
| resultBox.style.display = 'block'; |
| resultBox.innerHTML = `<div class="alert alert-danger">An error occurred during analysis</div>`; |
| } |
| }); |
| |
| |
| function toggleTheme() { |
| const html = document.documentElement; |
| const themeToggle = document.querySelector('.theme-toggle'); |
| const currentTheme = html.getAttribute('data-theme'); |
| const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; |
| |
| html.setAttribute('data-theme', newTheme); |
| localStorage.setItem('theme', newTheme); |
| |
| |
| const icon = themeToggle.querySelector('i'); |
| const text = themeToggle.querySelector('span'); |
| if (newTheme === 'dark') { |
| icon.className = 'fas fa-moon'; |
| text.textContent = 'Dark Mode'; |
| } else { |
| icon.className = 'fas fa-sun'; |
| text.textContent = 'Light Mode'; |
| } |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| const savedTheme = localStorage.getItem('theme') || 'dark'; |
| document.documentElement.setAttribute('data-theme', savedTheme); |
| |
| |
| const themeToggle = document.querySelector('.theme-toggle'); |
| const icon = themeToggle.querySelector('i'); |
| const text = themeToggle.querySelector('span'); |
| if (savedTheme === 'dark') { |
| icon.className = 'fas fa-moon'; |
| text.textContent = 'Dark Mode'; |
| } else { |
| icon.className = 'fas fa-sun'; |
| text.textContent = 'Light Mode'; |
| } |
| }); |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| const navTabs = document.querySelector('.nav-tabs'); |
| const aiAnalysisTab = document.createElement('li'); |
| aiAnalysisTab.className = 'nav-item'; |
| aiAnalysisTab.innerHTML = ` |
| <a class="nav-link" data-bs-toggle="tab" href="#ai-analysis"> |
| <i class="fas fa-robot"></i> AI Analysis |
| </a> |
| `; |
| navTabs.appendChild(aiAnalysisTab); |
| }); |
| |
| |
| function collectResults() { |
| |
| const heartParams = { |
| age: document.getElementById('age').value, |
| sex: document.getElementById('sex').value, |
| cp: document.getElementById('cp').value, |
| trestbps: document.getElementById('trestbps').value, |
| chol: document.getElementById('chol').value, |
| fbs: document.getElementById('fbs').value, |
| restecg: document.getElementById('restecg').value, |
| thalach: document.getElementById('thalach').value, |
| exang: document.getElementById('exang').value, |
| oldpeak: document.getElementById('oldpeak').value, |
| slope: document.getElementById('slope').value, |
| ca: document.getElementById('ca').value, |
| thal: document.getElementById('thal').value |
| }; |
| |
| const results = { |
| heartDisease: document.getElementById('heartDiseaseResults').innerHTML, |
| ecg: document.getElementById('ecgResults').innerHTML, |
| heartSound: document.getElementById('heartSoundResults').innerHTML, |
| heartParams: heartParams |
| }; |
| return results; |
| } |
| |
| |
| async function generateAnalysis() { |
| const results = collectResults(); |
| |
| |
| if (!results.heartDisease && !results.ecg && !results.heartSound) { |
| alert('Please complete at least one test to generate analysis'); |
| return; |
| } |
| |
| try { |
| const response = await fetch('/api/generate_analysis', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify(results) |
| }); |
| |
| const data = await response.json(); |
| if (data.status === 'success') { |
| const resultDiv = document.getElementById('aiAnalysisResult'); |
| |
| |
| const heartParamsSection = ` |
| <div class="report-section"> |
| <h4>Heart Disease Risk Parameters</h4> |
| <div class="parameters-grid"> |
| <div class="parameter-item"> |
| <span class="parameter-label">Age:</span> |
| <span class="parameter-value">${results.heartParams.age} years</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Sex:</span> |
| <span class="parameter-value">${results.heartParams.sex === '1' ? 'Male' : 'Female'}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Chest Pain Type:</span> |
| <span class="parameter-value">${getChestPainType(results.heartParams.cp)}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Resting Blood Pressure:</span> |
| <span class="parameter-value">${results.heartParams.trestbps} mmHg</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Serum Cholesterol:</span> |
| <span class="parameter-value">${results.heartParams.chol} mg/dl</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Fasting Blood Sugar:</span> |
| <span class="parameter-value">${results.heartParams.fbs === '1' ? '> 120 mg/dl' : '<= 120 mg/dl'}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Resting ECG Results:</span> |
| <span class="parameter-value">${getRestingECG(results.heartParams.restecg)}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Maximum Heart Rate:</span> |
| <span class="parameter-value">${results.heartParams.thalach} bpm</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Exercise Induced Angina:</span> |
| <span class="parameter-value">${results.heartParams.exang === '1' ? 'Yes' : 'No'}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">ST Depression:</span> |
| <span class="parameter-value">${results.heartParams.oldpeak} mm</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Slope of Peak Exercise:</span> |
| <span class="parameter-value">${getSlope(results.heartParams.slope)}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Number of Major Vessels:</span> |
| <span class="parameter-value">${results.heartParams.ca}</span> |
| </div> |
| <div class="parameter-item"> |
| <span class="parameter-label">Thalassemia:</span> |
| <span class="parameter-value">${getThalassemia(results.heartParams.thal)}</span> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| |
| const analysisHTML = data.analysis; |
| const headerEndIndex = analysisHTML.indexOf('</div>', analysisHTML.indexOf('<div class="report-header">')) + 6; |
| const finalHTML = analysisHTML.slice(0, headerEndIndex) + heartParamsSection + analysisHTML.slice(headerEndIndex); |
| |
| resultDiv.innerHTML = ` |
| <h4>AI Analysis Report</h4> |
| <div class="analysis-content"> |
| ${finalHTML} |
| </div> |
| `; |
| resultDiv.classList.add('show'); |
| } else { |
| throw new Error(data.message); |
| } |
| } catch (error) { |
| console.error('Error generating analysis:', error); |
| alert('Error generating analysis. Please try again.'); |
| } |
| } |
| |
| |
| function getChestPainType(value) { |
| const types = { |
| '1': 'Typical Angina', |
| '2': 'Atypical Angina', |
| '3': 'Non-anginal Pain', |
| '4': 'Asymptomatic' |
| }; |
| return types[value] || 'N/A'; |
| } |
| |
| function getRestingECG(value) { |
| const types = { |
| '0': 'Normal', |
| '1': 'ST-T Wave Abnormality', |
| '2': 'Left Ventricular Hypertrophy' |
| }; |
| return types[value] || 'N/A'; |
| } |
| |
| function getSlope(value) { |
| const types = { |
| '0': 'Upsloping', |
| '1': 'Flat', |
| '2': 'Downsloping' |
| }; |
| return types[value] || 'N/A'; |
| } |
| |
| function getThalassemia(value) { |
| const types = { |
| '0': 'Normal', |
| '1': 'Fixed Defect', |
| '2': 'Reversible Defect', |
| '3': 'Other' |
| }; |
| return types[value] || 'N/A'; |
| } |
| |
| |
| document.getElementById('generateAnalysis').addEventListener('click', generateAnalysis); |
| |
| |
| function updateResults(tab, content) { |
| const resultBox = document.getElementById(`${tab}Results`); |
| if (resultBox) { |
| resultBox.textContent = content; |
| } |
| } |
| </script> |
| </body> |
| </html> |