Spaces:
Runtime error
Runtime error
Initial Space setup of broadfield-dev/test-dfsdfsd via Builder
Browse files- .gitattributes +1 -35
- README.md +20 -4
- app.py +184 -0
- app_logic.py +255 -0
- requirements.txt +5 -0
.gitattributes
CHANGED
@@ -1,35 +1 @@
|
|
1 |
-
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
[Binary file - 1519 bytes]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.32.1
|
8 |
app_file: app.py
|
@@ -10,3 +10,19 @@ pinned: false
|
|
10 |
---
|
11 |
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Space Builder
|
3 |
+
emoji: 🛠️
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: green
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.32.1
|
8 |
app_file: app.py
|
|
|
10 |
---
|
11 |
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
13 |
+
|
14 |
+
Space Builder
|
15 |
+
|
16 |
+
A Gradio app to create, view, and update Hugging Face Spaces with a custom file structure defined via markdown input. Provide your Hugging Face API token and a markdown string to create a new Space, list files in an existing Space, or update specific files with Git commits.
|
17 |
+
|
18 |
+
## Features
|
19 |
+
- Create a new Hugging Face Space with a specified file structure.
|
20 |
+
- View files in an existing Space.
|
21 |
+
- Update files in a Space with custom commit messages.
|
22 |
+
- Modern Gradio UI with a soft theme.
|
23 |
+
|
24 |
+
## Usage
|
25 |
+
1. Enter your Hugging Face API token.
|
26 |
+
2. To create a Space, provide the space name, owner (optional), SDK, and markdown input with file structure and contents.
|
27 |
+
3. To view files, enter the space name and owner (optional).
|
28 |
+
4. To update a file, specify the space, file path, new content, and commit message.
|
app.py
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
No os needed here now for file paths unless for basename for display
|
3 |
+
from app_logic import (
|
4 |
+
create_space,
|
5 |
+
update_space_file,
|
6 |
+
load_token_from_image_and_set_env,
|
7 |
+
KEYLOCK_DECODE_AVAILABLE,
|
8 |
+
list_space_files_for_browsing,
|
9 |
+
get_space_file_content,
|
10 |
+
)
|
11 |
+
|
12 |
+
Gradio interface
|
13 |
+
def main_ui():
|
14 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky), title="Hugging Face Space Builder") as demo:
|
15 |
+
gr.Markdown(
|
16 |
+
"""
|
17 |
+
# 🛠️ Hugging Face Space Builder
|
18 |
+
## Build Huggingface Space from a standardized string that models can be prompted to export.
|
19 |
+
|
20 |
+
Create, view, and manage Hugging Face Spaces.
|
21 |
+
Provide your Hugging Face API token directly or load it from a KeyLock Wallet image.
|
22 |
+
"""
|
23 |
+
)
|
24 |
+
|
25 |
+
# --- Authentication Section (Unchanged) ---
|
26 |
+
with gr.Accordion("🔑 Authentication Methods", open=False):
|
27 |
+
gr.Markdown(
|
28 |
+
"""
|
29 |
+
**Token Precedence:**
|
30 |
+
1. If a token is successfully loaded from a KeyLock Wallet image, it will be used.
|
31 |
+
2. Otherwise, the token entered in the 'Enter API Token Directly' textbox will be used.
|
32 |
+
"""
|
33 |
+
)
|
34 |
+
gr.Markdown("---")
|
35 |
+
gr.Markdown("### Method 1: Enter API Token Directly")
|
36 |
+
api_token_ui_input = gr.Textbox(label="Hugging Face API Token (hf_xxx)", type="password", placeholder="Enter token OR load from Wallet image")
|
37 |
+
if KEYLOCK_DECODE_AVAILABLE:
|
38 |
+
gr.Markdown("---")
|
39 |
+
gr.Markdown("### Method 2: Load API Token to this sytem environment from KeyLock Wallet Image")
|
40 |
+
gr.Markdown("### Get a KeyLock Wallet Image Here: [/spaces/broadfield-dev/KeyLock-API-Wallet](https://huggingface.co/spaces/broadfield-dev/KeyLock-API-Wallet)")
|
41 |
+
with gr.Row():
|
42 |
+
keylock_image_input = gr.Image(label="KeyLock Wallet Image (PNG)", type="pil", image_mode="RGBA")
|
43 |
+
keylock_password_input = gr.Textbox(label="Image Password", type="password")
|
44 |
+
keylock_decode_button = gr.Button("Load Token from Wallet Image", variant="secondary")
|
45 |
+
keylock_status_output = gr.Markdown(label="Wallet Image Decoding Status", value="Status...")
|
46 |
+
keylock_decode_button.click(load_token_from_image_and_set_env, [keylock_image_input, keylock_password_input], [keylock_status_output])
|
47 |
+
else:
|
48 |
+
gr.Markdown("_(KeyLock Wallet image decoding disabled: library not found.)_")
|
49 |
+
|
50 |
+
# --- Main Application Tabs ---
|
51 |
+
with gr.Tabs():
|
52 |
+
with gr.TabItem("🚀 Create New Space"):
|
53 |
+
# (Create Space UI Unchanged)
|
54 |
+
with gr.Row():
|
55 |
+
space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
|
56 |
+
owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
|
57 |
+
sdk_create_input = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio")
|
58 |
+
gr.Markdown("### Example Source: [/spaces/broadfield-dev/repo_to_md](https://huggingface.co/spaces/broadfield-dev/repo_to_md)")
|
59 |
+
markdown_input_create = gr.Textbox(label="Markdown File Structure & Content", placeholder="Example:\n### File: app.py\n# ```python\nprint(\"Hello\")\n# ```", lines=15, interactive=True)
|
60 |
+
create_btn = gr.Button("Create Space", variant="primary")
|
61 |
+
create_output_md = gr.Markdown(label="Result")
|
62 |
+
create_btn.click(create_space, [api_token_ui_input, space_name_create_input, owner_create_input, sdk_create_input, markdown_input_create], create_output_md)
|
63 |
+
|
64 |
+
|
65 |
+
# --- "Browse & Edit Files" Tab (Hub-based) ---
|
66 |
+
with gr.TabItem("📂 Browse & Edit Space Files"):
|
67 |
+
gr.Markdown("Browse, view, and edit files directly on a Hugging Face Space.")
|
68 |
+
with gr.Row():
|
69 |
+
browse_space_name_input = gr.Textbox(label="Space Name", placeholder="my-target-app", scale=2)
|
70 |
+
browse_owner_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
|
71 |
+
|
72 |
+
browse_load_files_button = gr.Button("Load Files List from Space", variant="secondary")
|
73 |
+
browse_status_output = gr.Markdown(label="File List Status")
|
74 |
+
|
75 |
+
gr.Markdown("---")
|
76 |
+
gr.Markdown("### Select File to View/Edit")
|
77 |
+
# Using Radio for file list. Could be Dropdown for many files.
|
78 |
+
# `choices` will be updated dynamically.
|
79 |
+
file_selector_radio = gr.Radio(
|
80 |
+
label="Files in Space",
|
81 |
+
choices=[],
|
82 |
+
interactive=True,
|
83 |
+
info="Select a file to load its content below."
|
84 |
+
)
|
85 |
+
|
86 |
+
gr.Markdown("---")
|
87 |
+
gr.Markdown("### File Editor")
|
88 |
+
file_editor_textbox = gr.Textbox(
|
89 |
+
label="File Content (Editable)", lines=20, interactive=True,
|
90 |
+
placeholder="Select a file from the list above to view/edit its content."
|
91 |
+
)
|
92 |
+
edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g., Update app.py content")
|
93 |
+
update_edited_file_button = gr.Button("Update File in Space", variant="primary")
|
94 |
+
edit_update_status_output = gr.Markdown(label="File Update Result")
|
95 |
+
|
96 |
+
# --- Event Handlers for Browse & Edit Tab (Hub-based) ---
|
97 |
+
def handle_load_space_files_list(token_from_ui, space_name, owner_name):
|
98 |
+
if not space_name:
|
99 |
+
return {
|
100 |
+
browse_status_output: gr.Markdown("Error: Space Name cannot be empty."),
|
101 |
+
file_selector_radio: gr.Radio(choices=[], value=None), # Clear radio
|
102 |
+
file_editor_textbox: gr.Textbox(value=""), # Clear editor
|
103 |
+
}
|
104 |
+
|
105 |
+
files_list, error_msg = list_space_files_for_browsing(token_from_ui, space_name, owner_name)
|
106 |
+
|
107 |
+
if error_msg and files_list is None: # Indicates a hard error
|
108 |
+
return {
|
109 |
+
browse_status_output: gr.Markdown(f"Error: {error_msg}"),
|
110 |
+
file_selector_radio: gr.Radio(choices=[], value=None),
|
111 |
+
file_editor_textbox: gr.Textbox(value=""),
|
112 |
+
}
|
113 |
+
if error_msg and not files_list: # Info message like "no files found"
|
114 |
+
return {
|
115 |
+
browse_status_output: gr.Markdown(error_msg), # Show "No files found"
|
116 |
+
file_selector_radio: gr.Radio(choices=[], value=None),
|
117 |
+
file_editor_textbox: gr.Textbox(value=""),
|
118 |
+
}
|
119 |
+
|
120 |
+
return {
|
121 |
+
browse_status_output: gr.Markdown(f"Files loaded for '{owner_name}/{space_name}'. Select a file to edit."),
|
122 |
+
file_selector_radio: gr.Radio(choices=files_list, value=None, label=f"Files in {owner_name}/{space_name}"),
|
123 |
+
file_editor_textbox: gr.Textbox(value=""), # Clear editor on new list load
|
124 |
+
}
|
125 |
+
|
126 |
+
browse_load_files_button.click(
|
127 |
+
fn=handle_load_space_files_list,
|
128 |
+
inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input],
|
129 |
+
outputs=[browse_status_output, file_selector_radio, file_editor_textbox]
|
130 |
+
)
|
131 |
+
|
132 |
+
def handle_file_selected_for_editing(token_from_ui, space_name, owner_name, selected_filepath_evt: gr.SelectData):
|
133 |
+
if not selected_filepath_evt or not selected_filepath_evt.value:
|
134 |
+
# This might happen if the radio is cleared or has no selection
|
135 |
+
return {
|
136 |
+
file_editor_textbox: gr.Textbox(value=""),
|
137 |
+
browse_status_output: gr.Markdown("No file selected or selection cleared.")
|
138 |
+
}
|
139 |
+
|
140 |
+
selected_filepath = selected_filepath_evt.value # The value of the selected radio button
|
141 |
+
|
142 |
+
if not space_name: # Should not happen if file list is populated
|
143 |
+
return {
|
144 |
+
file_editor_textbox: gr.Textbox(value="Error: Space name is missing."),
|
145 |
+
browse_status_output: gr.Markdown("Error: Space context lost. Please reload file list.")
|
146 |
+
}
|
147 |
+
|
148 |
+
content, error_msg = get_space_file_content(token_from_ui, space_name, owner_name, selected_filepath)
|
149 |
+
|
150 |
+
if error_msg:
|
151 |
+
return {
|
152 |
+
file_editor_textbox: gr.Textbox(value=f"Error loading file content: {error_msg}"),
|
153 |
+
browse_status_output: gr.Markdown(f"Failed to load '{selected_filepath}': {error_msg}")
|
154 |
+
}
|
155 |
+
|
156 |
+
return {
|
157 |
+
file_editor_textbox: gr.Textbox(value=content),
|
158 |
+
browse_status_output: gr.Markdown(f"Content loaded for: {selected_filepath}")
|
159 |
+
}
|
160 |
+
|
161 |
+
# Use .select event for gr.Radio
|
162 |
+
file_selector_radio.select(
|
163 |
+
fn=handle_file_selected_for_editing,
|
164 |
+
inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input], # Pass space context again
|
165 |
+
outputs=[file_editor_textbox, browse_status_output]
|
166 |
+
)
|
167 |
+
|
168 |
+
update_edited_file_button.click(
|
169 |
+
fn=update_space_file,
|
170 |
+
inputs=[
|
171 |
+
api_token_ui_input,
|
172 |
+
browse_space_name_input,
|
173 |
+
browse_owner_input,
|
174 |
+
file_selector_radio, # Pass the selected file path from the radio
|
175 |
+
file_editor_textbox,
|
176 |
+
edit_commit_message_input
|
177 |
+
],
|
178 |
+
outputs=[edit_update_status_output]
|
179 |
+
)
|
180 |
+
return demo
|
181 |
+
|
182 |
+
if __name__ == "__main__":
|
183 |
+
demo = main_ui()
|
184 |
+
demo.launch(mcp_server=True)
|
app_logic.py
ADDED
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
import tempfile
|
4 |
+
import shutil
|
5 |
+
import git
|
6 |
+
import re
|
7 |
+
|
8 |
+
from huggingface_hub import (
|
9 |
+
create_repo,
|
10 |
+
upload_folder,
|
11 |
+
list_repo_files,
|
12 |
+
Repository,
|
13 |
+
whoami,
|
14 |
+
hf_hub_download, # New import
|
15 |
+
)
|
16 |
+
import logging
|
17 |
+
from pathlib import Path
|
18 |
+
from PIL import Image
|
19 |
+
|
20 |
+
try:
|
21 |
+
from keylock_decode import decode_from_image_pil
|
22 |
+
KEYLOCK_DECODE_AVAILABLE = True
|
23 |
+
except ImportError:
|
24 |
+
KEYLOCK_DECODE_AVAILABLE = False
|
25 |
+
decode_from_image_pil = None
|
26 |
+
logging.warning("keylock-decode library not found. KeyLock Wallet image feature will be disabled.")
|
27 |
+
|
28 |
+
logging.basicConfig(
|
29 |
+
level=logging.INFO,
|
30 |
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
31 |
+
)
|
32 |
+
logger = logging.getLogger(__name__)
|
33 |
+
|
34 |
+
--- Helper Function to Get API Token (Unchanged) ---
|
35 |
+
def _get_api_token(ui_token_from_textbox=None):
|
36 |
+
env_token = os.getenv('HF_TOKEN')
|
37 |
+
if env_token: return env_token, None
|
38 |
+
if ui_token_from_textbox: return ui_token_from_textbox, None
|
39 |
+
return None, "Error: Hugging Face API token not provided."
|
40 |
+
|
41 |
+
--- `load_token_from_image_and_set_env` (Unchanged - Terminology and debug logic as before) ---
|
42 |
+
def load_token_from_image_and_set_env(image_pil_object: Image.Image, password: str):
|
43 |
+
if not KEYLOCK_DECODE_AVAILABLE: return "Error: KeyLock-Decode library is not installed."
|
44 |
+
if image_pil_object is None: return "Error: No KeyLock Wallet image provided."
|
45 |
+
if not password: return "Error: Password cannot be empty."
|
46 |
+
status_messages_display = []
|
47 |
+
# ... (rest of the function, ensure debug logic is as intended or removed)
|
48 |
+
try:
|
49 |
+
logger.info(f"Attempting to decode from KeyLock Wallet image...")
|
50 |
+
decoded_data, status_msgs_from_lib = decode_from_image_pil(image_pil_object, password, set_environment_variables=True)
|
51 |
+
status_messages_display.extend(status_msgs_from_lib)
|
52 |
+
if decoded_data:
|
53 |
+
status_messages_display.append("\n**Decoded Data Summary (sensitive values masked):**")
|
54 |
+
for key, value in decoded_data.items():
|
55 |
+
display_value = '********' if any(k_word in key.upper() for k_word in ['TOKEN', 'KEY', 'SECRET', 'PASS']) else value
|
56 |
+
status_messages_display.append(f"- {key}: {display_value}")
|
57 |
+
if os.getenv('HF_TOKEN'): status_messages_display.append(f"\n**SUCCESS: HF_TOKEN set from KeyLock Wallet image.**")
|
58 |
+
# ... (other status messages)
|
59 |
+
except ValueError as e: status_messages_display.append(f"**Decoding Error:** {e}")
|
60 |
+
except Exception as e: status_messages_display.append(f"**Unexpected decoding error:** {str(e)}")
|
61 |
+
return "\n".join(status_messages_display)
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
'''def process_commented_markdown(commented_input):
|
66 |
+
"""Process a commented markdown string by stripping '# ' from each line if '# # Space:' is present."""
|
67 |
+
lines = commented_input.strip().split("\n")
|
68 |
+
print(type(lines))
|
69 |
+
# Check for '# # Space:' or variations (e.g., '# Space:') in any line
|
70 |
+
if any( "# # Space:" in line.strip() for line in lines):
|
71 |
+
print("YES")
|
72 |
+
cleaned_lines = [line.lstrip("# ") for line in lines]
|
73 |
+
return cleaned_lines
|
74 |
+
return lines'''
|
75 |
+
|
76 |
+
def process_commented_markdown(commented_input):
|
77 |
+
"""Process a commented markdown string by stripping '# ' from each line if '# # Space:' is present."""
|
78 |
+
lines = commented_input.strip().split("\n")
|
79 |
+
print(type(lines)) # Original debug print
|
80 |
+
# Check for '# # Space:' or variations (e.g., '# Space:') in any line
|
81 |
+
if any( "# # Space:" in line.strip() for line in lines):
|
82 |
+
print("YES") # Original debug print
|
83 |
+
|
84 |
+
cleaned_lines = [line[2:] if line.startswith("# ") else line for line in lines]
|
85 |
+
|
86 |
+
return cleaned_lines
|
87 |
+
return lines
|
88 |
+
|
89 |
+
def parse_markdown(markdown_input):
|
90 |
+
space_info = {"repo_name_md": "", "owner_md": "", "files": []}
|
91 |
+
current_file_path = None; current_file_content_lines = []
|
92 |
+
in_file_definition = False; in_code_block = False
|
93 |
+
print(markdown_input)
|
94 |
+
lines = process_commented_markdown(markdown_input)
|
95 |
+
print(lines)
|
96 |
+
#lines = markdown_input.strip().split("\n")
|
97 |
+
|
98 |
+
for line_content_orig in lines:
|
99 |
+
line_content_stripped = line_content_orig.strip()
|
100 |
+
if line_content_stripped.startswith("### File:"):
|
101 |
+
if current_file_path and in_file_definition:
|
102 |
+
space_info["files"].append({"path": current_file_path, "content": "\n".join(current_file_content_lines)})
|
103 |
+
current_file_path = line_content_stripped.replace("### File:", "").strip()
|
104 |
+
current_file_content_lines = []
|
105 |
+
in_file_definition = True; in_code_block = False
|
106 |
+
continue
|
107 |
+
if not in_file_definition:
|
108 |
+
if line_content_stripped.startswith("# Space:"):
|
109 |
+
full_space_name_md = line_content_stripped.replace("# Space:", "").strip()
|
110 |
+
if "/" in full_space_name_md: space_info["owner_md"], space_info["repo_name_md"] = full_space_name_md.split("/", 1)
|
111 |
+
else: space_info["repo_name_md"] = full_space_name_md
|
112 |
+
continue
|
113 |
+
if line_content_stripped.startswith("```"):
|
114 |
+
in_code_block = not in_code_block
|
115 |
+
continue
|
116 |
+
current_file_content_lines.append(line_content_orig)
|
117 |
+
if current_file_path and in_file_definition:
|
118 |
+
space_info["files"].append({"path": current_file_path, "content": "\n".join(current_file_content_lines)})
|
119 |
+
space_info["files"] = [f for f in space_info["files"] if f.get("path")]
|
120 |
+
return space_info
|
121 |
+
|
122 |
+
|
123 |
+
|
124 |
+
--- `_determine_repo_id` (Unchanged) ---
|
125 |
+
def _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui):
|
126 |
+
if not space_name_ui: return None, "Error: Space Name cannot be empty."
|
127 |
+
if "/" in space_name_ui: return None, "Error: Space Name should not contain '/'. Use Owner field."
|
128 |
+
final_owner = owner_ui; error_message = None
|
129 |
+
if not final_owner:
|
130 |
+
resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
|
131 |
+
if token_err: return None, token_err
|
132 |
+
if not resolved_api_token: return None, "Error: API token required for auto owner determination."
|
133 |
+
try:
|
134 |
+
user_info = whoami(token=resolved_api_token)
|
135 |
+
if user_info and 'name' in user_info: final_owner = user_info['name']
|
136 |
+
else: error_message = "Error: Could not retrieve username. Check token/permissions or specify Owner."
|
137 |
+
except Exception as e: error_message = f"Error retrieving username: {str(e)}. Specify Owner."
|
138 |
+
if error_message: return None, error_message
|
139 |
+
if not final_owner: return None, "Error: Owner could not be determined."
|
140 |
+
return f"{final_owner}/{space_name_ui}", None
|
141 |
+
|
142 |
+
|
143 |
+
--- New Function to Fetch File Content from Hub ---
|
144 |
+
def get_space_file_content(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo):
|
145 |
+
"""Fetches content of a specific file from a Hugging Face Space."""
|
146 |
+
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
147 |
+
try:
|
148 |
+
resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
|
149 |
+
if token_err:
|
150 |
+
return None, token_err # Return error as second element for consistency
|
151 |
+
|
152 |
+
repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
|
153 |
+
if err:
|
154 |
+
return None, err
|
155 |
+
repo_id_for_error_logging = repo_id
|
156 |
+
|
157 |
+
if not file_path_in_repo:
|
158 |
+
return None, "Error: File path cannot be empty."
|
159 |
+
|
160 |
+
logger.info(f"Attempting to download file: {file_path_in_repo} from Space: {repo_id}")
|
161 |
+
downloaded_file_path = hf_hub_download(
|
162 |
+
repo_id=repo_id,
|
163 |
+
filename=file_path_in_repo,
|
164 |
+
repo_type="space",
|
165 |
+
token=resolved_api_token,
|
166 |
+
# revision="main", # Optional: specify a branch/commit
|
167 |
+
# cache_dir=... # Optional: manage cache
|
168 |
+
)
|
169 |
+
|
170 |
+
content = Path(downloaded_file_path).read_text(encoding="utf-8")
|
171 |
+
logger.info(f"Successfully downloaded and read file: {file_path_in_repo} from {repo_id}")
|
172 |
+
return content, None # Return content and no error
|
173 |
+
|
174 |
+
except Exception as e:
|
175 |
+
# Catch specific huggingface_hub.utils.HFValidationError for not found etc.
|
176 |
+
if "404" in str(e) or "not found" in str(e).lower():
|
177 |
+
logger.warning(f"File not found {file_path_in_repo} in {repo_id_for_error_logging}: {e}")
|
178 |
+
return None, f"Error: File '{file_path_in_repo}' not found in Space '{repo_id_for_error_logging}'."
|
179 |
+
logger.exception(f"Error fetching file content for {file_path_in_repo} from {repo_id_for_error_logging}:")
|
180 |
+
return None, f"Error fetching file content: {str(e)}"
|
181 |
+
|
182 |
+
--- Function to list files (reused, but now distinct from fetching content) ---
|
183 |
+
def list_space_files_for_browsing(ui_api_token_from_textbox, space_name_ui, owner_ui):
|
184 |
+
"""Lists files in a Hugging Face Space, returns list or error."""
|
185 |
+
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
186 |
+
try:
|
187 |
+
resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
|
188 |
+
if token_err: return None, token_err
|
189 |
+
|
190 |
+
repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
|
191 |
+
if err: return None, err
|
192 |
+
repo_id_for_error_logging = repo_id
|
193 |
+
|
194 |
+
files = list_repo_files(repo_id=repo_id, token=resolved_api_token, repo_type="space")
|
195 |
+
if not files:
|
196 |
+
return [], f"No files found in Space `{repo_id}`." # Return empty list and info message
|
197 |
+
return files, None # Return list of files and no error
|
198 |
+
except Exception as e:
|
199 |
+
logger.exception(f"Error listing files for {repo_id_for_error_logging}:")
|
200 |
+
return None, f"Error listing files for `{repo_id_for_error_logging}`: {str(e)}"
|
201 |
+
|
202 |
+
|
203 |
+
--- Core Functions: `create_space`, `update_space_file` (Unchanged from previous correct versions) ---
|
204 |
+
def create_space(ui_api_token_from_textbox, space_name_ui, owner_ui, sdk_ui, markdown_input):
|
205 |
+
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
206 |
+
try:
|
207 |
+
resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
|
208 |
+
if token_err: return token_err
|
209 |
+
repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
|
210 |
+
if err: return err
|
211 |
+
repo_id_for_error_logging = repo_id
|
212 |
+
space_info = parse_markdown(markdown_input)
|
213 |
+
if not space_info["files"]: return "Error: No files found in markdown."
|
214 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
215 |
+
repo_staging_path = Path(temp_dir) / "repo_staging_content"
|
216 |
+
repo_staging_path.mkdir(exist_ok=True)
|
217 |
+
for file_info in space_info["files"]:
|
218 |
+
if not file_info.get("path"): continue
|
219 |
+
file_path_abs = repo_staging_path / file_info["path"]
|
220 |
+
file_path_abs.parent.mkdir(parents=True, exist_ok=True)
|
221 |
+
with open(file_path_abs, "w", encoding="utf-8") as f: f.write(file_info["content"])
|
222 |
+
try:
|
223 |
+
create_repo(repo_id=repo_id, token=resolved_api_token, repo_type="space", space_sdk=sdk_ui, private=False)
|
224 |
+
except Exception as e:
|
225 |
+
err_str = str(e).lower()
|
226 |
+
if not ("already exists" in err_str or "you already created this repo" in err_str or "exists" in err_str):
|
227 |
+
return f"Error creating Space '{repo_id}': {str(e)}"
|
228 |
+
upload_folder(repo_id=repo_id, folder_path=str(repo_staging_path), path_in_repo=".", token=resolved_api_token, repo_type="space", commit_message=f"Initial Space setup of {repo_id} via Builder")
|
229 |
+
return f"Successfully created/updated Space: [{repo_id}](https://huggingface.co/spaces/{repo_id})"
|
230 |
+
except Exception as e:
|
231 |
+
logger.exception(f"Error in create_space for {repo_id_for_error_logging}:")
|
232 |
+
return f"Error during Space creation/update: {str(e)}"
|
233 |
+
|
234 |
+
def update_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, file_content, commit_message_ui):
|
235 |
+
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
236 |
+
try:
|
237 |
+
resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
|
238 |
+
if token_err: return token_err
|
239 |
+
repo_id, err = _determine_repo_id(ui_api_token_from_textbox, space_name_ui, owner_ui)
|
240 |
+
if err: return err
|
241 |
+
repo_id_for_error_logging = repo_id
|
242 |
+
if not file_path_in_repo: return "Error: File Path to update cannot be empty."
|
243 |
+
file_path_in_repo = file_path_in_repo.lstrip('/').replace(os.sep, '/')
|
244 |
+
commit_message_ui = commit_message_ui or f"Update {file_path_in_repo} via Space Builder"
|
245 |
+
with tempfile.TemporaryDirectory() as temp_dir_for_update:
|
246 |
+
repo_local_clone_path = Path(temp_dir_for_update) / "update_clone"
|
247 |
+
cloned_repo = Repository(local_dir=str(repo_local_clone_path), clone_from=f"https://huggingface.co/spaces/{repo_id}", repo_type="space", use_auth_token=resolved_api_token, git_user="Space Builder Bot", git_email="[email protected]")
|
248 |
+
full_local_file_path = Path(cloned_repo.local_dir) / file_path_in_repo
|
249 |
+
full_local_file_path.parent.mkdir(parents=True, exist_ok=True)
|
250 |
+
with open(full_local_file_path, "w", encoding="utf-8") as f: f.write(file_content)
|
251 |
+
cloned_repo.push_to_hub(commit_message=commit_message_ui)
|
252 |
+
return f"Successfully updated `{file_path_in_repo}` in Space [{repo_id}](https://huggingface.co/spaces/{repo_id})"
|
253 |
+
except Exception as e:
|
254 |
+
logger.exception(f"Error in update_space_file for {repo_id_for_error_logging}, file {file_path_in_repo}:")
|
255 |
+
return f"Error updating file for `{repo_id_for_error_logging}`: {str(e)}"
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio==4.44.0
|
2 |
+
huggingface_hub==0.24.6
|
3 |
+
gitpython==3.1.43
|
4 |
+
Pillow
|
5 |
+
git+https://github.com/broadfield-dev/keylock-decode.git
|