zukhriddinai commited on
Commit
57d015e
·
verified ·
1 Parent(s): 610121c

Deploy public authenticated AutoML gateway

Browse files
Files changed (4) hide show
  1. gateway_app.py +87 -122
  2. guided_workbench.py +17 -4
  3. studio_design.py +322 -0
  4. ui_design.py +7 -0
gateway_app.py CHANGED
@@ -38,6 +38,7 @@ from guided_workbench import (
38
  approval_status_html,
39
  approved_research_metadata,
40
  candidate_cards_html,
 
41
  candidate_is_executable,
42
  experiment_reflection_outputs,
43
  new_workbench_state,
@@ -54,15 +55,12 @@ from guided_workbench import (
54
  update_context,
55
  validate_state,
56
  )
 
57
  from ui_design import (
58
- APP_CSS,
59
  AUTOMATIC_SELECTION,
60
- INFERENCE_GUIDE_HTML,
61
  QUALITY_PRESET_CHOICES,
62
  RESEARCH_NOTICE_HTML,
63
- RESPONSIVE_HEAD_HTML,
64
  STUDIO_APP_HEADER_HTML,
65
- professional_theme,
66
  )
67
  from gateway_auth import (
68
  AuthResult,
@@ -2830,7 +2828,7 @@ def _sync_workbench_ui_state(
2830
  def _workbench_candidate_update(state: WorkbenchState):
2831
  candidates = (
2832
  [
2833
- (candidate.name or candidate.candidate_id, candidate.candidate_id)
2834
  for candidate in state.research_plan.candidates
2835
  if candidate_is_executable(
2836
  state.research_plan, candidate.candidate_id
@@ -3356,15 +3354,6 @@ def clear_workbench_variants_ui(state_data):
3356
 
3357
  def _add_results_section(label: str = "Latest run"):
3358
  with gr.Group(elem_classes=["results-shell"]):
3359
- gr.HTML(
3360
- f"""
3361
- <div class="results-heading">
3362
- <p class="section-kicker">Measured run record</p>
3363
- <h2>{html.escape(label)}</h2>
3364
- <p>Measured outputs remain separate from pre-run estimates.</p>
3365
- </div>
3366
- """
3367
- )
3368
  status = gr.Markdown(
3369
  "Status: _idle_",
3370
  visible=False,
@@ -3606,27 +3595,13 @@ def create_interface() -> gr.Blocks:
3606
 
3607
  with gr.Tabs(visible=False, elem_id="workspace-tabs") as main_tabs:
3608
  with gr.TabItem("Build"):
3609
- gr.HTML(
3610
- """
3611
- <section class="guided-workbench-heading studio-build-intro">
3612
- <div>
3613
- <p class="section-kicker">Guided research workbench</p>
3614
- <h2>Design a defensible training run</h2>
3615
- </div>
3616
- <p>
3617
- Move from data to execution while preserving the evidence,
3618
- assumptions, and decisions behind every component.
3619
- </p>
3620
- </section>
3621
- """
3622
- )
3623
  with gr.Row(
3624
  equal_height=False,
3625
  elem_classes=["guided-instrument-bench", "studio-workbench"],
3626
  ):
3627
  with gr.Column(
3628
- scale=2,
3629
- min_width=170,
3630
  elem_classes=["guided-step-rail", "studio-workflow"],
3631
  ):
3632
  gr.HTML(
@@ -3654,67 +3629,9 @@ def create_interface() -> gr.Blocks:
3654
 
3655
  with gr.Column(
3656
  scale=7,
3657
- min_width=520,
3658
  elem_classes=["guided-work-canvas", "studio-primary"],
3659
  ):
3660
- gr.HTML(
3661
- """
3662
- <div class="guided-canvas-heading">
3663
- <div>
3664
- <h2>Explore the recommended pipeline</h2>
3665
- <p>Every component includes its source, compatibility, and supporting evidence.</p>
3666
- </div>
3667
- <span class="guided-quality-badge">All stages visible</span>
3668
- </div>
3669
- <div class="guided-pipeline-meta">
3670
- <strong>Candidate pipeline</strong>
3671
- <span>Select a stage to inspect its evidence</span>
3672
- </div>
3673
- """
3674
- )
3675
- pipeline_card_outputs = []
3676
- pipeline_buttons = []
3677
- with gr.Row(
3678
- elem_classes=["guided-pipeline-diagram", "studio-pipeline"],
3679
- equal_height=True,
3680
- ):
3681
- for node_id, node_label, card_html in zip(
3682
- PIPELINE_NODE_IDS,
3683
- (
3684
- "Input",
3685
- "Preprocessing",
3686
- "Feature Extractor",
3687
- "Post-processing",
3688
- "Model",
3689
- "Evaluation",
3690
- ),
3691
- initial_node_cards,
3692
- ):
3693
- with gr.Group(
3694
- elem_classes=[
3695
- "guided-pipeline-card-shell",
3696
- f"guided-node-{node_id}",
3697
- ]
3698
- ):
3699
- pipeline_card_outputs.append(
3700
- gr.HTML(
3701
- card_html,
3702
- container=False,
3703
- padding=False,
3704
- elem_classes=[
3705
- "guided-pipeline-card-output"
3706
- ],
3707
- )
3708
- )
3709
- pipeline_buttons.append(
3710
- gr.Button(
3711
- f"Inspect {node_label} node",
3712
- elem_classes=[
3713
- "guided-pipeline-node-hitarea"
3714
- ],
3715
- )
3716
- )
3717
-
3718
  with gr.Group(
3719
  visible=True,
3720
  elem_classes=["guided-step-panel"],
@@ -3873,6 +3790,49 @@ def create_interface() -> gr.Blocks:
3873
  '<p>Open any node to review its selection source, '
3874
  'alternatives, compatibility, and report contract.</p></div>'
3875
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3876
  node_inspector = gr.HTML(
3877
  node_inspector_html(initial_workbench),
3878
  elem_classes=["guided-node-inspector-output"],
@@ -4193,25 +4153,24 @@ def create_interface() -> gr.Blocks:
4193
  'prepared. The current selection can run once.</div>'
4194
  )
4195
 
4196
- with gr.Column(
4197
- scale=3,
4198
- min_width=285,
4199
- elem_classes=["guided-research-panel", "studio-assistant"],
4200
- ):
4201
- dossier = gr.HTML(
4202
- research_dossier_html(initial_workbench),
4203
- elem_classes=["guided-dossier-output"],
4204
- )
4205
- with gr.Group(elem_classes=["guided-refinement-box"]):
4206
- refinement = gr.Textbox(
4207
- label="Refine the recommendation",
4208
- placeholder="For example, prefer interpretability",
4209
- lines=2,
4210
- )
4211
- refine_button = gr.Button(
4212
- "Apply refinement",
4213
- elem_classes=["guided-secondary-action"],
4214
  )
 
 
 
 
 
 
 
 
 
 
4215
 
4216
  with gr.Group(elem_classes=["guided-run-bar", "studio-runbar"]):
4217
  with gr.Row(equal_height=True):
@@ -4253,8 +4212,8 @@ def create_interface() -> gr.Blocks:
4253
  variant_two_outputs = _add_results_section("Variant 2")
4254
 
4255
  with gr.TabItem("Models") as replay_tab:
4256
- with gr.Row(equal_height=False):
4257
- with gr.Column(scale=2, elem_classes=["panel-card"]):
4258
  gr.HTML(
4259
  """
4260
  <div class="configuration-heading">
@@ -4269,18 +4228,19 @@ def create_interface() -> gr.Blocks:
4269
  interactive=False,
4270
  )
4271
  saved_model_selection = gr.State("")
4272
- refresh_models = gr.Button(
4273
- "Refresh Saved Models",
4274
- interactive=False,
4275
- )
4276
- export_saved_model = gr.Button("Prepare saved model download")
 
 
 
4277
  saved_export_status = gr.Markdown("")
4278
- saved_export_file = gr.DownloadButton(
4279
- "Download saved model", interactive=False,
4280
- )
4281
  model_upload = gr.File(
4282
  label="Trained Model Package (.zip)",
4283
  file_types=[".zip"],
 
4284
  )
4285
  model_info = gr.Markdown(
4286
  "Choose a saved model or upload a package."
@@ -4330,6 +4290,11 @@ def create_interface() -> gr.Blocks:
4330
  scale=3,
4331
  elem_classes=["inference-output"],
4332
  ):
 
 
 
 
 
4333
  predict_status = gr.Markdown(
4334
  "Ready for a model.",
4335
  elem_classes=["run-status"],
@@ -4371,10 +4336,10 @@ def create_interface() -> gr.Blocks:
4371
  )
4372
  predict_frame = gr.DataFrame(
4373
  label="Prediction",
 
 
4374
  interactive=False,
4375
  )
4376
- with gr.Column(scale=1):
4377
- gr.HTML(INFERENCE_GUIDE_HTML)
4378
 
4379
  with gr.TabItem("Account"):
4380
  with gr.Group(elem_classes=["panel-card"]):
@@ -5162,9 +5127,9 @@ if __name__ == "__main__":
5162
  server_name="0.0.0.0",
5163
  server_port=7860,
5164
  show_error=False,
5165
- theme=professional_theme(),
5166
- css=APP_CSS,
5167
- head=RESPONSIVE_HEAD_HTML,
5168
  )
5169
 
5170
 
 
38
  approval_status_html,
39
  approved_research_metadata,
40
  candidate_cards_html,
41
+ candidate_display_name,
42
  candidate_is_executable,
43
  experiment_reflection_outputs,
44
  new_workbench_state,
 
55
  update_context,
56
  validate_state,
57
  )
58
+ from studio_design import STUDIO_CSS, STUDIO_HEAD_HTML, studio_theme
59
  from ui_design import (
 
60
  AUTOMATIC_SELECTION,
 
61
  QUALITY_PRESET_CHOICES,
62
  RESEARCH_NOTICE_HTML,
 
63
  STUDIO_APP_HEADER_HTML,
 
64
  )
65
  from gateway_auth import (
66
  AuthResult,
 
2828
  def _workbench_candidate_update(state: WorkbenchState):
2829
  candidates = (
2830
  [
2831
+ (candidate_display_name(candidate), candidate.candidate_id)
2832
  for candidate in state.research_plan.candidates
2833
  if candidate_is_executable(
2834
  state.research_plan, candidate.candidate_id
 
3354
 
3355
  def _add_results_section(label: str = "Latest run"):
3356
  with gr.Group(elem_classes=["results-shell"]):
 
 
 
 
 
 
 
 
 
3357
  status = gr.Markdown(
3358
  "Status: _idle_",
3359
  visible=False,
 
3595
 
3596
  with gr.Tabs(visible=False, elem_id="workspace-tabs") as main_tabs:
3597
  with gr.TabItem("Build"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3598
  with gr.Row(
3599
  equal_height=False,
3600
  elem_classes=["guided-instrument-bench", "studio-workbench"],
3601
  ):
3602
  with gr.Column(
3603
+ scale=1,
3604
+ min_width=180,
3605
  elem_classes=["guided-step-rail", "studio-workflow"],
3606
  ):
3607
  gr.HTML(
 
3629
 
3630
  with gr.Column(
3631
  scale=7,
3632
+ min_width=320,
3633
  elem_classes=["guided-work-canvas", "studio-primary"],
3634
  ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3635
  with gr.Group(
3636
  visible=True,
3637
  elem_classes=["guided-step-panel"],
 
3790
  '<p>Open any node to review its selection source, '
3791
  'alternatives, compatibility, and report contract.</p></div>'
3792
  )
3793
+ pipeline_card_outputs = []
3794
+ pipeline_buttons = []
3795
+ with gr.Row(
3796
+ elem_classes=["guided-pipeline-diagram", "studio-pipeline"],
3797
+ equal_height=True,
3798
+ ):
3799
+ for node_id, node_label, card_html in zip(
3800
+ PIPELINE_NODE_IDS,
3801
+ (
3802
+ "Input",
3803
+ "Preprocessing",
3804
+ "Feature Extractor",
3805
+ "Post-processing",
3806
+ "Model",
3807
+ "Evaluation",
3808
+ ),
3809
+ initial_node_cards,
3810
+ ):
3811
+ with gr.Group(
3812
+ elem_classes=[
3813
+ "guided-pipeline-card-shell",
3814
+ f"guided-node-{node_id}",
3815
+ ]
3816
+ ):
3817
+ pipeline_card_outputs.append(
3818
+ gr.HTML(
3819
+ card_html,
3820
+ container=False,
3821
+ padding=False,
3822
+ elem_classes=[
3823
+ "guided-pipeline-card-output"
3824
+ ],
3825
+ )
3826
+ )
3827
+ pipeline_buttons.append(
3828
+ gr.Button(
3829
+ f"Inspect {node_label} node",
3830
+ elem_classes=[
3831
+ "guided-pipeline-node-hitarea"
3832
+ ],
3833
+ )
3834
+ )
3835
+
3836
  node_inspector = gr.HTML(
3837
  node_inspector_html(initial_workbench),
3838
  elem_classes=["guided-node-inspector-output"],
 
4153
  'prepared. The current selection can run once.</div>'
4154
  )
4155
 
4156
+ with gr.Accordion(
4157
+ "Research assistant", open=False,
4158
+ elem_classes=["studio-assistant"],
4159
+ ):
4160
+ dossier = gr.HTML(
4161
+ research_dossier_html(initial_workbench),
4162
+ elem_classes=["guided-dossier-output"],
 
 
 
 
 
 
 
 
 
 
 
4163
  )
4164
+ with gr.Group(elem_classes=["guided-refinement-box"]):
4165
+ refinement = gr.Textbox(
4166
+ label="Refine the recommendation",
4167
+ placeholder="For example, prefer interpretability",
4168
+ lines=2,
4169
+ )
4170
+ refine_button = gr.Button(
4171
+ "Apply refinement",
4172
+ elem_classes=["guided-secondary-action"],
4173
+ )
4174
 
4175
  with gr.Group(elem_classes=["guided-run-bar", "studio-runbar"]):
4176
  with gr.Row(equal_height=True):
 
4212
  variant_two_outputs = _add_results_section("Variant 2")
4213
 
4214
  with gr.TabItem("Models") as replay_tab:
4215
+ with gr.Row(equal_height=False, elem_classes=["studio-models"]):
4216
+ with gr.Column(scale=2, min_width=300, elem_classes=["studio-model-controls"]):
4217
  gr.HTML(
4218
  """
4219
  <div class="configuration-heading">
 
4228
  interactive=False,
4229
  )
4230
  saved_model_selection = gr.State("")
4231
+ with gr.Row(elem_classes=["studio-model-actions"]):
4232
+ refresh_models = gr.Button(
4233
+ "Refresh models", interactive=False,
4234
+ )
4235
+ export_saved_model = gr.Button("Prepare download")
4236
+ saved_export_file = gr.DownloadButton(
4237
+ "Download package", interactive=False,
4238
+ )
4239
  saved_export_status = gr.Markdown("")
 
 
 
4240
  model_upload = gr.File(
4241
  label="Trained Model Package (.zip)",
4242
  file_types=[".zip"],
4243
+ height=150,
4244
  )
4245
  model_info = gr.Markdown(
4246
  "Choose a saved model or upload a package."
 
4290
  scale=3,
4291
  elem_classes=["inference-output"],
4292
  ):
4293
+ gr.HTML(
4294
+ '<div class="configuration-heading">'
4295
+ '<h2>Prediction results</h2>'
4296
+ '<p>Visual previews and the complete prediction table.</p></div>'
4297
+ )
4298
  predict_status = gr.Markdown(
4299
  "Ready for a model.",
4300
  elem_classes=["run-status"],
 
4336
  )
4337
  predict_frame = gr.DataFrame(
4338
  label="Prediction",
4339
+ headers=["Prediction"],
4340
+ value=[],
4341
  interactive=False,
4342
  )
 
 
4343
 
4344
  with gr.TabItem("Account"):
4345
  with gr.Group(elem_classes=["panel-card"]):
 
5127
  server_name="0.0.0.0",
5128
  server_port=7860,
5129
  show_error=False,
5130
+ theme=studio_theme(),
5131
+ css=STUDIO_CSS,
5132
+ head=STUDIO_HEAD_HTML,
5133
  )
5134
 
5135
 
guided_workbench.py CHANGED
@@ -1407,6 +1407,16 @@ def _expected_metrics_html(candidate: CandidatePipeline) -> str:
1407
  return "".join(cards)
1408
 
1409
 
 
 
 
 
 
 
 
 
 
 
1410
  def candidate_cards_html(
1411
  state: Mapping[str, Any] | WorkbenchState,
1412
  ) -> str:
@@ -1470,11 +1480,14 @@ def candidate_cards_html(
1470
  f'" data-candidate-id="{_escape(candidate.candidate_id)}">'
1471
  f'<div class="guided-candidate-index">Candidate {index}</div>'
1472
  f"{recommended_badge}{verification_badge}"
1473
- f'<h3>{_escape(candidate.name or candidate.candidate_id)}</h3>'
1474
  '<p class="guided-candidate-rationale">'
1475
  f'{_escape(candidate.rationale or "No rationale provided")}</p>'
1476
  '<div class="guided-candidate-section"><h4>Estimated metric values · unmeasured</h4>'
1477
  f'<div class="guided-estimate-grid">{_expected_metrics_html(candidate)}</div></div>'
 
 
 
1478
  '<div class="guided-candidate-columns">'
1479
  '<div><h4>Assumptions</h4>'
1480
  f'{_list_html(candidate.assumptions)}</div>'
@@ -1485,7 +1498,7 @@ def candidate_cards_html(
1485
  f'{_list_html(candidate.evidence_ids)}</div>'
1486
  '<div class="guided-candidate-section"><h4>Warnings</h4>'
1487
  f'{_list_html(candidate.warnings)}</div>'
1488
- f"{verification_details}"
1489
  '<p class="guided-selection-state">'
1490
  f'{_escape("Selected · " + execution_status if selected else execution_status)}</p>'
1491
  "</article>"
@@ -1932,7 +1945,7 @@ def review_html(state: Mapping[str, Any] | WorkbenchState) -> str:
1932
  f'{_escape(current.dataset_name or current.upload_type)}</strong></div>'
1933
  f'<div><small>Objective</small><strong>{_escape(current.target)}</strong></div>'
1934
  '<div><small>Candidate</small><strong>'
1935
- f'{_escape(current.current_candidate.name)}</strong></div>'
1936
  f'<div><small>Quality profile</small><strong>{_escape(current.preset)}</strong></div>'
1937
  f'<div><small>Time budget</small><strong>{current.time_limit} seconds</strong></div>'
1938
  f'<div><small>Candidate trials</small><strong>{current.trials}</strong></div>'
@@ -1952,7 +1965,7 @@ def run_plan_summary_html(
1952
  variant_label = "variant" if run_count == 1 else "variants"
1953
  candidate = "Selection pending"
1954
  try:
1955
- candidate = current.current_candidate.name
1956
  except ValueError:
1957
  pass
1958
  status = "Ready" if not issues else f"{len(issues)} item(s) need attention"
 
1407
  return "".join(cards)
1408
 
1409
 
1410
+ def candidate_display_name(candidate: CandidatePipeline) -> str:
1411
+ """Readable labels for known deterministic defaults; identity stays canonical."""
1412
+ names = {
1413
+ "no_feature_selection_tool + no_transform_tool + autogluon_tool": "AutoGluon baseline",
1414
+ "t_test_feature_selection_tool + no_transform_tool + autogluon_tool": "AutoGluon with feature selection",
1415
+ "no_feature_selection_tool + pca_transform_tool + autogluon_tool": "AutoGluon with PCA",
1416
+ }
1417
+ return names.get(candidate.name, candidate.name or candidate.candidate_id)
1418
+
1419
+
1420
  def candidate_cards_html(
1421
  state: Mapping[str, Any] | WorkbenchState,
1422
  ) -> str:
 
1480
  f'" data-candidate-id="{_escape(candidate.candidate_id)}">'
1481
  f'<div class="guided-candidate-index">Candidate {index}</div>'
1482
  f"{recommended_badge}{verification_badge}"
1483
+ f'<h3>{_escape(candidate_display_name(candidate))}</h3>'
1484
  '<p class="guided-candidate-rationale">'
1485
  f'{_escape(candidate.rationale or "No rationale provided")}</p>'
1486
  '<div class="guided-candidate-section"><h4>Estimated metric values · unmeasured</h4>'
1487
  f'<div class="guided-estimate-grid">{_expected_metrics_html(candidate)}</div></div>'
1488
+ '<details class="guided-candidate-details"><summary>Evidence and assessment</summary>'
1489
+ '<h4>Registered pipeline</h4>'
1490
+ f'<p>{_escape(candidate.name or candidate.candidate_id)}</p>'
1491
  '<div class="guided-candidate-columns">'
1492
  '<div><h4>Assumptions</h4>'
1493
  f'{_list_html(candidate.assumptions)}</div>'
 
1498
  f'{_list_html(candidate.evidence_ids)}</div>'
1499
  '<div class="guided-candidate-section"><h4>Warnings</h4>'
1500
  f'{_list_html(candidate.warnings)}</div>'
1501
+ f"{verification_details}</details>"
1502
  '<p class="guided-selection-state">'
1503
  f'{_escape("Selected · " + execution_status if selected else execution_status)}</p>'
1504
  "</article>"
 
1945
  f'{_escape(current.dataset_name or current.upload_type)}</strong></div>'
1946
  f'<div><small>Objective</small><strong>{_escape(current.target)}</strong></div>'
1947
  '<div><small>Candidate</small><strong>'
1948
+ f'{_escape(candidate_display_name(current.current_candidate))}</strong></div>'
1949
  f'<div><small>Quality profile</small><strong>{_escape(current.preset)}</strong></div>'
1950
  f'<div><small>Time budget</small><strong>{current.time_limit} seconds</strong></div>'
1951
  f'<div><small>Candidate trials</small><strong>{current.trials}</strong></div>'
 
1965
  variant_label = "variant" if run_count == 1 else "variants"
1966
  candidate = "Selection pending"
1967
  try:
1968
+ candidate = candidate_display_name(current.current_candidate)
1969
  except ValueError:
1970
  pass
1971
  status = "Ready" if not issues else f"{len(issues)} item(s) need attention"
studio_design.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Public Studio visual system, independent of the legacy/private application CSS."""
2
+
3
+ import gradio as gr
4
+
5
+ from ui_design import STUDIO_INTERACTION_HEAD_HTML
6
+
7
+ def studio_theme() -> gr.Theme:
8
+ """System fonts and restrained defaults, without remote font requests."""
9
+ return gr.themes.Base(
10
+ primary_hue=gr.themes.colors.blue,
11
+ neutral_hue=gr.themes.colors.slate,
12
+ font=["system-ui", "sans-serif"],
13
+ font_mono=["ui-monospace", "monospace"],
14
+ ).set(
15
+ body_background_fill="#f7f8fa",
16
+ body_text_color="#18212f",
17
+ block_label_text_color="#18212f",
18
+ input_border_color="#e1e5eb",
19
+ button_primary_background_fill="#315fd6",
20
+ button_primary_background_fill_hover="#264dac",
21
+ button_primary_text_color="#ffffff",
22
+ shadow_drop="none",
23
+ )
24
+
25
+
26
+ # Document-level rules belong in head: Gradio scopes launch CSS to its app root.
27
+ STUDIO_HEAD_HTML = STUDIO_INTERACTION_HEAD_HTML + """
28
+ <style>
29
+ html { color-scheme: light; background: #f7f8fa; }
30
+ body { margin: 0; background: #f7f8fa; }
31
+ gradio-app { display: block; min-height: 100vh; }
32
+ /* Gradio scopes media selectors beneath .contain; the root is outside it. */
33
+ @media (max-width: 1100px) { html .gradio-container { padding: 0 24px 24px !important; } }
34
+ @media (max-width: 800px) { html .gradio-container { padding: 0 20px 24px !important; } }
35
+ @media (max-width: 480px) { html .gradio-container { padding: 0 16px 20px !important; } }
36
+ </style>
37
+ """
38
+
39
+ STUDIO_CSS = r"""
40
+ .gradio-container {
41
+ --studio-canvas: #f7f8fa;
42
+ --studio-surface: #fff;
43
+ --studio-text: #18212f;
44
+ --studio-muted: #5c6778;
45
+ --studio-line: #e1e5eb;
46
+ --studio-action: #315fd6;
47
+ --body-background-fill: var(--studio-canvas);
48
+ --body-text-color: var(--studio-text);
49
+ --body-text-color-subdued: var(--studio-muted);
50
+ --block-background-fill: var(--studio-surface);
51
+ --block-border-color: var(--studio-line);
52
+ --block-radius: 8px;
53
+ --input-background-fill: var(--studio-surface);
54
+ --input-border-color: var(--studio-line);
55
+ --input-radius: 6px;
56
+ --button-primary-background-fill: var(--studio-action);
57
+ --button-primary-background-fill-hover: #264dac;
58
+ --button-primary-text-color: #fff;
59
+ --button-primary-border-color: var(--studio-action);
60
+ --button-secondary-background-fill: #fff;
61
+ --button-secondary-background-fill-hover: #f0f3f8;
62
+ --button-secondary-border-color: var(--studio-line);
63
+ --button-secondary-text-color: var(--studio-text);
64
+ width: 100% !important;
65
+ max-width: 1440px !important;
66
+ margin: 0 auto;
67
+ padding: 0 40px 32px !important;
68
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
69
+ font-size: 15px;
70
+ line-height: 1.5;
71
+ color: var(--studio-text);
72
+ background: var(--studio-canvas);
73
+ }
74
+ footer { display: none !important; }
75
+ .gradio-container .app { gap: 0; }
76
+ .gradio-container .main.app { width: 100%; max-width: none; padding: 0; }
77
+ .gradio-container .html-container { padding: 0 !important; }
78
+ .gradio-container h1, .gradio-container h2, .gradio-container h3,
79
+ .gradio-container h4 { color: var(--studio-text); font-family: inherit; letter-spacing: -.025em; }
80
+ h1 { font-size: 22px; line-height: 1.2; font-weight: 650; }
81
+ h2 { font-size: 28px; line-height: 1.25; font-weight: 650; }
82
+ h3 { font-size: 22px; line-height: 1.3; font-weight: 650; }
83
+ h4 { font-size: 14px; line-height: 1.5; font-weight: 650; }
84
+ p { margin: 0; }
85
+ p, li, dd { overflow-wrap: anywhere; }
86
+ a { color: var(--studio-action); }
87
+ button, input, textarea, select { font: inherit; }
88
+ button { border-radius: 6px !important; box-shadow: none !important; }
89
+ button:focus-visible, input:focus-visible, textarea:focus-visible,
90
+ summary:focus-visible { outline: 2px solid var(--studio-action) !important; outline-offset: 3px; }
91
+ button:disabled { cursor: not-allowed; opacity: .48; }
92
+ .gradio-container .form { border: 0; box-shadow: none; background: transparent; gap: 16px; }
93
+ .gradio-container .block { box-shadow: none; }
94
+ /* Gradio 6 duplicates Group classes on its inner wrapper. Decorate once. */
95
+ .gr-group > .gr-group { flex: 0 0 auto !important; min-width: 0 !important; padding: 0 !important; margin: 0 !important; border: 0 !important; border-radius: 0 !important; background: transparent !important; max-width: none; }
96
+ .gr-group > .styler { --layout-gap: 16px !important; --form-gap-width: 16px !important; --block-radius: 6px !important; --button-border-width: 1px !important; --button-large-radius: 6px !important; --button-small-radius: 6px !important; background: transparent !important; }
97
+ /* A hidden Gradio Group can retain nested visible children. Its step heading
98
+ is the authoritative visibility signal set by the existing navigation event. */
99
+ .studio-primary > .guided-step-panel:not(:has(.guided-step-heading)) { display: none !important; }
100
+ .account-card:not(:has(h2)) { display: none !important; }
101
+ .gradio-container .block:has(> .html-container) { padding: 0 !important; }
102
+ .gradio-container .block .wrap > label { border-radius: 6px; }
103
+ .gradio-container .block .wrap:has(> .wrap-inner) { border: 1px solid var(--studio-line); border-radius: 6px; min-height: 42px; }
104
+ .gradio-container textarea { border: 1px solid var(--studio-line) !important; border-radius: 6px; background: #fff; padding: 12px; }
105
+ .gradio-container .html-container > .prose > :first-child { margin-top: 0; }
106
+ .gradio-container .html-container > .prose > :last-child { margin-bottom: 0; }
107
+ .guided-rail-note { border-top: 1px solid var(--studio-line); padding-top: 16px; font-size: 12px; color: var(--studio-muted); }
108
+ .guided-rail-note strong { display: block; color: var(--studio-text); font-size: 12px; margin-bottom: 6px; }
109
+ .guided-rail-note span { display: block; }
110
+
111
+ .gradio-container .gap { gap: 16px; }
112
+ .gradio-container .row { gap: 24px; }
113
+ .gradio-container .prose { font-size: 15px; }
114
+ .gradio-container .prose h3 { font-size: 20px; }
115
+ .gradio-container .prose p { color: var(--studio-muted); }
116
+ .gradio-container label > span, .gradio-container .block > .label-wrap { color: var(--studio-text); }
117
+ .studio-product-header { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 24px 0 20px; }
118
+ .studio-product-identity { display: flex; align-items: center; gap: 12px; }
119
+ .studio-product-identity h1 { margin: 0; font-size: 20px; }
120
+ .studio-product-identity p { color: var(--studio-muted); font-size: 12px; margin-top: 3px; }
121
+ .studio-product-mark { display: grid; place-items: center; width: 36px; height: 36px; border: 1px solid #ccd6eb; border-radius: 9px; font-size: 12px; font-weight: 750; color: var(--studio-action); background: #edf2fc; }
122
+ .studio-product-profile { display: flex; align-items: center; gap: 10px; color: var(--studio-muted); font-size: 12px; }
123
+ .studio-profile-badge { display: none; }
124
+ .research-notice { display: flex; gap: 12px; border-top: 1px solid var(--studio-line); padding: 10px 0; color: var(--studio-muted); font-size: 11px; }
125
+ .notice-title { flex: 0 0 auto; font-weight: 650; }
126
+ #workspace-tabs { border: 0; padding: 0; background: transparent; }
127
+ #workspace-tabs > .tab-wrapper { border-bottom: 1px solid var(--studio-line); padding: 0; margin-bottom: 28px; }
128
+ #workspace-tabs > .tab-wrapper > .tab-container { gap: 28px; }
129
+ #workspace-tabs > .tab-wrapper button[role="tab"] { border-radius: 0 !important; border: 0; border-bottom: 2px solid transparent; padding: 14px 2px; background: transparent; font-size: 14px; color: var(--studio-muted); }
130
+ #workspace-tabs > .tab-wrapper button[role="tab"][aria-selected="true"] { border-bottom-color: var(--studio-action); color: var(--studio-text); font-weight: 650; }
131
+ #workspace-tabs > .tabitem { border: 0; padding: 0; background: transparent; }
132
+ .studio-workbench { align-items: flex-start; gap: 40px !important; }
133
+ .studio-workflow { flex: 0 0 180px !important; min-width: 0 !important; gap: 20px !important; padding: 4px 0; }
134
+ .guided-rail-label, .section-kicker { font-size: 11px; font-weight: 650; color: var(--studio-muted); letter-spacing: .08em; text-transform: uppercase; }
135
+ .guided-stepper { padding: 0 !important; background: transparent !important; border: 0 !important; }
136
+ .guided-stepper .wrap { flex-direction: column; gap: 4px; counter-reset: step; }
137
+ .guided-stepper label { display: flex; align-items: center; gap: 12px; border: 0 !important; border-radius: 6px !important; background: transparent !important; padding: 10px 12px !important; margin: 0 !important; min-height: 42px; color: var(--studio-muted); font-size: 14px; counter-increment: step; }
138
+ .guided-stepper label::before { content: counter(step, decimal-leading-zero); font-size: 11px; color: #7b8595; font-variant-numeric: tabular-nums; }
139
+ .guided-stepper label:has(input:checked) { background: #eaf0fc !important; color: var(--studio-action); font-weight: 650; }
140
+ .guided-stepper label:has(input:checked)::before { color: var(--studio-action); }
141
+ .guided-stepper input { position: absolute; opacity: 0; width: 1px; }
142
+ .guided-stepper label:has(input:focus-visible) { outline: 2px solid var(--studio-action); outline-offset: 2px; }
143
+ .studio-primary { flex: 1 1 0 !important; min-width: 0 !important; }
144
+ .guided-step-panel { border: 1px solid var(--studio-line) !important; border-radius: 12px !important; background: var(--studio-surface) !important; padding: 28px !important; }
145
+ .guided-step-panel > .styler { background: transparent !important; }
146
+ .guided-step-heading { margin-bottom: 12px; }
147
+ .guided-step-heading > span { display: block; color: var(--studio-muted); font-size: 11px; font-weight: 650; letter-spacing: .07em; text-transform: uppercase; margin-bottom: 8px; }
148
+ .guided-step-heading h3 { font-size: 28px; margin: 0 0 10px; }
149
+ .guided-step-heading p { color: var(--studio-muted); max-width: 72ch; font-size: 14px; }
150
+ .guided-research-status, .guided-approval-status, .guided-inline-error, .guided-inline-warning { font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
151
+ .guided-approval-status strong, .guided-approval-status span { display: block; }
152
+ .guided-approval-status span { color: var(--studio-muted); margin-top: 4px; }
153
+ .guided-inline-error { color: #ad3434; }
154
+ .guided-inline-warning { color: #8a5a14; }
155
+ .studio-assistant { border: 1px solid var(--studio-line) !important; border-radius: 8px !important; background: transparent !important; }
156
+ .studio-assistant > .label-wrap { padding: 14px 16px; }
157
+ .guided-dossier-header h2 { font-size: 22px; }
158
+ .guided-dossier-block, .guided-inspector-contract, .guided-candidate-section { margin-top: 20px; }
159
+ .guided-dossier-block h4, .guided-inspector-contract h4, .guided-candidate-section h4 { margin: 0 0 8px; }
160
+ .guided-dossier-block p, .guided-dossier-block li { font-size: 14px; color: var(--studio-muted); }
161
+ .guided-refinement-box { background: transparent !important; border: 0 !important; margin-top: 20px; }
162
+ .guided-run-bar { margin-top: 28px; border: 0 !important; border-top: 1px solid var(--studio-line) !important; border-radius: 0 !important; padding: 20px 0 !important; background: transparent !important; }
163
+ .guided-run-bar > .styler { background: transparent !important; }
164
+ .guided-run-bar .row { align-items: center; }
165
+ .guided-run-summary-output { flex: 3 1 540px !important; }
166
+ .guided-run-bar button { flex: 1 1 160px !important; min-width: 160px !important; }
167
+ .guided-run-summary { display: flex; align-items: flex-start; gap: 24px; font-size: 12px; }
168
+ .guided-summary-status { min-width: 120px; }
169
+ .guided-summary-status > span, .guided-summary-facts small { display: block; color: var(--studio-muted); font-size: 11px; margin-bottom: 5px; }
170
+ .guided-summary-status strong { font-size: 13px; font-weight: 600; }
171
+ .guided-summary-facts { display: flex; flex-wrap: wrap; gap: 12px 24px; }
172
+ .guided-summary-facts > span:first-child { max-width: 240px; }
173
+ .guided-empty-state { padding: 24px 0; color: var(--studio-muted); }
174
+ .guided-empty-state h3 { font-size: 16px; margin: 0 0 8px; }
175
+ .guided-empty-copy { color: var(--studio-muted); font-size: 13px; }
176
+ .guided-candidate-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 16px; }
177
+ .guided-plan-context { grid-column: 1 / -1; font-size: 13px; color: var(--studio-muted); }
178
+ .guided-plan-context h4 { margin: 0; }
179
+ .guided-plan-context ul { margin: 8px 0; padding-left: 20px; }
180
+ .guided-candidate-card { padding: 20px; border: 1px solid var(--studio-line); border-radius: 10px; min-width: 0; background: #fff; }
181
+ .guided-candidate-card.recommended { border-color: #9cb3ed; }
182
+ .guided-candidate-card.selected { outline: 2px solid var(--studio-action); outline-offset: 2px; }
183
+ .guided-candidate-index { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--studio-muted); margin-bottom: 12px; }
184
+ .guided-candidate-card h3 { font-size: 20px; margin: 12px 0; }
185
+ .guided-candidate-card p, .guided-candidate-card li { font-size: 13px; line-height: 1.6; }
186
+ .guided-candidate-card ul { padding-left: 18px; }
187
+ .guided-candidate-rationale { color: var(--studio-muted); }
188
+ .guided-recommended-badge, .guided-verification-badge, .guided-source-badge, .guided-node-source, .guided-compatibility { display: inline-block; border: 1px solid var(--studio-line); border-radius: 4px; padding: 2px 6px; font-size: 10px; font-weight: 600; margin: 0 4px 4px 0; }
189
+ .guided-recommended-badge { color: var(--studio-action); background: #edf2fc; border-color: #d4dff8; }
190
+ .guided-verification-badge.passed, .guided-compatibility.compatible { color: #267350; }
191
+ .guided-verification-badge.rejected, .guided-compatibility.incompatible { color: #ad3434; }
192
+ .guided-estimate-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 8px; }
193
+ .guided-estimate-grid > div { padding: 8px; background: #f7f8fa; border-radius: 6px; font-size: 12px; }
194
+ .guided-estimate-grid strong, .guided-estimate-grid small { display: block; }
195
+ .guided-candidate-details { margin-top: 20px; border-top: 1px solid var(--studio-line); padding-top: 12px; }
196
+ .guided-candidate-details summary { font-size: 13px; font-weight: 600; cursor: pointer; }
197
+ .guided-selection-state { border-top: 1px solid var(--studio-line); margin-top: 16px; padding-top: 12px; color: var(--studio-muted); }
198
+ .studio-pipeline { gap: 8px !important; }
199
+ .guided-pipeline-card-shell { flex: 1 1 120px !important; min-width: 120px !important; border: 1px solid var(--studio-line) !important; border-radius: 8px !important; background: #fafbfc !important; }
200
+ .guided-pipeline-card-shell > .styler { background: transparent !important; }
201
+ .guided-pipeline-card-content { padding: 12px; min-height: 156px; }
202
+ .guided-node-topline { display: flex; gap: 6px; font-size: 11px; color: var(--studio-muted); }
203
+ .guided-node-icon { display: none; }
204
+ .guided-pipeline-card-content h4 { margin: 12px 0 6px; font-size: 13px; overflow-wrap: anywhere; }
205
+ .guided-node-parameter, .guided-node-shape { font-size: 11px; color: var(--studio-muted); margin: 4px 0; }
206
+ .guided-node-shape { display: none; }
207
+ .guided-node-selected { font-size: 10px; color: var(--studio-action); }
208
+ .guided-node-footer { display: flex !important; flex-wrap: wrap; gap: 4px; margin-top: 10px; }
209
+ .guided-node-compatibility { font-size: 10px; }
210
+ .guided-pipeline-node-hitarea { min-width: 0 !important; font-size: 11px !important; padding: 8px 4px !important; border: 0 !important; border-top: 1px solid var(--studio-line) !important; background: #fff !important; border-radius: 0 !important; }
211
+ .guided-node-inspector { margin-top: 12px; }
212
+ .guided-inspector-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--studio-line); }
213
+ .guided-inspector-heading h3 { margin: 4px 0; font-size: 22px; }
214
+ .guided-inspector-heading span { color: var(--studio-muted); font-size: 12px; }
215
+ .guided-mapping-row, .guided-detail-row { display: grid; grid-template-columns: minmax(120px, 1fr) 3fr; gap: 16px; padding: 8px 0; border-bottom: 1px solid #eef0f3; font-size: 13px; }
216
+ .guided-workbench-heading { margin: 0 0 24px; display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; }
217
+ .guided-workbench-heading h2 { margin: 6px 0 0; }
218
+ .guided-workbench-heading > p { color: var(--studio-muted); font-size: 13px; max-width: 50ch; }
219
+ .results-shell { border: 0 !important; background: transparent !important; }
220
+ .guided-result-tabs > .tabitem { border: 1px solid var(--studio-line); border-radius: 8px; padding: 24px; background: #fff; }
221
+ .guided-result-tabs .tab-container { flex-wrap: wrap; }
222
+ .guided-snapshot-heading { margin-bottom: 20px; }
223
+ .guided-snapshot-heading span { font-size: 11px; color: var(--studio-muted); text-transform: uppercase; letter-spacing: .06em; }
224
+ .guided-snapshot-heading h3 { margin: 6px 0; }
225
+ .guided-snapshot { max-width: 100%; }
226
+ .guided-snapshot pre, .guided-snapshot code { white-space: pre-wrap; overflow-wrap: anywhere; }
227
+ .guided-reflection-metric-grid, .metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px,1fr)); gap: 16px; margin: 16px 0; }
228
+ .guided-reflection-metric, .metric-card { padding: 16px; border: 1px solid var(--studio-line); border-radius: 8px; }
229
+ .candidate-comparison { overflow-x: auto; }
230
+ .candidate-comparison table { width: 100%; border-collapse: collapse; font-size: 13px; }
231
+ .candidate-comparison table th, .candidate-comparison table td { border: 0 !important; border-bottom: 1px solid var(--studio-line) !important; padding: 12px !important; text-align: left; }
232
+ .candidate-comparison table th { color: var(--studio-muted); background: #f7f8fa; font-weight: 500; }
233
+ .studio-model-actions { gap: 8px !important; }
234
+ .studio-model-actions button { min-width: 100px !important; font-size: 12px !important; padding: 10px 8px !important; }
235
+ .studio-models { gap: 32px !important; }
236
+ .studio-model-controls { padding-right: 24px; border-right: 1px solid var(--studio-line); }
237
+ .configuration-heading { margin-bottom: 12px; }
238
+ .configuration-heading h2 { font-size: 28px; margin: 0 0 12px; }
239
+ .configuration-heading p { font-size: 14px; color: var(--studio-muted); }
240
+ .inference-output { min-width: 0; }
241
+ .visual-prediction-gallery { background: #fff; border: 1px solid var(--studio-line); border-radius: 8px; }
242
+ .panel-card, .account-card { border: 1px solid var(--studio-line) !important; border-radius: 12px !important; padding: 28px !important; background: #fff !important; }
243
+ .account-card { max-width: 600px; margin: 40px auto; }
244
+ .account-card h2 { margin: 0 0 12px; }
245
+ .account-card p { color: var(--studio-muted); }
246
+ .run-context-card { padding: 0; }
247
+ .run-status-strip { display: flex; align-items: center; gap: 8px; font-size: 13px; padding-bottom: 20px; }
248
+ .run-dot { width: 7px; height: 7px; border-radius: 50%; background: #8a94a3; }
249
+ .state-complete .run-dot { background: #267350; }
250
+ .state-failed .run-dot { background: #ad3434; }
251
+ .run-alias-row { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; border-bottom: 1px solid var(--studio-line); padding-bottom: 16px; }
252
+ .run-context-label { font-size: 12px; color: var(--studio-muted); }
253
+ .run-alias { font-size: 14px; }
254
+ .run-id-copy { padding: 4px 8px; font-size: 11px; border: 1px solid var(--studio-line); background: #fff; }
255
+ .run-context-list, .run-technical-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; margin: 20px 0; }
256
+ .run-context-item { display: flex; flex-direction: column; gap: 4px; font-size: 14px; }
257
+ .run-context-item strong { font-weight: 500; }
258
+ .run-technical summary, .reported-metrics-detail summary { font-size: 13px; color: var(--studio-muted); cursor: pointer; }
259
+ .workbench-metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 24px; }
260
+ .workbench-metric-card { border-bottom: 1px solid var(--studio-line); padding: 16px 0; }
261
+ .metric-label { display: block; color: var(--studio-muted); font-size: 12px; margin-bottom: 8px; }
262
+ .metric-value { display: block; font-size: 28px; font-weight: 600; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
263
+ .metric-value.metric-model { font-size: 18px; }
264
+ .guided-review-grid, .guided-inspector-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 24px; margin: 24px 0; }
265
+ .guided-review-grid small, .guided-review-grid strong { display: block; }
266
+ .guided-review-grid small { color: var(--studio-muted); font-size: 12px; margin-bottom: 8px; }
267
+ .guided-review-grid strong { font-size: 14px; font-weight: 500; overflow-wrap: anywhere; }
268
+ .guided-provider-badge, .guided-evidence-provenance { display: inline-block; font-size: 11px; color: var(--studio-muted); margin: 4px 0; }
269
+ .guided-dossier-heading { margin-bottom: 20px; }
270
+ .guided-dossier-heading h3 { margin: 8px 0; }
271
+ .guided-research-insights, .guided-evidence-grid, .guided-next-experiment-grid { display: grid; gap: 16px; margin: 20px 0; }
272
+ .guided-research-insight, .guided-evidence-card, .guided-next-experiment-card, .guided-trace-entry { padding: 16px 0; border-top: 1px solid var(--studio-line); }
273
+ .guided-evidence-heading h4 { margin: 0 0 8px; }
274
+ .guided-insight-label { color: var(--studio-muted); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
275
+ .guided-research-icon { display: none; }
276
+ .guided-dossier-empty h3 { font-size: 18px; margin: 0 0 6px; }
277
+ .guided-empty-insight { padding: 20px 0; }
278
+ .guided-provider-note, .guided-agent-intro { color: var(--studio-muted); font-size: 13px; }
279
+ .guided-estimate > span { display: block; font-size: 11px; color: var(--studio-muted); }
280
+ .guided-estimate strong { font-size: 16px; margin: 6px 0; }
281
+ .guided-trace-list { padding: 0; list-style: none; }
282
+ @media (max-width: 1100px) {
283
+ .studio-workbench { gap: 24px !important; }
284
+ .studio-workflow { flex-basis: 150px !important; }
285
+ .guided-step-panel { padding: 24px !important; }
286
+ .guided-summary-facts { gap: 12px 16px; }
287
+ }
288
+ @media (max-width: 800px) {
289
+ .studio-workbench { flex-direction: column; }
290
+ .studio-workflow, .studio-primary { width: 100%; flex: 1 1 auto !important; }
291
+ .studio-workflow { gap: 8px !important; padding: 0; }
292
+ .guided-rail-label { display: none; }
293
+ .guided-stepper .wrap { flex-direction: row; flex-wrap: wrap; gap: 4px; }
294
+ .guided-stepper label { flex: 0 0 auto; padding: 8px 10px !important; min-height: 40px; gap: 6px; font-size: 12px; }
295
+ .guided-stepper label::before { font-size: 10px; }
296
+ .studio-model-controls { padding-right: 0; border-right: 0; }
297
+ .guided-workbench-heading { display: block; }
298
+ .guided-workbench-heading > p { margin-top: 12px; }
299
+ .guided-run-summary { flex-direction: column; gap: 12px; }
300
+ .research-notice { font-size: 10px; }
301
+ }
302
+ @media (max-width: 480px) {
303
+ .guided-stepper .wrap { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
304
+ .guided-stepper label { padding: 8px 4px !important; font-size: 11px; justify-content: center; }
305
+ .guided-stepper label::before { display: none; }
306
+ .studio-product-header { padding: 20px 0 16px; }
307
+ .studio-product-profile { display: none; }
308
+ .research-notice { display: block; }
309
+ .notice-title { margin-bottom: 4px; }
310
+ #workspace-tabs > .tab-wrapper { margin-bottom: 20px; }
311
+ #workspace-tabs > .tab-wrapper > .tab-container { gap: 24px; }
312
+ .guided-step-panel, .panel-card { padding: 20px !important; }
313
+ .guided-step-heading h3, .configuration-heading h2 { font-size: 24px; }
314
+ .guided-run-bar .row { gap: 12px; }
315
+ .guided-result-tabs > .tabitem { padding: 16px; }
316
+ .guided-mapping-row, .guided-detail-row { grid-template-columns: 1fr; gap: 4px; }
317
+ .guided-inspector-heading { flex-direction: column; }
318
+ }
319
+ @media (prefers-reduced-motion: reduce) {
320
+ *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
321
+ }
322
+ """
ui_design.py CHANGED
@@ -2,6 +2,8 @@
2
 
3
  from __future__ import annotations
4
 
 
 
5
  import gradio as gr
6
 
7
 
@@ -6025,6 +6027,11 @@ html:not(.automl-embedded) .gradio-container:not(:has(#workspace-tabs)) {
6025
  </script>
6026
  """
6027
 
 
 
 
 
 
6028
 
6029
  def professional_theme() -> gr.Theme:
6030
  """Return a restrained, high-contrast theme built from system fonts."""
 
2
 
3
  from __future__ import annotations
4
 
5
+ import re
6
+
7
  import gradio as gr
8
 
9
 
 
6027
  </script>
6028
  """
6029
 
6030
+ # Reuse the proven interaction fixes without importing the legacy shell layout.
6031
+ STUDIO_INTERACTION_HEAD_HTML = "\n".join(
6032
+ re.findall(r"<script>.*?</script>", RESPONSIVE_HEAD_HTML, flags=re.DOTALL)
6033
+ )
6034
+
6035
 
6036
  def professional_theme() -> gr.Theme:
6037
  """Return a restrained, high-contrast theme built from system fonts."""