Tarun Claude Sonnet 4.6 commited on
Commit
e724ab0
Β·
1 Parent(s): a89bbe8

Add mobile responsiveness and state diagram to Scenarios tab

Browse files

Mobile fixes:
- Add CSS media queries (@media 900px, 600px) for padding, font sizes,
stat grid (6-col -> 3 -> 2), unique-grid (auto -> 1-col), metrics-bar
(4-col -> 2-col), table horizontal scroll
- Add class='hero-section', 'stat-grid-resp', 'unique-grid-resp' to
inline-styled divs so media queries can override with !important
- Benchmark table and arch code block wrapped in overflow-x:auto containers
- .table-wrap changed to overflow-x:auto for scenario tables on mobile

State diagram:
- New _state_diagram_html() function: 5-node flow diagram
(reset -> Phase1 -> Phase2 -> Phase3 -> done) with per-node reward
badges, state-after annotations, and transition labels
- Embedded <style> block makes diagram stack vertically on <=640px
- Added to top of Scenarios tab via _scenarios_html()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +137 -6
  2. server/app.py +137 -6
app.py CHANGED
@@ -316,7 +316,7 @@ CSS = """
316
  .gradio-container .wrap { border: none !important; }
317
  .gradio-container .output-html { padding: 0 !important; }
318
 
319
- /* ── Tab bar ──────────────────────────────────────────────────────── */
320
  .gradio-container .tab-nav {
321
  background: #0d0d0d !important;
322
  border-bottom: 1px solid #1f1f1f !important;
@@ -698,6 +698,32 @@ button.secondary:hover { border-color: #444 !important; color: #e8e8e8 !importan
698
  }
699
  .footer a { color: #555; text-decoration: none; }
700
  .footer a:hover { color: #00d084; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  """
702
 
703
  # ---------------------------------------------------------------------------
@@ -739,12 +765,12 @@ def _hero_html() -> str:
739
  _stat_card("Reward", "[0.002,&nbsp;0.998]", small=True)
740
  )
741
  return (
742
- f"<div style='background:#141414;border-bottom:1px solid #1f1f1f;"
743
  f"padding:32px 32px 28px;font-family:Inter,sans-serif'>"
744
  f"<div style='font-size:26px;font-weight:700;color:#f0f0f0;line-height:1.2;"
745
  f"margin-bottom:10px;letter-spacing:-.3px'>Customer Support Agent</div>"
746
  f"<div style='font-size:13px;color:#666;line-height:1.75;max-width:860px'>{desc}</div>"
747
- f"<div style='display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:24px'>"
748
  f"{stats}"
749
  f"</div></div>"
750
  )
@@ -797,7 +823,7 @@ def _overview_html() -> str:
797
  "contaminating the reward signal across runs."),
798
  ]
799
  grid = (
800
- f"<div style='display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));"
801
  f"gap:14px;margin-top:4px'>"
802
  + "".join(_unique_card(icon, title, body) for icon, title, body in cards)
803
  + "</div>"
@@ -863,7 +889,7 @@ def _overview_html() -> str:
863
  "border-bottom:2px solid #1f1f1f;font-family:Inter,sans-serif'"
864
  )
865
  bench_table = (
866
- f"<div style='background:#141414;border:1px solid #1f1f1f;border-radius:8px;overflow:hidden;margin-top:4px'>"
867
  f"<table style='width:100%;border-collapse:collapse;font-size:12px;font-family:\"JetBrains Mono\",monospace'>"
868
  f"<thead><tr>"
869
  f"<th {th}>Model</th>"
@@ -875,7 +901,7 @@ def _overview_html() -> str:
875
  f"<th {thc}>Overall</th>"
876
  f"</tr></thead>"
877
  f"<tbody>{bench_html_rows}</tbody>"
878
- f"</table></div>"
879
  )
880
 
881
  return (
@@ -900,6 +926,110 @@ def _overview_html() -> str:
900
 
901
 
902
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
903
  def _scenarios_html() -> str:
904
  out = ""
905
  tier_meta = {
@@ -961,6 +1091,7 @@ def _scenarios_html() -> str:
961
  </div>
962
  </div>"""
963
  return f"""
 
964
  <h2 class='section-h'>30 support tickets Β· 3 difficulty tiers</h2>
965
  <p class='section-sub'>
966
  Each scenario runs the full 3-phase MDP. Hover over a message cell to read the full text.
 
316
  .gradio-container .wrap { border: none !important; }
317
  .gradio-container .output-html { padding: 0 !important; }
318
 
319
+ /* ���─ Tab bar ──────────────���────────��────────────────���─────────────── */
320
  .gradio-container .tab-nav {
321
  background: #0d0d0d !important;
322
  border-bottom: 1px solid #1f1f1f !important;
 
698
  }
699
  .footer a { color: #555; text-decoration: none; }
700
  .footer a:hover { color: #00d084; }
701
+
702
+ /* ── Table scrolling (all viewports) ─────────────────────────────── */
703
+ .table-wrap { overflow-x: auto !important; }
704
+
705
+ /* ── Mobile / tablet breakpoints ─────────────────────────────────── */
706
+ @media (max-width: 900px) {
707
+ .gradio-container > .main { padding: 0 16px 32px !important; }
708
+ .gradio-container .tab-nav { padding: 0 16px !important; }
709
+ .hero-section { padding: 24px 16px 20px !important; }
710
+ .stat-grid-resp { grid-template-columns: repeat(3, 1fr) !important; }
711
+ .unique-grid-resp { grid-template-columns: 1fr !important; }
712
+ .metrics-bar { grid-template-columns: repeat(2, 1fr) !important; }
713
+ .msg-cell { max-width: 180px !important; }
714
+ }
715
+
716
+ @media (max-width: 600px) {
717
+ .gradio-container > .main { padding: 0 10px 20px !important; }
718
+ .gradio-container .tab-nav { padding: 0 4px !important; }
719
+ .gradio-container .tab-nav button { padding: 10px 10px !important; font-size: 11px !important; }
720
+ .hero-section { padding: 16px 12px 14px !important; }
721
+ .stat-grid-resp { grid-template-columns: repeat(2, 1fr) !important; }
722
+ .metrics-bar { grid-template-columns: 1fr 1fr !important; }
723
+ .msg-cell { max-width: 120px !important; }
724
+ .kw-cell { display: none !important; }
725
+ .hide-mobile { display: none !important; }
726
+ }
727
  """
728
 
729
  # ---------------------------------------------------------------------------
 
765
  _stat_card("Reward", "[0.002,&nbsp;0.998]", small=True)
766
  )
767
  return (
768
+ f"<div class='hero-section' style='background:#141414;border-bottom:1px solid #1f1f1f;"
769
  f"padding:32px 32px 28px;font-family:Inter,sans-serif'>"
770
  f"<div style='font-size:26px;font-weight:700;color:#f0f0f0;line-height:1.2;"
771
  f"margin-bottom:10px;letter-spacing:-.3px'>Customer Support Agent</div>"
772
  f"<div style='font-size:13px;color:#666;line-height:1.75;max-width:860px'>{desc}</div>"
773
+ f"<div class='stat-grid-resp' style='display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:24px'>"
774
  f"{stats}"
775
  f"</div></div>"
776
  )
 
823
  "contaminating the reward signal across runs."),
824
  ]
825
  grid = (
826
+ f"<div class='unique-grid-resp' style='display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));"
827
  f"gap:14px;margin-top:4px'>"
828
  + "".join(_unique_card(icon, title, body) for icon, title, body in cards)
829
  + "</div>"
 
889
  "border-bottom:2px solid #1f1f1f;font-family:Inter,sans-serif'"
890
  )
891
  bench_table = (
892
+ f"<div style='overflow-x:auto;margin-top:4px'><div style='background:#141414;border:1px solid #1f1f1f;border-radius:8px;overflow:hidden;min-width:520px'>"
893
  f"<table style='width:100%;border-collapse:collapse;font-size:12px;font-family:\"JetBrains Mono\",monospace'>"
894
  f"<thead><tr>"
895
  f"<th {th}>Model</th>"
 
901
  f"<th {thc}>Overall</th>"
902
  f"</tr></thead>"
903
  f"<tbody>{bench_html_rows}</tbody>"
904
+ f"</table></div></div>"
905
  )
906
 
907
  return (
 
926
 
927
 
928
 
929
+ def _state_diagram_html() -> str:
930
+ def node(phase_num: int, color: str, label: str, action: str, state_after: str, rewards: list) -> str:
931
+ reward_pills = "".join(
932
+ f"<span style='background:{c}11;color:{c};border:1px solid {c}33;"
933
+ f"border-radius:3px;padding:1px 6px;font-size:9px;"
934
+ f"font-family:\"JetBrains Mono\",monospace;font-weight:700'>{r}</span> "
935
+ for r, c in rewards
936
+ )
937
+ return (
938
+ f"<div class='sd-node' style='flex:1;min-width:150px'>"
939
+ f"<div style='background:#141414;border:2px solid {color}44;border-radius:10px;"
940
+ f"padding:14px 12px;height:100%;box-sizing:border-box'>"
941
+ f"<div style='font-size:9px;font-weight:700;color:{color};letter-spacing:1.5px;"
942
+ f"text-transform:uppercase;margin-bottom:6px'>Phase {phase_num}</div>"
943
+ f"<div style='font-size:14px;font-weight:700;color:#f0f0f0;margin-bottom:10px'>{label}</div>"
944
+ f"<div style='background:{color}0d;border:1px solid {color}22;border-radius:5px;"
945
+ f"padding:6px 8px;margin-bottom:10px;font-family:\"JetBrains Mono\",monospace;"
946
+ f"font-size:10px;color:#aaa'>{action}</div>"
947
+ f"<div style='font-size:9px;color:#444;text-transform:uppercase;letter-spacing:.8px;margin-bottom:5px'>State after</div>"
948
+ f"<div style='font-size:10px;font-family:\"JetBrains Mono\",monospace;color:#555;"
949
+ f"line-height:1.8;margin-bottom:10px'>{state_after}</div>"
950
+ f"<div style='display:flex;gap:3px;flex-wrap:wrap'>{reward_pills}</div>"
951
+ f"</div></div>"
952
+ )
953
+
954
+ def arrow(label: str) -> str:
955
+ return (
956
+ f"<div class='sd-arrow' style='display:flex;flex-direction:column;align-items:center;"
957
+ f"justify-content:center;width:52px;flex-shrink:0;padding-top:36px'>"
958
+ f"<div style='font-size:9px;color:#333;text-align:center;margin-bottom:5px;"
959
+ f"font-family:\"JetBrains Mono\",monospace;line-height:1.5;white-space:nowrap'>{label}</div>"
960
+ f"<div style='display:flex;align-items:center;width:100%'>"
961
+ f"<div style='flex:1;height:1px;background:linear-gradient(to right,#222,#333)'></div>"
962
+ f"<div style='color:#444;font-size:12px;line-height:1'>&#9654;</div>"
963
+ f"</div></div>"
964
+ )
965
+
966
+ reset_node = (
967
+ f"<div class='sd-node' style='flex:0 0 auto;min-width:110px;max-width:130px'>"
968
+ f"<div style='background:#111;border:1px solid #222;border-radius:10px;"
969
+ f"padding:14px 12px;height:100%;box-sizing:border-box'>"
970
+ f"<div style='font-size:9px;color:#333;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:6px'>Start</div>"
971
+ f"<div style='font-size:13px;font-weight:600;color:#555;margin-bottom:12px;font-family:\"JetBrains Mono\",monospace'>reset()</div>"
972
+ f"<div style='font-size:10px;font-family:\"JetBrains Mono\",monospace;color:#3a3a3a;line-height:1.8'>"
973
+ f"issue_type:<br><span style='color:#444'>unknown</span><br>history: []<br>phase: 1"
974
+ f"</div></div></div>"
975
+ )
976
+
977
+ done_node = (
978
+ f"<div class='sd-node' style='flex:0 0 auto;min-width:110px;max-width:130px'>"
979
+ f"<div style='background:#111;border:1px solid #00d08433;border-radius:10px;"
980
+ f"padding:14px 12px;height:100%;box-sizing:border-box'>"
981
+ f"<div style='font-size:9px;color:#00d084;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:6px'>End</div>"
982
+ f"<div style='font-size:13px;font-weight:600;color:#00d084;margin-bottom:12px;font-family:\"JetBrains Mono\",monospace'>done=True</div>"
983
+ f"<div style='font-size:10px;font-family:\"JetBrains Mono\",monospace;color:#3a3a3a;line-height:1.8'>"
984
+ f"score =<br>mean(r1,r2,r3)"
985
+ f"</div></div></div>"
986
+ )
987
+
988
+ p1 = node(1, "#60a5fa", "Triage",
989
+ "classify + issue_type",
990
+ "issue_type: <span style='color:#60a5fa'>revealed</span><br>history: +1 msg",
991
+ [("0.998","#00d084"),("0.55","#ffa94d"),("0.002","#ff6b6b")])
992
+ p2 = node(2, "#ffa94d", "Route",
993
+ "assign + team",
994
+ "team: <span style='color:#ffa94d'>in history</span><br>phase: 3",
995
+ [("0.998","#00d084"),("0.400","#ffa94d"),("0.002","#ff6b6b")])
996
+ p3 = node(3, "#00d084", "Resolve",
997
+ "respond / refund / escalate",
998
+ "done: <span style='color:#00d084'>True</span><br>or retry if r&lt;0.5",
999
+ [("0.002–0.998","#00d084")])
1000
+
1001
+ a01 = arrow("")
1002
+ a12 = arrow("issue_type<br>revealed")
1003
+ a23 = arrow("team added<br>to history")
1004
+ a3d = arrow("episode<br>ends")
1005
+
1006
+ return (
1007
+ f"<style>"
1008
+ f"@media(max-width:640px){{"
1009
+ f".sd-flow{{flex-direction:column!important;}}"
1010
+ f".sd-arrow{{flex-direction:row!important;width:100%!important;padding-top:0!important;"
1011
+ f"padding-left:36px!important;height:36px!important;}}"
1012
+ f".sd-arrow>div:last-child{{flex-direction:row!important;}}"
1013
+ f"}}"
1014
+ f"</style>"
1015
+ f"<div style='font-family:Inter,sans-serif;margin-bottom:36px'>"
1016
+ f"<div style='font-size:18px;font-weight:600;color:#f0f0f0;margin-bottom:6px'>Episode State Diagram</div>"
1017
+ f"<div style='font-size:13px;color:#666;margin-bottom:20px;line-height:1.6'>"
1018
+ f"State evolves at each phase transition. The agent sees richer context at every step β€” "
1019
+ f"decisions in early phases directly constrain options in later ones.</div>"
1020
+ f"<div style='overflow-x:auto;padding-bottom:4px'>"
1021
+ f"<div class='sd-flow' style='display:flex;align-items:stretch;gap:0;min-width:620px'>"
1022
+ f"{reset_node}{a01}{p1}{a12}{p2}{a23}{p3}{a3d}{done_node}"
1023
+ f"</div></div>"
1024
+ f"<div style='display:flex;gap:20px;margin-top:12px;flex-wrap:wrap'>"
1025
+ f"<span style='font-size:11px;color:#444;font-family:\"JetBrains Mono\",monospace'>"
1026
+ f"<span style='color:#00d084'>&#9632;</span> correct &nbsp;"
1027
+ f"<span style='color:#ffa94d'>&#9632;</span> partial/wrong-team &nbsp;"
1028
+ f"<span style='color:#ff6b6b'>&#9632;</span> wrong action</span>"
1029
+ f"</div></div>"
1030
+ )
1031
+
1032
+
1033
  def _scenarios_html() -> str:
1034
  out = ""
1035
  tier_meta = {
 
1091
  </div>
1092
  </div>"""
1093
  return f"""
1094
+ {_state_diagram_html()}
1095
  <h2 class='section-h'>30 support tickets Β· 3 difficulty tiers</h2>
1096
  <p class='section-sub'>
1097
  Each scenario runs the full 3-phase MDP. Hover over a message cell to read the full text.
server/app.py CHANGED
@@ -316,7 +316,7 @@ CSS = """
316
  .gradio-container .wrap { border: none !important; }
317
  .gradio-container .output-html { padding: 0 !important; }
318
 
319
- /* ── Tab bar ──────────────────────────────────────────────────────── */
320
  .gradio-container .tab-nav {
321
  background: #0d0d0d !important;
322
  border-bottom: 1px solid #1f1f1f !important;
@@ -698,6 +698,32 @@ button.secondary:hover { border-color: #444 !important; color: #e8e8e8 !importan
698
  }
699
  .footer a { color: #555; text-decoration: none; }
700
  .footer a:hover { color: #00d084; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  """
702
 
703
  # ---------------------------------------------------------------------------
@@ -739,12 +765,12 @@ def _hero_html() -> str:
739
  _stat_card("Reward", "[0.002,&nbsp;0.998]", small=True)
740
  )
741
  return (
742
- f"<div style='background:#141414;border-bottom:1px solid #1f1f1f;"
743
  f"padding:32px 32px 28px;font-family:Inter,sans-serif'>"
744
  f"<div style='font-size:26px;font-weight:700;color:#f0f0f0;line-height:1.2;"
745
  f"margin-bottom:10px;letter-spacing:-.3px'>Customer Support Agent</div>"
746
  f"<div style='font-size:13px;color:#666;line-height:1.75;max-width:860px'>{desc}</div>"
747
- f"<div style='display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:24px'>"
748
  f"{stats}"
749
  f"</div></div>"
750
  )
@@ -797,7 +823,7 @@ def _overview_html() -> str:
797
  "contaminating the reward signal across runs."),
798
  ]
799
  grid = (
800
- f"<div style='display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));"
801
  f"gap:14px;margin-top:4px'>"
802
  + "".join(_unique_card(icon, title, body) for icon, title, body in cards)
803
  + "</div>"
@@ -863,7 +889,7 @@ def _overview_html() -> str:
863
  "border-bottom:2px solid #1f1f1f;font-family:Inter,sans-serif'"
864
  )
865
  bench_table = (
866
- f"<div style='background:#141414;border:1px solid #1f1f1f;border-radius:8px;overflow:hidden;margin-top:4px'>"
867
  f"<table style='width:100%;border-collapse:collapse;font-size:12px;font-family:\"JetBrains Mono\",monospace'>"
868
  f"<thead><tr>"
869
  f"<th {th}>Model</th>"
@@ -875,7 +901,7 @@ def _overview_html() -> str:
875
  f"<th {thc}>Overall</th>"
876
  f"</tr></thead>"
877
  f"<tbody>{bench_html_rows}</tbody>"
878
- f"</table></div>"
879
  )
880
 
881
  return (
@@ -900,6 +926,110 @@ def _overview_html() -> str:
900
 
901
 
902
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
903
  def _scenarios_html() -> str:
904
  out = ""
905
  tier_meta = {
@@ -961,6 +1091,7 @@ def _scenarios_html() -> str:
961
  </div>
962
  </div>"""
963
  return f"""
 
964
  <h2 class='section-h'>30 support tickets Β· 3 difficulty tiers</h2>
965
  <p class='section-sub'>
966
  Each scenario runs the full 3-phase MDP. Hover over a message cell to read the full text.
 
316
  .gradio-container .wrap { border: none !important; }
317
  .gradio-container .output-html { padding: 0 !important; }
318
 
319
+ /* ���─ Tab bar ──────────────���────────��────────────────���─────────────── */
320
  .gradio-container .tab-nav {
321
  background: #0d0d0d !important;
322
  border-bottom: 1px solid #1f1f1f !important;
 
698
  }
699
  .footer a { color: #555; text-decoration: none; }
700
  .footer a:hover { color: #00d084; }
701
+
702
+ /* ── Table scrolling (all viewports) ─────────────────────────────── */
703
+ .table-wrap { overflow-x: auto !important; }
704
+
705
+ /* ── Mobile / tablet breakpoints ─────────────────────────────────── */
706
+ @media (max-width: 900px) {
707
+ .gradio-container > .main { padding: 0 16px 32px !important; }
708
+ .gradio-container .tab-nav { padding: 0 16px !important; }
709
+ .hero-section { padding: 24px 16px 20px !important; }
710
+ .stat-grid-resp { grid-template-columns: repeat(3, 1fr) !important; }
711
+ .unique-grid-resp { grid-template-columns: 1fr !important; }
712
+ .metrics-bar { grid-template-columns: repeat(2, 1fr) !important; }
713
+ .msg-cell { max-width: 180px !important; }
714
+ }
715
+
716
+ @media (max-width: 600px) {
717
+ .gradio-container > .main { padding: 0 10px 20px !important; }
718
+ .gradio-container .tab-nav { padding: 0 4px !important; }
719
+ .gradio-container .tab-nav button { padding: 10px 10px !important; font-size: 11px !important; }
720
+ .hero-section { padding: 16px 12px 14px !important; }
721
+ .stat-grid-resp { grid-template-columns: repeat(2, 1fr) !important; }
722
+ .metrics-bar { grid-template-columns: 1fr 1fr !important; }
723
+ .msg-cell { max-width: 120px !important; }
724
+ .kw-cell { display: none !important; }
725
+ .hide-mobile { display: none !important; }
726
+ }
727
  """
728
 
729
  # ---------------------------------------------------------------------------
 
765
  _stat_card("Reward", "[0.002,&nbsp;0.998]", small=True)
766
  )
767
  return (
768
+ f"<div class='hero-section' style='background:#141414;border-bottom:1px solid #1f1f1f;"
769
  f"padding:32px 32px 28px;font-family:Inter,sans-serif'>"
770
  f"<div style='font-size:26px;font-weight:700;color:#f0f0f0;line-height:1.2;"
771
  f"margin-bottom:10px;letter-spacing:-.3px'>Customer Support Agent</div>"
772
  f"<div style='font-size:13px;color:#666;line-height:1.75;max-width:860px'>{desc}</div>"
773
+ f"<div class='stat-grid-resp' style='display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:24px'>"
774
  f"{stats}"
775
  f"</div></div>"
776
  )
 
823
  "contaminating the reward signal across runs."),
824
  ]
825
  grid = (
826
+ f"<div class='unique-grid-resp' style='display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));"
827
  f"gap:14px;margin-top:4px'>"
828
  + "".join(_unique_card(icon, title, body) for icon, title, body in cards)
829
  + "</div>"
 
889
  "border-bottom:2px solid #1f1f1f;font-family:Inter,sans-serif'"
890
  )
891
  bench_table = (
892
+ f"<div style='overflow-x:auto;margin-top:4px'><div style='background:#141414;border:1px solid #1f1f1f;border-radius:8px;overflow:hidden;min-width:520px'>"
893
  f"<table style='width:100%;border-collapse:collapse;font-size:12px;font-family:\"JetBrains Mono\",monospace'>"
894
  f"<thead><tr>"
895
  f"<th {th}>Model</th>"
 
901
  f"<th {thc}>Overall</th>"
902
  f"</tr></thead>"
903
  f"<tbody>{bench_html_rows}</tbody>"
904
+ f"</table></div></div>"
905
  )
906
 
907
  return (
 
926
 
927
 
928
 
929
+ def _state_diagram_html() -> str:
930
+ def node(phase_num: int, color: str, label: str, action: str, state_after: str, rewards: list) -> str:
931
+ reward_pills = "".join(
932
+ f"<span style='background:{c}11;color:{c};border:1px solid {c}33;"
933
+ f"border-radius:3px;padding:1px 6px;font-size:9px;"
934
+ f"font-family:\"JetBrains Mono\",monospace;font-weight:700'>{r}</span> "
935
+ for r, c in rewards
936
+ )
937
+ return (
938
+ f"<div class='sd-node' style='flex:1;min-width:150px'>"
939
+ f"<div style='background:#141414;border:2px solid {color}44;border-radius:10px;"
940
+ f"padding:14px 12px;height:100%;box-sizing:border-box'>"
941
+ f"<div style='font-size:9px;font-weight:700;color:{color};letter-spacing:1.5px;"
942
+ f"text-transform:uppercase;margin-bottom:6px'>Phase {phase_num}</div>"
943
+ f"<div style='font-size:14px;font-weight:700;color:#f0f0f0;margin-bottom:10px'>{label}</div>"
944
+ f"<div style='background:{color}0d;border:1px solid {color}22;border-radius:5px;"
945
+ f"padding:6px 8px;margin-bottom:10px;font-family:\"JetBrains Mono\",monospace;"
946
+ f"font-size:10px;color:#aaa'>{action}</div>"
947
+ f"<div style='font-size:9px;color:#444;text-transform:uppercase;letter-spacing:.8px;margin-bottom:5px'>State after</div>"
948
+ f"<div style='font-size:10px;font-family:\"JetBrains Mono\",monospace;color:#555;"
949
+ f"line-height:1.8;margin-bottom:10px'>{state_after}</div>"
950
+ f"<div style='display:flex;gap:3px;flex-wrap:wrap'>{reward_pills}</div>"
951
+ f"</div></div>"
952
+ )
953
+
954
+ def arrow(label: str) -> str:
955
+ return (
956
+ f"<div class='sd-arrow' style='display:flex;flex-direction:column;align-items:center;"
957
+ f"justify-content:center;width:52px;flex-shrink:0;padding-top:36px'>"
958
+ f"<div style='font-size:9px;color:#333;text-align:center;margin-bottom:5px;"
959
+ f"font-family:\"JetBrains Mono\",monospace;line-height:1.5;white-space:nowrap'>{label}</div>"
960
+ f"<div style='display:flex;align-items:center;width:100%'>"
961
+ f"<div style='flex:1;height:1px;background:linear-gradient(to right,#222,#333)'></div>"
962
+ f"<div style='color:#444;font-size:12px;line-height:1'>&#9654;</div>"
963
+ f"</div></div>"
964
+ )
965
+
966
+ reset_node = (
967
+ f"<div class='sd-node' style='flex:0 0 auto;min-width:110px;max-width:130px'>"
968
+ f"<div style='background:#111;border:1px solid #222;border-radius:10px;"
969
+ f"padding:14px 12px;height:100%;box-sizing:border-box'>"
970
+ f"<div style='font-size:9px;color:#333;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:6px'>Start</div>"
971
+ f"<div style='font-size:13px;font-weight:600;color:#555;margin-bottom:12px;font-family:\"JetBrains Mono\",monospace'>reset()</div>"
972
+ f"<div style='font-size:10px;font-family:\"JetBrains Mono\",monospace;color:#3a3a3a;line-height:1.8'>"
973
+ f"issue_type:<br><span style='color:#444'>unknown</span><br>history: []<br>phase: 1"
974
+ f"</div></div></div>"
975
+ )
976
+
977
+ done_node = (
978
+ f"<div class='sd-node' style='flex:0 0 auto;min-width:110px;max-width:130px'>"
979
+ f"<div style='background:#111;border:1px solid #00d08433;border-radius:10px;"
980
+ f"padding:14px 12px;height:100%;box-sizing:border-box'>"
981
+ f"<div style='font-size:9px;color:#00d084;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:6px'>End</div>"
982
+ f"<div style='font-size:13px;font-weight:600;color:#00d084;margin-bottom:12px;font-family:\"JetBrains Mono\",monospace'>done=True</div>"
983
+ f"<div style='font-size:10px;font-family:\"JetBrains Mono\",monospace;color:#3a3a3a;line-height:1.8'>"
984
+ f"score =<br>mean(r1,r2,r3)"
985
+ f"</div></div></div>"
986
+ )
987
+
988
+ p1 = node(1, "#60a5fa", "Triage",
989
+ "classify + issue_type",
990
+ "issue_type: <span style='color:#60a5fa'>revealed</span><br>history: +1 msg",
991
+ [("0.998","#00d084"),("0.55","#ffa94d"),("0.002","#ff6b6b")])
992
+ p2 = node(2, "#ffa94d", "Route",
993
+ "assign + team",
994
+ "team: <span style='color:#ffa94d'>in history</span><br>phase: 3",
995
+ [("0.998","#00d084"),("0.400","#ffa94d"),("0.002","#ff6b6b")])
996
+ p3 = node(3, "#00d084", "Resolve",
997
+ "respond / refund / escalate",
998
+ "done: <span style='color:#00d084'>True</span><br>or retry if r&lt;0.5",
999
+ [("0.002–0.998","#00d084")])
1000
+
1001
+ a01 = arrow("")
1002
+ a12 = arrow("issue_type<br>revealed")
1003
+ a23 = arrow("team added<br>to history")
1004
+ a3d = arrow("episode<br>ends")
1005
+
1006
+ return (
1007
+ f"<style>"
1008
+ f"@media(max-width:640px){{"
1009
+ f".sd-flow{{flex-direction:column!important;}}"
1010
+ f".sd-arrow{{flex-direction:row!important;width:100%!important;padding-top:0!important;"
1011
+ f"padding-left:36px!important;height:36px!important;}}"
1012
+ f".sd-arrow>div:last-child{{flex-direction:row!important;}}"
1013
+ f"}}"
1014
+ f"</style>"
1015
+ f"<div style='font-family:Inter,sans-serif;margin-bottom:36px'>"
1016
+ f"<div style='font-size:18px;font-weight:600;color:#f0f0f0;margin-bottom:6px'>Episode State Diagram</div>"
1017
+ f"<div style='font-size:13px;color:#666;margin-bottom:20px;line-height:1.6'>"
1018
+ f"State evolves at each phase transition. The agent sees richer context at every step β€” "
1019
+ f"decisions in early phases directly constrain options in later ones.</div>"
1020
+ f"<div style='overflow-x:auto;padding-bottom:4px'>"
1021
+ f"<div class='sd-flow' style='display:flex;align-items:stretch;gap:0;min-width:620px'>"
1022
+ f"{reset_node}{a01}{p1}{a12}{p2}{a23}{p3}{a3d}{done_node}"
1023
+ f"</div></div>"
1024
+ f"<div style='display:flex;gap:20px;margin-top:12px;flex-wrap:wrap'>"
1025
+ f"<span style='font-size:11px;color:#444;font-family:\"JetBrains Mono\",monospace'>"
1026
+ f"<span style='color:#00d084'>&#9632;</span> correct &nbsp;"
1027
+ f"<span style='color:#ffa94d'>&#9632;</span> partial/wrong-team &nbsp;"
1028
+ f"<span style='color:#ff6b6b'>&#9632;</span> wrong action</span>"
1029
+ f"</div></div>"
1030
+ )
1031
+
1032
+
1033
  def _scenarios_html() -> str:
1034
  out = ""
1035
  tier_meta = {
 
1091
  </div>
1092
  </div>"""
1093
  return f"""
1094
+ {_state_diagram_html()}
1095
  <h2 class='section-h'>30 support tickets Β· 3 difficulty tiers</h2>
1096
  <p class='section-sub'>
1097
  Each scenario runs the full 3-phase MDP. Hover over a message cell to read the full text.