""" ui/components.py ---------------- Reusable Gradio component builders for AutoDevAgent. Each function returns a configured Gradio component (or group of components) that can be assembled into the main app.py layout. Components built here: - build_hitl_panel() Human-in-the-Loop panel — shown when max retries is hit. Displays the failed code, the last error, 2-3 fix options, and an inline code editor. - build_reflection_panel() Self-reflection display — shows the debug agent's structured reasoning per iteration ("saw X, thought Y, did Z"). - build_iteration_counter() Live iteration + token + time display. - build_model_switcher() Toggle between Llama 8B and 70B. - build_language_selector() Language radio with auto-detect badge. Design: - All components are built with Gradio's Blocks API. - State is managed via gr.State objects passed through event handlers. - Components are returned as named dicts so app.py can wire them into event handlers without importing internals. Usage: import gradio as gr from ui.components import build_hitl_panel, build_reflection_panel with gr.Blocks() as demo: hitl = build_hitl_panel() reflex = build_reflection_panel() """ import gradio as gr from typing import Any # ------------------------------------------------------------------ # # Human-in-the-Loop Panel # # ------------------------------------------------------------------ # def build_hitl_panel() -> dict[str, Any]: """ Build the Human-in-the-Loop panel shown when max retries is hit. Layout: ┌────────────────────────────────────────┐ │ ⚠ Max retries reached │ │ Last error: │ │ │ │ Fix options: │ │ ○ Option 1 description │ │ ○ Option 2 description │ │ ○ Option 3 description │ │ │ │ Or edit the code directly: │ │ [code editor textbox] │ │ │ │ [Apply fix] [Resubmit edited code] │ └────────────────────────────────────────┘ Returns: Dict with keys: - "panel": gr.Group — the whole panel - "error_display": gr.Textbox — shows the last error - "options_radio": gr.Radio — 2-3 fix options - "code_editor": gr.Code — inline code editor - "apply_btn": gr.Button — apply chosen fix option - "resubmit_btn": gr.Button — resubmit edited code """ with gr.Group(visible=False) as panel: gr.Markdown("### ⚠ Max retries reached — Human input needed") with gr.Row(): gr.Markdown( "The agent could not fix the code automatically. " "Choose a fix strategy or edit the code directly below." ) error_display = gr.Textbox( label="Last error", lines=4, interactive=False, elem_id="hitl_error_display", ) options_radio = gr.Radio( choices=[], label="Suggested fix strategies", value=None, elem_id="hitl_options_radio", ) gr.Markdown("**Or edit the code directly:**") code_editor = gr.Code( label="Code editor", language="python", lines=20, interactive=True, elem_id="hitl_code_editor", ) with gr.Row(): apply_btn = gr.Button( value="Apply selected fix", variant="primary", size="sm", ) resubmit_btn = gr.Button( value="Resubmit edited code", variant="secondary", size="sm", ) return { "panel": panel, "error_display": error_display, "options_radio": options_radio, "code_editor": code_editor, "apply_btn": apply_btn, "resubmit_btn": resubmit_btn, } def update_hitl_panel( hitl_components: dict[str, Any], error_msg: str, options: list[str], broken_code: str, ) -> None: """ Populate the HITL panel with the current error state. Called from app.py when the pipeline returns AWAITING_HUMAN status. Updates the error display, radio options, and code editor in place. Args: hitl_components: Dict returned by build_hitl_panel(). error_msg: The last error message from the executor. options: List of 2-3 fix option strings from DebugAgent. broken_code: The most recent failing code to pre-populate the inline editor. """ hitl_components["error_display"].value = error_msg hitl_components["options_radio"].choices = options hitl_components["options_radio"].value = None hitl_components["code_editor"].value = broken_code hitl_components["panel"].visible = True # ------------------------------------------------------------------ # # Clarification Panel # # ------------------------------------------------------------------ # def build_clarification_panel() -> dict[str, Any]: """ Build the clarification panel shown when the agent needs more info. Layout: ┌────────────────────────────────────────┐ │ 🤔 Task needs clarification │ │ │ │ [user input textbox] │ │ [Submit Clarification] │ └────────────────────────────────────────┘ Returns: Dict with keys: - "panel": gr.Group — container (visible=False by default) - "question_md": gr.Markdown — displays the agent's question - "answer_input": gr.Textbox — user types their clarification - "submit_btn": gr.Button — submits the clarification """ with gr.Group(visible=False, elem_id="clarification_panel") as panel: gr.Markdown("### 🤔 Task needs clarification") question_md = gr.Markdown( value="", elem_id="clarification_question", ) answer_input = gr.Textbox( label="Your clarification", placeholder="Type your answer here...", lines=2, elem_id="clarification_answer", ) with gr.Row(): submit_btn = gr.Button( value="Submit Clarification ▶", variant="primary", size="sm", elem_id="clarification_submit_btn", ) return { "panel": panel, "question_md": question_md, "answer_input": answer_input, "submit_btn": submit_btn, } # ------------------------------------------------------------------ # # Self-Reflection Panel # # ------------------------------------------------------------------ # def build_reflection_panel() -> dict[str, Any]: """ Build the self-reflection panel displayed during each debug iteration. Shows the debug agent's structured reasoning before each rewrite: - What I saw: the exact error observed - What I think: the agent's hypothesis for the root cause - What I will do: the specific change the agent will make Returns: Dict with keys: - "panel": gr.Accordion — collapsible wrapper - "saw_box": gr.Textbox — what the agent observed - "think_box": gr.Textbox — the agent's hypothesis - "do_box": gr.Textbox — the planned fix - "iteration_md": gr.Markdown — current iteration label """ with gr.Accordion( label="Self-reflection (debug agent reasoning)", open=False, visible=False, ) as panel: iteration_md = gr.Markdown("**Iteration:** —") with gr.Row(): saw_box = gr.Textbox( label="What I saw", lines=2, interactive=False, elem_id="reflection_saw", ) with gr.Row(): think_box = gr.Textbox( label="What I think caused it", lines=2, interactive=False, elem_id="reflection_think", ) with gr.Row(): do_box = gr.Textbox( label="What I will change", lines=2, interactive=False, elem_id="reflection_do", ) return { "panel": panel, "saw_box": saw_box, "think_box": think_box, "do_box": do_box, "iteration_md": iteration_md, } def update_reflection_panel( reflection_components: dict[str, Any], what_i_saw: str, what_i_think: str, what_i_will_do: str, iteration: int, max_retries: int, ) -> None: """ Populate the reflection panel with the latest debug iteration data. Args: reflection_components: Dict returned by build_reflection_panel(). what_i_saw: The error or wrong output observed. what_i_think: The agent's hypothesis. what_i_will_do: The planned fix. iteration: Current iteration number (1-based). max_retries: Configured max retries for the label. """ reflection_components["iteration_md"].value = ( f"**Iteration:** {iteration} / {max_retries}" ) reflection_components["saw_box"].value = what_i_saw reflection_components["think_box"].value = what_i_think reflection_components["do_box"].value = what_i_will_do reflection_components["panel"].visible = True reflection_components["panel"].open = True # ------------------------------------------------------------------ # # Iteration Counter + Observability Strip # # ------------------------------------------------------------------ # def build_stats_strip() -> dict[str, Any]: """ Build the live stats strip showing iteration count, exec time, and Groq token usage. Displayed as a compact row of metric cards beneath the main output. Returns: Dict with keys: - "iterations_md": gr.Markdown — debug iteration count - "exec_time_md": gr.Markdown — execution time in seconds - "tokens_md": gr.Markdown — total Groq tokens used - "warning_md": gr.Markdown — rate limit warning (hidden by default) """ with gr.Row(elem_id="stats_strip"): iterations_md = gr.Markdown( value="**Iterations:** 0", elem_id="stats_iterations", ) exec_time_md = gr.Markdown( value="**Time:** 0.0s", elem_id="stats_exec_time", ) tokens_md = gr.Markdown( value="**Tokens:** 0", elem_id="stats_tokens", ) warning_md = gr.Markdown( value="", visible=False, elem_id="stats_warning", ) return { "iterations_md": iterations_md, "exec_time_md": exec_time_md, "tokens_md": tokens_md, "warning_md": warning_md, } def update_stats_strip( stats_components: dict[str, Any], iterations: int, exec_time: float, total_tokens: int, rate_limit_warning: bool = False, ) -> None: """ Update the stats strip with current pipeline run metrics. Args: stats_components: Dict returned by build_stats_strip(). iterations: Number of debug iterations completed. exec_time: Total wall-clock time in seconds. total_tokens: Total Groq tokens used this run. rate_limit_warning: If True, show a rate limit warning. """ stats_components["iterations_md"].value = f"**Iterations:** {iterations}" stats_components["exec_time_md"].value = f"**Time:** {exec_time:.1f}s" stats_components["tokens_md"].value = f"**Tokens:** {total_tokens:,}" if rate_limit_warning: stats_components["warning_md"].value = ( "⚠ Approaching Groq rate limit — consider switching to llama3-8b" ) stats_components["warning_md"].visible = True else: stats_components["warning_md"].visible = False # ------------------------------------------------------------------ # # Model Switcher # # ------------------------------------------------------------------ # def build_model_switcher() -> dict[str, Any]: """ Build the model switcher toggle between Llama 3.1 8B and 70B. 8B is faster and cheaper — good for simple tasks and when approaching rate limits. 70B gives higher quality for complex tasks. Returns: Dict with keys: - "radio": gr.Radio — model selection """ radio = gr.Radio( choices=["llama3-8b-8192", "llama3-70b-8192"], value="llama3-70b-8192", label="Model", info="8B is faster · 70B is more capable", elem_id="model_switcher", ) return {"radio": radio} # ------------------------------------------------------------------ # # Language Selector with Auto-detect Badge # # ------------------------------------------------------------------ # def build_language_selector() -> dict[str, Any]: """ Build the language selector radio with auto-detect status display. The auto-detect badge shows the detected language and confidence so the user knows whether to trust the pre-selection. Returns: Dict with keys: - "radio": gr.Radio — Python / SQL selector - "badge_md": gr.Markdown — auto-detect result badge """ with gr.Group(): radio = gr.Radio( choices=["python", "sql"], value="python", label="Language", elem_id="language_selector", ) badge_md = gr.Markdown( value="", visible=False, elem_id="language_badge", ) return { "radio": radio, "badge_md": badge_md, } def update_language_badge( selector_components: dict[str, Any], detected_language: str, confidence: str, reason: str, ) -> None: """ Update the auto-detect badge after detection runs. Args: selector_components: Dict returned by build_language_selector(). detected_language: The detected language string. confidence: 'high', 'medium', or 'low'. reason: One-sentence explanation of the detection. """ emoji = {"high": "🟢", "medium": "🟡", "low": "🔴"}.get(confidence, "⚪") selector_components["badge_md"].value = ( f"{emoji} Auto-detected: **{detected_language}** " f"({confidence} confidence) — {reason}" ) selector_components["badge_md"].visible = True selector_components["radio"].value = detected_language