musharraf7 commited on
Commit
1dcdbe0
Β·
verified Β·
1 Parent(s): fdb62ab

ui: rebuild ESCTR frontend with direct app landing and blog tab

Browse files
Files changed (1) hide show
  1. server/gradio_ui.py +117 -182
server/gradio_ui.py CHANGED
@@ -1,86 +1,103 @@
1
  """Interactive Gradio UI for the ESCTR Environment."""
2
 
3
- import gradio as gr
4
  import random
 
 
 
5
  from .environment import ESCTREnvironment
6
  from .models import ESCTRAction
7
 
8
 
9
- # ── Styling ──────────────────────────────────────────────────────────────────
10
-
11
  CSS = """
12
  body, .gradio-container {
13
- background: #e8f4f8 !important;
14
- color: #2d3748 !important;
15
  font-family: 'Times New Roman', Times, Georgia, serif !important;
 
 
16
  }
17
  .gradio-container {
18
  max-width: 1120px !important;
19
  margin: 0 auto !important;
20
  }
21
- footer, .built-with, .gradio-container > footer { display: none !important; }
22
- .main-header { text-align: center; padding: 1.5rem 1rem 0.8rem; }
 
 
 
 
23
  .main-header h1 {
24
  margin: 0;
25
- font-size: 2rem;
26
  font-weight: 600;
27
  color: #1a202c;
28
  }
29
  .main-header .subtitle {
30
- margin-top: 0.35rem;
31
  color: #5a6b7a;
32
  font-style: italic;
33
  font-size: 1rem;
34
  }
35
- .main-header .links { margin-top: 0.5rem; font-size: 0.88rem; color: #718096; }
36
- .main-header .links a { color: #3d4f5f; text-decoration: none; border-bottom: 1px dotted #8a9caa; }
37
- .main-header .links a:hover { opacity: 0.75; }
 
 
 
 
 
 
 
 
38
 
39
- .tabs { border: none !important; background: transparent !important; }
40
- .tab-nav {
41
- justify-content: center !important;
42
- gap: 0.25rem !important;
43
  border: none !important;
44
  background: transparent !important;
 
 
 
 
 
 
45
  }
46
  .tab-nav button {
47
  border: 1px solid transparent !important;
48
  background: none !important;
49
- border-radius: 4px !important;
50
  color: #2d3748 !important;
51
- font-size: 0.92rem !important;
52
- padding: 0.4rem 0.9rem !important;
 
53
  }
54
  .tab-nav button.selected {
 
55
  border-color: #2d3748 !important;
56
  color: #1a202c !important;
57
- font-weight: 700 !important;
58
- }
59
- .tabitem, .tabitem > .column {
60
- background: transparent !important;
61
- border: none !important;
62
- box-shadow: none !important;
63
  }
64
 
65
  .surface {
66
- background: #f9fcfd;
67
- border: 1px solid #d7e5ec;
68
  border-radius: 8px;
69
- padding: 1rem;
70
  }
71
 
72
- .prose { max-width: 760px; margin: 0 auto; line-height: 1.85; color: #374151; }
73
- .prose h2 { color: #1a202c; font-weight: 600; margin-top: 1.7rem; }
74
- .prose h3 { color: #2d3748; font-weight: 600; }
75
- .prose code {
76
- background: #edf3f7;
77
- padding: 0.1rem 0.3rem;
78
- border-radius: 3px;
79
- font-family: 'Courier New', Consolas, monospace !important;
 
 
 
 
 
 
 
 
80
  }
81
- .prose table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
82
- .prose th, .prose td { border-bottom: 1px solid #dce7ee; padding: 0.5rem 0.45rem; text-align: left; }
83
- .prose th { color: #1a202c; font-weight: 600; }
84
 
85
  .log-box textarea {
86
  font-family: 'Courier New', Consolas, monospace !important;
@@ -88,157 +105,66 @@ footer, .built-with, .gradio-container > footer { display: none !important; }
88
  line-height: 1.55 !important;
89
  }
90
  .reward-big textarea {
91
- font-size: 2rem !important;
92
  font-weight: 700 !important;
93
  text-align: center !important;
94
  }
95
  .tool-btn { min-height: 42px !important; }
96
- .plot-img img { border-radius: 6px; border: 1px solid #dbe7ee; }
 
 
 
 
97
  """
98
 
99
 
100
  # ── Overview Tab Content ───────────────────────────────────────────────���─────
101
 
102
  OVERVIEW_MD = """
103
- <div class="prose">
104
-
105
- ## ESCTR: Enterprise Supply Chain & Tax Reconciliation
106
-
107
- > *Training LLMs to be autonomous financial auditors β€” powered by Reinforcement Learning with Verifiable Rewards (RLVR)*
108
 
109
- Every day, enterprises process millions of procurement transactions. Between Purchase Orders,
110
- shipping manifests, SLA contracts, and vendor invoices β€” discrepancies are inevitable.
111
- Resolving them means humans manually cross-referencing siloed databases, interpreting
112
- contract clauses, and performing precise arithmetic under pressure.
113
 
114
- **ESCTR** frames this as a reinforcement learning problem. The agent operates as a
115
- financial controller, armed with four ERP tools, navigating a multi-step audit pipeline
116
- against adversarial vendors β€” graded by mathematically precise, verifiable reward signals.
117
 
118
- ### Three Tasks, Escalating Stakes
 
 
 
 
119
 
120
- | Task | Difficulty | What the Agent Must Do |
121
- |------|-----------|----------------------|
122
- | **Procurement Reconciliation** | 🟒 Easy | Identify overcharged line items, calculate exact overcharge |
123
- | **SLA Enforcement** | 🟑 Medium | Discover late shipments, retrieve SLA contract, compute penalty |
124
- | **Adversarial Auditing** | πŸ”΄ Hard | All above *plus* disprove vendor counter-claims using warehouse logs |
125
 
126
- ### Four ERP Tools
 
 
 
127
 
128
- - `query_database` β€” search purchase orders, invoices, shipping logs, SLA contracts, warehouse logs
129
- - `read_document` β€” retrieve the full text of any document by ID
130
- - `communicate_vendor` β€” negotiate with an adversarial vendor that lies, deflects, and offers bad settlements
131
- - `submit_financial_decision` β€” submit the final adjustment (terminal action, ends the episode)
132
 
133
- ### Reward Design
134
 
135
- ```
136
- R_total = Ξ± Β· R_outcome + Ξ² Β· R_trajectory βˆ’ penalties
137
- ```
138
-
139
- - **R_outcome** (60–70%): Did the agent submit the *exact* correct adjustment?
140
- - **R_trajectory** (30–40%): Did the agent follow proper investigative procedure?
141
- - **Penalties**: Step costs, hallucination, accepting bad settlements
142
-
143
- Every scenario is **procedurally generated from a seed** β€” infinite training configurations with
144
- deterministic, reproducible grading. No memorization possible.
145
-
146
- ---
147
-
148
- *Use the **Playground** tab to try the environment interactively, or see **Training** for results from three model runs.*
149
-
150
- </div>
151
  """
152
 
153
 
154
  # ── Training Tab Content ─────────────────────────────────────────────────────
155
 
156
  TRAINING_MD = """
157
- <div class="prose">
158
-
159
- ## Training: Three Models, Three GPUs, One Reward Signal
160
-
161
- We trained three models using **TRL's GRPOTrainer** with `environment_factory`,
162
- iterating across model sizes from 0.6B to 4B β€” following the approach of
163
- **small models + fast iteration**.
164
-
165
- ### πŸš€ Qwen3-4B (Production Model)
166
 
167
- Trained on RTX 4090 (24GB VRAM) with 4-bit QLoRA. After overcoming "zero-reward collapse"
168
- through **shaped investigation rewards** and **high-temperature exploration (T=1.5)**, the model
169
- achieved:
170
 
171
- | Metric | Value |
172
- |--------|-------|
173
- | Peak Reward | **0.27** (vs 0.09 baseline) |
174
- | Tool Calls/Episode | Converged to **4.0** |
175
- | Tool Failures | **0** across 300 episodes |
176
- | Training Time | **71.3 minutes** |
177
 
178
- ### πŸ”„ Qwen3-1.7B (HF Jobs β€” In Progress)
179
-
180
- Running on HuggingFace infrastructure (T4-medium), confirming reward architecture transfers cleanly:
181
-
182
- | Step | Loss | Reward | Tool Calls | Entropy |
183
- |------|------|--------|------------|---------|
184
- | 5 | 0.184 | **0.195** | **3.9** | 0.132 |
185
- | 10 | 0.116 | 0.195 | **3.9** | 0.127 |
186
- | 15 | 0.088 | 0.180 | 3.6 | 0.028 |
187
- | 20 | 0.186 | 0.190 | 3.8 | 0.047 |
188
-
189
- ### βœ… Qwen3-0.6B (Proof of Concept)
190
-
191
- Initial validation: reward improved from **0.09 β†’ 0.30** (+222%) over 500 episodes on T4 GPU.
192
-
193
- </div>
194
- """
195
-
196
-
197
- # ── About Tab Content ────────────────────────────────────────────────────────
198
-
199
- ABOUT_MD = """
200
- <div class="prose">
201
-
202
- ## Technical Summary
203
-
204
- | Parameter | 0.6B Run | 4B Run | 1.7B Run |
205
- |-----------|----------|--------|----------|
206
- | Model | Qwen/Qwen3-0.6B | Qwen/Qwen3-4B | Qwen/Qwen3-1.7B |
207
- | GPU | T4 (Colab) | RTX 4090 (RunPod) | T4 (HF Jobs) |
208
- | Quantization | None | 4-bit QLoRA | 4-bit QLoRA |
209
- | Adapter | Full model | LoRA (r=16) | LoRA (r=16) |
210
- | Episodes | 500 | 300 | 500 (ongoing) |
211
- | Training Time | ~2 hours | ~71 minutes | ~7 hours |
212
- | Framework | TRL GRPOTrainer | TRL GRPOTrainer | TRL GRPOTrainer |
213
-
214
- ## Links
215
-
216
- - 🏒 [Live Environment](https://huggingface.co/spaces/musharraf7/esctr-environment)
217
- - πŸ“ [Blog Post](https://huggingface.co/spaces/musharraf7/esctr-environment/blob/main/Blog.md)
218
- - πŸ“Š [Training Dashboard (Trackio)](https://huggingface.co/spaces/musharraf7/esctr-grpo-trained)
219
- - πŸ’» [Source Code (GitHub)](https://github.com/Musharraf1128/esctr-environment)
220
- - πŸ‹οΈ Training Scripts:
221
- [train.py](https://huggingface.co/spaces/musharraf7/esctr-environment/blob/main/train.py) Β·
222
- [train_4b.py](https://huggingface.co/spaces/musharraf7/esctr-environment/blob/main/train_4b.py) Β·
223
- [train_hf_jobs.py](https://huggingface.co/spaces/musharraf7/esctr-environment/blob/main/train_hf_jobs.py)
224
-
225
- ## Why This Matters
226
-
227
- ESCTR demonstrates that **RLVR can teach LLMs enterprise-grade financial reasoning** β€” a
228
- domain nearly absent from existing RL training benchmarks.
229
-
230
- Unlike game environments, our environment tests capabilities that exist in production:
231
-
232
- - **Real-world professional skills** β€” procurement auditing, SLA enforcement, dispute resolution
233
- - **Adversarial reasoning** β€” vendor negotiation with active deception
234
- - **Verifiable, precise rewards** β€” exact answers from contract mathematics
235
- - **Production integration** β€” the tool interface could plug into SAP or Oracle
236
-
237
- ---
238
-
239
- *Built for the [OpenEnv Hackathon](https://github.com/meta-pytorch/OpenEnv) by Musharraf.*
240
-
241
- </div>
242
  """
243
 
244
 
@@ -348,6 +274,15 @@ def submit_decision(env, log, step_count, amount, reason):
348
 
349
  PLOT_BASE = "https://raw.githubusercontent.com/Musharraf1128/esctr-environment/main/plots"
350
 
 
 
 
 
 
 
 
 
 
351
  def build_gradio_app():
352
  with gr.Blocks(title="ESCTR Environment", css=CSS) as demo:
353
 
@@ -358,7 +293,7 @@ def build_gradio_app():
358
  # ── Header ────────────────────────────────────────────
359
  gr.HTML("""
360
  <div class="main-header">
361
- <h1>🏒 ESCTR</h1>
362
  <div class="subtitle">Enterprise Supply Chain & Tax Reconciliation</div>
363
  <div class="links">
364
  OpenEnv Hackathon 2026 Β·
@@ -374,20 +309,20 @@ def build_gradio_app():
374
 
375
  # ── Tab 1: Overview ───────────────────────────────
376
  with gr.Tab("Readme"):
377
- gr.HTML(OVERVIEW_MD)
378
 
379
  # ── Tab 2: Playground ─────────────────────────────
380
  with gr.Tab("Playground"):
381
  with gr.Row():
382
  # Left: Controls
383
  with gr.Column(scale=1):
384
- gr.HTML('<div class="surface"><h3 style="margin-top:0;">Episode Controls</h3>')
385
 
386
  task_dropdown = gr.Dropdown(
387
  choices=[
388
- ("🟒 Procurement Reconciliation", "procurement_reconciliation"),
389
- ("🟑 SLA Enforcement", "sla_enforcement"),
390
- ("πŸ”΄ Adversarial Auditing", "adversarial_auditing"),
391
  ],
392
  value="procurement_reconciliation",
393
  label="Task",
@@ -397,25 +332,25 @@ def build_gradio_app():
397
  placeholder="e.g., 42",
398
  value="",
399
  )
400
- reset_btn = gr.Button("πŸ”„ Start New Episode", variant="primary", size="lg")
401
- gr.HTML('<div style="height:0.8rem;"></div><h3 style="margin:0.2rem 0 0.6rem 0;">Tools</h3>')
402
 
403
- with gr.Accordion("πŸ“Š Query Database", open=True):
404
  db_table = gr.Dropdown(
405
  choices=["purchase_orders", "invoices", "shipping_logs", "sla_contracts", "warehouse_logs"],
406
  label="Table", value="purchase_orders",
407
  )
408
  query_btn = gr.Button("Run Query", elem_classes="tool-btn")
409
 
410
- with gr.Accordion("πŸ“„ Read Document", open=False):
411
  doc_id_input = gr.Textbox(label="Document ID", placeholder="PO-2025-1234")
412
  read_btn = gr.Button("Read Document", elem_classes="tool-btn")
413
 
414
- with gr.Accordion("πŸ’¬ Contact Vendor", open=False):
415
  vendor_msg = gr.Textbox(label="Message", placeholder="We reject your settlement...", lines=2)
416
  vendor_btn = gr.Button("Send Message", elem_classes="tool-btn")
417
 
418
- with gr.Accordion("βš–οΈ Submit Decision", open=False):
419
  adj_amount = gr.Textbox(label="Adjustment ($)", placeholder="-450.00")
420
  adj_reason = gr.Textbox(label="Reason", placeholder="Overcharge on line item...", lines=2)
421
  submit_btn = gr.Button("Submit Decision", variant="stop", elem_classes="tool-btn")
@@ -428,10 +363,10 @@ def build_gradio_app():
428
 
429
  with gr.Row():
430
  reward_display = gr.Textbox(
431
- label="πŸ’° Reward", value="β€”",
432
  interactive=False, elem_classes="reward-big",
433
  )
434
- seed_display = gr.Textbox(label="🎲 Seed", value="β€”", interactive=False)
435
 
436
  log_output = gr.Textbox(
437
  label="Investigation Log",
@@ -443,9 +378,9 @@ def build_gradio_app():
443
 
444
  # ── Tab 3: Training ───────────────────────────────
445
  with gr.Tab("Logs"):
446
- gr.HTML(TRAINING_MD)
447
 
448
- gr.Markdown("### πŸ“ˆ Training Plots")
449
 
450
  with gr.Row():
451
  gr.Image(
@@ -483,9 +418,9 @@ def build_gradio_app():
483
  elem_classes="plot-img",
484
  )
485
 
486
- # ── Tab 4: About ─────────────────────────────────
487
- with gr.Tab("Fleet"):
488
- gr.HTML(ABOUT_MD)
489
 
490
  # ── Event Handlers ────────────────────────────────────
491
 
 
1
  """Interactive Gradio UI for the ESCTR Environment."""
2
 
3
+ from pathlib import Path
4
  import random
5
+
6
+ import gradio as gr
7
+
8
  from .environment import ESCTREnvironment
9
  from .models import ESCTRAction
10
 
11
 
 
 
12
  CSS = """
13
  body, .gradio-container {
14
+ background-color: #e8f4f8 !important;
 
15
  font-family: 'Times New Roman', Times, Georgia, serif !important;
16
+ color: #2d3748 !important;
17
+ font-size: 18px !important;
18
  }
19
  .gradio-container {
20
  max-width: 1120px !important;
21
  margin: 0 auto !important;
22
  }
23
+ footer, .gradio-container > footer, .built-with { display: none !important; }
24
+
25
+ .main-header {
26
+ text-align: center;
27
+ padding: 1rem 0.5rem 0.6rem 0.5rem;
28
+ }
29
  .main-header h1 {
30
  margin: 0;
31
+ font-size: 2.05rem;
32
  font-weight: 600;
33
  color: #1a202c;
34
  }
35
  .main-header .subtitle {
36
+ margin-top: 0.25rem;
37
  color: #5a6b7a;
38
  font-style: italic;
39
  font-size: 1rem;
40
  }
41
+ .main-header .links {
42
+ margin-top: 0.4rem;
43
+ font-size: 0.86rem;
44
+ color: #718096;
45
+ }
46
+ .main-header .links a {
47
+ color: #3d4f5f;
48
+ text-decoration: none;
49
+ border-bottom: 1px dotted #8a9caa;
50
+ }
51
+ .main-header .links a:hover { opacity: 0.7; }
52
 
53
+ .tabs, .tab-nav, .tabitem, .tabitem > .column {
 
 
 
54
  border: none !important;
55
  background: transparent !important;
56
+ box-shadow: none !important;
57
+ }
58
+ .tab-nav {
59
+ justify-content: center !important;
60
+ gap: 0.2rem !important;
61
+ margin-bottom: 0.2rem !important;
62
  }
63
  .tab-nav button {
64
  border: 1px solid transparent !important;
65
  background: none !important;
 
66
  color: #2d3748 !important;
67
+ border-radius: 3px !important;
68
+ font-size: 0.94rem !important;
69
+ padding: 0.38rem 0.88rem !important;
70
  }
71
  .tab-nav button.selected {
72
+ font-weight: 700 !important;
73
  border-color: #2d3748 !important;
74
  color: #1a202c !important;
 
 
 
 
 
 
75
  }
76
 
77
  .surface {
78
+ background: #f7fcfe;
79
+ border: 1px solid #d8e7ee;
80
  border-radius: 8px;
81
+ padding: 0.9rem;
82
  }
83
 
84
+ .esctr-page .markdown-body,
85
+ .esctr-page .prose {
86
+ max-width: 820px !important;
87
+ margin: 0 auto !important;
88
+ line-height: 1.85 !important;
89
+ color: #3b4a5a !important;
90
+ }
91
+ .esctr-page h1, .esctr-page h2, .esctr-page h3 {
92
+ color: #1a202c !important;
93
+ font-weight: 600 !important;
94
+ }
95
+ .esctr-page table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
96
+ .esctr-page th, .esctr-page td {
97
+ border-bottom: 1px solid #d7e5ec;
98
+ padding: 0.45rem 0.5rem;
99
+ text-align: left;
100
  }
 
 
 
101
 
102
  .log-box textarea {
103
  font-family: 'Courier New', Consolas, monospace !important;
 
105
  line-height: 1.55 !important;
106
  }
107
  .reward-big textarea {
108
+ font-size: 1.9rem !important;
109
  font-weight: 700 !important;
110
  text-align: center !important;
111
  }
112
  .tool-btn { min-height: 42px !important; }
113
+ .plot-img img {
114
+ border-radius: 6px;
115
+ border: 1px solid #dbe7ee;
116
+ background: #f7fcfe;
117
+ }
118
  """
119
 
120
 
121
  # ── Overview Tab Content ───────────────────────────────────────────────���─────
122
 
123
  OVERVIEW_MD = """
124
+ # ESCTR
125
+ *Enterprise Supply Chain & Tax Reconciliation*
 
 
 
126
 
127
+ ESCTR is an OpenEnv-compatible reinforcement learning environment for enterprise financial operations.
128
+ An agent plays the role of a financial controller and must investigate discrepancies, compute valid
129
+ adjustments, and handle adversarial vendor interactions.
 
130
 
131
+ ## Task Suite
 
 
132
 
133
+ | Task | Difficulty | Objective |
134
+ |---|---|---|
135
+ | Procurement Reconciliation | Easy | Detect and quantify invoice overcharges |
136
+ | SLA Enforcement | Medium | Compute penalties from delivery delays and SLA terms |
137
+ | Adversarial Auditing | Hard | Resolve disputes and reject invalid settlement tactics |
138
 
139
+ ## Tools
 
 
 
 
140
 
141
+ - `query_database` for operational and financial tables
142
+ - `read_document` for raw contract and transaction artifacts
143
+ - `communicate_vendor` for negotiation and claim rebuttal
144
+ - `submit_financial_decision` for final terminal adjustment
145
 
146
+ ## Reward Shape
 
 
 
147
 
148
+ `R_total = alpha * R_outcome + beta * R_trajectory - penalties`
149
 
150
+ - Outcome rewards exact final correctness
151
+ - Trajectory rewards disciplined investigative flow
152
+ - Penalties discourage invalid actions and poor process
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  """
154
 
155
 
156
  # ── Training Tab Content ─────────────────────────────────────────────────────
157
 
158
  TRAINING_MD = """
159
+ # Logs
 
 
 
 
 
 
 
 
160
 
161
+ Training snapshots from ESCTR runs:
 
 
162
 
163
+ - Qwen3-4B: stabilized tool usage with improved reward profile
164
+ - Qwen3-1.7B: transfer behavior under HF Jobs infrastructure
165
+ - Qwen3-0.6B: proof-of-concept baseline for fast iteration
 
 
 
166
 
167
+ Plots below summarize reward movement, tool discipline, and loss trajectory.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  """
169
 
170
 
 
274
 
275
  PLOT_BASE = "https://raw.githubusercontent.com/Musharraf1128/esctr-environment/main/plots"
276
 
277
+
278
+ def _load_blog_markdown() -> str:
279
+ root = Path(__file__).resolve().parents[1]
280
+ blog_path = root / "Blog.md"
281
+ if blog_path.exists():
282
+ return blog_path.read_text(encoding="utf-8")
283
+ return "# Blog\n\nBlog content not found."
284
+
285
+
286
  def build_gradio_app():
287
  with gr.Blocks(title="ESCTR Environment", css=CSS) as demo:
288
 
 
293
  # ── Header ────────────────────────────────────────────
294
  gr.HTML("""
295
  <div class="main-header">
296
+ <h1>ESCTR</h1>
297
  <div class="subtitle">Enterprise Supply Chain & Tax Reconciliation</div>
298
  <div class="links">
299
  OpenEnv Hackathon 2026 Β·
 
309
 
310
  # ── Tab 1: Overview ───────────────────────────────
311
  with gr.Tab("Readme"):
312
+ gr.Markdown(OVERVIEW_MD, elem_classes=["esctr-page"])
313
 
314
  # ── Tab 2: Playground ─────────────────────────────
315
  with gr.Tab("Playground"):
316
  with gr.Row():
317
  # Left: Controls
318
  with gr.Column(scale=1):
319
+ gr.HTML('<div class="surface"><h3 style="margin-top:0; margin-bottom:0.7rem;">Episode Controls</h3>')
320
 
321
  task_dropdown = gr.Dropdown(
322
  choices=[
323
+ ("Procurement Reconciliation", "procurement_reconciliation"),
324
+ ("SLA Enforcement", "sla_enforcement"),
325
+ ("Adversarial Auditing", "adversarial_auditing"),
326
  ],
327
  value="procurement_reconciliation",
328
  label="Task",
 
332
  placeholder="e.g., 42",
333
  value="",
334
  )
335
+ reset_btn = gr.Button("Start New Episode", variant="primary", size="lg")
336
+ gr.HTML('<div style="height:0.8rem;"></div><h3 style="margin:0.2rem 0 0.65rem 0;">Tools</h3>')
337
 
338
+ with gr.Accordion("Query Database", open=True):
339
  db_table = gr.Dropdown(
340
  choices=["purchase_orders", "invoices", "shipping_logs", "sla_contracts", "warehouse_logs"],
341
  label="Table", value="purchase_orders",
342
  )
343
  query_btn = gr.Button("Run Query", elem_classes="tool-btn")
344
 
345
+ with gr.Accordion("Read Document", open=False):
346
  doc_id_input = gr.Textbox(label="Document ID", placeholder="PO-2025-1234")
347
  read_btn = gr.Button("Read Document", elem_classes="tool-btn")
348
 
349
+ with gr.Accordion("Contact Vendor", open=False):
350
  vendor_msg = gr.Textbox(label="Message", placeholder="We reject your settlement...", lines=2)
351
  vendor_btn = gr.Button("Send Message", elem_classes="tool-btn")
352
 
353
+ with gr.Accordion("Submit Decision", open=False):
354
  adj_amount = gr.Textbox(label="Adjustment ($)", placeholder="-450.00")
355
  adj_reason = gr.Textbox(label="Reason", placeholder="Overcharge on line item...", lines=2)
356
  submit_btn = gr.Button("Submit Decision", variant="stop", elem_classes="tool-btn")
 
363
 
364
  with gr.Row():
365
  reward_display = gr.Textbox(
366
+ label="Reward", value="β€”",
367
  interactive=False, elem_classes="reward-big",
368
  )
369
+ seed_display = gr.Textbox(label="Seed", value="β€”", interactive=False)
370
 
371
  log_output = gr.Textbox(
372
  label="Investigation Log",
 
378
 
379
  # ── Tab 3: Training ───────────────────────────────
380
  with gr.Tab("Logs"):
381
+ gr.Markdown(TRAINING_MD, elem_classes=["esctr-page"])
382
 
383
+ gr.Markdown("### Training Plots", elem_classes=["esctr-page"])
384
 
385
  with gr.Row():
386
  gr.Image(
 
418
  elem_classes="plot-img",
419
  )
420
 
421
+ # ── Tab 4: Blog ──────────────────────────────────
422
+ with gr.Tab("Blog"):
423
+ gr.Markdown(_load_blog_markdown(), elem_classes=["esctr-page"])
424
 
425
  # ── Event Handlers ────────────────────────────────────
426