broadfield-dev commited on
Commit
486885c
ยท
verified ยท
1 Parent(s): 9a9ea97

Update app.py

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