broadfield-dev commited on
Commit
e69109f
·
verified ·
1 Parent(s): 13591d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -83
app.py CHANGED
@@ -1,13 +1,15 @@
1
  import gradio as gr
 
 
2
  from app_logic import (
3
  create_space,
4
- view_space_files,
5
  update_space_file,
6
- load_token_from_image_and_set_env, # New import
7
- KEYLOCK_DECODE_AVAILABLE # New import
 
 
8
  )
9
- # PIL Image for type hint if needed, though gr.Image handles it
10
- # from PIL import Image
11
 
12
  # Gradio interface
13
  def main_ui():
@@ -15,48 +17,39 @@ def main_ui():
15
  gr.Markdown(
16
  """
17
  # 🛠️ Hugging Face Space Builder
18
- Create, view, and update Hugging Face Spaces.
19
- Provide your Hugging Face API token directly or load it from a steganographic image.
20
  """
21
  )
22
 
23
- # --- Authentication Section ---
24
  with gr.Accordion("🔑 Authentication Methods", open=True):
25
  gr.Markdown(
26
  """
27
  **Token Precedence:**
28
- 1. If a token is successfully loaded from an image (and `HF_TOKEN` is set in the environment), it will be used.
29
  2. Otherwise, the token entered in the 'Enter API Token Directly' textbox will be used.
30
- 3. If a system-wide `HF_TOKEN` environment variable was already set when this app started, it might also be used if methods 1 & 2 don't yield a token.
31
  """
32
  )
33
  gr.Markdown("---")
34
  gr.Markdown("### Method 1: Enter API Token Directly")
35
- api_token_ui_input = gr.Textbox( # Renamed to avoid confusion with resolved_api_token
36
- label="Hugging Face API Token (hf_xxx)",
37
- type="password",
38
- placeholder="Enter your HF token OR load from image below",
39
- info="Get from hf.co/settings/tokens. Needs 'write' access. This is used if image loading fails or is not used."
40
  )
41
 
42
  if KEYLOCK_DECODE_AVAILABLE:
43
  gr.Markdown("---")
44
- gr.Markdown("### Method 2: Load API Token from Steganographic Image")
45
  with gr.Row():
46
  keylock_image_input = gr.Image(
47
- label="Stego Image (PNG containing HF_TOKEN)",
48
- type="pil",
49
- image_mode="RGBA", # << TRY ADDING THIS. Use "RGB" if your images are RGB.
50
- # If you're unsure, you can omit it first to see if the debug saving helps.
51
- # sources=["upload"],
52
  )
53
- keylock_password_input = gr.Textbox(
54
- label="Image Password",
55
- type="password",
56
- placeholder="Password for image decryption"
57
- )
58
- keylock_decode_button = gr.Button("Load Token from Image", variant="secondary")
59
- keylock_status_output = gr.Markdown(label="Image Decoding Status", value="Status will appear here...")
60
 
61
  keylock_decode_button.click(
62
  fn=load_token_from_image_and_set_env,
@@ -64,90 +57,188 @@ def main_ui():
64
  outputs=[keylock_status_output]
65
  )
66
  else:
67
- gr.Markdown(
68
- "_(Image decoding feature (KeyLock-Decode) is disabled as the library could not be imported.)_"
69
- )
70
 
 
 
 
 
71
  # --- Main Application Tabs ---
72
  with gr.Tabs():
73
  with gr.TabItem("🚀 Create New Space"):
74
- # ... (rest of the Create Space UI, inputs will use api_token_ui_input)
75
  with gr.Row():
76
  space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
77
  owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
78
- sdk_create_input = gr.Dropdown(
79
- label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio",
80
- info="Select the type of Space."
81
- )
82
  markdown_input_create = gr.Textbox(
83
  label="Markdown File Structure & Content",
84
- placeholder="""Define files using '### File: path/to/file.ext'
85
- followed by content, optionally in code blocks.
86
- Example:
87
  ### File: app.py
88
  # ```python
89
  print("Hello World!")
90
  # ```
91
- """,
 
 
 
 
 
92
  lines=15, interactive=True,
93
- info="Define files using '### File: path/to/your/file.ext' followed by content."
94
  )
95
  create_btn = gr.Button("Create Space", variant="primary")
96
  create_output_md = gr.Markdown(label="Result")
97
 
98
- with gr.TabItem("📄 View Space Files"):
99
- # ... (rest of the View Space Files UI, inputs will use api_token_ui_input)
 
100
  with gr.Row():
101
- space_name_view_input = gr.Textbox(label="Space Name", placeholder="my-existing-app (no slashes)", scale=2)
102
- owner_view_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
103
- view_btn = gr.Button("List Files", variant="primary")
104
- view_output_md = gr.Markdown(label="Files in Space")
105
-
106
- with gr.TabItem("✏️ Update Space File"):
107
- # ... (rest of the Update Space File UI, inputs will use api_token_ui_input)
108
  with gr.Row():
109
- space_name_update_input = gr.Textbox(label="Space Name", placeholder="my-target-app (no slashes)", scale=2)
110
- owner_update_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
111
- file_path_update_input = gr.Textbox(
112
- label="File Path in Repository", placeholder="e.g., app.py or src/utils.py",
113
- info="The full path to the file within the space."
 
 
 
 
 
 
 
 
 
 
 
 
114
  )
115
- file_content_update_input = gr.Textbox(
116
- label="New File Content", placeholder="Enter the complete new content for the file.",
117
- lines=10, interactive=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  )
119
- commit_message_update_input = gr.Textbox(
120
- label="Commit Message", placeholder="e.g., Update app.py with new feature",
121
- info="Describe the changes."
 
 
 
 
 
 
 
 
 
122
  )
123
- update_btn = gr.Button("Update File", variant="primary")
124
- update_output_md = gr.Markdown(label="Result")
125
 
126
- # Event handlers
127
- # Pass the UI token input to all backend functions.
128
- # The backend's _get_api_token helper will decide the actual token to use.
129
  create_btn.click(
130
  fn=create_space,
131
  inputs=[api_token_ui_input, space_name_create_input, owner_create_input, sdk_create_input, markdown_input_create],
132
  outputs=create_output_md,
133
  )
134
- view_btn.click(
135
- fn=view_space_files,
136
- inputs=[api_token_ui_input, space_name_view_input, owner_view_input],
137
- outputs=view_output_md,
138
- )
139
- update_btn.click(
140
- fn=update_space_file,
141
- inputs=[
142
- api_token_ui_input,
143
- space_name_update_input,
144
- owner_update_input,
145
- file_path_update_input,
146
- file_content_update_input,
147
- commit_message_update_input,
148
- ],
149
- outputs=update_output_md,
150
- )
151
  return demo
152
 
153
  if __name__ == "__main__":
 
1
  import gradio as gr
2
+ import os # For os.path.relpath, os.path.isfile, os.path.basename, os.sep
3
+ from pathlib import Path # For path manipulations if needed
4
  from app_logic import (
5
  create_space,
6
+ # view_space_files, # Optional: can be removed
7
  update_space_file,
8
+ load_token_from_image_and_set_env,
9
+ KEYLOCK_DECODE_AVAILABLE,
10
+ get_space_local_clone_path,
11
+ read_file_from_local_path,
12
  )
 
 
13
 
14
  # Gradio interface
15
  def main_ui():
 
17
  gr.Markdown(
18
  """
19
  # 🛠️ Hugging Face Space Builder
20
+ Create, view, and manage Hugging Face Spaces.
21
+ Provide your Hugging Face API token directly or load it from a KeyLock Wallet image.
22
  """
23
  )
24
 
25
+ # --- Authentication Section (Terminology updated) ---
26
  with gr.Accordion("🔑 Authentication Methods", open=True):
27
  gr.Markdown(
28
  """
29
  **Token Precedence:**
30
+ 1. If a token is successfully loaded from a KeyLock Wallet image, it will be used.
31
  2. Otherwise, the token entered in the 'Enter API Token Directly' textbox will be used.
 
32
  """
33
  )
34
  gr.Markdown("---")
35
  gr.Markdown("### Method 1: Enter API Token Directly")
36
+ api_token_ui_input = gr.Textbox(
37
+ label="Hugging Face API Token (hf_xxx)", type="password",
38
+ placeholder="Enter your HF token OR load from KeyLock Wallet image below",
39
+ info="Get from hf.co/settings/tokens. Needs 'write' access."
 
40
  )
41
 
42
  if KEYLOCK_DECODE_AVAILABLE:
43
  gr.Markdown("---")
44
+ gr.Markdown("### Method 2: Load API Token from KeyLock Wallet Image")
45
  with gr.Row():
46
  keylock_image_input = gr.Image(
47
+ label="KeyLock Wallet Image (PNG containing HF_TOKEN)", type="pil",
48
+ image_mode="RGBA", # Recommended
 
 
 
49
  )
50
+ keylock_password_input = gr.Textbox(label="Image Password", type="password")
51
+ keylock_decode_button = gr.Button("Load Token from Wallet Image", variant="secondary")
52
+ keylock_status_output = gr.Markdown(label="Wallet Image Decoding Status", value="Status will appear here.")
 
 
 
 
53
 
54
  keylock_decode_button.click(
55
  fn=load_token_from_image_and_set_env,
 
57
  outputs=[keylock_status_output]
58
  )
59
  else:
60
+ gr.Markdown("_(KeyLock Wallet image decoding is disabled as the library could not be imported.)_")
 
 
61
 
62
+ # --- State for File Browser/Editor ---
63
+ current_clone_root_path_state = gr.State(None)
64
+ current_editing_file_relative_path_state = gr.State(None)
65
+
66
  # --- Main Application Tabs ---
67
  with gr.Tabs():
68
  with gr.TabItem("🚀 Create New Space"):
 
69
  with gr.Row():
70
  space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
71
  owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
72
+ sdk_create_input = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio")
 
 
 
73
  markdown_input_create = gr.Textbox(
74
  label="Markdown File Structure & Content",
75
+ placeholder="""Example:
 
 
76
  ### File: app.py
77
  # ```python
78
  print("Hello World!")
79
  # ```
80
+
81
+ ### File: README.md
82
+ # ```markdown
83
+ # My App
84
+ This is a README.
85
+ # ```""",
86
  lines=15, interactive=True,
87
+ info="Define files using '### File: path/to/your/file.ext'."
88
  )
89
  create_btn = gr.Button("Create Space", variant="primary")
90
  create_output_md = gr.Markdown(label="Result")
91
 
92
+ # --- Revamped "Browse & Edit Files" Tab ---
93
+ with gr.TabItem("📂 Browse & Edit Files"):
94
+ gr.Markdown("Browse the file structure of a Space, view, and edit files.")
95
  with gr.Row():
96
+ browse_space_name_input = gr.Textbox(label="Space Name", placeholder="my-target-app", scale=2)
97
+ browse_owner_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
98
+
 
 
 
 
99
  with gr.Row():
100
+ browse_files_button = gr.Button("Load Space Files for Browsing", variant="secondary")
101
+ force_refresh_clone_checkbox = gr.Checkbox(label="Force Refresh Clone", value=False, info="Re-download the space.")
102
+
103
+ browse_status_output = gr.Markdown(label="Browsing Status", value="Status will appear here.")
104
+
105
+ gr.Markdown("---")
106
+ gr.Markdown("### File Explorer (Select a file to view/edit)")
107
+ file_explorer_component = gr.FileExplorer(label="Space File Tree", file_count="single", interactive=True, glob="**/*") # glob for all files
108
+
109
+ gr.Markdown("---")
110
+ gr.Markdown("### File Editor")
111
+ # Display for current file being edited
112
+ current_file_display_ro = gr.Textbox(label="Currently Editing File (Relative Path):", interactive=False, placeholder="No file selected.")
113
+
114
+ file_editor_textbox = gr.Textbox(
115
+ label="File Content (Editable)", lines=20, interactive=True,
116
+ placeholder="Select a file from the explorer above to view/edit its content."
117
  )
118
+ edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g., Fix typo in README.md")
119
+ update_edited_file_button = gr.Button("Update File in Space", variant="primary")
120
+ edit_update_status_output = gr.Markdown(label="File Update Result", value="Result will appear here.")
121
+
122
+ # --- Event Handlers for Browse & Edit Tab ---
123
+ def handle_browse_space_files(token_from_ui, space_name, owner_name, force_refresh):
124
+ if not space_name:
125
+ return {
126
+ browse_status_output: gr.Markdown("Error: Space Name cannot be empty."),
127
+ file_explorer_component: gr.FileExplorer(value=None),
128
+ current_clone_root_path_state: None,
129
+ file_editor_textbox: gr.Textbox(value=""), # Clear editor
130
+ current_file_display_ro: gr.Textbox(value="No file selected."),
131
+ current_editing_file_relative_path_state: None
132
+ }
133
+
134
+ new_clone_root_path, error_msg = get_space_local_clone_path(token_from_ui, space_name, owner_name, force_refresh)
135
+
136
+ if error_msg:
137
+ return {
138
+ browse_status_output: gr.Markdown(f"Error: {error_msg}"),
139
+ file_explorer_component: gr.FileExplorer(value=None),
140
+ current_clone_root_path_state: None,
141
+ file_editor_textbox: gr.Textbox(value=""),
142
+ current_file_display_ro: gr.Textbox(value="No file selected."),
143
+ current_editing_file_relative_path_state: None
144
+ }
145
+
146
+ return {
147
+ browse_status_output: gr.Markdown(f"Space '{owner_name}/{space_name}' files loaded. Local clone: `{new_clone_root_path}`"),
148
+ file_explorer_component: gr.FileExplorer(value=new_clone_root_path),
149
+ current_clone_root_path_state: new_clone_root_path,
150
+ file_editor_textbox: gr.Textbox(value=""),
151
+ current_file_display_ro: gr.Textbox(value="No file selected."),
152
+ current_editing_file_relative_path_state: None
153
+ }
154
+
155
+ browse_files_button.click(
156
+ fn=handle_browse_space_files,
157
+ inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input, force_refresh_clone_checkbox],
158
+ outputs=[browse_status_output, file_explorer_component, current_clone_root_path_state, file_editor_textbox, current_file_display_ro, current_editing_file_relative_path_state]
159
+ )
160
+
161
+ def handle_file_selected_in_explorer(selected_file_abs_path_evt: gr.SelectData, clone_root_path_from_state):
162
+ if not selected_file_abs_path_evt or not selected_file_abs_path_evt.value:
163
+ return {
164
+ file_editor_textbox: gr.Textbox(value=""),
165
+ current_file_display_ro: gr.Textbox(value="No file selected."),
166
+ current_editing_file_relative_path_state: None,
167
+ browse_status_output: gr.Markdown("File selection cleared or invalid.")
168
+ }
169
+
170
+ selected_file_abs_path = selected_file_abs_path_evt.value[0] # FileExplorer with file_count="single" returns a list of one item
171
+
172
+ if not clone_root_path_from_state:
173
+ return {
174
+ file_editor_textbox: gr.Textbox(value=""),
175
+ current_file_display_ro: gr.Textbox(value="Error: Clone root path not set."),
176
+ current_editing_file_relative_path_state: None,
177
+ browse_status_output: gr.Markdown("Error: Clone root path state is not set. Please load space files first.")
178
+ }
179
+
180
+ if not os.path.isfile(selected_file_abs_path):
181
+ return {
182
+ file_editor_textbox: gr.Textbox(value=""),
183
+ current_file_display_ro: gr.Textbox(value="Selected item is not a file."),
184
+ current_editing_file_relative_path_state: None,
185
+ browse_status_output: gr.Markdown(f"'{os.path.basename(selected_file_abs_path)}' is a directory. Please select a file.")
186
+ }
187
+
188
+ content, error_msg = read_file_from_local_path(selected_file_abs_path)
189
+
190
+ if error_msg:
191
+ return {
192
+ file_editor_textbox: gr.Textbox(value=f"Error reading file: {error_msg}"),
193
+ current_file_display_ro: gr.Textbox(value="Error reading file."),
194
+ current_editing_file_relative_path_state: None,
195
+ browse_status_output: gr.Markdown(f"Error loading content for {os.path.basename(selected_file_abs_path)}.")
196
+ }
197
+
198
+ try:
199
+ relative_path = os.path.relpath(selected_file_abs_path, start=clone_root_path_from_state)
200
+ relative_path = relative_path.replace(os.sep, '/') # Normalize to forward slashes for HF Hub
201
+ except ValueError as e:
202
+ return {
203
+ file_editor_textbox: gr.Textbox(value=f"Error: Could not determine file's relative path. {e}"),
204
+ current_file_display_ro: gr.Textbox(value="Error: Path calculation failed."),
205
+ current_editing_file_relative_path_state: None,
206
+ browse_status_output: gr.Markdown("Error: File path calculation issue.")
207
+ }
208
+
209
+ return {
210
+ file_editor_textbox: gr.Textbox(value=content),
211
+ current_file_display_ro: gr.Textbox(value=relative_path),
212
+ current_editing_file_relative_path_state: relative_path,
213
+ browse_status_output: gr.Markdown(f"Loaded content for: {relative_path}")
214
+ }
215
+
216
+ file_explorer_component.select( # .select() is correct for FileExplorer
217
+ fn=handle_file_selected_in_explorer,
218
+ inputs=[current_clone_root_path_state],
219
+ outputs=[file_editor_textbox, current_file_display_ro, current_editing_file_relative_path_state, browse_status_output]
220
  )
221
+
222
+ update_edited_file_button.click(
223
+ fn=update_space_file,
224
+ inputs=[
225
+ api_token_ui_input,
226
+ browse_space_name_input,
227
+ browse_owner_input,
228
+ current_editing_file_relative_path_state,
229
+ file_editor_textbox,
230
+ edit_commit_message_input
231
+ ],
232
+ outputs=[edit_update_status_output]
233
  )
 
 
234
 
235
+ # --- Event handlers for Create Space Tab (unchanged) ---
 
 
236
  create_btn.click(
237
  fn=create_space,
238
  inputs=[api_token_ui_input, space_name_create_input, owner_create_input, sdk_create_input, markdown_input_create],
239
  outputs=create_output_md,
240
  )
241
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  return demo
243
 
244
  if __name__ == "__main__":