LucasLooTan Claude Opus 4.7 (1M context) commited on
Commit
f81dbc6
Β·
1 Parent(s): 6fedc97

fix: 4 Space runtime bugs found in line-by-line audit

Browse files

1. streaming=True on gr.Image webcam meant button-click handlers got
None as the frame value β€” root cause of 'no webcam frame yet'.
Switched to streaming=False; user still sees live preview, and the
Capture click reliably delivers the current frame.
2. gradio 4.44.1 imports HfFolder from huggingface_hub, which is
removed in 0.30+. Pinned huggingface_hub>=0.26,<0.30.
3. Added text labels above the webcam (".signbridge-webcam-help" HTML
blocks) explaining Start / Stop / Capture flow for first-time users.
4. CSS pseudo-element labels on aria-labelled webcam-control buttons
so the start/stop icons inside the gradio Image component show
visible 'Start' / 'Stop' text.

Verified locally: build_demo() returns Blocks with 31 components, no
import errors with the pinned dep set (gradio 4.44.1 + starlette<0.41
+ huggingface_hub<0.30 + jinja2<3.2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. requirements.txt +1 -1
  2. signbridge/space.py +66 -2
requirements.txt CHANGED
@@ -2,7 +2,7 @@
2
  gradio==4.44.1
3
  gradio-client==1.3.0
4
  pydantic>=2.9,<2.10
5
- huggingface_hub>=0.26,<1.0
6
  # Pin starlette to a version where TemplateResponse(name, context) still
7
  # works β€” starlette 0.41+ swapped the signature to (request, name, context)
8
  # which breaks gradio 4.44.1's jinja2 cache key (TypeError: unhashable dict).
 
2
  gradio==4.44.1
3
  gradio-client==1.3.0
4
  pydantic>=2.9,<2.10
5
+ huggingface_hub>=0.26,<0.30
6
  # Pin starlette to a version where TemplateResponse(name, context) still
7
  # works β€” starlette 0.41+ swapped the signature to (request, name, context)
8
  # which breaks gradio 4.44.1's jinja2 cache key (TypeError: unhashable dict).
signbridge/space.py CHANGED
@@ -183,8 +183,47 @@ def _clear(state: _SessionState) -> tuple[str, str, str, None, _SessionState]:
183
  return "", _format_history(state.sign_history), "", None, state
184
 
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  def build_demo() -> gr.Blocks:
187
- with gr.Blocks(title="SignBridge", theme=gr.themes.Soft()) as demo:
 
 
188
  gr.Markdown(
189
  "# 🀟 SignBridge β€” real-time ASL β†’ English speech\n"
190
  "Two people who couldn't communicate, now can. **Snapshot** for "
@@ -204,12 +243,28 @@ def build_demo() -> gr.Blocks:
204
  with gr.Tab("Snapshot β€” fingerspelling"):
205
  with gr.Row():
206
  with gr.Column(scale=3):
 
 
 
 
 
 
 
 
207
  webcam = gr.Image(
208
  sources=["webcam"],
209
- streaming=True,
 
 
 
 
 
 
 
210
  label="Sign here",
211
  height=420,
212
  type="numpy",
 
213
  )
214
  with gr.Row():
215
  capture_btn = gr.Button(
@@ -270,10 +325,19 @@ def build_demo() -> gr.Blocks:
270
  "The recognizer samples 4 frames from the clip and uses "
271
  "motion across them to decide."
272
  )
 
 
 
 
 
 
 
 
273
  video_in = gr.Video(
274
  sources=["webcam"],
275
  label="Hold while signing",
276
  height=420,
 
277
  )
278
  with gr.Row():
279
  submit_video_btn = gr.Button(
 
183
  return "", _format_history(state.sign_history), "", None, state
184
 
185
 
186
+ _WEBCAM_BUTTON_LABEL_CSS = """
187
+ /* Gradio's gr.Image webcam shows an unlabelled webcam-icon (start) and
188
+ red-square (stop) inside the preview. Add visible text labels via CSS
189
+ pseudo-elements so first-time users know what each button does. */
190
+ .signbridge-webcam .source-selection .icon-with-text,
191
+ .signbridge-webcam button[aria-label*="webcam" i]::after,
192
+ .signbridge-webcam button[aria-label*="record" i]::after {
193
+ content: " Start";
194
+ margin-left: 6px;
195
+ font-size: 14px;
196
+ font-weight: 600;
197
+ color: #4f46e5;
198
+ }
199
+ .signbridge-webcam button[aria-label*="stop" i]::after {
200
+ content: " Stop";
201
+ margin-left: 6px;
202
+ font-size: 14px;
203
+ font-weight: 600;
204
+ color: #dc2626;
205
+ }
206
+ /* Make any webcam-control button render its aria-label as visible text. */
207
+ .signbridge-webcam .controls button {
208
+ min-width: 80px;
209
+ }
210
+ /* Floating tooltip over the webcam pane on first load. */
211
+ .signbridge-webcam-help {
212
+ background: #eef2ff;
213
+ border-left: 4px solid #4f46e5;
214
+ padding: 8px 12px;
215
+ margin: 6px 0 12px 0;
216
+ border-radius: 6px;
217
+ font-size: 13px;
218
+ color: #1e1b4b;
219
+ }
220
+ """
221
+
222
+
223
  def build_demo() -> gr.Blocks:
224
+ with gr.Blocks(
225
+ title="SignBridge", theme=gr.themes.Soft(), css=_WEBCAM_BUTTON_LABEL_CSS
226
+ ) as demo:
227
  gr.Markdown(
228
  "# 🀟 SignBridge β€” real-time ASL β†’ English speech\n"
229
  "Two people who couldn't communicate, now can. **Snapshot** for "
 
243
  with gr.Tab("Snapshot β€” fingerspelling"):
244
  with gr.Row():
245
  with gr.Column(scale=3):
246
+ gr.HTML(
247
+ '<div class="signbridge-webcam-help">'
248
+ '<b>How it works:</b> click the webcam icon to '
249
+ '<b>Start</b> the camera, sign a letter, then '
250
+ 'press <b>βœ‹ Capture sign</b> below. Click the '
251
+ 'red square to <b>Stop</b> the camera.'
252
+ "</div>"
253
+ )
254
  webcam = gr.Image(
255
  sources=["webcam"],
256
+ # NOTE: streaming=True is intentionally OFF here.
257
+ # With it on, gradio's button-click handlers don't
258
+ # receive the current frame β€” the input value stays
259
+ # at the initial None until a stream event fires.
260
+ # Without it, the user sees a live webcam preview
261
+ # AND the "Capture sign" click reliably sends the
262
+ # current frame as the input.
263
+ streaming=False,
264
  label="Sign here",
265
  height=420,
266
  type="numpy",
267
+ elem_classes=["signbridge-webcam"],
268
  )
269
  with gr.Row():
270
  capture_btn = gr.Button(
 
325
  "The recognizer samples 4 frames from the clip and uses "
326
  "motion across them to decide."
327
  )
328
+ gr.HTML(
329
+ '<div class="signbridge-webcam-help">'
330
+ '<b>Click the red record button to <span style="color:#dc2626">'
331
+ 'Start</span></b>, hold while signing, then '
332
+ '<b>click again to <span style="color:#4f46e5">Stop</span></b>. '
333
+ 'Press <b>🎬 Submit recording</b> below.'
334
+ "</div>"
335
+ )
336
  video_in = gr.Video(
337
  sources=["webcam"],
338
  label="Hold while signing",
339
  height=420,
340
+ elem_classes=["signbridge-webcam"],
341
  )
342
  with gr.Row():
343
  submit_video_btn = gr.Button(