Update app.py
Browse files
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
|
| 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.
|
| 159 |
-
with gr.
|
| 160 |
-
gr.
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
with gr.
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
gr.
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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]
|