Team Ai
Apppublic

openenv/echo_env

sourceHugging Faceupdated 1d agoView on Hugging Face
6likes
gradio_ui.py237 linesDownload Raw Back to env_server
1# SPDX-License-Identifier: BSD-3-Clause2 3"""4Gradio-based web UI for OpenEnv environments.5 6Replaces the legacy HTML/JavaScript interface when ENABLE_WEB_INTERFACE is set.7Mount at /web via gr.mount_gradio_app() from create_web_interface_app().8"""9 10from __future__ import annotations11 12import json13import re14from typing import Any, Dict, List, Optional15 16import gradio as gr17 18from .types import EnvironmentMetadata19 20 21def _escape_md(text: str) -> str:22    """Escape Markdown special characters in user-controlled content."""23    return re.sub(r"([\\`*_\{\}\[\]()#+\-.!|~>])", r"\\\1", str(text))24 25 26def _format_observation(data: Dict[str, Any]) -> str:27    """Format reset/step response for Markdown display."""28    lines: List[str] = []29    obs = data.get("observation", {})30    if isinstance(obs, dict):31        if obs.get("prompt"):32            lines.append(f"**Prompt:**\n\n{_escape_md(obs['prompt'])}\n")33        messages = obs.get("messages", [])34        if messages:35            lines.append("**Messages:**\n")36            for msg in messages:37                sender = _escape_md(str(msg.get("sender_id", "?")))38                content = _escape_md(str(msg.get("content", "")))39                cat = _escape_md(str(msg.get("category", "")))40                lines.append(f"- `[{cat}]` Player {sender}: {content}")41            lines.append("")42    reward = data.get("reward")43    done = data.get("done")44    if reward is not None:45        lines.append(f"**Reward:** `{reward}`")46    if done is not None:47        lines.append(f"**Done:** `{done}`")48    return "\n".join(lines) if lines else "*No observation data*"49 50 51def _readme_section(metadata: Optional[EnvironmentMetadata]) -> str:52    """README content for the left panel."""53    if not metadata or not metadata.readme_content:54        return "*No README available.*"55    return metadata.readme_content56 57 58def get_gradio_display_title(59    metadata: Optional[EnvironmentMetadata],60    fallback: str = "OpenEnv Environment",61) -> str:62    """Return the title used for the Gradio app (browser tab and Blocks)."""63    name = metadata.name if metadata else fallback64    return f"OpenEnv Agentic Environment: {name}"65 66 67def build_gradio_app(68    web_manager: Any,69    action_fields: List[Dict[str, Any]],70    metadata: Optional[EnvironmentMetadata],71    is_chat_env: bool,72    title: str = "OpenEnv Environment",73    quick_start_md: Optional[str] = None,74) -> gr.Blocks:75    """76    Build a Gradio Blocks app for the OpenEnv web interface.77 78    Args:79        web_manager: WebInterfaceManager (reset/step_environment, get_state).80        action_fields: Field dicts from _extract_action_fields(action_cls).81        metadata: Environment metadata for README/name.82        is_chat_env: If True, single message textbox; else form from action_fields.83        title: App title (overridden by metadata.name when present; see get_gradio_display_title).84        quick_start_md: Optional Quick Start markdown (class names already replaced).85 86    Returns:87        gr.Blocks to mount with gr.mount_gradio_app(app, blocks, path="/web").88    """89    readme_content = _readme_section(metadata)90    display_title = get_gradio_display_title(metadata, fallback=title)91 92    async def reset_env():93        try:94            data = await web_manager.reset_environment()95            obs_md = _format_observation(data)96            return (97                obs_md,98                json.dumps(data, indent=2),99                "Environment reset successfully.",100            )101        except Exception as e:102            return ("", "", f"Error: {e}")103 104    def _step_with_action(action_data: Dict[str, Any]):105        async def _run():106            try:107                data = await web_manager.step_environment(action_data)108                obs_md = _format_observation(data)109                return (110                    obs_md,111                    json.dumps(data, indent=2),112                    "Step complete.",113                )114            except Exception as e:115                return ("", "", f"Error: {e}")116 117        return _run118 119    async def step_chat(message: str):120        if not (message or str(message).strip()):121            return ("", "", "Please enter an action message.")122        action = {"message": str(message).strip()}123        return await _step_with_action(action)()124 125    def get_state_sync():126        try:127            data = web_manager.get_state()128            return json.dumps(data, indent=2)129        except Exception as e:130            return f"Error: {e}"131 132    with gr.Blocks(title=display_title) as demo:133        with gr.Row():134            with gr.Column(scale=1, elem_classes="col-left"):135                if quick_start_md:136                    with gr.Accordion("Quick Start", open=True):137                        gr.Markdown(quick_start_md)138                with gr.Accordion("README", open=False):139                    gr.Markdown(readme_content)140 141            with gr.Column(scale=2, elem_classes="col-right"):142                obs_display = gr.Markdown(143                    value=("# Playground\n\nClick **Reset** to start a new episode."),144                )145                with gr.Group():146                    if is_chat_env:147                        action_input = gr.Textbox(148                            label="Action message",149                            placeholder="e.g. Enter your message...",150                        )151                        step_inputs = [action_input]152                        step_fn = step_chat153                    else:154                        step_inputs = []155                        for field in action_fields:156                            name = field["name"]157                            field_type = field.get("type", "text")158                            label = name.replace("_", " ").title()159                            placeholder = field.get("placeholder", "")160                            if field_type == "checkbox":161                                inp = gr.Checkbox(label=label)162                            elif field_type == "number":163                                inp = gr.Number(label=label)164                            elif field_type == "select":165                                choices = field.get("choices") or []166                                inp = gr.Dropdown(167                                    choices=choices,168                                    label=label,169                                    allow_custom_value=False,170                                )171                            elif field_type in ("textarea", "tensor"):172                                inp = gr.Textbox(173                                    label=label,174                                    placeholder=placeholder,175                                    lines=3,176                                )177                            else:178                                inp = gr.Textbox(179                                    label=label,180                                    placeholder=placeholder,181                                )182                            step_inputs.append(inp)183 184                        async def step_form(*values):185                            if not action_fields:186                                return await _step_with_action({})()187                            action_data = {}188                            for i, field in enumerate(action_fields):189                                if i >= len(values):190                                    break191                                name = field["name"]192                                val = values[i]193                                if field.get("type") == "checkbox":194                                    action_data[name] = bool(val)195                                elif val is not None and val != "":196                                    action_data[name] = val197                            return await _step_with_action(action_data)()198 199                        step_fn = step_form200 201                    with gr.Row():202                        step_btn = gr.Button("Step", variant="primary")203                        reset_btn = gr.Button("Reset", variant="secondary")204                        state_btn = gr.Button("Get state", variant="secondary")205                    with gr.Row():206                        status = gr.Textbox(207                            label="Status",208                            interactive=False,209                        )210                    raw_json = gr.Code(211                        label="Raw JSON response",212                        language="json",213                        interactive=False,214                    )215 216        reset_btn.click(217            fn=reset_env,218            outputs=[obs_display, raw_json, status],219        )220        step_btn.click(221            fn=step_fn,222            inputs=step_inputs,223            outputs=[obs_display, raw_json, status],224        )225        if is_chat_env:226            action_input.submit(227                fn=step_fn,228                inputs=step_inputs,229                outputs=[obs_display, raw_json, status],230            )231        state_btn.click(232            fn=get_state_sync,233            outputs=[raw_json],234        )235 236    return demo237