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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +280 -135
app.py CHANGED
@@ -1,173 +1,318 @@
1
  import gradio as gr
2
- from huggingface_hub import HfApi, HfFolder
3
- from huggingface_hub.utils import HfHubHTTPError
 
 
 
 
4
 
5
- # Helper function to initialize the HfApi client
6
  def get_hf_api(token):
7
  if not token:
8
- raise gr.Error("Hugging Face API token is required.")
 
9
  return HfApi(token=token)
10
 
11
- # --- Spaces Functions ---
12
- def list_user_spaces(token, author):
13
- try:
14
- api = get_hf_api(token)
15
- spaces = api.list_spaces(author=author)
16
- return "\n".join([space.id for space in spaces]) if spaces else "No spaces found for this user."
17
- except HfHubHTTPError as e:
18
- return f"Error: {e}"
19
 
20
- def create_space(token, repo_id, repo_type, space_sdk):
21
- try:
22
- api = get_hf_api(token)
23
- repo_url = api.create_repo(repo_id=repo_id, repo_type=repo_type, space_sdk=space_sdk)
24
- return f"Space created successfully! URL: {repo_url}"
25
- except HfHubHTTPError as e:
26
- return f"Error: {e}"
 
 
 
 
 
 
 
 
 
27
 
28
- def delete_space(token, repo_id):
29
  try:
30
  api = get_hf_api(token)
31
- api.delete_repo(repo_id=repo_id, repo_type='space')
32
- return f"Space '{repo_id}' deleted successfully."
33
- except HfHubHTTPError as e:
34
- return f"Error: {e}"
 
 
 
 
 
 
 
 
 
35
 
36
- # --- Models Functions ---
37
- def list_user_models(token, author):
38
- try:
39
- api = get_hf_api(token)
40
- models = api.list_models(author=author)
41
- return "\n".join([model.id for model in models]) if models else "No models found for this user."
42
  except HfHubHTTPError as e:
43
- return f"Error: {e}"
 
 
 
 
 
 
 
 
 
 
44
 
45
- def delete_model(token, repo_id):
 
 
 
 
46
  try:
47
  api = get_hf_api(token)
48
- api.delete_repo(repo_id=repo_id, repo_type='model')
49
- return f"Model '{repo_id}' deleted successfully."
 
50
  except HfHubHTTPError as e:
51
- return f"Error: {e}"
 
52
 
53
- # --- Datasets Functions ---
54
- def list_user_datasets(token, author):
 
 
 
 
 
 
 
 
 
 
 
 
55
  try:
56
  api = get_hf_api(token)
57
- datasets = api.list_datasets(author=author)
58
- return "\n".join([dataset.id for dataset in datasets]) if datasets else "No datasets found for this user."
 
 
59
  except HfHubHTTPError as e:
60
- return f"Error: {e}"
 
61
 
62
- def delete_dataset(token, repo_id):
 
 
 
 
 
 
63
  try:
64
  api = get_hf_api(token)
65
- api.delete_repo(repo_id=repo_id, repo_type='dataset')
66
- return f"Dataset '{repo_id}' deleted successfully."
67
- except HfHubHTTPError as e:
68
- return f"Error: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- # --- User Info Functions ---
71
- def whoami(token):
 
 
 
72
  try:
73
  api = get_hf_api(token)
74
- user_info = api.whoami()
75
- return user_info
76
- except HfHubHTTPError as e:
77
- return f"Error: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- def get_user_info(token, user):
80
  try:
 
 
 
 
 
 
 
 
 
81
  api = get_hf_api(token)
82
- user_info = api.get_user_info(user)
83
- return user_info
84
- except HfHubHTTPError as e:
85
- return f"Error: {e}"
 
 
 
 
 
 
 
 
 
 
 
86
 
 
 
 
 
 
 
 
87
 
88
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
89
- gr.Markdown("# Hugging Face Hub Toolkit")
90
- gr.Markdown("A Gradio interface for interacting with the Hugging Face Hub. Enter your Hugging Face API token with 'write' permissions to use the tools.")
91
 
92
  with gr.Row():
93
- hf_token = gr.Textbox(label="Hugging Face API Token", type="password", placeholder="hf_...")
 
 
 
 
 
 
94
 
95
- with gr.Tabs():
96
- with gr.TabItem("Spaces"):
97
- with gr.Row():
98
- with gr.Column():
99
- gr.Markdown("### List User Spaces")
100
- list_spaces_author = gr.Textbox(label="Author (Username)")
101
- list_spaces_btn = gr.Button("List Spaces")
102
- list_spaces_output = gr.Textbox(label="User Spaces", lines=10)
103
- with gr.Column():
104
- gr.Markdown("### Create a New Space")
105
- create_space_id = gr.Textbox(label="Space Repo ID (e.g., username/spacename)")
106
- create_space_sdk = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"])
107
- create_space_btn = gr.Button("Create Space")
108
- create_space_output = gr.Textbox(label="Result")
109
- with gr.Column():
110
- gr.Markdown("### Delete a Space")
111
- delete_space_id = gr.Textbox(label="Space Repo ID to Delete")
112
- delete_space_btn = gr.Button("Delete Space")
113
- delete_space_output = gr.Textbox(label="Result")
114
-
115
- with gr.TabItem("Models"):
116
- with gr.Row():
117
- with gr.Column():
118
- gr.Markdown("### List User Models")
119
- list_models_author = gr.Textbox(label="Author (Username)")
120
- list_models_btn = gr.Button("List Models")
121
- list_models_output = gr.Textbox(label="User Models", lines=10)
122
- with gr.Column():
123
- gr.Markdown("### Delete a Model")
124
- delete_model_id = gr.Textbox(label="Model Repo ID to Delete")
125
- delete_model_btn = gr.Button("Delete Model")
126
- delete_model_output = gr.Textbox(label="Result")
127
-
128
- with gr.TabItem("Datasets"):
129
- with gr.Row():
130
- with gr.Column():
131
- gr.Markdown("### List User Datasets")
132
- list_datasets_author = gr.Textbox(label="Author (Username)")
133
- list_datasets_btn = gr.Button("List Datasets")
134
- list_datasets_output = gr.Textbox(label="User Datasets", lines=10)
135
- with gr.Column():
136
- gr.Markdown("### Delete a Dataset")
137
- delete_dataset_id = gr.Textbox(label="Dataset Repo ID to Delete")
138
- delete_dataset_btn = gr.Button("Delete Dataset")
139
- delete_dataset_output = gr.Textbox(label="Result")
140
-
141
- with gr.TabItem("User Info"):
142
  with gr.Row():
143
- with gr.Column():
144
- gr.Markdown("### Who Am I?")
145
- whoami_btn = gr.Button("Get My Info")
146
- whoami_output = gr.JSON(label="My User Info")
147
- with gr.Column():
148
- gr.Markdown("### Get User Info")
149
- get_user_info_user = gr.Textbox(label="Username")
150
- get_user_info_btn = gr.Button("Get User Info")
151
- get_user_info_output = gr.JSON(label="User Info")
152
-
153
- # --- Event Handlers ---
154
- # Spaces
155
- list_spaces_btn.click(list_user_spaces, inputs=[hf_token, list_spaces_author], outputs=list_spaces_output)
156
- create_space_btn.click(create_space, inputs=[hf_token, create_space_id, gr.Textbox(value="space", visible=False), create_space_sdk], outputs=create_space_output)
157
- delete_space_btn.click(delete_space, inputs=[hf_token, delete_space_id], outputs=delete_space_output)
158
-
159
- # Models
160
- list_models_btn.click(list_user_models, inputs=[hf_token, list_models_author], outputs=list_models_output)
161
- delete_model_btn.click(delete_model, inputs=[hf_token, delete_model_id], outputs=delete_model_output)
162
-
163
- # Datasets
164
- list_datasets_btn.click(list_user_datasets, inputs=[hf_token, list_datasets_author], outputs=list_datasets_output)
165
- delete_dataset_btn.click(delete_dataset, inputs=[hf_token, delete_dataset_id], outputs=delete_dataset_output)
166
-
167
- # User Info
168
- whoami_btn.click(whoami, inputs=hf_token, outputs=whoami_output)
169
- get_user_info_btn.click(get_user_info, inputs=[hf_token, get_user_info_user], outputs=get_user_info_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
 
 
 
 
 
171
 
172
  if __name__ == "__main__":
173
- demo.launch()
 
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
 
 
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),
43
+ commit_btn: gr.update(interactive=True),
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:
51
+ # Token is invalid
52
+ gr.Warning(f"Invalid Token: {e}. You can only perform read-only actions.")
53
+ update_dict = {
54
+ manage_files_btn: gr.update(interactive=False),
55
+ delete_repo_btn: gr.update(interactive=False),
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."""
79
+ if repo_id:
80
+ return gr.update(visible=True), gr.update(visible=False) # Show actions, hide editor
81
+ return gr.update(visible=False), gr.update(visible=False) # Hide everything
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
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
+
103
+ 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.")
124
+ return gr.update(visible=False), gr.update(), gr.update(), gr.update()
125
+ except Exception as e:
126
+ gr.Error(f"Could not list files: {e}")
127
+ return gr.update(visible=False), gr.update(), gr.update(), gr.update()
128
 
129
+
130
+ def load_file_content(token, repo_id, repo_type, filepath):
131
+ """Downloads and displays the content of a selected file."""
132
+ if not 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}")
155
+ return gr.update(value=f"Error loading file: {e}", language='plaintext')
156
+
157
+ def commit_file(token, repo_id, repo_type, filepath, content, commit_message):
158
+ """Commits the edited file content back to the repository."""
159
+ if not token:
160
+ gr.Error("A write-enabled token is required to commit changes.")
161
+ return
162
+ if not filepath:
163
+ gr.Warning("No file is selected to commit.")
164
+ return
165
+ if not commit_message:
166
+ gr.Warning("Commit message cannot be empty.")
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:
177
+ f.write(content)
178
+
179
  api = get_hf_api(token)
180
+ api.upload_file(
181
+ path_or_fileobj=temp_file_path,
182
+ path_in_repo=filepath,
183
+ repo_id=repo_id,
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.**")
 
206
 
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)
251
+ code_editor = gr.Code(label="File Content", language="markdown", interactive=True)
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],
293
+ outputs=[editor_panel, file_selector, code_editor, commit_message_input]
294
+ )
295
+
296
+ delete_repo_btn.click(
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],
308
+ outputs=code_editor
309
+ )
310
 
311
+ commit_btn.click(
312
+ fn=commit_file,
313
+ inputs=[hf_token_state, selected_repo_id, selected_repo_type, file_selector, code_editor, commit_message_input],
314
+ outputs=[]
315
+ )
316
 
317
  if __name__ == "__main__":
318
+ demo.launch(debug=True)