md896 commited on
Commit
029f9cf
·
1 Parent(s): 4724001

Ship polished Space UI with Gradio dashboard and evidence-rich demo.

Browse files

Add the upgraded Gradio experience, structured blog/reporting sections, and curated artifact images while keeping OpenEnv API endpoints intact.

Made-with: Cursor

requirements.txt CHANGED
@@ -5,4 +5,5 @@ openenv-core>=0.1.0
5
  openai>=2.0.0
6
  httpx>=0.27.0
7
  python-multipart==0.0.9
 
8
 
 
5
  openai>=2.0.0
6
  httpx>=0.27.0
7
  python-multipart==0.0.9
8
+ gradio>=4.44.0
9
 
server/demo_page.html ADDED
@@ -0,0 +1,900 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <meta name="color-scheme" content="light" />
7
+ <meta name="theme-color" content="#f6f7fb" />
8
+ <meta name="description" content="SQL Debug OpenEnv: architecture, live /reset and /step playground, and training evidence. Hugging Face Space." />
9
+ <meta property="og:title" content="SQL Debug Environment — Space Demo" />
10
+ <meta property="og:description" content="OpenEnv-compliant SQL debugging environment with live rewards, GRPO training hooks, and reproducible artifacts." />
11
+ <meta property="og:type" content="website" />
12
+ <title>SQL Debug Environment · Hugging Face Space</title>
13
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
15
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,500&family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
16
+ <style>
17
+ :root {
18
+ --space-bg: #f0f2f6;
19
+ --space-bg-elevated: #fafbfe;
20
+ --space-border: #e2e6ef;
21
+ --space-border-strong: #cdd5e5;
22
+ --ink: #111827;
23
+ --ink-soft: #374151;
24
+ --muted: #6b7280;
25
+ --muted-light: #9ca3af;
26
+ --card: #ffffff;
27
+ --card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 8px 28px rgba(16, 24, 40, 0.06);
28
+ --card-shadow-hover: 0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 36px rgba(16, 24, 40, 0.08);
29
+ --hf-amber: #f59e0b;
30
+ --hf-amber-soft: #fff7ed;
31
+ --accent: #2563eb;
32
+ --accent-soft: #eff6ff;
33
+ --diagram-bg: #0c1222;
34
+ --diagram-border: #1e293b;
35
+ --radius: 14px;
36
+ --radius-lg: 20px;
37
+ --font: "DM Sans", system-ui, -apple-system, sans-serif;
38
+ --font-display: "Fraunces", Georgia, serif;
39
+ --font-mono: "JetBrains Mono", ui-monospace, monospace;
40
+ --safe-top: env(safe-area-inset-top, 0px);
41
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
42
+ }
43
+ * { box-sizing: border-box; }
44
+ html {
45
+ scroll-behavior: smooth;
46
+ scroll-padding-top: 112px;
47
+ }
48
+ body {
49
+ margin: 0;
50
+ font-family: var(--font);
51
+ color: var(--ink);
52
+ background: var(--space-bg);
53
+ min-height: 100vh;
54
+ min-height: 100dvh;
55
+ line-height: 1.55;
56
+ -webkit-font-smoothing: antialiased;
57
+ }
58
+ a { color: var(--accent); }
59
+ a:focus-visible, button:focus-visible, select:focus-visible, textarea:focus-visible {
60
+ outline: 2px solid var(--accent);
61
+ outline-offset: 2px;
62
+ }
63
+ .space-shell {
64
+ min-height: 100vh;
65
+ min-height: 100dvh;
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+ .space-banner {
70
+ position: sticky;
71
+ top: 0;
72
+ z-index: 40;
73
+ padding: calc(10px + var(--safe-top)) 16px 10px;
74
+ background: linear-gradient(180deg, rgba(255,255,255,0.96) 0%, rgba(250,251,254,0.94) 100%);
75
+ backdrop-filter: blur(12px);
76
+ -webkit-backdrop-filter: blur(12px);
77
+ border-bottom: 1px solid var(--space-border);
78
+ box-shadow: 0 4px 24px rgba(15, 23, 42, 0.04);
79
+ }
80
+ .space-banner-inner {
81
+ max-width: 1120px;
82
+ margin: 0 auto;
83
+ display: flex;
84
+ flex-wrap: wrap;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ gap: 12px 20px;
88
+ }
89
+ .space-brand {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 12px;
93
+ flex: 1 1 auto;
94
+ min-width: 0;
95
+ }
96
+ .space-logo {
97
+ width: 38px;
98
+ height: 38px;
99
+ border-radius: 10px;
100
+ background: linear-gradient(135deg, #fbbf24, #f59e0b);
101
+ box-shadow: 0 2px 8px rgba(245, 158, 11, 0.35);
102
+ flex-shrink: 0;
103
+ }
104
+ .space-brand h1 {
105
+ margin: 0;
106
+ font-family: var(--font-display);
107
+ font-size: 1.05rem;
108
+ font-weight: 600;
109
+ letter-spacing: -0.02em;
110
+ color: var(--ink);
111
+ line-height: 1.2;
112
+ }
113
+ .space-brand p {
114
+ margin: 2px 0 0;
115
+ font-size: 0.75rem;
116
+ color: var(--muted);
117
+ font-weight: 500;
118
+ }
119
+ .space-actions {
120
+ display: flex;
121
+ flex-wrap: wrap;
122
+ align-items: center;
123
+ gap: 8px;
124
+ }
125
+ .btn-ghost {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ gap: 6px;
130
+ padding: 8px 14px;
131
+ font-size: 0.8125rem;
132
+ font-weight: 600;
133
+ font-family: inherit;
134
+ color: var(--ink-soft);
135
+ background: var(--card);
136
+ border: 1px solid var(--space-border-strong);
137
+ border-radius: 999px;
138
+ text-decoration: none;
139
+ cursor: pointer;
140
+ transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
141
+ }
142
+ .btn-ghost:hover {
143
+ border-color: var(--muted-light);
144
+ box-shadow: var(--card-shadow);
145
+ }
146
+ .btn-primary {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ gap: 6px;
151
+ padding: 8px 16px;
152
+ font-size: 0.8125rem;
153
+ font-weight: 700;
154
+ font-family: inherit;
155
+ color: #1c1917;
156
+ background: linear-gradient(180deg, #fde68a, #fbbf24);
157
+ border: 1px solid #d97706;
158
+ border-radius: 999px;
159
+ text-decoration: none;
160
+ cursor: pointer;
161
+ box-shadow: 0 1px 0 rgba(255,255,255,0.5) inset;
162
+ transition: filter 0.15s, transform 0.1s;
163
+ }
164
+ .btn-primary:hover { filter: brightness(1.03); }
165
+ .btn-primary:active { transform: scale(0.98); }
166
+ .sticky-nav {
167
+ position: sticky;
168
+ top: calc(58px + var(--safe-top));
169
+ z-index: 30;
170
+ margin: 0 auto;
171
+ max-width: 1120px;
172
+ padding: 0 16px 8px;
173
+ }
174
+ .sticky-nav-inner {
175
+ display: flex;
176
+ flex-wrap: wrap;
177
+ gap: 6px;
178
+ padding: 6px;
179
+ background: var(--card);
180
+ border: 1px solid var(--space-border);
181
+ border-radius: 999px;
182
+ box-shadow: var(--card-shadow);
183
+ width: fit-content;
184
+ max-width: 100%;
185
+ }
186
+ .sticky-nav a {
187
+ padding: 8px 14px;
188
+ font-size: 0.78rem;
189
+ font-weight: 600;
190
+ color: var(--muted);
191
+ text-decoration: none;
192
+ border-radius: 999px;
193
+ transition: background 0.15s, color 0.15s;
194
+ white-space: nowrap;
195
+ }
196
+ .sticky-nav a:hover {
197
+ color: var(--ink);
198
+ background: var(--space-bg);
199
+ }
200
+ .main {
201
+ flex: 1;
202
+ max-width: 1120px;
203
+ margin: 0 auto;
204
+ padding: 8px 16px calc(32px + var(--safe-bottom));
205
+ width: 100%;
206
+ }
207
+ .api-strip {
208
+ display: flex;
209
+ flex-wrap: wrap;
210
+ gap: 8px;
211
+ margin-bottom: 20px;
212
+ }
213
+ .api-chip {
214
+ font-family: var(--font-mono);
215
+ font-size: 0.68rem;
216
+ font-weight: 500;
217
+ padding: 5px 10px;
218
+ border-radius: 8px;
219
+ background: var(--card);
220
+ border: 1px solid var(--space-border);
221
+ color: var(--ink-soft);
222
+ }
223
+ .api-chip span { color: var(--muted); margin-right: 6px; }
224
+ .section {
225
+ margin-bottom: 28px;
226
+ }
227
+ .section-id {
228
+ font-size: 0.65rem;
229
+ font-weight: 700;
230
+ letter-spacing: 0.18em;
231
+ text-transform: uppercase;
232
+ color: var(--hf-amber);
233
+ margin-bottom: 8px;
234
+ }
235
+ .hero-title {
236
+ font-family: var(--font-display);
237
+ font-weight: 600;
238
+ font-size: clamp(1.75rem, 4.2vw, 2.5rem);
239
+ line-height: 1.12;
240
+ margin: 0 0 12px;
241
+ letter-spacing: -0.02em;
242
+ color: var(--ink);
243
+ }
244
+ .hero-title em {
245
+ font-style: italic;
246
+ color: var(--accent);
247
+ }
248
+ .lede {
249
+ max-width: 54ch;
250
+ color: var(--muted);
251
+ font-size: 1rem;
252
+ margin: 0 0 18px;
253
+ }
254
+ .layer-strip {
255
+ display: flex;
256
+ flex-wrap: wrap;
257
+ gap: 8px;
258
+ margin-bottom: 20px;
259
+ }
260
+ .layer {
261
+ font-size: 0.68rem;
262
+ font-weight: 700;
263
+ letter-spacing: 0.05em;
264
+ text-transform: uppercase;
265
+ padding: 6px 11px;
266
+ border-radius: 8px;
267
+ border: 1px solid var(--space-border);
268
+ background: var(--space-bg-elevated);
269
+ color: var(--muted);
270
+ }
271
+ .layer b { color: var(--ink); }
272
+ .panel {
273
+ background: var(--card);
274
+ border: 1px solid var(--space-border);
275
+ border-radius: var(--radius-lg);
276
+ padding: 20px;
277
+ box-shadow: var(--card-shadow);
278
+ margin-bottom: 20px;
279
+ transition: box-shadow 0.2s;
280
+ }
281
+ .panel:hover { box-shadow: var(--card-shadow-hover); }
282
+ .panel-header {
283
+ display: flex;
284
+ flex-wrap: wrap;
285
+ align-items: flex-start;
286
+ justify-content: space-between;
287
+ gap: 12px;
288
+ margin-bottom: 14px;
289
+ }
290
+ .panel-header h2 {
291
+ margin: 0;
292
+ font-size: 1.1rem;
293
+ font-weight: 700;
294
+ color: var(--ink);
295
+ }
296
+ .panel-header .caption {
297
+ margin: 0;
298
+ font-size: 0.8125rem;
299
+ color: var(--muted);
300
+ max-width: 38ch;
301
+ line-height: 1.45;
302
+ }
303
+ .diagram-wrap {
304
+ border-radius: var(--radius);
305
+ overflow: hidden;
306
+ background: var(--diagram-bg);
307
+ border: 1px solid var(--diagram-border);
308
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
309
+ }
310
+ .diagram-wrap img {
311
+ display: block;
312
+ width: 100%;
313
+ height: auto;
314
+ max-height: min(68vh, 820px);
315
+ object-fit: contain;
316
+ object-position: center top;
317
+ }
318
+ .figure-footer {
319
+ display: flex;
320
+ flex-wrap: wrap;
321
+ justify-content: space-between;
322
+ gap: 10px;
323
+ padding-top: 14px;
324
+ margin-top: 4px;
325
+ font-size: 0.75rem;
326
+ color: var(--muted);
327
+ }
328
+ .legend {
329
+ display: flex;
330
+ flex-wrap: wrap;
331
+ gap: 12px;
332
+ }
333
+ .legend span::before {
334
+ content: "";
335
+ display: inline-block;
336
+ width: 7px;
337
+ height: 7px;
338
+ border-radius: 2px;
339
+ margin-right: 5px;
340
+ vertical-align: middle;
341
+ }
342
+ .legend .l-api::before { background: #22c55e; }
343
+ .legend .l-env::before { background: #a78bfa; }
344
+ .legend .l-data::before { background: #fb923c; }
345
+ .legend .l-train::before { background: #2dd4bf; }
346
+ .badges {
347
+ display: flex;
348
+ flex-wrap: wrap;
349
+ gap: 8px;
350
+ }
351
+ .badge {
352
+ font-size: 0.65rem;
353
+ font-weight: 700;
354
+ letter-spacing: 0.05em;
355
+ text-transform: uppercase;
356
+ padding: 5px 10px;
357
+ border-radius: 999px;
358
+ border: 1px solid var(--space-border);
359
+ color: var(--muted);
360
+ background: var(--space-bg-elevated);
361
+ }
362
+ .section-head {
363
+ margin-bottom: 14px;
364
+ }
365
+ .section-head h2 {
366
+ margin: 0 0 6px;
367
+ font-family: var(--font-display);
368
+ font-size: 1.35rem;
369
+ font-weight: 600;
370
+ color: var(--ink);
371
+ }
372
+ .section-head p {
373
+ margin: 0;
374
+ color: var(--muted);
375
+ font-size: 0.9375rem;
376
+ }
377
+ .grid {
378
+ display: grid;
379
+ gap: 16px;
380
+ grid-template-columns: 1fr;
381
+ }
382
+ @media (min-width: 860px) {
383
+ .grid.cols-2 { grid-template-columns: 1fr 1fr; }
384
+ .grid.cols-12 { grid-template-columns: repeat(12, 1fr); }
385
+ .span-4 { grid-column: span 4; }
386
+ .span-8 { grid-column: span 8; }
387
+ }
388
+ .play-card {
389
+ background: var(--card);
390
+ border: 1px solid var(--space-border);
391
+ border-radius: var(--radius-lg);
392
+ padding: 20px;
393
+ box-shadow: var(--card-shadow);
394
+ }
395
+ label {
396
+ display: block;
397
+ font-size: 0.7rem;
398
+ font-weight: 700;
399
+ letter-spacing: 0.07em;
400
+ text-transform: uppercase;
401
+ color: var(--muted);
402
+ margin-top: 14px;
403
+ margin-bottom: 6px;
404
+ }
405
+ label:first-of-type { margin-top: 0; }
406
+ select, textarea {
407
+ width: 100%;
408
+ font-family: inherit;
409
+ font-size: 0.9375rem;
410
+ border-radius: 10px;
411
+ border: 1px solid var(--space-border-strong);
412
+ background: var(--space-bg-elevated);
413
+ color: var(--ink);
414
+ padding: 12px 14px;
415
+ transition: border-color 0.15s, box-shadow 0.15s;
416
+ }
417
+ select:focus, textarea:focus {
418
+ outline: none;
419
+ border-color: var(--accent);
420
+ box-shadow: 0 0 0 3px var(--accent-soft);
421
+ }
422
+ textarea {
423
+ min-height: 140px;
424
+ resize: vertical;
425
+ font-family: var(--font-mono);
426
+ font-size: 0.8125rem;
427
+ line-height: 1.5;
428
+ }
429
+ .btn-action {
430
+ margin-top: 12px;
431
+ width: 100%;
432
+ min-height: 46px;
433
+ font-family: inherit;
434
+ font-size: 0.9375rem;
435
+ font-weight: 700;
436
+ cursor: pointer;
437
+ border-radius: 10px;
438
+ border: none;
439
+ color: #fff;
440
+ background: linear-gradient(135deg, #2563eb, #4f46e5);
441
+ box-shadow: 0 4px 14px rgba(37, 99, 235, 0.35);
442
+ transition: opacity 0.15s, transform 0.1s;
443
+ }
444
+ .btn-action:hover:not(:disabled) { filter: brightness(1.05); }
445
+ .btn-action:active:not(:disabled) { transform: scale(0.99); }
446
+ .btn-action:disabled {
447
+ opacity: 0.55;
448
+ cursor: not-allowed;
449
+ }
450
+ .session-pill {
451
+ display: inline-flex;
452
+ align-items: center;
453
+ gap: 8px;
454
+ font-size: 0.75rem;
455
+ color: var(--muted);
456
+ margin-bottom: 10px;
457
+ padding: 6px 12px;
458
+ background: var(--accent-soft);
459
+ border-radius: 999px;
460
+ border: 1px solid #bfdbfe;
461
+ }
462
+ .session-pill strong {
463
+ color: var(--accent);
464
+ font-family: var(--font-mono);
465
+ font-weight: 500;
466
+ font-size: 0.72rem;
467
+ }
468
+ code.pre {
469
+ display: block;
470
+ white-space: pre-wrap;
471
+ font-family: var(--font-mono);
472
+ font-size: 0.72rem;
473
+ line-height: 1.5;
474
+ background: #f8fafc;
475
+ border: 1px solid var(--space-border);
476
+ border-radius: 10px;
477
+ padding: 12px 14px;
478
+ color: #1e293b;
479
+ min-height: 72px;
480
+ max-height: 260px;
481
+ overflow: auto;
482
+ }
483
+ .proof-grid {
484
+ display: grid;
485
+ gap: 16px;
486
+ grid-template-columns: 1fr;
487
+ }
488
+ @media (min-width: 720px) {
489
+ .proof-grid { grid-template-columns: 1fr 1fr; }
490
+ }
491
+ .proof-card {
492
+ border-radius: var(--radius);
493
+ overflow: hidden;
494
+ border: 1px solid var(--space-border);
495
+ background: var(--card);
496
+ box-shadow: var(--card-shadow);
497
+ }
498
+ .proof-card figcaption {
499
+ padding: 10px 14px;
500
+ font-size: 0.8125rem;
501
+ color: var(--muted);
502
+ border-top: 1px solid var(--space-border);
503
+ background: var(--space-bg-elevated);
504
+ }
505
+ .proof-card img {
506
+ display: block;
507
+ width: 100%;
508
+ height: auto;
509
+ }
510
+ .link-list a {
511
+ color: var(--accent);
512
+ text-decoration: none;
513
+ font-weight: 600;
514
+ display: block;
515
+ padding: 10px 0;
516
+ border-bottom: 1px solid var(--space-border);
517
+ font-size: 0.9rem;
518
+ }
519
+ .link-list a:last-child { border-bottom: 0; }
520
+ .link-list a:hover { text-decoration: underline; }
521
+ .space-footer {
522
+ margin-top: auto;
523
+ padding: 20px 16px calc(16px + var(--safe-bottom));
524
+ border-top: 1px solid var(--space-border);
525
+ background: linear-gradient(180deg, var(--space-bg-elevated), var(--space-bg));
526
+ }
527
+ .space-footer-inner {
528
+ max-width: 1120px;
529
+ margin: 0 auto;
530
+ display: flex;
531
+ flex-wrap: wrap;
532
+ align-items: center;
533
+ justify-content: space-between;
534
+ gap: 12px;
535
+ font-size: 0.8125rem;
536
+ color: var(--muted);
537
+ }
538
+ .space-footer a { color: var(--muted); font-weight: 600; }
539
+ .space-footer a:hover { color: var(--ink); }
540
+ .blog-quote {
541
+ border-left: 4px solid #2563eb;
542
+ background: #eff6ff;
543
+ color: #1e3a8a;
544
+ padding: 10px 12px;
545
+ border-radius: 8px;
546
+ font-size: 0.9rem;
547
+ margin: 0 0 12px;
548
+ }
549
+ .blog-mini-grid {
550
+ display: grid;
551
+ grid-template-columns: repeat(3, minmax(0, 1fr));
552
+ gap: 8px;
553
+ margin: 0 0 12px;
554
+ }
555
+ .blog-mini {
556
+ background: #f8fafc;
557
+ border: 1px solid var(--space-border);
558
+ border-radius: 10px;
559
+ padding: 10px;
560
+ font-size: 0.82rem;
561
+ color: var(--muted);
562
+ }
563
+ .blog-mini b { color: var(--ink); display:block; font-size:0.98rem; margin-bottom: 2px; }
564
+ @media (max-width: 900px) {
565
+ .blog-mini-grid { grid-template-columns: 1fr; }
566
+ }
567
+ </style>
568
+ </head>
569
+ <body>
570
+ <div class="space-shell">
571
+ <header class="space-banner">
572
+ <div class="space-banner-inner">
573
+ <div class="space-brand">
574
+ <div class="space-logo" aria-hidden="true"></div>
575
+ <div>
576
+ <h1>SQL Debug Environment</h1>
577
+ <p>OpenEnv · FastAPI · Live SQL rewards</p>
578
+ </div>
579
+ </div>
580
+ <div class="space-actions">
581
+ <a class="btn-primary" href="/">Gradio UI</a>
582
+ <button type="button" class="btn-ghost" id="btnOpenTab" title="Opens this demo in a full browser tab">Open full page</button>
583
+ <a class="btn-ghost" href="https://huggingface.co/spaces/md896/sql-debug-env" target="_blank" rel="noopener">Space on Hub ↗</a>
584
+ </div>
585
+ </div>
586
+ </header>
587
+
588
+ <nav class="sticky-nav" aria-label="On-page navigation">
589
+ <div class="sticky-nav-inner">
590
+ <a href="#environment">Environment</a>
591
+ <a href="#first-training">First Training</a>
592
+ <a href="#playground">Playground</a>
593
+ <a href="#evidence">Evidence</a>
594
+ <a href="#repro">Reproduce</a>
595
+ <a href="/">Gradio</a>
596
+ </div>
597
+ </nav>
598
+
599
+ <main class="main">
600
+ <div class="api-strip" aria-label="Key API endpoints">
601
+ <span class="api-chip"><span>GET</span>/health</span>
602
+ <span class="api-chip"><span>GET</span>/tasks</span>
603
+ <span class="api-chip"><span>POST</span>/reset</span>
604
+ <span class="api-chip"><span>POST</span>/step</span>
605
+ <span class="api-chip"><span>POST</span>/step_with_review</span>
606
+ <span class="api-chip"><span>GET</span>/benchmark</span>
607
+ </div>
608
+
609
+ <section id="environment" class="section" aria-labelledby="env-title">
610
+ <p class="section-id">Space · Architecture</p>
611
+ <h2 class="hero-title" id="env-title">Environment first — <em>how</em> the agent sees the world.</h2>
612
+ <p class="lede">
613
+ This Space hosts the same HTTP API your trainer calls: sessions, typed observations, SQLite-backed tasks, and a decomposed reward. Below is the end-to-end map judges can skim in seconds.
614
+ </p>
615
+ <div class="layer-strip" aria-hidden="true">
616
+ <span class="layer"><b>Client</b> / agent</span>
617
+ <span class="layer"><b>API</b> session + JSON</span>
618
+ <span class="layer"><b>Env</b> SQLDebugEnv</span>
619
+ <span class="layer"><b>Data</b> tasks + SQLite</span>
620
+ <span class="layer"><b>Train</b> GRPO + artifacts</span>
621
+ </div>
622
+
623
+ <div class="panel">
624
+ <div class="panel-header">
625
+ <h2>Environment visualization</h2>
626
+ <p class="caption">Runtime flow (solid) vs training and ops (dashed). Reviewer-guarded path optional for safer rollouts.</p>
627
+ </div>
628
+ <div class="diagram-wrap">
629
+ <img src="/static/environment-workflow.png" alt="End-to-end workflow: Client, FastAPI, environment core, data and reward layer, training and deployment." width="1600" height="900" loading="eager" decoding="async" />
630
+ </div>
631
+ <div class="figure-footer">
632
+ <div class="legend">
633
+ <span class="l-api">API</span>
634
+ <span class="l-env">Env core</span>
635
+ <span class="l-data">DB / tasks / reward</span>
636
+ <span class="l-train">Training &amp; Space</span>
637
+ </div>
638
+ <span>sql-debug-env workflow</span>
639
+ </div>
640
+ </div>
641
+ <div class="badges">
642
+ <span class="badge">OpenEnv</span>
643
+ <span class="badge">TRL · GRPO</span>
644
+ <span class="badge">Live rewards</span>
645
+ <span class="badge">Reviewer path</span>
646
+ </div>
647
+ </section>
648
+
649
+ <section id="first-training" class="section" aria-labelledby="first-training-title">
650
+ <div class="section-head">
651
+ <p class="section-id">Training · First Context</p>
652
+ <h2 id="first-training-title">Start with the first bridge run</h2>
653
+ <p>This is the exact first training context you shared: dependency bootstrap, W&amp;B tracking, then benchmark/eval steps.</p>
654
+ </div>
655
+ <div class="grid cols-12">
656
+ <div class="play-card span-4">
657
+ <div class="link-list">
658
+ <a href="https://colab.research.google.com/drive/1H6SLfCBhHzRJtnymLgevjfyytWUximF5#scrollTo=j-9MptXvmPk8" target="_blank" rel="noopener">First training context (Colab anchor)</a>
659
+ <a href="https://colab.research.google.com/drive/1H6SLfCBhHzRJtnymLgevjfyytWUximF5#scrollTo=x5YuvatGyyu_" target="_blank" rel="noopener">Full training notebook anchor</a>
660
+ <a href="https://wandb.ai/mdayanbag-pesitm/sql-debug-grpo-best-budget/workspace?nw=nwusermdayanbag" target="_blank" rel="noopener">W&amp;B workspace: sql-debug-grpo-best-budget</a>
661
+ <a href="https://huggingface.co/spaces/md896/sql-debug-env/tree/main/artifacts/runs/20260426-064318-sample-rewards-32eval" target="_blank" rel="noopener">Sample rewards (32-eval) artifacts</a>
662
+ <a href="https://huggingface.co/md896/sql-debug-agent-qwen25-05b-grpo-wandb-continue-v2" target="_blank" rel="noopener">Model card (winner)</a>
663
+ </div>
664
+ </div>
665
+ <div class="play-card span-8">
666
+ <label>First training context code</label>
667
+ <code class="pre"># SQL Debug Env: FINAL REAL-WORLD BRIDGE
668
+ import os
669
+ print("Checking libraries...")
670
+ os.system("pip install trl accelerate wandb -U")
671
+
672
+ import httpx
673
+ import torch
674
+ import wandb
675
+ # W&B workspace: https://wandb.ai/mdayanbag-pesitm/sql-debug-grpo-best-budget/workspace?nw=nwusermdayanbag</code>
676
+ </div>
677
+ </div>
678
+ </section>
679
+
680
+ <section id="playground" class="section" aria-labelledby="play-title">
681
+ <div class="section-head">
682
+ <p class="section-id">Live · Playground</p>
683
+ <h2 id="play-title">Try <code style="font-family:var(--font-mono);font-size:0.85em;background:#f1f5f9;padding:2px 6px;border-radius:4px">/reset</code> and <code style="font-family:var(--font-mono);font-size:0.85em;background:#f1f5f9;padding:2px 6px;border-radius:4px">/step</code> from the browser</h2>
684
+ <p>Use the same <strong>X-Session-Id</strong> header on every call (here: <code style="font-family:var(--font-mono);font-size:0.85em">demo-session</code>).</p>
685
+ </div>
686
+ <div class="grid cols-2">
687
+ <div class="play-card">
688
+ <label for="taskId">Task</label>
689
+ <select id="taskId" aria-label="Select task">
690
+ <option value="easy_syntax_fix">easy_syntax_fix</option>
691
+ <option value="medium_logic_fix">medium_logic_fix</option>
692
+ <option value="hard_multi_bug">hard_multi_bug</option>
693
+ <option value="hard_finance_explosion">hard_finance_explosion</option>
694
+ </select>
695
+ <button type="button" class="btn-action" id="btnReset" onclick="resetTask()">Reset task</button>
696
+
697
+ <label for="query">Candidate SQL</label>
698
+ <textarea id="query" placeholder="SELECT ..." aria-label="SQL query"></textarea>
699
+ <button type="button" class="btn-action" id="btnSubmit" onclick="submitQuery()">Submit query</button>
700
+ </div>
701
+ <div class="play-card">
702
+ <div class="session-pill">Session <strong>demo-session</strong></div>
703
+ <label>Task observation</label>
704
+ <code id="observation" class="pre">Run “Reset task” to load the broken query and observation JSON.</code>
705
+ <label style="margin-top:14px">Step result</label>
706
+ <code id="result" class="pre">Submit a query to see reward, done, and info.</code>
707
+ </div>
708
+ </div>
709
+ </section>
710
+
711
+ <section id="evidence" class="section" aria-labelledby="evidence-title">
712
+ <div class="section-head">
713
+ <p class="section-id">Evidence · Artifacts</p>
714
+ <h2 id="evidence-title">Training plots from real runs</h2>
715
+ <p>Regenerate with <code style="font-family:var(--font-mono);font-size:0.85em">presentation_graphs.py</code>; commit PNGs under <code style="font-family:var(--font-mono);font-size:0.85em">server/static/</code>.</p>
716
+ </div>
717
+ <div class="proof-grid">
718
+ <figure class="proof-card">
719
+ <img src="/static/proof-combo.png" alt="Presentation combo chart from training run" width="1200" height="800" loading="lazy" decoding="async" />
720
+ <figcaption>Presentation combo — logged metrics.</figcaption>
721
+ </figure>
722
+ <figure class="proof-card">
723
+ <img src="/static/proof-distribution-shift.png" alt="Reward distribution shift" width="1200" height="800" loading="lazy" decoding="async" />
724
+ <figcaption>Per-sample reward shift (baseline vs trained).</figcaption>
725
+ </figure>
726
+ </div>
727
+ <div class="link-list" style="margin-top:12px">
728
+ <a href="/static/training_reward_curve_final.png" target="_blank" rel="noopener">training_reward_curve_final.png</a>
729
+ <a href="/static/training_diagnostics_dual_axis_final.png" target="_blank" rel="noopener">training_diagnostics_dual_axis_final.png</a>
730
+ <a href="/static/baseline_vs_trained_by_task_final.png" target="_blank" rel="noopener">baseline_vs_trained_by_task_final.png</a>
731
+ <a href="/static/task_delta_post_minus_base_final.png" target="_blank" rel="noopener">task_delta_post_minus_base_final.png</a>
732
+ <a href="/static/reward_distribution_shift_red_green_final.png" target="_blank" rel="noopener">reward_distribution_shift_red_green_final.png</a>
733
+ <a href="/static/presentation_combo_final.png" target="_blank" rel="noopener">presentation_combo_final.png</a>
734
+ <a href="/static/benchmark_style_summary_final.png" target="_blank" rel="noopener">benchmark_style_summary_final.png</a>
735
+ <a href="/static/checkpoint_leaderboard_step_vs_reward_final.png" target="_blank" rel="noopener">checkpoint_leaderboard_step_vs_reward_final.png</a>
736
+ <a href="/static/cost_vs_performance_final.png" target="_blank" rel="noopener">cost_vs_performance_final.png</a>
737
+ </div>
738
+ </section>
739
+
740
+ <section id="repro" class="section">
741
+ <div class="grid cols-12">
742
+ <div class="play-card span-4">
743
+ <div class="section-head" style="margin-bottom:10px">
744
+ <p class="section-id">Reproduce</p>
745
+ <h2 style="font-family:var(--font-display);font-size:1.15rem;margin:0;font-weight:600">Runs &amp; assets</h2>
746
+ </div>
747
+ <div class="link-list">
748
+ <a href="https://colab.research.google.com/drive/1H6SLfCBhHzRJtnymLgevjfyytWUximF5#scrollTo=x5YuvatGyyu_" target="_blank" rel="noopener">Colab training notebook</a>
749
+ <a href="https://huggingface.co/spaces/md896/sql-debug-env/tree/main/artifacts/runs/20260426-060502-final-pass-32eval" target="_blank" rel="noopener">Eval artifacts (32-run)</a>
750
+ <a href="https://huggingface.co/md896/sql-debug-agent-qwen25-05b-grpo-wandb-continue-v2" target="_blank" rel="noopener">Model card</a>
751
+ <a href="/benchmark" target="_blank" rel="noopener">Benchmark JSON</a>
752
+ <a href="/health" target="_blank" rel="noopener">Health</a>
753
+ </div>
754
+ </div>
755
+ <div class="play-card span-8">
756
+ <div class="section-head" style="margin-bottom:10px">
757
+ <p class="section-id">Engineering Notes</p>
758
+ <h2 style="font-family:var(--font-display);font-size:1.15rem;margin:0;font-weight:600">Why I picked SQL debugging and why this architecture exists</h2>
759
+ </div>
760
+ <div class="blog-quote">
761
+ “The goal is not to generate beautiful SQL text. The goal is to produce SQL fixes that survive execution, repeatedly, under changing runtime conditions.”
762
+ </div>
763
+ <div class="blog-mini-grid">
764
+ <div class="blog-mini"><b>0.5B -> 7B</b>Model track from first bridge run to main baseline.</div>
765
+ <div class="blog-mini"><b>32-run eval</b>Final artifact path with sample rewards and run logs.</div>
766
+ <div class="blog-mini"><b>Execution-first</b>Reward is computed from runtime outcomes, not prompt resemblance.</div>
767
+ </div>
768
+ <p style="color:var(--muted);margin:0 0 12px;font-size:0.9375rem">
769
+ The motive for this project was not to build another text-to-SQL demo. The motive was reliability. SQL bugs are expensive because they fail late:
770
+ queries can look clean in review but break under real schema constraints, data skew, or join cardinality shifts. I picked this problem because it sits at the
771
+ boundary between language modeling and systems engineering. If the agent improves here, it is learning runtime correctness, not cosmetic fluency.
772
+ </p>
773
+ <p style="color:var(--muted);margin:0 0 12px;font-size:0.9375rem">
774
+ The architecture follows an OpenEnv-style contract:
775
+ <code>reset -&gt; observation</code> and <code>step(action) -&gt; observation, reward, done, info</code>.
776
+ Each episode runs on isolated in-memory SQLite state, deterministic task grading, and execution-grounded rewards. This pushes the model toward behaviors that survive runtime:
777
+ valid table references, stable aggregations, and join logic that does not collapse in edge cases.
778
+ </p>
779
+ <code class="pre">Conceptual reward:
780
+ R_t = w_c*C_t + w_e*E_t + w_p*P_t + w_s*S_t - lambda*Penalty_t
781
+
782
+ Objective:
783
+ J(pi) = E_{tau ~ pi}[sum_{t=0..T} gamma^t * R_t]</code>
784
+ <p style="color:var(--muted);margin:0 0 12px;font-size:0.9375rem">
785
+ The technical design makes debugging measurable. Session state exposes observations, action history, and reward trajectories.
786
+ The reviewer-gated path adds risk control for unsafe submissions while preserving gradient signal (instead of hard-failing every risky step).
787
+ This gives the policy useful consequences: what failed, why it failed, and how far a candidate moved toward a valid fix.
788
+ </p>
789
+ <code class="pre">Data snapshot shown on this page:
790
+ - Spider-style industry baseline: 48.2%
791
+ - Qwen-7B base: 52.4%
792
+ - RL agent headline: 78.5%
793
+ - Performance leap view: 0.0% -> 25.0%
794
+ - Hard evidence: 32-run eval + sample reward artifacts</code>
795
+ <p style="color:var(--muted);margin:12px 0 12px;font-size:0.9375rem">
796
+ Another deliberate choice is traceability. This page is an evidence chain: first training context, live interaction, then artifact-backed plots.
797
+ If a metric appears, it should map to concrete run folders, reward JSON files, and checkpoint lineage.
798
+ </p>
799
+ <p style="color:var(--muted);margin:0 0 12px;font-size:0.9375rem">
800
+ Industry and research point the same direction: robust text-to-SQL requires context quality, intent handling, dialect robustness, and execution safeguards.
801
+ Enterprise SQL debugging remains difficult when feedback is detached from runtime behavior. The objective here is to close that gap with a reproducible,
802
+ execution-grounded learning loop.
803
+ </p>
804
+ <div class="link-list" style="margin-top:12px">
805
+ <a href="https://cloud.google.com/blog/products/databases/techniques-for-improving-text-to-sql" target="_blank" rel="noopener">Google Cloud: techniques for improving text-to-SQL</a>
806
+ <a href="https://arxiv.org/abs/2601.18119" target="_blank" rel="noopener">OurBench / Squirrel: enterprise SQL debugging benchmark</a>
807
+ <a href="https://karpathy.github.io/2014/09/02/what-i-learned-from-competing-against-a-convnet-on-imagenet/" target="_blank" rel="noopener">Writing inspiration: Karpathy field-notes style</a>
808
+ <a href="#" target="_blank" rel="noopener">Final public blog URL (replace)</a>
809
+ <a href="#" target="_blank" rel="noopener">Slides (URL)</a>
810
+ <a href="#" target="_blank" rel="noopener">Demo video (URL)</a>
811
+ </div>
812
+ </div>
813
+ </div>
814
+ </section>
815
+ </main>
816
+
817
+ <footer class="space-footer">
818
+ <div class="space-footer-inner">
819
+ <span>Custom Space UI · FastAPI <code style="font-family:var(--font-mono);font-size:0.75em">/demo</code></span>
820
+ <span>
821
+ <a href="https://huggingface.co/docs/hub/spaces" target="_blank" rel="noopener">Spaces docs</a>
822
+ ·
823
+ <a href="https://huggingface.co/spaces/md896/sql-debug-env/tree/main" target="_blank" rel="noopener">Files &amp; versions</a>
824
+ </span>
825
+ </div>
826
+ </footer>
827
+ </div>
828
+
829
+ <script>
830
+ (function () {
831
+ var btn = document.getElementById("btnOpenTab");
832
+ if (btn) {
833
+ btn.addEventListener("click", function () {
834
+ try {
835
+ window.open(window.location.href, "_blank", "noopener,noreferrer");
836
+ } catch (e) {
837
+ window.location.href = window.location.href;
838
+ }
839
+ });
840
+ }
841
+ })();
842
+
843
+ const sessionId = "demo-session";
844
+
845
+ function setLoading(which, on) {
846
+ var el = document.getElementById(which);
847
+ if (!el) return;
848
+ el.disabled = on;
849
+ if (on && !el.dataset.label) el.dataset.label = el.textContent;
850
+ el.textContent = on ? "Please wait…" : (el.dataset.label || el.textContent);
851
+ }
852
+
853
+ async function resetTask() {
854
+ setLoading("btnReset", true);
855
+ try {
856
+ const taskId = document.getElementById("taskId").value;
857
+ const resp = await fetch("/reset", {
858
+ method: "POST",
859
+ headers: {
860
+ "Content-Type": "application/json",
861
+ "X-Session-Id": sessionId
862
+ },
863
+ body: JSON.stringify({ task_id: taskId })
864
+ });
865
+ const data = await resp.json();
866
+ document.getElementById("observation").textContent = JSON.stringify(data, null, 2);
867
+ const broken = data && data.observation && data.observation.original_query;
868
+ document.getElementById("query").value = broken || "";
869
+ } finally {
870
+ setLoading("btnReset", false);
871
+ }
872
+ }
873
+
874
+ async function submitQuery() {
875
+ setLoading("btnSubmit", true);
876
+ try {
877
+ const query = document.getElementById("query").value;
878
+ const payload = {
879
+ action: {
880
+ action_type: "submit_query",
881
+ query: query
882
+ }
883
+ };
884
+ const resp = await fetch("/step", {
885
+ method: "POST",
886
+ headers: {
887
+ "Content-Type": "application/json",
888
+ "X-Session-Id": sessionId
889
+ },
890
+ body: JSON.stringify(payload)
891
+ });
892
+ const data = await resp.json();
893
+ document.getElementById("result").textContent = JSON.stringify(data, null, 2);
894
+ } finally {
895
+ setLoading("btnSubmit", false);
896
+ }
897
+ }
898
+ </script>
899
+ </body>
900
+ </html>
server/gradio_ui.py ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Single-page Gradio UI for the Hugging Face Space (same process as the OpenEnv FastAPI API).
3
+
4
+ Playground uses POST /reset and POST /step via loopback HTTP with X-Session-Id.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import os
10
+ import uuid
11
+ from pathlib import Path
12
+ from typing import Any, Optional, Tuple
13
+
14
+ import httpx
15
+
16
+ COLAB_FIRST_TRAINING = (
17
+ "https://colab.research.google.com/drive/1H6SLfCBhHzRJtnymLgevjfyytWUximF5"
18
+ "#scrollTo=j-9MptXvmPk8"
19
+ )
20
+ COLAB_TRAINING_ROOT = (
21
+ "https://colab.research.google.com/drive/1H6SLfCBhHzRJtnymLgevjfyytWUximF5"
22
+ "#scrollTo=x5YuvatGyyu_"
23
+ )
24
+ HF_SPACE = "https://huggingface.co/spaces/md896/sql-debug-env"
25
+ HF_SAMPLE_REWARDS = (
26
+ "https://huggingface.co/spaces/md896/sql-debug-env/tree/main/"
27
+ "artifacts/runs/20260426-064318-sample-rewards-32eval"
28
+ )
29
+ HF_EVAL_32 = (
30
+ "https://huggingface.co/spaces/md896/sql-debug-env/tree/main/"
31
+ "artifacts/runs/20260426-060502-final-pass-32eval"
32
+ )
33
+ HF_MODEL = "https://huggingface.co/md896/sql-debug-agent-qwen25-05b-grpo-wandb-continue-v2"
34
+ WANDB_TRAINING_RUN = "https://wandb.ai/mdayanbag-pesitm/sql-debug-grpo-best-budget/workspace?nw=nwusermdayanbag"
35
+ GCLOUD_TEXT2SQL_BLOG = "https://cloud.google.com/blog/products/databases/techniques-for-improving-text-to-sql"
36
+ OURBENCH_PAPER = "https://arxiv.org/abs/2601.18119"
37
+ KARPATHY_STYLE_REFERENCE = "https://karpathy.github.io/2014/09/02/what-i-learned-from-competing-against-a-convnet-on-imagenet/"
38
+
39
+ PREDEFINED_QUERIES: dict[str, list[tuple[str, str]]] = {
40
+ "easy_syntax_fix": [
41
+ ("Broken baseline: typo table", "SELECT * FROM userss;"),
42
+ ("Simple lookup", "SELECT id, name FROM users ORDER BY id LIMIT 10;"),
43
+ ("Potential invalid write", "UPDATE users SET name='test';"),
44
+ ],
45
+ "medium_logic_fix": [
46
+ ("Broken: missing GROUP BY", "SELECT department, COUNT(*) FROM employees;"),
47
+ ("Revenue by month", "SELECT strftime('%Y-%m', order_date) AS ym, SUM(amount) FROM orders GROUP BY ym ORDER BY ym;"),
48
+ ("Top entities", "SELECT customer_id, SUM(total) AS spend FROM invoices GROUP BY customer_id ORDER BY spend DESC LIMIT 5;"),
49
+ ],
50
+ "hard_multi_bug": [
51
+ ("Broken join alias", "SELECT u.name, o.total FROM users u JOIN orders o ON user.id = o.user_id;"),
52
+ ("Join + aggregate", "SELECT p.category, AVG(p.price) AS avg_price FROM products p GROUP BY p.category ORDER BY avg_price DESC;"),
53
+ ("Nested query", "SELECT name FROM customers WHERE id IN (SELECT customer_id FROM orders GROUP BY customer_id HAVING COUNT(*) > 2);"),
54
+ ],
55
+ "hard_finance_explosion": [
56
+ ("Broken finance calc", "SELECT account_id, SUM(amount) / COUNT(*) AS risk FROM txn GROUP BY account;"),
57
+ ("PnL-style aggregate", "SELECT symbol, SUM(CASE WHEN side='BUY' THEN -notional ELSE notional END) AS pnl FROM trades GROUP BY symbol ORDER BY pnl DESC;"),
58
+ ("Daily exposure", "SELECT date(trade_ts) AS d, SUM(abs(notional)) AS exposure FROM trades GROUP BY d ORDER BY d;"),
59
+ ],
60
+ }
61
+
62
+ GRADIO_CSS = """
63
+ :root {
64
+ --sde-ink: #0f172a;
65
+ --sde-muted: #64748b;
66
+ --sde-line: #e2e8f0;
67
+ --sde-card: #ffffff;
68
+ --sde-glow:
69
+ radial-gradient(120% 140% at 0% 0%, rgba(45, 212, 191, 0.22) 0%, rgba(45, 212, 191, 0) 52%),
70
+ radial-gradient(120% 140% at 100% 0%, rgba(147, 197, 253, 0.22) 0%, rgba(147, 197, 253, 0) 55%),
71
+ linear-gradient(132deg, #0f172a 0%, #1e293b 45%, #0f766e 100%);
72
+ }
73
+ .gradio-container { max-width: 1180px !important; margin-left: auto !important; margin-right: auto !important; }
74
+ .sde-hero-wrap {
75
+ background: var(--sde-glow);
76
+ color: #f8fafc;
77
+ border-radius: 20px;
78
+ padding: 1.75rem 1.5rem 1.5rem;
79
+ margin-bottom: 1.25rem;
80
+ border: 1px solid rgba(148, 163, 184, 0.24);
81
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.20), inset 0 1px 0 rgba(255, 255, 255, 0.12);
82
+ }
83
+ .sde-hero-wrap h1 { margin: 0 0 0.35rem 0; font-size: 1.85rem; letter-spacing: -0.03em; }
84
+ .sde-hero-wrap p { margin: 0; color: #e2e8f0; font-size: 0.95rem; line-height: 1.5; }
85
+ .sde-pill-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; }
86
+ .sde-pill {
87
+ display: inline-block;
88
+ padding: 0.35rem 0.75rem;
89
+ border-radius: 999px;
90
+ font-size: 0.72rem;
91
+ font-weight: 700;
92
+ letter-spacing: 0.06em;
93
+ text-transform: uppercase;
94
+ background: rgba(15, 23, 42, 0.26);
95
+ border: 1px solid rgba(226, 232, 240, 0.34);
96
+ color: #f8fafc;
97
+ }
98
+ .sde-section-title {
99
+ font-size: 1.05rem;
100
+ font-weight: 700;
101
+ color: var(--sde-ink);
102
+ margin: 1.5rem 0 0.75rem 0;
103
+ letter-spacing: -0.02em;
104
+ }
105
+ .sde-link-row a {
106
+ color: #2563eb !important;
107
+ font-weight: 600;
108
+ margin-right: 1rem;
109
+ }
110
+ .sde-kpi-grid {
111
+ display: grid;
112
+ grid-template-columns: repeat(4, minmax(0, 1fr));
113
+ gap: 0.75rem;
114
+ margin: 0.5rem 0 1rem;
115
+ }
116
+ .sde-kpi {
117
+ background: #ffffff;
118
+ border: 1px solid #dbe3f0;
119
+ border-radius: 14px;
120
+ padding: 0.85rem 0.95rem;
121
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
122
+ }
123
+ .sde-kpi .v {
124
+ font-size: 1.25rem;
125
+ font-weight: 800;
126
+ letter-spacing: -0.02em;
127
+ color: #0f172a;
128
+ }
129
+ .sde-kpi .k {
130
+ margin-top: 0.15rem;
131
+ font-size: 0.73rem;
132
+ text-transform: uppercase;
133
+ letter-spacing: 0.06em;
134
+ color: #64748b;
135
+ }
136
+ .sde-callout {
137
+ border-left: 4px solid #2563eb;
138
+ background: #eff6ff;
139
+ color: #1e3a8a;
140
+ padding: 0.7rem 0.8rem;
141
+ border-radius: 8px;
142
+ margin: 0.5rem 0 0.75rem;
143
+ font-size: 0.86rem;
144
+ }
145
+ @media (max-width: 900px) {
146
+ .sde-kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
147
+ }
148
+ """
149
+
150
+
151
+ def _api_base() -> str:
152
+ return os.environ.get(
153
+ "INTERNAL_API_BASE",
154
+ f"http://127.0.0.1:{os.environ.get('PORT', '7860')}",
155
+ ).rstrip("/")
156
+
157
+
158
+ def _blog_url() -> str:
159
+ return (os.environ.get("BLOG_URL") or "").strip()
160
+
161
+
162
+ def _http() -> httpx.Client:
163
+ return httpx.Client(timeout=120.0)
164
+
165
+
166
+ def _img_path(static_dir: Path, *names: str) -> Optional[str]:
167
+ for n in names:
168
+ p = static_dir / n
169
+ if p.is_file():
170
+ return str(p.resolve())
171
+ return None
172
+
173
+
174
+ def _preset_options(task_id: str) -> list[str]:
175
+ return [name for name, _ in PREDEFINED_QUERIES.get(task_id, [])]
176
+
177
+
178
+ def _preset_query(task_id: str, preset_name: str) -> str:
179
+ for name, query in PREDEFINED_QUERIES.get(task_id, []):
180
+ if name == preset_name:
181
+ return query
182
+ return ""
183
+
184
+
185
+ def _safe_reward(value: Any) -> float:
186
+ try:
187
+ return float(value)
188
+ except Exception:
189
+ return 0.0
190
+
191
+
192
+ def build_blocks(static_dir: Path) -> Any:
193
+ import gradio as gr
194
+
195
+ wf = _img_path(static_dir, "environment-workflow.png")
196
+ chart_leap = _img_path(static_dir, "chart-performance-leap.png", "hero_performance_leap.png")
197
+ chart_dual = _img_path(static_dir, "chart-comparison-shift.png", "hero_dual_benchmark.png")
198
+ chart_spider = _img_path(static_dir, "chart-spider-benchmark.png", "hero_spider_sota.png")
199
+ proof_combo = _img_path(static_dir, "proof-combo.png")
200
+ proof_dist = _img_path(static_dir, "proof-distribution-shift.png")
201
+ final_gallery_paths = [
202
+ "training_reward_curve_final.png",
203
+ "training_diagnostics_dual_axis_final.png",
204
+ "baseline_vs_trained_by_task_final.png",
205
+ "task_delta_post_minus_base_final.png",
206
+ "reward_distribution_shift_red_green_final.png",
207
+ "presentation_combo_final.png",
208
+ "benchmark_style_summary_final.png",
209
+ "checkpoint_leaderboard_step_vs_reward_final.png",
210
+ "cost_vs_performance_final.png",
211
+ ]
212
+ final_gallery: list[tuple[str, str]] = []
213
+ for filename in final_gallery_paths:
214
+ path = _img_path(static_dir, filename)
215
+ if path:
216
+ title = filename.replace("_final.png", "").replace("_", " ").title()
217
+ final_gallery.append((path, title))
218
+
219
+ blog = _blog_url()
220
+ blog_md = (
221
+ f"### Blog\n[Read the write-up]({blog})"
222
+ if blog
223
+ else "### Blog\nAdd a **Space secret** named `BLOG_URL` with your post URL (e.g. Medium, personal site, or Hugging Face blog)."
224
+ )
225
+
226
+ task_choices = [
227
+ "easy_syntax_fix",
228
+ "medium_logic_fix",
229
+ "hard_multi_bug",
230
+ "hard_finance_explosion",
231
+ ]
232
+
233
+ def reset_fn(
234
+ task_id: str, session_id: Optional[str]
235
+ ) -> Tuple[str, str, str, str]:
236
+ sid = session_id or str(uuid.uuid4())
237
+ try:
238
+ with _http() as client:
239
+ r = client.post(
240
+ f"{_api_base()}/reset",
241
+ json={"task_id": task_id},
242
+ headers={"X-Session-Id": sid},
243
+ )
244
+ r.raise_for_status()
245
+ data = r.json()
246
+ except Exception as e:
247
+ err = {"error": str(e), "hint": "Is the server listening on PORT?"}
248
+ return json.dumps(err, indent=2), "", sid, f"Session: `{sid}` · **error**"
249
+ obs = json.dumps(data, indent=2)
250
+ q = (data.get("observation") or {}).get("original_query") or ""
251
+ return obs, q, sid, f"Session: `{sid}`"
252
+
253
+ def submit_fn(
254
+ query: str, session_id: Optional[str]
255
+ ) -> Tuple[str, str]:
256
+ if not session_id:
257
+ return (
258
+ json.dumps({"error": "Click “Reset task” first to create a session."}, indent=2),
259
+ "",
260
+ )
261
+ payload = {"action": {"action_type": "submit_query", "query": query or ""}}
262
+ try:
263
+ with _http() as client:
264
+ r = client.post(
265
+ f"{_api_base()}/step",
266
+ json=payload,
267
+ headers={"X-Session-Id": session_id},
268
+ )
269
+ r.raise_for_status()
270
+ data = r.json()
271
+ except httpx.HTTPStatusError as e:
272
+ try:
273
+ detail = e.response.json()
274
+ except Exception:
275
+ detail = e.response.text
276
+ return json.dumps({"error": str(e), "detail": detail}, indent=2), ""
277
+ except Exception as e:
278
+ return json.dumps({"error": str(e)}, indent=2), ""
279
+ out = json.dumps(data, indent=2)
280
+ reward = data.get("reward")
281
+ done = data.get("done")
282
+ return out, f"**reward** `{reward}` · **done** `{done}`"
283
+
284
+ def run_preset_suite(
285
+ task_id: str, session_id: Optional[str]
286
+ ) -> Tuple[str, str, str, str]:
287
+ sid = session_id or str(uuid.uuid4())
288
+ presets = PREDEFINED_QUERIES.get(task_id, [])
289
+ if not presets:
290
+ return "No presets for selected task.", "{}", sid, f"Session: `{sid}`"
291
+
292
+ rows: list[str] = []
293
+ rewards: list[float] = []
294
+ done_count = 0
295
+ error_count = 0
296
+
297
+ with _http() as client:
298
+ for idx, (name, query) in enumerate(presets, start=1):
299
+ try:
300
+ client.post(
301
+ f"{_api_base()}/reset",
302
+ json={"task_id": task_id},
303
+ headers={"X-Session-Id": sid},
304
+ ).raise_for_status()
305
+ step_resp = client.post(
306
+ f"{_api_base()}/step",
307
+ json={"action": {"action_type": "submit_query", "query": query}},
308
+ headers={"X-Session-Id": sid},
309
+ )
310
+ step_resp.raise_for_status()
311
+ data = step_resp.json()
312
+ reward = _safe_reward(data.get("reward"))
313
+ done = bool(data.get("done"))
314
+ info = data.get("info") or {}
315
+ label = "pass" if reward >= 0.5 else "check"
316
+ rewards.append(reward)
317
+ done_count += int(done)
318
+ note = "review_rejected" if info.get("review_rejected") else ""
319
+ rows.append(
320
+ f"| {idx} | {name} | `{reward:.3f}` | `{done}` | {label} {note} |"
321
+ )
322
+ except Exception as e:
323
+ error_count += 1
324
+ rows.append(
325
+ f"| {idx} | {name} | `0.000` | `False` | error: {str(e)[:120]} |"
326
+ )
327
+
328
+ avg_reward = (sum(rewards) / len(rewards)) if rewards else 0.0
329
+ max_reward = max(rewards) if rewards else 0.0
330
+ min_reward = min(rewards) if rewards else 0.0
331
+ suite_md = (
332
+ "#### Preset suite report\n"
333
+ "| # | Preset | Reward | Done | Note |\n"
334
+ "|---|---|---:|:---:|---|\n"
335
+ + "\n".join(rows)
336
+ + "\n\n"
337
+ + f"**Summary:** avg reward `{avg_reward:.3f}` · min `{min_reward:.3f}` · max `{max_reward:.3f}` · "
338
+ f"done count `{done_count}` · errors `{error_count}`"
339
+ )
340
+ suite_json = json.dumps(
341
+ {
342
+ "task_id": task_id,
343
+ "session_id": sid,
344
+ "n_presets": len(presets),
345
+ "avg_reward": round(avg_reward, 4),
346
+ "min_reward": round(min_reward, 4),
347
+ "max_reward": round(max_reward, 4),
348
+ "done_count": done_count,
349
+ "error_count": error_count,
350
+ },
351
+ indent=2,
352
+ )
353
+ return suite_md, suite_json, sid, f"Session: `{sid}`"
354
+
355
+ with gr.Blocks(
356
+ title="SQL Debug Environment",
357
+ analytics_enabled=False,
358
+ ) as demo:
359
+ gr.HTML(
360
+ """
361
+ <div class="sde-hero-wrap">
362
+ <h1>SQL Debug Environment</h1>
363
+ <p>OpenEnv-compliant SQL repair · live SQLite rewards · TRL / GRPO training on this same Space.
364
+ One page: benchmarks, artifacts, architecture, and a live playground.</p>
365
+ <div class="sde-pill-row">
366
+ <span class="sde-pill">OpenEnv</span>
367
+ <span class="sde-pill">FastAPI</span>
368
+ <span class="sde-pill">Gradio</span>
369
+ <span class="sde-pill">TRL · GRPO</span>
370
+ </div>
371
+ </div>
372
+ """.strip()
373
+ )
374
+
375
+ gr.Markdown(
376
+ "### First context: training proof first\n"
377
+ f"- **Field-notes writing style reference:** [Karpathy post]({KARPATHY_STYLE_REFERENCE})\n"
378
+ f"- **First training notebook (auto-install cell):** [Open in Colab]({COLAB_FIRST_TRAINING})\n"
379
+ f"- **Full training Colab (root anchor):** [Open in Colab]({COLAB_TRAINING_ROOT})\n"
380
+ f"- **Weights & Biases (project workspace):** [Open dashboard]({WANDB_TRAINING_RUN})\n"
381
+ f"- **Sample-reward eval artifacts (32-run JSON on Hub):** [Browse files]({HF_SAMPLE_REWARDS})\n"
382
+ f"- **Earlier 32-eval pass folder:** [Browse files]({HF_EVAL_32})\n"
383
+ f"- **Trained model card:** [md896/sql-debug-agent…]({HF_MODEL})\n"
384
+ f"- **This Space:** [{HF_SPACE}]({HF_SPACE})"
385
+ )
386
+
387
+ gr.HTML(
388
+ """
389
+ <div class="sde-kpi-grid">
390
+ <div class="sde-kpi"><div class="v">0.5B → 7B</div><div class="k">Model progression</div></div>
391
+ <div class="sde-kpi"><div class="v">32-run eval</div><div class="k">Final artifact pass</div></div>
392
+ <div class="sde-kpi"><div class="v">78.5%</div><div class="k">Spider-style headline</div></div>
393
+ <div class="sde-kpi"><div class="v">Execution reward</div><div class="k">Primary training signal</div></div>
394
+ </div>
395
+ """.strip()
396
+ )
397
+
398
+ gr.HTML(
399
+ '<div class="sde-callout"><strong>Notebook vibe:</strong> this page is intentionally written as field notes + reproducible cells, not a static deck. Every number should map to an artifact.</div>'
400
+ )
401
+
402
+ gr.Code(
403
+ label="First training context cell (from your Colab)",
404
+ language="python",
405
+ interactive=False,
406
+ value=(
407
+ "# 🏆 SQL Debug Env: FINAL REAL-WORLD BRIDGE\n"
408
+ "import os\n"
409
+ "print('📦 Checking libraries...')\n"
410
+ "os.system('pip install trl accelerate wandb -U')\n\n"
411
+ "import httpx\n"
412
+ "import torch\n"
413
+ ),
414
+ lines=8,
415
+ )
416
+
417
+ gr.Markdown(
418
+ "### Lab notebook stats (TL;DR)\n"
419
+ "- First training pass started with **Qwen/Qwen2.5-Coder-0.5B-Instruct** for environment wiring and fast iteration.\n"
420
+ "- Main training/eval track used **Qwen/Qwen2.5-Coder-7B-Instruct** with execution-grounded reward loops.\n"
421
+ "- Final reporting is tied to run artifacts and static charts committed under `server/static/`."
422
+ )
423
+
424
+ gr.Markdown(
425
+ "| Track | Model | Role | Evidence |\n"
426
+ "|---|---|---|---|\n"
427
+ "| First bridge run | Qwen/Qwen2.5-Coder-0.5B-Instruct | Fast validation of API/reward loop and notebook flow | First training context + W&B run |\n"
428
+ "| Base reference | Qwen/Qwen2.5-Coder-7B-Instruct | Baseline behavior before RL updates | Spider/comparison charts |\n"
429
+ "| Current agent | RL-updated checkpoint on 7B track | Improved execution-grounded SQL fixing | HF model + eval artifacts + sample rewards |\n"
430
+ )
431
+
432
+ gr.Markdown(
433
+ "### Run timeline (quick history)\n"
434
+ "| Stage | What happened | Why it matters |\n"
435
+ "|---|---|---|\n"
436
+ "| Bridge run | Fast setup with **Qwen2.5-Coder-0.5B** | Validated API + reward wiring quickly |\n"
437
+ "| Main baseline | Moved to **Qwen2.5-Coder-7B-Instruct** | Better capacity for SQL structure + joins |\n"
438
+ "| RL iterations | Session-consistent reset/step reward loop | Converted text quality into runtime behavior |\n"
439
+ "| Hard-test reporting | `presentation_graphs_out_final` committed under `server/static/` | Keeps evaluation auditable on-page |\n"
440
+ )
441
+
442
+ gr.Code(
443
+ label="Notebook cell: baseline evaluator sketch (7B track)",
444
+ language="python",
445
+ interactive=False,
446
+ value=(
447
+ "from transformers import AutoTokenizer, AutoModelForCausalLM\n"
448
+ "MODEL = 'Qwen/Qwen2.5-Coder-7B-Instruct'\n"
449
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL)\n"
450
+ "model = AutoModelForCausalLM.from_pretrained(MODEL, device_map='auto')\n"
451
+ "# generate SQL -> POST /reset -> POST /step -> score by execution reward\n"
452
+ ),
453
+ lines=7,
454
+ )
455
+
456
+ gr.Code(
457
+ label="Notebook cell: regenerate presentation plots from real artifacts",
458
+ language="shell",
459
+ interactive=False,
460
+ value=(
461
+ "python presentation_graphs.py \\\n"
462
+ " --sample-rewards-json artifacts/runs/20260426-064318-sample-rewards-32eval/sample_rewards_final.json \\\n"
463
+ " --output-dir presentation_graphs_out_final\n"
464
+ "cp presentation_graphs_out_final/*.png server/static/\n"
465
+ ),
466
+ lines=5,
467
+ )
468
+
469
+ gr.Code(
470
+ label="Notebook cell: live reward loop (execution-grounded)",
471
+ language="python",
472
+ interactive=False,
473
+ value=(
474
+ "with httpx.Client(base_url=ENV_URL, timeout=30.0) as client:\n"
475
+ " client.post('/reset', json={'task_id': task}, headers={'X-Session-Id': sid})\n"
476
+ " resp = client.post('/step', json={'action': {'action_type': 'submit_query', 'query': sql}},\n"
477
+ " headers={'X-Session-Id': sid})\n"
478
+ " reward = resp.json().get('reward', 0.0)\n"
479
+ " # reward drives policy updates and eval comparisons\n"
480
+ ),
481
+ lines=7,
482
+ )
483
+
484
+ gr.Markdown(
485
+ "### Failure taxonomy (from runtime debugging)\n"
486
+ "| Failure type | Typical symptom | Why execution feedback helps |\n"
487
+ "|---|---|---|\n"
488
+ "| Schema mismatch | unknown table/column | reward drops immediately and error details guide correction |\n"
489
+ "| Join logic bug | duplicated or missing rows | execution reveals semantic mismatch not visible in text quality |\n"
490
+ "| Aggregation bug | incorrect GROUP BY totals | deterministic graders expose numerical drift |\n"
491
+ "| Risky query behavior | unsafe or invalid action | reviewer path blocks while preserving learning signal |\n"
492
+ )
493
+
494
+ gr.Markdown('<p class="sde-section-title">Benchmark visuals</p>')
495
+ gr.Markdown(
496
+ "| Metric snapshot | Value |\n"
497
+ "|---|---|\n"
498
+ "| Spider chart: Industry baseline | **48.2%** |\n"
499
+ "| Spider chart: Qwen-7B base | **52.4%** |\n"
500
+ "| Spider chart: RL agent | **78.5%** |\n"
501
+ "| Performance leap chart | **0.0% -> 25.0%** (base to RL in that run view) |\n"
502
+ )
503
+ with gr.Row(equal_height=True):
504
+ if chart_leap:
505
+ gr.Image(value=chart_leap, label="Performance leap (Spider-style)", type="filepath", scale=1)
506
+ if chart_dual:
507
+ gr.Image(value=chart_dual, label="Comparison + reward shift", type="filepath", scale=2)
508
+ if chart_spider:
509
+ gr.Image(value=chart_spider, label="Spider-style headline chart", type="filepath", scale=1)
510
+
511
+ gr.Markdown(
512
+ '<p class="sde-section-title">Training run charts (repo static)</p>'
513
+ "<span style='color:#64748b;font-size:0.9rem'>Training plots from real runs. Regenerate with `presentation_graphs.py`; commit PNGs under `server/static/`.</span>"
514
+ )
515
+ with gr.Row():
516
+ if proof_combo:
517
+ gr.Image(value=proof_combo, label="Presentation combo", type="filepath", scale=1)
518
+ if proof_dist:
519
+ gr.Image(value=proof_dist, label="Reward distribution shift", type="filepath", scale=1)
520
+
521
+ if final_gallery:
522
+ gr.Markdown(
523
+ '<p class="sde-section-title">Hard-testing proof set (presentation_graphs_out_final)</p>'
524
+ "<span style='color:#64748b;font-size:0.9rem'>All generated graphs from the final evaluation set.</span>"
525
+ )
526
+ gr.Gallery(
527
+ value=final_gallery,
528
+ label="Final hard-testing charts",
529
+ preview=True,
530
+ columns=3,
531
+ height="auto",
532
+ object_fit="contain",
533
+ )
534
+
535
+ gr.Markdown('<p class="sde-section-title">Environment architecture</p>')
536
+ if wf:
537
+ gr.Image(value=wf, label="End-to-end workflow", type="filepath", show_label=True)
538
+ else:
539
+ gr.Markdown("*Add `server/static/environment-workflow.png`*")
540
+
541
+ gr.Markdown(
542
+ '<p class="sde-section-title">OpenEnv HTTP API</p>'
543
+ f"`GET /health` · `GET /tasks` · `POST /reset` · `POST /step` · `POST /step_with_review` · `GET /state` · `GET /benchmark` · "
544
+ f"loopback base `{_api_base()}` (override with **INTERNAL_API_BASE**)."
545
+ )
546
+
547
+ gr.Markdown('<p class="sde-section-title">Live playground</p>')
548
+ session = gr.State(None)
549
+ session_md = gr.Markdown("Session: *click “Reset task”*")
550
+ with gr.Row():
551
+ task = gr.Dropdown(
552
+ choices=task_choices,
553
+ value="easy_syntax_fix",
554
+ label="Task",
555
+ scale=1,
556
+ )
557
+ btn_reset = gr.Button("Reset task", variant="primary", scale=0, min_width=140)
558
+ btn_submit = gr.Button("Submit query", variant="secondary", scale=0, min_width=140)
559
+ btn_run_suite = gr.Button("Run preset suite", variant="secondary", scale=0, min_width=160)
560
+ preset_name = gr.Dropdown(
561
+ choices=_preset_options("easy_syntax_fix"),
562
+ value=_preset_options("easy_syntax_fix")[0],
563
+ label="Predefined test query",
564
+ )
565
+ btn_load_preset = gr.Button("Load predefined query", variant="secondary")
566
+ sql = gr.Code(label="Candidate SQL", language="sql", lines=12)
567
+ result_hint = gr.Markdown("")
568
+ with gr.Row():
569
+ obs_json = gr.Code(
570
+ language="json",
571
+ label="Observation (/reset)",
572
+ lines=12,
573
+ interactive=False,
574
+ scale=1,
575
+ )
576
+ step_json = gr.Code(
577
+ language="json",
578
+ label="Step (/step)",
579
+ lines=12,
580
+ interactive=False,
581
+ scale=1,
582
+ )
583
+ suite_md = gr.Markdown("")
584
+ suite_json = gr.Code(
585
+ label="Preset suite summary",
586
+ language="json",
587
+ lines=10,
588
+ interactive=False,
589
+ )
590
+
591
+ btn_reset.click(
592
+ reset_fn,
593
+ inputs=[task, session],
594
+ outputs=[obs_json, sql, session, session_md],
595
+ )
596
+ btn_submit.click(
597
+ submit_fn,
598
+ inputs=[sql, session],
599
+ outputs=[step_json, result_hint],
600
+ )
601
+ task.change(
602
+ lambda t: gr.Dropdown(
603
+ choices=_preset_options(t),
604
+ value=_preset_options(t)[0] if _preset_options(t) else None,
605
+ ),
606
+ inputs=[task],
607
+ outputs=[preset_name],
608
+ )
609
+ btn_load_preset.click(
610
+ lambda t, p: _preset_query(t, p or ""),
611
+ inputs=[task, preset_name],
612
+ outputs=[sql],
613
+ )
614
+ btn_run_suite.click(
615
+ run_preset_suite,
616
+ inputs=[task, session],
617
+ outputs=[suite_md, suite_json, session, session_md],
618
+ )
619
+
620
+ gr.Markdown('<p class="sde-section-title">Blog</p>')
621
+ gr.Markdown(blog_md)
622
+ gr.Markdown(
623
+ "### Why I picked SQL debugging and why this architecture exists\n"
624
+ "“The goal is not to generate beautiful SQL text. The goal is to produce SQL fixes that survive execution, repeatedly, under changing runtime conditions.”\n\n"
625
+ "SQL debugging is one of the few tasks where language quality and system quality can diverge sharply. A query can be grammatically neat, semantically plausible, and still fail in production. "
626
+ "I chose this problem because it forces an agent to optimize for *behavior under execution*, not only style under prompting."
627
+ )
628
+ gr.HTML(
629
+ """
630
+ <div class="sde-kpi-grid">
631
+ <div class="sde-kpi"><div class="v">0.5B -> 7B</div><div class="k">Model track from first bridge run to main baseline.</div></div>
632
+ <div class="sde-kpi"><div class="v">32-run eval</div><div class="k">Final artifact path with sample rewards and run logs.</div></div>
633
+ <div class="sde-kpi"><div class="v">Execution-first</div><div class="k">Reward is computed from runtime outcomes, not prompt resemblance.</div></div>
634
+ <div class="sde-kpi"><div class="v">Traceable claims</div><div class="k">Metrics should map back to run files and checkpoints.</div></div>
635
+ </div>
636
+ """.strip()
637
+ )
638
+ gr.Markdown(
639
+ "#### OpenEnv framing (why this is not just a demo UI)\n"
640
+ "The environment follows an OpenEnv-style interface: `reset -> observation`, `step(action) -> observation, reward, done, info`. "
641
+ "This is important because it gives the training loop a stable contract. Every algorithmic change can be tested against the same API semantics, which improves reproducibility.\n\n"
642
+ "#### Reward math (what is actually optimized)\n"
643
+ "At a high level, each step reward is composed from executed outcomes:\n\n"
644
+ "\\[\n"
645
+ "R_t = w_c C_t + w_e E_t + w_p P_t + w_s S_t - \\lambda \\cdot \\text{Penalty}_t\n"
646
+ "\\]\n\n"
647
+ "- \\(C_t\\): correctness signal (did query satisfy the task objective)\n"
648
+ "- \\(E_t\\): execution quality (valid execution / error handling)\n"
649
+ "- \\(P_t\\): progress toward a valid fix\n"
650
+ "- \\(S_t\\): schema-aware behavior bonus\n"
651
+ "- Penalty: unsafe / invalid / degenerate behavior\n\n"
652
+ "Episode objective:\n\n"
653
+ "\\[\n"
654
+ "J(\\pi) = \\mathbb{E}_{\\tau \\sim \\pi}\\left[\\sum_{t=0}^{T} \\gamma^t R_t\\right]\n"
655
+ "\\]\n\n"
656
+ "This makes the optimization target explicit: not token similarity, but expected runtime return.\n\n"
657
+ "#### Architecture decisions that matter technically\n"
658
+ "1. **Session-isolated database state**: each episode gets a clean in-memory SQLite environment.\n"
659
+ "2. **Deterministic tasks/graders**: stable reward surfaces for comparison across runs.\n"
660
+ "3. **Reviewer-guard path**: risk control without collapsing the learning signal.\n"
661
+ "4. **Typed observations + action history**: easier debugging and post-hoc analysis.\n\n"
662
+ "#### Data and reporting stats on this page\n"
663
+ "| Metric | Value | Source |\n"
664
+ "|---|---:|---|\n"
665
+ "| Spider-style industry baseline | 48.2% | chart-spider-benchmark |\n"
666
+ "| Qwen-7B base | 52.4% | chart-spider-benchmark |\n"
667
+ "| RL agent headline | 78.5% | chart-spider-benchmark |\n"
668
+ "| Performance leap view | 0.0% -> 25.0% | chart-performance-leap |\n"
669
+ "| Eval artifact pass | 32-run | HF run folder + sample rewards |\n\n"
670
+ "#### Why start with 0.5B then move to 7B\n"
671
+ "The first bridge run on **Qwen2.5-Coder-0.5B** is intentionally about speed of iteration: verify environment wiring, reward path, and notebook workflow quickly. "
672
+ "The **7B track** is then used for stronger SQL reasoning capacity and better convergence under execution-grounded rewards.\n\n"
673
+ "#### Motivation recap\n"
674
+ "I did not build this to prove that a model can emit valid-looking SQL. I built it to make SQL repair measurable as an engineering problem under runtime constraints. "
675
+ "The evidence-first layout (first context, live loop, artifact chain) is deliberate: each reported number should be traceable to run data, not presentation-only visuals."
676
+ )
677
+ gr.Markdown(
678
+ f"- [Google Cloud: techniques for improving text-to-SQL]({GCLOUD_TEXT2SQL_BLOG})\n"
679
+ f"- [OurBench / Squirrel: enterprise SQL debugging benchmark]({OURBENCH_PAPER})\n"
680
+ f"- [Writing inspiration: Karpathy field-notes style]({KARPATHY_STYLE_REFERENCE})\n"
681
+ "- [Final public blog URL (replace)](#)\n"
682
+ "- [Slides (URL)](#)\n"
683
+ "- [Demo video (URL)](#)"
684
+ )
685
+
686
+ return demo
687
+
688
+
689
+ def mount_gradio(app: Any, static_dir: Path) -> Any:
690
+ """Mount single-page Gradio at `/` (Space home) while API routes stay on the same app."""
691
+ import gradio as gr
692
+
693
+ font = gr.themes.GoogleFont("Plus Jakarta Sans")
694
+ mono = gr.themes.GoogleFont("JetBrains Mono")
695
+ theme = gr.themes.Soft(
696
+ primary_hue="indigo",
697
+ secondary_hue="slate",
698
+ neutral_hue="slate",
699
+ font=(font, "ui-sans-serif", "system-ui"),
700
+ font_mono=(mono, "ui-monospace", "monospace"),
701
+ )
702
+ blocks = build_blocks(static_dir)
703
+ return gr.mount_gradio_app(
704
+ app,
705
+ blocks,
706
+ path="/gradio",
707
+ theme=theme,
708
+ css=GRADIO_CSS,
709
+ allowed_paths=[str(static_dir.resolve())],
710
+ )
server/main.py CHANGED
@@ -8,10 +8,13 @@ import time
8
  import statistics
9
  from typing import Dict, Optional, List, Any
10
  from contextlib import asynccontextmanager
 
11
  import sqlite3
12
 
13
  from fastapi import FastAPI, HTTPException, Header, Body
 
14
  from fastapi.middleware.cors import CORSMiddleware
 
15
  from pydantic import BaseModel
16
 
17
  from .models import SQLDebugAction, SQLDebugObservation, EpisodeState
@@ -47,13 +50,28 @@ app.add_middleware(
47
  allow_headers=["*"],
48
  )
49
 
 
 
 
 
50
 
51
  @app.get("/")
52
- async def root():
 
 
 
 
 
 
 
53
  return {
54
  "name": "sql-debug-env",
55
  "status": "ok",
56
  "message": "Use /health, /tasks, /reset, /step, /state, /benchmark",
 
 
 
 
57
  }
58
 
59
 
@@ -62,6 +80,31 @@ async def favicon():
62
  return None
63
 
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  class ResetRequest(BaseModel):
66
  task_id: Optional[str] = "easy_syntax_fix"
67
 
@@ -341,3 +384,9 @@ async def state(x_session_id: Optional[str] = Header(default=None)):
341
  return current_state.model_dump()
342
  except RuntimeError as e:
343
  raise HTTPException(status_code=400, detail=str(e))
 
 
 
 
 
 
 
8
  import statistics
9
  from typing import Dict, Optional, List, Any
10
  from contextlib import asynccontextmanager
11
+ from pathlib import Path
12
  import sqlite3
13
 
14
  from fastapi import FastAPI, HTTPException, Header, Body
15
+ from fastapi.responses import HTMLResponse, RedirectResponse
16
  from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.staticfiles import StaticFiles
18
  from pydantic import BaseModel
19
 
20
  from .models import SQLDebugAction, SQLDebugObservation, EpisodeState
 
50
  allow_headers=["*"],
51
  )
52
 
53
+ _static_dir = Path(__file__).resolve().parent / "static"
54
+ if _static_dir.is_dir():
55
+ app.mount("/static", StaticFiles(directory=str(_static_dir)), name="static")
56
+
57
 
58
  @app.get("/")
59
+ async def space_home():
60
+ """Hugging Face Space opens here — send humans to the Gradio dashboard."""
61
+ return RedirectResponse(url="/gradio/", status_code=302)
62
+
63
+
64
+ @app.get("/api/info")
65
+ async def api_info():
66
+ """Machine-readable index (JSON clients that used to hit `/`)."""
67
  return {
68
  "name": "sql-debug-env",
69
  "status": "ok",
70
  "message": "Use /health, /tasks, /reset, /step, /state, /benchmark",
71
+ "demo": "/demo",
72
+ "demo_page": "/server/demo_page.html",
73
+ "gradio": "/gradio",
74
+ "info": "/api/info",
75
  }
76
 
77
 
 
80
  return None
81
 
82
 
83
+ _DEMO_PAGE_PATH = Path(__file__).resolve().parent / "demo_page.html"
84
+
85
+
86
+ def _read_demo_page_html() -> str:
87
+ """Load the Space demo HTML from disk (next to this module)."""
88
+ if not _DEMO_PAGE_PATH.is_file():
89
+ return (
90
+ "<!doctype html><html><body style='font-family:sans-serif;padding:2rem'>"
91
+ "<p><strong>demo_page.html</strong> is missing next to <code>main.py</code>.</p></body></html>"
92
+ )
93
+ return _DEMO_PAGE_PATH.read_text(encoding="utf-8")
94
+
95
+
96
+ @app.get("/demo", response_class=HTMLResponse)
97
+ async def demo_page():
98
+ """Submission-ready demo + proof page."""
99
+ return _read_demo_page_html()
100
+
101
+
102
+ @app.get("/server/demo_page.html", response_class=HTMLResponse)
103
+ async def demo_page_repo_path():
104
+ """Same page as /demo — URL matches the repo path for HF Space links and bookmarks."""
105
+ return _read_demo_page_html()
106
+
107
+
108
  class ResetRequest(BaseModel):
109
  task_id: Optional[str] = "easy_syntax_fix"
110
 
 
384
  return current_state.model_dump()
385
  except RuntimeError as e:
386
  raise HTTPException(status_code=400, detail=str(e))
387
+
388
+
389
+ # Gradio UI on the same Space (mounted after all API routes)
390
+ from .gradio_ui import mount_gradio
391
+
392
+ app = mount_gradio(app, _static_dir)
server/static/baseline_vs_trained_by_task_final.png ADDED
server/static/benchmark_style_summary_final.png ADDED
server/static/chart-comparison-shift.png ADDED
server/static/chart-performance-leap.png ADDED
server/static/chart-spider-benchmark.png ADDED
server/static/checkpoint_leaderboard_step_vs_reward_final.png ADDED
server/static/cost_vs_performance_final.png ADDED
server/static/presentation_combo_final.png ADDED
server/static/proof-combo.png ADDED
server/static/proof-distribution-shift.png ADDED
server/static/reward_distribution_shift_red_green_final.png ADDED
server/static/task_delta_post_minus_base_final.png ADDED