itsLu commited on
Commit
a5b01e6
·
verified ·
1 Parent(s): 82e0db2

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +185 -332
templates/index.html CHANGED
@@ -1,373 +1,226 @@
1
- <!doctype html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>NeuraScan | AI Brain MRI Analysis</title>
7
- <meta name="description" content="NeuraScan - AI-powered brain MRI Alzheimer's disease analysis tool" />
8
- <link rel="icon" type="image/svg+xml" href="/assets/brain/brain.svg" />
9
  <style>
10
- :root { color-scheme: dark; }
11
- body { margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial; background:#0b1020; color:#e6e8ee; }
12
- a { color: inherit; }
13
- .container { max-width: 1040px; margin: 0 auto; padding: 28px 18px 64px; }
14
- .header { display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom: 22px; }
15
- .brand { display:flex; align-items:center; gap:12px; }
16
- .brand img { width:42px; height:42px; }
17
- h1 { font-size: 22px; margin:0; letter-spacing: .3px; }
18
- .sub { margin: 4px 0 0; color:#aab0c0; font-size: 14px; }
19
 
20
- .grid { display:grid; gap:16px; grid-template-columns: 1.05fr .95fr; }
21
- @media (max-width: 920px) { .grid { grid-template-columns: 1fr; } }
 
 
 
 
 
 
22
 
23
- .card { background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.08); border-radius: 18px; padding: 18px; box-shadow: 0 10px 30px rgba(0,0,0,.25); }
24
- .card h2 { margin: 0 0 10px; font-size: 16px; }
25
- .muted { color:#aab0c0; font-size: 13px; line-height: 1.45; }
 
 
 
 
26
 
27
- .controls { display:flex; gap:10px; flex-wrap: wrap; align-items: center; margin: 10px 0 14px; }
28
- select, button { border-radius: 12px; border: 1px solid rgba(255,255,255,.14); background: rgba(255,255,255,.06); color: #e6e8ee; padding: 10px 12px; font-size: 14px; }
29
- button { cursor:pointer; }
30
- button.primary { background: rgba(59,130,246,.9); border-color: rgba(59,130,246,.9); }
31
- button.primary:disabled { opacity:.55; cursor:not-allowed; }
 
32
 
33
- .drop { border: 1px dashed rgba(255,255,255,.22); border-radius: 18px; padding: 18px; background: rgba(255,255,255,.03); }
34
- .drop.dragover { border-color: rgba(59,130,246,.9); box-shadow: 0 0 0 4px rgba(59,130,246,.18); }
35
- .drop .row { display:flex; align-items:center; justify-content:space-between; gap: 12px; flex-wrap: wrap; }
36
- .hintlist { margin: 12px 0 0; padding-left: 18px; }
37
- .hintlist li { margin: 6px 0; }
38
 
39
- .preview { margin-top: 14px; display:none; gap: 12px; align-items: center; }
40
- .preview img { width: 92px; height: 92px; object-fit: cover; border-radius: 14px; border: 1px solid rgba(255,255,255,.12); }
41
- .pill { display:inline-flex; align-items:center; gap: 8px; padding: 8px 10px; border-radius: 999px; background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.10); font-size: 13px; color:#d6daea; }
 
42
 
43
- .result { margin-top: 12px; display:none; }
44
- .result .big { font-size: 18px; margin: 4px 0 0; }
45
- .bar { height: 10px; border-radius: 999px; background: rgba(255,255,255,.08); overflow:hidden; border: 1px solid rgba(255,255,255,.08); }
46
- .bar > div { height: 100%; width: 0%; background: rgba(59,130,246,.9); }
47
- .warn { margin-top: 12px; padding: 12px; border-radius: 14px; background: rgba(245,158,11,.12); border: 1px solid rgba(245,158,11,.24); color:#ffe9c2; display:none; }
 
 
 
48
 
49
- .team { display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; margin-top: 10px; }
50
- .member { display:flex; gap: 10px; align-items:center; padding: 10px; border-radius: 14px; background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.07); }
51
- .member img { width: 42px; height: 42px; border-radius: 999px; object-fit: cover; border: 1px solid rgba(255,255,255,.10); }
52
- .member .name { font-weight: 600; font-size: 14px; }
53
- .member .role { color:#aab0c0; font-size: 12px; margin-top: 2px; }
 
54
 
55
- .examples { display:grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
56
- .ex { border-radius: 16px; overflow:hidden; border: 1px solid rgba(255,255,255,.10); background: rgba(255,255,255,.03); }
57
- .ex img { width: 100%; height: 140px; object-fit: cover; display:block; }
58
- .ex .cap { padding: 10px; font-size: 13px; color:#aab0c0; }
59
- footer { margin-top: 20px; color:#8f95a8; font-size: 12px; }
60
- code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 8px; }
61
-
62
- /* Modal */
63
- .modal { position: fixed; inset: 0; display:none; }
64
- .modal[aria-hidden="false"] { display:block; }
65
- .modal-backdrop { position:absolute; inset:0; background: rgba(0,0,0,.55); }
66
- .modal-card { position:relative; max-width: 520px; margin: 8vh auto; background: rgba(10,12,18,.95);
67
- border: 1px solid rgba(255,255,255,.12); border-radius: 18px; padding: 18px; box-shadow: 0 30px 60px rgba(0,0,0,.35); }
68
- .prob-row { display:flex; justify-content:space-between; gap:12px; padding:10px 12px; border-radius: 14px;
69
- border: 1px solid rgba(255,255,255,.08); background: rgba(255,255,255,.04); margin-top: 10px; }
70
- </style>
71
- </head>
72
 
73
- <body>
74
- <div class="container">
75
- <div class="header">
76
- <div class="brand">
77
- <img src="/assets/brain/brain.svg" alt="NeuraScan logo" />
78
- <div>
79
- <h1>NeuraScan</h1>
80
- <div class="sub">AI-assisted MRI screening for Alzheimer’s disease (demo)</div>
81
- </div>
82
- </div>
83
- <div class="pill" id="statusPill">Ready</div>
84
- </div>
85
 
86
- <div class="grid">
87
- <div class="card">
88
- <h2>New scan</h2>
89
- <div class="muted">
90
- Choose a model, then upload an MRI image. You can <b>drag & drop</b>, <b>paste</b> (Ctrl+V), or use the upload button.
91
- </div>
 
92
 
93
- <div class="controls">
94
- <label class="muted" for="modelSelect">Model</label>
95
- <select id="modelSelect" aria-label="Model selection"></select><input id="fileInput" type="file" accept="image/*" hidden />
96
- <button id="uploadBtn">Upload image</button>
97
- <button class="primary" id="scanBtn" disabled>Run scan</button>
98
- <button id="newBtn" style="display:none;">New scan</button>
99
- </div>
100
 
101
- <div id="drop" class="drop" tabindex="0">
102
- <div class="row">
103
- <div>
104
- <div style="font-weight:600;">Drop an image here</div>
105
- <div class="muted">Or paste from clipboard • PNG/JPG recommended</div>
106
- </div>
107
- <div class="pill"><span>Tip</span><span>Click box then Ctrl+V</span></div>
108
- </div>
109
 
110
- <ul class="hintlist muted">
111
- <li>Best results: clear axial brain MRI, centered, minimal blur.</li>
112
- <li>We don’t store uploads — they’re processed for inference only.</li>
113
- </ul>
 
114
 
115
- <div class="preview" id="preview">
116
- <img id="previewImg" alt="Preview" />
117
- <div>
118
- <div class="muted" id="fileMeta"></div>
119
- <div class="muted">Selected model: <span id="modelName"></span></div>
120
- </div>
121
- </div>
122
- </div>
123
 
124
- <div class="result" id="result">
125
- <div class="muted">Result</div>
126
- <div class="big" id="resultText">—</div>
127
- <div class="muted" id="msgText" style="margin-top:6px;"></div>
128
- <div style="margin-top:12px;">
129
- <div class="muted" style="margin-bottom:8px;">Confidence</div>
130
- <div class="bar"><div id="confBar"></div></div>
131
- <div class="muted" style="margin-top:6px;" id="confText">—</div>
132
- </div>
133
- <div class="warn" id="warnBox">
134
- Low confidence: consider re-uploading a clearer MRI or trying another model.
135
- </div>
136
- <div style="display:flex; gap:10px; margin-top:12px; flex-wrap:wrap;">
137
- <button id="detailsBtn" style="display:none;">Show most likely result</button>
138
- <button id="newBtn">Start a new scan</button>
139
- </div>
140
- </div>
141
 
142
- <footer>
143
- This is an educational demo and not medical advice.
144
- </footer>
145
- </div>
 
146
 
147
- <div class="card">
148
- <h2>Examples</h2>
149
- <div class="muted">Replace the placeholder example images in <code>static/assets/brain/</code>.</div>
150
- <div class="examples">
151
- <div class="ex">
152
- <img src="/assets/brain/supportedexample.jpg" onerror="this.style.display='none';" alt="Supported example"/>
153
- <div class="cap">Supported example</div>
154
- </div>
155
- <div class="ex">
156
- <img src="/assets/brain/unsupportedexample.jpg" onerror="this.style.display='none';" alt="Unsupported example"/>
157
- <div class="cap">Unsupported example</div>
158
- </div>
159
- </div>
160
 
161
- <h2 style="margin-top:18px;">Team</h2>
162
- <div class="team">
163
- <div class="member"><img src="/assets/images/team/asem.jpg" alt="Asem"/><div><div class="name">Asem</div><div class="role">ML / Deployment</div></div></div>
164
- <div class="member"><img src="/assets/images/team/sameh.jpg" alt="Sameh"/><div><div class="name">Sameh</div><div class="role">ML / Research</div></div></div>
165
- <div class="member"><img src="/assets/images/team/fatma.jpg" alt="Fatma"/><div><div class="name">Fatma</div><div class="role">Data / QA</div></div></div>
166
- <div class="member"><img src="/assets/images/team/gehad.jpg" alt="Gehad"/><div><div class="name">Gehad</div><div class="role">Frontend</div></div></div>
167
- </div>
168
- </div>
169
  </div>
170
  </div>
 
 
171
 
172
- <script>
173
- const statusPill = document.getElementById('statusPill');
174
- const modelSelect = document.getElementById('modelSelect');
175
- const detailsBtn = document.getElementById('detailsBtn'); const modelName = document.getElementById('modelName');
176
- const fileInput = document.getElementById('fileInput');
177
- const uploadBtn = document.getElementById('uploadBtn');
178
- const scanBtn = document.getElementById('scanBtn');
179
- const newBtn = document.getElementById('newBtn');
180
- const modal = document.getElementById('modal');
181
- const modalBackdrop = document.getElementById('modalBackdrop');
182
- const modalClose = document.getElementById('modalClose');
183
- const modalTitle = document.getElementById('modalTitle');
184
- const modalSubtitle = document.getElementById('modalSubtitle');
185
- const modalList = document.getElementById('modalList');
186
- const drop = document.getElementById('drop');
187
- const preview = document.getElementById('preview');
188
- const previewImg = document.getElementById('previewImg');
189
- const fileMeta = document.getElementById('fileMeta');
190
-
191
- const resultBox = document.getElementById('result');
192
- const resultText = document.getElementById('resultText');
193
- const msgText = document.getElementById('msgText');
194
- const confBar = document.getElementById('confBar');
195
- const confText = document.getElementById('confText');
196
- const warnBox = document.getElementById('warnBox');
197
-
198
- let selectedFile = null;
199
- let modelMeta = {};
200
- let lastResult = null;
201
- let models = [];
202
-
203
- function setStatus(text) { statusPill.textContent = text; }
204
 
205
- function clamp01(x){ return Math.max(0, Math.min(1, x)); }
206
- }
207
- }
208
 
209
- function openModal(result) {
210
- if (!result) return;
211
- const top = result.top || (result.probabilities || [])[0] || {};
212
- const label = top.label || '—';
213
- const prob = (top.prob ?? top.probability ?? 0);
214
- modalTitle.textContent = label;
215
- modalSubtitle.textContent = `Confidence: ${(prob*100).toFixed(1)}%`;
216
- modalList.innerHTML = '';
217
- modal.setAttribute('aria-hidden','false');
218
- }
219
- function closeModal() {
220
- modal.setAttribute('aria-hidden','true');
221
- }
222
 
 
223
 
224
- async function loadModels() {
225
- const res = await fetch('/api/models');
226
- const payload = await res.json();
227
- const list = payload.models || [];
228
- modelMeta = Object.fromEntries(list.map(m => [m.id, m]));
229
- modelSelect.innerHTML = list.map(m => `<option value="${m.id}">${m.name}</option>`).join('');
230
- if (payload.default_model_id && modelMeta[payload.default_model_id]) {
231
- modelSelect.value = payload.default_model_id;
232
- }
233
- modelName.textContent = modelSelect.options[modelSelect.selectedIndex]?.text || 'Model';
234
 
235
- }
 
 
 
 
 
236
 
237
- function resetUI() {
238
- selectedFile = null;
239
- fileInput.value = '';
240
- preview.style.display = 'none';
241
- resultBox.style.display = 'none';
242
- warnBox.style.display = 'none';
243
- scanBtn.disabled = true;
244
- scanBtn.style.display = '';
245
- detailsBtn.style.display = 'none';
246
- newBtn.style.display = '';
 
 
 
 
 
 
 
 
 
 
 
247
 
248
- setStatus('Ready');
249
- }
 
250
 
251
- function setFile(file) {
252
- selectedFile = file;
253
- if (!file) return;
254
- previewImg.src = URL.createObjectURL(file);
255
- preview.style.display = 'flex';
256
- fileMeta.textContent = `${file.name} • ${(file.size/1024/1024).toFixed(2)} MB`;
257
- modelName.textContent = modelSelect.options[modelSelect.selectedIndex].text;
258
- scanBtn.disabled = false;
 
 
 
 
 
259
  }
260
 
261
- uploadBtn.addEventListener('click', () => fileInput.click());
262
- fileInput.addEventListener('change', (e) => setFile(e.target.files?.[0]));
263
-
264
- modelSelect.addEventListener('change', () => {
265
- modelName.textContent = modelSelect.options[modelSelect.selectedIndex].text;
266
-
267
- });
268
-
269
- // Threshold controls
270
-
271
- // Drag & drop
272
- ['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, (e) => {
273
- e.preventDefault(); e.stopPropagation();
274
- drop.classList.add('dragover');
275
- }));
276
- ['dragleave','drop'].forEach(ev => drop.addEventListener(ev, (e) => {
277
- e.preventDefault(); e.stopPropagation();
278
- drop.classList.remove('dragover');
279
- }));
280
- drop.addEventListener('drop', (e) => {
281
- const f = e.dataTransfer.files?.[0];
282
- if (f) setFile(f);
283
- });
284
-
285
- // Paste
286
- window.addEventListener('paste', (e) => {
287
- const item = [...(e.clipboardData?.items || [])].find(i => i.type.startsWith('image/'));
288
- if (!item) return;
289
- const f = item.getAsFile();
290
- if (f) setFile(f);
291
- });
292
-
293
- async function runScan() {
294
- if (!selectedFile) return;
295
- setStatus('Scanning…');
296
- scanBtn.disabled = true;
297
- warnBox.style.display = 'none';
298
-
299
- const fd = new FormData();
300
- fd.append('file', selectedFile);
301
- fd.append('model_id', modelSelect.value);
302
-
303
- try {
304
- const res = await fetch('/api/classify', { method: 'POST', body: fd });
305
- const data = await res.json();
306
- if (!res.ok) throw new Error(data.error || 'Request failed');
307
-
308
- resultBox.style.display = 'block';
309
- lastResult = data;
310
- const pred = data.prediction || {};
311
- const model = data.model || {};
312
- const top = (data.probabilities || [])[0] || {};
313
- const isUncertain = (pred.id === 'Uncertain') || (pred.label === 'Uncertain');
314
-
315
- resultText.textContent = pred.label || '—';
316
- const pct = Math.round((pred.confidence || 0) * 100);
317
-
318
- if (isUncertain) {
319
- confBar.style.width = '0%';
320
- confText.textContent = '';
321
- } else {
322
- confBar.style.width = pct + '%';
323
- confText.textContent = `${pct}%`;
324
- }
325
-
326
- if (isUncertain) {
327
- warnBox.style.display = 'block';
328
- msgText.textContent = `${model.name || 'Model'} • Uncertain — consult a medical professional.`;
329
- detailsBtn.style.display = '';
330
- } else {
331
- warnBox.style.display = 'none';
332
- msgText.textContent = `${model.name || 'Model'} • Prediction generated successfully.`;
333
- detailsBtn.style.display = 'none';
334
- }
335
-
336
- setStatus('Done');
337
- scanBtn.style.display = 'none';
338
- newBtn.style.display = '';
339
- } catch (err) {
340
- setStatus('Error');
341
- alert(err.message);
342
- scanBtn.disabled = false;
343
- }
344
  }
345
 
346
- scanBtn.addEventListener('click', runScan);
347
- newBtn.addEventListener('click', resetUI);
348
- detailsBtn.addEventListener('click', () => openModal(lastResult));
349
- modalBackdrop.addEventListener('click', closeModal);
350
- modalClose.addEventListener('click', closeModal);
351
- document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
352
 
353
- loadModels().catch(() => setStatus('Ready (offline)'));
354
- resetUI();
355
  </script>
356
-
357
- <div id="modal" class="modal" aria-hidden="true">
358
- <div class="modal-backdrop" id="modalBackdrop"></div>
359
- <div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
360
- <div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
361
- <div>
362
- <div class="muted">Most likely result</div>
363
- <div class="big" id="modalTitle">—</div>
364
- </div>
365
- <button id="modalClose" aria-label="Close details">Close</button>
366
- </div>
367
- <div class="muted" style="margin-top:8px;" id="modalSubtitle">—</div>
368
- <div style="margin-top:14px;" id="modalList"></div>
369
- </div>
370
- </div>
371
-
372
  </body>
373
  </html>
 
1
+ <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <title>NeuraScan</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+
 
8
  <style>
9
+ :root {
10
+ --bg: #0b1220;
11
+ --card: #0f1b33;
12
+ --text: #e8eefc;
13
+ --muted: #a9b7d0;
14
+ --border: rgba(255,255,255,.08);
15
+ --primary: #4f7cff;
16
+ }
 
17
 
18
+ :root[data-theme="light"] {
19
+ --bg: #f6f7fb;
20
+ --card: #ffffff;
21
+ --text: #0c1222;
22
+ --muted: #44506a;
23
+ --border: rgba(0,0,0,.08);
24
+ --primary: #355dff;
25
+ }
26
 
27
+ * { box-sizing: border-box; }
28
+ body {
29
+ margin: 0;
30
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
31
+ background: var(--bg);
32
+ color: var(--text);
33
+ }
34
 
35
+ header {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ padding: 20px 28px;
40
+ }
41
 
42
+ header .brand {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 12px;
46
+ }
47
 
48
+ header img {
49
+ width: 40px;
50
+ height: 40px;
51
+ }
52
 
53
+ .chip {
54
+ border: 1px solid var(--border);
55
+ background: var(--card);
56
+ color: var(--text);
57
+ padding: 6px 12px;
58
+ border-radius: 999px;
59
+ cursor: pointer;
60
+ }
61
 
62
+ main {
63
+ display: grid;
64
+ grid-template-columns: 1.2fr 1fr;
65
+ gap: 24px;
66
+ padding: 0 28px 28px;
67
+ }
68
 
69
+ .card {
70
+ background: var(--card);
71
+ border: 1px solid var(--border);
72
+ border-radius: 16px;
73
+ padding: 20px;
74
+ }
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ h2 { margin-top: 0; }
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ .upload-box {
79
+ border: 2px dashed var(--border);
80
+ border-radius: 12px;
81
+ padding: 20px;
82
+ text-align: center;
83
+ margin-top: 12px;
84
+ }
85
 
86
+ select, button {
87
+ padding: 8px 12px;
88
+ border-radius: 8px;
89
+ border: 1px solid var(--border);
90
+ background: var(--card);
91
+ color: var(--text);
92
+ }
93
 
94
+ button.primary {
95
+ background: var(--primary);
96
+ border: none;
97
+ color: white;
98
+ cursor: pointer;
99
+ }
 
 
100
 
101
+ .team {
102
+ display: grid;
103
+ grid-template-columns: 1fr 1fr;
104
+ gap: 12px;
105
+ }
106
 
107
+ .member {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 10px;
111
+ }
 
 
 
112
 
113
+ .member img {
114
+ width: 40px;
115
+ height: 40px;
116
+ border-radius: 50%;
117
+ object-fit: cover;
118
+ }
 
 
 
 
 
 
 
 
 
 
 
119
 
120
+ .examples img {
121
+ width: 100%;
122
+ border-radius: 12px;
123
+ margin-top: 8px;
124
+ }
125
 
126
+ footer {
127
+ padding: 12px 28px;
128
+ font-size: 12px;
129
+ color: var(--muted);
130
+ }
131
+ </style>
132
+ </head>
 
 
 
 
 
 
133
 
134
+ <body>
135
+ <header>
136
+ <div class="brand">
137
+ <img src="{{ url_for('static', filename='assets/brain/brain.svg') }}" alt="NeuraScan">
138
+ <div>
139
+ <strong>NeuraScan</strong><br>
140
+ <small class="muted">AI-assisted MRI screening (demo)</small>
 
141
  </div>
142
  </div>
143
+ <button id="themeToggle" class="chip">Light</button>
144
+ </header>
145
 
146
+ <main>
147
+ <div class="card">
148
+ <h2>New scan</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ <label>Model</label><br>
151
+ <select id="modelSelect"></select><br><br>
 
152
 
153
+ <input type="file" id="fileInput" accept="image/*"><br><br>
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
+ <button class="primary">Run scan</button>
156
 
157
+ <div class="upload-box">
158
+ Drag & drop, paste (Ctrl+V), or upload an MRI image
159
+ </div>
160
+ </div>
 
 
 
 
 
 
161
 
162
+ <div class="card">
163
+ <h2>Examples</h2>
164
+ <div class="examples">
165
+ <img src="{{ url_for('static', filename='assets/brain/supportedexample.jpg') }}">
166
+ <img src="{{ url_for('static', filename='assets/brain/unsupportedexample.jpg') }}">
167
+ </div>
168
 
169
+ <h2 style="margin-top:20px;">Team</h2>
170
+ <div class="team">
171
+ <div class="member">
172
+ <img src="{{ url_for('static', filename='assets/images/team/asem.jpg') }}">
173
+ <div>Asem<br><small>ML / Deployment</small></div>
174
+ </div>
175
+ <div class="member">
176
+ <img src="{{ url_for('static', filename='assets/images/team/sameh.jpg') }}">
177
+ <div>Sameh<br><small>ML / Research</small></div>
178
+ </div>
179
+ <div class="member">
180
+ <img src="{{ url_for('static', filename='assets/images/team/fatma.jpg') }}">
181
+ <div>Fatma<br><small>Data / QA</small></div>
182
+ </div>
183
+ <div class="member">
184
+ <img src="{{ url_for('static', filename='assets/images/team/gehad.jpg') }}">
185
+ <div>Gehad<br><small>Frontend</small></div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </main>
190
 
191
+ <footer>
192
+ This is an educational demo and not medical advice.
193
+ </footer>
194
 
195
+ <script>
196
+ async function loadModels() {
197
+ const res = await fetch("/api/models");
198
+ const models = await res.json();
199
+ const select = document.getElementById("modelSelect");
200
+ select.innerHTML = "";
201
+ models.forEach(m => {
202
+ const opt = document.createElement("option");
203
+ opt.value = m.id;
204
+ opt.textContent = m.name;
205
+ if (!m.available) opt.disabled = true;
206
+ select.appendChild(opt);
207
+ });
208
  }
209
 
210
+ function setTheme(theme) {
211
+ document.documentElement.dataset.theme = theme;
212
+ localStorage.setItem("theme", theme);
213
+ document.getElementById("themeToggle").textContent =
214
+ theme === "dark" ? "Light" : "Dark";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
 
217
+ document.getElementById("themeToggle").onclick = () => {
218
+ const t = document.documentElement.dataset.theme === "dark" ? "light" : "dark";
219
+ setTheme(t);
220
+ };
 
 
221
 
222
+ setTheme(localStorage.getItem("theme") || "dark");
223
+ loadModels();
224
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  </body>
226
  </html>