broadfield-dev commited on
Commit
d486fa8
Β·
verified Β·
1 Parent(s): a84c1a2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +251 -0
app.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from app_logic import (
3
+ create_space,
4
+ update_space_file,
5
+ load_token_from_image_and_set_env,
6
+ KEYLOCK_DECODE_AVAILABLE,
7
+ list_space_files_for_browsing,
8
+ get_space_file_content,
9
+ )
10
+ KEYLOCK_DECODE_AVAILABLE = False
11
+ # Gradio interface
12
+ def main_ui():
13
+ 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:
14
+ gr.Markdown(
15
+ """
16
+ # πŸ› οΈ Hugging Face Space Builder
17
+ ## Build Huggingface Space from a standardized string that models can be prompted to export.
18
+
19
+ Create, view, and manage Hugging Face Spaces.
20
+ Provide your Hugging Face API token directly or load it from a KeyLock Wallet image.
21
+ """
22
+ )
23
+
24
+ # --- Authentication Section (Unchanged) ---
25
+ with gr.Accordion("πŸ”‘ Authentication Methods", open=False):
26
+ gr.Markdown(
27
+ """
28
+ **Token Precedence:**
29
+ 1. If a token is successfully loaded from a KeyLock Wallet image, it will be used.
30
+ 2. Otherwise, the token entered in the 'Enter API Token Directly' textbox will be used.
31
+ """
32
+ )
33
+ gr.Markdown("---")
34
+ gr.Markdown("### Method 1: Enter API Token Directly")
35
+ api_token_ui_input = gr.Textbox(label="Hugging Face API Token (hf_xxx)", type="password", placeholder="Enter token OR load from Wallet image")
36
+ if KEYLOCK_DECODE_AVAILABLE:
37
+ gr.Markdown("---")
38
+ gr.Markdown("### Method 2: Load API Token to this sytem environment from KeyLock Wallet Image")
39
+ gr.Markdown("### Get a KeyLock Wallet Image Here: [/spaces/broadfield-dev/KeyLock-API-Wallet](https://huggingface.co/spaces/broadfield-dev/KeyLock-API-Wallet)")
40
+ with gr.Row():
41
+ keylock_image_input = gr.Image(label="KeyLock Wallet Image (PNG)", type="pil", image_mode="RGBA")
42
+ keylock_password_input = gr.Textbox(label="Image Password", type="password")
43
+ keylock_decode_button = gr.Button("Load Token from Wallet Image", variant="secondary")
44
+ keylock_status_output = gr.Markdown(label="Wallet Image Decoding Status", value="Status...")
45
+ keylock_decode_button.click(load_token_from_image_and_set_env, [keylock_image_input, keylock_password_input], [keylock_status_output])
46
+ else:
47
+ gr.Markdown("_(KeyLock Wallet image decoding disabled: library not found.)_")
48
+ with gr.Accordion("πŸ“£ Example Prompt", open=False):
49
+ gr.Markdown("""```plaintext
50
+ # Prompt:
51
+
52
+ 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.**
53
+
54
+ # Format (exact return format with single leading "# "):
55
+
56
+ # # Space: [owner/project-name]
57
+ # ## File Structure
58
+ # ```
59
+ # πŸ“ Root
60
+ # πŸ“„ [file1]
61
+ # πŸ“„ [file2]
62
+ # ...
63
+ #
64
+ #
65
+ # Below are the contents of all files in the space:
66
+ #
67
+ # ### File: [file1]
68
+ # ```[language]
69
+ # [content]
70
+ # ```
71
+ #
72
+ # ### File: [file2]
73
+ # ```[language]
74
+ # [content]
75
+ # ```
76
+ #
77
+ # ... (repeat for each file)
78
+ # Correct Example Output (exact, every line prefixed with '# '):
79
+ # # Space: user/my-app
80
+ # ## File Structure
81
+ # ```
82
+ # πŸ“ Root
83
+ # πŸ“„ app.py
84
+ # πŸ“„ requirements.txt
85
+ # ```
86
+ #
87
+ # # Below are the contents of all files in the space:
88
+ #
89
+ # ### File: app.py
90
+ # ```python
91
+ # print("Hello, World!")
92
+ # ```
93
+ #
94
+ # ### File: requirements.txt
95
+ # ```text
96
+ # gradio==4.44.0
97
+ # ```
98
+ # Incorrect Example Output:
99
+ ## ## File Structure <- INCORRECT: AI used "## " instead of "# "
100
+ ## ```text <- INCORRECT
101
+ ## πŸ“ Root <- INCORRECT (missing "# ")
102
+ # # # πŸ“ Root <- INCORRECT: AI used "# # #" instead of "# "
103
+
104
+ # Instructions:
105
+ - Use exactly `# # Space: [owner/project-name]` as the header (e.g., `user/my-app`).
106
+ - Under `## File Structure`, start with `# πŸ“ Root` followed by `# πŸ“„` for each file, using exact icons and spacing.
107
+ - 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 `# `.
108
+ - For binary files, use `# [Binary file - size in bytes]` as content with no language identifier.
109
+ - **Every line** must start with `# `, no exceptions, including empty lines within code blocks.
110
+ - Provide accurate, functional code or content for each file, suitable for the project’s purpose.
111
+ - Ensure the output is concise, complete, and parseable to extract file structure and contents.
112
+ - Output everything as a single plain text string within one code box.
113
+ ```
114
+ """
115
+ )
116
+
117
+ # --- Main Application Tabs ---
118
+ with gr.Tabs():
119
+ with gr.TabItem("πŸš€ Create New Space"):
120
+ # (Create Space UI Unchanged)
121
+ with gr.Row():
122
+ space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
123
+ owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
124
+ sdk_create_input = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio")
125
+ gr.Markdown("### Example Source: [/spaces/broadfield-dev/repo_to_md](https://huggingface.co/spaces/broadfield-dev/repo_to_md)")
126
+ 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)
127
+ create_btn = gr.Button("Create Space", variant="primary")
128
+ create_output_md = gr.Markdown(label="Result")
129
+ 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)
130
+
131
+
132
+ # --- "Browse & Edit Files" Tab (Hub-based) ---
133
+ with gr.TabItem("πŸ“‚ Browse & Edit Space Files"):
134
+ gr.Markdown("Browse, view, and edit files directly on a Hugging Face Space.")
135
+ with gr.Row():
136
+ browse_space_name_input = gr.Textbox(label="Space Name", placeholder="my-target-app", scale=2)
137
+ browse_owner_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
138
+
139
+ browse_load_files_button = gr.Button("Load Files List from Space", variant="secondary")
140
+ browse_status_output = gr.Markdown(label="File List Status")
141
+
142
+ gr.Markdown("---")
143
+ gr.Markdown("### Select File to View/Edit")
144
+ # Using Radio for file list. Could be Dropdown for many files.
145
+ # `choices` will be updated dynamically.
146
+ file_selector_radio = gr.Radio(
147
+ label="Files in Space",
148
+ choices=[],
149
+ interactive=True,
150
+ info="Select a file to load its content below."
151
+ )
152
+
153
+ gr.Markdown("---")
154
+ gr.Markdown("### File Editor")
155
+ file_editor_textbox = gr.Textbox(
156
+ label="File Content (Editable)", lines=20, interactive=True,
157
+ placeholder="Select a file from the list above to view/edit its content."
158
+ )
159
+ edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g., Update app.py content")
160
+ update_edited_file_button = gr.Button("Update File in Space", variant="primary")
161
+ edit_update_status_output = gr.Markdown(label="File Update Result")
162
+
163
+ # --- Event Handlers for Browse & Edit Tab (Hub-based) ---
164
+ def handle_load_space_files_list(token_from_ui, space_name, owner_name):
165
+ if not space_name:
166
+ return {
167
+ browse_status_output: gr.Markdown("Error: Space Name cannot be empty."),
168
+ file_selector_radio: gr.Radio(choices=[], value=None), # Clear radio
169
+ file_editor_textbox: gr.Textbox(value=""), # Clear editor
170
+ }
171
+
172
+ files_list, error_msg = list_space_files_for_browsing(token_from_ui, space_name, owner_name)
173
+
174
+ if error_msg and files_list is None: # Indicates a hard error
175
+ return {
176
+ browse_status_output: gr.Markdown(f"Error: {error_msg}"),
177
+ file_selector_radio: gr.Radio(choices=[], value=None),
178
+ file_editor_textbox: gr.Textbox(value=""),
179
+ }
180
+ if error_msg and not files_list: # Info message like "no files found"
181
+ return {
182
+ browse_status_output: gr.Markdown(error_msg), # Show "No files found"
183
+ file_selector_radio: gr.Radio(choices=[], value=None),
184
+ file_editor_textbox: gr.Textbox(value=""),
185
+ }
186
+
187
+ return {
188
+ browse_status_output: gr.Markdown(f"Files loaded for '{owner_name}/{space_name}'. Select a file to edit."),
189
+ file_selector_radio: gr.Radio(choices=files_list, value=None, label=f"Files in {owner_name}/{space_name}"),
190
+ file_editor_textbox: gr.Textbox(value=""), # Clear editor on new list load
191
+ }
192
+
193
+ browse_load_files_button.click(
194
+ fn=handle_load_space_files_list,
195
+ inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input],
196
+ outputs=[browse_status_output, file_selector_radio, file_editor_textbox]
197
+ )
198
+
199
+ def handle_file_selected_for_editing(token_from_ui, space_name, owner_name, selected_filepath_evt: gr.SelectData):
200
+ if not selected_filepath_evt or not selected_filepath_evt.value:
201
+ # This might happen if the radio is cleared or has no selection
202
+ return {
203
+ file_editor_textbox: gr.Textbox(value=""),
204
+ browse_status_output: gr.Markdown("No file selected or selection cleared.")
205
+ }
206
+
207
+ selected_filepath = selected_filepath_evt.value # The value of the selected radio button
208
+
209
+ if not space_name: # Should not happen if file list is populated
210
+ return {
211
+ file_editor_textbox: gr.Textbox(value="Error: Space name is missing."),
212
+ browse_status_output: gr.Markdown("Error: Space context lost. Please reload file list.")
213
+ }
214
+
215
+ content, error_msg = get_space_file_content(token_from_ui, space_name, owner_name, selected_filepath)
216
+
217
+ if error_msg:
218
+ return {
219
+ file_editor_textbox: gr.Textbox(value=f"Error loading file content: {error_msg}"),
220
+ browse_status_output: gr.Markdown(f"Failed to load '{selected_filepath}': {error_msg}")
221
+ }
222
+
223
+ return {
224
+ file_editor_textbox: gr.Textbox(value=content),
225
+ browse_status_output: gr.Markdown(f"Content loaded for: {selected_filepath}")
226
+ }
227
+
228
+ # Use .select event for gr.Radio
229
+ file_selector_radio.select(
230
+ fn=handle_file_selected_for_editing,
231
+ inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input], # Pass space context again
232
+ outputs=[file_editor_textbox, browse_status_output]
233
+ )
234
+
235
+ update_edited_file_button.click(
236
+ fn=update_space_file,
237
+ inputs=[
238
+ api_token_ui_input,
239
+ browse_space_name_input,
240
+ browse_owner_input,
241
+ file_selector_radio, # Pass the selected file path from the radio
242
+ file_editor_textbox,
243
+ edit_commit_message_input
244
+ ],
245
+ outputs=[edit_update_status_output]
246
+ )
247
+ return demo
248
+
249
+ if __name__ == "__main__":
250
+ demo = main_ui()
251
+ demo.launch(mcp_server=True,show_error=True)