broadfield-dev commited on
Commit
2d0a69d
·
verified ·
1 Parent(s): 4e2684d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -186
app.py CHANGED
@@ -1,6 +1,6 @@
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
@@ -12,7 +12,7 @@ def get_hf_api(token):
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 ---
16
 
17
  def handle_token_change(token):
18
  """
@@ -20,88 +20,84 @@ def handle_token_change(token):
20
  and auto-fills the author fields in both tabs.
21
  """
22
  if not token:
23
- # No token, disable write actions and clear user-specific info
24
- update_dict = {
25
- manage_files_btn: gr.update(interactive=False),
26
- delete_repo_btn: gr.update(interactive=False),
27
- commit_btn: gr.update(interactive=False),
28
- author_input: gr.update(value=""),
29
- download_author_input: gr.update(value=""),
30
- whoami_output: gr.update(value=None, visible=False)
31
  }
32
- return (None, *update_dict.values())
33
-
34
  try:
35
  api = get_hf_api(token)
36
  user_info = api.whoami()
37
  username = user_info.get('name')
38
-
39
- # Token is valid, enable write actions and set author everywhere
40
- update_dict = {
41
- manage_files_btn: gr.update(interactive=True),
42
- delete_repo_btn: gr.update(interactive=True),
43
- commit_btn: gr.update(interactive=True),
44
- author_input: gr.update(value=username),
45
- download_author_input: gr.update(value=username),
46
- whoami_output: gr.update(value=user_info, visible=True)
47
  }
48
- return (token, *update_dict.values())
49
-
50
  except HfHubHTTPError as e:
51
  gr.Warning(f"Invalid Token: {e}. You can only perform read-only actions.")
52
- update_dict = {
53
- manage_files_btn: gr.update(interactive=False),
54
- delete_repo_btn: gr.update(interactive=False),
55
- commit_btn: gr.update(interactive=False),
56
- whoami_output: gr.update(value=None, visible=False)
57
  }
58
- return (token, *update_dict.values())
59
-
60
 
61
  def list_repos(token, author, repo_type):
62
- """Lists repositories for a given author and type into a dropdown."""
63
  if not author:
64
- gr.Info("Please enter an author (username or organization) to list repositories.")
65
- return gr.update(choices=[], value=None)
66
  try:
67
  api = get_hf_api(token)
68
  list_fn = getattr(api, f"list_{repo_type}s")
69
  repos = list_fn(author=author)
70
  repo_ids = [repo.id for repo in repos]
71
  gr.Info(f"Found {len(repo_ids)} {repo_type}s for '{author}'.")
72
- return gr.update(choices=repo_ids, value=None)
73
  except HfHubHTTPError as e:
74
  gr.Error(f"Could not list repositories: {e}")
75
- return gr.update(choices=[], value=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  def on_manage_repo_select(repo_id):
78
- """Called when a repo is selected in the Manage tab. Makes action buttons visible."""
79
- if repo_id:
80
- return gr.update(visible=True) # Show actions
81
- return gr.update(visible=False) # Hide actions
82
 
83
  def delete_repo(token, repo_id, repo_type):
84
  """Deletes the selected repository."""
85
  if not token:
86
  gr.Error("A write-enabled Hugging Face token is required to delete a repository.")
87
- return repo_id
88
- if not repo_id:
89
- gr.Warning("No repository selected to delete.")
90
- return repo_id
91
  try:
92
  api = get_hf_api(token)
93
  api.delete_repo(repo_id=repo_id, repo_type=repo_type)
94
  gr.Info(f"Successfully deleted '{repo_id}'.")
95
- # After deletion, hide panels and return None to clear selection
96
- return None
97
  except HfHubHTTPError as e:
98
  gr.Error(f"Failed to delete repository: {e}")
99
- return repo_id # Keep repo selected on failure
100
 
101
  # --- File Management Functions ---
102
 
103
  def show_files_and_load_first(token, repo_id, repo_type):
104
- """Lists files and pre-loads the first file's content for editing."""
105
  if not repo_id:
106
  gr.Warning("No repository selected.")
107
  return gr.update(visible=False), gr.update(), gr.update()
@@ -111,106 +107,76 @@ def show_files_and_load_first(token, repo_id, repo_type):
111
  filtered_files = [f for f in repo_files if not f.startswith('.')]
112
 
113
  if not filtered_files:
114
- return (
115
- gr.update(visible=True),
116
- gr.update(choices=[], value=None),
117
- gr.update(value="## This repository is empty or contains only hidden files.", language='markdown')
118
- )
119
 
120
- # Load the content of the first file automatically
121
  first_file_path = filtered_files[0]
122
  content, language = load_file_content_backend(token, repo_id, repo_type, first_file_path)
123
 
124
- return (
125
- gr.update(visible=True),
126
- gr.update(choices=filtered_files, value=first_file_path),
127
- gr.update(value=content, language=language)
128
- )
129
  except Exception as e:
130
- gr.Error(f"Could not list files: {e}")
131
  return gr.update(visible=False), gr.update(), gr.update()
132
 
133
  def load_file_content_backend(token, repo_id, repo_type, filepath):
134
- """Backend logic to fetch and format file content. Returns content and language."""
135
- if not filepath:
136
- return "## Select a file to view its content.", 'markdown'
137
  try:
138
  api = get_hf_api(token)
139
  local_path = api.hf_hub_download(repo_id=repo_id, repo_type=repo_type, filename=filepath, token=token)
140
- with open(local_path, 'r', encoding='utf-8') as f:
141
- content = f.read()
142
 
143
- language = os.path.splitext(filepath)[1].lstrip('.').lower()
144
- if language in ['py', 'python']: language = 'python'
145
- elif language == 'js': language = 'javascript'
146
- elif language == 'md': language = 'markdown'
147
- else: language = 'plaintext' # Default
148
-
149
  return content, language
150
  except Exception as e:
151
  return f"Error loading file: {e}", 'plaintext'
152
 
153
  def load_file_content_for_editor(token, repo_id, repo_type, filepath):
154
- """Gradio wrapper to update the code editor when a new file is selected."""
155
  content, language = load_file_content_backend(token, repo_id, repo_type, filepath)
156
  return gr.update(value=content, language=language)
157
 
158
-
159
  def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
 
160
  if not token: gr.Error("A write-enabled token is required."); return
161
  if not filepath: gr.Warning("No file selected."); return
162
  if not commit_message: gr.Warning("Commit message cannot be empty."); return
163
  try:
164
  api = get_hf_api(token)
165
- api.upload_file(
166
- path_or_fileobj=bytes(content, 'utf-8'), path_in_repo=filepath,
167
- repo_id=repo_id, repo_type=repo_type, commit_message=commit_message,
168
- )
169
  gr.Info(f"Successfully committed '{filepath}' to '{repo_id}'!")
170
- except Exception as e:
171
- gr.Error(f"Failed to commit file: {e}")
172
 
173
  # --- Download Tab Functions ---
174
 
175
  def download_repos_as_zip(token, selected_repo_ids, repo_type, progress=gr.Progress()):
176
- """Downloads selected repos of a given type and zips them up."""
177
  if not selected_repo_ids:
178
- gr.Warning("No repositories selected for download.")
179
- return gr.update(visible=False, value=None)
180
  if not repo_type:
181
- gr.Warning("Please list a repository type (Spaces, Models, etc.) before downloading.")
182
- return gr.update(visible=False, value=None)
183
 
184
  download_root_dir = tempfile.mkdtemp()
185
  try:
186
- total_repos = len(selected_repo_ids)
187
- progress(0, desc="Starting download...")
188
-
189
  for i, repo_id in enumerate(selected_repo_ids):
190
- progress((i) / total_repos, desc=f"Downloading {repo_id} ({i+1}/{total_repos})")
191
- folder_name = repo_id.replace("/", "__")
192
- target_path = os.path.join(download_root_dir, folder_name)
193
-
194
  try:
195
- snapshot_download(
196
- repo_id=repo_id, repo_type=repo_type, local_dir=target_path,
197
- token=token, local_dir_use_symlinks=False, resume_download=True,
198
- )
199
- except Exception as e:
200
- gr.Error(f"Failed to download {repo_id}: {e}")
201
- continue
202
-
203
- progress(0.95, desc="All items downloaded. Creating ZIP file...")
204
- zip_base_name = os.path.join(tempfile.gettempdir(), f"hf_{repo_type}s_archive_{uuid.uuid4().hex}")
205
- zip_path = shutil.make_archive(base_name=zip_base_name, format='zip', root_dir=download_root_dir)
206
 
 
 
 
207
  progress(1, desc="Download ready!")
208
- gr.Info("ZIP file created successfully!")
209
  return gr.update(value=zip_path, visible=True)
210
  finally:
211
  shutil.rmtree(download_root_dir, ignore_errors=True)
212
 
213
-
214
  # --- Gradio UI Layout ---
215
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub Toolkit") as demo:
216
  # State management
@@ -219,119 +185,74 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub
219
  download_repo_type_state = gr.State(None)
220
 
221
  gr.Markdown("# Hugging Face Hub Toolkit")
222
- gr.Markdown("An intuitive interface to manage and download your Hugging Face repositories.")
223
-
224
  with gr.Sidebar():
225
- hf_token = gr.Textbox(label="Hugging Face API Token", type="password", placeholder="hf_...", scale=3)
226
- whoami_output = gr.JSON(label="Authenticated User", visible=False, scale=1)
227
 
228
  with gr.Tabs():
229
  with gr.TabItem("Manage Repositories"):
230
  with gr.Row():
231
  with gr.Column(scale=1):
232
- gr.Markdown("### 1. Select Author & Repo Type")
233
- author_input = gr.Textbox(label="Author (Username or Org)", interactive=True)
234
  with gr.Row():
235
  for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
236
  btn = gr.Button(f"List {label}")
237
- btn.click(
238
- fn=lambda rt: (rt, None, gr.update(visible=False), gr.update(visible=False)),
239
- inputs=gr.State(repo_type),
240
- outputs=[manage_repo_type_state, gr.Dropdown(), gr.Column(), gr.Column()] # Reset dropdown and hide panels
241
- ).then(
242
- fn=list_repos,
243
- inputs=[hf_token_state, author_input, gr.State(repo_type)],
244
- outputs=gr.Dropdown() # This is manage_repo_dropdown
245
- )
246
-
247
- gr.Markdown("### 2. Select Repository")
248
  manage_repo_dropdown = gr.Dropdown(label="Select a Repository", interactive=True)
249
 
250
  with gr.Column(scale=2):
251
  with gr.Column(visible=False) as action_panel:
252
- gr.Markdown("### 3. Choose an Action")
253
- with gr.Row():
254
- manage_files_btn = gr.Button("Manage Files", interactive=False)
255
- delete_repo_btn = gr.Button("Delete This Repo", variant="stop", interactive=False)
256
-
257
  with gr.Column(visible=False) as editor_panel:
258
- gr.Markdown("### 4. Edit Files")
259
  file_selector = gr.Dropdown(label="Select File", interactive=True)
260
- code_editor = gr.Code(label="File Content", language="markdown", interactive=True)
261
- commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md", interactive=True)
262
  commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
263
 
264
  with gr.TabItem("Bulk Download (ZIP)"):
265
- gr.Markdown("## Bulk Download Repositories as a ZIP Archive")
266
- gr.Markdown("Select one or more repositories from an author to download them as a single ZIP file.")
267
-
268
  with gr.Row():
269
- download_author_input = gr.Textbox(label="Author (Username or Org)", interactive=True)
270
  with gr.Row():
271
- for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
272
  btn = gr.Button(f"List {label}")
273
- btn.click(
274
- fn=lambda rt: rt,
275
- inputs=gr.State(repo_type),
276
- outputs=download_repo_type_state
277
- ).then(
278
- fn=list_repos,
279
- inputs=[hf_token_state, download_author_input, gr.State(repo_type)],
280
- outputs=gr.Dropdown() # This is download_repo_dropdown
281
- )
282
-
283
- download_repo_dropdown = gr.Dropdown(label="Available Repositories", info="Select the items you want to download.", multiselect=True, interactive=True)
284
  download_btn = gr.Button("Download Selected as ZIP", variant="primary")
285
  download_output_file = gr.File(label="Your Downloaded ZIP File", visible=False)
286
 
287
  # --- Event Handlers ---
288
-
289
- # Token Authentication
290
- hf_token.change(
291
- fn=handle_token_change, inputs=hf_token,
292
- outputs=[hf_token_state, manage_files_btn, delete_repo_btn, commit_btn, author_input, download_author_input, whoami_output]
293
- )
294
 
295
- # --- Manage Tab Handlers ---
296
- manage_repo_dropdown.select(
297
- fn=on_manage_repo_select,
298
- inputs=manage_repo_dropdown,
299
- outputs=action_panel
300
- )
301
 
302
- manage_files_btn.click(
303
- fn=show_files_and_load_first,
304
- inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state],
305
- outputs=[editor_panel, file_selector, code_editor]
306
- )
307
 
308
- delete_repo_btn.click(
309
- fn=delete_repo,
310
- inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state],
311
- outputs=[manage_repo_dropdown],
312
- js="() => confirm('Are you sure you want to permanently delete this repository? This action cannot be undone.')"
313
- ).then( # After deletion, hide panels
314
- lambda: (gr.update(visible=False), gr.update(visible=False)),
315
- outputs=[action_panel, editor_panel]
316
- )
317
-
318
- file_selector.change(
319
- fn=load_file_content_for_editor,
320
- inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state, file_selector],
321
- outputs=code_editor
322
- )
323
 
324
- commit_btn.click(
325
- fn=commit_file,
326
- inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state, file_selector, code_editor, commit_message_input]
327
- )
328
 
329
- # --- Download Tab Handlers ---
330
- download_btn.click(
331
- fn=download_repos_as_zip,
332
- inputs=[hf_token_state, download_repo_dropdown, download_repo_type_state],
333
- outputs=[download_output_file]
334
- )
335
 
336
  if __name__ == "__main__":
337
  demo.launch(debug=True)
 
1
  import gradio as gr
2
  from huggingface_hub import HfApi, snapshot_download
3
+ from huggingface_hub.utils import HfHubHTTPError
4
  import os
5
  import uuid
6
  import shutil
 
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
+ # --- Core Logic Functions ---
16
 
17
  def handle_token_change(token):
18
  """
 
20
  and auto-fills the author fields in both tabs.
21
  """
22
  if not token:
23
+ updates = {
24
+ manage_files_btn: gr.update(interactive=False), delete_repo_btn: gr.update(interactive=False),
25
+ commit_btn: gr.update(interactive=False), author_input: gr.update(value=""),
26
+ download_author_input: gr.update(value=""), whoami_output: gr.update(value=None, visible=False)
 
 
 
 
27
  }
28
+ return (None, *updates.values())
 
29
  try:
30
  api = get_hf_api(token)
31
  user_info = api.whoami()
32
  username = user_info.get('name')
33
+ updates = {
34
+ manage_files_btn: gr.update(interactive=True), delete_repo_btn: gr.update(interactive=True),
35
+ commit_btn: gr.update(interactive=True), author_input: gr.update(value=username),
36
+ download_author_input: gr.update(value=username), whoami_output: gr.update(value=user_info, visible=True)
 
 
 
 
 
37
  }
38
+ return (token, *updates.values())
 
39
  except HfHubHTTPError as e:
40
  gr.Warning(f"Invalid Token: {e}. You can only perform read-only actions.")
41
+ updates = {
42
+ manage_files_btn: gr.update(interactive=False), delete_repo_btn: gr.update(interactive=False),
43
+ commit_btn: gr.update(interactive=False), whoami_output: gr.update(value=None, visible=False)
 
 
44
  }
45
+ return (token, *updates.values())
 
46
 
47
  def list_repos(token, author, repo_type):
48
+ """Backend function to fetch repository IDs."""
49
  if not author:
50
+ gr.Info("Please enter an author (username or organization).")
51
+ return []
52
  try:
53
  api = get_hf_api(token)
54
  list_fn = getattr(api, f"list_{repo_type}s")
55
  repos = list_fn(author=author)
56
  repo_ids = [repo.id for repo in repos]
57
  gr.Info(f"Found {len(repo_ids)} {repo_type}s for '{author}'.")
58
+ return repo_ids
59
  except HfHubHTTPError as e:
60
  gr.Error(f"Could not list repositories: {e}")
61
+ return []
62
+
63
+ def list_repos_for_management(token, author, repo_type):
64
+ """Gradio wrapper to update the management dropdown and reset the UI."""
65
+ repo_ids = list_repos(token, author, repo_type)
66
+ return (
67
+ repo_type,
68
+ gr.update(choices=repo_ids, value=None), # Update dropdown choices
69
+ gr.update(visible=False), # Hide action panel
70
+ gr.update(visible=False) # Hide editor panel
71
+ )
72
+
73
+ def list_repos_for_download(token, author, repo_type):
74
+ """Gradio wrapper to update the download dropdown."""
75
+ repo_ids = list_repos(token, author, repo_type)
76
+ return repo_type, gr.update(choices=repo_ids, value=None)
77
+
78
 
79
  def on_manage_repo_select(repo_id):
80
+ """Shows action buttons when a repo is selected in the Manage tab."""
81
+ return gr.update(visible=bool(repo_id))
 
 
82
 
83
  def delete_repo(token, repo_id, repo_type):
84
  """Deletes the selected repository."""
85
  if not token:
86
  gr.Error("A write-enabled Hugging Face token is required to delete a repository.")
87
+ return repo_id, gr.update(visible=True), gr.update(visible=False)
 
 
 
88
  try:
89
  api = get_hf_api(token)
90
  api.delete_repo(repo_id=repo_id, repo_type=repo_type)
91
  gr.Info(f"Successfully deleted '{repo_id}'.")
92
+ return None, gr.update(visible=False), gr.update(visible=False)
 
93
  except HfHubHTTPError as e:
94
  gr.Error(f"Failed to delete repository: {e}")
95
+ return repo_id, gr.update(visible=True), gr.update(visible=False)
96
 
97
  # --- File Management Functions ---
98
 
99
  def show_files_and_load_first(token, repo_id, repo_type):
100
+ """Lists files in a dropdown and pre-loads the first file's content."""
101
  if not repo_id:
102
  gr.Warning("No repository selected.")
103
  return gr.update(visible=False), gr.update(), gr.update()
 
107
  filtered_files = [f for f in repo_files if not f.startswith('.')]
108
 
109
  if not filtered_files:
110
+ return (gr.update(visible=True), gr.update(choices=[], value=None),
111
+ gr.update(value="## This repository is empty.", language='markdown'))
 
 
 
112
 
 
113
  first_file_path = filtered_files[0]
114
  content, language = load_file_content_backend(token, repo_id, repo_type, first_file_path)
115
 
116
+ return (gr.update(visible=True), gr.update(choices=filtered_files, value=first_file_path),
117
+ gr.update(value=content, language=language))
 
 
 
118
  except Exception as e:
119
+ gr.Error(f"Could not manage files: {e}")
120
  return gr.update(visible=False), gr.update(), gr.update()
121
 
122
  def load_file_content_backend(token, repo_id, repo_type, filepath):
123
+ """Backend logic to fetch and format file content."""
124
+ if not filepath: return "## Select a file to view.", 'markdown'
 
125
  try:
126
  api = get_hf_api(token)
127
  local_path = api.hf_hub_download(repo_id=repo_id, repo_type=repo_type, filename=filepath, token=token)
128
+ with open(local_path, 'r', encoding='utf-8') as f: content = f.read()
 
129
 
130
+ ext = os.path.splitext(filepath)[1].lstrip('.').lower()
131
+ lang_map = {'py': 'python', 'js': 'javascript', 'md': 'markdown'}
132
+ language = lang_map.get(ext, 'plaintext')
 
 
 
133
  return content, language
134
  except Exception as e:
135
  return f"Error loading file: {e}", 'plaintext'
136
 
137
  def load_file_content_for_editor(token, repo_id, repo_type, filepath):
138
+ """Gradio wrapper to update the code editor when a file is selected."""
139
  content, language = load_file_content_backend(token, repo_id, repo_type, filepath)
140
  return gr.update(value=content, language=language)
141
 
 
142
  def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
143
+ """Commits a file to the repository."""
144
  if not token: gr.Error("A write-enabled token is required."); return
145
  if not filepath: gr.Warning("No file selected."); return
146
  if not commit_message: gr.Warning("Commit message cannot be empty."); return
147
  try:
148
  api = get_hf_api(token)
149
+ api.upload_file(bytes(content, 'utf-8'), path_in_repo=filepath,
150
+ repo_id=repo_id, repo_type=repo_type, commit_message=commit_message)
 
 
151
  gr.Info(f"Successfully committed '{filepath}' to '{repo_id}'!")
152
+ except Exception as e: gr.Error(f"Failed to commit file: {e}")
 
153
 
154
  # --- Download Tab Functions ---
155
 
156
  def download_repos_as_zip(token, selected_repo_ids, repo_type, progress=gr.Progress()):
157
+ """Downloads selected repos and zips them."""
158
  if not selected_repo_ids:
159
+ gr.Warning("No repositories selected for download."); return
 
160
  if not repo_type:
161
+ gr.Warning("Please list a repository type (Spaces, etc.) before downloading."); return
 
162
 
163
  download_root_dir = tempfile.mkdtemp()
164
  try:
 
 
 
165
  for i, repo_id in enumerate(selected_repo_ids):
166
+ progress((i) / len(selected_repo_ids), desc=f"Downloading {repo_id}")
 
 
 
167
  try:
168
+ snapshot_download(repo_id=repo_id, repo_type=repo_type, local_dir=os.path.join(download_root_dir, repo_id.replace("/", "__")),
169
+ token=token, local_dir_use_symlinks=False, resume_download=True)
170
+ except Exception as e: gr.Error(f"Failed to download {repo_id}: {e}")
 
 
 
 
 
 
 
 
171
 
172
+ progress(0.95, desc="Creating ZIP file...")
173
+ zip_base_name = os.path.join(tempfile.gettempdir(), f"hf_{repo_type}s_{uuid.uuid4().hex}")
174
+ zip_path = shutil.make_archive(zip_base_name, 'zip', download_root_dir)
175
  progress(1, desc="Download ready!")
 
176
  return gr.update(value=zip_path, visible=True)
177
  finally:
178
  shutil.rmtree(download_root_dir, ignore_errors=True)
179
 
 
180
  # --- Gradio UI Layout ---
181
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub Toolkit") as demo:
182
  # State management
 
185
  download_repo_type_state = gr.State(None)
186
 
187
  gr.Markdown("# Hugging Face Hub Toolkit")
 
 
188
  with gr.Sidebar():
189
+ hf_token = gr.Textbox(label="Hugging Face API Token", type="password", placeholder="hf_...")
190
+ whoami_output = gr.JSON(label="Authenticated User", visible=False)
191
 
192
  with gr.Tabs():
193
  with gr.TabItem("Manage Repositories"):
194
  with gr.Row():
195
  with gr.Column(scale=1):
196
+ gr.Markdown("### 1. Select a Repository")
197
+ author_input = gr.Textbox(label="Author (Username or Org)")
198
  with gr.Row():
199
  for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
200
  btn = gr.Button(f"List {label}")
201
+ btn.click(fn=list_repos_for_management,
202
+ inputs=[hf_token_state, author_input, gr.State(repo_type)],
203
+ outputs=[manage_repo_type_state, gr.Dropdown(), action_panel, editor_panel])
 
 
 
 
 
 
 
 
204
  manage_repo_dropdown = gr.Dropdown(label="Select a Repository", interactive=True)
205
 
206
  with gr.Column(scale=2):
207
  with gr.Column(visible=False) as action_panel:
208
+ gr.Markdown("### 2. Choose an Action")
209
+ manage_files_btn = gr.Button("Manage Files", interactive=False)
210
+ delete_repo_btn = gr.Button("Delete This Repo", variant="stop", interactive=False)
 
 
211
  with gr.Column(visible=False) as editor_panel:
212
+ gr.Markdown("### 3. Edit Files")
213
  file_selector = gr.Dropdown(label="Select File", interactive=True)
214
+ code_editor = gr.Code(label="File Content", interactive=True)
215
+ commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md")
216
  commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
217
 
218
  with gr.TabItem("Bulk Download (ZIP)"):
219
+ gr.Markdown("## Download Multiple Repositories as a ZIP")
 
 
220
  with gr.Row():
221
+ download_author_input = gr.Textbox(label="Author (Username or Org)")
222
  with gr.Row():
223
+ for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
224
  btn = gr.Button(f"List {label}")
225
+ btn.click(fn=list_repos_for_download,
226
+ inputs=[hf_token_state, download_author_input, gr.State(repo_type)],
227
+ outputs=[download_repo_type_state, gr.Dropdown()])
228
+ download_repo_dropdown = gr.Dropdown(label="Select Repositories", multiselect=True, interactive=True)
 
 
 
 
 
 
 
229
  download_btn = gr.Button("Download Selected as ZIP", variant="primary")
230
  download_output_file = gr.File(label="Your Downloaded ZIP File", visible=False)
231
 
232
  # --- Event Handlers ---
233
+ hf_token.change(fn=handle_token_change, inputs=hf_token,
234
+ outputs=[hf_token_state, manage_files_btn, delete_repo_btn, commit_btn, author_input, download_author_input, whoami_output])
 
 
 
 
235
 
236
+ manage_repo_dropdown.select(fn=on_manage_repo_select, inputs=manage_repo_dropdown, outputs=action_panel)
 
 
 
 
 
237
 
238
+ manage_files_btn.click(fn=show_files_and_load_first,
239
+ inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state],
240
+ outputs=[editor_panel, file_selector, code_editor])
 
 
241
 
242
+ delete_repo_btn.click(fn=delete_repo, inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state],
243
+ outputs=[manage_repo_dropdown, action_panel, editor_panel],
244
+ js="() => confirm('Are you sure you want to permanently delete this repository?')")
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
+ file_selector.change(fn=load_file_content_for_editor,
247
+ inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state, file_selector],
248
+ outputs=code_editor)
 
249
 
250
+ commit_btn.click(fn=commit_file,
251
+ inputs=[hf_token_state, manage_repo_dropdown, manage_repo_type_state, file_selector, code_editor, commit_message_input])
252
+
253
+ download_btn.click(fn=download_repos_as_zip,
254
+ inputs=[hf_token_state, download_repo_dropdown, download_repo_type_state],
255
+ outputs=[download_output_file])
256
 
257
  if __name__ == "__main__":
258
  demo.launch(debug=True)