broadfield-dev commited on
Commit
a2ea4b0
·
verified ·
1 Parent(s): 18c5dec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -97
app.py CHANGED
@@ -7,36 +7,32 @@ import uuid
7
  # --- State Management and API Client ---
8
 
9
  def get_hf_api(token):
10
- if not token:
11
- # Allow read-only operations without a token
12
- return HfApi()
13
- return HfApi(token=token)
14
 
15
  # --- UI Functions ---
16
 
17
- def handle_token_change(token):
18
- """Called when the token is entered. Fetches user info and enables/disables UI elements."""
 
 
19
  if not token:
20
- # No token, so disable write actions and clear username
21
  update_dict = {
22
- # In 'Actions' panel
23
  manage_files_btn: gr.update(interactive=False),
24
  delete_repo_btn: gr.update(interactive=False),
25
- # In 'Editor' panel
26
  commit_btn: gr.update(interactive=False),
27
- # In 'List Repos' panel
28
- author_input: gr.update(value=""),
29
- # User info output
30
  whoami_output: gr.update(value=None, visible=False)
31
  }
32
- return (None, "") + (gr.update(),) * (len(update_dict))
 
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
40
  update_dict = {
41
  manage_files_btn: gr.update(interactive=True),
42
  delete_repo_btn: gr.update(interactive=True),
@@ -44,7 +40,6 @@ def handle_token_change(token):
44
  author_input: gr.update(value=username),
45
  whoami_output: gr.update(value=user_info, visible=True)
46
  }
47
- # The first two return values update gr.State objects
48
  return (token, username, *update_dict.values())
49
 
50
  except HfHubHTTPError as e:
@@ -56,23 +51,25 @@ def handle_token_change(token):
56
  commit_btn: gr.update(interactive=False),
57
  whoami_output: gr.update(value=None, visible=False)
58
  }
59
- # Clear username but keep the invalid token for read-only API calls
60
- return (token, "", *update_dict.values())
61
 
62
 
63
  def list_repos(token, author, repo_type):
64
  """Lists repositories for a given author and type."""
65
  if not author:
66
  gr.Info("Please enter an author (username or organization) to list repositories.")
67
- return gr.update(choices=[], value=None), gr.update(visible=False)
68
  try:
69
  api = get_hf_api(token)
70
- repos = api.list_repos(author=author, repo_type=repo_type)
 
 
71
  repo_ids = [repo.id for repo in repos]
72
- return gr.update(choices=repo_ids, value=None), gr.update(visible=False)
73
  except HfHubHTTPError as e:
74
  gr.Error(f"Could not list repositories: {e}")
75
- return gr.update(choices=[], value=None), gr.update(visible=False)
76
 
77
  def handle_repo_selection(repo_id):
78
  """Called when a repo is selected. Makes action buttons visible."""
@@ -84,19 +81,18 @@ 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
88
  if not repo_id:
89
  gr.Warning("No repository selected to delete.")
90
- return
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}'. Please re-list repositories.")
95
- # Clear selection and hide action/editor panels
96
  return None, gr.update(visible=False), gr.update(visible=False)
97
  except HfHubHTTPError as e:
98
  gr.Error(f"Failed to delete repository: {e}")
99
- return repo_id, gr.update(visible=True), gr.update(visible=False) # Keep state on failure
100
 
101
  # --- File Editor Functions ---
102
 
@@ -104,20 +100,17 @@ def show_file_manager(token, repo_id, repo_type):
104
  """Lists files in the selected repo and shows the editor panel."""
105
  if not repo_id:
106
  gr.Warning("No repository selected.")
107
- return gr.update(visible=False)
108
  try:
109
  api = get_hf_api(token)
110
  repo_files = api.list_repo_files(repo_id=repo_id, repo_type=repo_type)
111
-
112
- # Don't show .gitattributes or other hidden files by default
113
  filtered_files = [f for f in repo_files if not f.startswith('.')]
114
 
115
- # Update UI components for the editor
116
  return (
117
- gr.update(visible=True), # Show editor panel
118
- gr.update(choices=filtered_files, value=None), # Update file dropdown
119
- gr.update(value=f"## Select a file from the dropdown to view or edit its content.", language=None), # Clear code view
120
- "" # Clear commit message
121
  )
122
  except RepositoryNotFoundError:
123
  gr.Error(f"Repository '{repo_id}' not found. It might be private and require a token.")
@@ -133,22 +126,19 @@ def load_file_content(token, repo_id, repo_type, filepath):
133
  return gr.update(value="## Select a file to view its content.", language='markdown')
134
  try:
135
  api = get_hf_api(token)
136
- # Download the file to a temporary local path
137
  local_path = api.hf_hub_download(
138
- repo_id=repo_id,
139
- repo_type=repo_type,
140
- filename=filepath,
141
- token=token,
142
  )
143
  with open(local_path, 'r', encoding='utf-8') as f:
144
  content = f.read()
145
 
146
- # Determine language for syntax highlighting
147
- language = os.path.splitext(filepath)[1].lstrip('.')
148
- if language in ['py', 'js', 'html', 'css', 'json', 'md']:
149
- return gr.update(value=content, language=language)
150
- else:
151
- return gr.update(value=content, language='plaintext')
 
152
 
153
  except Exception as e:
154
  gr.Error(f"Could not load file '{filepath}': {e}")
@@ -167,10 +157,8 @@ def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
167
  return
168
 
169
  try:
170
- # Write content to a temporary file to upload it
171
  temp_dir = "hf_temp_files"
172
  os.makedirs(temp_dir, exist_ok=True)
173
- # Use a unique filename to avoid conflicts
174
  temp_file_path = os.path.join(temp_dir, f"{uuid.uuid4()}_{os.path.basename(filepath)}")
175
 
176
  with open(temp_file_path, "w", encoding="utf-8") as f:
@@ -184,22 +172,19 @@ def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
184
  repo_type=repo_type,
185
  commit_message=commit_message,
186
  )
187
-
188
- # Clean up the temporary file
189
  os.remove(temp_file_path)
190
-
191
  gr.Info(f"Successfully committed '{filepath}' to '{repo_id}'!")
192
  except Exception as e:
193
  gr.Error(f"Failed to commit file: {e}")
194
 
195
 
196
  # --- Gradio UI Layout ---
197
- with gr.Blocks(theme=gr.themes.Soft(), title="Hugging Face Hub Toolkit") as demo:
198
  # State management
199
  hf_token_state = gr.State(None)
200
- username_state = gr.State("")
201
  selected_repo_id = gr.State(None)
202
- selected_repo_type = gr.State("space") # Default to spaces
203
 
204
  gr.Markdown("# Hugging Face Hub Dashboard")
205
  gr.Markdown("An intuitive interface to manage your Hugging Face repositories. **Enter a write-token for full access.**")
@@ -207,44 +192,47 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Hugging Face Hub Toolkit") as demo
207
  with gr.Row():
208
  hf_token = gr.Textbox(
209
  label="Hugging Face API Token (write permission recommended)",
210
- type="password",
211
- placeholder="hf_...",
212
- scale=3,
213
  )
214
  whoami_output = gr.JSON(label="Authenticated User", visible=False, scale=1)
215
 
216
- with gr.Row():
217
  # PANEL 1: List and Select Repos
218
  with gr.Column(scale=1):
219
  gr.Markdown("### 1. Select a Repository")
220
- author_input = gr.Textbox(label="Author (Username or Org)")
221
 
222
- with gr.Tabs() as repo_type_tabs:
223
- # This helper function creates the list button and radio selector for a repo type
224
- def create_repo_lister(repo_type, label):
225
- with gr.Tab(label, id=repo_type):
226
- gr.Button(f"List {label}").click(
227
- fn=list_repos,
228
- inputs=[hf_token_state, author_input, gr.State(repo_type)],
229
- outputs=[repo_selector, editor_panel]
230
- )
231
-
232
- create_repo_lister("space", "Spaces")
233
- create_repo_lister("model", "Models")
234
- create_repo_lister("dataset", "Datasets")
 
 
 
235
 
236
- repo_selector = gr.Radio(label="Select a Repository", interactive=True)
 
 
 
 
237
 
238
  # PANEL 2 & 3: Actions and Editor
239
  with gr.Column(scale=3):
240
  with gr.Row():
241
- # PANEL 2: Action Buttons
242
  with gr.Column(scale=1, visible=False) as action_panel:
243
  gr.Markdown("### 2. Choose an Action")
244
  manage_files_btn = gr.Button("Manage Files", interactive=False)
245
  delete_repo_btn = gr.Button("Delete this Repo", variant="stop", interactive=False)
246
 
247
- # PANEL 3: File Editor
248
  with gr.Column(scale=3, visible=False) as editor_panel:
249
  gr.Markdown("### 3. Edit Files")
250
  file_selector = gr.Dropdown(label="Select File", interactive=True)
@@ -252,41 +240,34 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Hugging Face Hub Toolkit") as demo
252
  commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md", interactive=True)
253
  commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
254
 
255
- # --- Event Wiring ---
 
 
 
 
 
 
 
 
256
 
257
- # When token changes, update auth state and UI
258
  hf_token.change(
259
  fn=handle_token_change,
260
- inputs=hf_token,
261
- outputs=[
262
- hf_token_state,
263
- username_state,
264
- manage_files_btn,
265
- delete_repo_btn,
266
- commit_btn,
267
- author_input,
268
- whoami_output
269
- ]
270
  )
271
-
272
- # When repo type tab is changed, store the new type and clear selection
273
- def on_tab_change(repo_type):
274
- return repo_type, None, gr.update(choices=[], value=None), gr.update(visible=False), gr.update(visible=False)
275
-
276
  repo_type_tabs.select(
277
- fn=on_tab_change,
278
  inputs=repo_type_tabs,
279
  outputs=[selected_repo_type, selected_repo_id, repo_selector, action_panel, editor_panel]
280
  )
281
 
282
- # When a repo is selected, update state and show the action panel
283
  repo_selector.select(
284
  fn=lambda repo_id: (repo_id, *handle_repo_selection(repo_id)),
285
  inputs=repo_selector,
286
  outputs=[selected_repo_id, action_panel, editor_panel]
287
  )
288
 
289
- # Action button clicks
290
  manage_files_btn.click(
291
  fn=show_file_manager,
292
  inputs=[hf_token_state, selected_repo_id, selected_repo_type],
@@ -297,11 +278,14 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Hugging Face Hub Toolkit") as demo
297
  fn=delete_repo,
298
  inputs=[hf_token_state, selected_repo_id, selected_repo_type],
299
  outputs=[selected_repo_id, action_panel, editor_panel],
300
- # Add a confirmation popup before deleting
301
  js="() => confirm('Are you sure you want to permanently delete this repository? This action cannot be undone.')"
 
 
 
 
 
302
  )
303
 
304
- # Editor interactions
305
  file_selector.change(
306
  fn=load_file_content,
307
  inputs=[hf_token_state, selected_repo_id, selected_repo_type, file_selector],
@@ -315,4 +299,4 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Hugging Face Hub Toolkit") as demo
315
  )
316
 
317
  if __name__ == "__main__":
318
- demo.launch(debug=True)
 
7
  # --- State Management and API Client ---
8
 
9
  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, current_author):
16
+ """
17
+ Called when the token is entered. Fetches user info and updates UI interactivity.
18
+ """
19
  if not token:
20
+ # No token, disable write actions and clear user-specific info
21
  update_dict = {
 
22
  manage_files_btn: gr.update(interactive=False),
23
  delete_repo_btn: gr.update(interactive=False),
 
24
  commit_btn: gr.update(interactive=False),
 
 
 
25
  whoami_output: gr.update(value=None, visible=False)
26
  }
27
+ # Do not clear the author field if the user typed it manually
28
+ return (None, current_author, *update_dict.values())
29
 
30
  try:
31
  api = get_hf_api(token)
32
  user_info = api.whoami()
33
  username = user_info.get('name')
34
 
35
+ # Token is valid, enable write actions and set author
36
  update_dict = {
37
  manage_files_btn: gr.update(interactive=True),
38
  delete_repo_btn: gr.update(interactive=True),
 
40
  author_input: gr.update(value=username),
41
  whoami_output: gr.update(value=user_info, visible=True)
42
  }
 
43
  return (token, username, *update_dict.values())
44
 
45
  except HfHubHTTPError as e:
 
51
  commit_btn: gr.update(interactive=False),
52
  whoami_output: gr.update(value=None, visible=False)
53
  }
54
+ # Clear username but keep author field as is
55
+ return (token, current_author, *update_dict.values())
56
 
57
 
58
  def list_repos(token, author, repo_type):
59
  """Lists repositories for a given author and type."""
60
  if not author:
61
  gr.Info("Please enter an author (username or organization) to list repositories.")
62
+ return gr.update(choices=[], value=None), gr.update(visible=False), gr.update(visible=False)
63
  try:
64
  api = get_hf_api(token)
65
+ # Use the dedicated list functions for clarity
66
+ list_fn = getattr(api, f"list_{repo_type}s")
67
+ repos = list_fn(author=author)
68
  repo_ids = [repo.id for repo in repos]
69
+ return gr.update(choices=repo_ids, value=None), gr.update(visible=False), gr.update(visible=False)
70
  except HfHubHTTPError as e:
71
  gr.Error(f"Could not list repositories: {e}")
72
+ return gr.update(choices=[], value=None), gr.update(visible=False), gr.update(visible=False)
73
 
74
  def handle_repo_selection(repo_id):
75
  """Called when a repo is selected. Makes action buttons visible."""
 
81
  """Deletes the selected repository."""
82
  if not token:
83
  gr.Error("A write-enabled Hugging Face token is required to delete a repository.")
84
+ return repo_id, gr.update(visible=True), gr.update(visible=False)
85
  if not repo_id:
86
  gr.Warning("No repository selected to delete.")
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}'. Listing updated repositories.")
 
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 Editor Functions ---
98
 
 
100
  """Lists files in the selected repo and shows the editor panel."""
101
  if not repo_id:
102
  gr.Warning("No repository selected.")
103
+ return gr.update(visible=False), gr.update(), gr.update(), gr.update()
104
  try:
105
  api = get_hf_api(token)
106
  repo_files = api.list_repo_files(repo_id=repo_id, repo_type=repo_type)
 
 
107
  filtered_files = [f for f in repo_files if not f.startswith('.')]
108
 
 
109
  return (
110
+ gr.update(visible=True),
111
+ gr.update(choices=filtered_files, value=None),
112
+ gr.update(value="## Select a file to view or edit.", language='markdown'),
113
+ ""
114
  )
115
  except RepositoryNotFoundError:
116
  gr.Error(f"Repository '{repo_id}' not found. It might be private and require a token.")
 
126
  return gr.update(value="## Select a file to view its content.", language='markdown')
127
  try:
128
  api = get_hf_api(token)
 
129
  local_path = api.hf_hub_download(
130
+ repo_id=repo_id, repo_type=repo_type, filename=filepath, token=token
 
 
 
131
  )
132
  with open(local_path, 'r', encoding='utf-8') as f:
133
  content = f.read()
134
 
135
+ language = os.path.splitext(filepath)[1].lstrip('.').lower()
136
+ supported_langs = ['python', 'typescript', 'css', 'json', 'markdown', 'html', 'javascript']
137
+ if language == 'py': language = 'python'
138
+ if language == 'js': language = 'javascript'
139
+ if language == 'md': language = 'markdown'
140
+
141
+ return gr.update(value=content, language=language if language in supported_langs else 'plaintext')
142
 
143
  except Exception as e:
144
  gr.Error(f"Could not load file '{filepath}': {e}")
 
157
  return
158
 
159
  try:
 
160
  temp_dir = "hf_temp_files"
161
  os.makedirs(temp_dir, exist_ok=True)
 
162
  temp_file_path = os.path.join(temp_dir, f"{uuid.uuid4()}_{os.path.basename(filepath)}")
163
 
164
  with open(temp_file_path, "w", encoding="utf-8") as f:
 
172
  repo_type=repo_type,
173
  commit_message=commit_message,
174
  )
 
 
175
  os.remove(temp_file_path)
 
176
  gr.Info(f"Successfully committed '{filepath}' to '{repo_id}'!")
177
  except Exception as e:
178
  gr.Error(f"Failed to commit file: {e}")
179
 
180
 
181
  # --- Gradio UI Layout ---
182
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub Toolkit") as demo:
183
  # State management
184
  hf_token_state = gr.State(None)
185
+ author_state = gr.State("")
186
  selected_repo_id = gr.State(None)
187
+ selected_repo_type = gr.State("space") # Default
188
 
189
  gr.Markdown("# Hugging Face Hub Dashboard")
190
  gr.Markdown("An intuitive interface to manage your Hugging Face repositories. **Enter a write-token for full access.**")
 
192
  with gr.Row():
193
  hf_token = gr.Textbox(
194
  label="Hugging Face API Token (write permission recommended)",
195
+ type="password", placeholder="hf_...", scale=3,
 
 
196
  )
197
  whoami_output = gr.JSON(label="Authenticated User", visible=False, scale=1)
198
 
199
+ with gr.Row(equal_height=False):
200
  # PANEL 1: List and Select Repos
201
  with gr.Column(scale=1):
202
  gr.Markdown("### 1. Select a Repository")
203
+ author_input = gr.Textbox(label="Author (Username or Org)", interactive=True)
204
 
205
+ repo_selector = gr.Radio(label="Select a Repository", interactive=True, value=None)
206
+
207
+ # This is the corrected function definition. It now takes UI elements as arguments.
208
+ def create_repo_lister(repo_type, label, repo_selector_el, action_panel_el, editor_panel_el):
209
+ with gr.Tab(label, id=repo_type):
210
+ btn = gr.Button(f"List {label}")
211
+ btn.click(
212
+ fn=list_repos,
213
+ inputs=[hf_token_state, author_input, gr.State(repo_type)],
214
+ outputs=[repo_selector_el, action_panel_el, editor_panel_el]
215
+ ).then(
216
+ # After listing, update the author state
217
+ fn=lambda author: author,
218
+ inputs=author_input,
219
+ outputs=author_state
220
+ )
221
 
222
+ with gr.Tabs() as repo_type_tabs:
223
+ # The action and editor panels are defined later but referenced here.
224
+ # We will define placeholder variables for them now.
225
+ action_panel_ref = gr.Column()
226
+ editor_panel_ref = gr.Column()
227
 
228
  # PANEL 2 & 3: Actions and Editor
229
  with gr.Column(scale=3):
230
  with gr.Row():
 
231
  with gr.Column(scale=1, visible=False) as action_panel:
232
  gr.Markdown("### 2. Choose an Action")
233
  manage_files_btn = gr.Button("Manage Files", interactive=False)
234
  delete_repo_btn = gr.Button("Delete this Repo", variant="stop", interactive=False)
235
 
 
236
  with gr.Column(scale=3, visible=False) as editor_panel:
237
  gr.Markdown("### 3. Edit Files")
238
  file_selector = gr.Dropdown(label="Select File", interactive=True)
 
240
  commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md", interactive=True)
241
  commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
242
 
243
+ # --- Post-Layout UI Wiring ---
244
+ # Now that action_panel and editor_panel are fully defined, we can wire them up
245
+ # in the create_repo_lister function calls within the Tabs context.
246
+ with repo_type_tabs:
247
+ create_repo_lister("space", "Spaces", repo_selector, action_panel, editor_panel)
248
+ create_repo_lister("model", "Models", repo_selector, action_panel, editor_panel)
249
+ create_repo_lister("dataset", "Datasets", repo_selector, action_panel, editor_panel)
250
+
251
+ # --- Event Handlers ---
252
 
 
253
  hf_token.change(
254
  fn=handle_token_change,
255
+ inputs=[hf_token, author_state],
256
+ outputs=[hf_token_state, author_state, manage_files_btn, delete_repo_btn, commit_btn, author_input, whoami_output]
 
 
 
 
 
 
 
 
257
  )
258
+
 
 
 
 
259
  repo_type_tabs.select(
260
+ fn=lambda rt: (rt, None, gr.update(choices=[], value=None), gr.update(visible=False), gr.update(visible=False)),
261
  inputs=repo_type_tabs,
262
  outputs=[selected_repo_type, selected_repo_id, repo_selector, action_panel, editor_panel]
263
  )
264
 
 
265
  repo_selector.select(
266
  fn=lambda repo_id: (repo_id, *handle_repo_selection(repo_id)),
267
  inputs=repo_selector,
268
  outputs=[selected_repo_id, action_panel, editor_panel]
269
  )
270
 
 
271
  manage_files_btn.click(
272
  fn=show_file_manager,
273
  inputs=[hf_token_state, selected_repo_id, selected_repo_type],
 
278
  fn=delete_repo,
279
  inputs=[hf_token_state, selected_repo_id, selected_repo_type],
280
  outputs=[selected_repo_id, action_panel, editor_panel],
 
281
  js="() => confirm('Are you sure you want to permanently delete this repository? This action cannot be undone.')"
282
+ ).then(
283
+ # After attempting deletion, refresh the list
284
+ fn=list_repos,
285
+ inputs=lambda: (hf_token_state.value, author_state.value, selected_repo_type.value),
286
+ outputs=[repo_selector, action_panel, editor_panel]
287
  )
288
 
 
289
  file_selector.change(
290
  fn=load_file_content,
291
  inputs=[hf_token_state, selected_repo_id, selected_repo_type, file_selector],
 
299
  )
300
 
301
  if __name__ == "__main__":
302
+ demo.launch()