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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -125
app.py CHANGED
@@ -12,7 +12,7 @@ def get_hf_api(token):
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
  """
@@ -22,10 +22,10 @@ def handle_token_change(token, current_author):
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)
@@ -43,7 +43,6 @@ def handle_token_change(token, current_author):
43
  return (token, username, *update_dict.values())
44
 
45
  except HfHubHTTPError as e:
46
- # Token is invalid
47
  gr.Warning(f"Invalid Token: {e}. You can only perform read-only actions.")
48
  update_dict = {
49
  manage_files_btn: gr.update(interactive=False),
@@ -51,8 +50,7 @@ def handle_token_change(token, current_author):
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):
@@ -62,7 +60,7 @@ def list_repos(token, author, repo_type):
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]
@@ -88,7 +86,7 @@ def delete_repo(token, repo_id, repo_type):
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}")
@@ -97,7 +95,6 @@ def delete_repo(token, repo_id, repo_type):
97
  # --- File Editor Functions ---
98
 
99
  def show_file_manager(token, repo_id, repo_type):
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()
@@ -105,79 +102,43 @@ def show_file_manager(token, repo_id, repo_type):
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.")
117
- return gr.update(visible=False), gr.update(), gr.update(), gr.update()
118
  except Exception as e:
119
  gr.Error(f"Could not list files: {e}")
120
  return gr.update(visible=False), gr.update(), gr.update(), gr.update()
121
 
122
-
123
  def load_file_content(token, repo_id, repo_type, filepath):
124
- """Downloads and displays the content of a selected file."""
125
  if not filepath:
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}")
145
  return gr.update(value=f"Error loading file: {e}", language='plaintext')
146
 
147
  def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
148
- """Commits the edited file content back to the repository."""
149
- if not token:
150
- gr.Error("A write-enabled token is required to commit changes.")
151
- return
152
- if not filepath:
153
- gr.Warning("No file is selected to commit.")
154
- return
155
- if not commit_message:
156
- gr.Warning("Commit message cannot be empty.")
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:
165
- f.write(content)
166
-
167
  api = get_hf_api(token)
168
  api.upload_file(
169
- path_or_fileobj=temp_file_path,
170
- path_in_repo=filepath,
171
- repo_id=repo_id,
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
@@ -190,113 +151,75 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub
190
  gr.Markdown("An intuitive interface to manage your Hugging Face repositories. **Enter a write-token for full access.**")
191
 
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)
239
- code_editor = gr.Code(label="File Content", language="markdown", 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],
274
  outputs=[editor_panel, file_selector, code_editor, commit_message_input]
275
  )
276
-
277
  delete_repo_btn.click(
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],
292
  outputs=code_editor
293
  )
294
-
295
  commit_btn.click(
296
  fn=commit_file,
297
- inputs=[hf_token_state, selected_repo_id, selected_repo_type, file_selector, code_editor, commit_message_input],
298
- outputs=[]
299
  )
300
 
301
  if __name__ == "__main__":
302
- demo.launch()
 
12
 
13
  # --- UI Functions ---
14
 
15
+ def handle_token_change(token):
16
  """
17
  Called when the token is entered. Fetches user info and updates UI interactivity.
18
  """
 
22
  manage_files_btn: gr.update(interactive=False),
23
  delete_repo_btn: gr.update(interactive=False),
24
  commit_btn: gr.update(interactive=False),
25
+ author_input: gr.update(value=""),
26
  whoami_output: gr.update(value=None, visible=False)
27
  }
28
+ return (None, "", *update_dict.values())
 
29
 
30
  try:
31
  api = get_hf_api(token)
 
43
  return (token, username, *update_dict.values())
44
 
45
  except HfHubHTTPError as e:
 
46
  gr.Warning(f"Invalid Token: {e}. You can only perform read-only actions.")
47
  update_dict = {
48
  manage_files_btn: gr.update(interactive=False),
 
50
  commit_btn: gr.update(interactive=False),
51
  whoami_output: gr.update(value=None, visible=False)
52
  }
53
+ return (token, "", *update_dict.values())
 
54
 
55
 
56
  def list_repos(token, author, repo_type):
 
60
  return gr.update(choices=[], value=None), gr.update(visible=False), gr.update(visible=False)
61
  try:
62
  api = get_hf_api(token)
63
+ # Use the dedicated list functions for clarity e.g. api.list_models, api.list_spaces
64
  list_fn = getattr(api, f"list_{repo_type}s")
65
  repos = list_fn(author=author)
66
  repo_ids = [repo.id for repo in repos]
 
86
  try:
87
  api = get_hf_api(token)
88
  api.delete_repo(repo_id=repo_id, repo_type=repo_type)
89
+ gr.Info(f"Successfully deleted '{repo_id}'.")
90
  return None, gr.update(visible=False), gr.update(visible=False)
91
  except HfHubHTTPError as e:
92
  gr.Error(f"Failed to delete repository: {e}")
 
95
  # --- File Editor Functions ---
96
 
97
  def show_file_manager(token, repo_id, repo_type):
 
98
  if not repo_id:
99
  gr.Warning("No repository selected.")
100
  return gr.update(visible=False), gr.update(), gr.update(), gr.update()
 
102
  api = get_hf_api(token)
103
  repo_files = api.list_repo_files(repo_id=repo_id, repo_type=repo_type)
104
  filtered_files = [f for f in repo_files if not f.startswith('.')]
 
105
  return (
106
+ gr.update(visible=True), gr.update(choices=filtered_files, value=None),
107
+ gr.update(value="## Select a file to view or edit.", language='markdown'), ""
 
 
108
  )
 
 
 
109
  except Exception as e:
110
  gr.Error(f"Could not list files: {e}")
111
  return gr.update(visible=False), gr.update(), gr.update(), gr.update()
112
 
 
113
  def load_file_content(token, repo_id, repo_type, filepath):
 
114
  if not filepath:
115
  return gr.update(value="## Select a file to view its content.", language='markdown')
116
  try:
117
  api = get_hf_api(token)
118
+ local_path = api.hf_hub_download(repo_id=repo_id, repo_type=repo_type, filename=filepath, token=token)
119
+ with open(local_path, 'r', encoding='utf-8') as f: content = f.read()
 
 
 
 
120
  language = os.path.splitext(filepath)[1].lstrip('.').lower()
 
121
  if language == 'py': language = 'python'
122
  if language == 'js': language = 'javascript'
123
  if language == 'md': language = 'markdown'
124
+ return gr.update(value=content, language=language)
 
 
125
  except Exception as e:
 
126
  return gr.update(value=f"Error loading file: {e}", language='plaintext')
127
 
128
  def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
129
+ if not token: gr.Error("A write-enabled token is required."); return
130
+ if not filepath: gr.Warning("No file selected."); return
131
+ if not commit_message: gr.Warning("Commit message cannot be empty."); return
 
 
 
 
 
 
 
 
132
  try:
 
 
 
 
 
 
 
133
  api = get_hf_api(token)
134
  api.upload_file(
135
+ path_or_fileobj=bytes(content, 'utf-8'), path_in_repo=filepath,
136
+ repo_id=repo_id, repo_type=repo_type, commit_message=commit_message,
 
 
 
137
  )
 
138
  gr.Info(f"Successfully committed '{filepath}' to '{repo_id}'!")
139
  except Exception as e:
140
  gr.Error(f"Failed to commit file: {e}")
141
 
 
142
  # --- Gradio UI Layout ---
143
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Hugging Face Hub Toolkit") as demo:
144
  # State management
 
151
  gr.Markdown("An intuitive interface to manage your Hugging Face repositories. **Enter a write-token for full access.**")
152
 
153
  with gr.Row():
154
+ hf_token = gr.Textbox(label="Hugging Face API Token", type="password", placeholder="hf_...", scale=3)
 
 
 
155
  whoami_output = gr.JSON(label="Authenticated User", visible=False, scale=1)
156
 
157
  with gr.Row(equal_height=False):
 
158
  with gr.Column(scale=1):
159
  gr.Markdown("### 1. Select a Repository")
160
  author_input = gr.Textbox(label="Author (Username or Org)", interactive=True)
 
161
  repo_selector = gr.Radio(label="Select a Repository", interactive=True, value=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  with gr.Tabs() as repo_type_tabs:
163
+ for repo_type, label in [("space", "Spaces"), ("model", "Models"), ("dataset", "Datasets")]:
164
+ with gr.Tab(label, id=repo_type):
165
+ btn = gr.Button(f"List {label}")
166
+ btn.click(
167
+ fn=list_repos,
168
+ inputs=[hf_token_state, author_input, gr.State(repo_type)],
169
+ outputs=[repo_selector, gr.Column(), gr.Column()] # Dummy outputs for panels to hide them
170
+ ).then(fn=lambda author: author, inputs=author_input, outputs=author_state)
171
+
172
  with gr.Column(scale=3):
173
+ with gr.Column(visible=False) as action_panel:
174
+ gr.Markdown("### 2. Choose an Action")
175
+ with gr.Row():
176
+ manage_files_btn = gr.Button("Manage Files", interactive=False, scale=1)
177
+ delete_repo_btn = gr.Button("Delete This Repo", variant="stop", interactive=False, scale=1)
178
+
179
+ with gr.Column(visible=False) as editor_panel:
180
+ gr.Markdown("### 3. Edit Files")
181
+ file_selector = gr.Dropdown(label="Select File", interactive=True)
182
+ code_editor = gr.Code(label="File Content", language="markdown", interactive=True)
183
+ commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Update README.md", interactive=True)
184
+ commit_btn = gr.Button("Commit Changes", variant="primary", interactive=False)
 
 
 
 
 
 
 
 
185
 
186
  # --- Event Handlers ---
 
187
  hf_token.change(
188
+ fn=handle_token_change, inputs=hf_token,
 
189
  outputs=[hf_token_state, author_state, manage_files_btn, delete_repo_btn, commit_btn, author_input, whoami_output]
190
  )
 
191
  repo_type_tabs.select(
192
  fn=lambda rt: (rt, None, gr.update(choices=[], value=None), gr.update(visible=False), gr.update(visible=False)),
193
  inputs=repo_type_tabs,
194
  outputs=[selected_repo_type, selected_repo_id, repo_selector, action_panel, editor_panel]
195
  )
 
196
  repo_selector.select(
197
  fn=lambda repo_id: (repo_id, *handle_repo_selection(repo_id)),
198
+ inputs=repo_selector, outputs=[selected_repo_id, action_panel, editor_panel]
 
199
  )
 
200
  manage_files_btn.click(
201
+ fn=show_file_manager, inputs=[hf_token_state, selected_repo_id, selected_repo_type],
 
202
  outputs=[editor_panel, file_selector, code_editor, commit_message_input]
203
  )
 
204
  delete_repo_btn.click(
205
+ fn=delete_repo, inputs=[hf_token_state, selected_repo_id, selected_repo_type],
 
206
  outputs=[selected_repo_id, action_panel, editor_panel],
207
  js="() => confirm('Are you sure you want to permanently delete this repository? This action cannot be undone.')"
208
  ).then(
209
+ # On successful deletion, refresh the repo list.
210
+ # THIS IS THE CORRECTED PART: Pass Gradio components to inputs, not a lambda.
211
  fn=list_repos,
212
+ inputs=[hf_token_state, author_state, selected_repo_type],
213
  outputs=[repo_selector, action_panel, editor_panel]
214
  )
 
215
  file_selector.change(
216
+ fn=load_file_content, inputs=[hf_token_state, selected_repo_id, selected_repo_type, file_selector],
 
217
  outputs=code_editor
218
  )
 
219
  commit_btn.click(
220
  fn=commit_file,
221
+ inputs=[hf_token_state, selected_repo_id, selected_repo_type, file_selector, code_editor, commit_message_input]
 
222
  )
223
 
224
  if __name__ == "__main__":
225
+ demo.launch(debug=True)