import json import logging import os import gradio as gr from dotenv import load_dotenv from huggingface_hub import HfApi # Import analysis pipeline helpers from analysis_utils import (check_cache_and_download, check_endpoint_status, fetch_and_validate_code, format_tldr_prompt, generate_and_parse_tldr, generate_detailed_report, generate_summary_report, parse_tldr_json_response, render_data_details_markdown, render_tldr_markdown, upload_results) # Import general utils from utils import list_cached_spaces # Added import # Removed LLM interface imports, handled by analysis_utils # from llm_interface import ERROR_503_DICT # from llm_interface import parse_qwen_response, query_qwen_endpoint # Removed prompts import, handled by analysis_utils # from prompts import format_privacy_prompt, format_summary_highlights_prompt # Removed specific utils imports now handled via analysis_utils # from utils import ( # check_report_exists, # download_cached_reports, # get_space_code_files, # upload_reports_to_dataset, # ) # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) # Load environment variables from .env file # This is important to ensure API keys and endpoints are loaded before use load_dotenv() # --- Constants --- HF_TOKEN = os.getenv("HF_TOKEN") ENDPOINT_NAME = "qwen2-5-coder-32b-instruct-pmf" DATASET_ID = "yjernite/spaces-privacy-reports" CACHE_INFO_MSG = ( "\n\n*(Report retrieved from cache)*" # Still needed for dropdown cache hit message ) DEFAULT_SELECTION = "HuggingFaceTB/SmolVLM2" # TRUNCATION_WARNING now defined and used within analysis_utils # TRUNCATION_WARNING = """**⚠️ Warning:** The input data (code and/or prior analysis) was too long for the AI model's context limit and had to be truncated. The analysis below may be incomplete or based on partial information.\n\n---\n\n""" ERROR_503_USER_MESSAGE = """It appears that the analysis model endpoint is currently down or starting up. You have a few options: * **Wait & Retry:** Try clicking "Get Space Report" again in ~3-5 minutes. Endpoints often scale down to save resources and take a short time to wake up. * **Select Cached Report:** Use the dropdown above to view a report for a Space that has already been analyzed. * **Request Analysis:** If the error persists, please [open an issue or discussion](https://huggingface.co/spaces/yjernite/space-privacy/discussions) in the Space's Community tab requesting analysis for your target Space ID. We can run the job manually when the endpoint is available. """ def _run_live_analysis(space_id: str, progress=gr.Progress(track_tqdm=True)): """ Performs the full analysis pipeline using helper functions from analysis_utils. Yields tuples of Gradio updates. """ total_steps = 9 # Increased step count for TLDR generation current_step = 0 summary_report = "" privacy_report = "" tldr_data = None tldr_markdown_content = "*TLDR loading...*" data_details_content = ( "*Data details loading...*" # Default message for new component ) # Initial message before first step tldr_status_message = "*Starting analysis...*" # --- Step 1: Check Cache --- current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Checking cache..." progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" yield ( gr.update(value=tldr_status_message, visible=True), # TLDR shows progress gr.update(value="*Checking cache...*", visible=True), gr.update(value="Checking cache for existing reports...", visible=True), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) cache_result = check_cache_and_download(space_id, DATASET_ID, HF_TOKEN) if cache_result["status"] == "cache_hit": progress(total_steps / total_steps, desc="Complete (from cache)") # Try to parse and render TLDR from cache tldr_json_str = cache_result.get("tldr_json_str") rendered_tldr = "*TLDR not found in cache.*" if tldr_json_str: try: cached_tldr_data = json.loads(tldr_json_str) # Render both parts rendered_tldr = render_tldr_markdown(cached_tldr_data, space_id) rendered_data_details = render_data_details_markdown(cached_tldr_data) except Exception as parse_err: logging.warning( f"Failed to parse cached TLDR JSON for {space_id}: {parse_err}" ) rendered_tldr = "*Error parsing cached TLDR.*" rendered_data_details = ( "*Could not load data details due to parsing error.*" ) yield ( gr.update(value=rendered_tldr, visible=True), gr.update(value=rendered_data_details, visible=True), gr.update(value=cache_result["summary"], visible=True), gr.update(value=cache_result["privacy"], visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) return # End generation successfully from cache elif cache_result["status"] == "cache_error": # Display final error in TLDR field tldr_status_message = ( f"*Cache download failed. {cache_result.get('ui_message', '')}*" ) data_details_content = "*Data details unavailable due to cache error.*" yield ( gr.update(value=tldr_status_message, visible=True), gr.update(value=data_details_content, visible=True), gr.update(value=cache_result["ui_message"], visible=True), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) # Still continue to live analysis if cache download fails elif cache_result["status"] == "cache_miss": tldr_status_message = f"*{progress_desc} - Cache miss.*" # Update status data_details_content = "*Generating report...*" yield ( gr.update(value=tldr_status_message, visible=True), gr.update(value=data_details_content, visible=True), gr.update(value="Cache miss. Starting live analysis...", visible=True), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) elif "error_message" in cache_result: # Display final error in TLDR field tldr_status_message = ( f"*Cache check failed. {cache_result.get('error_message', '')}*" ) data_details_content = "*Data details unavailable due to cache error.*" yield ( gr.update(value=tldr_status_message, visible=True), gr.update(value=data_details_content, visible=True), gr.update( value=f"Cache check failed: {cache_result.get('error_message', 'Unknown error')}. Proceeding with live analysis...", visible=True, ), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) # Still continue if cache check fails # --- Step 2: Check Endpoint Status --- current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Checking endpoint..." progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" yield ( gr.update(value=tldr_status_message, visible=True), # TLDR shows progress gr.update(), gr.update(value="Checking analysis model endpoint status...", visible=True), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) endpoint_result = check_endpoint_status( ENDPOINT_NAME, HF_TOKEN, ERROR_503_USER_MESSAGE ) if endpoint_result["status"] == "error": progress(total_steps / total_steps, desc="Endpoint Error") # Display final error in TLDR field tldr_markdown_content = endpoint_result["ui_message"] yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value="", visible=False), gr.update(value="", visible=False), gr.update(value="", visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), ) return # --- Step 3: Fetch Code Files --- current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Fetching code..." progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" yield ( gr.update(value=tldr_status_message, visible=True), # TLDR shows progress gr.update(), gr.update(value="Fetching code files from the Space...", visible=True), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) code_result = fetch_and_validate_code(space_id) if code_result["status"] == "error": progress(total_steps / total_steps, desc="Code Fetch Error") # Display final error in TLDR field tldr_markdown_content = ( f"**Error:** {code_result.get('ui_message', 'Failed to fetch code.')}" ) yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value="", visible=False), gr.update(value="", visible=False), gr.update(value="Analysis Canceled", visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True, open=False), ) return code_files = code_result["code_files"] # --- Step 4: Generate DETAILED Privacy Report (LLM Call 1) --- current_step += 1 progress_desc = ( f"Step {current_step}/{total_steps}: Generating privacy report (AI Call 1)..." ) progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" yield ( gr.update(value=tldr_status_message, visible=True), # TLDR shows progress gr.update(), gr.update( value="Generating detailed privacy report (AI Call 1)...", visible=True ), gr.update(value="Generating detailed privacy report via AI...", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=True), ) privacy_result = generate_detailed_report( space_id, code_files, ERROR_503_USER_MESSAGE ) if privacy_result["status"] == "error": progress(total_steps / total_steps, desc="Privacy Report Error") # Display final error in TLDR field tldr_markdown_content = f"**Error:** {privacy_result.get('ui_message', 'Failed during detailed report generation.')}" yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value="", visible=False), gr.update(value="", visible=False), gr.update(value="", visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), ) return privacy_report = privacy_result["report"] # Update UI with successful detailed report yield ( gr.update(value=tldr_status_message, visible=True), # Still show progress gr.update(), gr.update( value="Detailed privacy report generated. Proceeding...", visible=True ), gr.update(value=privacy_report, visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=True), ) # --- Step 5: Fetch Model Descriptions (Placeholder/Optional) --- current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Extracting model info..." progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" logging.info(progress_desc + " (Placeholder)") yield ( gr.update(value=tldr_status_message, visible=True), # TLDR shows progress gr.update(), gr.update(value="Extracting model info...", visible=True), gr.update(), gr.update(), gr.update(), gr.update(), ) # model_ids = extract_hf_model_ids(code_files) # utils function not imported # model_descriptions = get_model_descriptions(model_ids) # utils function not imported # Add model_descriptions to context if needed for summary prompt later # --- Step 6: Generate Summary + Highlights Report (LLM Call 2) --- current_step += 1 progress_desc = ( f"Step {current_step}/{total_steps}: Generating summary (AI Call 2)..." ) progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" yield ( gr.update(value=tldr_status_message, visible=True), # TLDR shows progress gr.update(), gr.update(value="Generating summary & highlights (AI Call 2)...", visible=True), gr.update(), gr.update(), gr.update(), gr.update(), ) summary_result = generate_summary_report( space_id, code_files, privacy_report, ERROR_503_USER_MESSAGE ) if ( summary_result["status"] == "error_503_summary" or summary_result["status"] == "error_summary" ): progress(total_steps / total_steps, desc="Summary Report Error") # Display error in TLDR, show partial results below tldr_markdown_content = f"**Error:** {summary_result.get('ui_message', 'Failed during summary generation.')}" data_details_content = "*Data details may be incomplete.*" yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value=data_details_content, visible=True), gr.update(value=summary_result["ui_message"], visible=True), gr.update(value=privacy_report, visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=True), ) return elif summary_result["status"] != "success": progress(total_steps / total_steps, desc="Summary Report Error") # Display error in TLDR, show partial results below tldr_markdown_content = f"**Error:** Unexpected error generating summary: {summary_result.get('ui_message', 'Unknown')}" data_details_content = "*Data details unavailable.*" yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value=data_details_content, visible=True), gr.update( value=f"Unexpected error generating summary: {summary_result.get('ui_message', 'Unknown')}", visible=True, ), gr.update(value=privacy_report, visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=True), ) return summary_report = summary_result["report"] # Update UI with successful summary report before TLDR generation tldr_status_message = ( f"*{progress_desc} - Success. Generating TLDR...*" # Update status ) data_details_content = "*Generating data details...*" yield ( gr.update(value=tldr_status_message, visible=True), gr.update(value=data_details_content, visible=True), gr.update(value=summary_report, visible=True), gr.update(value=privacy_report, visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=True), ) # --- Step 7: Generate TLDR --- (New Step) current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Generating TLDR summary..." progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" yield ( gr.update(value=tldr_status_message, visible=True), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), ) tldr_data = None # Reset tldr_data before attempt try: # Call the combined helper function from analysis_utils tldr_data = generate_and_parse_tldr(privacy_report, summary_report) if tldr_data: logging.info(f"Successfully generated and parsed TLDR for {space_id}.") tldr_markdown_content = render_tldr_markdown(tldr_data, space_id) data_details_content = render_data_details_markdown(tldr_data) else: logging.warning( f"Failed to generate or parse TLDR for {space_id}. Proceeding without it." ) tldr_markdown_content = "*TLDR generation failed.*" data_details_content = "*Data details generation failed.*" except Exception as tldr_err: # This catch block might be redundant now if generate_and_parse_tldr handles its errors logging.error( f"Unexpected error during TLDR generation step call for {space_id}: {tldr_err}" ) tldr_markdown_content = "*Error during TLDR generation step.*" data_details_content = "*Error generating data details.*" tldr_data = None # Ensure it's None on error # Update UI including the generated (or failed) TLDR before upload yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value=data_details_content, visible=True), gr.update(), gr.update(), gr.update(visible=True, open=False), gr.update(), gr.update(), ) # --- Step 8: Upload to Cache --- (Old Step 7) current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Uploading to cache..." progress(current_step / total_steps, desc=progress_desc) tldr_status_message = f"*{progress_desc}*" # Display final action in TLDR field yield ( gr.update(value=tldr_status_message, visible=True), gr.update(), gr.update(value="Uploading results to cache...", visible=True), gr.update(), gr.update(), gr.update(), gr.update(), ) upload_needed = ( cache_result["status"] != "cache_hit" and cache_result["status"] != "cache_error" ) if upload_needed: # Call imported function, now passing tldr_data upload_result = upload_results( space_id, summary_report, privacy_report, DATASET_ID, HF_TOKEN, tldr_json_data=tldr_data, ) if upload_result["status"] == "error": # Ensure logging uses f-string if adding step count here logging.error( f"Cache upload failed: {upload_result.get('message', 'Unknown error')}" ) # Non-critical, don't stop the UI, just log elif upload_result["status"] == "skipped": logging.info(f"Cache upload skipped: {upload_result.get('reason', '')}") else: logging.info( "Skipping cache upload as results were loaded from cache or cache check failed." ) # Update UI including the generated (or failed) TLDR before upload # Yield 7 updates yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value=data_details_content, visible=True), gr.update(value=summary_report, visible=True), gr.update(value=privacy_report, visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) # --- Step 9: Final Update --- (Old Step 8) current_step += 1 progress_desc = f"Step {current_step}/{total_steps}: Analysis Complete!" progress(current_step / total_steps, desc=progress_desc) logging.info(progress_desc + f" Analysis complete for {space_id}.") # Yield final state again to ensure UI is correct after potential upload messages # Display final generated TLDR and Data Details yield ( gr.update(value=tldr_markdown_content, visible=True), gr.update(value=data_details_content, visible=True), gr.update(value=summary_report, visible=True), gr.update(value=privacy_report, visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) # --- Original Input Handling Wrapper (updated yields for initial errors) --- def get_space_report_wrapper( selected_cached_space: str | None, new_space_id: str | None, progress=gr.Progress(track_tqdm=True), ): """ Wrapper function to decide whether to fetch cache or run live analysis. Handles the logic based on Dropdown and Textbox inputs. Yields tuples of Gradio updates. """ target_space_id = None source = "new" # Assume new input unless dropdown is chosen # Prioritize new_space_id if provided if new_space_id and new_space_id.strip(): target_space_id = new_space_id.strip() if target_space_id == selected_cached_space: source = "dropdown_match" # User typed ID that exists in dropdown else: source = "new" elif selected_cached_space: target_space_id = selected_cached_space source = "dropdown" if not target_space_id: # Yield 7 updates yield ( gr.update(value="*Please provide a Space ID.*", visible=True), gr.update(value="", visible=False), gr.update( value="Please select an existing report or enter a new Space ID.", visible=True, ), gr.update(value="", visible=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=False), ) return if "/" not in target_space_id: # Yield 7 updates yield ( gr.update(value="*Invalid Space ID format.*", visible=True), gr.update(value="", visible=False), gr.update( value=f"Invalid Space ID format: '{target_space_id}'. Use 'owner/name'.", visible=True, ), gr.update(value="", visible=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=False), ) return logging.info(f"Request received for: '{target_space_id}' (Source: {source})") if source == "dropdown": progress(0.1, desc="Fetching selected cached report...") # Yield 7 updates (initial placeholder) yield ( gr.update(value="*Loading TLDR...*", visible=True), gr.update(value="*Loading data details...*", visible=True), gr.update(value="Fetching selected cached report...", visible=True), gr.update(value="", visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) cache_result = check_cache_and_download(target_space_id, DATASET_ID, HF_TOKEN) if cache_result["status"] == "cache_hit": logging.info( f"Successfully displayed cached reports for selected '{target_space_id}'." ) progress(1.0, desc="Complete (from cache)") # Use the cached report text directly here, adding the cache message is done within the helper now. # Parse and render TLDR if available tldr_json_str = cache_result.get("tldr_json_str") rendered_tldr = "*TLDR not found in cache.*" if tldr_json_str: try: cached_tldr_data = json.loads(tldr_json_str) rendered_tldr = render_tldr_markdown( cached_tldr_data, target_space_id ) rendered_data_details = render_data_details_markdown( cached_tldr_data ) except Exception as parse_err: logging.warning( f"Failed to parse cached TLDR JSON for {target_space_id}: {parse_err}" ) rendered_tldr = "*Error parsing cached TLDR.*" rendered_data_details = ( "*Could not load data details due to parsing error.*" ) yield ( gr.update(value=rendered_tldr, visible=True), gr.update(value=rendered_data_details, visible=True), gr.update(value=cache_result["summary"], visible=True), gr.update(value=cache_result["privacy"], visible=True), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), ) else: # Cache miss or error for a dropdown selection is an error state error_msg = cache_result.get( "ui_message", f"Failed to find or download cached report for selected '{target_space_id}'.", ) logging.error(error_msg) progress(1.0, desc="Error") yield ( gr.update(value="*TLDR load failed.*", visible=True), gr.update(value="*Data details load failed.*", visible=True), gr.update(value=error_msg, visible=True), gr.update(value="", visible=False), gr.update(visible=True, open=False), gr.update(visible=True, open=False), gr.update(visible=False), ) return # Stop after handling dropdown source # --- Live Analysis or Check Cache for New Input --- # If it came from the textbox OR was a dropdown match, run the full live analysis pipeline # which includes its own cache check at the beginning. else: # source == "new" or source == "dropdown_match" # Yield intermediate updates from the generator by iterating through it for update_tuple in _run_live_analysis(target_space_id, progress): yield update_tuple # --- Load Initial Data Function (for demo.load) --- def load_cached_list(): """Fetches the list of cached spaces and determines the default selection.""" print("Running demo.load: Fetching list of cached spaces...") # Use os.getenv here directly as HF_TOKEN might be loaded after initial import token = os.getenv("HF_TOKEN") cached_list = list_cached_spaces(DATASET_ID, token) default_value = DEFAULT_SELECTION if DEFAULT_SELECTION in cached_list else None if not cached_list: print( "WARNING: No cached spaces found or failed to fetch list during demo.load." ) # Return an update object for the dropdown using gr.update() return gr.update(choices=cached_list, value=default_value) # --- Gradio Interface Definition --- # Use HTML/CSS for centering the title TITLE = "

🤗 Space Privacy Analyzer 🕵️

\n

Automatic code Data transfer review powered by Qwen2.5-Coder-32B-Instruct

" DESCRIPTION = """ ### Hugging Face 🤗 Space - Privacy & Data Check [Hugging Face 🤗 Spaces](https://huggingface.co/spaces) offer a convenient way to build and share code demos online; especially leveraging and exploring AI systems. In most cases, the code for these demos is open source — which provides a unique opportunity to **examine how privacy and data transfers are managed**. This demo leverages a code analysis model ([Qwen2.5-Coder-32B-Instruct](https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct)) to help explore privacy questions in two steps: 1. Obtain and **parse the code** of a Space to identify: - data inputs, - AI model use, - API calls, - data transfers. 2. Generate a summary of the Space's function and highlight **key privacy points**. Use the dropdown menu below to explore the [reports generated for some popular Spaces](https://huggingface.co/datasets/yjernite/spaces-privacy-reports/tree/main), or enter a new Space ID to query your own 👇 *Please note the following limitations:* - *The model may miss important details in the code, especially when it leverages Docker files or external libraries.* - *This app uses the base Qwen Coder model without specific adaptation to the task. We'd love to discuss how to improve this, if you want to participate [feel free to open a discussion!](https://huggingface.co/spaces/yjernite/space-privacy/discussions)* """ with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown(TITLE) # This will now render the centered HTML with gr.Row(): with gr.Column(scale=1): # Left column for inputs description_accordion = gr.Accordion( "What Privacy Questions do 🤗 Spaces Raise? Click here for Demo Description 👇", open=False, visible=True, ) with description_accordion: gr.Markdown(DESCRIPTION) cached_spaces_dropdown = gr.Dropdown( label="Select Existing Report", info="Select a Space whose report has been previously generated.", choices=[], # Initialize empty, will be populated by demo.load value=None, # Initialize empty ) space_id_input = gr.Textbox( label="Or Enter New Space ID", placeholder="owner/space-name", info="Enter a new Space ID to analyze (takes precedence over selection).", ) analyze_button = gr.Button("Get Space Report", variant="primary", scale=1) with gr.Column(scale=1): # Right column for outputs # Define TLDR Markdown component first, always visible gr.Markdown("### Privacy TLDR 🕵️\n", visible=True) tldr_markdown = gr.Markdown( "*Select or enter a Space ID to get started.*", visible=True ) # Define Accordions next, closed by default, visible data_types_accordion = gr.Accordion( "Data Types at Play", open=False, visible=True ) with data_types_accordion: data_details_markdown = gr.Markdown("*Data details will appear here.*") summary_accordion = gr.Accordion( "Summary & Privacy Highlights", open=False, visible=True, # Changed to open=False ) privacy_accordion = gr.Accordion( "Detailed Privacy Analysis Report", open=False, visible=True, # Changed to open=False ) with summary_accordion: summary_markdown = gr.Markdown( "Enter or select a Space ID and click Get Report.", show_copy_button=True, ) with privacy_accordion: privacy_markdown = gr.Markdown( "Detailed report will appear here.", show_copy_button=True ) # --- Event Listeners --- # Load event to populate the dropdown when the UI loads for a user session demo.load(fn=load_cached_list, inputs=None, outputs=cached_spaces_dropdown) # Button click event analyze_button.click( fn=get_space_report_wrapper, inputs=[cached_spaces_dropdown, space_id_input], outputs=[ tldr_markdown, data_details_markdown, # Added data details output summary_markdown, privacy_markdown, data_types_accordion, # Added data details accordion output summary_accordion, privacy_accordion, ], show_progress="full", ) # --- Application Entry Point --- if __name__ == "__main__": logging.info("Starting Gradio application...") demo.launch()