Bit-Trading-Company commited on
Commit
9d6050e
·
verified ·
1 Parent(s): c201e66

CI deploy 13e1bd68

Browse files
Files changed (2) hide show
  1. app.py +7 -2
  2. src/ui/bridge.py +48 -2
app.py CHANGED
@@ -28,7 +28,8 @@ from src.runtime import RunError, RunRecord, RunRequest
28
  from src.ui import components as C
29
  from src.ui import compare_tab as CT
30
  from src.ui import shell, theme
31
- from src.ui.bridge import ACTION_ELEMENT_ID, BRIDGE_JS, parse_action, parse_pair
 
32
  from src.ui.format import EM, count, esc, money, num, pct, tone
33
 
34
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(message)s")
@@ -635,11 +636,15 @@ def build_app() -> gr.Blocks:
635
  render_table(runtime.coverage_frame(), empty="no coverage"),
636
  extension.status_html())
637
 
 
 
 
638
  demo.load(on_load, [state],
639
  [state, left_html, catalog_meta, *compare_out,
640
  models_note, acc_plot, cal_plot, model_bars, score_table,
641
  sig_panel, runs_table, coverage_kpis, coverage_html,
642
- extend_panel])
 
643
 
644
  return demo
645
 
 
28
  from src.ui import components as C
29
  from src.ui import compare_tab as CT
30
  from src.ui import shell, theme
31
+ from src.ui.bridge import (ACTION_ELEMENT_ID, BRIDGE_JS, BRIDGE_LOAD_JS,
32
+ parse_action, parse_pair)
33
  from src.ui.format import EM, count, esc, money, num, pct, tone
34
 
35
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(message)s")
 
636
  render_table(runtime.coverage_frame(), empty="no coverage"),
637
  extension.status_html())
638
 
639
+ # `js=` is what actually installs the click bridge on Spaces: the
640
+ # `head=` injection above works locally but HF serves the page from its
641
+ # own template and drops it. The installer is idempotent.
642
  demo.load(on_load, [state],
643
  [state, left_html, catalog_meta, *compare_out,
644
  models_note, acc_plot, cal_plot, model_bars, score_table,
645
  sig_panel, runs_table, coverage_kpis, coverage_html,
646
+ extend_panel],
647
+ js=BRIDGE_LOAD_JS)
648
 
649
  return demo
650
 
src/ui/bridge.py CHANGED
@@ -66,8 +66,11 @@ ACTION_ELEMENT_ID = "bit-action"
66
  # (benchmark)" -- which legitimately contain spaces. No value contains a pipe.
67
  NONCE_SEP = "|"
68
 
69
- # Installed once into <head>. Capture-phase delegation keeps it working after
70
- # Gradio replaces the innerHTML of a re-rendered zone.
 
 
 
71
  BRIDGE_JS = """
72
  <script>
73
  (function () {
@@ -157,3 +160,46 @@ def parse_pair(value: str):
157
  """Split a compound action value like `fast_ma=20` -> ("fast_ma", "20")."""
158
  name, _, val = value.partition("=")
159
  return name.strip(), val.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  # (benchmark)" -- which legitimately contain spaces. No value contains a pipe.
67
  NONCE_SEP = "|"
68
 
69
+ # The listener body. Installed two ways, because neither alone is reliable:
70
+ # `gr.Blocks(head=...)` works locally but Hugging Face Spaces serves the page
71
+ # through its own template and drops it, while `demo.load(js=...)` runs
72
+ # client-side wherever the page came from. Both call the same idempotent
73
+ # installer, guarded by a window flag, so running twice is harmless.
74
  BRIDGE_JS = """
75
  <script>
76
  (function () {
 
160
  """Split a compound action value like `fast_ma=20` -> ("fast_ma", "20")."""
161
  name, _, val = value.partition("=")
162
  return name.strip(), val.strip()
163
+
164
+
165
+ # `demo.load(js=...)` takes a JS *function*. This is the same installer as
166
+ # BRIDGE_JS, in the form Gradio's load hook expects, and is the path that
167
+ # actually works on Hugging Face Spaces.
168
+ BRIDGE_LOAD_JS = """
169
+ () => {
170
+ if (window.__bitBridgeInstalled) return;
171
+ window.__bitBridgeInstalled = true;
172
+
173
+ function holder() {
174
+ var root = document.getElementById('__ELEM_ID__');
175
+ return root ? root.querySelector('textarea, input') : null;
176
+ }
177
+
178
+ function send(payload) {
179
+ var box = holder();
180
+ if (!box) return;
181
+ var nonce = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
182
+ box.value = payload + '|' + nonce;
183
+ box.dispatchEvent(new Event('input', { bubbles: true }));
184
+ }
185
+
186
+ document.addEventListener('click', function (e) {
187
+ var el = e.target && e.target.closest ? e.target.closest('[data-bit]') : null;
188
+ if (!el) return;
189
+ var action = el.getAttribute('data-bit');
190
+ if (!action || action.indexOf('noop:') === 0) return;
191
+ e.preventDefault();
192
+ e.stopPropagation();
193
+ send(action);
194
+ }, true);
195
+
196
+ function commit(el) {
197
+ if (!el || !el.hasAttribute || !el.hasAttribute('data-bit-input')) return;
198
+ send(el.getAttribute('data-bit-input') + '=' + el.value);
199
+ }
200
+ document.addEventListener('change', function (e) { commit(e.target); }, true);
201
+ document.addEventListener('keydown', function (e) {
202
+ if (e.key === 'Enter') { commit(e.target); }
203
+ }, true);
204
+ }
205
+ """.replace("__ELEM_ID__", ACTION_ELEMENT_ID)