Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 577 |
-
|
| 578 |
-
|
|
|
|
|
|
|
| 579 |
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
|
| 584 |
-
|
| 585 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 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)
|