broadfield-dev commited on
Commit
0ba847f
·
verified ·
1 Parent(s): c0f7c5e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -139
app.py CHANGED
@@ -1,14 +1,12 @@
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
@@ -22,7 +20,7 @@ def main_ui():
22
  """
23
  )
24
 
25
- # --- Authentication Section (Terminology updated) ---
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 will appear here.")
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 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(
@@ -225,20 +167,12 @@ This is a README.
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__":
 
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__":