Mayank022 commited on
Commit
ea4f1cd
Β·
verified Β·
1 Parent(s): 5936836

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. gradio_app.py +1075 -15
gradio_app.py CHANGED
@@ -16,6 +16,778 @@ from models import APITestAction, APITestObservation, HTTPMethod
16
  from server.environment import APITestEnvironment, TASKS, API_SPEC
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  @dataclass
20
  class SessionState:
21
  env: APITestEnvironment = field(default_factory=APITestEnvironment)
@@ -469,8 +1241,284 @@ def format_endpoints():
469
  # UI
470
  # =====================================================================
471
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  def build_ui():
473
- with gr.Blocks(title="API Testing Environment") as demo:
 
 
 
474
  session = gr.State(value=new_session())
475
 
476
  gr.Markdown(
@@ -526,6 +1574,11 @@ def build_ui():
526
  # ── Center Panel ──
527
  with gr.Column(scale=2):
528
  with gr.Tabs():
 
 
 
 
 
529
  with gr.Tab("Manual Testing"):
530
  gr.Markdown("### Craft Your Request")
531
  with gr.Row():
@@ -559,11 +1612,6 @@ def build_ui():
559
  )
560
  quick_btn = gr.Button("Load Quick Action", variant="secondary")
561
 
562
- with gr.Tab("Run Baseline Agent"):
563
- gr.Markdown("### Automated Agents\nWatch a baseline agent test the API step by step.")
564
- agent_dropdown = gr.Dropdown(choices=["random", "sequential", "smart"], value="smart", label="Agent Type")
565
- run_agent_btn = gr.Button("Run Agent", variant="primary", size="lg")
566
-
567
  gr.Markdown("---")
568
  gr.Markdown("### Response")
569
  response_display = gr.Markdown("")
@@ -572,17 +1620,21 @@ def build_ui():
572
  feedback_display = gr.Markdown("")
573
 
574
  # ── Right Panel ──
 
 
575
  with gr.Column(scale=1):
576
- with gr.Tabs():
577
- with gr.Tab("Discovered Bugs"):
578
- bug_list_display = gr.Markdown("No bugs found yet.")
 
 
579
 
580
- with gr.Tab("Bug Report"):
581
- gr.Markdown("*Auto-generated OWASP security report. Populates as bugs are found.*")
582
- bug_report_display = gr.Markdown("No bugs found yet. Send requests to discover vulnerabilities.")
583
 
584
- with gr.Tab("Activity Log"):
585
- log_display = gr.Markdown("No steps yet.")
586
 
587
  # ── Wiring ──
588
  reset_outputs = [
@@ -624,4 +1676,12 @@ if __name__ == "__main__":
624
  parser.add_argument("--host", default="0.0.0.0")
625
  parser.add_argument("--share", action="store_true")
626
  args = parser.parse_args()
627
- build_ui().launch(server_name=args.host, server_port=args.port, share=args.share)
 
 
 
 
 
 
 
 
 
16
  from server.environment import APITestEnvironment, TASKS, API_SPEC
17
 
18
 
19
+ # =====================================================================
20
+ # Editorial blog-style documentation rendered below the playground.
21
+ # Uses an inline <style> block so it works without external CSS files,
22
+ # and adapts to both light and dark Gradio themes via CSS variables.
23
+ # Aesthetic: terminal-framed editorial zine β€” EB Garamond + JetBrains
24
+ # Mono, parchment cream over ink black, amber + forensic-green accents.
25
+ # =====================================================================
26
+
27
+ BLOG_HTML = r"""
28
+ <style>
29
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fraunces:opsz,wght,SOFT@9..144,200..400,30..100&family=IBM+Plex+Mono:wght@400;500&display=swap');
30
+
31
+ /* ─────────────────────────────────────────────────────────────────
32
+ ElevenLabs-inspired editorial section.
33
+ - Light Fraunces (used as Waldenburg-substitute) display
34
+ - Warm stone surfaces with sub-0.1 shadow stacks
35
+ - Pill buttons, generous whitespace
36
+ - Adapts to both light and dark Gradio themes
37
+ ───────────────────────────────────────────────────────────────── */
38
+
39
+ /* ─────────────────────────────────────────────────────────────────
40
+ Theme variables.
41
+ Strategy: ALWAYS default to LIGHT. Only flip to dark when an
42
+ explicit `.dark` class is present on the body (Gradio sets this
43
+ reliably based on ?__theme=dark URL param or system preference).
44
+ We do NOT use `prefers-color-scheme` here because Gradio already
45
+ reads it once and forwards the result via the body class β€” using
46
+ the media query as well causes double-flipping in light mode when
47
+ the OS is dark.
48
+ ───────────────────────────────────────────────────────────────── */
49
+
50
+ .eleven {
51
+ /* Default = LIGHT theme (always β€” overridden by .dark below) */
52
+ --bg: #ffffff;
53
+ --bg-soft: #f5f5f5;
54
+ --stone: rgba(245, 242, 239, 0.85);
55
+ --stone-solid: #f5f2ef;
56
+ --fg: #000000;
57
+ --fg-2: #4e4e4e;
58
+ --fg-3: #777169;
59
+ --hairline: rgba(0, 0, 0, 0.05);
60
+ --border: #e5e5e5;
61
+ --warm-shadow: rgba(78, 50, 23, 0.04);
62
+ --inset-edge: rgba(0, 0, 0, 0.075);
63
+ --outline-ring: rgba(0, 0, 0, 0.06);
64
+ --soft-elev: rgba(0, 0, 0, 0.04);
65
+
66
+ --accent-mint: #2db97a;
67
+ --accent-amber: #d97757;
68
+ --accent-coral: #c4513a;
69
+ --accent-blue: #4a6fa5;
70
+
71
+ --display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
72
+ --body: 'Inter', system-ui, sans-serif;
73
+ --mono: 'IBM Plex Mono', 'SF Mono', Menlo, monospace;
74
+
75
+ display: block;
76
+ width: 100%;
77
+ background: var(--bg);
78
+ color: var(--fg);
79
+ font-family: var(--body);
80
+ margin-top: 48px;
81
+ border-top: 1px solid var(--hairline);
82
+ }
83
+
84
+ /* DARK theme β€” triggered by Gradio's .dark class on body / container.
85
+ Gradio handles `?__theme=dark` and `prefers-color-scheme: dark` for
86
+ us by setting this class, so a single rule covers all cases. */
87
+ .dark .eleven,
88
+ body.dark .eleven,
89
+ .gradio-container.dark .eleven,
90
+ html.dark .eleven {
91
+ --bg: #0a0a0a;
92
+ --bg-soft: #131313;
93
+ --stone: rgba(28, 24, 20, 0.9);
94
+ --stone-solid: #1c1814;
95
+ --fg: #f5f2ef;
96
+ --fg-2: #b8b3ad;
97
+ --fg-3: #8a847d;
98
+ --hairline: rgba(245, 242, 239, 0.06);
99
+ --border: rgba(245, 242, 239, 0.10);
100
+ --warm-shadow: rgba(0, 0, 0, 0.4);
101
+ --inset-edge: rgba(245, 242, 239, 0.08);
102
+ --outline-ring: rgba(245, 242, 239, 0.08);
103
+ --soft-elev: rgba(0, 0, 0, 0.35);
104
+ }
105
+
106
+ /* ── Section container ── */
107
+ .eleven-section {
108
+ padding: 96px 64px;
109
+ max-width: 1280px;
110
+ margin: 0 auto;
111
+ }
112
+ .eleven-section.alt {
113
+ background: var(--bg-soft);
114
+ max-width: none;
115
+ padding: 96px 64px;
116
+ }
117
+ .eleven-section.alt > .eleven-section-inner {
118
+ max-width: 1280px;
119
+ margin: 0 auto;
120
+ }
121
+ @media (max-width: 900px) {
122
+ .eleven-section,
123
+ .eleven-section.alt { padding: 64px 24px; }
124
+ }
125
+
126
+ /* ── Eyebrow label ── */
127
+ .eleven-eyebrow {
128
+ font-family: var(--mono);
129
+ font-size: 12px;
130
+ font-weight: 500;
131
+ text-transform: uppercase;
132
+ letter-spacing: 0.18em;
133
+ color: var(--fg-3);
134
+ display: inline-flex;
135
+ align-items: center;
136
+ gap: 10px;
137
+ margin-bottom: 28px;
138
+ }
139
+ .eleven-eyebrow::before {
140
+ content: "";
141
+ width: 6px;
142
+ height: 6px;
143
+ border-radius: 50%;
144
+ background: var(--accent-mint);
145
+ box-shadow: 0 0 12px var(--accent-mint);
146
+ }
147
+
148
+ /* ── Hero masthead ── */
149
+ .eleven-hero {
150
+ text-align: center;
151
+ padding: 120px 24px 64px 24px;
152
+ max-width: 960px;
153
+ margin: 0 auto;
154
+ }
155
+ .eleven-hero h1 {
156
+ font-family: var(--display);
157
+ font-size: clamp(48px, 7vw, 96px);
158
+ font-weight: 300;
159
+ font-variation-settings: "SOFT" 50, "opsz" 144;
160
+ line-height: 1.04;
161
+ letter-spacing: -0.025em;
162
+ color: var(--fg);
163
+ margin: 0 0 24px 0;
164
+ }
165
+ .eleven-hero h1 em {
166
+ font-style: italic;
167
+ font-weight: 300;
168
+ color: var(--fg);
169
+ font-variation-settings: "SOFT" 100, "opsz" 144;
170
+ }
171
+ .eleven-hero .eleven-deck {
172
+ font-family: var(--body);
173
+ font-size: 20px;
174
+ font-weight: 400;
175
+ line-height: 1.55;
176
+ letter-spacing: 0.1px;
177
+ color: var(--fg-2);
178
+ margin: 0 auto 40px auto;
179
+ max-width: 640px;
180
+ }
181
+
182
+ /* ── Pill buttons (hero CTA row) ── */
183
+ .eleven-pills {
184
+ display: inline-flex;
185
+ gap: 12px;
186
+ flex-wrap: wrap;
187
+ justify-content: center;
188
+ }
189
+ .eleven-pill {
190
+ display: inline-flex;
191
+ align-items: center;
192
+ gap: 8px;
193
+ padding: 12px 22px;
194
+ border-radius: 9999px;
195
+ text-decoration: none;
196
+ font-family: var(--body);
197
+ font-size: 15px;
198
+ font-weight: 500;
199
+ letter-spacing: 0.1px;
200
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
201
+ }
202
+ .eleven-pill.dark {
203
+ background: var(--fg);
204
+ color: var(--bg);
205
+ }
206
+ .eleven-pill.dark:hover {
207
+ transform: translateY(-1px);
208
+ }
209
+ .eleven-pill.warm {
210
+ background: var(--stone);
211
+ color: var(--fg);
212
+ box-shadow:
213
+ var(--inset-edge) 0 0 0 0.5px inset,
214
+ var(--warm-shadow) 0 6px 16px;
215
+ }
216
+ .eleven-pill.warm:hover {
217
+ transform: translateY(-1px);
218
+ box-shadow:
219
+ var(--inset-edge) 0 0 0 0.5px inset,
220
+ var(--warm-shadow) 0 8px 20px;
221
+ }
222
+ .eleven-pill .arrow {
223
+ font-family: var(--body);
224
+ font-weight: 400;
225
+ font-size: 16px;
226
+ }
227
+
228
+ /* ── Two-column section: label + content ── */
229
+ .eleven-row {
230
+ display: grid;
231
+ grid-template-columns: 240px 1fr;
232
+ gap: 80px;
233
+ align-items: start;
234
+ }
235
+ @media (max-width: 900px) {
236
+ .eleven-row { grid-template-columns: 1fr; gap: 24px; }
237
+ }
238
+
239
+ .eleven-label {
240
+ font-family: var(--mono);
241
+ font-size: 11px;
242
+ font-weight: 600;
243
+ letter-spacing: 0.2em;
244
+ text-transform: uppercase;
245
+ color: var(--fg-3);
246
+ position: sticky;
247
+ top: 32px;
248
+ }
249
+ .eleven-label .num {
250
+ display: block;
251
+ font-family: var(--display);
252
+ font-size: 56px;
253
+ font-weight: 300;
254
+ font-variation-settings: "SOFT" 100, "opsz" 144;
255
+ letter-spacing: -0.04em;
256
+ color: var(--fg);
257
+ line-height: 1;
258
+ margin-bottom: 12px;
259
+ }
260
+
261
+ .eleven-content h2 {
262
+ font-family: var(--display);
263
+ font-size: clamp(36px, 4.5vw, 56px);
264
+ font-weight: 300;
265
+ font-variation-settings: "SOFT" 50, "opsz" 144;
266
+ line-height: 1.08;
267
+ letter-spacing: -0.018em;
268
+ color: var(--fg);
269
+ margin: 0 0 28px 0;
270
+ }
271
+ .eleven-content h2 em {
272
+ font-style: italic;
273
+ font-weight: 300;
274
+ font-variation-settings: "SOFT" 100, "opsz" 144;
275
+ color: var(--fg);
276
+ }
277
+
278
+ .eleven-content p {
279
+ font-family: var(--body);
280
+ font-size: 18px;
281
+ font-weight: 400;
282
+ line-height: 1.62;
283
+ letter-spacing: 0.18px;
284
+ color: var(--fg-2);
285
+ margin: 0 0 18px 0;
286
+ max-width: 64ch;
287
+ }
288
+ .eleven-content p strong {
289
+ color: var(--fg);
290
+ font-weight: 600;
291
+ }
292
+
293
+ /* Inline tag chip */
294
+ .eleven-chip {
295
+ display: inline-flex;
296
+ align-items: center;
297
+ font-family: var(--mono);
298
+ font-size: 12px;
299
+ font-weight: 500;
300
+ background: var(--bg);
301
+ color: var(--fg);
302
+ padding: 2px 10px;
303
+ border-radius: 9999px;
304
+ margin: 0 2px;
305
+ box-shadow:
306
+ var(--inset-edge) 0 0 0 0.5px inset,
307
+ var(--soft-elev) 0 1px 2px;
308
+ vertical-align: 2px;
309
+ }
310
+
311
+ /* ── Pull quote (subtle, ElevenLabs-style β€” not loud) ── */
312
+ .eleven-quote {
313
+ font-family: var(--display);
314
+ font-size: clamp(28px, 3.5vw, 42px);
315
+ font-weight: 300;
316
+ font-variation-settings: "SOFT" 80, "opsz" 144;
317
+ line-height: 1.2;
318
+ letter-spacing: -0.012em;
319
+ color: var(--fg);
320
+ margin: 32px 0 16px 0;
321
+ max-width: 28ch;
322
+ }
323
+ .eleven-quote em {
324
+ font-style: italic;
325
+ font-weight: 300;
326
+ color: var(--fg-3);
327
+ font-variation-settings: "SOFT" 100, "opsz" 144;
328
+ }
329
+
330
+ /* ── Reward cards (5 elegant cards instead of bar chart) ── */
331
+ .eleven-rewards {
332
+ display: grid;
333
+ grid-template-columns: repeat(2, 1fr);
334
+ gap: 16px;
335
+ margin: 32px 0 24px 0;
336
+ }
337
+ @media (max-width: 700px) {
338
+ .eleven-rewards { grid-template-columns: 1fr; }
339
+ }
340
+ .eleven-reward {
341
+ background: var(--bg);
342
+ border-radius: 16px;
343
+ padding: 24px 26px;
344
+ box-shadow:
345
+ var(--inset-edge) 0 0 0 0.5px inset,
346
+ var(--outline-ring) 0 0 0 1px,
347
+ var(--soft-elev) 0 4px 12px;
348
+ display: flex;
349
+ flex-direction: column;
350
+ gap: 8px;
351
+ }
352
+ .eleven-reward.featured {
353
+ grid-column: 1 / -1;
354
+ background: var(--stone);
355
+ box-shadow:
356
+ var(--inset-edge) 0 0 0 0.5px inset,
357
+ var(--warm-shadow) 0 6px 16px;
358
+ }
359
+ .eleven-reward-head {
360
+ display: flex;
361
+ align-items: baseline;
362
+ justify-content: space-between;
363
+ gap: 12px;
364
+ margin-bottom: 4px;
365
+ }
366
+ .eleven-reward-name {
367
+ font-family: var(--display);
368
+ font-size: 22px;
369
+ font-weight: 300;
370
+ font-variation-settings: "SOFT" 80, "opsz" 144;
371
+ letter-spacing: -0.005em;
372
+ color: var(--fg);
373
+ }
374
+ .eleven-reward-val {
375
+ font-family: var(--mono);
376
+ font-size: 14px;
377
+ font-weight: 600;
378
+ color: var(--accent-mint);
379
+ white-space: nowrap;
380
+ }
381
+ .eleven-reward-val.neg {
382
+ color: var(--accent-coral);
383
+ }
384
+ .eleven-reward p {
385
+ font-family: var(--body);
386
+ font-size: 15px;
387
+ line-height: 1.55;
388
+ letter-spacing: 0.14px;
389
+ color: var(--fg-2);
390
+ margin: 0;
391
+ max-width: none;
392
+ }
393
+
394
+ .eleven-r-foot {
395
+ font-family: var(--display);
396
+ font-size: 20px;
397
+ font-style: italic;
398
+ font-weight: 300;
399
+ font-variation-settings: "SOFT" 100, "opsz" 144;
400
+ line-height: 1.5;
401
+ color: var(--fg-2);
402
+ margin: 32px 0 0 0;
403
+ max-width: 64ch;
404
+ padding-left: 18px;
405
+ border-left: 1px solid var(--border);
406
+ }
407
+
408
+ /* ── Steps as an editorial table ── */
409
+ .eleven-steps {
410
+ margin: 40px 0 0 0;
411
+ border-top: 1px solid var(--border);
412
+ counter-reset: eleven-step;
413
+ }
414
+ .eleven-step {
415
+ display: grid;
416
+ grid-template-columns: 72px minmax(180px, 1.1fr) minmax(0, 2.4fr);
417
+ column-gap: 32px;
418
+ row-gap: 8px;
419
+ align-items: start;
420
+ padding: 32px 8px;
421
+ border-bottom: 1px solid var(--border);
422
+ counter-increment: eleven-step;
423
+ transition: background 0.18s ease;
424
+ }
425
+ .eleven-step:hover {
426
+ background: var(--bg-soft);
427
+ }
428
+ @media (max-width: 900px) {
429
+ .eleven-step {
430
+ grid-template-columns: 56px 1fr;
431
+ column-gap: 16px;
432
+ padding: 24px 4px;
433
+ }
434
+ .eleven-step-body { grid-column: 2 / -1; }
435
+ }
436
+ .eleven-step-num {
437
+ font-family: var(--mono);
438
+ font-size: 12px;
439
+ font-weight: 600;
440
+ letter-spacing: 0.18em;
441
+ color: var(--fg-3);
442
+ padding-top: 10px;
443
+ position: relative;
444
+ }
445
+ .eleven-step-num::before {
446
+ content: counter(eleven-step, decimal-leading-zero);
447
+ }
448
+ .eleven-step-num::after {
449
+ content: "";
450
+ position: absolute;
451
+ left: 0;
452
+ top: 0;
453
+ width: 18px;
454
+ height: 2px;
455
+ background: var(--accent-mint);
456
+ }
457
+ .eleven-step-title {
458
+ font-family: var(--display);
459
+ font-size: 24px;
460
+ font-weight: 400;
461
+ font-variation-settings: "SOFT" 80, "opsz" 144;
462
+ letter-spacing: -0.012em;
463
+ line-height: 1.2;
464
+ color: var(--fg);
465
+ }
466
+ .eleven-step-body {
467
+ font-family: var(--body);
468
+ font-size: 15px;
469
+ line-height: 1.65;
470
+ color: var(--fg-2);
471
+ }
472
+ .eleven-step-body p {
473
+ margin: 0;
474
+ }
475
+ .eleven-step-chips {
476
+ display: flex;
477
+ flex-wrap: wrap;
478
+ gap: 6px;
479
+ margin-top: 14px;
480
+ }
481
+ .eleven-step-chip {
482
+ font-family: var(--mono);
483
+ font-size: 11px;
484
+ font-weight: 500;
485
+ letter-spacing: 0.02em;
486
+ padding: 5px 11px;
487
+ border-radius: 999px;
488
+ background: var(--bg-soft);
489
+ color: var(--fg);
490
+ border: 1px solid var(--border);
491
+ white-space: nowrap;
492
+ }
493
+ .dark .eleven-step-chip {
494
+ background: rgba(245, 242, 239, 0.04);
495
+ }
496
+ .eleven-step-chip.accent {
497
+ background: rgba(45, 185, 122, 0.10);
498
+ color: var(--accent-mint);
499
+ border-color: rgba(45, 185, 122, 0.35);
500
+ }
501
+
502
+ /* ── Stack tiles (4 cards) ── */
503
+ .eleven-stack {
504
+ display: grid;
505
+ grid-template-columns: repeat(2, 1fr);
506
+ gap: 16px;
507
+ margin: 32px 0 0 0;
508
+ }
509
+ @media (max-width: 700px) {
510
+ .eleven-stack { grid-template-columns: 1fr; }
511
+ }
512
+ .eleven-tile {
513
+ background: var(--bg);
514
+ border-radius: 20px;
515
+ padding: 32px 32px;
516
+ display: flex;
517
+ flex-direction: column;
518
+ gap: 14px;
519
+ box-shadow:
520
+ var(--inset-edge) 0 0 0 0.5px inset,
521
+ var(--outline-ring) 0 0 0 1px,
522
+ var(--soft-elev) 0 4px 12px;
523
+ }
524
+ .eleven-tile-tag {
525
+ font-family: var(--mono);
526
+ font-size: 11px;
527
+ font-weight: 500;
528
+ text-transform: uppercase;
529
+ letter-spacing: 0.18em;
530
+ color: var(--fg-3);
531
+ }
532
+ .eleven-tile h3 {
533
+ font-family: var(--display);
534
+ font-size: 28px;
535
+ font-weight: 300;
536
+ font-variation-settings: "SOFT" 80, "opsz" 144;
537
+ letter-spacing: -0.012em;
538
+ line-height: 1.1;
539
+ color: var(--fg);
540
+ margin: 0;
541
+ }
542
+ .eleven-tile p {
543
+ font-family: var(--body);
544
+ font-size: 15px;
545
+ line-height: 1.55;
546
+ letter-spacing: 0.14px;
547
+ color: var(--fg-2);
548
+ margin: 0;
549
+ }
550
+ .eleven-tile p code {
551
+ font-family: var(--mono);
552
+ font-size: 12px;
553
+ background: var(--bg-soft);
554
+ padding: 1px 6px;
555
+ border-radius: 4px;
556
+ color: var(--fg);
557
+ font-weight: 500;
558
+ box-shadow: var(--inset-edge) 0 0 0 0.5px inset;
559
+ }
560
+
561
+ /* ── Link list (minimal underlined text) ── */
562
+ .eleven-links {
563
+ display: flex;
564
+ flex-direction: column;
565
+ gap: 14px;
566
+ margin: 32px 0 0 0;
567
+ }
568
+ .eleven-link {
569
+ font-family: var(--mono);
570
+ font-size: 14px;
571
+ line-height: 1.55;
572
+ color: var(--fg);
573
+ text-decoration: underline;
574
+ text-decoration-color: var(--border);
575
+ text-underline-offset: 4px;
576
+ text-decoration-thickness: 1px;
577
+ word-break: break-all;
578
+ transition: text-decoration-color 0.18s ease, color 0.18s ease;
579
+ }
580
+ .eleven-link:hover {
581
+ text-decoration-color: var(--accent-mint);
582
+ color: var(--accent-mint);
583
+ }
584
+ </style>
585
+
586
+ <div class="eleven">
587
+
588
+ <!-- ─── HERO ─── -->
589
+ <div class="eleven-hero">
590
+ <h1>Where agents learn to <em>break APIs.</em></h1>
591
+ <p class="eleven-deck">An OpenEnv reinforcement learning environment for API security testing. A live REST API with thirteen planted vulnerabilities, a verifiable reward function mapped to the OWASP API Security Top 10, and an episode that ends with a structured bug report.</p>
592
+ </div>
593
+
594
+ <!-- ─── 01 WHAT IS THIS ─── -->
595
+ <div class="eleven-section">
596
+ <div class="eleven-row">
597
+ <div class="eleven-label"><span class="num">01</span>The premise</div>
598
+ <div class="eleven-content">
599
+ <h2>What <em>this is.</em></h2>
600
+ <p>A Gradio playground for an OpenEnv RL environment that trains AI agents to test REST APIs the way a security engineer would. Behind the UI is a Task Management API with <strong>13 deliberately planted bugs</strong> covering 6 categories from the <strong>OWASP API Security Top 10</strong>.</p>
601
+ <p>The agent connects, sends HTTP requests, earns rewards for finding bugs and covering endpoints, and generates a bug bounty report when the episode ends.</p>
602
+ <div class="eleven-quote">Real API. Real bugs. Real OWASP categories β€” <em>verifiable end to end.</em></div>
603
+ </div>
604
+ </div>
605
+ </div>
606
+
607
+ <!-- ─── 02 WHY BOTHER ─── -->
608
+ <div class="eleven-section alt">
609
+ <div class="eleven-section-inner">
610
+ <div class="eleven-row">
611
+ <div class="eleven-label"><span class="num">02</span>The gap</div>
612
+ <div class="eleven-content">
613
+ <h2>Why <em>bother.</em></h2>
614
+ <p>Every team ships APIs and every API has bugs. The usual tools <span class="eleven-chip">Postman</span> <span class="eleven-chip">Schemathesis</span> <span class="eleven-chip">OWASP&nbsp;ZAP</span> either need humans writing tests by hand or fall back to brute-force fuzzing.</p>
615
+ <p>Recent papers β€” <em>APIRL</em> at AAAI 2025, <em>ARAT-RL</em> at ASE 2023 β€” show RL beats both. But there hasn't been a standard RL benchmark for it.</p>
616
+ <div class="eleven-quote">This environment <em>is the benchmark.</em></div>
617
+ <p>The agent doesn't get a written test plan. It reads the API spec, plans a campaign, runs it, and reports what broke. The reward function is verifiable β€” no LLM judge, no soft heuristics β€” and every signal maps to a real OWASP category, so episodes can be scored deterministically.</p>
618
+ </div>
619
+ </div>
620
+ </div>
621
+ </div>
622
+
623
+ <!-- ─── 03 REWARD ─── -->
624
+ <div class="eleven-section">
625
+ <div class="eleven-row">
626
+ <div class="eleven-label"><span class="num">03</span>How reward works</div>
627
+ <div class="eleven-content">
628
+ <h2>Five signals,<br><em>one episode.</em></h2>
629
+ <p>The reward function is verifiable β€” no LLM judge, no soft heuristics. Each step accumulates from five components and the task grader caps the episode with a terminal score in <span class="eleven-chip">[0, 1]</span>.</p>
630
+
631
+ <div class="eleven-rewards">
632
+ <div class="eleven-reward featured">
633
+ <div class="eleven-reward-head">
634
+ <div class="eleven-reward-name">Bug discovery</div>
635
+ <div class="eleven-reward-val">+0.10 / +0.15 / +0.25</div>
636
+ </div>
637
+ <p>Finding a planted bug, scaled by severity. Easy bugs (status codes, missing fields) are worth 0.10. Medium (validation, auth) gets 0.15. Hard (BOLA, injection, broken auth chains) gets 0.25.</p>
638
+ </div>
639
+ <div class="eleven-reward">
640
+ <div class="eleven-reward-head">
641
+ <div class="eleven-reward-name">Coverage</div>
642
+ <div class="eleven-reward-val">+0.20</div>
643
+ </div>
644
+ <p>Hitting endpoints, methods, and status codes the agent hasn't tried yet.</p>
645
+ </div>
646
+ <div class="eleven-reward">
647
+ <div class="eleven-reward-head">
648
+ <div class="eleven-reward-name">Validity</div>
649
+ <div class="eleven-reward-val">+0.18</div>
650
+ </div>
651
+ <p>Well-formed requests, plus chaining IDs from previous responses.</p>
652
+ </div>
653
+ <div class="eleven-reward">
654
+ <div class="eleven-reward-head">
655
+ <div class="eleven-reward-name">Exploration</div>
656
+ <div class="eleven-reward-val">+0.05</div>
657
+ </div>
658
+ <p>Trying genuinely novel action patterns the agent hasn't tried before.</p>
659
+ </div>
660
+ <div class="eleven-reward">
661
+ <div class="eleven-reward-head">
662
+ <div class="eleven-reward-name">Penalty</div>
663
+ <div class="eleven-reward-val neg">βˆ’0.08</div>
664
+ </div>
665
+ <p>Repeating the same exact request twice β€” anti-spam, anti-loop.</p>
666
+ </div>
667
+ </div>
668
+
669
+ <p class="eleven-r-foot">When the episode ends, the task grader adds a terminal score based on its own criteria β€” CRUD coverage, dependency chaining, security probing, that kind of thing.</p>
670
+ </div>
671
+ </div>
672
+ </div>
673
+
674
+ <!-- ─── 04 HOW TO USE ─── -->
675
+ <div class="eleven-section alt">
676
+ <div class="eleven-section-inner">
677
+ <div class="eleven-row">
678
+ <div class="eleven-label"><span class="num">04</span>How to use this</div>
679
+ <div class="eleven-content">
680
+ <h2>Five steps<br><em>to a verdict.</em></h2>
681
+
682
+ <div class="eleven-steps">
683
+
684
+ <div class="eleven-step">
685
+ <div class="eleven-step-num"></div>
686
+ <div class="eleven-step-title">Pick a task</div>
687
+ <div class="eleven-step-body">
688
+ <p>Three difficulty tiers in the dropdown on the left, from a CRUD smoke-test to a full BOLA + injection chain.</p>
689
+ <div class="eleven-step-chips">
690
+ <span class="eleven-step-chip accent">basic_validation</span>
691
+ <span class="eleven-step-chip accent">edge_cases</span>
692
+ <span class="eleven-step-chip accent">security_workflows</span>
693
+ </div>
694
+ </div>
695
+ </div>
696
+
697
+ <div class="eleven-step">
698
+ <div class="eleven-step-num"></div>
699
+ <div class="eleven-step-title">Reset the environment</div>
700
+ <div class="eleven-step-body">
701
+ <p>Every reset spins up a fresh database with new users, new tasks, and randomized ownership, so the agent can't memorize answers between episodes.</p>
702
+ </div>
703
+ </div>
704
+
705
+ <div class="eleven-step">
706
+ <div class="eleven-step-num"></div>
707
+ <div class="eleven-step-title">Run a baseline</div>
708
+ <div class="eleven-step-body">
709
+ <p>The Run Baseline Agent tab is open by default. Pick a strategy and watch it test the API step by step.</p>
710
+ <div class="eleven-step-chips">
711
+ <span class="eleven-step-chip">random</span>
712
+ <span class="eleven-step-chip">sequential</span>
713
+ <span class="eleven-step-chip">smart</span>
714
+ </div>
715
+ </div>
716
+ </div>
717
+
718
+ <div class="eleven-step">
719
+ <div class="eleven-step-num"></div>
720
+ <div class="eleven-step-title">Or test manually</div>
721
+ <div class="eleven-step-body">
722
+ <p>Switch to Manual Testing. Quick Actions give one-click bug hunts, or craft your own request from scratch β€” method, endpoint, headers, body, expected status.</p>
723
+ </div>
724
+ </div>
725
+
726
+ <div class="eleven-step">
727
+ <div class="eleven-step-num"></div>
728
+ <div class="eleven-step-title">Watch the panel</div>
729
+ <div class="eleven-step-body">
730
+ <p>Discovered Bugs and the Activity Log update live as the agent works. When the episode ends, expand the Bug Report (OWASP) drawer for the full structured findings, severities, and fix recommendations.</p>
731
+ </div>
732
+ </div>
733
+
734
+ </div>
735
+ </div>
736
+ </div>
737
+ </div>
738
+ </div>
739
+
740
+ <!-- ─── 05 STACK ─── -->
741
+ <div class="eleven-section">
742
+ <div class="eleven-row">
743
+ <div class="eleven-label"><span class="num">05</span>Under the hood</div>
744
+ <div class="eleven-content">
745
+ <h2>Three <em>layers.</em></h2>
746
+ <p>Self-contained, reproducible, and runs on a free-tier HuggingFace Space.</p>
747
+
748
+ <div class="eleven-stack">
749
+ <div class="eleven-tile">
750
+ <span class="eleven-tile-tag">L1 Β· ENVIRONMENT</span>
751
+ <h3>FastAPI + SQLite</h3>
752
+ <p>A buggy Task Management API wrapped in OpenEnv's <code>step()</code> / <code>reset()</code> / <code>state()</code> contract. Runs in-process or as a Docker image, with seed-randomized data on every reset so episodes can't be memorized.</p>
753
+ </div>
754
+ <div class="eleven-tile">
755
+ <span class="eleven-tile-tag">L2 Β· INFERENCE</span>
756
+ <h3>OpenAI-compatible client</h3>
757
+ <p><code>inference.py</code> talks to any HuggingFace-hosted model through the OpenAI SDK and structured JSON output. Plug in any model that follows the protocol β€” no environment-specific glue.</p>
758
+ </div>
759
+ <div class="eleven-tile">
760
+ <span class="eleven-tile-tag">L3 Β· DEPLOY</span>
761
+ <h3>Docker + HF Spaces</h3>
762
+ <p>Containerized on top of the official <code>openenv-base</code> image and deployed as a public HuggingFace Space, so judges can hit it with a single HTTP call.</p>
763
+ </div>
764
+ </div>
765
+ </div>
766
+ </div>
767
+ </div>
768
+
769
+ <!-- ─── 06 LINKS ─── -->
770
+ <div class="eleven-section alt">
771
+ <div class="eleven-section-inner">
772
+ <div class="eleven-row">
773
+ <div class="eleven-label"><span class="num">06</span>The artifacts</div>
774
+ <div class="eleven-content">
775
+ <h2>Everything <em>reproducible.</em></h2>
776
+ <p>Source code, deployed environment, framework. Open and inspectable.</p>
777
+
778
+ <div class="eleven-links">
779
+ <a class="eleven-link" href="https://github.com/Mayankpratapsingh022/API-Testing-RL" target="_blank" rel="noopener">https://github.com/Mayankpratapsingh022/API-Testing-RL</a>
780
+ <a class="eleven-link" href="https://meta-pytorch.org/OpenEnv/" target="_blank" rel="noopener">https://meta-pytorch.org/OpenEnv/</a>
781
+ </div>
782
+ </div>
783
+ </div>
784
+ </div>
785
+ </div>
786
+
787
+ </div>
788
+ """
789
+
790
+
791
  @dataclass
792
  class SessionState:
793
  env: APITestEnvironment = field(default_factory=APITestEnvironment)
 
1241
  # UI
1242
  # =====================================================================
1243
 
1244
+ _GRADIO_THEME = gr.themes.Soft(
1245
+ primary_hue="emerald",
1246
+ secondary_hue="green",
1247
+ neutral_hue="slate",
1248
+ font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
1249
+ font_mono=[gr.themes.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace"],
1250
+ )
1251
+
1252
+
1253
+ # Custom CSS injected into the Gradio app to highlight important interactive
1254
+ # elements (primary buttons, active tabs, hover states) so the playground
1255
+ # doesn't feel washed out. Works in both light and dark mode.
1256
+ _GRADIO_CSS = """
1257
+ /* ─── Mintlify-inspired green palette (flat, no gradients) ─── */
1258
+ :root {
1259
+ --accent: #18E299; /* Brand Green */
1260
+ --accent-hover: #0fa76e; /* Brand Green Deep */
1261
+ --accent-soft: #d4fae8; /* Brand Green Light */
1262
+ --accent-border: rgba(15, 167, 110, 0.28);
1263
+ --ink: #0d0d0d;
1264
+ --ink-muted: #666666;
1265
+ --line: #e5e5e5;
1266
+ --surface: #fafafa;
1267
+ --success: #16a34a;
1268
+ --danger: #dc2626;
1269
+ --info: #2563eb;
1270
+ }
1271
+ .dark {
1272
+ --accent: #18E299;
1273
+ --accent-hover: #34efaa;
1274
+ --accent-soft: rgba(24, 226, 153, 0.14);
1275
+ --accent-border: rgba(24, 226, 153, 0.35);
1276
+ --ink: #f5f5f5;
1277
+ --ink-muted: #a0a0a0;
1278
+ --line: rgba(255, 255, 255, 0.10);
1279
+ --surface: #141414;
1280
+ }
1281
+
1282
+ /* ─── Primary buttons ─────────────────────────────────────────────
1283
+ Light mode: near-black surface, white text, green hover.
1284
+ Dark mode: bright green surface, near-black text.
1285
+ Both flat (no gradients, no glow). */
1286
+ button.primary,
1287
+ .gr-button.primary,
1288
+ button[class*="primary"],
1289
+ .gr-button-primary {
1290
+ background: #0d0d0d !important;
1291
+ background-image: none !important;
1292
+ color: #ffffff !important;
1293
+ border: 1px solid #0d0d0d !important;
1294
+ box-shadow: none !important;
1295
+ font-weight: 600 !important;
1296
+ letter-spacing: 0.01em !important;
1297
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease !important;
1298
+ }
1299
+ button.primary:hover,
1300
+ .gr-button.primary:hover,
1301
+ button[class*="primary"]:hover,
1302
+ .gr-button-primary:hover {
1303
+ background: #0fa76e !important;
1304
+ background-image: none !important;
1305
+ color: #ffffff !important;
1306
+ border-color: #0fa76e !important;
1307
+ box-shadow: none !important;
1308
+ transform: none !important;
1309
+ filter: none !important;
1310
+ }
1311
+ button.primary:active,
1312
+ .gr-button.primary:active,
1313
+ .gr-button-primary:active {
1314
+ background: #0a8a5a !important;
1315
+ border-color: #0a8a5a !important;
1316
+ transform: none !important;
1317
+ filter: none !important;
1318
+ }
1319
+ /* Dark-mode override: bright green CTA pops against the dark surface */
1320
+ .dark button.primary,
1321
+ .dark .gr-button.primary,
1322
+ .dark button[class*="primary"],
1323
+ .dark .gr-button-primary {
1324
+ background: #18E299 !important;
1325
+ color: #07301f !important;
1326
+ border: 1px solid #18E299 !important;
1327
+ }
1328
+ .dark button.primary:hover,
1329
+ .dark .gr-button.primary:hover,
1330
+ .dark button[class*="primary"]:hover,
1331
+ .dark .gr-button-primary:hover {
1332
+ background: #34efaa !important;
1333
+ border-color: #34efaa !important;
1334
+ color: #07301f !important;
1335
+ }
1336
+
1337
+ /* ─── Secondary buttons ──────────────────────────────────────────
1338
+ Light mode: white with dark border, fills near-black on hover.
1339
+ Dark mode: ghost button with green border. */
1340
+ button.secondary,
1341
+ .gr-button.secondary,
1342
+ .gr-button-secondary {
1343
+ background: #ffffff !important;
1344
+ background-image: none !important;
1345
+ border: 1px solid #0d0d0d !important;
1346
+ color: #0d0d0d !important;
1347
+ font-weight: 500 !important;
1348
+ box-shadow: none !important;
1349
+ transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease !important;
1350
+ }
1351
+ button.secondary:hover,
1352
+ .gr-button.secondary:hover,
1353
+ .gr-button-secondary:hover {
1354
+ background: #0d0d0d !important;
1355
+ color: #ffffff !important;
1356
+ border-color: #0d0d0d !important;
1357
+ }
1358
+ .dark button.secondary,
1359
+ .dark .gr-button.secondary,
1360
+ .dark .gr-button-secondary {
1361
+ background: transparent !important;
1362
+ border: 1px solid var(--accent-border) !important;
1363
+ color: var(--accent) !important;
1364
+ }
1365
+ .dark button.secondary:hover,
1366
+ .dark .gr-button.secondary:hover,
1367
+ .dark .gr-button-secondary:hover {
1368
+ background: var(--accent) !important;
1369
+ color: #07301f !important;
1370
+ border-color: var(--accent) !important;
1371
+ }
1372
+
1373
+ /* ─── Tabs (selected tab uses brand green) ─── */
1374
+ button[role="tab"][aria-selected="true"],
1375
+ .tab-nav button.selected,
1376
+ .tab-nav button[aria-selected="true"] {
1377
+ color: var(--accent-hover) !important;
1378
+ border-bottom: 2px solid var(--accent) !important;
1379
+ font-weight: 600 !important;
1380
+ }
1381
+ .dark button[role="tab"][aria-selected="true"],
1382
+ .dark .tab-nav button.selected,
1383
+ .dark .tab-nav button[aria-selected="true"] {
1384
+ color: var(--accent) !important;
1385
+ }
1386
+ button[role="tab"]:hover,
1387
+ .tab-nav button:hover {
1388
+ color: var(--accent-hover) !important;
1389
+ }
1390
+ .dark button[role="tab"]:hover,
1391
+ .dark .tab-nav button:hover {
1392
+ color: var(--accent) !important;
1393
+ }
1394
+
1395
+ /* ─── Inputs (focus ring uses brand green, no glow) ─── */
1396
+ .gr-dropdown,
1397
+ .gr-input,
1398
+ .gr-textbox,
1399
+ input[type="text"],
1400
+ input[type="number"],
1401
+ textarea,
1402
+ select {
1403
+ transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
1404
+ }
1405
+ .gr-dropdown:focus-within,
1406
+ .gr-input:focus-within,
1407
+ .gr-textbox:focus-within,
1408
+ input:focus,
1409
+ textarea:focus,
1410
+ select:focus {
1411
+ border-color: var(--accent) !important;
1412
+ box-shadow: 0 0 0 3px var(--accent-soft) !important;
1413
+ outline: none !important;
1414
+ }
1415
+
1416
+ /* ─── Section headings ─── */
1417
+ h1, h2, h3 {
1418
+ letter-spacing: -0.01em !important;
1419
+ }
1420
+ h1, h2 {
1421
+ color: #0d0d0d !important;
1422
+ }
1423
+ .dark h1, .dark h2 {
1424
+ color: #f5f5f5 !important;
1425
+ }
1426
+ h3 {
1427
+ color: #0d0d0d !important;
1428
+ font-weight: 600 !important;
1429
+ border-bottom: 1px solid var(--line) !important;
1430
+ padding-bottom: 6px !important;
1431
+ margin-bottom: 12px !important;
1432
+ position: relative !important;
1433
+ }
1434
+ /* small green accent bar before each section heading for brand identity */
1435
+ h3::before {
1436
+ content: "" !important;
1437
+ display: inline-block !important;
1438
+ width: 4px !important;
1439
+ height: 14px !important;
1440
+ background: #18E299 !important;
1441
+ border-radius: 2px !important;
1442
+ margin-right: 8px !important;
1443
+ vertical-align: -2px !important;
1444
+ }
1445
+ .dark h3 {
1446
+ color: #f5f5f5 !important;
1447
+ }
1448
+
1449
+ /* ─── Markdown links ─── */
1450
+ .prose a, .markdown a, a {
1451
+ color: var(--accent-hover) !important;
1452
+ text-decoration: none !important;
1453
+ border-bottom: 1px solid var(--accent-border) !important;
1454
+ }
1455
+ .dark .prose a, .dark .markdown a, .dark a {
1456
+ color: var(--accent) !important;
1457
+ }
1458
+ .prose a:hover, .markdown a:hover, a:hover {
1459
+ border-bottom-color: var(--accent) !important;
1460
+ }
1461
+
1462
+ /* ─── Accordion headers ─── */
1463
+ .gr-accordion > button,
1464
+ button[class*="accordion"] {
1465
+ color: var(--accent-hover) !important;
1466
+ font-weight: 600 !important;
1467
+ }
1468
+ .dark .gr-accordion > button,
1469
+ .dark button[class*="accordion"] {
1470
+ color: var(--accent) !important;
1471
+ }
1472
+
1473
+ /* ─── Card borders (Mintlify principle: borders, not shadows) ─── */
1474
+ .gr-block.gr-box {
1475
+ border-color: var(--line) !important;
1476
+ box-shadow: none !important;
1477
+ }
1478
+
1479
+ /* ─── Match the Gradio dark surface to the blog section ──────────
1480
+ The blog section below uses #0a0a0a as its background. Override
1481
+ Gradio's default slate so the page reads as one continuous canvas. */
1482
+ .dark {
1483
+ --body-background-fill: #0a0a0a !important;
1484
+ --background-fill-primary: #0a0a0a !important;
1485
+ --background-fill-secondary: #131313 !important;
1486
+ --block-background-fill: #131313 !important;
1487
+ --panel-background-fill: #131313 !important;
1488
+ --input-background-fill: #131313 !important;
1489
+ --border-color-primary: rgba(255, 255, 255, 0.08) !important;
1490
+ }
1491
+ .dark,
1492
+ .dark body,
1493
+ .dark gradio-app,
1494
+ .dark .gradio-container,
1495
+ .dark .main,
1496
+ .dark .wrap,
1497
+ .dark .app,
1498
+ .dark .contain {
1499
+ background: #0a0a0a !important;
1500
+ background-color: #0a0a0a !important;
1501
+ }
1502
+ /* Cards / blocks get a slightly lighter surface so they remain
1503
+ visually separated from the page background. */
1504
+ .dark .gr-block,
1505
+ .dark .gr-box,
1506
+ .dark .gr-form,
1507
+ .dark .gr-panel,
1508
+ .dark .block,
1509
+ .dark .form {
1510
+ background: #131313 !important;
1511
+ background-color: #131313 !important;
1512
+ border-color: rgba(255, 255, 255, 0.08) !important;
1513
+ }
1514
+ """
1515
+
1516
+
1517
  def build_ui():
1518
+ # Mintlify-inspired green Soft theme β€” adapts to ?__theme=light / ?__theme=dark
1519
+ # URL params on HuggingFace Spaces. The blog section below also reads the
1520
+ # .dark body class so the entire page adapts together.
1521
+ with gr.Blocks(title="API Testing Environment", theme=_GRADIO_THEME, css=_GRADIO_CSS) as demo:
1522
  session = gr.State(value=new_session())
1523
 
1524
  gr.Markdown(
 
1574
  # ── Center Panel ──
1575
  with gr.Column(scale=2):
1576
  with gr.Tabs():
1577
+ with gr.Tab("Run Baseline Agent"):
1578
+ gr.Markdown("### Automated Agents\nWatch a baseline agent test the API step by step. Pick a strategy and click Run Agent.")
1579
+ agent_dropdown = gr.Dropdown(choices=["random", "sequential", "smart"], value="smart", label="Agent Type")
1580
+ run_agent_btn = gr.Button("Run Agent", variant="primary", size="lg")
1581
+
1582
  with gr.Tab("Manual Testing"):
1583
  gr.Markdown("### Craft Your Request")
1584
  with gr.Row():
 
1612
  )
1613
  quick_btn = gr.Button("Load Quick Action", variant="secondary")
1614
 
 
 
 
 
 
1615
  gr.Markdown("---")
1616
  gr.Markdown("### Response")
1617
  response_display = gr.Markdown("")
 
1620
  feedback_display = gr.Markdown("")
1621
 
1622
  # ── Right Panel ──
1623
+ # Stacked (no tabs) so Discovered Bugs and Activity Log are both
1624
+ # visible at once β€” users shouldn't have to click to see the log.
1625
  with gr.Column(scale=1):
1626
+ gr.Markdown("### Discovered Bugs")
1627
+ bug_list_display = gr.Markdown("No bugs found yet.")
1628
+
1629
+ gr.Markdown("### Activity Log")
1630
+ log_display = gr.Markdown("No steps yet.")
1631
 
1632
+ with gr.Accordion("Bug Report (OWASP)", open=False):
1633
+ gr.Markdown("*Auto-generated OWASP security report. Populates as bugs are found.*")
1634
+ bug_report_display = gr.Markdown("No bugs found yet. Send requests to discover vulnerabilities.")
1635
 
1636
+ # ── Editorial blog-style documentation below the app ──
1637
+ gr.HTML(BLOG_HTML)
1638
 
1639
  # ── Wiring ──
1640
  reset_outputs = [
 
1676
  parser.add_argument("--host", default="0.0.0.0")
1677
  parser.add_argument("--share", action="store_true")
1678
  args = parser.parse_args()
1679
+
1680
+ # Pass theme + css to both Blocks() (Gradio 5.x) and launch() (Gradio 6.0+)
1681
+ # so it works on whichever version the host runs.
1682
+ launch_kwargs = dict(server_name=args.host, server_port=args.port, share=args.share)
1683
+ try:
1684
+ build_ui().launch(theme=_GRADIO_THEME, css=_GRADIO_CSS, **launch_kwargs)
1685
+ except TypeError:
1686
+ # Older Gradio: launch() doesn't accept theme/css β€” Blocks() already has them
1687
+ build_ui().launch(**launch_kwargs)