danapantoja commited on
Commit
a804ce4
·
1 Parent(s): b801ecb

changes from todays meeting

Browse files
webapp/app/main.py CHANGED
@@ -107,23 +107,51 @@ if static_path.exists():
107
 
108
  # Mount PyShiny visualization apps
109
  if SHINY_AVAILABLE:
110
- # Use dynamic port from settings (7860 for HF Spaces/Docker, can be overridden via env)
111
- api_base_url = f"http://localhost:{settings.server_port}"
 
112
  logger.info(f"Configuring Shiny apps with API base URL: {api_base_url}")
 
113
 
114
  try:
115
- heatmap_shiny_app = create_heatmap_app(api_base_url=api_base_url)
116
- app.mount("/shiny/heatmap", heatmap_shiny_app, name="shiny_heatmap")
117
- logger.info("PyShiny heatmap app mounted at /shiny/heatmap")
 
 
 
 
 
 
 
 
 
118
  except Exception as e:
119
  logger.error(f"Failed to mount PyShiny heatmap app: {e}")
 
 
120
 
121
  try:
122
- silhouette_shiny_app = create_silhouette_app(api_base_url=api_base_url)
123
- app.mount("/shiny/silhouette", silhouette_shiny_app, name="shiny_silhouette")
124
- logger.info("PyShiny silhouette app mounted at /shiny/silhouette")
 
 
 
 
 
 
 
 
 
125
  except Exception as e:
126
  logger.error(f"Failed to mount PyShiny silhouette app: {e}")
 
 
 
 
 
 
127
 
128
  # Set up templates
129
  templates_path = Path(__file__).parent.parent / "templates"
 
107
 
108
  # Mount PyShiny visualization apps
109
  if SHINY_AVAILABLE:
110
+ # For server-side requests within the same process, use 127.0.0.1 with the configured port
111
+ # The PyShiny apps make server-side HTTP requests to fetch data
112
+ api_base_url = f"http://127.0.0.1:{settings.server_port}"
113
  logger.info(f"Configuring Shiny apps with API base URL: {api_base_url}")
114
+ logger.info(f"Server port from settings: {settings.server_port}")
115
 
116
  try:
117
+ heatmap_shiny_app = create_heatmap_app(api_base_url=api_base_url, fastapi_app=app)
118
+ logger.info(f"Heatmap app created: {type(heatmap_shiny_app)}")
119
+ # Try mounting the PyShiny App directly first (it's ASGI-compatible)
120
+ # If that doesn't work, fall back to starlette_app
121
+ try:
122
+ app.mount("/shiny/heatmap", heatmap_shiny_app, name="shiny_heatmap")
123
+ logger.info("PyShiny heatmap app mounted directly at /shiny/heatmap")
124
+ except Exception as mount_error:
125
+ logger.warning(f"Direct mount failed, trying starlette_app: {mount_error}")
126
+ starlette_app = heatmap_shiny_app.starlette_app
127
+ app.mount("/shiny/heatmap", starlette_app, name="shiny_heatmap")
128
+ logger.info("PyShiny heatmap app mounted via starlette_app at /shiny/heatmap")
129
  except Exception as e:
130
  logger.error(f"Failed to mount PyShiny heatmap app: {e}")
131
+ import traceback
132
+ traceback.print_exc()
133
 
134
  try:
135
+ silhouette_shiny_app = create_silhouette_app(api_base_url=api_base_url, fastapi_app=app)
136
+ logger.info(f"Silhouette app created: {type(silhouette_shiny_app)}")
137
+ # Try mounting the PyShiny App directly first (it's ASGI-compatible)
138
+ # If that doesn't work, fall back to starlette_app
139
+ try:
140
+ app.mount("/shiny/silhouette", silhouette_shiny_app, name="shiny_silhouette")
141
+ logger.info("PyShiny silhouette app mounted directly at /shiny/silhouette")
142
+ except Exception as mount_error:
143
+ logger.warning(f"Direct mount failed, trying starlette_app: {mount_error}")
144
+ starlette_app = silhouette_shiny_app.starlette_app
145
+ app.mount("/shiny/silhouette", starlette_app, name="shiny_silhouette")
146
+ logger.info("PyShiny silhouette app mounted via starlette_app at /shiny/silhouette")
147
  except Exception as e:
148
  logger.error(f"Failed to mount PyShiny silhouette app: {e}")
149
+ import traceback
150
+ traceback.print_exc()
151
+ else:
152
+ logger.warning("PyShiny is not available - visualization apps will not be mounted")
153
+
154
+ # ... rest of existing code ...
155
 
156
  # Set up templates
157
  templates_path = Path(__file__).parent.parent / "templates"
webapp/app/shiny_apps/heatmap_app.py CHANGED
@@ -76,8 +76,13 @@ def filter_to_icon_url(filter_name: str) -> str:
76
  return f"/static/filters/{filter_name}.png"
77
 
78
 
79
- def create_app(api_base_url: str = "http://localhost:8000"):
80
- """Create the PyShiny heatmap app."""
 
 
 
 
 
81
 
82
  app_ui = ui.page_fluid(
83
  ui.head_content(
@@ -137,24 +142,19 @@ def create_app(api_base_url: str = "http://localhost:8000"):
137
  window.addEventListener('message', function(event) {{
138
  if (event.data && event.data.type === 'setParams') {{
139
  console.log('[Heatmap] Received params via postMessage:', event.data);
140
- <<<<<<< HEAD
141
- setShinyParams(event.data.job_id, event.data.batch_index);
142
- }}
143
- =======
144
  // Wait for Shiny to be ready, then set input values
145
- if (typeof Shiny !== 'undefined' && Shiny.setInputValue) {
146
  Shiny.setInputValue('pm_job_id', event.data.job_id);
147
  Shiny.setInputValue('pm_batch_index', event.data.batch_index);
148
- } else {
149
 
150
  // Retry after Shiny loads
151
- document.addEventListener('shiny:connected', function() {
152
  Shiny.setInputValue('pm_job_id', event.data.job_id);
153
  Shiny.setInputValue('pm_batch_index', event.data.batch_index);
154
- });
155
- }
156
- }
157
- >>>>>>> feat/remove-checkboxes
158
  // Handle download request from parent
159
  if (event.data && event.data.type === 'downloadRequest') {{
160
  console.log('[Heatmap] Download requested');
@@ -204,7 +204,7 @@ def create_app(api_base_url: str = "http://localhost:8000"):
204
  setTimeout(retrySetParams, 1000);
205
  }});
206
  """),
207
- ui.tags.style("""
208
  body {
209
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
210
  background: #f9fafb;
@@ -236,7 +236,7 @@ def create_app(api_base_url: str = "http://localhost:8000"):
236
  }
237
  """)
238
  ),
239
- ui.row(
240
  ui.column(
241
  12,
242
  ui.div(
@@ -274,29 +274,42 @@ def create_app(api_base_url: str = "http://localhost:8000"):
274
  print(f"[Heatmap Server] params_received set to True", flush=True)
275
 
276
  try:
277
- url = f"{api_base_url}/api/vis_data/{job_id}"
278
-
279
- if batch_index is not None:
280
- url += f"?batch_index={batch_index}"
281
-
282
- print(f"[Heatmap Server] Making API request to: {url}", flush=True)
283
-
284
- async with httpx.AsyncClient(timeout=60.0) as client:
285
- print(f"[Heatmap] GET {url}", flush=True)
286
- response = await client.get(url)
287
  print(f"[Heatmap Server] API response status: {response.status_code}", flush=True)
288
- response.raise_for_status()
 
289
  data = response.json()
290
- print(f"[Heatmap Server] API response data keys: {list(data.keys()) if data else 'None'}", flush=True)
291
- vis_data.set(data)
292
- error_message.set(None) # Clear any error
293
- print("[Heatmap Server] vis_data set successfully", flush=True)
294
- except httpx.HTTPError as e:
295
- error_msg = f"HTTP Error fetching data: {str(e)}"
296
- print(f"[Heatmap Server] {error_msg}", flush=True)
297
- error_message.set(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
298
  except Exception as e:
299
- error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}"
300
  print(f"[Heatmap Server] {error_msg}", flush=True)
301
  import traceback
302
  traceback.print_exc()
@@ -375,7 +388,7 @@ def create_app(api_base_url: str = "http://localhost:8000"):
375
  )
376
 
377
  # Highlight exon region
378
- fig.add_vrect(x0=start-0.5, x1=end-0.5, fillcolor="#d0d0d0", line_width=0, opacity=0.1)
379
 
380
  # Add filter icons to the left of y-axis labels
381
  fig.update_layout(images=[])
@@ -427,17 +440,17 @@ def create_app(api_base_url: str = "http://localhost:8000"):
427
  "toImageButtonOptions": {
428
  "format": "svg",
429
  "filename": "heatmap_view",
430
- <<<<<<< HEAD
431
- "height": 700,
432
- "width": 1200,
433
- =======
434
  "height": 500,
435
  "width": 1100,
436
  "scale": 2
437
- >>>>>>> feat/remove-checkboxes
438
  },
439
  "displayModeBar": True,
440
- "modeBarButtonsToAdd": ["toImage"],
 
 
 
 
 
441
  },
442
  )
443
 
@@ -447,4 +460,4 @@ def create_app(api_base_url: str = "http://localhost:8000"):
447
 
448
 
449
  # Create the app instance
450
- app = create_app()
 
76
  return f"/static/filters/{filter_name}.png"
77
 
78
 
79
+ def create_app(api_base_url: str = "http://localhost:8000", fastapi_app=None):
80
+ """Create the PyShiny heatmap app.
81
+
82
+ Args:
83
+ api_base_url: Base URL for API requests (used if fastapi_app is None)
84
+ fastapi_app: Optional FastAPI app instance for internal API calls
85
+ """
86
 
87
  app_ui = ui.page_fluid(
88
  ui.head_content(
 
142
  window.addEventListener('message', function(event) {{
143
  if (event.data && event.data.type === 'setParams') {{
144
  console.log('[Heatmap] Received params via postMessage:', event.data);
 
 
 
 
145
  // Wait for Shiny to be ready, then set input values
146
+ if (typeof Shiny !== 'undefined' && Shiny.setInputValue) {{
147
  Shiny.setInputValue('pm_job_id', event.data.job_id);
148
  Shiny.setInputValue('pm_batch_index', event.data.batch_index);
149
+ }} else {{
150
 
151
  // Retry after Shiny loads
152
+ document.addEventListener('shiny:connected', function() {{
153
  Shiny.setInputValue('pm_job_id', event.data.job_id);
154
  Shiny.setInputValue('pm_batch_index', event.data.batch_index);
155
+ }});
156
+ }}
157
+ }}
 
158
  // Handle download request from parent
159
  if (event.data && event.data.type === 'downloadRequest') {{
160
  console.log('[Heatmap] Download requested');
 
204
  setTimeout(retrySetParams, 1000);
205
  }});
206
  """),
207
+ ui.tags.style("""
208
  body {
209
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
210
  background: #f9fafb;
 
236
  }
237
  """)
238
  ),
239
+ ui.row(
240
  ui.column(
241
  12,
242
  ui.div(
 
274
  print(f"[Heatmap Server] params_received set to True", flush=True)
275
 
276
  try:
277
+ # Try to use internal FastAPI app if available, otherwise use HTTP
278
+ if fastapi_app is not None:
279
+ # Use TestClient for internal requests (synchronous but works)
280
+ from fastapi.testclient import TestClient
281
+ client = TestClient(fastapi_app)
282
+ url_path = f"/api/vis_data/{job_id}"
283
+ if batch_index is not None:
284
+ url_path += f"?batch_index={batch_index}"
285
+ print(f"[Heatmap Server] Making internal API request to: {url_path}", flush=True)
286
+ response = client.get(url_path)
287
  print(f"[Heatmap Server] API response status: {response.status_code}", flush=True)
288
+ if response.status_code != 200:
289
+ raise Exception(f"API returned status {response.status_code}: {response.text}")
290
  data = response.json()
291
+ else:
292
+ # Fallback to HTTP request
293
+ url = f"{api_base_url}/api/vis_data/{job_id}"
294
+ if batch_index is not None:
295
+ url += f"?batch_index={batch_index}"
296
+ print(f"[Heatmap Server] Making HTTP API request to: {url}", flush=True)
297
+ async with httpx.AsyncClient(
298
+ timeout=60.0,
299
+ follow_redirects=True,
300
+ verify=False
301
+ ) as client:
302
+ response = await client.get(url)
303
+ print(f"[Heatmap Server] API response status: {response.status_code}", flush=True)
304
+ response.raise_for_status()
305
+ data = response.json()
306
+
307
+ print(f"[Heatmap Server] API response data keys: {list(data.keys()) if data else 'None'}", flush=True)
308
+ vis_data.set(data)
309
+ error_message.set(None) # Clear any error
310
+ print("[Heatmap Server] vis_data set successfully", flush=True)
311
  except Exception as e:
312
+ error_msg = f"Error fetching data: {type(e).__name__}: {str(e)}"
313
  print(f"[Heatmap Server] {error_msg}", flush=True)
314
  import traceback
315
  traceback.print_exc()
 
388
  )
389
 
390
  # Highlight exon region
391
+ #fig.add_vrect(x0=start-0.5, x1=end-0.5, fillcolor="#d0d0d0", line_width=0, opacity=0.1)
392
 
393
  # Add filter icons to the left of y-axis labels
394
  fig.update_layout(images=[])
 
440
  "toImageButtonOptions": {
441
  "format": "svg",
442
  "filename": "heatmap_view",
 
 
 
 
443
  "height": 500,
444
  "width": 1100,
445
  "scale": 2
 
446
  },
447
  "displayModeBar": True,
448
+
449
+ "modeBarButtons": [[
450
+ "zoom2d",
451
+ "toImage"
452
+ ]],
453
+ "displaylogo": False,
454
  },
455
  )
456
 
 
460
 
461
 
462
  # Create the app instance
463
+ #app = create_app()
webapp/app/shiny_apps/silhouette_app.py CHANGED
@@ -84,8 +84,13 @@ def position_totals_for_selected_filters(nucleotide_activations_children, L, sel
84
  return side_to_total(incl_node), side_to_total(skip_node)
85
 
86
 
87
- def create_app(api_base_url: str = "http://localhost:8000"):
88
- """Create the PyShiny silhouette app."""
 
 
 
 
 
89
 
90
  app_ui = ui.page_fluid(
91
  ui.head_content(
@@ -273,27 +278,42 @@ def create_app(api_base_url: str = "http://localhost:8000"):
273
  print(f"[Silhouette Server] params_received set to True", flush=True)
274
 
275
  try:
276
- url = f"{api_base_url}/api/vis_data/{job_id}"
277
- if batch_index is not None:
278
- url += f"?batch_index={batch_index}"
279
-
280
- print(f"[Silhouette Server] Making API request to: {url}", flush=True)
281
-
282
- async with httpx.AsyncClient(timeout=60.0) as client:
283
- response = await client.get(url)
 
 
284
  print(f"[Silhouette Server] API response status: {response.status_code}", flush=True)
285
- response.raise_for_status()
 
286
  data = response.json()
287
- print(f"[Silhouette Server] API response data keys: {list(data.keys()) if data else 'None'}", flush=True)
288
- vis_data.set(data)
289
- error_message.set(None) # Clear any error
290
- print("[Silhouette Server] vis_data set successfully", flush=True)
291
- except httpx.HTTPError as e:
292
- error_msg = f"HTTP Error fetching data: {str(e)}"
293
- print(f"[Silhouette Server] {error_msg}", flush=True)
294
- error_message.set(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
295
  except Exception as e:
296
- error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}"
297
  print(f"[Silhouette Server] {error_msg}", flush=True)
298
  import traceback
299
  traceback.print_exc()
@@ -352,7 +372,7 @@ def create_app(api_base_url: str = "http://localhost:8000"):
352
  ax.bar(x, -skip_total, width=1, color="#f0a5a5", label="Skipping")
353
 
354
  # Shade exon region
355
- ax.axvspan(start - 0.5, end - 0.5, color="#d0d0d0", alpha=0.15)
356
  ax.axhline(0, linewidth=1, color='black')
357
 
358
  ax.set_xticks(x)
@@ -402,4 +422,4 @@ def create_app(api_base_url: str = "http://localhost:8000"):
402
 
403
 
404
  # Create the app instance
405
- app = create_app()
 
84
  return side_to_total(incl_node), side_to_total(skip_node)
85
 
86
 
87
+ def create_app(api_base_url: str = "http://localhost:8000", fastapi_app=None):
88
+ """Create the PyShiny silhouette app.
89
+
90
+ Args:
91
+ api_base_url: Base URL for API requests (used if fastapi_app is None)
92
+ fastapi_app: Optional FastAPI app instance for internal API calls
93
+ """
94
 
95
  app_ui = ui.page_fluid(
96
  ui.head_content(
 
278
  print(f"[Silhouette Server] params_received set to True", flush=True)
279
 
280
  try:
281
+ # Try to use internal FastAPI app if available, otherwise use HTTP
282
+ if fastapi_app is not None:
283
+ # Use TestClient for internal requests (synchronous but works)
284
+ from fastapi.testclient import TestClient
285
+ client = TestClient(fastapi_app)
286
+ url_path = f"/api/vis_data/{job_id}"
287
+ if batch_index is not None:
288
+ url_path += f"?batch_index={batch_index}"
289
+ print(f"[Silhouette Server] Making internal API request to: {url_path}", flush=True)
290
+ response = client.get(url_path)
291
  print(f"[Silhouette Server] API response status: {response.status_code}", flush=True)
292
+ if response.status_code != 200:
293
+ raise Exception(f"API returned status {response.status_code}: {response.text}")
294
  data = response.json()
295
+ else:
296
+ # Fallback to HTTP request
297
+ url = f"{api_base_url}/api/vis_data/{job_id}"
298
+ if batch_index is not None:
299
+ url += f"?batch_index={batch_index}"
300
+ print(f"[Silhouette Server] Making HTTP API request to: {url}", flush=True)
301
+ async with httpx.AsyncClient(
302
+ timeout=60.0,
303
+ follow_redirects=True,
304
+ verify=False
305
+ ) as client:
306
+ response = await client.get(url)
307
+ print(f"[Silhouette Server] API response status: {response.status_code}", flush=True)
308
+ response.raise_for_status()
309
+ data = response.json()
310
+
311
+ print(f"[Silhouette Server] API response data keys: {list(data.keys()) if data else 'None'}", flush=True)
312
+ vis_data.set(data)
313
+ error_message.set(None) # Clear any error
314
+ print("[Silhouette Server] vis_data set successfully", flush=True)
315
  except Exception as e:
316
+ error_msg = f"Error fetching data: {type(e).__name__}: {str(e)}"
317
  print(f"[Silhouette Server] {error_msg}", flush=True)
318
  import traceback
319
  traceback.print_exc()
 
372
  ax.bar(x, -skip_total, width=1, color="#f0a5a5", label="Skipping")
373
 
374
  # Shade exon region
375
+ #ax.axvspan(start - 0.5, end - 0.5, color="#d0d0d0", alpha=0.15)
376
  ax.axhline(0, linewidth=1, color='black')
377
 
378
  ax.set_xticks(x)
 
422
 
423
 
424
  # Create the app instance
425
+ #app = create_app()
webapp/templates/base.html CHANGED
@@ -110,65 +110,6 @@
110
  </div>
111
  </div>
112
 
113
- <!-- Auth dropdown -->
114
- <div id="auth-nav-desktop" class="relative">
115
- <!-- Not logged in: show Login button -->
116
- <div id="auth-logged-out">
117
- <button onclick="toggleAuthDropdown()" class="px-3 py-2 text-sm font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-50 rounded-md flex items-center">
118
- Login
119
- <svg class="ml-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
120
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
121
- </svg>
122
- </button>
123
- <!-- Dropdown menu -->
124
- <div id="auth-dropdown" class="hidden absolute right-0 mt-2 w-72 bg-white rounded-lg shadow-lg border border-gray-200 p-4 z-50">
125
- <!-- Message area -->
126
- <div id="auth-message" class="hidden mb-3 p-3 rounded-md text-sm"></div>
127
-
128
- <!-- Form -->
129
- <div id="auth-form-container">
130
- <div class="space-y-3">
131
- <div>
132
- <label for="auth-email" class="block text-xs font-medium text-gray-700 mb-1">Email</label>
133
- <input type="email" id="auth-email" placeholder="user@example.com"
134
- class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
135
- </div>
136
- <div>
137
- <label for="auth-password" class="block text-xs font-medium text-gray-700 mb-1">Password</label>
138
- <input type="password" id="auth-password" placeholder="••••••••"
139
- class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
140
- </div>
141
- <button onclick="submitAuth()" id="auth-submit-btn"
142
- class="w-full py-2 px-4 bg-primary-600 text-white text-sm font-medium rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center">
143
- <span id="auth-btn-text">Continue</span>
144
- <svg id="auth-loading" class="hidden animate-spin ml-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
145
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
146
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
147
- </svg>
148
- </button>
149
- </div>
150
- <div class="mt-3 pt-3 border-t border-gray-200">
151
- <p class="text-xs text-gray-500 text-center">No account? We'll create one automatically.</p>
152
- </div>
153
- </div>
154
- </div>
155
- </div>
156
-
157
- <!-- Logged in: show email + logout dropdown -->
158
- <div id="auth-logged-in" class="hidden">
159
- <button onclick="toggleUserDropdown()" class="px-3 py-2 text-sm font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-50 rounded-md flex items-center">
160
- <span id="user-email-display" class="max-w-[150px] truncate"></span>
161
- <svg class="ml-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
162
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
163
- </svg>
164
- </button>
165
- <div id="user-dropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-50">
166
- <button onclick="logoutUser()" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
167
- Logout
168
- </button>
169
- </div>
170
- </div>
171
- </div>
172
  </div>
173
 
174
  <!-- Mobile menu button -->
@@ -223,29 +164,6 @@
223
  </div>
224
  </div>
225
 
226
- <!-- Auth links mobile -->
227
- <div id="auth-nav-mobile" class="border-t border-gray-200 pt-2 mt-2">
228
- <div id="mobile-auth-logged-out">
229
- <button onclick="toggleMobileAuthForm()" class="w-full text-left px-3 py-2 text-base font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-50 rounded-md">Login</button>
230
- <!-- Mobile auth form -->
231
- <div id="mobile-auth-form" class="hidden px-3 py-2 space-y-3">
232
- <div id="mobile-auth-message" class="hidden p-3 rounded-md text-sm"></div>
233
- <input type="email" id="mobile-auth-email" placeholder="Email"
234
- class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm">
235
- <input type="password" id="mobile-auth-password" placeholder="Password"
236
- class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm">
237
- <button onclick="submitMobileAuth()" id="mobile-auth-submit"
238
- class="w-full py-2 px-4 bg-primary-600 text-white text-sm font-medium rounded-md hover:bg-primary-700">
239
- Continue
240
- </button>
241
- <p class="text-xs text-gray-500 text-center">No account? We'll create one automatically.</p>
242
- </div>
243
- </div>
244
- <div id="mobile-auth-logged-in" class="hidden">
245
- <div class="px-3 py-2 text-sm text-gray-600" id="mobile-user-email"></div>
246
- <button onclick="logoutUser()" class="w-full text-left px-3 py-2 text-base font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-50 rounded-md">Logout</button>
247
- </div>
248
- </div>
249
  </div>
250
  </div>
251
  </nav>
 
110
  </div>
111
  </div>
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  </div>
114
 
115
  <!-- Mobile menu button -->
 
164
  </div>
165
  </div>
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  </div>
168
  </div>
169
  </nav>
webapp/templates/result.html CHANGED
@@ -129,9 +129,7 @@
129
  </svg>
130
  </div>
131
  <div class="collapsible-content">
132
- <p class="text-sm text-gray-500 mb-4">
133
- Blue = inclusion strength, red = skipping strength per position.
134
- </p>
135
  <div id="silhouette-container" class="w-full" style="min-height: 488px;">
136
  <iframe
137
  id="silhouette-iframe"
@@ -153,9 +151,7 @@
153
  </svg>
154
  </div>
155
  <div class="collapsible-content">
156
- <p class="text-sm text-gray-500 mb-4">
157
- Blue = inclusion, red = skipping, with filter icons.
158
- </p>
159
  <div id="heatmap-container" class="w-full" style="min-height: 675px;">
160
  <iframe
161
  id="heatmap-iframe"
 
129
  </svg>
130
  </div>
131
  <div class="collapsible-content">
132
+
 
 
133
  <div id="silhouette-container" class="w-full" style="min-height: 488px;">
134
  <iframe
135
  id="silhouette-iframe"
 
151
  </svg>
152
  </div>
153
  <div class="collapsible-content">
154
+
 
 
155
  <div id="heatmap-container" class="w-full" style="min-height: 675px;">
156
  <iframe
157
  id="heatmap-iframe"