m0ksh commited on
Commit
fb40e86
·
verified ·
1 Parent(s): 25a6bc5

Sync from GitHub (preserve manual model files)

Browse files
StreamlitApp/StreamlitApp.py CHANGED
@@ -24,6 +24,7 @@ from utils.shared_ui import (
24
  build_analysis_insights,
25
  build_analysis_summary_text,
26
  )
 
27
  from utils.visualize import (
28
  KNOWN_AMPS,
29
  MAX_3D_SEQUENCE_LENGTH,
@@ -50,6 +51,25 @@ def _tooltip_label(label: str, tooltip_text: str) -> None:
50
  st.markdown(f"{label} <span title='{safe}' style='cursor:help;color:#666'>(i)</span>", unsafe_allow_html=True)
51
 
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  def _try_copy_to_clipboard(text: str) -> None:
54
  # Best-effort server-side clipboard copy (browser copy is intentionally avoided).
55
  if pyperclip is not None:
@@ -103,6 +123,7 @@ page = st.sidebar.radio(
103
  "About",
104
  ],
105
  )
 
106
 
107
  if st.sidebar.button("Clear All Fields"):
108
  # Reset only app-owned state keys, then rerun to refresh all widgets.
@@ -119,6 +140,8 @@ if st.sidebar.button("Clear All Fields"):
119
  "visualize_df",
120
  "visualize_peptide_input",
121
  ]
 
 
122
  for k in keys:
123
  if k in st.session_state:
124
  del st.session_state[k]
@@ -191,6 +214,8 @@ if page == "Predict":
191
 
192
  if not sequences:
193
  st.warning("Please input or upload sequences first.")
 
 
194
  else:
195
  progress = st.progress(0.0)
196
  with st.spinner("Running prediction..."):
@@ -263,28 +288,31 @@ elif page == "Analyze":
263
 
264
  # Recompute only when sequence changes to avoid redundant work on reruns.
265
  if seq and seq != st.session_state.get("analyze_input", ""):
266
- with st.spinner("Running analysis..."):
267
- label, conf = predict_amp(seq, model)
268
- conf_pct = round(conf * 100, 1)
269
- conf_display = conf_pct if label == "AMP" else 100 - conf_pct
 
 
 
 
 
 
270
 
271
- comp = aa_composition(seq)
272
- props = compute_properties(seq)
 
273
 
274
- # Normalize property key variants returned by helper functions.
275
- net_charge = props.get("Net Charge (approx.)",
276
- props.get("Net charge", props.get("NetCharge", 0)))
277
-
278
- length = props.get("Length", len(seq))
279
- hydro = props.get("Hydrophobic Fraction", props.get("Hydrophobic", 0))
280
- charge = net_charge
281
- mw = props.get("Molecular Weight (Da)", props.get("MolecularWeight", 0))
282
 
283
- analysis = build_analysis_insights(label, conf, comp, length, float(hydro), float(charge))
284
 
285
- # Save computed payload for display + report exports below.
286
- st.session_state.analyze_input = seq
287
- st.session_state.analyze_output = (label, conf, conf_display, comp, props, analysis)
288
 
289
  # Render last computed analysis block.
290
  if st.session_state.analyze_output:
@@ -490,14 +518,17 @@ elif page == "Optimize":
490
  # Re-run optimization when the entered sequence changes.
491
  if seq and str(seq).strip() and str(seq).strip() != st.session_state.get("optimize_last_ran_input", ""):
492
  seq = str(seq).strip()
493
- st.session_state.optimize_last_ran_input = seq
494
- progress = st.progress(0.0, text="Optimizing...")
495
- with st.spinner("Optimizing sequence..."):
496
- improved_seq, improved_conf, history = optimize_sequence(seq, model)
497
- _ol, orig_conf = predict_amp(seq, model)
498
- st.session_state.optimize_output = (seq, orig_conf, improved_seq, improved_conf, history)
499
- progress.progress(1.0, text="Optimization complete")
500
- st.success("Optimization finished.")
 
 
 
501
 
502
  # Render latest optimization artifacts from session state.
503
  if st.session_state.optimize_output:
@@ -641,6 +672,8 @@ elif page == "t-SNE":
641
  sequences = st.session_state.visualize_sequences
642
  if len(sequences) < 2:
643
  st.warning("Need at least 2 sequences for t-SNE visualization.")
 
 
644
  else:
645
  progress = st.progress(0.0, text="Generating embedding...")
646
  with st.spinner("Generating embedding..."):
 
24
  build_analysis_insights,
25
  build_analysis_summary_text,
26
  )
27
+ from utils.rate_limit import RateLimiter
28
  from utils.visualize import (
29
  KNOWN_AMPS,
30
  MAX_3D_SEQUENCE_LENGTH,
 
51
  st.markdown(f"{label} <span title='{safe}' style='cursor:help;color:#666'>(i)</span>", unsafe_allow_html=True)
52
 
53
 
54
+ def _session_rate_limiter(state_key: str, max_calls: int, period_seconds: float) -> RateLimiter:
55
+ # One limiter object per browser session (Streamlit reruns keep the same session_state).
56
+ if state_key not in st.session_state:
57
+ st.session_state[state_key] = RateLimiter(max_calls, period_seconds)
58
+ return st.session_state[state_key]
59
+
60
+
61
+ def _rate_limit_ok(state_key: str, max_calls: int, period_seconds: float, action_label: str) -> bool:
62
+ rl = _session_rate_limiter(state_key, max_calls, period_seconds)
63
+ if rl.allow():
64
+ return True
65
+ wait = max(1.0, rl.time_until_next())
66
+ st.warning(
67
+ f"Rate limit: please wait **~{int(wait)}s** before another {action_label}. "
68
+ "(Light throttle on shared hosting.)"
69
+ )
70
+ return False
71
+
72
+
73
  def _try_copy_to_clipboard(text: str) -> None:
74
  # Best-effort server-side clipboard copy (browser copy is intentionally avoided).
75
  if pyperclip is not None:
 
123
  "About",
124
  ],
125
  )
126
+ st.sidebar.caption("Light per-session rate limits apply on expensive model runs.")
127
 
128
  if st.sidebar.button("Clear All Fields"):
129
  # Reset only app-owned state keys, then rerun to refresh all widgets.
 
140
  "visualize_df",
141
  "visualize_peptide_input",
142
  ]
143
+ for rk in ("_rl_predict", "_rl_analyze", "_rl_optimize", "_rl_tsne"):
144
+ keys.append(rk)
145
  for k in keys:
146
  if k in st.session_state:
147
  del st.session_state[k]
 
214
 
215
  if not sequences:
216
  st.warning("Please input or upload sequences first.")
217
+ elif not _rate_limit_ok("_rl_predict", 40, 60.0, "batch prediction"):
218
+ pass
219
  else:
220
  progress = st.progress(0.0)
221
  with st.spinner("Running prediction..."):
 
288
 
289
  # Recompute only when sequence changes to avoid redundant work on reruns.
290
  if seq and seq != st.session_state.get("analyze_input", ""):
291
+ if not _rate_limit_ok("_rl_analyze", 35, 60.0, "analysis run"):
292
+ pass
293
+ else:
294
+ with st.spinner("Running analysis..."):
295
+ label, conf = predict_amp(seq, model)
296
+ conf_pct = round(conf * 100, 1)
297
+ conf_display = conf_pct if label == "AMP" else 100 - conf_pct
298
+
299
+ comp = aa_composition(seq)
300
+ props = compute_properties(seq)
301
 
302
+ # Normalize property key variants returned by helper functions.
303
+ net_charge = props.get("Net Charge (approx.)",
304
+ props.get("Net charge", props.get("NetCharge", 0)))
305
 
306
+ length = props.get("Length", len(seq))
307
+ hydro = props.get("Hydrophobic Fraction", props.get("Hydrophobic", 0))
308
+ charge = net_charge
309
+ mw = props.get("Molecular Weight (Da)", props.get("MolecularWeight", 0))
 
 
 
 
310
 
311
+ analysis = build_analysis_insights(label, conf, comp, length, float(hydro), float(charge))
312
 
313
+ # Save computed payload for display + report exports below.
314
+ st.session_state.analyze_input = seq
315
+ st.session_state.analyze_output = (label, conf, conf_display, comp, props, analysis)
316
 
317
  # Render last computed analysis block.
318
  if st.session_state.analyze_output:
 
518
  # Re-run optimization when the entered sequence changes.
519
  if seq and str(seq).strip() and str(seq).strip() != st.session_state.get("optimize_last_ran_input", ""):
520
  seq = str(seq).strip()
521
+ if not _rate_limit_ok("_rl_optimize", 12, 60.0, "optimization run"):
522
+ pass
523
+ else:
524
+ st.session_state.optimize_last_ran_input = seq
525
+ progress = st.progress(0.0, text="Optimizing...")
526
+ with st.spinner("Optimizing sequence..."):
527
+ improved_seq, improved_conf, history = optimize_sequence(seq, model)
528
+ _ol, orig_conf = predict_amp(seq, model)
529
+ st.session_state.optimize_output = (seq, orig_conf, improved_seq, improved_conf, history)
530
+ progress.progress(1.0, text="Optimization complete")
531
+ st.success("Optimization finished.")
532
 
533
  # Render latest optimization artifacts from session state.
534
  if st.session_state.optimize_output:
 
672
  sequences = st.session_state.visualize_sequences
673
  if len(sequences) < 2:
674
  st.warning("Need at least 2 sequences for t-SNE visualization.")
675
+ elif not _rate_limit_ok("_rl_tsne", 10, 120.0, "t-SNE embedding run"):
676
+ pass
677
  else:
678
  progress = st.progress(0.0, text="Generating embedding...")
679
  with st.spinner("Generating embedding..."):
StreamlitApp/utils/rate_limit.py CHANGED
@@ -1,4 +1,4 @@
1
- # Optional rate limiter (not wired to a sidebar page yet).
2
  import time
3
  from collections import deque
4
 
 
1
+ # Simple per-session throttle for StreamlitApp (in-memory; per server process on HF).
2
  import time
3
  from collections import deque
4