TwoQuarks commited on
Commit
fc76c35
·
verified ·
1 Parent(s): aedff98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +365 -361
app.py CHANGED
@@ -1,361 +1,365 @@
1
- /* ===== TwoQuarks skin for Gradio ===== */
2
- *{margin:0;padding:0;box-sizing:border-box}
3
-
4
- :root{
5
- --bg:#000;
6
- --fg:#fff;
7
- --muted:rgba(255,255,255,.55);
8
-
9
- --card:rgba(255,255,255,.04);
10
- --card2:rgba(255,255,255,.08);
11
- --stroke:rgba(255,255,255,.10);
12
-
13
- /* glass needs to be visible but not milky */
14
- --glass:rgba(0,0,0,.32);
15
-
16
- --accent:#8cb4ff;
17
- --accentGlow: rgba(140,180,255,.20);
18
-
19
- --rSm:10px;
20
- --rMd:12px;
21
- --rLg:14px;
22
- }
23
-
24
- html,body{height:100%}
25
- body, .gradio-container{
26
- font-family: Inter, ui-sans-serif, system-ui, Arial;
27
- background:var(--bg) !important;
28
- color:var(--fg) !important;
29
- overflow-x:hidden;
30
- }
31
-
32
- /* Starfield canvas (keep it inside stacking context) */
33
- #quantumField{
34
- position:fixed; inset:0;
35
- width:100vw; height:100vh;
36
- z-index:0;
37
- background:#000;
38
- pointer-events:none;
39
- opacity:.70;
40
- }
41
-
42
- /* Nav */
43
- #tqNav{
44
- position: fixed;
45
- top: 0; left: 0; right: 0;
46
- z-index: 999;
47
- padding: 12px 22px;
48
- background: linear-gradient(180deg, rgba(0,0,0,.55), rgba(0,0,0,.22));
49
- backdrop-filter: blur(12px);
50
- border-bottom: 1px solid rgba(255,255,255,.06);
51
- }
52
-
53
- #tqNav .nav-inner{
54
- max-width: 980px;
55
- margin: 0 auto;
56
- display:flex;
57
- align-items:center;
58
- gap: 12px;
59
- }
60
-
61
- #tqNav .brand{
62
- font-weight: 700;
63
- font-size: 1.05rem;
64
- letter-spacing: .22px;
65
- opacity: .92;
66
- }
67
-
68
- #tqNav .nav-links{
69
- display:flex;
70
- gap:10px;
71
- margin-left: 14px;
72
- flex-wrap: wrap;
73
- }
74
-
75
- #tqNav .nav-links a{
76
- text-decoration:none;
77
- color: var(--fg);
78
- padding: 8px 12px;
79
- border: 1px solid rgba(255,255,255,.12);
80
- border-radius: 999px;
81
- font-size: .9rem;
82
- opacity: .78;
83
- transition: .15s ease;
84
- white-space: nowrap;
85
- background: rgba(255,255,255,.02);
86
- }
87
-
88
- #tqNav .nav-links a:hover{
89
- opacity: 1;
90
- background: rgba(255,255,255,.06);
91
- border-color: rgba(255,255,255,.18);
92
- }
93
-
94
- #tqNav .spacer{ flex:1; }
95
-
96
- /* Hamburger button */
97
- #tqNav .menu-btn{
98
- width: 34px;
99
- height: 34px;
100
- display:flex;
101
- align-items:center;
102
- justify-content:center;
103
- border: 1px solid rgba(255,255,255,.14);
104
- border-radius: 12px;
105
- cursor: pointer;
106
- background: rgba(255,255,255,.03);
107
- transition: .15s ease;
108
- }
109
- #tqNav .menu-btn:hover{
110
- background: rgba(255,255,255,.06);
111
- border-color: rgba(255,255,255,.20);
112
- }
113
-
114
- #tqNav .menu-btn span{
115
- display:block;
116
- width:16px;
117
- height:2px;
118
- background: var(--fg);
119
- position:relative;
120
- opacity:.9;
121
- }
122
-
123
- #tqNav .menu-btn span::before,
124
- #tqNav .menu-btn span::after{
125
- content:"";
126
- position:absolute;
127
- left:0;
128
- width:16px;
129
- height:2px;
130
- background: var(--fg);
131
- opacity:.9;
132
- }
133
-
134
- #tqNav .menu-btn span::before{ top:-6px; }
135
- #tqNav .menu-btn span::after{ top: 6px; }
136
-
137
- #tqNav .menu{
138
- position:absolute;
139
- top: 60px;
140
- right: 24px;
141
- min-width: 220px;
142
- padding: 10px;
143
- border-radius: var(--rLg);
144
- background: var(--glass);
145
- backdrop-filter: blur(14px);
146
- border: 1px solid rgba(255,255,255,.10);
147
- box-shadow: 0 0 28px rgba(0,0,0,.6);
148
- display:none;
149
- }
150
-
151
- #tqNav .menu.open{ display:block; }
152
-
153
- #tqNav .menu a{
154
- display:block;
155
- padding: 10px 10px;
156
- border-radius: 10px;
157
- text-decoration:none;
158
- color: var(--fg);
159
- opacity: .88;
160
- }
161
-
162
- #tqNav .menu a:hover{
163
- background: rgba(255,255,255,.06);
164
- opacity: 1;
165
- }
166
-
167
- #tqNav .menu .sep{
168
- height:1px;
169
- background: rgba(255,255,255,.10);
170
- margin: 8px 2px;
171
- }
172
-
173
- /* Make app sit above canvas and below fixed nav */
174
- #tqApp{
175
- position:relative;
176
- z-index:1;
177
- padding-top: 84px;
178
- }
179
-
180
- /* Hero / header panel (glass) */
181
- .tq-hero{
182
- margin: 10px 0 14px 0;
183
- padding: 14px 18px;
184
- background: var(--glass);
185
- backdrop-filter: blur(12px);
186
- border: 1px solid rgba(255,255,255,.06);
187
- border-radius: var(--rMd);
188
- box-shadow: 0 12px 34px rgba(0,0,0,.45);
189
- }
190
-
191
- .tq-hero .brand{
192
- font-weight:700;
193
- letter-spacing:.22px;
194
- font-size:1.08rem;
195
- opacity:.92;
196
- }
197
-
198
- .tq-hero .desc{
199
- margin-top:8px;
200
- color: var(--muted);
201
- font-size: .98rem;
202
- line-height: 1.45;
203
- }
204
-
205
- /* Cards / panels */
206
- .gradio-container .gr-panel,
207
- .gradio-container .block,
208
- .gradio-container .wrap{
209
- border:1px solid var(--stroke) !important;
210
- background: var(--card) !important;
211
- backdrop-filter: blur(10px) !important;
212
- border-radius: var(--rMd) !important;
213
- }
214
-
215
- /* Hover effect */
216
- .gradio-container .gr-panel:hover{
217
- background: var(--card2) !important;
218
- border-color: rgba(255,255,255,.14) !important;
219
- }
220
-
221
- /* Tabs (pill vibe) */
222
- .gradio-container .gr-tab-nav{
223
- background: transparent !important;
224
- border: none !important;
225
- gap: 10px !important;
226
- }
227
-
228
- .gradio-container .gr-tab-nav button{
229
- border:1px solid rgba(255,255,255,.12) !important;
230
- border-radius:999px !important;
231
- padding:8px 12px !important;
232
- color: var(--fg) !important;
233
- opacity:.78 !important;
234
- background: rgba(255,255,255,.02) !important;
235
- transition:.18s ease !important;
236
- }
237
-
238
- .gradio-container .gr-tab-nav button:hover{
239
- opacity:1 !important;
240
- background: rgba(255,255,255,.06) !important;
241
- border-color: rgba(255,255,255,.18) !important;
242
- }
243
-
244
- .gradio-container .gr-tab-nav button.selected{
245
- opacity: 1 !important;
246
- border-color: rgba(140,180,255,.55) !important;
247
- box-shadow:
248
- 0 0 0 1px rgba(140,180,255,.18) inset,
249
- 0 0 20px var(--accentGlow) !important;
250
- }
251
-
252
- /* Inputs */
253
- .gradio-container input,
254
- .gradio-container textarea{
255
- background: rgba(255,255,255,.03) !important;
256
- border:1px solid rgba(255,255,255,.12) !important;
257
- color: var(--fg) !important;
258
- border-radius: var(--rMd) !important;
259
- }
260
-
261
- /* Buttons (base) */
262
- .gradio-container button,
263
- .gradio-container .gr-button{
264
- color: var(--fg) !important;
265
- border:1px solid rgba(255,255,255,.12) !important;
266
- background: rgba(255,255,255,.04) !important;
267
- border-radius:999px !important;
268
- padding:10px 14px !important;
269
- opacity:.86 !important;
270
- transition:.15s ease !important;
271
- }
272
-
273
- /* Buttons: make them feel like buttons, not banners */
274
- .gradio-container .gr-button,
275
- .gradio-container button{
276
- width:auto !important;
277
- min-width: 160px;
278
- max-width: 280px;
279
- display:inline-flex !important;
280
- align-items:center !important;
281
- justify-content:center !important;
282
- font-weight: 650;
283
- letter-spacing: .2px;
284
- }
285
-
286
- .gradio-container button:hover,
287
- .gradio-container .gr-button:hover{
288
- opacity:1 !important;
289
- background: rgba(255,255,255,.08) !important;
290
- border-color: rgba(140,180,255,.35) !important;
291
- box-shadow: 0 0 18px var(--accentGlow);
292
- }
293
-
294
- /* Gallery base */
295
- .gradio-container .gallery{
296
- border-radius: var(--rMd) !important;
297
- }
298
-
299
- /* ===== Gallery → horizontal carousel (needs elem_id in app.py) ===== */
300
-
301
- /* Grid row becomes horizontal strip */
302
- #down_gallery .grid,
303
- #strange_gallery .grid{
304
- display:flex !important;
305
- flex-wrap:nowrap !important;
306
- gap: 14px !important;
307
-
308
- overflow-x:auto !important;
309
- overflow-y:hidden !important;
310
-
311
- padding: 12px !important;
312
- scroll-snap-type: x mandatory;
313
- -webkit-overflow-scrolling: touch;
314
- }
315
-
316
- /* Each slide */
317
- #down_gallery .grid > figure,
318
- #strange_gallery .grid > figure{
319
- flex: 0 0 auto !important;
320
- width: 220px !important;
321
- scroll-snap-align: start;
322
-
323
- background: rgba(255,255,255,.03);
324
- border: 1px solid rgba(255,255,255,.08);
325
- border-radius: 14px;
326
- padding: 8px;
327
- }
328
-
329
- /* Thumbnail size */
330
- #down_gallery img,
331
- #strange_gallery img{
332
- width: 100% !important;
333
- height: 130px !important;
334
- object-fit: contain !important; /* use cover if you prefer */
335
- border-radius: 10px !important;
336
- }
337
-
338
- /* Hide captions/filenames */
339
- #down_gallery figcaption,
340
- #strange_gallery figcaption,
341
- #down_gallery .caption,
342
- #strange_gallery .caption{
343
- display:none !important;
344
- }
345
-
346
- /* Sleek scrollbar */
347
- #down_gallery .grid::-webkit-scrollbar,
348
- #strange_gallery .grid::-webkit-scrollbar{
349
- height: 6px;
350
- }
351
- #down_gallery .grid::-webkit-scrollbar-thumb,
352
- #strange_gallery .grid::-webkit-scrollbar-thumb{
353
- background: rgba(255,255,255,.14);
354
- border-radius: 999px;
355
- }
356
-
357
- /* Range accent */
358
- input[type="range"]{ accent-color: var(--accent); }
359
-
360
- /* Optional: hide Gradio footer */
361
- footer{ display:none !important; }
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ from pathlib import Path
5
+ import gradio as gr
6
+
7
+ # ============================================================
8
+ # Paths (Linux is case-sensitive!)
9
+ # ============================================================
10
+
11
+ ROOT = Path(__file__).resolve().parent
12
+
13
+ DOWN_ROOT = ROOT / "Down"
14
+ STRANGE_ROOT = ROOT / "Strange"
15
+
16
+ DOWN_GRAPHICS = DOWN_ROOT / "graphics"
17
+ STRANGE_GRAPHICS = STRANGE_ROOT / "graphics"
18
+
19
+ DOWN_RESULTS = DOWN_ROOT / "results"
20
+ STRANGE_RESULTS = STRANGE_ROOT / "results"
21
+
22
+ STYLE_PATH = ROOT / "style.css"
23
+
24
+
25
+ # ============================================================
26
+ # CSS + JS (canvas + menu)
27
+ # ============================================================
28
+
29
+ CSS = STYLE_PATH.read_text(encoding="utf-8") if STYLE_PATH.exists() else ""
30
+
31
+ JS = r"""
32
+ () => {
33
+ // prevent double init (Gradio can re-render)
34
+ if (window.__tq_inited) return;
35
+ window.__tq_inited = true;
36
+
37
+ // --- Menu toggle ---
38
+ const btn = document.getElementById("tqMenuBtn");
39
+ const menu = document.getElementById("tqMenu");
40
+ if (btn && menu) {
41
+ btn.addEventListener("click", () => menu.classList.toggle("open"));
42
+ document.addEventListener("click", (e) => {
43
+ if (!menu.contains(e.target) && !btn.contains(e.target)) {
44
+ menu.classList.remove("open");
45
+ }
46
+ });
47
+ }
48
+
49
+ // --- Starfield canvas ---
50
+ const c = document.getElementById("quantumField");
51
+ if (!c) return;
52
+ const ctx = c.getContext("2d");
53
+ if (!ctx) return;
54
+
55
+ let w = 0, h = 0;
56
+ const stars = [];
57
+ const N = 170;
58
+
59
+ function resize() {
60
+ w = c.width = window.innerWidth;
61
+ h = c.height = window.innerHeight;
62
+ }
63
+
64
+ function seed() {
65
+ stars.length = 0;
66
+ for (let i = 0; i < N; i++) {
67
+ stars.push({
68
+ x: Math.random() * w,
69
+ y: Math.random() * h,
70
+ z: 0.2 + Math.random() * 0.8,
71
+ v: 0.15 + Math.random() * 0.55,
72
+ });
73
+ }
74
+ }
75
+
76
+ function tick() {
77
+ ctx.clearRect(0, 0, w, h);
78
+
79
+ // subtle vignette
80
+ const g = ctx.createRadialGradient(w/2, h/2, 10, w/2, h/2, Math.max(w,h)/1.1);
81
+ g.addColorStop(0, "rgba(0,0,0,0)");
82
+ g.addColorStop(1, "rgba(0,0,0,0.55)");
83
+ ctx.fillStyle = g;
84
+ ctx.fillRect(0, 0, w, h);
85
+
86
+ ctx.globalAlpha = 0.9;
87
+
88
+ for (const s of stars) {
89
+ s.y += s.v * (1.0 + (1.0 - s.z));
90
+ if (s.y > h + 10) { s.y = -10; s.x = Math.random() * w; }
91
+
92
+ const r = 0.6 + 1.6 * (1.0 - s.z);
93
+ ctx.fillStyle = "rgba(255,255,255,0.75)";
94
+ ctx.beginPath();
95
+ ctx.arc(s.x, s.y, r, 0, Math.PI * 2);
96
+ ctx.fill();
97
+ }
98
+
99
+ requestAnimationFrame(tick);
100
+ }
101
+
102
+ resize();
103
+ seed();
104
+ window.addEventListener("resize", () => { resize(); seed(); });
105
+ tick();
106
+ }
107
+ """
108
+
109
+
110
+ # ============================================================
111
+ # Helpers
112
+ # ============================================================
113
+
114
+ def _clean_pngs(folder: Path):
115
+ folder.mkdir(parents=True, exist_ok=True)
116
+ for f in folder.rglob("*.png"):
117
+ try:
118
+ f.unlink()
119
+ except Exception:
120
+ pass
121
+
122
+
123
+ def _collect_pngs(folder: Path):
124
+ if not folder.exists():
125
+ return []
126
+ return sorted(str(p) for p in folder.rglob("*.png"))
127
+
128
+
129
+ def _run_capture(cmd, cwd: Path, env: dict):
130
+ """
131
+ Run a subprocess and capture stdout/stderr.
132
+ If it fails, raise a RuntimeError containing the logs so Gradio shows it.
133
+ """
134
+ result = subprocess.run(
135
+ cmd,
136
+ cwd=str(cwd),
137
+ env=env,
138
+ capture_output=True,
139
+ text=True,
140
+ )
141
+ if result.returncode != 0:
142
+ raise RuntimeError(
143
+ "Subprocess failed\n"
144
+ f"CMD: {' '.join(map(str, cmd))}\n"
145
+ f"CWD: {cwd}\n\n"
146
+ f"STDOUT:\n{result.stdout}\n"
147
+ f"STDERR:\n{result.stderr}"
148
+ )
149
+ return result.stdout, result.stderr
150
+
151
+
152
+ # ============================================================
153
+ # DOWN (Unified: Down + AntiDown)
154
+ # ============================================================
155
+
156
+ def run_down_all(episodes: int):
157
+ run_all = DOWN_ROOT / "run_all.py"
158
+ if not run_all.exists():
159
+ raise RuntimeError(f"{run_all} not found (check folder name: must be 'Down/')")
160
+
161
+ DOWN_GRAPHICS.mkdir(parents=True, exist_ok=True)
162
+ DOWN_RESULTS.mkdir(parents=True, exist_ok=True)
163
+
164
+ _clean_pngs(DOWN_GRAPHICS)
165
+
166
+ env = os.environ.copy()
167
+ env["TWOQUARKS_RESULTS_DIR"] = str(DOWN_RESULTS)
168
+ env["TWOQUARKS_GRAPHICS_DIR"] = str(DOWN_GRAPHICS)
169
+
170
+ cmd = [sys.executable, str(run_all), "--episodes", str(int(episodes))]
171
+
172
+ _run_capture(cmd, cwd=DOWN_ROOT, env=env)
173
+
174
+ images = _collect_pngs(DOWN_GRAPHICS)
175
+ meta = {
176
+ "status": "completed",
177
+ "pipeline": "DOWN + AntiDown",
178
+ "episodes_per_phase": int(episodes),
179
+ "graphics_count": len(images),
180
+ "graphics_dir": str(DOWN_GRAPHICS),
181
+ "results_dir": str(DOWN_RESULTS),
182
+ "csv_down": str(DOWN_RESULTS / "down_paradox_tabular_results.csv"),
183
+ "csv_antidown": str(DOWN_RESULTS / "antidown_corrupted_valley_tabular.csv"),
184
+ }
185
+ return meta, images
186
+
187
+
188
+ # ============================================================
189
+ # STRANGE (Unified: Strange + AntiStrange)
190
+ # ============================================================
191
+
192
+ def run_strange_all():
193
+ run_all = STRANGE_ROOT / "run_all.py"
194
+ if not run_all.exists():
195
+ raise RuntimeError(f"{run_all} not found (check folder name: must be 'Strange/')")
196
+
197
+ STRANGE_GRAPHICS.mkdir(parents=True, exist_ok=True)
198
+ STRANGE_RESULTS.mkdir(parents=True, exist_ok=True)
199
+
200
+ _clean_pngs(STRANGE_GRAPHICS)
201
+
202
+ env = os.environ.copy()
203
+ env["TWOQUARKS_RESULTS_DIR"] = str(STRANGE_RESULTS)
204
+ env["TWOQUARKS_GRAPHICS_DIR"] = str(STRANGE_GRAPHICS)
205
+
206
+ cmd = [sys.executable, str(run_all)]
207
+
208
+ _run_capture(cmd, cwd=STRANGE_ROOT, env=env)
209
+
210
+ images = _collect_pngs(STRANGE_GRAPHICS)
211
+ meta = {
212
+ "status": "completed",
213
+ "pipeline": "STRANGE + AntiStrange",
214
+ "graphics_count": len(images),
215
+ "graphics_dir": str(STRANGE_GRAPHICS),
216
+ "results_dir": str(STRANGE_RESULTS),
217
+ }
218
+ return meta, images
219
+
220
+
221
+ # ============================================================
222
+ # UI
223
+ # ============================================================
224
+
225
+ def build_ui():
226
+ with gr.Blocks(title="TwoQuarks — QuarksLab", css=CSS, js=JS) as demo:
227
+
228
+ # Inject DOM for canvas + nav
229
+ gr.HTML(
230
+ """
231
+ <canvas id="quantumField"></canvas>
232
+
233
+ <nav id="tqNav">
234
+ <div class="nav-inner">
235
+ <div class="brand">TwoQuarks</div>
236
+
237
+ <div class="nav-links">
238
+ <a href="#down">Down</a>
239
+ <a href="#strange">Strange</a>
240
+ </div>
241
+
242
+ <div class="spacer"></div>
243
+
244
+ <div class="menu-btn" id="tqMenuBtn" aria-label="menu">
245
+ <span></span>
246
+ </div>
247
+
248
+ <div class="menu" id="tqMenu">
249
+ <a href="#down">DOWN / AntiDown</a>
250
+ <div class="sep"></div>
251
+ <a href="#strange">STRANGE / AntiStrange</a>
252
+ </div>
253
+ </div>
254
+ </nav>
255
+ """
256
+ )
257
+
258
+ # App wrapper (needed for z-index + padding-top in your CSS)
259
+ with gr.Column(elem_id="tqApp"):
260
+
261
+ gr.Markdown(
262
+ """
263
+ <div class="tq-hero">
264
+ <div class="brand">TwoQuarks • QuarksLab</div>
265
+ <div class="desc"><b>Run Models.</b> Real artifacts. Real Models.</div>
266
+ </div>
267
+ """,
268
+ elem_id="top",
269
+ )
270
+
271
+ # ----------------------------
272
+ # DOWN
273
+ # ----------------------------
274
+ gr.HTML('<div id="down"></div>')
275
+
276
+ gr.Markdown("## DOWN / AntiDown")
277
+ gr.Markdown(
278
+ """
279
+ The Down / AntiDown models are reinforcement learning agents
280
+ designed to expose and analyze failure modes under deceptive reward conditions.
281
+ """
282
+ )
283
+
284
+ down_eps = gr.Slider(
285
+ minimum=50,
286
+ maximum=500,
287
+ step=50,
288
+ value=200,
289
+ label="Episodes per phase",
290
+ )
291
+
292
+ run_down_btn = gr.Button("Run DOWN Model", elem_classes=["tq-primary"])
293
+ down_meta = gr.JSON(label="Run status")
294
+
295
+ # elem_id is REQUIRED for carousel CSS
296
+ down_gallery = gr.Gallery(
297
+ label="Generated plots",
298
+ columns=2,
299
+ height="auto",
300
+ elem_id="down_gallery",
301
+ )
302
+
303
+ run_down_btn.click(
304
+ fn=run_down_all,
305
+ inputs=[down_eps],
306
+ outputs=[down_meta, down_gallery],
307
+ )
308
+
309
+ gr.Markdown("---")
310
+
311
+ # ----------------------------
312
+ # STRANGE
313
+ # ----------------------------
314
+ gr.HTML('<div id="strange"></div>')
315
+
316
+ gr.Markdown("## STRANGE / AntiStrange")
317
+ gr.Markdown(
318
+ """
319
+ Strange / AntiStrange are reinforcement learning agents
320
+ designed to study failure under hidden regime shifts and phase transitions.
321
+ """
322
+ )
323
+
324
+ run_strange_btn = gr.Button("Run STRANGE Model", elem_classes=["tq-primary"])
325
+ strange_meta = gr.JSON(label="Run status")
326
+
327
+ strange_gallery = gr.Gallery(
328
+ label="Generated plots",
329
+ columns=2,
330
+ height="auto",
331
+ elem_id="strange_gallery",
332
+ )
333
+
334
+ run_strange_btn.click(
335
+ fn=run_strange_all,
336
+ inputs=[],
337
+ outputs=[strange_meta, strange_gallery],
338
+ )
339
+
340
+ gr.Markdown(
341
+ """
342
+ ---
343
+ ### Notes:
344
+ - This lab, its models & the TwoQuarks website,
345
+ are developed end to end by Jaime Ledesma.
346
+ - Gradio does not import or orchestrate internal model logic.
347
+ - All visualizations are the result produced by the model.
348
+ """
349
+ )
350
+
351
+ return demo
352
+
353
+
354
+ # ============================================================
355
+ # Launch
356
+ # ============================================================
357
+
358
+ demo = build_ui()
359
+
360
+ if __name__ == "__main__":
361
+ demo.launch(
362
+ server_name="0.0.0.0",
363
+ server_port=int(os.getenv("PORT", "7860")),
364
+ show_api=False,
365
+ )