Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,14 +1,12 @@
|
|
1 |
import gradio as gr
|
2 |
-
|
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 |
-
|
11 |
-
|
12 |
)
|
13 |
|
14 |
# Gradio interface
|
@@ -22,7 +20,7 @@ def main_ui():
|
|
22 |
"""
|
23 |
)
|
24 |
|
25 |
-
# --- Authentication Section (
|
26 |
with gr.Accordion("🔑 Authentication Methods", open=True):
|
27 |
gr.Markdown(
|
28 |
"""
|
@@ -33,190 +31,134 @@ def main_ui():
|
|
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
|
53 |
-
|
54 |
-
keylock_decode_button.click(
|
55 |
-
fn=load_token_from_image_and_set_env,
|
56 |
-
inputs=[keylock_image_input, keylock_password_input],
|
57 |
-
outputs=[keylock_status_output]
|
58 |
-
)
|
59 |
else:
|
60 |
-
gr.Markdown("_(KeyLock Wallet image decoding
|
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 |
-
|
93 |
-
|
94 |
-
|
|
|
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 |
-
|
100 |
-
|
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("###
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
117 |
)
|
118 |
-
edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g.,
|
119 |
update_edited_file_button = gr.Button("Update File in Space", variant="primary")
|
120 |
-
edit_update_status_output = gr.Markdown(label="File Update Result"
|
121 |
|
122 |
-
# --- Event Handlers for Browse & Edit Tab ---
|
123 |
-
def
|
124 |
if not space_name:
|
125 |
return {
|
126 |
browse_status_output: gr.Markdown("Error: Space Name cannot be empty."),
|
127 |
-
|
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 |
-
|
135 |
|
136 |
-
if error_msg:
|
137 |
return {
|
138 |
browse_status_output: gr.Markdown(f"Error: {error_msg}"),
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
|
|
|
|
|
|
144 |
}
|
145 |
|
146 |
return {
|
147 |
-
browse_status_output: gr.Markdown(f"
|
148 |
-
|
149 |
-
|
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 |
-
|
156 |
-
fn=
|
157 |
-
inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input
|
158 |
-
outputs=[browse_status_output,
|
159 |
)
|
160 |
|
161 |
-
def
|
162 |
-
if not
|
163 |
-
|
164 |
-
|
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 |
-
|
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
|
181 |
-
|
182 |
-
file_editor_textbox: gr.Textbox(value=""),
|
183 |
-
|
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 =
|
189 |
|
190 |
if error_msg:
|
191 |
return {
|
192 |
-
file_editor_textbox: gr.Textbox(value=f"Error
|
193 |
-
|
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 |
-
|
212 |
-
current_editing_file_relative_path_state: relative_path,
|
213 |
-
browse_status_output: gr.Markdown(f"Loaded content for: {relative_path}")
|
214 |
}
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
|
|
220 |
)
|
221 |
|
222 |
update_edited_file_button.click(
|
@@ -225,20 +167,12 @@ This is a README.
|
|
225 |
api_token_ui_input,
|
226 |
browse_space_name_input,
|
227 |
browse_owner_input,
|
228 |
-
|
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__":
|
|
|
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, # New
|
9 |
+
get_space_file_content, # New
|
10 |
)
|
11 |
|
12 |
# Gradio interface
|
|
|
20 |
"""
|
21 |
)
|
22 |
|
23 |
+
# --- Authentication Section (Unchanged) ---
|
24 |
with gr.Accordion("🔑 Authentication Methods", open=True):
|
25 |
gr.Markdown(
|
26 |
"""
|
|
|
31 |
)
|
32 |
gr.Markdown("---")
|
33 |
gr.Markdown("### Method 1: Enter API Token Directly")
|
34 |
+
api_token_ui_input = gr.Textbox(label="Hugging Face API Token (hf_xxx)", type="password", placeholder="Enter token OR load from Wallet image")
|
|
|
|
|
|
|
|
|
|
|
35 |
if KEYLOCK_DECODE_AVAILABLE:
|
36 |
gr.Markdown("---")
|
37 |
gr.Markdown("### Method 2: Load API Token from KeyLock Wallet Image")
|
38 |
with gr.Row():
|
39 |
+
keylock_image_input = gr.Image(label="KeyLock Wallet Image (PNG)", type="pil", image_mode="RGBA")
|
|
|
|
|
|
|
40 |
keylock_password_input = gr.Textbox(label="Image Password", type="password")
|
41 |
keylock_decode_button = gr.Button("Load Token from Wallet Image", variant="secondary")
|
42 |
+
keylock_status_output = gr.Markdown(label="Wallet Image Decoding Status", value="Status...")
|
43 |
+
keylock_decode_button.click(load_token_from_image_and_set_env, [keylock_image_input, keylock_password_input], [keylock_status_output])
|
|
|
|
|
|
|
|
|
|
|
44 |
else:
|
45 |
+
gr.Markdown("_(KeyLock Wallet image decoding disabled: library not found.)_")
|
46 |
|
|
|
|
|
|
|
|
|
47 |
# --- Main Application Tabs ---
|
48 |
with gr.Tabs():
|
49 |
with gr.TabItem("🚀 Create New Space"):
|
50 |
+
# (Create Space UI Unchanged)
|
51 |
with gr.Row():
|
52 |
space_name_create_input = gr.Textbox(label="Space Name", placeholder="my-awesome-app (no slashes)", scale=2)
|
53 |
owner_create_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank for your HF username", scale=1)
|
54 |
sdk_create_input = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio")
|
55 |
+
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
create_btn = gr.Button("Create Space", variant="primary")
|
57 |
create_output_md = gr.Markdown(label="Result")
|
58 |
+
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)
|
59 |
|
60 |
+
|
61 |
+
# --- "Browse & Edit Files" Tab (Hub-based) ---
|
62 |
+
with gr.TabItem("📂 Browse & Edit Space Files"):
|
63 |
+
gr.Markdown("Browse, view, and edit files directly on a Hugging Face Space.")
|
64 |
with gr.Row():
|
65 |
browse_space_name_input = gr.Textbox(label="Space Name", placeholder="my-target-app", scale=2)
|
66 |
browse_owner_input = gr.Textbox(label="Owner Username/Org", placeholder="Leave blank if it's your space", scale=1)
|
67 |
|
68 |
+
browse_load_files_button = gr.Button("Load Files List from Space", variant="secondary")
|
69 |
+
browse_status_output = gr.Markdown(label="File List Status")
|
|
|
|
|
|
|
70 |
|
71 |
gr.Markdown("---")
|
72 |
+
gr.Markdown("### Select File to View/Edit")
|
73 |
+
# Using Radio for file list. Could be Dropdown for many files.
|
74 |
+
# `choices` will be updated dynamically.
|
75 |
+
file_selector_radio = gr.Radio(
|
76 |
+
label="Files in Space",
|
77 |
+
choices=[],
|
78 |
+
interactive=True,
|
79 |
+
info="Select a file to load its content below."
|
80 |
+
)
|
81 |
|
82 |
gr.Markdown("---")
|
83 |
gr.Markdown("### File Editor")
|
|
|
|
|
|
|
84 |
file_editor_textbox = gr.Textbox(
|
85 |
label="File Content (Editable)", lines=20, interactive=True,
|
86 |
+
placeholder="Select a file from the list above to view/edit its content."
|
87 |
)
|
88 |
+
edit_commit_message_input = gr.Textbox(label="Commit Message for Update", placeholder="e.g., Update app.py content")
|
89 |
update_edited_file_button = gr.Button("Update File in Space", variant="primary")
|
90 |
+
edit_update_status_output = gr.Markdown(label="File Update Result")
|
91 |
|
92 |
+
# --- Event Handlers for Browse & Edit Tab (Hub-based) ---
|
93 |
+
def handle_load_space_files_list(token_from_ui, space_name, owner_name):
|
94 |
if not space_name:
|
95 |
return {
|
96 |
browse_status_output: gr.Markdown("Error: Space Name cannot be empty."),
|
97 |
+
file_selector_radio: gr.Radio(choices=[], value=None), # Clear radio
|
|
|
98 |
file_editor_textbox: gr.Textbox(value=""), # Clear editor
|
|
|
|
|
99 |
}
|
100 |
|
101 |
+
files_list, error_msg = list_space_files_for_browsing(token_from_ui, space_name, owner_name)
|
102 |
|
103 |
+
if error_msg and files_list is None: # Indicates a hard error
|
104 |
return {
|
105 |
browse_status_output: gr.Markdown(f"Error: {error_msg}"),
|
106 |
+
file_selector_radio: gr.Radio(choices=[], value=None),
|
107 |
+
file_editor_textbox: gr.Textbox(value=""),
|
108 |
+
}
|
109 |
+
if error_msg and not files_list: # Info message like "no files found"
|
110 |
+
return {
|
111 |
+
browse_status_output: gr.Markdown(error_msg), # Show "No files found"
|
112 |
+
file_selector_radio: gr.Radio(choices=[], value=None),
|
113 |
+
file_editor_textbox: gr.Textbox(value=""),
|
114 |
}
|
115 |
|
116 |
return {
|
117 |
+
browse_status_output: gr.Markdown(f"Files loaded for '{owner_name}/{space_name}'. Select a file to edit."),
|
118 |
+
file_selector_radio: gr.Radio(choices=files_list, value=None, label=f"Files in {owner_name}/{space_name}"),
|
119 |
+
file_editor_textbox: gr.Textbox(value=""), # Clear editor on new list load
|
|
|
|
|
|
|
120 |
}
|
121 |
|
122 |
+
browse_load_files_button.click(
|
123 |
+
fn=handle_load_space_files_list,
|
124 |
+
inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input],
|
125 |
+
outputs=[browse_status_output, file_selector_radio, file_editor_textbox]
|
126 |
)
|
127 |
|
128 |
+
def handle_file_selected_for_editing(token_from_ui, space_name, owner_name, selected_filepath_evt: gr.SelectData):
|
129 |
+
if not selected_filepath_evt or not selected_filepath_evt.value:
|
130 |
+
# This might happen if the radio is cleared or has no selection
|
131 |
+
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
file_editor_textbox: gr.Textbox(value=""),
|
133 |
+
browse_status_output: gr.Markdown("No file selected or selection cleared.")
|
|
|
|
|
134 |
}
|
135 |
+
|
136 |
+
selected_filepath = selected_filepath_evt.value # The value of the selected radio button
|
137 |
|
138 |
+
if not space_name: # Should not happen if file list is populated
|
139 |
+
return {
|
140 |
+
file_editor_textbox: gr.Textbox(value="Error: Space name is missing."),
|
141 |
+
browse_status_output: gr.Markdown("Error: Space context lost. Please reload file list.")
|
|
|
|
|
142 |
}
|
143 |
+
|
144 |
+
content, error_msg = get_space_file_content(token_from_ui, space_name, owner_name, selected_filepath)
|
145 |
|
146 |
if error_msg:
|
147 |
return {
|
148 |
+
file_editor_textbox: gr.Textbox(value=f"Error loading file content: {error_msg}"),
|
149 |
+
browse_status_output: gr.Markdown(f"Failed to load '{selected_filepath}': {error_msg}")
|
|
|
|
|
150 |
}
|
151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
return {
|
153 |
file_editor_textbox: gr.Textbox(value=content),
|
154 |
+
browse_status_output: gr.Markdown(f"Content loaded for: {selected_filepath}")
|
|
|
|
|
155 |
}
|
156 |
+
|
157 |
+
# Use .select event for gr.Radio
|
158 |
+
file_selector_radio.select(
|
159 |
+
fn=handle_file_selected_for_editing,
|
160 |
+
inputs=[api_token_ui_input, browse_space_name_input, browse_owner_input], # Pass space context again
|
161 |
+
outputs=[file_editor_textbox, browse_status_output]
|
162 |
)
|
163 |
|
164 |
update_edited_file_button.click(
|
|
|
167 |
api_token_ui_input,
|
168 |
browse_space_name_input,
|
169 |
browse_owner_input,
|
170 |
+
file_selector_radio, # Pass the selected file path from the radio
|
171 |
file_editor_textbox,
|
172 |
edit_commit_message_input
|
173 |
],
|
174 |
outputs=[edit_update_status_output]
|
175 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
return demo
|
177 |
|
178 |
if __name__ == "__main__":
|