import gradio as gr # No os needed here now for file paths unless for basename for display from app_logic import ( create_space, update_space_file, load_token_from_image_and_set_env, KEYLOCK_DECODE_AVAILABLE, list_space_files_for_browsing, get_space_file_content, ) KEYLOCK_DECODE_AVAILABLE = False # Gradio interface def main_ui(): with gr.Blocks(theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky), title="Hugging Face Space Builder") as demo: gr.Markdown( """ # πŸ› οΈ Hugging Face Space Builder ## Build Huggingface Space from a standardized string that models can be prompted to export. Create, view, and manage Hugging Face Spaces. Provide your Hugging Face API token directly or load it from a KeyLock Wallet image. """ ) # --- Authentication Section (Unchanged) --- with gr.Accordion("πŸ”‘ Authentication Methods", open=False): gr.Markdown( """ **Token Precedence:** 1. If a token is successfully loaded from a KeyLock Wallet image, it will be used. 2. Otherwise, the token entered in the 'Enter API Token Directly' textbox will be used. """ ) gr.Markdown("---") gr.Markdown("### Method 1: Enter API Token Directly") api_token_ui_input = gr.Textbox(label="Hugging Face API Token (hf_xxx)", type="password", placeholder="Enter token OR load from Wallet image") if KEYLOCK_DECODE_AVAILABLE: gr.Markdown("---") gr.Markdown("### Method 2: Load API Token to this sytem environment from KeyLock Wallet Image") gr.Markdown("### Get a KeyLock Wallet Image Here: [/spaces/broadfield-dev/KeyLock-API-Wallet](https://huggingface.co/spaces/broadfield-dev/KeyLock-API-Wallet)") with gr.Row(): keylock_image_input = gr.Image(label="KeyLock Wallet Image (PNG)", type="pil", image_mode="RGBA") keylock_password_input = gr.Textbox(label="Image Password", type="password") keylock_decode_button = gr.Button("Load Token from Wallet Image", variant="secondary") keylock_status_output = gr.Markdown(label="Wallet Image Decoding Status", value="Status...") keylock_decode_button.click(load_token_from_image_and_set_env, [keylock_image_input, keylock_password_input], [keylock_status_output]) else: gr.Markdown("_(KeyLock Wallet image decoding disabled: library not found.)_") with gr.Accordion("πŸ“£ Example Prompt", open=False): gr.Markdown("""```plaintext # Prompt: Generate program files for a project as a single plain text string, strictly adhering to the markdown format below. **Every single line**, including backticks, language identifiers, file content, and empty lines, **must** be prefixed with '# ' to comment it out. This is critical to avoid code box interference. The output must include a complete file structure and the contents of each file, with all necessary code and configurations for a functional project. **Do not deviate from this format under any circumstances.** # Format (exact return format with single leading "# "): # # Space: [owner/project-name] # ## File Structure # ``` # πŸ“ Root # πŸ“„ [file1] # πŸ“„ [file2] # ... # # # Below are the contents of all files in the space: # # ### File: [file1] # ```[language] # [content] # ``` # # ### File: [file2] # ```[language] # [content] # ``` # # ... (repeat for each file) # Correct Example Output (exact, every line prefixed with '# '): # # Space: user/my-app # ## File Structure # ``` # πŸ“ Root # πŸ“„ app.py # πŸ“„ requirements.txt # ``` # # # Below are the contents of all files in the space: # # ### File: app.py # ```python # print("Hello, World!") # ``` # # ### File: requirements.txt # ```text # gradio==4.44.0 # ``` # Incorrect Example Output: ## ## File Structure <- INCORRECT: AI used "## " instead of "# " ## ```text <- INCORRECT ## πŸ“ Root <- INCORRECT (missing "# ") # # # πŸ“ Root <- INCORRECT: AI used "# # #" instead of "# " # Instructions: - Use exactly `# # Space: [owner/project-name]` as the header (e.g., `user/my-app`). - Under `## File Structure`, start with `# πŸ“ Root` followed by `# πŸ“„` for each file, using exact icons and spacing. - For each file, use `# ### File: [filename]` followed by a code block where every line, including backticks (e.g., `# ```), language identifier (e.g., `# python`), and content (e.g., `# print("Hello")`), is prefixed with `# `. - For binary files, use `# [Binary file - size in bytes]` as content with no language identifier. - **Every line** must start with `# `, no exceptions, including empty lines within code blocks. - Provide accurate, functional code or content for each file, suitable for the project’s purpose. - Ensure the output is concise, complete, and parseable to extract file structure and contents. - Output everything as a single plain text string within one code box. ``` """ ) # --- Main Application Tabs --- with gr.Tabs(): with gr.TabItem("πŸš€ Create New Space"): # (Create Space UI Unchanged) with gr.Row(): space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2) owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1) sdk_create_input = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio") gr.Markdown("### Example Source: [/spaces/broadfield-dev/repo_to_md](https://huggingface.co/spaces/broadfield-dev/repo_to_md)") markdown_input_create = gr.Textbox(label="Markdown File Structure & Content", placeholder="Example:\n### File: app.py\n# ```python\nprint(\"Hello\")\n# ```", lines=15, interactive=True) create_btn = gr.Button("Create Space", variant="primary") create_output_md = gr.Markdown(label="Result") create_btn.click(create_space, [api_token_ui_input, space_name_create_input, owner_create_input, sdk_create_input, markdown_input_create], create_output_md) # --- "Browse & Edit Files" Tab (Hub-based) --- with gr.TabItem("πŸ“‚ Browse & Edit Space Files"): gr.Markdown("Browse, view, and edit files directly on a Hugging Face Space.") with gr.Row(): browse_space_name_input = gr.Textbox(label="Space Name", placeholder="my-target-app", scale=2) browse_owner_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1) browse_load_files_button = gr.Button("Load Files List from Space", variant="secondary") browse_status_output = gr.Markdown(label="File List Status") gr.Markdown("---") gr.Markdown("### Select File to View/Edit") # Using Radio for file list. Could be Dropdown for many files. # `choices` will be updated dynamically. file_selector_radio = gr.Radio( label="Files in Space", choices=[], interactive=True, info="Select a file to load its content below." ) gr.Markdown("---") gr.Markdown("### File Editor") file_editor_textbox = gr.Textbox( label="File Content (Editable)", lines=20, interactive=True, placeholder="Select a file from the list above to view/edit its content." ) edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g., Update app.py content") update_edited_file_button = gr.Button("Update File in Space", variant="primary") edit_update_status_output = gr.Markdown(label="File Update Result") # --- Event Handlers for Browse & Edit Tab (Hub-based) --- def handle_load_space_files_list(token_from_ui, space_name, owner_name): if not space_name: return { browse_status_output: gr.Markdown("Error: Space Name cannot be empty."), file_selector_radio: gr.Radio(choices=[], value=None), # Clear radio file_editor_textbox: gr.Textbox(value=""), # Clear editor } files_list, error_msg = list_space_files_for_browsing(token_from_ui, space_name, owner_name) if error_msg and files_list is None: # Indicates a hard error return { browse_status_output: gr.Markdown(f"Error: {error_msg}"), file_selector_radio: gr.Radio(choices=[], value=None), file_editor_textbox: gr.Textbox(value=""), } if error_msg and not files_list: # Info message like "no files found" return { browse_status_output: gr.Markdown(error_msg), # Show "No files found" file_selector_radio: gr.Radio(choices=[], value=None), file_editor_textbox: gr.Textbox(value=""), } return { browse_status_output: gr.Markdown(f"Files loaded for '{owner_name}/{space_name}'. Select a file to edit."), file_selector_radio: gr.Radio(choices=files_list, value=None, label=f"Files in {owner_name}/{space_name}"), file_editor_textbox: gr.Textbox(value=""), # Clear editor on new list load } browse_load_files_button.click( fn=handle_load_space_files_list, inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input], outputs=[browse_status_output, file_selector_radio, file_editor_textbox] ) def handle_file_selected_for_editing(token_from_ui, space_name, owner_name, selected_filepath_evt: gr.SelectData): if not selected_filepath_evt or not selected_filepath_evt.value: # This might happen if the radio is cleared or has no selection return { file_editor_textbox: gr.Textbox(value=""), browse_status_output: gr.Markdown("No file selected or selection cleared.") } selected_filepath = selected_filepath_evt.value # The value of the selected radio button if not space_name: # Should not happen if file list is populated return { file_editor_textbox: gr.Textbox(value="Error: Space name is missing."), browse_status_output: gr.Markdown("Error: Space context lost. Please reload file list.") } content, error_msg = get_space_file_content(token_from_ui, space_name, owner_name, selected_filepath) if error_msg: return { file_editor_textbox: gr.Textbox(value=f"Error loading file content: {error_msg}"), browse_status_output: gr.Markdown(f"Failed to load '{selected_filepath}': {error_msg}") } return { file_editor_textbox: gr.Textbox(value=content), browse_status_output: gr.Markdown(f"Content loaded for: {selected_filepath}") } # Use .select event for gr.Radio file_selector_radio.select( fn=handle_file_selected_for_editing, inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input], # Pass space context again outputs=[file_editor_textbox, browse_status_output] ) update_edited_file_button.click( fn=update_space_file, inputs=[ api_token_ui_input, browse_space_name_input, browse_owner_input, file_selector_radio, # Pass the selected file path from the radio file_editor_textbox, edit_commit_message_input ], outputs=[edit_update_status_output] ) return demo if __name__ == "__main__": demo = main_ui() demo.launch(mcp_server=True,show_error=True)