|
24 | 24 |
|
25 | 25 | PROJECT_ROOT = Path(__file__).resolve().parent.parent |
26 | 26 |
|
| 27 | +# Gradio moved where `theme` lives across versions: <=5 uses `Blocks(theme=...)`, |
| 28 | +# >=6 moved it to `launch()`. Detect the installed major so the WebUI works on |
| 29 | +# any supported version without an ignored-argument warning or a TypeError. |
| 30 | +_GRADIO_MAJOR = int((getattr(gr, "__version__", "4.0").split(".")[0]) or 4) |
| 31 | + |
27 | 32 |
|
28 | 33 | # ─── Config helpers ────────────────────────────────────────────────────────── |
29 | 34 |
|
@@ -468,9 +473,10 @@ def render_pipeline_html(active_stage: str = "") -> str: |
468 | 473 | def build_ui(): |
469 | 474 | configs = discover_configs() |
470 | 475 |
|
471 | | - with gr.Blocks( |
472 | | - title="SkillOpt WebUI", |
473 | | - ) as app: |
| 476 | + _blocks_kwargs = {"title": "SkillOpt WebUI"} |
| 477 | + if _GRADIO_MAJOR < 6: |
| 478 | + _blocks_kwargs["theme"] = gr.themes.Soft(primary_hue="indigo") |
| 479 | + with gr.Blocks(**_blocks_kwargs) as app: |
474 | 480 | gr.Markdown("# 🧠 SkillOpt Training Dashboard") |
475 | 481 | gr.Markdown("*SKILLOPT: Executive Strategy for Self-Evolving Agent Skills — Configure, launch, and monitor training.*") |
476 | 482 |
|
@@ -598,6 +604,8 @@ def on_refresh(): |
598 | 604 |
|
599 | 605 | def scan_outputs(out_dir): |
600 | 606 | rows = [] |
| 607 | + if not out_dir: |
| 608 | + return rows |
601 | 609 | base = PROJECT_ROOT / out_dir |
602 | 610 | if not base.exists(): |
603 | 611 | return rows |
@@ -639,21 +647,41 @@ def scan_outputs(out_dir): |
639 | 647 | return app |
640 | 648 |
|
641 | 649 |
|
| 650 | +def build_launch_kwargs(server_name: str, server_port: int, share: bool) -> dict: |
| 651 | + """Build the launch kwargs exactly as production ``main()`` applies them. |
| 652 | +
|
| 653 | + Gradio 6 moved the theme to ``launch()``; applying it here (rather than in |
| 654 | + ``build_ui``) keeps the theme on the right object for the installed major |
| 655 | + without an ignored-argument warning. Tests call this so they exercise the |
| 656 | + same path a real ``skillopt-webui`` run does instead of injecting their own. |
| 657 | + """ |
| 658 | + kwargs = dict(server_name=server_name, server_port=server_port, share=share) |
| 659 | + if _GRADIO_MAJOR >= 6: |
| 660 | + kwargs["theme"] = gr.themes.Soft(primary_hue="indigo") |
| 661 | + return kwargs |
| 662 | + |
| 663 | + |
642 | 664 | def main(): |
643 | 665 | parser = argparse.ArgumentParser(description="SkillOpt WebUI") |
644 | 666 | parser.add_argument("--port", type=int, default=7860) |
645 | 667 | parser.add_argument("--share", action="store_true") |
646 | | - parser.add_argument("--host", type=str, default="0.0.0.0", |
647 | | - help="Server host. Use 0.0.0.0 for public access.") |
| 668 | + parser.add_argument("--host", type=str, default="127.0.0.1", |
| 669 | + help="Server host. Default is localhost; use 0.0.0.0 " |
| 670 | + "to expose publicly (no auth, use with care).") |
648 | 671 | args = parser.parse_args() |
649 | 672 |
|
| 673 | + if args.host and args.host not in ("127.0.0.1", "localhost", "::1"): |
| 674 | + print( |
| 675 | + f"⚠ warning: binding SkillOpt WebUI on {args.host} with no auth " |
| 676 | + "exposes the Output Explorer (reads any path you type) and the " |
| 677 | + "training controls to reachable clients. Prefer --host 127.0.0.1; " |
| 678 | + "use 0.0.0.0 only if you understand the risk.", |
| 679 | + file=sys.stderr, |
| 680 | + ) |
| 681 | + |
650 | 682 | app = build_ui() |
651 | | - app.launch( |
652 | | - server_name=args.host, |
653 | | - server_port=args.port, |
654 | | - share=args.share, |
655 | | - theme=gr.themes.Soft(primary_hue="indigo"), |
656 | | - ) |
| 683 | + launch_kwargs = build_launch_kwargs(args.host, args.port, args.share) |
| 684 | + app.launch(**launch_kwargs) |
657 | 685 |
|
658 | 686 |
|
659 | 687 | if __name__ == "__main__": |
|
0 commit comments