Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -3,44 +3,101 @@ import re
|
|
3 |
import json
|
4 |
import os
|
5 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
from build_logic import (
|
8 |
-
create_space as build_logic_create_space,
|
9 |
-
_get_api_token as build_logic_get_api_token,
|
10 |
-
whoami as build_logic_whoami,
|
11 |
-
list_space_files_for_browsing,
|
12 |
-
get_space_repository_info,
|
13 |
-
get_space_file_content,
|
14 |
-
update_space_file,
|
15 |
-
parse_markdown as build_logic_parse_markdown,
|
16 |
-
delete_space_file as build_logic_delete_space_file,
|
17 |
-
get_space_runtime_status
|
18 |
-
)
|
19 |
-
print("build_logic.py loaded successfully.")
|
20 |
-
|
21 |
-
from model_logic import (
|
22 |
-
get_available_providers,
|
23 |
-
get_models_for_provider,
|
24 |
-
get_default_model_for_provider,
|
25 |
-
get_model_id_from_display_name,
|
26 |
-
generate_stream
|
27 |
-
)
|
28 |
-
print("model_logic.py loaded successfully.")
|
29 |
-
|
30 |
-
bbb = chr(96) * 3
|
31 |
parsed_code_blocks_state_cache = []
|
32 |
BOT_ROLE_NAME = "assistant"
|
33 |
|
34 |
-
DEFAULT_SYSTEM_PROMPT = f"""You are an expert AI programmer. Your role is to generate code and file structures based on user requests, or to modify existing code provided by the user.
|
|
|
|
|
35 |
When you provide NEW code for a file, or MODIFIED code for an existing file, use the following format exactly:
|
36 |
### File: path/to/filename.ext
|
37 |
(You can add a short, optional, parenthesized description after the filename on the SAME line)
|
38 |
{bbb}language
|
39 |
# Your full code here
|
40 |
{bbb}
|
|
|
41 |
If the file is binary, or you cannot show its content, use this format:
|
42 |
### File: path/to/binaryfile.ext
|
43 |
[Binary file - approximate_size bytes]
|
|
|
44 |
When you provide a project file structure, use this format:
|
45 |
## File Structure
|
46 |
{bbb}
|
@@ -49,16 +106,34 @@ When you provide a project file structure, use this format:
|
|
49 |
π subfolder
|
50 |
π file2.js
|
51 |
{bbb}
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
"""
|
61 |
|
|
|
62 |
def escape_html_for_markdown(text):
|
63 |
if not isinstance(text, str): return ""
|
64 |
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
@@ -108,6 +183,7 @@ def _clean_filename(filename_line_content):
|
|
108 |
return filename_candidate if filename_candidate else text.strip()
|
109 |
|
110 |
def _parse_chat_stream_logic(latest_bot_message_content, existing_files_state=None):
|
|
|
111 |
latest_blocks_dict = {}
|
112 |
if existing_files_state:
|
113 |
for block in existing_files_state:
|
@@ -124,7 +200,7 @@ def _parse_chat_stream_logic(latest_bot_message_content, existing_files_state=No
|
|
124 |
if structure_match:
|
125 |
latest_blocks_dict["File Structure (original)"] = {"filename": "File Structure (original)", "language": structure_match.group("struct_lang") or "plaintext", "code": structure_match.group("structure_code").strip(), "is_binary": False, "is_structure_block": True}
|
126 |
else:
|
127 |
-
existing_structure_block = next((b for b in
|
128 |
if existing_structure_block:
|
129 |
latest_blocks_dict["File Structure (original)"] = existing_structure_block.copy()
|
130 |
|
@@ -142,19 +218,16 @@ def _parse_chat_stream_logic(latest_bot_message_content, existing_files_state=No
|
|
142 |
current_message_file_blocks[filename] = item_data
|
143 |
|
144 |
latest_blocks_dict.update(current_message_file_blocks)
|
145 |
-
|
146 |
current_parsed_blocks = list(latest_blocks_dict.values())
|
147 |
current_parsed_blocks.sort(key=lambda b: (0, b["filename"]) if b.get("is_structure_block") else (1, b["filename"]))
|
148 |
-
|
149 |
results["parsed_code_blocks"] = current_parsed_blocks
|
150 |
results["default_selected_filenames"] = [b["filename"] for b in current_parsed_blocks if not b.get("is_structure_block")]
|
151 |
return results
|
152 |
|
153 |
def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_blocks_for_export):
|
154 |
results = {"output_str": "", "error_message": None, "download_filepath": None}
|
155 |
-
exportable_blocks_content = [b for b in parsed_blocks_for_export if not b.get("is_structure_block") and not b.get("is_binary") and not (b.get("code", "").startswith("[Error loading content:"
|
156 |
binary_blocks_content = [b for b in parsed_blocks_for_export if b.get("is_binary") or b.get("code", "").startswith("[Binary or Skipped file]")]
|
157 |
-
|
158 |
all_filenames_in_state = sorted(list(set(b["filename"] for b in parsed_blocks_for_export if not b.get("is_structure_block"))))
|
159 |
|
160 |
if not all_filenames_in_state and not any(b.get("is_structure_block") for b in parsed_blocks_for_export):
|
@@ -166,7 +239,6 @@ def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_bl
|
|
166 |
return results
|
167 |
|
168 |
output_lines = [f"# Space: {space_line_name_for_md}"]
|
169 |
-
|
170 |
structure_block = next((b for b in parsed_blocks_for_export if b.get("is_structure_block")), None)
|
171 |
if structure_block:
|
172 |
output_lines.extend(["## File Structure", bbb, structure_block["code"].strip(), bbb, ""])
|
@@ -177,25 +249,14 @@ def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_bl
|
|
177 |
output_lines.extend([bbb, ""])
|
178 |
|
179 |
output_lines.append("Below are the contents of all files in the space:\n")
|
180 |
-
|
181 |
-
|
182 |
-
files_to_export_content = []
|
183 |
-
if selected_filenames:
|
184 |
-
files_to_export_content = [b for b in exportable_blocks_content if b["filename"] in selected_filenames]
|
185 |
-
else:
|
186 |
-
files_to_export_content = exportable_blocks_content
|
187 |
-
|
188 |
-
binary_error_blocks_to_export = []
|
189 |
-
if selected_filenames:
|
190 |
-
binary_error_blocks_to_export = [b for b in binary_blocks_content if b["filename"] in selected_filenames]
|
191 |
-
elif binary_blocks_content:
|
192 |
-
binary_error_blocks_to_export = binary_blocks_content
|
193 |
-
|
194 |
all_blocks_to_export_content = sorted(files_to_export_content + binary_error_blocks_to_export, key=lambda b: b["filename"])
|
195 |
|
|
|
196 |
for block in all_blocks_to_export_content:
|
197 |
output_lines.append(f"### File: {block['filename']}")
|
198 |
-
if block.get('is_binary') or block.get("code", "").startswith("[Binary file"
|
199 |
output_lines.append(block.get('code','[Binary or Skipped file]'))
|
200 |
else:
|
201 |
output_lines.extend([f"{bbb}{block.get('language', 'plaintext') or 'plaintext'}", block.get('code',''), bbb])
|
@@ -209,7 +270,9 @@ def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_bl
|
|
209 |
try:
|
210 |
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".md", encoding='utf-8') as tmpfile:
|
211 |
tmpfile.write(final_output_str); results["download_filepath"] = tmpfile.name
|
212 |
-
except Exception as e:
|
|
|
|
|
213 |
return results
|
214 |
|
215 |
def _convert_gr_history_to_api_messages(system_prompt, gr_history, current_user_message=None):
|
@@ -221,11 +284,9 @@ def _convert_gr_history_to_api_messages(system_prompt, gr_history, current_user_
|
|
221 |
return messages
|
222 |
|
223 |
def _generate_ui_outputs_from_cache(owner, space_name):
|
224 |
-
# This function only reads the global variable, does not need 'global' declaration to read.
|
225 |
-
# Added global for consistency, but technically reads don't require it.
|
226 |
global parsed_code_blocks_state_cache
|
227 |
preview_md_val = "*No files in cache to display.*"
|
228 |
-
formatted_md_val = f"# Space: {owner}/{space_name}\n## File Structure\n{bbb}\nπ Root\n{bbb}\n\n*No files in cache.*" if owner
|
229 |
download_file = None
|
230 |
|
231 |
if parsed_code_blocks_state_cache:
|
@@ -234,689 +295,330 @@ def _generate_ui_outputs_from_cache(owner, space_name):
|
|
234 |
preview_md_lines.append(f"\n----\n**File:** `{escape_html_for_markdown(block['filename'])}`")
|
235 |
if block.get('is_structure_block'): preview_md_lines.append(f" (Original File Structure from AI)\n")
|
236 |
elif block.get('is_binary'): preview_md_lines.append(f" (Binary File)\n")
|
237 |
-
|
238 |
-
else: preview_md_lines.append("\n")
|
239 |
|
240 |
content = block.get('code', '')
|
241 |
-
if block.get('is_binary') or content.startswith("["):
|
242 |
preview_md_lines.append(f"\n`{escape_html_for_markdown(content)}`\n")
|
243 |
-
elif block.get('is_structure_block'):
|
244 |
-
preview_md_lines.append(f"\n{bbb}{block.get('language', 'plaintext') or 'plaintext'}\n{content}\n{bbb}\n")
|
245 |
else:
|
246 |
preview_md_lines.append(f"\n{bbb}{block.get('language', 'plaintext') or 'plaintext'}\n{content}\n{bbb}\n")
|
247 |
-
|
248 |
preview_md_val = "\n".join(preview_md_lines)
|
249 |
space_line_name = f"{owner}/{space_name}" if owner and space_name else (owner or space_name or "your-space")
|
250 |
-
|
251 |
export_result = _export_selected_logic(None, space_line_name, parsed_code_blocks_state_cache)
|
252 |
formatted_md_val = export_result["output_str"]
|
253 |
download_file = export_result["download_filepath"]
|
254 |
|
255 |
return formatted_md_val, preview_md_val, gr.update(value=download_file, interactive=download_file is not None)
|
256 |
|
257 |
-
def handle_chat_submit(user_message, chat_history,
|
258 |
-
# This global declaration is needed because the function ASSIGNS to parsed_code_blocks_state_cache later.
|
259 |
-
# It must be at the very top of the function before any use or assignment.
|
260 |
global parsed_code_blocks_state_cache
|
261 |
_chat_msg_in = ""
|
262 |
_chat_hist = list(chat_history)
|
263 |
_status = "Initializing..."
|
264 |
_detected_files_update, _formatted_output_update, _download_btn_update = gr.update(), gr.update(), gr.update(interactive=False, value=None)
|
265 |
|
266 |
-
if user_message and _current_formatted_markdown:
|
267 |
-
try:
|
268 |
-
parsed_from_md = build_logic_parse_markdown(_current_formatted_markdown)
|
269 |
-
new_cache_state = []
|
270 |
-
# This line USES parsed_code_blocks_state_cache. It must come AFTER the global declaration.
|
271 |
-
existing_structure_block = next((b for b in parsed_code_blocks_state_cache if b.get("is_structure_block")), None)
|
272 |
-
if existing_structure_block:
|
273 |
-
new_cache_state.append(existing_structure_block.copy())
|
274 |
-
|
275 |
-
for f_info in parsed_from_md.get("files", []):
|
276 |
-
if f_info.get("path") and f_info["path"] != "File Structure (original)":
|
277 |
-
is_binary_repr = isinstance(f_info.get("content"), str) and (f_info["content"].startswith("[Binary file") or f_info["content"].startswith("[Error loading content:") or f_info["content"].startswith("[Binary or Skipped file]"))
|
278 |
-
found_existing = False
|
279 |
-
for i, block in enumerate(new_cache_state):
|
280 |
-
if block["filename"] == f_info["path"] and not block.get("is_structure_block"):
|
281 |
-
new_cache_state[i] = {
|
282 |
-
"filename": f_info["path"],
|
283 |
-
"code": f_info.get("content", ""),
|
284 |
-
"language": "binary" if is_binary_repr else _infer_lang_from_filename(f_info["path"]),
|
285 |
-
"is_binary": is_binary_repr,
|
286 |
-
"is_structure_block": False
|
287 |
-
}
|
288 |
-
found_existing = True
|
289 |
-
break
|
290 |
-
if not found_existing:
|
291 |
-
new_cache_state.append({
|
292 |
-
"filename": f_info["path"],
|
293 |
-
"code": f_info.get("content", ""),
|
294 |
-
"language": "binary" if is_binary_repr else _infer_lang_from_filename(f_info["path"]),
|
295 |
-
"is_binary": is_binary_repr,
|
296 |
-
"is_structure_block": False
|
297 |
-
})
|
298 |
-
new_cache_state.sort(key=lambda b: (0, b["filename"]) if b.get("is_structure_block") else (1, b["filename"]))
|
299 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache. This is why the global declaration is needed.
|
300 |
-
parsed_code_blocks_state_cache = new_cache_state
|
301 |
-
|
302 |
-
except Exception as e:
|
303 |
-
print(f"Error parsing formatted markdown before chat submit: {e}")
|
304 |
-
|
305 |
if not user_message.strip():
|
306 |
_status = "Cannot send an empty message."
|
307 |
yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update); return
|
308 |
-
|
|
|
309 |
yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update)
|
310 |
|
311 |
-
api_key_override = api_key_input
|
312 |
current_sys_prompt = system_prompt.strip() or DEFAULT_SYSTEM_PROMPT
|
|
|
|
|
|
|
313 |
|
314 |
-
|
315 |
-
# This block USES parsed_code_blocks_state_cache. It must come AFTER the global declaration.
|
316 |
-
if parsed_code_blocks_state_cache:
|
317 |
-
current_files_context = "\n\n## Current Files in Space\n"
|
318 |
-
for block in parsed_code_blocks_state_cache:
|
319 |
-
if block.get("is_structure_block"):
|
320 |
-
current_files_context += f"### File: {block['filename']}\n{bbb}\n{block['code']}\n{bbb}\n"
|
321 |
-
else:
|
322 |
-
current_files_context += f"### File: {block['filename']}\n"
|
323 |
-
if block.get("is_binary") or block.get("code", "").startswith("["):
|
324 |
-
current_files_context += f"{block['code']}\n"
|
325 |
-
else:
|
326 |
-
current_files_context += f"{bbb}{block.get('language', 'plaintext') or 'plaintext'}\n{block.get('code','')}\n{bbb}\n"
|
327 |
-
current_files_context += "\n"
|
328 |
-
|
329 |
-
user_message_with_context = user_message.strip()
|
330 |
-
if current_files_context.strip():
|
331 |
-
user_message_with_context = user_message_with_context + current_files_context + "\nBased on the current files above and our chat history, please provide updated file contents using the `### File: ...\n```...\n```\n` format for any files you are creating, modifying, or want to include in the final output. If you are providing a file structure, use the `## File Structure\n```\n...\n```\n` format. Omit files you want to delete from your response."
|
332 |
-
|
333 |
api_msgs = _convert_gr_history_to_api_messages(current_sys_prompt, _chat_hist[:-1], user_message_with_context)
|
334 |
|
335 |
try:
|
336 |
-
_status = f"Waiting for {model_select}
|
|
|
337 |
|
338 |
full_bot_response_content = ""
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
360 |
|
|
|
361 |
if parsing_res["error_message"]:
|
362 |
-
|
363 |
-
_detected_files_update = gr.Markdown(f"## Parsing Error\n`{escape_html_for_markdown(parsing_res['error_message'])}`")
|
364 |
else:
|
365 |
-
# REMOVE THE REDUNDANT GLOBAL DECLARATION HERE.
|
366 |
-
# global parsed_code_blocks_state_cache # Redundant global, but harmless. The one at the top is effective.
|
367 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
368 |
parsed_code_blocks_state_cache = parsing_res["parsed_code_blocks"]
|
|
|
|
|
369 |
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
if not error_during_stream:
|
374 |
-
_status = "AI response complete, but returned no content."
|
375 |
-
_formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
376 |
|
377 |
except Exception as e:
|
378 |
-
error_msg = f"An unexpected error occurred
|
379 |
-
print(f"
|
380 |
-
if _chat_hist
|
381 |
-
|
382 |
-
else:
|
383 |
-
_chat_hist.append((user_message, error_msg))
|
384 |
_status = error_msg
|
385 |
_formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
386 |
|
387 |
yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update)
|
388 |
|
389 |
def update_models_dropdown(provider_select):
|
390 |
-
if not provider_select:
|
391 |
-
return gr.update(choices=[], value=None)
|
392 |
models = get_models_for_provider(provider_select)
|
393 |
default_model = get_default_model_for_provider(provider_select)
|
394 |
-
|
395 |
-
selected_value = default_model
|
396 |
-
elif models:
|
397 |
-
selected_value = models[0]
|
398 |
-
else:
|
399 |
-
selected_value = None
|
400 |
return gr.update(choices=models, value=selected_value)
|
401 |
|
402 |
def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
403 |
-
# This global declaration is needed because the function ASSIGNS to parsed_code_blocks_state_cache later.
|
404 |
global parsed_code_blocks_state_cache
|
405 |
_formatted_md_val, _detected_preview_val, _status_val = "*Loading files...*", "*Loading files...*", f"Loading Space: {ui_owner_name}/{ui_space_name}..."
|
406 |
_file_browser_update, _iframe_html_update, _download_btn_update = gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False), gr.update(interactive=False, value=None)
|
407 |
-
_build_status_clear, _edit_status_clear, _runtime_status_clear = "*Build status
|
408 |
_chat_history_clear = []
|
|
|
|
|
409 |
|
410 |
-
|
411 |
-
|
412 |
-
owner_to_use, updated_owner_name_val = ui_owner_name, ui_owner_name
|
413 |
-
error_occurred = False
|
414 |
-
|
415 |
if not owner_to_use:
|
416 |
-
token,
|
417 |
-
if
|
418 |
-
_status_val = f"Error: {
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
|
429 |
if not owner_to_use or not ui_space_name:
|
430 |
-
|
431 |
-
|
432 |
-
if error_occurred:
|
433 |
-
yield (f"*Error: {_status_val}*", f"*Error: {_status_val}*", _status_val, _file_browser_update, updated_owner_name_val, ui_space_name, _iframe_html_update, _download_btn_update, _build_status_clear, _edit_status_clear, _runtime_status_clear, _chat_history_clear)
|
434 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
435 |
-
parsed_code_blocks_state_cache = []
|
436 |
-
return
|
437 |
|
438 |
-
|
|
|
|
|
|
|
|
|
|
|
439 |
|
440 |
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_to_use.lower()).strip('-') or 'owner'
|
441 |
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name.lower()).strip('-') or 'space'
|
442 |
-
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if
|
443 |
-
|
444 |
-
|
445 |
-
if err_list_files and not file_list:
|
446 |
-
_status_val = f"File List Error: {err_list_files}"
|
447 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
448 |
-
parsed_code_blocks_state_cache = []
|
449 |
-
_formatted_md_val, _detected_preview_val, _download_btn_update = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
450 |
-
_file_browser_update = gr.update(visible=True, choices=[], value="Error loading files")
|
451 |
-
yield (f"*Error: {err_list_files}*", "*Error loading files*", _status_val, _file_browser_update, updated_owner_name_val, ui_space_name, _iframe_html_update, _download_btn_update, _build_status_clear, _edit_status_clear, _runtime_status_clear, _chat_history_clear)
|
452 |
-
return
|
453 |
-
|
454 |
-
if not file_list:
|
455 |
-
_status_val = f"Loaded Space: {owner_to_use}/{ui_space_name}. No files found ({err_list_files or 'Repository is empty'})."
|
456 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
457 |
-
parsed_code_blocks_state_cache = []
|
458 |
-
_formatted_md_val, _detected_preview_val, _download_btn_update = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
459 |
-
_file_browser_update = gr.update(visible=True, choices=[], value="No files found")
|
460 |
-
yield (_formatted_md_val, _detected_preview_val, _status_val, _file_browser_update, updated_owner_name_val, ui_space_name, _iframe_html_update, _download_btn_update, _build_status_clear, _edit_status_clear, _runtime_status_clear, _chat_history_clear)
|
461 |
-
return
|
462 |
-
|
463 |
-
loaded_files_for_cache = []
|
464 |
-
_status_val = f"Loading {len(file_list)} files from {owner_to_use}/{ui_space_name} (SDK: {sdk_for_iframe or 'unknown'})...";
|
465 |
-
yield (_formatted_md_val, _detected_preview_val, _status_val, gr.update(visible=True, choices=sorted(file_list or []), value=None), updated_owner_name_val, ui_space_name, _iframe_html_update, _download_btn_update, _build_status_clear, _edit_status_clear, _runtime_status_clear, _chat_history_clear)
|
466 |
|
|
|
467 |
for file_path in file_list:
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
loaded_files_for_cache.append({"filename": file_path, "code": f"[Error loading content: {err_get}]", "language": _infer_lang_from_filename(file_path), "is_binary": False, "is_structure_block": False})
|
483 |
-
print(f"Error loading {file_path}: {err_get}");
|
484 |
-
continue
|
485 |
-
loaded_files_for_cache.append({"filename": file_path, "code": content, "language": _infer_lang_from_filename(file_path), "is_binary": False, "is_structure_block": False})
|
486 |
-
except Exception as content_ex:
|
487 |
-
loaded_files_for_cache.append({"filename": file_path, "code": f"[Unexpected error loading content: {content_ex}]", "language": _infer_lang_from_filename(file_path), "is_binary": False, "is_structure_block": False})
|
488 |
-
print(f"Unexpected error loading {file_path}: {content_ex}")
|
489 |
-
continue
|
490 |
-
|
491 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
492 |
-
parsed_code_blocks_state_cache = loaded_files_for_cache
|
493 |
-
_formatted_md_val, _detected_preview_val, _download_btn_update = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
494 |
-
_status_val = f"Successfully loaded Space: {owner_to_use}/{ui_space_name}. Markdown ready. {len(file_list)} files listed."
|
495 |
-
_file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value=None)
|
496 |
-
|
497 |
-
yield (_formatted_md_val, _detected_preview_val, _status_val, _file_browser_update, updated_owner_name_val, ui_space_name, _iframe_html_update, _download_btn_update, _build_status_clear, _edit_status_clear, _runtime_status_clear, _chat_history_clear)
|
498 |
-
|
499 |
-
def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, space_sdk_ui, formatted_markdown_content):
|
500 |
-
# This global declaration is needed because the function ASSIGNS to parsed_code_blocks_state_cache later.
|
501 |
-
global parsed_code_blocks_state_cache
|
502 |
_build_status, _iframe_html, _file_browser_update = "Starting space build process...", gr.update(value=None, visible=False), gr.update(visible=False, choices=[], value=None)
|
503 |
yield _build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part)
|
504 |
-
if not ui_space_name_part or "/" in ui_space_name_part:
|
505 |
-
|
506 |
-
|
507 |
-
token_for_whoami, token_err = build_logic_get_api_token(hf_api_key_ui)
|
508 |
-
if token_err: _build_status = f"Build Error: {token_err}"; yield _build_status, _iframe_html, _file_browser_update, gr.update(), gr.update(); return
|
509 |
-
if token_for_whoami:
|
510 |
-
try:
|
511 |
-
user_info = build_logic_whoami(token=token_for_whoami)
|
512 |
-
final_owner_for_build = user_info['name'] if user_info and 'name' in user_info else final_owner_for_build
|
513 |
-
if not final_owner_for_build: _build_status += "\n(Warning: Could not auto-detect owner from token for build. Please specify.)"
|
514 |
-
except Exception as e: _build_status += f"\n(Warning: Could not auto-detect owner for build: {e}. Please specify.)"
|
515 |
-
else: _build_status += "\n(Warning: Owner not specified and no token to auto-detect for build. Please specify owner or provide a token.)"
|
516 |
-
|
517 |
-
if not final_owner_for_build: _build_status = "Build Error: HF Owner Name could not be determined. Please specify it."; yield _build_status, _iframe_html, _file_browser_update, gr.update(), gr.update(); return
|
518 |
-
|
519 |
-
try:
|
520 |
-
parsed_from_md_for_build = build_logic_parse_markdown(formatted_markdown_content)
|
521 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
522 |
-
parsed_code_blocks_state_cache = []
|
523 |
-
if parsed_from_md_for_build.get("owner_md"):
|
524 |
-
ui_owner_name_part = parsed_from_md_for_build["owner_md"]
|
525 |
-
if parsed_from_md_for_build.get("repo_name_md"):
|
526 |
-
ui_space_name_part = parsed_from_md_for_build["repo_name_md"]
|
527 |
-
|
528 |
-
structure_block_md = next((f for f in parsed_from_md_for_build.get("files", []) if f.get("path") == "File Structure (original)"), None)
|
529 |
-
if structure_block_md:
|
530 |
-
# This line modifies the global list.
|
531 |
-
parsed_code_blocks_state_cache.append({
|
532 |
-
"filename": structure_block_md["path"],
|
533 |
-
"code": structure_block_md["content"],
|
534 |
-
"language": "plaintext",
|
535 |
-
"is_binary": False,
|
536 |
-
"is_structure_block": True
|
537 |
-
})
|
538 |
-
|
539 |
-
for f_info in parsed_from_md_for_build.get("files", []):
|
540 |
-
if f_info.get("path") and f_info["path"] != "File Structure (original)":
|
541 |
-
is_binary_repr = isinstance(f_info.get("content"), str) and (f_info["content"].startswith("[Binary file") or f_info["content"].startswith("[Error loading content:") or f_info["content"].startswith("[Binary or Skipped file]"))
|
542 |
-
# This line modifies the global list.
|
543 |
-
parsed_code_blocks_state_cache.append({
|
544 |
-
"filename": f_info["path"],
|
545 |
-
"code": f_info.get("content", ""),
|
546 |
-
"language": "binary" if is_binary_repr else _infer_lang_from_filename(f_info["path"]),
|
547 |
-
"is_binary": is_binary_repr,
|
548 |
-
"is_structure_block": False
|
549 |
-
})
|
550 |
-
# This line modifies the global list.
|
551 |
-
parsed_code_blocks_state_cache.sort(key=lambda b: (0, b["filename"]) if b.get("is_structure_block") else (1, b["filename"]))
|
552 |
-
|
553 |
-
except Exception as e:
|
554 |
-
_build_status = f"Build Error: Failed to parse Markdown structure before building: {e}";
|
555 |
-
yield _build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part); return
|
556 |
|
557 |
-
result_message = build_logic_create_space(hf_api_key_ui, ui_space_name_part,
|
558 |
_build_status = f"Build Process: {result_message}"
|
559 |
|
560 |
-
owner_name_output = gr.update(value=ui_owner_name_part)
|
561 |
-
space_name_output = gr.update(value=ui_space_name_part)
|
562 |
-
|
563 |
if "Successfully" in result_message:
|
564 |
-
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', ui_owner_name_part.lower()).strip('-')
|
565 |
-
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name_part.lower()).strip('-')
|
566 |
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if space_sdk_ui == 'static' else '.hf.space'}"
|
567 |
-
_iframe_html = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="700px"
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
if err_list: _build_status += f"\nFile list refresh error after build: {err_list}"; _file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value="Error refreshing files")
|
572 |
-
else: _file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value=None if file_list else "No files found")
|
573 |
-
|
574 |
-
yield _build_status, _iframe_html, _file_browser_update, owner_name_output, space_name_output
|
575 |
|
576 |
def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
_file_content_val, _edit_status_val, _commit_msg_val, _lang_update = "", "Error: No file selected.", gr.update(value=""), gr.update(language="python")
|
581 |
-
if not selected_file_path or selected_file_path in ["No files found", "Error loading files", "Error refreshing files"]:
|
582 |
-
yield _file_content_val, "Select a file from the dropdown.", _commit_msg_val, _lang_update
|
583 |
-
return
|
584 |
-
|
585 |
-
owner_to_use = ui_owner_name_part
|
586 |
-
if not owner_to_use:
|
587 |
-
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
588 |
-
if token_err: _edit_status_val = f"Error: {token_err}"; yield (_file_content_val, _edit_status_val, _commit_msg_val, _lang_update); return
|
589 |
-
if token:
|
590 |
-
try:
|
591 |
-
user_info = build_logic_whoami(token=token); owner_to_use = user_info['name'] if user_info and 'name' in user_info else owner_to_use
|
592 |
-
if not owner_to_use: _edit_status_val = "Error: Could not auto-detect owner from token."; yield (_file_content_val, _edit_status_val, _commit_msg_val, _lang_update); return
|
593 |
-
except Exception as e: _edit_status_val = f"Error auto-detecting owner for editing file: {e}"; yield (_file_content_val, _edit_status_val, _commit_msg_val, _lang_update); return
|
594 |
-
else: _edit_status_val = "Error: HF Owner Name not set and no token to auto-detect."; yield (_file_content_val, _edit_status_val, _commit_msg_val, _lang_update); return
|
595 |
-
|
596 |
-
if not owner_to_use or not ui_space_name_part: _edit_status_val = "Error: HF Owner and/or Space Name is missing."; yield (_file_content_val, _edit_status_val, _commit_msg_val, _lang_update); return
|
597 |
-
|
598 |
-
_edit_status_val = f"Loading {selected_file_path}..."
|
599 |
-
yield gr.update(value=""), _edit_status_val, gr.update(value=""), gr.update(language="python")
|
600 |
-
|
601 |
-
content, err = get_space_file_content(hf_api_key_ui, ui_space_name_part, owner_to_use, selected_file_path)
|
602 |
|
|
|
603 |
if err:
|
604 |
-
|
605 |
-
_commit_msg_val = f"Error loading {selected_file_path}"
|
606 |
-
_file_content_val = f"Error loading {selected_file_path}:\n{err}"
|
607 |
-
_lang_update = gr.update(language="python")
|
608 |
-
yield _file_content_val, _edit_status_val, _commit_msg_val, _lang_update
|
609 |
return
|
610 |
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
_lang_update = gr.update(language=_infer_lang_from_filename(selected_file_path))
|
615 |
-
|
616 |
-
yield _file_content_val, _edit_status_val, _commit_msg_val, _lang_update
|
617 |
|
618 |
def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_edit_path, edited_content, commit_message):
|
619 |
-
|
|
|
620 |
global parsed_code_blocks_state_cache
|
621 |
-
|
622 |
-
|
623 |
-
_formatted_md_out = gr.update()
|
624 |
-
_detected_preview_out = gr.update()
|
625 |
-
_download_btn_out = gr.update()
|
626 |
-
|
627 |
-
yield _edit_status_val, _file_browser_update_val, _formatted_md_out, _detected_preview_out, _download_btn_out
|
628 |
-
|
629 |
-
if not file_to_edit_path or file_to_edit_path in ["No files found", "Error loading files", "Error refreshing files"]:
|
630 |
-
_edit_status_val = "Error: No valid file selected for commit.";
|
631 |
-
yield _edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(); return
|
632 |
-
|
633 |
-
owner_to_use = ui_owner_name_part
|
634 |
-
if not owner_to_use:
|
635 |
-
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
636 |
-
if token_err: _edit_status_val = f"Error: {token_err}"; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update()); return
|
637 |
-
if token:
|
638 |
-
try:
|
639 |
-
user_info = build_logic_whoami(token=token); owner_to_use = user_info['name'] if user_info and 'name' in user_info else owner_to_use
|
640 |
-
if not owner_to_use: _edit_status_val = "Error: Could not auto-detect owner from token."; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update()); return
|
641 |
-
except Exception as e: _edit_status_val = f"Error auto-detecting owner for committing file: {e}"; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update()); return
|
642 |
-
else: _edit_status_val = "Error: HF Owner Name not set and no token to auto-detect."; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update()); return
|
643 |
-
|
644 |
-
if not owner_to_use or not ui_space_name_part: _edit_status_val = "Error: HF Owner and/or Space Name is missing."; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update()); return
|
645 |
-
|
646 |
-
status_msg = update_space_file(hf_api_key_ui, ui_space_name_part, owner_to_use, file_to_edit_path, edited_content, commit_message)
|
647 |
-
_edit_status_val = status_msg
|
648 |
-
|
649 |
-
if "Successfully updated" in status_msg:
|
650 |
-
found_in_cache = False
|
651 |
-
# This loop modifies items within the global list.
|
652 |
for block in parsed_code_blocks_state_cache:
|
653 |
if block["filename"] == file_to_edit_path:
|
654 |
block["code"] = edited_content
|
655 |
-
block["language"] = _infer_lang_from_filename(file_to_edit_path)
|
656 |
-
block["is_binary"] = False
|
657 |
-
block["is_structure_block"] = False
|
658 |
-
found_in_cache = True
|
659 |
break
|
660 |
-
|
661 |
-
|
662 |
-
parsed_code_blocks_state_cache = [b for b in parsed_code_blocks_state_cache if b["filename"] != file_to_edit_path]
|
663 |
-
# This line modifies the global list.
|
664 |
-
parsed_code_blocks_state_cache.append({
|
665 |
-
"filename": file_to_edit_path,
|
666 |
-
"code": edited_content,
|
667 |
-
"language": _infer_lang_from_filename(file_to_edit_path),
|
668 |
-
"is_binary": False,
|
669 |
-
"is_structure_block": False
|
670 |
-
})
|
671 |
-
# This line modifies the global list.
|
672 |
-
parsed_code_blocks_state_cache.sort(key=lambda b: (0, b["filename"]) if b.get("is_structure_block") else (1, b["filename"]))
|
673 |
-
|
674 |
-
_formatted_md_out, _detected_preview_out, _download_btn_out = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name_part)
|
675 |
-
|
676 |
-
new_file_list, err_list = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, owner_to_use)
|
677 |
-
if err_list:
|
678 |
-
_edit_status_val += f"\nFile list refresh error: {err_list}"
|
679 |
-
_file_browser_update_val = gr.update(choices=sorted(new_file_list or []), value="Error refreshing files")
|
680 |
-
else:
|
681 |
-
_file_browser_update_val = gr.update(choices=sorted(new_file_list or []), value=file_to_edit_path)
|
682 |
-
|
683 |
-
yield _edit_status_val, _file_browser_update_val, _formatted_md_out, _detected_preview_out, _download_btn_out
|
684 |
|
685 |
def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
|
686 |
-
|
|
|
|
|
|
|
|
|
687 |
global parsed_code_blocks_state_cache
|
688 |
-
|
689 |
-
_file_browser_choices_update = gr.update()
|
690 |
-
_file_browser_value_update = None
|
691 |
-
_file_content_editor_update = gr.update(value="")
|
692 |
-
_commit_msg_update = gr.update(value="")
|
693 |
-
_lang_update = gr.update(language="plaintext")
|
694 |
-
_formatted_md_out = gr.update()
|
695 |
-
_detected_preview_out = gr.update()
|
696 |
-
_download_btn_out = gr.update()
|
697 |
-
|
698 |
-
yield (_edit_status_val, _file_browser_choices_update, _file_browser_value_update, _file_content_editor_update, _commit_msg_update, _lang_update, _formatted_md_out, _detected_preview_out, _download_btn_out)
|
699 |
-
|
700 |
-
if not file_to_delete_path or file_to_delete_path in ["No files found", "Error loading files", "Error refreshing files"]:
|
701 |
-
_edit_status_val = "Error: No valid file selected for deletion.";
|
702 |
-
yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()); return
|
703 |
-
|
704 |
-
owner_to_use = ui_owner_name_part
|
705 |
-
if not owner_to_use:
|
706 |
-
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
707 |
-
if token_err: _edit_status_val = f"API Token Error: {token_err}"; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()); return
|
708 |
-
if token:
|
709 |
-
try:
|
710 |
-
user_info = build_logic_whoami(token=token); owner_to_use = user_info['name'] if user_info and 'name' in user_info else owner_to_use
|
711 |
-
if not owner_to_use: _edit_status_val = "Error: Could not auto-detect owner from token."; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()); return
|
712 |
-
except Exception as e: _edit_status_val = f"Error auto-detecting owner for deleting file: {e}"; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()); return
|
713 |
-
else: _edit_status_val = "Error: HF Token needed to auto-detect owner."; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()); return
|
714 |
-
|
715 |
-
if not owner_to_use or not ui_space_name_part: _edit_status_val = "Error: Owner and Space Name are required."; yield (_edit_status_val, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()); return
|
716 |
-
|
717 |
-
deletion_status_msg = build_logic_delete_space_file(hf_api_key_ui, ui_space_name_part, owner_to_use, file_to_delete_path)
|
718 |
-
_edit_status_val = deletion_status_msg
|
719 |
-
|
720 |
-
new_file_list, err_list = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, owner_to_use)
|
721 |
-
|
722 |
-
if "Successfully deleted" in deletion_status_msg:
|
723 |
-
# This line ASSIGNS to parsed_code_blocks_state_cache.
|
724 |
parsed_code_blocks_state_cache = [b for b in parsed_code_blocks_state_cache if b["filename"] != file_to_delete_path]
|
725 |
-
|
726 |
-
|
727 |
-
if err_list:
|
728 |
-
_edit_status_val += f"\nFile list refresh error: {err_list}"
|
729 |
-
_file_browser_choices_update = gr.update(choices=sorted(new_file_list or []), value="Error refreshing files")
|
730 |
-
else:
|
731 |
-
_file_browser_choices_update = gr.update(choices=sorted(new_file_list or []), value=None)
|
732 |
-
|
733 |
-
_file_browser_value_update = None
|
734 |
-
else:
|
735 |
-
if err_list:
|
736 |
-
_edit_status_val += f"\nFile list refresh error: {err_list}"
|
737 |
-
_file_browser_choices_update = gr.update(choices=sorted(new_file_list or []), value="Error refreshing files")
|
738 |
-
_file_browser_value_update = "Error refreshing files"
|
739 |
-
else:
|
740 |
-
_file_browser_choices_update = gr.update(choices=sorted(new_file_list or []), value=file_to_delete_path)
|
741 |
-
_file_browser_value_update = file_to_delete_path
|
742 |
-
_formatted_md_out, _detected_preview_out, _download_btn_out = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name_part)
|
743 |
-
|
744 |
-
yield (_edit_status_val, _file_browser_choices_update, _file_browser_value_update, _file_content_editor_update, _commit_msg_update, _lang_update, _formatted_md_out, _detected_preview_out, _download_btn_out)
|
745 |
|
746 |
def handle_refresh_space_status(hf_api_key_ui, ui_owner_name, ui_space_name):
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
if status_details.get('raw_data'):
|
764 |
-
md_lines.append(f"\n<details><summary>Raw Status Data (JSON)</summary>\n\n```json\n{json.dumps(status_details.get('raw_data', {}), indent=2)}\n```\n</details>")
|
765 |
-
_status_display_md = "\n".join(md_lines)
|
766 |
-
else: _status_display_md = "Could not retrieve status details."
|
767 |
-
yield _status_display_md
|
768 |
-
|
769 |
-
custom_theme = gr.themes.Base(
|
770 |
-
primary_hue="teal",
|
771 |
-
secondary_hue="purple",
|
772 |
-
neutral_hue="zinc",
|
773 |
-
text_size="sm",
|
774 |
-
spacing_size="md",
|
775 |
-
radius_size="sm",
|
776 |
-
font=["System UI", "sans-serif"]
|
777 |
-
)
|
778 |
-
|
779 |
-
custom_css = """
|
780 |
-
body {
|
781 |
-
background: linear-gradient(to bottom right, #2c3e50, #34495e);
|
782 |
-
color: #ecf0f1;
|
783 |
-
}
|
784 |
-
.gradio-container {
|
785 |
-
background: transparent !important;
|
786 |
-
}
|
787 |
-
.gr-box, .gr-panel, .gr-pill {
|
788 |
-
background-color: rgba(44, 62, 80, 0.8) !important;
|
789 |
-
border-color: rgba(189, 195, 199, 0.2) !important;
|
790 |
-
}
|
791 |
-
.gr-textbox, .gr-dropdown, .gr-button, .gr-code, .gr-chat-message {
|
792 |
-
border-color: rgba(189, 195, 199, 0.3) !important;
|
793 |
-
background-color: rgba(52, 73, 94, 0.9) !important;
|
794 |
-
color: #ecf0f1 !important;
|
795 |
-
}
|
796 |
-
.gr-button.gr-button-primary {
|
797 |
-
background-color: #1abc9c !important;
|
798 |
-
color: white !important;
|
799 |
-
border-color: #16a085 !important;
|
800 |
-
}
|
801 |
-
.gr-button.gr-button-secondary {
|
802 |
-
background-color: #9b59b6 !important;
|
803 |
-
color: white !important;
|
804 |
-
border-color: #8e44ad !important;
|
805 |
-
}
|
806 |
-
.gr-button.gr-button-stop {
|
807 |
-
background-color: #e74c3c !important;
|
808 |
-
color: white !important;
|
809 |
-
border-color: #c0392b !important;
|
810 |
-
}
|
811 |
-
.gr-markdown {
|
812 |
-
background-color: rgba(44, 62, 80, 0.7) !important;
|
813 |
-
padding: 10px;
|
814 |
-
border-radius: 5px;
|
815 |
-
}
|
816 |
-
.gr-markdown h1, .gr-markdown h2, .gr-markdown h3, .gr-markdown h4, .gr-markdown h5, .gr-markdown h6 {
|
817 |
-
color: #ecf0f1 !important;
|
818 |
-
border-bottom-color: rgba(189, 195, 199, 0.3) !important;
|
819 |
-
}
|
820 |
-
.gr-markdown pre code {
|
821 |
-
background-color: rgba(52, 73, 94, 0.95) !important;
|
822 |
-
border-color: rgba(189, 195, 199, 0.3) !important;
|
823 |
-
}
|
824 |
-
.gr-chatbot {
|
825 |
-
background-color: rgba(44, 62, 80, 0.7) !important;
|
826 |
-
border-color: rgba(189, 195, 199, 0.2) !important;
|
827 |
-
}
|
828 |
-
.gr-chatbot .message {
|
829 |
-
background-color: rgba(52, 73, 94, 0.9) !important;
|
830 |
-
color: #ecf0f1 !important;
|
831 |
-
border-color: rgba(189, 195, 199, 0.3) !important;
|
832 |
-
}
|
833 |
-
.gr-chatbot .message.user {
|
834 |
-
background-color: rgba(46, 204, 113, 0.9) !important;
|
835 |
-
color: black !important;
|
836 |
-
}
|
837 |
-
"""
|
838 |
|
839 |
-
available_providers = get_available_providers()
|
840 |
-
default_provider = available_providers[0] if available_providers else None
|
841 |
-
initial_models = get_models_for_provider(default_provider) if default_provider else []
|
842 |
-
initial_default_model = get_default_model_for_provider(default_provider) if default_provider else None
|
843 |
-
if initial_default_model not in initial_models and initial_models:
|
844 |
-
initial_default_model = initial_models[0]
|
845 |
-
|
846 |
-
with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
|
847 |
-
gr.Markdown("# π€ AI Code & Space Generator")
|
848 |
-
gr.Markdown("Configure settings, chat with AI to generate/modify Hugging Face Spaces, then build, preview, and edit.")
|
849 |
with gr.Row():
|
850 |
-
with gr.
|
851 |
-
gr.
|
852 |
-
|
853 |
-
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
858 |
-
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
gr.
|
866 |
-
|
867 |
-
|
868 |
-
|
869 |
-
|
870 |
-
|
871 |
-
|
872 |
-
with gr.
|
873 |
-
with gr.
|
874 |
-
|
875 |
-
|
876 |
-
with gr.
|
877 |
-
gr.Markdown("
|
878 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
879 |
file_content_editor = gr.Code(label="File Content Editor", language="python", lines=15, interactive=True)
|
880 |
-
commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Updated app.py"
|
881 |
-
with gr.Row():
|
882 |
-
|
883 |
-
|
884 |
-
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
|
|
|
|
|
|
890 |
chat_outputs = [chat_message_input, chatbot_display, status_output, detected_files_preview, formatted_space_output_display, download_button]
|
891 |
-
|
892 |
-
|
893 |
-
send_chat_button.click(
|
894 |
-
fn=handle_chat_submit,
|
895 |
-
inputs=chat_inputs,
|
896 |
-
outputs=chat_outputs
|
897 |
-
)
|
898 |
-
chat_message_input.submit(
|
899 |
-
fn=handle_chat_submit,
|
900 |
-
inputs=chat_inputs,
|
901 |
-
outputs=chat_outputs
|
902 |
-
)
|
903 |
|
904 |
load_space_outputs = [formatted_space_output_display, detected_files_preview, status_output, file_browser_dropdown, owner_name_input, space_name_input, space_iframe_display, download_button, build_status_display, edit_status_display, space_runtime_status_display, chatbot_display]
|
905 |
load_space_button.click(fn=handle_load_existing_space, inputs=[hf_api_key_input, owner_name_input, space_name_input], outputs=load_space_outputs)
|
906 |
|
907 |
build_outputs = [build_status_display, space_iframe_display, file_browser_dropdown, owner_name_input, space_name_input]
|
908 |
-
|
|
|
909 |
|
910 |
-
file_edit_load_outputs = [file_content_editor, edit_status_display, commit_message_input, file_content_editor]
|
911 |
file_browser_dropdown.change(fn=handle_load_file_for_editing, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown], outputs=file_edit_load_outputs)
|
912 |
|
913 |
commit_file_outputs = [edit_status_display, file_browser_dropdown, formatted_space_output_display, detected_files_preview, download_button]
|
914 |
update_file_button.click(fn=handle_commit_file_changes, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown, file_content_editor, commit_message_input], outputs=commit_file_outputs)
|
915 |
|
916 |
-
delete_file_outputs = [edit_status_display, file_browser_dropdown,
|
917 |
delete_file_button.click(fn=handle_delete_file, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown], outputs=delete_file_outputs)
|
918 |
|
919 |
refresh_status_button.click(fn=handle_refresh_space_status, inputs=[hf_api_key_input, owner_name_input, space_name_input], outputs=[space_runtime_status_display])
|
920 |
|
921 |
if __name__ == "__main__":
|
922 |
-
demo.launch(debug=True
|
|
|
3 |
import json
|
4 |
import os
|
5 |
import tempfile
|
6 |
+
import shlex
|
7 |
+
from huggingface_hub import HfApi
|
8 |
+
|
9 |
+
try:
|
10 |
+
from build_logic import (
|
11 |
+
create_space as build_logic_create_space,
|
12 |
+
_get_api_token as build_logic_get_api_token,
|
13 |
+
whoami as build_logic_whoami,
|
14 |
+
list_space_files_for_browsing,
|
15 |
+
get_space_repository_info,
|
16 |
+
get_space_file_content,
|
17 |
+
update_space_file,
|
18 |
+
parse_markdown as build_logic_parse_markdown,
|
19 |
+
delete_space_file as build_logic_delete_space_file,
|
20 |
+
get_space_runtime_status
|
21 |
+
)
|
22 |
+
print("build_logic.py loaded successfully.")
|
23 |
+
|
24 |
+
from model_logic import (
|
25 |
+
get_available_providers,
|
26 |
+
get_models_for_provider,
|
27 |
+
get_default_model_for_provider,
|
28 |
+
generate_stream
|
29 |
+
)
|
30 |
+
print("model_logic.py loaded successfully.")
|
31 |
+
except ImportError:
|
32 |
+
print("Warning: Local modules (build_logic.py, model_logic.py) not found. Using dummy functions.")
|
33 |
+
# Define dummy functions so the app can at least start
|
34 |
+
def get_available_providers(): return ["DummyProvider"]
|
35 |
+
def get_models_for_provider(p): return ["dummy-model"]
|
36 |
+
def get_default_model_for_provider(p): return "dummy-model"
|
37 |
+
def generate_stream(p, m, a, msgs):
|
38 |
+
yield "Error: Local modules not found. This is a dummy response."
|
39 |
+
def build_logic_create_space(*args, **kwargs): return "Error: build_logic not found."
|
40 |
+
def build_logic_get_api_token(key): return (key or os.getenv("HF_TOKEN"), None)
|
41 |
+
def build_logic_whoami(token): return {"name": "dummy_user"}
|
42 |
+
def list_space_files_for_browsing(*args): return ([], "Error: build_logic not found.")
|
43 |
+
def get_space_repository_info(*args): return (None, [], "Error: build_logic not found.")
|
44 |
+
def get_space_file_content(*args): return ("", "Error: build_logic not found.")
|
45 |
+
def update_space_file(*args): return "Error: build_logic not found."
|
46 |
+
def build_logic_parse_markdown(md): return {"files": []}
|
47 |
+
def build_logic_delete_space_file(*args): return "Error: build_logic not found."
|
48 |
+
def get_space_runtime_status(*args): return (None, "Error: build_logic not found.")
|
49 |
+
|
50 |
+
|
51 |
+
# --- New Feature Functions (can be moved to build_logic.py) ---
|
52 |
+
def build_logic_set_space_privacy(hf_api_key, repo_id, private: bool):
|
53 |
+
"""Sets the privacy of a Hugging Face Space."""
|
54 |
+
print(f"[ACTION] Setting privacy for '{repo_id}' to {private}.")
|
55 |
+
try:
|
56 |
+
token, err = build_logic_get_api_token(hf_api_key)
|
57 |
+
if err or not token: return f"Error getting token: {err or 'Token not found.'}"
|
58 |
+
api = HfApi(token=token)
|
59 |
+
api.update_repo_visibility(repo_id=repo_id, private=private, repo_type='space')
|
60 |
+
return f"Successfully set privacy for {repo_id} to {private}."
|
61 |
+
except Exception as e:
|
62 |
+
print(f"Error setting privacy: {e}")
|
63 |
+
return f"Error setting privacy: {e}"
|
64 |
+
|
65 |
+
def build_logic_delete_space(hf_api_key, owner, space_name):
|
66 |
+
"""Deletes an entire Hugging Face Space."""
|
67 |
+
repo_id = f"{owner}/{space_name}"
|
68 |
+
print(f"[ACTION] Deleting space '{repo_id}'. THIS IS A DESTRUCTIVE ACTION.")
|
69 |
+
try:
|
70 |
+
token, err = build_logic_get_api_token(hf_api_key)
|
71 |
+
if err or not token: return f"Error getting token: {err or 'Token not found.'}"
|
72 |
+
api = HfApi(token=token)
|
73 |
+
api.delete_repo(repo_id=repo_id, repo_type='space')
|
74 |
+
return f"Successfully deleted space {repo_id}."
|
75 |
+
except Exception as e:
|
76 |
+
print(f"Error deleting space: {e}")
|
77 |
+
return f"Error deleting space: {e}"
|
78 |
+
|
79 |
+
|
80 |
+
# --- CORE FIX: Define triple backticks safely to prevent Markdown rendering issues ---
|
81 |
+
backtick = chr(96)
|
82 |
+
bbb = f'{backtick}{backtick}{backtick}'
|
83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
parsed_code_blocks_state_cache = []
|
85 |
BOT_ROLE_NAME = "assistant"
|
86 |
|
87 |
+
DEFAULT_SYSTEM_PROMPT = f"""You are an expert AI programmer and Hugging Face assistant. Your role is to generate code and file structures based on user requests, or to modify existing code provided by the user.
|
88 |
+
|
89 |
+
**File and Code Formatting:**
|
90 |
When you provide NEW code for a file, or MODIFIED code for an existing file, use the following format exactly:
|
91 |
### File: path/to/filename.ext
|
92 |
(You can add a short, optional, parenthesized description after the filename on the SAME line)
|
93 |
{bbb}language
|
94 |
# Your full code here
|
95 |
{bbb}
|
96 |
+
|
97 |
If the file is binary, or you cannot show its content, use this format:
|
98 |
### File: path/to/binaryfile.ext
|
99 |
[Binary file - approximate_size bytes]
|
100 |
+
|
101 |
When you provide a project file structure, use this format:
|
102 |
## File Structure
|
103 |
{bbb}
|
|
|
106 |
π subfolder
|
107 |
π file2.js
|
108 |
{bbb}
|
109 |
+
|
110 |
+
**Instructions and Rules:**
|
111 |
+
- The role name for your responses in the chat history must be '{BOT_ROLE_NAME}'.
|
112 |
+
- Adhere strictly to these formatting instructions.
|
113 |
+
- If you update a file, provide the FULL file content again under the same filename.
|
114 |
+
- Only the latest version of each file mentioned throughout the chat will be used for the final output. The system will merge your changes with the prior state.
|
115 |
+
- Filenames in the '### File:' line should be clean paths (e.g., 'src/app.py', 'Dockerfile') and should NOT include Markdown backticks.
|
116 |
+
|
117 |
+
**Hugging Face Space Actions:**
|
118 |
+
To perform direct actions on the Hugging Face Space, use the `HF_ACTION` command. This is a powerful tool to manage the repository programmatically.
|
119 |
+
The format is `### HF_ACTION: COMMAND arguments...` on a single line.
|
120 |
+
|
121 |
+
Available commands:
|
122 |
+
- `CREATE_SPACE owner/repo_name --sdk <sdk> --private <true|false>`: Creates a new, empty space. SDK can be gradio, streamlit, docker, or static. Private is optional and defaults to false.
|
123 |
+
- `DELETE_FILE path/to/file.ext`: Deletes a specific file from the current space.
|
124 |
+
- `SET_PRIVATE <true|false>`: Sets the privacy for the current space.
|
125 |
+
- `DELETE_SPACE`: Deletes the entire current space. THIS IS PERMANENT AND REQUIRES CAUTION.
|
126 |
+
|
127 |
+
You can issue multiple actions. For example, to delete a file and then add a new one:
|
128 |
+
### HF_ACTION: DELETE_FILE old_app.py
|
129 |
+
### File: new_app.py
|
130 |
+
{bbb}python
|
131 |
+
# new code
|
132 |
+
{bbb}
|
133 |
+
Use these actions when the user's request explicitly calls for them (e.g., "delete the readme file", "make this space private", "create a new private space called my-test-app"). If no code is provided, assist the user with their tasks.
|
134 |
"""
|
135 |
|
136 |
+
|
137 |
def escape_html_for_markdown(text):
|
138 |
if not isinstance(text, str): return ""
|
139 |
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
|
183 |
return filename_candidate if filename_candidate else text.strip()
|
184 |
|
185 |
def _parse_chat_stream_logic(latest_bot_message_content, existing_files_state=None):
|
186 |
+
global parsed_code_blocks_state_cache
|
187 |
latest_blocks_dict = {}
|
188 |
if existing_files_state:
|
189 |
for block in existing_files_state:
|
|
|
200 |
if structure_match:
|
201 |
latest_blocks_dict["File Structure (original)"] = {"filename": "File Structure (original)", "language": structure_match.group("struct_lang") or "plaintext", "code": structure_match.group("structure_code").strip(), "is_binary": False, "is_structure_block": True}
|
202 |
else:
|
203 |
+
existing_structure_block = next((b for b in parsed_code_blocks_state_cache if b.get("is_structure_block")), None)
|
204 |
if existing_structure_block:
|
205 |
latest_blocks_dict["File Structure (original)"] = existing_structure_block.copy()
|
206 |
|
|
|
218 |
current_message_file_blocks[filename] = item_data
|
219 |
|
220 |
latest_blocks_dict.update(current_message_file_blocks)
|
|
|
221 |
current_parsed_blocks = list(latest_blocks_dict.values())
|
222 |
current_parsed_blocks.sort(key=lambda b: (0, b["filename"]) if b.get("is_structure_block") else (1, b["filename"]))
|
|
|
223 |
results["parsed_code_blocks"] = current_parsed_blocks
|
224 |
results["default_selected_filenames"] = [b["filename"] for b in current_parsed_blocks if not b.get("is_structure_block")]
|
225 |
return results
|
226 |
|
227 |
def _export_selected_logic(selected_filenames, space_line_name_for_md, parsed_blocks_for_export):
|
228 |
results = {"output_str": "", "error_message": None, "download_filepath": None}
|
229 |
+
exportable_blocks_content = [b for b in parsed_blocks_for_export if not b.get("is_structure_block") and not b.get("is_binary") and not (b.get("code", "").startswith(("[Error loading content:", "[Binary or Skipped file]")))]
|
230 |
binary_blocks_content = [b for b in parsed_blocks_for_export if b.get("is_binary") or b.get("code", "").startswith("[Binary or Skipped file]")]
|
|
|
231 |
all_filenames_in_state = sorted(list(set(b["filename"] for b in parsed_blocks_for_export if not b.get("is_structure_block"))))
|
232 |
|
233 |
if not all_filenames_in_state and not any(b.get("is_structure_block") for b in parsed_blocks_for_export):
|
|
|
239 |
return results
|
240 |
|
241 |
output_lines = [f"# Space: {space_line_name_for_md}"]
|
|
|
242 |
structure_block = next((b for b in parsed_blocks_for_export if b.get("is_structure_block")), None)
|
243 |
if structure_block:
|
244 |
output_lines.extend(["## File Structure", bbb, structure_block["code"].strip(), bbb, ""])
|
|
|
249 |
output_lines.extend([bbb, ""])
|
250 |
|
251 |
output_lines.append("Below are the contents of all files in the space:\n")
|
252 |
+
files_to_export_content = [b for b in exportable_blocks_content if not selected_filenames or b["filename"] in selected_filenames]
|
253 |
+
binary_error_blocks_to_export = [b for b in binary_blocks_content if not selected_filenames or b["filename"] in selected_filenames]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
all_blocks_to_export_content = sorted(files_to_export_content + binary_error_blocks_to_export, key=lambda b: b["filename"])
|
255 |
|
256 |
+
exported_content = False
|
257 |
for block in all_blocks_to_export_content:
|
258 |
output_lines.append(f"### File: {block['filename']}")
|
259 |
+
if block.get('is_binary') or block.get("code", "").startswith(("[Binary file", "[Error loading content:", "[Binary or Skipped file]")):
|
260 |
output_lines.append(block.get('code','[Binary or Skipped file]'))
|
261 |
else:
|
262 |
output_lines.extend([f"{bbb}{block.get('language', 'plaintext') or 'plaintext'}", block.get('code',''), bbb])
|
|
|
270 |
try:
|
271 |
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".md", encoding='utf-8') as tmpfile:
|
272 |
tmpfile.write(final_output_str); results["download_filepath"] = tmpfile.name
|
273 |
+
except Exception as e:
|
274 |
+
print(f"Error creating temp file: {e}")
|
275 |
+
results["error_message"] = "Could not prepare file for download."
|
276 |
return results
|
277 |
|
278 |
def _convert_gr_history_to_api_messages(system_prompt, gr_history, current_user_message=None):
|
|
|
284 |
return messages
|
285 |
|
286 |
def _generate_ui_outputs_from_cache(owner, space_name):
|
|
|
|
|
287 |
global parsed_code_blocks_state_cache
|
288 |
preview_md_val = "*No files in cache to display.*"
|
289 |
+
formatted_md_val = f"# Space: {owner}/{space_name}\n## File Structure\n{bbb}\nπ Root\n{bbb}\n\n*No files in cache.*" if owner and space_name else "*Load or define a Space to see its Markdown structure.*"
|
290 |
download_file = None
|
291 |
|
292 |
if parsed_code_blocks_state_cache:
|
|
|
295 |
preview_md_lines.append(f"\n----\n**File:** `{escape_html_for_markdown(block['filename'])}`")
|
296 |
if block.get('is_structure_block'): preview_md_lines.append(f" (Original File Structure from AI)\n")
|
297 |
elif block.get('is_binary'): preview_md_lines.append(f" (Binary File)\n")
|
298 |
+
else: preview_md_lines.append(f" (Language: `{block['language']}`)\n")
|
|
|
299 |
|
300 |
content = block.get('code', '')
|
301 |
+
if block.get('is_binary') or content.startswith(("[Binary file", "[Error loading content:", "[Binary or Skipped file]")):
|
302 |
preview_md_lines.append(f"\n`{escape_html_for_markdown(content)}`\n")
|
|
|
|
|
303 |
else:
|
304 |
preview_md_lines.append(f"\n{bbb}{block.get('language', 'plaintext') or 'plaintext'}\n{content}\n{bbb}\n")
|
|
|
305 |
preview_md_val = "\n".join(preview_md_lines)
|
306 |
space_line_name = f"{owner}/{space_name}" if owner and space_name else (owner or space_name or "your-space")
|
|
|
307 |
export_result = _export_selected_logic(None, space_line_name, parsed_code_blocks_state_cache)
|
308 |
formatted_md_val = export_result["output_str"]
|
309 |
download_file = export_result["download_filepath"]
|
310 |
|
311 |
return formatted_md_val, preview_md_val, gr.update(value=download_file, interactive=download_file is not None)
|
312 |
|
313 |
+
def handle_chat_submit(user_message, chat_history, hf_api_key_input, provider_select, model_select, system_prompt, hf_owner_name, hf_repo_name, _current_formatted_markdown):
|
|
|
|
|
314 |
global parsed_code_blocks_state_cache
|
315 |
_chat_msg_in = ""
|
316 |
_chat_hist = list(chat_history)
|
317 |
_status = "Initializing..."
|
318 |
_detected_files_update, _formatted_output_update, _download_btn_update = gr.update(), gr.update(), gr.update(interactive=False, value=None)
|
319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
if not user_message.strip():
|
321 |
_status = "Cannot send an empty message."
|
322 |
yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update); return
|
323 |
+
|
324 |
+
_chat_hist.append((user_message, None)); _status = f"Sending to {model_select}..."
|
325 |
yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update)
|
326 |
|
|
|
327 |
current_sys_prompt = system_prompt.strip() or DEFAULT_SYSTEM_PROMPT
|
328 |
+
current_files_context = f"\n\n## Current Space Context: {hf_owner_name}/{hf_repo_name}\n"
|
329 |
+
export_result = _export_selected_logic(None, f"{hf_owner_name}/{hf_repo_name}", parsed_code_blocks_state_cache)
|
330 |
+
current_files_context += export_result["output_str"]
|
331 |
|
332 |
+
user_message_with_context = user_message.strip() + "\n" + current_files_context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
api_msgs = _convert_gr_history_to_api_messages(current_sys_prompt, _chat_hist[:-1], user_message_with_context)
|
334 |
|
335 |
try:
|
336 |
+
_status = f"Waiting for {model_select}..."
|
337 |
+
yield (_chat_msg_in, _chat_hist, _status, gr.update(), gr.update(), gr.update())
|
338 |
|
339 |
full_bot_response_content = ""
|
340 |
+
for chunk in generate_stream(provider_select, model_select, None, api_msgs):
|
341 |
+
if chunk is None: continue
|
342 |
+
if isinstance(chunk, str) and (chunk.startswith("Error:") or chunk.startswith("API HTTP Error")):
|
343 |
+
full_bot_response_content = chunk; break
|
344 |
+
full_bot_response_content += str(chunk)
|
345 |
+
_chat_hist[-1] = (user_message, full_bot_response_content)
|
346 |
+
_status = f"Streaming from {model_select}..."
|
347 |
+
yield (_chat_msg_in, _chat_hist, _status, gr.update(), gr.update(), gr.update())
|
348 |
+
|
349 |
+
if "Error:" in full_bot_response_content:
|
350 |
+
_status = full_bot_response_content
|
351 |
+
else:
|
352 |
+
_status = "Stream complete. Processing response..."
|
353 |
+
action_results = []
|
354 |
+
action_pattern = re.compile(r"### HF_ACTION:\s*(?P<command_line>[^\n]+)")
|
355 |
+
|
356 |
+
# Process destructive actions first
|
357 |
+
for match in action_pattern.finditer(full_bot_response_content):
|
358 |
+
cmd_parts = shlex.split(match.group("command_line").strip())
|
359 |
+
if not cmd_parts: continue
|
360 |
+
command, args = cmd_parts[0].upper(), cmd_parts[1:]
|
361 |
+
if command == "DELETE_FILE":
|
362 |
+
if not args: action_results.append("Action Failed: DELETE_FILE needs a path."); continue
|
363 |
+
filepath = args[0]
|
364 |
+
status_msg = build_logic_delete_space_file(hf_api_key_input, hf_repo_name, hf_owner_name, filepath)
|
365 |
+
action_results.append(f"DELETE '{filepath}': {status_msg}")
|
366 |
+
if "Successfully" in status_msg:
|
367 |
+
parsed_code_blocks_state_cache = [b for b in parsed_code_blocks_state_cache if b["filename"] != filepath]
|
368 |
+
elif command == "DELETE_SPACE":
|
369 |
+
status_msg = build_logic_delete_space(hf_api_key_input, hf_owner_name, hf_repo_name)
|
370 |
+
action_results.append(f"DELETE_SPACE '{hf_owner_name}/{hf_repo_name}': {status_msg}")
|
371 |
+
if "Successfully" in status_msg: parsed_code_blocks_state_cache = []
|
372 |
+
|
373 |
+
# Process other actions
|
374 |
+
for match in action_pattern.finditer(full_bot_response_content):
|
375 |
+
cmd_parts = shlex.split(match.group("command_line").strip())
|
376 |
+
if not cmd_parts: continue
|
377 |
+
command, args = cmd_parts[0].upper(), cmd_parts[1:]
|
378 |
+
if command == "SET_PRIVATE":
|
379 |
+
if not args: action_results.append("Action Failed: SET_PRIVATE needs true/false."); continue
|
380 |
+
is_private = args[0].lower() == 'true'
|
381 |
+
status_msg = build_logic_set_space_privacy(hf_api_key_input, f"{hf_owner_name}/{hf_repo_name}", private=is_private)
|
382 |
+
action_results.append(f"SET_PRIVATE to {is_private}: {status_msg}")
|
383 |
|
384 |
+
parsing_res = _parse_chat_stream_logic(full_bot_response_content, existing_files_state=parsed_code_blocks_state_cache)
|
385 |
if parsing_res["error_message"]:
|
386 |
+
action_results.append(f"Parsing Error: {parsing_res['error_message']}")
|
|
|
387 |
else:
|
|
|
|
|
|
|
388 |
parsed_code_blocks_state_cache = parsing_res["parsed_code_blocks"]
|
389 |
+
if any(f.get("filename") for f in parsing_res["parsed_code_blocks"]):
|
390 |
+
action_results.append("File content updated from AI response.")
|
391 |
|
392 |
+
_status = " | ".join(action_results) if action_results else "No actions performed. Files parsed."
|
393 |
+
|
394 |
+
_formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
|
|
|
|
|
|
395 |
|
396 |
except Exception as e:
|
397 |
+
error_msg = f"An unexpected error occurred: {e}"
|
398 |
+
print(f"Error in handle_chat_submit: {e}")
|
399 |
+
if _chat_hist:
|
400 |
+
_chat_hist[-1] = (user_message, error_msg)
|
|
|
|
|
401 |
_status = error_msg
|
402 |
_formatted_output_update, _detected_files_update, _download_btn_update = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
403 |
|
404 |
yield (_chat_msg_in, _chat_hist, _status, _detected_files_update, _formatted_output_update, _download_btn_update)
|
405 |
|
406 |
def update_models_dropdown(provider_select):
|
407 |
+
if not provider_select: return gr.update(choices=[], value=None)
|
|
|
408 |
models = get_models_for_provider(provider_select)
|
409 |
default_model = get_default_model_for_provider(provider_select)
|
410 |
+
selected_value = default_model if default_model in models else (models[0] if models else None)
|
|
|
|
|
|
|
|
|
|
|
411 |
return gr.update(choices=models, value=selected_value)
|
412 |
|
413 |
def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
|
|
414 |
global parsed_code_blocks_state_cache
|
415 |
_formatted_md_val, _detected_preview_val, _status_val = "*Loading files...*", "*Loading files...*", f"Loading Space: {ui_owner_name}/{ui_space_name}..."
|
416 |
_file_browser_update, _iframe_html_update, _download_btn_update = gr.update(visible=False, choices=[], value=None), gr.update(value=None, visible=False), gr.update(interactive=False, value=None)
|
417 |
+
_build_status_clear, _edit_status_clear, _runtime_status_clear = "*Build status...*", "*Select a file...*", "*Runtime status...*"
|
418 |
_chat_history_clear = []
|
419 |
+
outputs = [_formatted_md_val, _detected_preview_val, _status_val, _file_browser_update, gr.update(value=ui_owner_name), gr.update(value=ui_space_name), _iframe_html_update, _download_btn_update, _build_status_clear, _edit_status_clear, _runtime_status_clear, _chat_history_clear]
|
420 |
+
yield outputs
|
421 |
|
422 |
+
owner_to_use = ui_owner_name
|
|
|
|
|
|
|
|
|
423 |
if not owner_to_use:
|
424 |
+
token, err = build_logic_get_api_token(hf_api_key_ui)
|
425 |
+
if err or not token:
|
426 |
+
_status_val = f"Error: {err or 'Cannot determine owner from token.'}"
|
427 |
+
outputs[2] = _status_val; yield outputs; return
|
428 |
+
try:
|
429 |
+
user_info = build_logic_whoami(token=token)
|
430 |
+
owner_to_use = user_info.get('name')
|
431 |
+
if not owner_to_use: raise Exception("Could not find user name from token.")
|
432 |
+
outputs[4] = gr.update(value=owner_to_use)
|
433 |
+
_status_val += f" (Auto-detected owner: {owner_to_use})"
|
434 |
+
except Exception as e:
|
435 |
+
_status_val = f"Error auto-detecting owner: {e}"; outputs[2] = _status_val; yield outputs; return
|
436 |
|
437 |
if not owner_to_use or not ui_space_name:
|
438 |
+
_status_val = "Error: Owner and Space Name are required."; outputs[2] = _status_val; yield outputs; return
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
|
440 |
+
sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
|
441 |
+
if err and not file_list:
|
442 |
+
_status_val = f"File List Error: {err}"; parsed_code_blocks_state_cache = []
|
443 |
+
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
444 |
+
outputs[0], outputs[1], outputs[2], outputs[7] = _formatted, _detected, _status_val, _download
|
445 |
+
yield outputs; return
|
446 |
|
447 |
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_to_use.lower()).strip('-') or 'owner'
|
448 |
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name.lower()).strip('-') or 'space'
|
449 |
+
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk == 'static' else '.hf.space'}"
|
450 |
+
outputs[6] = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="500px"></iframe>', visible=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
451 |
|
452 |
+
loaded_files = []
|
453 |
for file_path in file_list:
|
454 |
+
content, err_get = get_space_file_content(hf_api_key_ui, ui_space_name, owner_to_use, file_path)
|
455 |
+
lang = _infer_lang_from_filename(file_path)
|
456 |
+
is_binary = lang == "binary" or err_get
|
457 |
+
code = f"[Error loading content: {err_get}]" if err_get else content
|
458 |
+
loaded_files.append({"filename": file_path, "code": code, "language": lang, "is_binary": is_binary, "is_structure_block": False})
|
459 |
+
|
460 |
+
parsed_code_blocks_state_cache = loaded_files
|
461 |
+
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
462 |
+
_status_val = f"Successfully loaded {len(file_list)} files from {owner_to_use}/{ui_space_name}."
|
463 |
+
outputs[0], outputs[1], outputs[2], outputs[7] = _formatted, _detected, _status_val, _download
|
464 |
+
outputs[3] = gr.update(visible=True, choices=sorted(file_list or []), value=None)
|
465 |
+
yield outputs
|
466 |
+
|
467 |
+
def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, space_sdk_ui, is_private_ui, formatted_markdown_content):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
468 |
_build_status, _iframe_html, _file_browser_update = "Starting space build process...", gr.update(value=None, visible=False), gr.update(visible=False, choices=[], value=None)
|
469 |
yield _build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part)
|
470 |
+
if not ui_space_name_part or "/" in ui_space_name_part:
|
471 |
+
_build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
|
472 |
+
yield _build_status, _iframe_html, _file_browser_update, gr.update(), gr.update(); return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
473 |
|
474 |
+
result_message = build_logic_create_space(hf_api_key=hf_api_key_ui, space_name=ui_space_name_part, owner=ui_owner_name_part, sdk=space_sdk_ui, markdown_content=formatted_markdown_content, private=is_private_ui)
|
475 |
_build_status = f"Build Process: {result_message}"
|
476 |
|
|
|
|
|
|
|
477 |
if "Successfully" in result_message:
|
478 |
+
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', ui_owner_name_part.lower()).strip('-')
|
479 |
+
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name_part.lower()).strip('-')
|
480 |
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if space_sdk_ui == 'static' else '.hf.space'}"
|
481 |
+
_iframe_html = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="700px"></iframe>', visible=True)
|
482 |
+
file_list, err = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
483 |
+
_file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value=None)
|
484 |
+
yield _build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part)
|
|
|
|
|
|
|
|
|
485 |
|
486 |
def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
|
487 |
+
if not selected_file_path:
|
488 |
+
yield gr.update(value=""), "Select a file.", gr.update(value=""), gr.update(language="plaintext")
|
489 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
490 |
|
491 |
+
content, err = get_space_file_content(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path)
|
492 |
if err:
|
493 |
+
yield f"Error: {err}", f"Error loading '{selected_file_path}': {err}", "", gr.update(language="plaintext")
|
|
|
|
|
|
|
|
|
494 |
return
|
495 |
|
496 |
+
lang = _infer_lang_from_filename(selected_file_path)
|
497 |
+
commit_msg = f"Update {selected_file_path}"
|
498 |
+
yield content, f"Loaded {selected_file_path}", commit_msg, gr.update(language=lang)
|
|
|
|
|
|
|
499 |
|
500 |
def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_edit_path, edited_content, commit_message):
|
501 |
+
status_msg = update_space_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_edit_path, edited_content, commit_message)
|
502 |
+
file_list, _ = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
503 |
global parsed_code_blocks_state_cache
|
504 |
+
if "Successfully" in status_msg:
|
505 |
+
# Update cache
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
for block in parsed_code_blocks_state_cache:
|
507 |
if block["filename"] == file_to_edit_path:
|
508 |
block["code"] = edited_content
|
|
|
|
|
|
|
|
|
509 |
break
|
510 |
+
_formatted, _detected, _download = _generate_ui_outputs_from_cache(ui_owner_name_part, ui_space_name_part)
|
511 |
+
return status_msg, gr.update(choices=sorted(file_list or [])), _formatted, _detected, _download
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
512 |
|
513 |
def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
|
514 |
+
if not file_to_delete_path:
|
515 |
+
return "No file selected to delete.", gr.update(), "", "", "plaintext", gr.update(), gr.update(), gr.update()
|
516 |
+
|
517 |
+
status_msg = build_logic_delete_space_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path)
|
518 |
+
file_list, _ = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
519 |
global parsed_code_blocks_state_cache
|
520 |
+
if "Successfully" in status_msg:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
parsed_code_blocks_state_cache = [b for b in parsed_code_blocks_state_cache if b["filename"] != file_to_delete_path]
|
522 |
+
_formatted, _detected, _download = _generate_ui_outputs_from_cache(ui_owner_name_part, ui_space_name_part)
|
523 |
+
return status_msg, gr.update(choices=sorted(file_list or []), value=None), "", "", "plaintext", _formatted, _detected, _download
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
524 |
|
525 |
def handle_refresh_space_status(hf_api_key_ui, ui_owner_name, ui_space_name):
|
526 |
+
if not ui_owner_name or not ui_space_name:
|
527 |
+
return "Owner and Space Name must be provided to get status."
|
528 |
+
|
529 |
+
status, err = get_space_runtime_status(hf_api_key_ui, ui_space_name, ui_owner_name)
|
530 |
+
if err: return f"**Error:** {err}"
|
531 |
+
if not status: return "Could not retrieve status."
|
532 |
+
|
533 |
+
md = f"### Status for {ui_owner_name}/{ui_space_name}\n"
|
534 |
+
for key, val in status.items():
|
535 |
+
md += f"- **{key.replace('_', ' ').title()}:** `{val}`\n"
|
536 |
+
return md
|
537 |
+
|
538 |
+
# --- Gradio UI Definition ---
|
539 |
+
with gr.Blocks(theme=gr.themes.Soft(), css=".gradio-container {background: linear-gradient(to bottom right, #eff6ff, #dbeafe);}") as demo:
|
540 |
+
gr.Markdown("# π€ AI-Powered Hugging Face Space Builder")
|
541 |
+
gr.Markdown("Use an AI assistant to create, modify, build, and manage your Hugging Face Spaces directly from this interface.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
542 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
543 |
with gr.Row():
|
544 |
+
with gr.Column(scale=1):
|
545 |
+
with gr.Accordion("βοΈ Configuration", open=True):
|
546 |
+
hf_api_key_input = gr.Textbox(label="Hugging Face Token", type="password", placeholder="hf_... (uses env var HF_TOKEN if empty)")
|
547 |
+
owner_name_input = gr.Textbox(label="HF Owner Name", placeholder="e.g., your-username")
|
548 |
+
space_name_input = gr.Textbox(label="HF Space Name", value="my-ai-space")
|
549 |
+
load_space_button = gr.Button("π Load Existing Space", variant="secondary")
|
550 |
+
|
551 |
+
with gr.Accordion("π€ AI Model Settings", open=True):
|
552 |
+
provider_select = gr.Dropdown(label="AI Provider", choices=get_available_providers(), value=get_default_model_for_provider(get_available_providers()[0] if get_available_providers() else None))
|
553 |
+
model_select = gr.Dropdown(label="AI Model", choices=[])
|
554 |
+
system_prompt_input = gr.Textbox(label="System Prompt", lines=10, value=DEFAULT_SYSTEM_PROMPT, elem_id="system-prompt")
|
555 |
+
|
556 |
+
with gr.Column(scale=2):
|
557 |
+
gr.Markdown("## π¬ AI Assistant Chat")
|
558 |
+
chatbot_display = gr.Chatbot(label="AI Chat", height=500, bubble_full_width=False, avatar_images=(None, "https://huggingface.co/datasets/huggingface/badges/resolve/main/huggingface-bot-avatar.svg"))
|
559 |
+
with gr.Row():
|
560 |
+
chat_message_input = gr.Textbox(show_label=False, placeholder="Your Message...", scale=7)
|
561 |
+
send_chat_button = gr.Button("Send", variant="primary", scale=1)
|
562 |
+
status_output = gr.Textbox(label="Last Action Status", interactive=False, value="Ready.")
|
563 |
+
|
564 |
+
with gr.Tabs():
|
565 |
+
with gr.TabItem("π Generated Markdown & Build"):
|
566 |
+
with gr.Row():
|
567 |
+
with gr.Column(scale=2):
|
568 |
+
formatted_space_output_display = gr.Textbox(label="Current Space Definition (Editable)", lines=20, interactive=True, value="*Load or create a space to see its definition.*")
|
569 |
+
download_button = gr.DownloadButton(label="Download .md", interactive=False)
|
570 |
+
with gr.Column(scale=1):
|
571 |
+
gr.Markdown("### Build Controls")
|
572 |
+
space_sdk_select = gr.Dropdown(label="Space SDK", choices=["gradio", "streamlit", "docker", "static"], value="gradio")
|
573 |
+
space_private_checkbox = gr.Checkbox(label="Make Space Private", value=False)
|
574 |
+
build_space_button = gr.Button("π Build / Update Space on HF", variant="primary")
|
575 |
+
build_status_display = gr.Textbox(label="Build Operation Status", interactive=False)
|
576 |
+
refresh_status_button = gr.Button("π Refresh Runtime Status")
|
577 |
+
space_runtime_status_display = gr.Markdown("*Runtime status will appear here.*")
|
578 |
+
|
579 |
+
with gr.TabItem("π Files Preview"):
|
580 |
+
detected_files_preview = gr.Markdown(value="*A preview of the latest file versions will appear here.*")
|
581 |
+
|
582 |
+
with gr.TabItem("βοΈ Live File Editor & Preview"):
|
583 |
+
with gr.Row():
|
584 |
+
with gr.Column(scale=1):
|
585 |
+
gr.Markdown("### Live Editor")
|
586 |
+
file_browser_dropdown = gr.Dropdown(label="Select File in Space", choices=[], interactive=True)
|
587 |
file_content_editor = gr.Code(label="File Content Editor", language="python", lines=15, interactive=True)
|
588 |
+
commit_message_input = gr.Textbox(label="Commit Message", placeholder="e.g., Updated app.py")
|
589 |
+
with gr.Row():
|
590 |
+
update_file_button = gr.Button("Commit Changes", variant="primary")
|
591 |
+
delete_file_button = gr.Button("ποΈ Delete Selected File", variant="stop")
|
592 |
+
edit_status_display = gr.Textbox(label="File Edit/Delete Status", interactive=False)
|
593 |
+
with gr.Column(scale=1):
|
594 |
+
gr.Markdown("### Live Space Preview")
|
595 |
+
space_iframe_display = gr.HTML(value="", visible=True)
|
596 |
+
|
597 |
+
# --- Event Listeners ---
|
598 |
+
provider_select.change(update_models_dropdown, inputs=provider_select, outputs=model_select)
|
599 |
+
|
600 |
+
chat_inputs = [chat_message_input, chatbot_display, hf_api_key_input, provider_select, model_select, system_prompt_input, owner_name_input, space_name_input, formatted_space_output_display]
|
601 |
chat_outputs = [chat_message_input, chatbot_display, status_output, detected_files_preview, formatted_space_output_display, download_button]
|
602 |
+
send_chat_button.click(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
|
603 |
+
chat_message_input.submit(handle_chat_submit, inputs=chat_inputs, outputs=chat_outputs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
604 |
|
605 |
load_space_outputs = [formatted_space_output_display, detected_files_preview, status_output, file_browser_dropdown, owner_name_input, space_name_input, space_iframe_display, download_button, build_status_display, edit_status_display, space_runtime_status_display, chatbot_display]
|
606 |
load_space_button.click(fn=handle_load_existing_space, inputs=[hf_api_key_input, owner_name_input, space_name_input], outputs=load_space_outputs)
|
607 |
|
608 |
build_outputs = [build_status_display, space_iframe_display, file_browser_dropdown, owner_name_input, space_name_input]
|
609 |
+
build_inputs = [hf_api_key_input, space_name_input, owner_name_input, space_sdk_select, space_private_checkbox, formatted_space_output_display]
|
610 |
+
build_space_button.click(fn=handle_build_space_button, inputs=build_inputs, outputs=build_outputs)
|
611 |
|
612 |
+
file_edit_load_outputs = [file_content_editor, edit_status_display, commit_message_input, file_content_editor] # last one updates language
|
613 |
file_browser_dropdown.change(fn=handle_load_file_for_editing, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown], outputs=file_edit_load_outputs)
|
614 |
|
615 |
commit_file_outputs = [edit_status_display, file_browser_dropdown, formatted_space_output_display, detected_files_preview, download_button]
|
616 |
update_file_button.click(fn=handle_commit_file_changes, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown, file_content_editor, commit_message_input], outputs=commit_file_outputs)
|
617 |
|
618 |
+
delete_file_outputs = [edit_status_display, file_browser_dropdown, file_content_editor, commit_message_input, file_content_editor, formatted_space_output_display, detected_files_preview, download_button]
|
619 |
delete_file_button.click(fn=handle_delete_file, inputs=[hf_api_key_input, space_name_input, owner_name_input, file_browser_dropdown], outputs=delete_file_outputs)
|
620 |
|
621 |
refresh_status_button.click(fn=handle_refresh_space_status, inputs=[hf_api_key_input, owner_name_input, space_name_input], outputs=[space_runtime_status_display])
|
622 |
|
623 |
if __name__ == "__main__":
|
624 |
+
demo.launch(debug=True)
|