Update app.py
Browse files
app.py
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import re
|
3 |
import json
|
@@ -83,7 +85,7 @@ If no code or actions are requested, respond conversationally and help the user
|
|
83 |
|
84 |
def escape_html_for_markdown(text):
|
85 |
if not isinstance(text, str): return ""
|
86 |
-
return text.replace("&", "&
|
87 |
|
88 |
def _infer_lang_from_filename(filename):
|
89 |
if not filename: return "plaintext"
|
@@ -350,30 +352,21 @@ def generate_and_stage_changes(ai_response_content, current_files_state, hf_owne
|
|
350 |
|
351 |
def handle_chat_submit(user_message, chat_history, hf_api_key_input, provider_api_key_input, provider_select, model_select, system_prompt, hf_owner_name, hf_repo_name):
|
352 |
global parsed_code_blocks_state_cache
|
353 |
-
_chat_msg_in
|
354 |
-
|
|
|
355 |
|
|
|
356 |
yield (
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
gr.update(), gr.update(), gr.update(), gr.update(),
|
367 |
-
[], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
368 |
-
)
|
369 |
-
return
|
370 |
-
|
371 |
-
_chat_hist.append((user_message, None))
|
372 |
-
_status = f"Sending to {model_select}..."
|
373 |
-
yield (
|
374 |
-
_chat_msg_in, _chat_hist, _status,
|
375 |
-
gr.update(), gr.update(), gr.update(), gr.update(),
|
376 |
-
[], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
377 |
)
|
378 |
|
379 |
current_sys_prompt = system_prompt.strip() or DEFAULT_SYSTEM_PROMPT
|
@@ -392,45 +385,59 @@ def handle_chat_submit(user_message, chat_history, hf_api_key_input, provider_ap
|
|
392 |
if chunk is None: continue
|
393 |
full_bot_response_content += str(chunk)
|
394 |
_chat_hist[-1] = (user_message, full_bot_response_content)
|
395 |
-
|
396 |
yield (
|
397 |
-
|
398 |
-
|
399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
)
|
401 |
|
|
|
|
|
402 |
if full_bot_response_content.startswith("Error:") or full_bot_response_content.startswith("API HTTP Error"):
|
403 |
_status = full_bot_response_content
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
|
|
|
|
|
|
409 |
|
|
|
410 |
parsed_code_blocks_state_cache, proposed_filenames_in_turn = _parse_and_update_state_cache(full_bot_response_content, parsed_code_blocks_state_cache)
|
|
|
|
|
411 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
412 |
|
|
|
413 |
staged_changeset, summary_md = generate_and_stage_changes(full_bot_response_content, parsed_code_blocks_state_cache, hf_owner_name, hf_repo_name)
|
414 |
|
|
|
415 |
if not staged_changeset:
|
416 |
-
_status = summary_md
|
417 |
yield (
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
423 |
)
|
424 |
else:
|
425 |
_status = "Change plan generated. Please review and confirm below."
|
426 |
yield (
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
gr.update(visible=True),
|
432 |
-
gr.update(visible=True),
|
433 |
-
gr.update(visible=True)
|
434 |
)
|
435 |
|
436 |
except Exception as e:
|
@@ -438,20 +445,26 @@ def handle_chat_submit(user_message, chat_history, hf_api_key_input, provider_ap
|
|
438 |
print(f"Error in handle_chat_submit: {e}")
|
439 |
import traceback
|
440 |
traceback.print_exc()
|
|
|
441 |
if _chat_hist:
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
|
|
|
|
|
|
|
|
447 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
448 |
|
|
|
449 |
yield (
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
|
455 |
)
|
456 |
|
457 |
|
@@ -540,45 +553,63 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
|
540 |
_confirm_ui_hidden = gr.update(visible=False)
|
541 |
|
542 |
outputs = [
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button
|
548 |
]
|
549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
|
551 |
owner_to_use = ui_owner_name
|
552 |
if not owner_to_use:
|
553 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
554 |
if token_err:
|
555 |
_status_val = f"Load Error: {token_err}"
|
556 |
-
|
|
|
557 |
try:
|
558 |
user_info = build_logic_whoami(token=token)
|
559 |
owner_to_use = user_info.get('name')
|
560 |
if not owner_to_use: raise Exception("Could not find user name from token.")
|
561 |
-
|
562 |
-
_status_val += f" (Auto-detected owner: {owner_to_use})"
|
563 |
except Exception as e:
|
564 |
-
_status_val = f"Load Error: Error auto-detecting owner: {e}"
|
|
|
|
|
565 |
|
566 |
if not owner_to_use or not ui_space_name:
|
567 |
-
_status_val = "Load Error: Owner and Space Name are required."
|
|
|
|
|
568 |
|
569 |
sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
|
570 |
|
571 |
-
|
572 |
-
|
|
|
573 |
|
574 |
if err:
|
575 |
_status_val = f"Load Error: {err}"
|
576 |
parsed_code_blocks_state_cache = []
|
577 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
|
|
|
|
|
|
|
|
582 |
|
583 |
loaded_files = []
|
584 |
for file_path in file_list:
|
@@ -592,19 +623,25 @@ def handle_load_existing_space(hf_api_key_ui, ui_owner_name, ui_space_name):
|
|
592 |
|
593 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
594 |
_status_val = f"Successfully loaded {len(file_list)} files from {owner_to_use}/{ui_space_name}. SDK: {sdk or 'unknown'}."
|
595 |
-
outputs[0], outputs[1], outputs[2], outputs[7] = _formatted, _detected, _status_val, _download
|
596 |
|
597 |
-
|
598 |
|
599 |
if owner_to_use and ui_space_name:
|
600 |
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_to_use.lower()).strip('-') or 'owner'
|
601 |
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name.lower()).strip('-') or 'space'
|
602 |
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk == 'static' else '.hf.space'}"
|
603 |
-
|
604 |
else:
|
605 |
-
|
606 |
|
607 |
-
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
608 |
|
609 |
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):
|
610 |
global parsed_code_blocks_state_cache
|
@@ -614,16 +651,26 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
614 |
_changeset_summary_clear = "*Manual build initiated, changes plan cleared.*"
|
615 |
_confirm_ui_hidden = gr.update(visible=False)
|
616 |
|
617 |
-
|
618 |
-
|
619 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
620 |
|
621 |
|
622 |
if not ui_space_name_part or "/" in ui_space_name_part:
|
623 |
_build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
|
624 |
-
yield (_build_status,
|
625 |
-
|
626 |
-
gr.update(), gr.update(), gr.update()); return
|
627 |
|
628 |
parsed_content = build_logic_parse_markdown(formatted_markdown_content)
|
629 |
proposed_files_list = parsed_content.get("files", [])
|
@@ -636,9 +683,8 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
636 |
|
637 |
if not manual_changeset:
|
638 |
_build_status = "Build Error: No target space specified or no files parsed from markdown."
|
639 |
-
yield (_build_status,
|
640 |
-
|
641 |
-
gr.update(), gr.update(), gr.update()); return
|
642 |
|
643 |
|
644 |
result_message = apply_staged_changes(hf_api_key_ui, ui_owner_name_part, ui_space_name_part, manual_changeset)
|
@@ -646,19 +692,27 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
646 |
|
647 |
owner_to_use = ui_owner_name_part
|
648 |
space_to_use = ui_space_name_part
|
649 |
-
|
|
|
|
|
|
|
650 |
|
651 |
-
|
652 |
-
|
653 |
-
|
|
|
|
|
|
|
|
|
654 |
|
|
|
655 |
sdk_built, file_list, err_list = get_space_repository_info(hf_api_key_ui, space_to_use, owner_to_use)
|
656 |
|
657 |
if err_list:
|
658 |
_build_status += f" | Error reloading file list after build: {err_list}"
|
659 |
parsed_code_blocks_state_cache = []
|
660 |
_file_browser_update = gr.update(visible=False, choices=[], value=None)
|
661 |
-
|
662 |
else:
|
663 |
loaded_files = []
|
664 |
for file_path in file_list:
|
@@ -675,16 +729,21 @@ def handle_build_space_button(hf_api_key_ui, ui_space_name_part, ui_owner_name_p
|
|
675 |
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_to_use.lower()).strip('-') or 'owner'
|
676 |
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name_part.lower()).strip('-') or 'space'
|
677 |
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk_built == 'static' else '.hf.space'}"
|
678 |
-
|
679 |
else:
|
680 |
-
|
|
|
681 |
|
682 |
-
_formatted_md, _detected_preview, _download = _generate_ui_outputs_from_cache(owner_to_use, space_to_use)
|
683 |
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
|
|
|
|
|
|
|
|
688 |
|
689 |
|
690 |
def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
|
@@ -700,12 +759,15 @@ def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_nam
|
|
700 |
return content, f"Loaded `{selected_file_path}`", commit_msg, gr.update(language=lang)
|
701 |
|
702 |
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):
|
|
|
|
|
703 |
if not file_to_edit_path:
|
704 |
return "Commit Error: No file selected for commit.", gr.update(), gr.update(), gr.update(), gr.update()
|
705 |
|
706 |
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)
|
707 |
|
708 |
global parsed_code_blocks_state_cache
|
|
|
709 |
if "Successfully" in status_msg:
|
710 |
found = False
|
711 |
for block in parsed_code_blocks_state_cache:
|
@@ -728,14 +790,12 @@ def handle_commit_file_changes(hf_api_key_ui, ui_space_name_part, ui_owner_name_
|
|
728 |
file_list, _ = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
729 |
file_browser_update = gr.update(choices=sorted(file_list or []))
|
730 |
|
731 |
-
else:
|
732 |
-
file_browser_update = gr.update()
|
733 |
-
|
734 |
-
|
735 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(ui_owner_name_part, ui_space_name_part)
|
736 |
-
return status_msg, file_browser_update, _formatted, _detected, _download
|
737 |
|
738 |
def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
|
|
|
|
|
739 |
if not file_to_delete_path:
|
740 |
return "Delete Error: No file selected to delete.", gr.update(), "", "", "plaintext", gr.update(), gr.update(), gr.update()
|
741 |
|
@@ -762,8 +822,8 @@ def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, fi
|
|
762 |
file_content_editor_update,
|
763 |
commit_message_update,
|
764 |
editor_lang_update,
|
765 |
-
_formatted,
|
766 |
-
_detected,
|
767 |
_download
|
768 |
)
|
769 |
|
@@ -972,4 +1032,4 @@ with gr.Blocks(theme=custom_theme, css=custom_css) as demo:
|
|
972 |
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])
|
973 |
|
974 |
if __name__ == "__main__":
|
975 |
-
demo.launch(debug=False, mcp_server=True)
|
|
|
1 |
+
### File: app.py
|
2 |
+
```python
|
3 |
import gradio as gr
|
4 |
import re
|
5 |
import json
|
|
|
85 |
|
86 |
def escape_html_for_markdown(text):
|
87 |
if not isinstance(text, str): return ""
|
88 |
+
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
89 |
|
90 |
def _infer_lang_from_filename(filename):
|
91 |
if not filename: return "plaintext"
|
|
|
352 |
|
353 |
def handle_chat_submit(user_message, chat_history, hf_api_key_input, provider_api_key_input, provider_select, model_select, system_prompt, hf_owner_name, hf_repo_name):
|
354 |
global parsed_code_blocks_state_cache
|
355 |
+
_chat_msg_in = user_message # Keep user input for clearing later
|
356 |
+
_chat_hist = list(chat_history)
|
357 |
+
_chat_hist.append((user_message, None)) # Append user message immediately
|
358 |
|
359 |
+
# Initial yield: Show loading state, clear input, clear/hide changes
|
360 |
yield (
|
361 |
+
"", # Clear chat_message_input
|
362 |
+
_chat_hist, # Update chat_history with user message
|
363 |
+
"Initializing...", # status_output
|
364 |
+
gr.update(), # Keep detected_files_preview
|
365 |
+
gr.update(), # Keep formatted_space_output_display
|
366 |
+
gr.update(interactive=False), # download_button (disable)
|
367 |
+
[], # changeset_state (clear)
|
368 |
+
"*No changes proposed.*", # changeset_display (clear text)
|
369 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) # Hide confirm UI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
)
|
371 |
|
372 |
current_sys_prompt = system_prompt.strip() or DEFAULT_SYSTEM_PROMPT
|
|
|
385 |
if chunk is None: continue
|
386 |
full_bot_response_content += str(chunk)
|
387 |
_chat_hist[-1] = (user_message, full_bot_response_content)
|
388 |
+
# Yield during streaming - ONLY update chatbot and status
|
389 |
yield (
|
390 |
+
gr.update(), # Keep chat_message_input cleared
|
391 |
+
_chat_hist, # Update chat_history with new chunk
|
392 |
+
f"Streaming from {model_select}...", # status_output
|
393 |
+
gr.update(), # Keep detected_files_preview
|
394 |
+
gr.update(), # Keep formatted_space_output_display
|
395 |
+
gr.update(), # Keep download_button
|
396 |
+
gr.update(), # Keep changeset_state
|
397 |
+
gr.update(), # Keep changeset_display
|
398 |
+
gr.update(), gr.update(), gr.update() # Keep confirm UI visibility
|
399 |
)
|
400 |
|
401 |
+
# --- Post-streaming: Parse AI output, update cache, stage changes, FINAL UI UPDATE ---
|
402 |
+
|
403 |
if full_bot_response_content.startswith("Error:") or full_bot_response_content.startswith("API HTTP Error"):
|
404 |
_status = full_bot_response_content
|
405 |
+
# Update chatbot with error in the last message, update status, clear/hide changes
|
406 |
+
yield (
|
407 |
+
gr.update(), _chat_hist, _status,
|
408 |
+
gr.update(), gr.update(), gr.update(), # Keep file previews as they were
|
409 |
+
[], "*Error occurred, changes plan cleared.*", # Clear changeset state and display
|
410 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) # Hide confirm UI
|
411 |
+
)
|
412 |
+
return # Exit handler
|
413 |
|
414 |
+
# Parse AI output and update state cache
|
415 |
parsed_code_blocks_state_cache, proposed_filenames_in_turn = _parse_and_update_state_cache(full_bot_response_content, parsed_code_blocks_state_cache)
|
416 |
+
|
417 |
+
# Regenerate UI previews based on the updated cache
|
418 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
419 |
|
420 |
+
# Generate changeset and summary
|
421 |
staged_changeset, summary_md = generate_and_stage_changes(full_bot_response_content, parsed_code_blocks_state_cache, hf_owner_name, hf_repo_name)
|
422 |
|
423 |
+
# Final yield based on whether changes were staged
|
424 |
if not staged_changeset:
|
425 |
+
_status = summary_md # Will be "No changes proposed" message
|
426 |
yield (
|
427 |
+
gr.update(), # Keep input box cleared
|
428 |
+
_chat_hist, _status, # Update chatbot (final), status
|
429 |
+
_detected, _formatted, _download, # Update file previews
|
430 |
+
[], summary_md, # Clear changeset state, display summary
|
431 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) # Hide confirm UI
|
432 |
)
|
433 |
else:
|
434 |
_status = "Change plan generated. Please review and confirm below."
|
435 |
yield (
|
436 |
+
gr.update(), # Keep input box cleared
|
437 |
+
_chat_hist, _status, # Update chatbot (final), status
|
438 |
+
_detected, _formatted, _download, # Update file previews
|
439 |
+
staged_changeset, summary_md, # Set changeset state, display summary
|
440 |
+
gr.update(visible=True), gr.update(visible=True), gr.update(visible=True) # Show confirm UI
|
|
|
|
|
441 |
)
|
442 |
|
443 |
except Exception as e:
|
|
|
445 |
print(f"Error in handle_chat_submit: {e}")
|
446 |
import traceback
|
447 |
traceback.print_exc()
|
448 |
+
# Update chatbot with error
|
449 |
if _chat_hist:
|
450 |
+
# Find the last message matching the user's input and append the error
|
451 |
+
try:
|
452 |
+
idx = next(i for i in reversed(range(len(_chat_hist))) if _chat_hist[i] and _chat_hist[i][0] == user_message)
|
453 |
+
current_bot_response = _chat_hist[idx][1] or ""
|
454 |
+
_chat_hist[idx] = (user_message, (current_bot_response + "\n\n" if current_bot_response else "") + error_msg)
|
455 |
+
except (StopIteration, IndexError):
|
456 |
+
_chat_hist.append((user_message, error_msg)) # Fallback if user message not found
|
457 |
+
|
458 |
+
# Regenerate previews from potentially partially updated cache
|
459 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
|
460 |
|
461 |
+
# Final error yield: Update chatbot, status, file previews, clear/hide changes
|
462 |
yield (
|
463 |
+
gr.update(), # Keep input box content
|
464 |
+
_chat_hist, error_msg, # Update chatbot, status
|
465 |
+
_detected, _formatted, _download, # Update file previews based on state before error
|
466 |
+
[], "*Error occurred, changes plan cleared.*", # Clear changeset state and display
|
467 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) # Hide confirm UI
|
468 |
)
|
469 |
|
470 |
|
|
|
553 |
_confirm_ui_hidden = gr.update(visible=False)
|
554 |
|
555 |
outputs = [
|
556 |
+
formatted_space_output_display, detected_files_preview, status_output,
|
557 |
+
file_browser_dropdown, owner_name_input, space_name_input,
|
558 |
+
space_iframe_display, download_button, build_status_display,
|
559 |
+
edit_status_display, space_runtime_status_display,
|
560 |
changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button
|
561 |
]
|
562 |
+
# Use gr.update for components that are not state variables
|
563 |
+
initial_updates = [
|
564 |
+
gr.update(value=_formatted_md_val), gr.update(value=_detected_preview_val), gr.update(value=_status_val), _file_browser_update,
|
565 |
+
gr.update(value=ui_owner_name), gr.update(value=ui_space_name),
|
566 |
+
_iframe_html_update, _download_btn_update, gr.update(value=_build_status_clear),
|
567 |
+
gr.update(value=_edit_status_clear), gr.update(value=_runtime_status_clear),
|
568 |
+
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden
|
569 |
+
]
|
570 |
+
|
571 |
+
yield initial_updates
|
572 |
|
573 |
owner_to_use = ui_owner_name
|
574 |
if not owner_to_use:
|
575 |
token, token_err = build_logic_get_api_token(hf_api_key_ui)
|
576 |
if token_err:
|
577 |
_status_val = f"Load Error: {token_err}"
|
578 |
+
yield gr.update(value=_status_val), # Only update status on early error
|
579 |
+
return
|
580 |
try:
|
581 |
user_info = build_logic_whoami(token=token)
|
582 |
owner_to_use = user_info.get('name')
|
583 |
if not owner_to_use: raise Exception("Could not find user name from token.")
|
584 |
+
yield gr.update(value=owner_to_use), gr.update(value=f"Loading Space: {owner_to_use}/{ui_space_name} (Auto-detected owner)...") # Update owner and status
|
|
|
585 |
except Exception as e:
|
586 |
+
_status_val = f"Load Error: Error auto-detecting owner: {e}"
|
587 |
+
yield gr.update(value=_status_val), # Only update status on early error
|
588 |
+
return
|
589 |
|
590 |
if not owner_to_use or not ui_space_name:
|
591 |
+
_status_val = "Load Error: Owner and Space Name are required."
|
592 |
+
yield gr.update(value=_status_val),
|
593 |
+
return
|
594 |
|
595 |
sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
|
596 |
|
597 |
+
# Always update owner/space inputs even on error, as user entered them
|
598 |
+
# These were updated in the initial yield, but we might need to update again if owner was auto-detected
|
599 |
+
yield gr.update(value=owner_to_use), gr.update(value=ui_space_name), # Update owner and space textboxes
|
600 |
|
601 |
if err:
|
602 |
_status_val = f"Load Error: {err}"
|
603 |
parsed_code_blocks_state_cache = []
|
604 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
605 |
+
yield (
|
606 |
+
gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val), # Update markdown, preview, status
|
607 |
+
gr.update(visible=False, choices=[], value=None), # Hide file browser
|
608 |
+
gr.update(), gr.update(), # Keep owner/space
|
609 |
+
gr.update(value=None, visible=False), # Hide iframe
|
610 |
+
_download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() # Rest are updates
|
611 |
+
)
|
612 |
+
return
|
613 |
|
614 |
loaded_files = []
|
615 |
for file_path in file_list:
|
|
|
623 |
|
624 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(owner_to_use, ui_space_name)
|
625 |
_status_val = f"Successfully loaded {len(file_list)} files from {owner_to_use}/{ui_space_name}. SDK: {sdk or 'unknown'}."
|
|
|
626 |
|
627 |
+
file_browser_update = gr.update(visible=True, choices=sorted(file_list or []), value=None)
|
628 |
|
629 |
if owner_to_use and ui_space_name:
|
630 |
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_to_use.lower()).strip('-') or 'owner'
|
631 |
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name.lower()).strip('-') or 'space'
|
632 |
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk == 'static' else '.hf.space'}"
|
633 |
+
iframe_update = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="500px"></iframe>', visible=True)
|
634 |
else:
|
635 |
+
iframe_update = gr.update(value=None, visible=False)
|
636 |
|
637 |
+
# Final yield after success
|
638 |
+
yield (
|
639 |
+
gr.update(value=_formatted), gr.update(value=_detected), gr.update(value=_status_val), # Update markdown, preview, status
|
640 |
+
file_browser_update, # Update file browser
|
641 |
+
gr.update(), gr.update(), # Keep owner/space
|
642 |
+
iframe_update, # Update iframe
|
643 |
+
_download, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() # Rest are updates
|
644 |
+
)
|
645 |
|
646 |
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):
|
647 |
global parsed_code_blocks_state_cache
|
|
|
651 |
_changeset_summary_clear = "*Manual build initiated, changes plan cleared.*"
|
652 |
_confirm_ui_hidden = gr.update(visible=False)
|
653 |
|
654 |
+
outputs = [
|
655 |
+
build_status_display, space_iframe_display, file_browser_dropdown,
|
656 |
+
owner_name_input, space_name_input,
|
657 |
+
changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button,
|
658 |
+
formatted_space_output_display, detected_files_preview, download_button
|
659 |
+
]
|
660 |
+
# Initial yield state
|
661 |
+
initial_updates = [
|
662 |
+
gr.update(value=_build_status), _iframe_html, _file_browser_update,
|
663 |
+
gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part),
|
664 |
+
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
|
665 |
+
gr.update(), gr.update(), gr.update() # Don't update markdown/download yet
|
666 |
+
]
|
667 |
+
yield initial_updates
|
668 |
|
669 |
|
670 |
if not ui_space_name_part or "/" in ui_space_name_part:
|
671 |
_build_status = f"Build Error: Invalid Space Name '{ui_space_name_part}'."
|
672 |
+
yield gr.update(value=_build_status), # Only update status
|
673 |
+
return
|
|
|
674 |
|
675 |
parsed_content = build_logic_parse_markdown(formatted_markdown_content)
|
676 |
proposed_files_list = parsed_content.get("files", [])
|
|
|
683 |
|
684 |
if not manual_changeset:
|
685 |
_build_status = "Build Error: No target space specified or no files parsed from markdown."
|
686 |
+
yield gr.update(value=_build_status), # Only update status
|
687 |
+
return
|
|
|
688 |
|
689 |
|
690 |
result_message = apply_staged_changes(hf_api_key_ui, ui_owner_name_part, ui_space_name_part, manual_changeset)
|
|
|
692 |
|
693 |
owner_to_use = ui_owner_name_part
|
694 |
space_to_use = ui_space_name_part
|
695 |
+
# Keep manual markdown content as is, clear/disable preview/download initially
|
696 |
+
_formatted_md = formatted_markdown_content
|
697 |
+
_detected_preview_val = "*Loading files after build...*"
|
698 |
+
_download_btn_update = gr.update(interactive=False, value=None)
|
699 |
|
700 |
+
# Yield intermediate state with build status and placeholders for reloading
|
701 |
+
yield (
|
702 |
+
gr.update(value=_build_status), _iframe_html, _file_browser_update, # Build status, iframe, file browser (initial clear state)
|
703 |
+
gr.update(value=owner_to_use), gr.update(value=space_to_use), # Update owner/space textboxes
|
704 |
+
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden, # Changeset clear/hide
|
705 |
+
gr.update(value=_formatted_md), gr.update(value=_detected_preview_val), _download_btn_update # Update markdown/preview/download
|
706 |
+
)
|
707 |
|
708 |
+
# Reload space state to reflect actual content after build attempt
|
709 |
sdk_built, file_list, err_list = get_space_repository_info(hf_api_key_ui, space_to_use, owner_to_use)
|
710 |
|
711 |
if err_list:
|
712 |
_build_status += f" | Error reloading file list after build: {err_list}"
|
713 |
parsed_code_blocks_state_cache = []
|
714 |
_file_browser_update = gr.update(visible=False, choices=[], value=None)
|
715 |
+
_iframe_html_update = gr.update(value=None, visible=False)
|
716 |
else:
|
717 |
loaded_files = []
|
718 |
for file_path in file_list:
|
|
|
729 |
sub_owner = re.sub(r'[^a-z0-9\-]+', '-', owner_to_use.lower()).strip('-') or 'owner'
|
730 |
sub_repo = re.sub(r'[^a-z0-9\-]+', '-', ui_space_name_part.lower()).strip('-') or 'space'
|
731 |
iframe_url = f"https://{sub_owner}-{sub_repo}{'.static.hf.space' if sdk_built == 'static' else '.hf.space'}"
|
732 |
+
_iframe_html_update = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="700px"></iframe>', visible=True)
|
733 |
else:
|
734 |
+
_iframe_html_update = gr.update(value=None, visible=False)
|
735 |
+
|
736 |
|
737 |
+
_formatted_md, _detected_preview, _download = _generate_ui_outputs_from_cache(owner_to_use, space_to_use) # Regenerate previews from reloaded cache
|
738 |
|
739 |
+
|
740 |
+
# Final yield after reloading
|
741 |
+
yield (
|
742 |
+
gr.update(value=_build_status), _iframe_html_update, _file_browser_update, # Build status, iframe, file browser
|
743 |
+
gr.update(value=owner_to_use), gr.update(value=space_to_use), # Update owner/space textboxes
|
744 |
+
_changeset_clear, gr.update(value=_changeset_summary_clear), _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden, # Changeset clear/hide
|
745 |
+
gr.update(value=_formatted_md), gr.update(value=_detected_preview), _download # Update markdown/preview/download
|
746 |
+
)
|
747 |
|
748 |
|
749 |
def handle_load_file_for_editing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, selected_file_path):
|
|
|
759 |
return content, f"Loaded `{selected_file_path}`", commit_msg, gr.update(language=lang)
|
760 |
|
761 |
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):
|
762 |
+
if not ui_owner_name_part or not ui_space_name_part:
|
763 |
+
return "Commit Error: Cannot commit changes. Please load a space first.", gr.update(), gr.update(), gr.update(), gr.update()
|
764 |
if not file_to_edit_path:
|
765 |
return "Commit Error: No file selected for commit.", gr.update(), gr.update(), gr.update(), gr.update()
|
766 |
|
767 |
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)
|
768 |
|
769 |
global parsed_code_blocks_state_cache
|
770 |
+
file_browser_update = gr.update()
|
771 |
if "Successfully" in status_msg:
|
772 |
found = False
|
773 |
for block in parsed_code_blocks_state_cache:
|
|
|
790 |
file_list, _ = list_space_files_for_browsing(hf_api_key_ui, ui_space_name_part, ui_owner_name_part)
|
791 |
file_browser_update = gr.update(choices=sorted(file_list or []))
|
792 |
|
|
|
|
|
|
|
|
|
793 |
_formatted, _detected, _download = _generate_ui_outputs_from_cache(ui_owner_name_part, ui_space_name_part)
|
794 |
+
return status_msg, file_browser_update, gr.update(value=_formatted), gr.update(value=_detected), _download
|
795 |
|
796 |
def handle_delete_file(hf_api_key_ui, ui_space_name_part, ui_owner_name_part, file_to_delete_path):
|
797 |
+
if not ui_owner_name_part or not ui_space_name_part:
|
798 |
+
return "Delete Error: Cannot delete file. Please load a space first.", gr.update(), "", "", "plaintext", gr.update(), gr.update(), gr.update()
|
799 |
if not file_to_delete_path:
|
800 |
return "Delete Error: No file selected to delete.", gr.update(), "", "", "plaintext", gr.update(), gr.update(), gr.update()
|
801 |
|
|
|
822 |
file_content_editor_update,
|
823 |
commit_message_update,
|
824 |
editor_lang_update,
|
825 |
+
gr.update(value=_formatted),
|
826 |
+
gr.update(value=_detected),
|
827 |
_download
|
828 |
)
|
829 |
|
|
|
1032 |
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])
|
1033 |
|
1034 |
if __name__ == "__main__":
|
1035 |
+
demo.launch(debug=False, mcp_server=True)
|