GitHub Actions commited on
Commit
f620ad7
·
1 Parent(s): 30c2a3b

fix: CSS via gr.Blocks(css=) + serve webagent as static files

Browse files

Problem 1 - Ticker was empty (CSP blocked inline <style> in gr.HTML()):
- Moved CSS out of gr.HTML() into gr.Blocks(css=...) parameter
- Gradio injects this as a proper stylesheet, bypassing CSP
- Doubled ticker items for seamless infinite scroll loop

Problem 2 - Agent modal showed blank (iframe pointed to localhost:8099):
- Mounted webagent/ directory as FastAPI static files at /webagent/
- Changed iframe src from http://127.0.0.1:8099/index.html -> /webagent/index.html
- Now works on both local and HF Space (same origin)

Keyboard shortcuts:
e = toggle news ticker at top of page
a = open/close browser agent popup modal

app.py CHANGED
@@ -394,5 +394,17 @@ _ui = _build_ui(
394
  )
395
  demo = _ui.build()
396
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  if __name__ == "__main__":
398
  demo.launch()
 
394
  )
395
  demo = _ui.build()
396
 
397
+ # Mount the browser agent as static files at /webagent/ so the 'a' key modal works
398
+ try:
399
+ import os
400
+ from pathlib import Path
401
+ from fastapi.staticfiles import StaticFiles
402
+
403
+ _webagent_dir = Path(__file__).parent / "webagent"
404
+ if _webagent_dir.exists():
405
+ demo.app.mount("/webagent", StaticFiles(directory=str(_webagent_dir)), name="webagent")
406
+ except Exception:
407
+ pass
408
+
409
  if __name__ == "__main__":
410
  demo.launch()
hearthnet/ui/__pycache__/app.cpython-313.pyc CHANGED
Binary files a/hearthnet/ui/__pycache__/app.cpython-313.pyc and b/hearthnet/ui/__pycache__/app.cpython-313.pyc differ
 
hearthnet/ui/app.py CHANGED
@@ -17,12 +17,8 @@ except ImportError:
17
  HAS_GRADIO = False
18
 
19
 
20
- # Easter egg ticker CSS (no script - will be injected via Blocks head parameter)
21
- _EASTER_EGG_CSS = """
22
- <style id="egg-ticker-style">
23
- body {
24
- position: relative;
25
- }
26
  .egg-ticker {
27
  position: fixed;
28
  top: -100px;
@@ -53,7 +49,7 @@ _EASTER_EGG_CSS = """
53
  }
54
  .egg-track {
55
  display: flex;
56
- animation: scroll 20s linear infinite;
57
  white-space: nowrap;
58
  gap: 40px;
59
  }
@@ -69,8 +65,6 @@ _EASTER_EGG_CSS = """
69
  color: #ff6b35;
70
  font-weight: bold;
71
  }
72
-
73
- /* Modal for agent page */
74
  .egg-modal-overlay {
75
  display: none;
76
  position: fixed;
@@ -80,7 +74,6 @@ _EASTER_EGG_CSS = """
80
  bottom: 0;
81
  background: rgba(0, 0, 0, 0.8);
82
  z-index: 10000;
83
- animation: fadeIn 0.3s ease;
84
  }
85
  .egg-modal-overlay.active {
86
  display: flex;
@@ -94,7 +87,7 @@ _EASTER_EGG_CSS = """
94
  height: 90%;
95
  max-width: 1200px;
96
  max-height: 900px;
97
- overflow: auto;
98
  position: relative;
99
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
100
  }
@@ -104,7 +97,7 @@ _EASTER_EGG_CSS = """
104
  right: 15px;
105
  font-size: 28px;
106
  font-weight: bold;
107
- color: #999;
108
  cursor: pointer;
109
  z-index: 10001;
110
  background: #fff;
@@ -115,27 +108,24 @@ _EASTER_EGG_CSS = """
115
  align-items: center;
116
  justify-content: center;
117
  border: 1px solid #ddd;
 
118
  }
119
  .egg-modal-close:hover {
120
- color: #000;
121
  background: #f0f0f0;
122
  }
123
  .egg-modal-iframe {
124
  width: 100%;
125
  height: 100%;
126
  border: none;
127
- border-radius: 8px;
128
  }
129
-
130
- @keyframes scroll {
131
  0% { transform: translateX(0); }
132
- 100% { transform: translateX(-100%); }
133
- }
134
- @keyframes fadeIn {
135
- from { opacity: 0; }
136
- to { opacity: 1; }
137
  }
138
- </style>
 
 
 
139
  <div id="egg-ticker" class="egg-ticker">
140
  <div class="egg-label">⚡ LIVE</div>
141
  <div id="egg-track" class="egg-track">
@@ -144,12 +134,17 @@ _EASTER_EGG_CSS = """
144
  <span class="etk"><b>TechCrunch</b> Latest</span>
145
  <span class="etk"><b>BBC</b> Breaking</span>
146
  <span class="etk"><b>AP News</b> Top Stories</span>
 
 
 
 
 
147
  </div>
148
  </div>
149
  <div id="egg-modal-overlay" class="egg-modal-overlay">
150
  <div class="egg-modal-content">
151
  <span id="egg-modal-close" class="egg-modal-close">×</span>
152
- <iframe id="egg-modal-iframe" class="egg-modal-iframe" src="http://127.0.0.1:8099/index.html"></iframe>
153
  </div>
154
  </div>
155
  """
@@ -242,9 +237,9 @@ class UiApp:
242
  node_id_display = self._meta.get("node_id", "unknown")
243
  display_name = self._meta.get("display_name", node_id_display[:20])
244
 
245
- with gr.Blocks(title=f"HearthNet — {display_name}", head=_EASTER_EGG_SCRIPT) as demo:
246
- # Inject easter egg ticker CSS & HTML
247
- gr.HTML(value=_EASTER_EGG_CSS)
248
 
249
  gr.Markdown(f"# 🔥 HearthNet — {display_name}")
250
 
 
17
  HAS_GRADIO = False
18
 
19
 
20
+ # Easter egg ticker raw CSS for gr.Blocks(css=...) (no <style> tags, Gradio injects it)
21
+ _EASTER_EGG_RAW_CSS = """
 
 
 
 
22
  .egg-ticker {
23
  position: fixed;
24
  top: -100px;
 
49
  }
50
  .egg-track {
51
  display: flex;
52
+ animation: hn-scroll 20s linear infinite;
53
  white-space: nowrap;
54
  gap: 40px;
55
  }
 
65
  color: #ff6b35;
66
  font-weight: bold;
67
  }
 
 
68
  .egg-modal-overlay {
69
  display: none;
70
  position: fixed;
 
74
  bottom: 0;
75
  background: rgba(0, 0, 0, 0.8);
76
  z-index: 10000;
 
77
  }
78
  .egg-modal-overlay.active {
79
  display: flex;
 
87
  height: 90%;
88
  max-width: 1200px;
89
  max-height: 900px;
90
+ overflow: hidden;
91
  position: relative;
92
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
93
  }
 
97
  right: 15px;
98
  font-size: 28px;
99
  font-weight: bold;
100
+ color: #333;
101
  cursor: pointer;
102
  z-index: 10001;
103
  background: #fff;
 
108
  align-items: center;
109
  justify-content: center;
110
  border: 1px solid #ddd;
111
+ line-height: 1;
112
  }
113
  .egg-modal-close:hover {
 
114
  background: #f0f0f0;
115
  }
116
  .egg-modal-iframe {
117
  width: 100%;
118
  height: 100%;
119
  border: none;
 
120
  }
121
+ @keyframes hn-scroll {
 
122
  0% { transform: translateX(0); }
123
+ 100% { transform: translateX(-50%); }
 
 
 
 
124
  }
125
+ """
126
+
127
+ # Easter egg ticker HTML elements (no <style> — CSS handled by gr.Blocks(css=...))
128
+ _EASTER_EGG_HTML = """
129
  <div id="egg-ticker" class="egg-ticker">
130
  <div class="egg-label">⚡ LIVE</div>
131
  <div id="egg-track" class="egg-track">
 
134
  <span class="etk"><b>TechCrunch</b> Latest</span>
135
  <span class="etk"><b>BBC</b> Breaking</span>
136
  <span class="etk"><b>AP News</b> Top Stories</span>
137
+ <span class="etk"><b>BleepingComputer</b> Security Updates</span>
138
+ <span class="etk"><b>Reuters</b> World News</span>
139
+ <span class="etk"><b>TechCrunch</b> Latest</span>
140
+ <span class="etk"><b>BBC</b> Breaking</span>
141
+ <span class="etk"><b>AP News</b> Top Stories</span>
142
  </div>
143
  </div>
144
  <div id="egg-modal-overlay" class="egg-modal-overlay">
145
  <div class="egg-modal-content">
146
  <span id="egg-modal-close" class="egg-modal-close">×</span>
147
+ <iframe id="egg-modal-iframe" class="egg-modal-iframe" src="/webagent/index.html"></iframe>
148
  </div>
149
  </div>
150
  """
 
237
  node_id_display = self._meta.get("node_id", "unknown")
238
  display_name = self._meta.get("display_name", node_id_display[:20])
239
 
240
+ with gr.Blocks(title=f"HearthNet — {display_name}", head=_EASTER_EGG_SCRIPT, css=_EASTER_EGG_RAW_CSS) as demo:
241
+ # Inject easter egg ticker HTML (CSS handled by gr.Blocks css= parameter above)
242
+ gr.HTML(value=_EASTER_EGG_HTML)
243
 
244
  gr.Markdown(f"# 🔥 HearthNet — {display_name}")
245
 
tests/test_easter_egg.py CHANGED
@@ -6,62 +6,50 @@ import re
6
 
7
 
8
  def test_easter_egg_implementation():
9
- """Verify easter egg ticker is properly implemented in app.py.
10
-
11
- Tests that:
12
- 1. Script is injected via head parameter (not innerHTML)
13
- 2. HTML/CSS is in gr.HTML() component
14
- 3. Event listener checks for 'e' key
15
- 4. Guards against INPUT/TEXTAREA elements
16
- """
17
- from hearthnet.ui.app import _EASTER_EGG_SCRIPT, _EASTER_EGG_CSS
18
-
19
- # Verify CSS contains the ticker styling
20
- assert ".egg-ticker" in _EASTER_EGG_CSS, "CSS missing .egg-ticker class"
21
- assert ".egg-ticker.active" in _EASTER_EGG_CSS, "CSS missing .egg-ticker.active state"
22
- assert "@keyframes scroll" in _EASTER_EGG_CSS, "CSS missing scroll animation"
23
-
24
- # Verify HTML contains the ticker element
25
- assert 'id="egg-ticker"' in _EASTER_EGG_CSS, "HTML missing egg-ticker id"
26
- assert "⚡ LIVE" in _EASTER_EGG_CSS, "HTML missing LIVE label"
27
- assert 'id="egg-track"' in _EASTER_EGG_CSS, "HTML missing egg-track id"
28
-
29
  # Verify script is injected via head parameter (must contain <script> tag)
30
  assert "<script>" in _EASTER_EGG_SCRIPT, "Script must be wrapped in <script> tag for head parameter"
31
  assert "</script>" in _EASTER_EGG_SCRIPT, "Script must have closing </script> tag"
32
-
33
  # Verify keydown listener
34
- assert "evt.key === 'e'" in _EASTER_EGG_SCRIPT, "Script missing 'e' key check (lowercase)"
35
- assert "evt.key === 'E'" in _EASTER_EGG_SCRIPT, "Script missing 'E' key check (uppercase)"
36
-
37
  # Verify input guard
38
  assert "'INPUT'" in _EASTER_EGG_SCRIPT, "Script missing INPUT guard"
39
  assert "'TEXTAREA'" in _EASTER_EGG_SCRIPT, "Script missing TEXTAREA guard"
40
-
41
- # Verify toggle function
42
  assert "toggleEasterEgg" in _EASTER_EGG_SCRIPT, "Script missing toggleEasterEgg function"
43
- assert ".active" in _EASTER_EGG_SCRIPT, "Script not toggling .active class"
44
-
45
- # Verify retry logic for DOM element
46
- assert "initEasterEgg" in _EASTER_EGG_SCRIPT, "Script missing initEasterEgg function"
47
- assert "setTimeout(initEasterEgg" in _EASTER_EGG_SCRIPT, "Script missing retry logic for DOM"
48
-
49
- # Verify script is injected in Blocks head parameter
50
  from hearthnet.ui.app import UiApp
51
  import inspect
52
-
53
  build_code = inspect.getsource(UiApp.build)
 
54
  assert "head=_EASTER_EGG_SCRIPT" in build_code, "Script not injected via head parameter in Blocks"
55
- assert "gr.HTML(value=_EASTER_EGG_CSS)" in build_code, "CSS not injected via gr.HTML()"
56
 
57
 
58
  def test_easter_egg_no_script_in_html():
59
- """Verify script is NOT embedded in gr.HTML() to avoid Gradio warning."""
60
- from hearthnet.ui.app import _EASTER_EGG_CSS, _EASTER_EGG_SCRIPT
61
-
62
- # CSS should NOT contain script tags (they go to head parameter)
63
- assert "<script>" not in _EASTER_EGG_CSS, "CSS component should not contain script (goes to head parameter)"
64
-
65
- # Script should be separate
66
- assert len(_EASTER_EGG_SCRIPT) > 0, "Script must exist"
67
- assert "<script>" in _EASTER_EGG_SCRIPT, "Script must be in _EASTER_EGG_SCRIPT constant"
 
6
 
7
 
8
  def test_easter_egg_implementation():
9
+ """Verify easter egg ticker is properly implemented in app.py."""
10
+ from hearthnet.ui.app import _EASTER_EGG_SCRIPT, _EASTER_EGG_RAW_CSS, _EASTER_EGG_HTML
11
+
12
+ # Verify CSS contains the ticker styling (no <style> tags — goes to gr.Blocks css=)
13
+ assert ".egg-ticker" in _EASTER_EGG_RAW_CSS, "CSS missing .egg-ticker class"
14
+ assert ".egg-ticker.active" in _EASTER_EGG_RAW_CSS, "CSS missing .egg-ticker.active state"
15
+ assert "@keyframes" in _EASTER_EGG_RAW_CSS, "CSS missing scroll animation"
16
+ assert "<style>" not in _EASTER_EGG_RAW_CSS, "Raw CSS must not contain <style> tags (goes to gr.Blocks css=)"
17
+
18
+ # Verify HTML contains the ticker element (no style tags)
19
+ assert 'id="egg-ticker"' in _EASTER_EGG_HTML, "HTML missing egg-ticker id"
20
+ assert "⚡ LIVE" in _EASTER_EGG_HTML, "HTML missing LIVE label"
21
+ assert 'id="egg-track"' in _EASTER_EGG_HTML, "HTML missing egg-track id"
22
+ assert "<style>" not in _EASTER_EGG_HTML, "HTML component must not contain <style> tags"
23
+
 
 
 
 
 
24
  # Verify script is injected via head parameter (must contain <script> tag)
25
  assert "<script>" in _EASTER_EGG_SCRIPT, "Script must be wrapped in <script> tag for head parameter"
26
  assert "</script>" in _EASTER_EGG_SCRIPT, "Script must have closing </script> tag"
27
+
28
  # Verify keydown listener
29
+ assert "evt.key === 'e'" in _EASTER_EGG_SCRIPT, "Script missing 'e' key check"
30
+ assert "evt.key === 'a'" in _EASTER_EGG_SCRIPT, "Script missing 'a' key check for modal"
31
+
32
  # Verify input guard
33
  assert "'INPUT'" in _EASTER_EGG_SCRIPT, "Script missing INPUT guard"
34
  assert "'TEXTAREA'" in _EASTER_EGG_SCRIPT, "Script missing TEXTAREA guard"
35
+
36
+ # Verify toggle functions
37
  assert "toggleEasterEgg" in _EASTER_EGG_SCRIPT, "Script missing toggleEasterEgg function"
38
+ assert "toggleAgentModal" in _EASTER_EGG_SCRIPT, "Script missing toggleAgentModal function"
39
+
40
+ # Verify gr.Blocks uses css= parameter and gr.HTML uses HTML only
 
 
 
 
41
  from hearthnet.ui.app import UiApp
42
  import inspect
 
43
  build_code = inspect.getsource(UiApp.build)
44
+ assert "css=_EASTER_EGG_RAW_CSS" in build_code, "Raw CSS not passed to gr.Blocks css= parameter"
45
  assert "head=_EASTER_EGG_SCRIPT" in build_code, "Script not injected via head parameter in Blocks"
46
+ assert "gr.HTML(value=_EASTER_EGG_HTML)" in build_code, "HTML not injected via gr.HTML()"
47
 
48
 
49
  def test_easter_egg_no_script_in_html():
50
+ """Verify script and style are NOT in gr.HTML() to avoid Gradio/CSP issues."""
51
+ from hearthnet.ui.app import _EASTER_EGG_HTML, _EASTER_EGG_RAW_CSS
52
+
53
+ assert "<script>" not in _EASTER_EGG_HTML, "HTML component must not contain scripts"
54
+ assert "<style>" not in _EASTER_EGG_HTML, "HTML component must not contain style tags (use gr.Blocks css=)"
55
+ assert "<style>" not in _EASTER_EGG_RAW_CSS, "Raw CSS must not be wrapped in style tags"