Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1 |
import gradio as gr
|
|
|
|
|
2 |
from app_logic import (
|
3 |
create_space,
|
4 |
-
view_space_files,
|
5 |
update_space_file,
|
6 |
-
load_token_from_image_and_set_env,
|
7 |
-
KEYLOCK_DECODE_AVAILABLE
|
|
|
|
|
8 |
)
|
9 |
-
# PIL Image for type hint if needed, though gr.Image handles it
|
10 |
-
# from PIL import Image
|
11 |
|
12 |
# Gradio interface
|
13 |
def main_ui():
|
@@ -15,48 +17,39 @@ def main_ui():
|
|
15 |
gr.Markdown(
|
16 |
"""
|
17 |
# 🛠️ Hugging Face Space Builder
|
18 |
-
Create, view, and
|
19 |
-
Provide your Hugging Face API token directly or load it from a
|
20 |
"""
|
21 |
)
|
22 |
|
23 |
-
# --- Authentication Section ---
|
24 |
with gr.Accordion("🔑 Authentication Methods", open=True):
|
25 |
gr.Markdown(
|
26 |
"""
|
27 |
**Token Precedence:**
|
28 |
-
1. If a token is successfully loaded from
|
29 |
2. Otherwise, the token entered in the 'Enter API Token Directly' textbox will be used.
|
30 |
-
3. If a system-wide `HF_TOKEN` environment variable was already set when this app started, it might also be used if methods 1 & 2 don't yield a token.
|
31 |
"""
|
32 |
)
|
33 |
gr.Markdown("---")
|
34 |
gr.Markdown("### Method 1: Enter API Token Directly")
|
35 |
-
api_token_ui_input = gr.Textbox(
|
36 |
-
label="Hugging Face API Token (hf_xxx)",
|
37 |
-
|
38 |
-
|
39 |
-
info="Get from hf.co/settings/tokens. Needs 'write' access. This is used if image loading fails or is not used."
|
40 |
)
|
41 |
|
42 |
if KEYLOCK_DECODE_AVAILABLE:
|
43 |
gr.Markdown("---")
|
44 |
-
gr.Markdown("### Method 2: Load API Token from
|
45 |
with gr.Row():
|
46 |
keylock_image_input = gr.Image(
|
47 |
-
label="
|
48 |
-
|
49 |
-
image_mode="RGBA", # << TRY ADDING THIS. Use "RGB" if your images are RGB.
|
50 |
-
# If you're unsure, you can omit it first to see if the debug saving helps.
|
51 |
-
# sources=["upload"],
|
52 |
)
|
53 |
-
keylock_password_input = gr.Textbox(
|
54 |
-
|
55 |
-
|
56 |
-
placeholder="Password for image decryption"
|
57 |
-
)
|
58 |
-
keylock_decode_button = gr.Button("Load Token from Image", variant="secondary")
|
59 |
-
keylock_status_output = gr.Markdown(label="Image Decoding Status", value="Status will appear here...")
|
60 |
|
61 |
keylock_decode_button.click(
|
62 |
fn=load_token_from_image_and_set_env,
|
@@ -64,90 +57,188 @@ def main_ui():
|
|
64 |
outputs=[keylock_status_output]
|
65 |
)
|
66 |
else:
|
67 |
-
gr.Markdown(
|
68 |
-
"_(Image decoding feature (KeyLock-Decode) is disabled as the library could not be imported.)_"
|
69 |
-
)
|
70 |
|
|
|
|
|
|
|
|
|
71 |
# --- Main Application Tabs ---
|
72 |
with gr.Tabs():
|
73 |
with gr.TabItem("🚀 Create New Space"):
|
74 |
-
# ... (rest of the Create Space UI, inputs will use api_token_ui_input)
|
75 |
with gr.Row():
|
76 |
space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
|
77 |
owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
|
78 |
-
sdk_create_input = gr.Dropdown(
|
79 |
-
label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio",
|
80 |
-
info="Select the type of Space."
|
81 |
-
)
|
82 |
markdown_input_create = gr.Textbox(
|
83 |
label="Markdown File Structure & Content",
|
84 |
-
placeholder="""
|
85 |
-
followed by content, optionally in code blocks.
|
86 |
-
Example:
|
87 |
### File: app.py
|
88 |
# ```python
|
89 |
print("Hello World!")
|
90 |
# ```
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
92 |
lines=15, interactive=True,
|
93 |
-
info="Define files using '### File: path/to/your/file.ext'
|
94 |
)
|
95 |
create_btn = gr.Button("Create Space", variant="primary")
|
96 |
create_output_md = gr.Markdown(label="Result")
|
97 |
|
98 |
-
|
99 |
-
|
|
|
100 |
with gr.Row():
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
view_output_md = gr.Markdown(label="Files in Space")
|
105 |
-
|
106 |
-
with gr.TabItem("✏️ Update Space File"):
|
107 |
-
# ... (rest of the Update Space File UI, inputs will use api_token_ui_input)
|
108 |
with gr.Row():
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
)
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
)
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
)
|
123 |
-
update_btn = gr.Button("Update File", variant="primary")
|
124 |
-
update_output_md = gr.Markdown(label="Result")
|
125 |
|
126 |
-
# Event handlers
|
127 |
-
# Pass the UI token input to all backend functions.
|
128 |
-
# The backend's _get_api_token helper will decide the actual token to use.
|
129 |
create_btn.click(
|
130 |
fn=create_space,
|
131 |
inputs=[api_token_ui_input, space_name_create_input, owner_create_input, sdk_create_input, markdown_input_create],
|
132 |
outputs=create_output_md,
|
133 |
)
|
134 |
-
|
135 |
-
fn=view_space_files,
|
136 |
-
inputs=[api_token_ui_input, space_name_view_input, owner_view_input],
|
137 |
-
outputs=view_output_md,
|
138 |
-
)
|
139 |
-
update_btn.click(
|
140 |
-
fn=update_space_file,
|
141 |
-
inputs=[
|
142 |
-
api_token_ui_input,
|
143 |
-
space_name_update_input,
|
144 |
-
owner_update_input,
|
145 |
-
file_path_update_input,
|
146 |
-
file_content_update_input,
|
147 |
-
commit_message_update_input,
|
148 |
-
],
|
149 |
-
outputs=update_output_md,
|
150 |
-
)
|
151 |
return demo
|
152 |
|
153 |
if __name__ == "__main__":
|
|
|
1 |
import gradio as gr
|
2 |
+
import os # For os.path.relpath, os.path.isfile, os.path.basename, os.sep
|
3 |
+
from pathlib import Path # For path manipulations if needed
|
4 |
from app_logic import (
|
5 |
create_space,
|
6 |
+
# view_space_files, # Optional: can be removed
|
7 |
update_space_file,
|
8 |
+
load_token_from_image_and_set_env,
|
9 |
+
KEYLOCK_DECODE_AVAILABLE,
|
10 |
+
get_space_local_clone_path,
|
11 |
+
read_file_from_local_path,
|
12 |
)
|
|
|
|
|
13 |
|
14 |
# Gradio interface
|
15 |
def main_ui():
|
|
|
17 |
gr.Markdown(
|
18 |
"""
|
19 |
# 🛠️ Hugging Face Space Builder
|
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 (Terminology updated) ---
|
26 |
with gr.Accordion("🔑 Authentication Methods", open=True):
|
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(
|
37 |
+
label="Hugging Face API Token (hf_xxx)", type="password",
|
38 |
+
placeholder="Enter your HF token OR load from KeyLock Wallet image below",
|
39 |
+
info="Get from hf.co/settings/tokens. Needs 'write' access."
|
|
|
40 |
)
|
41 |
|
42 |
if KEYLOCK_DECODE_AVAILABLE:
|
43 |
gr.Markdown("---")
|
44 |
+
gr.Markdown("### Method 2: Load API Token from KeyLock Wallet Image")
|
45 |
with gr.Row():
|
46 |
keylock_image_input = gr.Image(
|
47 |
+
label="KeyLock Wallet Image (PNG containing HF_TOKEN)", type="pil",
|
48 |
+
image_mode="RGBA", # Recommended
|
|
|
|
|
|
|
49 |
)
|
50 |
+
keylock_password_input = gr.Textbox(label="Image Password", type="password")
|
51 |
+
keylock_decode_button = gr.Button("Load Token from Wallet Image", variant="secondary")
|
52 |
+
keylock_status_output = gr.Markdown(label="Wallet Image Decoding Status", value="Status will appear here.")
|
|
|
|
|
|
|
|
|
53 |
|
54 |
keylock_decode_button.click(
|
55 |
fn=load_token_from_image_and_set_env,
|
|
|
57 |
outputs=[keylock_status_output]
|
58 |
)
|
59 |
else:
|
60 |
+
gr.Markdown("_(KeyLock Wallet image decoding is disabled as the library could not be imported.)_")
|
|
|
|
|
61 |
|
62 |
+
# --- State for File Browser/Editor ---
|
63 |
+
current_clone_root_path_state = gr.State(None)
|
64 |
+
current_editing_file_relative_path_state = gr.State(None)
|
65 |
+
|
66 |
# --- Main Application Tabs ---
|
67 |
with gr.Tabs():
|
68 |
with gr.TabItem("🚀 Create New Space"):
|
|
|
69 |
with gr.Row():
|
70 |
space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
|
71 |
owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
|
72 |
+
sdk_create_input = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio")
|
|
|
|
|
|
|
73 |
markdown_input_create = gr.Textbox(
|
74 |
label="Markdown File Structure & Content",
|
75 |
+
placeholder="""Example:
|
|
|
|
|
76 |
### File: app.py
|
77 |
# ```python
|
78 |
print("Hello World!")
|
79 |
# ```
|
80 |
+
|
81 |
+
### File: README.md
|
82 |
+
# ```markdown
|
83 |
+
# My App
|
84 |
+
This is a README.
|
85 |
+
# ```""",
|
86 |
lines=15, interactive=True,
|
87 |
+
info="Define files using '### File: path/to/your/file.ext'."
|
88 |
)
|
89 |
create_btn = gr.Button("Create Space", variant="primary")
|
90 |
create_output_md = gr.Markdown(label="Result")
|
91 |
|
92 |
+
# --- Revamped "Browse & Edit Files" Tab ---
|
93 |
+
with gr.TabItem("📂 Browse & Edit Files"):
|
94 |
+
gr.Markdown("Browse the file structure of a Space, view, and edit files.")
|
95 |
with gr.Row():
|
96 |
+
browse_space_name_input = gr.Textbox(label="Space Name", placeholder="my-target-app", scale=2)
|
97 |
+
browse_owner_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
|
98 |
+
|
|
|
|
|
|
|
|
|
99 |
with gr.Row():
|
100 |
+
browse_files_button = gr.Button("Load Space Files for Browsing", variant="secondary")
|
101 |
+
force_refresh_clone_checkbox = gr.Checkbox(label="Force Refresh Clone", value=False, info="Re-download the space.")
|
102 |
+
|
103 |
+
browse_status_output = gr.Markdown(label="Browsing Status", value="Status will appear here.")
|
104 |
+
|
105 |
+
gr.Markdown("---")
|
106 |
+
gr.Markdown("### File Explorer (Select a file to view/edit)")
|
107 |
+
file_explorer_component = gr.FileExplorer(label="Space File Tree", file_count="single", interactive=True, glob="**/*") # glob for all files
|
108 |
+
|
109 |
+
gr.Markdown("---")
|
110 |
+
gr.Markdown("### File Editor")
|
111 |
+
# Display for current file being edited
|
112 |
+
current_file_display_ro = gr.Textbox(label="Currently Editing File (Relative Path):", interactive=False, placeholder="No file selected.")
|
113 |
+
|
114 |
+
file_editor_textbox = gr.Textbox(
|
115 |
+
label="File Content (Editable)", lines=20, interactive=True,
|
116 |
+
placeholder="Select a file from the explorer above to view/edit its content."
|
117 |
)
|
118 |
+
edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g., Fix typo in README.md")
|
119 |
+
update_edited_file_button = gr.Button("Update File in Space", variant="primary")
|
120 |
+
edit_update_status_output = gr.Markdown(label="File Update Result", value="Result will appear here.")
|
121 |
+
|
122 |
+
# --- Event Handlers for Browse & Edit Tab ---
|
123 |
+
def handle_browse_space_files(token_from_ui, space_name, owner_name, force_refresh):
|
124 |
+
if not space_name:
|
125 |
+
return {
|
126 |
+
browse_status_output: gr.Markdown("Error: Space Name cannot be empty."),
|
127 |
+
file_explorer_component: gr.FileExplorer(value=None),
|
128 |
+
current_clone_root_path_state: None,
|
129 |
+
file_editor_textbox: gr.Textbox(value=""), # Clear editor
|
130 |
+
current_file_display_ro: gr.Textbox(value="No file selected."),
|
131 |
+
current_editing_file_relative_path_state: None
|
132 |
+
}
|
133 |
+
|
134 |
+
new_clone_root_path, error_msg = get_space_local_clone_path(token_from_ui, space_name, owner_name, force_refresh)
|
135 |
+
|
136 |
+
if error_msg:
|
137 |
+
return {
|
138 |
+
browse_status_output: gr.Markdown(f"Error: {error_msg}"),
|
139 |
+
file_explorer_component: gr.FileExplorer(value=None),
|
140 |
+
current_clone_root_path_state: None,
|
141 |
+
file_editor_textbox: gr.Textbox(value=""),
|
142 |
+
current_file_display_ro: gr.Textbox(value="No file selected."),
|
143 |
+
current_editing_file_relative_path_state: None
|
144 |
+
}
|
145 |
+
|
146 |
+
return {
|
147 |
+
browse_status_output: gr.Markdown(f"Space '{owner_name}/{space_name}' files loaded. Local clone: `{new_clone_root_path}`"),
|
148 |
+
file_explorer_component: gr.FileExplorer(value=new_clone_root_path),
|
149 |
+
current_clone_root_path_state: new_clone_root_path,
|
150 |
+
file_editor_textbox: gr.Textbox(value=""),
|
151 |
+
current_file_display_ro: gr.Textbox(value="No file selected."),
|
152 |
+
current_editing_file_relative_path_state: None
|
153 |
+
}
|
154 |
+
|
155 |
+
browse_files_button.click(
|
156 |
+
fn=handle_browse_space_files,
|
157 |
+
inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input, force_refresh_clone_checkbox],
|
158 |
+
outputs=[browse_status_output, file_explorer_component, current_clone_root_path_state, file_editor_textbox, current_file_display_ro, current_editing_file_relative_path_state]
|
159 |
+
)
|
160 |
+
|
161 |
+
def handle_file_selected_in_explorer(selected_file_abs_path_evt: gr.SelectData, clone_root_path_from_state):
|
162 |
+
if not selected_file_abs_path_evt or not selected_file_abs_path_evt.value:
|
163 |
+
return {
|
164 |
+
file_editor_textbox: gr.Textbox(value=""),
|
165 |
+
current_file_display_ro: gr.Textbox(value="No file selected."),
|
166 |
+
current_editing_file_relative_path_state: None,
|
167 |
+
browse_status_output: gr.Markdown("File selection cleared or invalid.")
|
168 |
+
}
|
169 |
+
|
170 |
+
selected_file_abs_path = selected_file_abs_path_evt.value[0] # FileExplorer with file_count="single" returns a list of one item
|
171 |
+
|
172 |
+
if not clone_root_path_from_state:
|
173 |
+
return {
|
174 |
+
file_editor_textbox: gr.Textbox(value=""),
|
175 |
+
current_file_display_ro: gr.Textbox(value="Error: Clone root path not set."),
|
176 |
+
current_editing_file_relative_path_state: None,
|
177 |
+
browse_status_output: gr.Markdown("Error: Clone root path state is not set. Please load space files first.")
|
178 |
+
}
|
179 |
+
|
180 |
+
if not os.path.isfile(selected_file_abs_path):
|
181 |
+
return {
|
182 |
+
file_editor_textbox: gr.Textbox(value=""),
|
183 |
+
current_file_display_ro: gr.Textbox(value="Selected item is not a file."),
|
184 |
+
current_editing_file_relative_path_state: None,
|
185 |
+
browse_status_output: gr.Markdown(f"'{os.path.basename(selected_file_abs_path)}' is a directory. Please select a file.")
|
186 |
+
}
|
187 |
+
|
188 |
+
content, error_msg = read_file_from_local_path(selected_file_abs_path)
|
189 |
+
|
190 |
+
if error_msg:
|
191 |
+
return {
|
192 |
+
file_editor_textbox: gr.Textbox(value=f"Error reading file: {error_msg}"),
|
193 |
+
current_file_display_ro: gr.Textbox(value="Error reading file."),
|
194 |
+
current_editing_file_relative_path_state: None,
|
195 |
+
browse_status_output: gr.Markdown(f"Error loading content for {os.path.basename(selected_file_abs_path)}.")
|
196 |
+
}
|
197 |
+
|
198 |
+
try:
|
199 |
+
relative_path = os.path.relpath(selected_file_abs_path, start=clone_root_path_from_state)
|
200 |
+
relative_path = relative_path.replace(os.sep, '/') # Normalize to forward slashes for HF Hub
|
201 |
+
except ValueError as e:
|
202 |
+
return {
|
203 |
+
file_editor_textbox: gr.Textbox(value=f"Error: Could not determine file's relative path. {e}"),
|
204 |
+
current_file_display_ro: gr.Textbox(value="Error: Path calculation failed."),
|
205 |
+
current_editing_file_relative_path_state: None,
|
206 |
+
browse_status_output: gr.Markdown("Error: File path calculation issue.")
|
207 |
+
}
|
208 |
+
|
209 |
+
return {
|
210 |
+
file_editor_textbox: gr.Textbox(value=content),
|
211 |
+
current_file_display_ro: gr.Textbox(value=relative_path),
|
212 |
+
current_editing_file_relative_path_state: relative_path,
|
213 |
+
browse_status_output: gr.Markdown(f"Loaded content for: {relative_path}")
|
214 |
+
}
|
215 |
+
|
216 |
+
file_explorer_component.select( # .select() is correct for FileExplorer
|
217 |
+
fn=handle_file_selected_in_explorer,
|
218 |
+
inputs=[current_clone_root_path_state],
|
219 |
+
outputs=[file_editor_textbox, current_file_display_ro, current_editing_file_relative_path_state, browse_status_output]
|
220 |
)
|
221 |
+
|
222 |
+
update_edited_file_button.click(
|
223 |
+
fn=update_space_file,
|
224 |
+
inputs=[
|
225 |
+
api_token_ui_input,
|
226 |
+
browse_space_name_input,
|
227 |
+
browse_owner_input,
|
228 |
+
current_editing_file_relative_path_state,
|
229 |
+
file_editor_textbox,
|
230 |
+
edit_commit_message_input
|
231 |
+
],
|
232 |
+
outputs=[edit_update_status_output]
|
233 |
)
|
|
|
|
|
234 |
|
235 |
+
# --- Event handlers for Create Space Tab (unchanged) ---
|
|
|
|
|
236 |
create_btn.click(
|
237 |
fn=create_space,
|
238 |
inputs=[api_token_ui_input, space_name_create_input, owner_create_input, sdk_create_input, markdown_input_create],
|
239 |
outputs=create_output_md,
|
240 |
)
|
241 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
return demo
|
243 |
|
244 |
if __name__ == "__main__":
|