broadfield-dev commited on
Commit
523ac73
·
verified ·
1 Parent(s): a7c17e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -35
app.py CHANGED
@@ -1,8 +1,10 @@
1
  import gradio as gr
2
- from huggingface_hub import HfApi
3
  from huggingface_hub.utils import HfHubHTTPError, RepositoryNotFoundError
4
  import os
5
  import uuid
 
 
6
 
7
  # --- State Management and API Client ---
8
 
@@ -10,7 +12,7 @@ def get_hf_api(token):
10
  """Initializes the HfApi client. Allows read-only operations if no token is provided."""
11
  return HfApi(token=token if token else None)
12
 
13
- # --- UI Functions ---
14
 
15
  def handle_token_change(token):
16
  """
@@ -92,7 +94,7 @@ def delete_repo(token, repo_id, repo_type):
92
  gr.Error(f"Failed to delete repository: {e}")
93
  return repo_id, gr.update(visible=True), gr.update(visible=False)
94
 
95
- # --- File Editor Functions ---
96
 
97
  def show_file_manager(token, repo_id, repo_type):
98
  if not repo_id:
@@ -140,6 +142,86 @@ def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
140
  except Exception as e:
141
  gr.Error(f"Failed to commit file: {e}")
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  # --- Gradio UI Layout ---
144
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub Toolkit") as demo:
145
  # State management
@@ -148,43 +230,74 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub
148
  selected_repo_id = gr.State(None)
149
  selected_repo_type = gr.State("space") # Default
150
 
151
- gr.Markdown("# Hugging Face Hub Dashboard")
152
  gr.Markdown("An intuitive interface to manage your Hugging Face repositories. **Enter a write-token for full access.**")
153
 
154
  with gr.Sidebar():
155
  hf_token = gr.Textbox(label="Hugging Face API Token", type="password", placeholder="hf_...", scale=3)
156
  whoami_output = gr.JSON(label="Authenticated User", visible=False, scale=1)
157
 
158
- with gr.Row(equal_height=False):
159
- with gr.Column(scale=1):
160
- gr.Markdown("### 1. Select a Repository")
161
- author_input = gr.Textbox(label="Author (Username or Org)", interactive=True)
162
- repo_selector = gr.Radio(label="Select a Repository", interactive=True, value=None)
163
- with gr.Tabs() as repo_type_tabs:
164
- for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
165
- with gr.Tab(label, id=repo_type):
166
- btn = gr.Button(f"List {label}")
167
- btn.click(
168
- fn=list_repos,
169
- inputs=[hf_token_state, author_input, gr.State(repo_type)],
170
- outputs=[repo_selector, gr.Column(), gr.Column()] # Dummy outputs for panels to hide them
171
- ).then(fn=lambda author: author, inputs=author_input, outputs=author_state)
172
-
173
- with gr.Column(scale=3):
174
- with gr.Column(visible=False) as action_panel:
175
- gr.Markdown("### 2. Choose an Action")
176
- with gr.Row():
177
- manage_files_btn = gr.Button("Manage Files", interactive=False, scale=1)
178
- delete_repo_btn = gr.Button("Delete This Repo", variant="stop", interactive=False, scale=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
- with gr.Column(visible=False) as editor_panel:
181
- gr.Markdown("### 3. Edit Files")
182
- file_selector = gr.Dropdown(label="Select File", interactive=True)
183
- code_editor = gr.Code(label="File Content", language="markdown", interactive=True)
184
- commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md", interactive=True)
185
- commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
186
-
187
- # --- Event Handlers ---
 
 
 
 
 
 
188
  hf_token.change(
189
  fn=handle_token_change, inputs=hf_token,
190
  outputs=[hf_token_state, author_state, manage_files_btn, delete_repo_btn, commit_btn, author_input, whoami_output]
@@ -207,8 +320,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub
207
  outputs=[selected_repo_id, action_panel, editor_panel],
208
  js="() => confirm('Are you sure you want to permanently delete this repository? This action cannot be undone.')"
209
  ).then(
210
- # On successful deletion, refresh the repo list.
211
- # THIS IS THE CORRECTED PART: Pass Gradio components to inputs, not a lambda.
212
  fn=list_repos,
213
  inputs=[hf_token_state, author_state, selected_repo_type],
214
  outputs=[repo_selector, action_panel, editor_panel]
 
1
  import gradio as gr
2
+ from huggingface_hub import HfApi, snapshot_download
3
  from huggingface_hub.utils import HfHubHTTPError, RepositoryNotFoundError
4
  import os
5
  import uuid
6
+ import shutil
7
+ import tempfile
8
 
9
  # --- State Management and API Client ---
10
 
 
12
  """Initializes the HfApi client. Allows read-only operations if no token is provided."""
13
  return HfApi(token=token if token else None)
14
 
15
+ # --- UI Functions (Original Tab) ---
16
 
17
  def handle_token_change(token):
18
  """
 
94
  gr.Error(f"Failed to delete repository: {e}")
95
  return repo_id, gr.update(visible=True), gr.update(visible=False)
96
 
97
+ # --- File Editor Functions (Original Tab) ---
98
 
99
  def show_file_manager(token, repo_id, repo_type):
100
  if not repo_id:
 
142
  except Exception as e:
143
  gr.Error(f"Failed to commit file: {e}")
144
 
145
+ # --- Download Tab Functions ---
146
+
147
+ def list_spaces_for_download(token, author):
148
+ """Lists spaces for a given author to populate the checkbox group."""
149
+ if not author:
150
+ gr.Info("Please enter an author (username or organization) to list spaces.")
151
+ return gr.update(choices=[], value=None)
152
+ try:
153
+ api = get_hf_api(token)
154
+ spaces = api.list_spaces(author=author)
155
+ repo_ids = [space.id for space in spaces]
156
+ if not repo_ids:
157
+ gr.Warning(f"No Spaces found for author '{author}'.")
158
+ return gr.update(choices=repo_ids, value=None)
159
+ except RepositoryNotFoundError:
160
+ gr.Warning(f"Author '{author}' not found or has no public spaces.")
161
+ return gr.update(choices=[], value=None)
162
+ except HfHubHTTPError as e:
163
+ gr.Error(f"Could not list spaces: {e}")
164
+ return gr.update(choices=[], value=None)
165
+
166
+ def download_spaces_as_zip(token, selected_space_ids, progress=gr.Progress()):
167
+ """Downloads selected spaces and zips them up."""
168
+ if not selected_space_ids:
169
+ gr.Warning("No spaces selected for download.")
170
+ return gr.update(visible=False, value=None)
171
+
172
+ # Create a temporary directory for all the downloaded content
173
+ download_root_dir = tempfile.mkdtemp()
174
+
175
+ try:
176
+ total_spaces = len(selected_space_ids)
177
+ progress(0, desc="Starting download...")
178
+
179
+ # 1. Download each space into a dedicated subfolder within the temp directory
180
+ for i, repo_id in enumerate(selected_space_ids):
181
+ progress((i) / total_spaces, desc=f"Downloading {repo_id} ({i+1}/{total_spaces})")
182
+
183
+ # Sanitize repo_id to create a valid folder name for the zip
184
+ folder_name = repo_id.replace("/", "__")
185
+ target_path = os.path.join(download_root_dir, folder_name)
186
+
187
+ try:
188
+ # Use snapshot_download to get the entire repo efficiently
189
+ snapshot_download(
190
+ repo_id=repo_id,
191
+ repo_type="space",
192
+ local_dir=target_path,
193
+ token=token,
194
+ local_dir_use_symlinks=False, # Crucial for zipping
195
+ resume_download=True,
196
+ )
197
+ except Exception as e:
198
+ # Log the error and skip this repo
199
+ gr.Error(f"Failed to download {repo_id}: {e}")
200
+ continue
201
+
202
+ # 2. Create the zip archive from the directory of downloaded spaces
203
+ progress(0.95, desc="All spaces downloaded. Creating ZIP file...")
204
+
205
+ # We create the zip file outside the download dir so we can clean up easily
206
+ zip_base_name = os.path.join(tempfile.gettempdir(), f"hf_spaces_archive_{uuid.uuid4().hex}")
207
+
208
+ # shutil.make_archive returns the full path to the created archive
209
+ zip_path = shutil.make_archive(
210
+ base_name=zip_base_name,
211
+ format='zip',
212
+ root_dir=download_root_dir # This becomes the root of the zip
213
+ )
214
+
215
+ progress(1, desc="Download ready!")
216
+ gr.Info("ZIP file created successfully!")
217
+
218
+ # Return the path to the zip file and make the component visible
219
+ return gr.update(value=zip_path, visible=True)
220
+
221
+ finally:
222
+ # 3. Clean up the large download directory, regardless of success or failure
223
+ shutil.rmtree(download_root_dir, ignore_errors=True)
224
+
225
  # --- Gradio UI Layout ---
226
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub Toolkit") as demo:
227
  # State management
 
230
  selected_repo_id = gr.State(None)
231
  selected_repo_type = gr.State("space") # Default
232
 
233
+ gr.Markdown("# Hugging Face Hub Toolkit")
234
  gr.Markdown("An intuitive interface to manage your Hugging Face repositories. **Enter a write-token for full access.**")
235
 
236
  with gr.Sidebar():
237
  hf_token = gr.Textbox(label="Hugging Face API Token", type="password", placeholder="hf_...", scale=3)
238
  whoami_output = gr.JSON(label="Authenticated User", visible=False, scale=1)
239
 
240
+ with gr.Tabs():
241
+ with gr.TabItem("Manage Repositories"):
242
+ with gr.Row(equal_height=False):
243
+ with gr.Column(scale=1):
244
+ gr.Markdown("### 1. Select a Repository")
245
+ author_input = gr.Textbox(label="Author (Username or Org)", interactive=True)
246
+ repo_selector = gr.Radio(label="Select a Repository", interactive=True, value=None)
247
+ with gr.Tabs() as repo_type_tabs:
248
+ for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
249
+ with gr.Tab(label, id=repo_type):
250
+ btn = gr.Button(f"List {label}")
251
+ btn.click(
252
+ fn=list_repos,
253
+ inputs=[hf_token_state, author_input, gr.State(repo_type)],
254
+ outputs=[repo_selector, gr.Column(), gr.Column()] # Dummy outputs for panels to hide them
255
+ ).then(fn=lambda author: author, inputs=author_input, outputs=author_state)
256
+
257
+ with gr.Column(scale=3):
258
+ with gr.Column(visible=False) as action_panel:
259
+ gr.Markdown("### 2. Choose an Action")
260
+ with gr.Row():
261
+ manage_files_btn = gr.Button("Manage Files", interactive=False, scale=1)
262
+ delete_repo_btn = gr.Button("Delete This Repo", variant="stop", interactive=False, scale=1)
263
+
264
+ with gr.Column(visible=False) as editor_panel:
265
+ gr.Markdown("### 3. Edit Files")
266
+ file_selector = gr.Dropdown(label="Select File", interactive=True)
267
+ code_editor = gr.Code(label="File Content", language="markdown", interactive=True)
268
+ commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md", interactive=True)
269
+ commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
270
+
271
+ with gr.TabItem("Download Spaces (ZIP)"):
272
+ gr.Markdown("## Bulk Download Spaces as a ZIP Archive")
273
+ gr.Markdown("Select one or more Spaces from an author to download them as a single ZIP file. Each Space will be in its own folder inside the archive.")
274
+
275
+ with gr.Row():
276
+ download_author_input = gr.Textbox(
277
+ label="Author (Username or Org)",
278
+ interactive=True,
279
+ placeholder="e.g., huggingface-projects or osanseviero"
280
+ )
281
+ list_spaces_btn = gr.Button("List Spaces", variant="secondary")
282
+
283
+ spaces_checkboxes = gr.CheckboxGroup(label="Available Spaces", info="Select the spaces you want to download.")
284
+ download_btn = gr.Button("Download Selected Spaces as ZIP", variant="primary")
285
+ download_output_file = gr.File(label="Your Downloaded ZIP File", visible=False)
286
 
287
+ # --- Event Handlers for Download Tab ---
288
+ list_spaces_btn.click(
289
+ fn=list_spaces_for_download,
290
+ inputs=[hf_token_state, download_author_input],
291
+ outputs=[spaces_checkboxes]
292
+ )
293
+
294
+ download_btn.click(
295
+ fn=download_spaces_as_zip,
296
+ inputs=[hf_token_state, spaces_checkboxes],
297
+ outputs=[download_output_file]
298
+ )
299
+
300
+ # --- Event Handlers (Original Tab) ---
301
  hf_token.change(
302
  fn=handle_token_change, inputs=hf_token,
303
  outputs=[hf_token_state, author_state, manage_files_btn, delete_repo_btn, commit_btn, author_input, whoami_output]
 
320
  outputs=[selected_repo_id, action_panel, editor_panel],
321
  js="() => confirm('Are you sure you want to permanently delete this repository? This action cannot be undone.')"
322
  ).then(
 
 
323
  fn=list_repos,
324
  inputs=[hf_token_state, author_state, selected_repo_type],
325
  outputs=[repo_selector, action_panel, editor_panel]