broadfield-dev commited on
Commit
62dcc8d
·
verified ·
1 Parent(s): 7165974

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -105
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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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, _chat_hist = "", list(chat_history)
354
- _status = "Initializing..."
 
355
 
 
356
  yield (
357
- _chat_msg_in, _chat_hist, _status,
358
- gr.update(), gr.update(), gr.update(interactive=False), gr.update(value="*No changes proposed.*"),
359
- [], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
360
- )
361
-
362
- if not user_message.strip():
363
- _status = "Cannot send an empty message."
364
- yield (
365
- _chat_msg_in, _chat_hist, _status,
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
- _status = f"Streaming from {model_select}..."
396
  yield (
397
- _chat_msg_in, _chat_hist, _status,
398
- gr.update(), gr.update(), gr.update(), gr.update(),
399
- [], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
 
 
 
 
 
 
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
- yield (_chat_msg_in, _chat_hist, _status, gr.update(), gr.update(), gr.update(), gr.update(), [], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
405
- return
406
-
407
- _status = "Stream complete. Parsing response and staging changes..."
408
- yield (_chat_msg_in, _chat_hist, _status, gr.update(), gr.update(), gr.update(), gr.update(), [], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
 
 
 
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
- _chat_msg_in, _chat_hist, _status,
419
- _detected, _formatted, _download,
420
- [],
421
- gr.update(value=summary_md),
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
- _chat_msg_in, _chat_hist, _status,
428
- _detected, _formatted, _download,
429
- staged_changeset,
430
- gr.update(value=summary_md),
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
- if _chat_hist[-1] and _chat_hist[-1][0] == user_message:
443
- _chat_hist[-1] = (user_message, (full_bot_response_content + "\n\n" if full_bot_response_content and full_bot_response_content != user_message else "") + error_msg)
444
- else:
445
- _chat_hist.append((user_message, error_msg))
446
-
 
 
 
 
447
  _formatted, _detected, _download = _generate_ui_outputs_from_cache(hf_owner_name, hf_repo_name)
448
 
 
449
  yield (
450
- _chat_msg_in, _chat_hist, error_msg,
451
- _detected, _formatted, _download,
452
- [],
453
- gr.update(value="*Error occurred, changes plan cleared.*"),
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
- _formatted_md_val, _detected_preview_val, _status_val, _file_browser_update,
544
- gr.update(value=ui_owner_name), gr.update(value=ui_space_name),
545
- _iframe_html_update, _download_btn_update, _build_status_clear,
546
- _edit_status_clear, _runtime_status_clear,
547
  changeset_state, changeset_display, confirm_accordion, confirm_button, cancel_button
548
  ]
549
- yield outputs
 
 
 
 
 
 
 
 
 
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
- outputs[2] = _status_val; yield outputs; return
 
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
- outputs[4] = gr.update(value=owner_to_use)
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}"; outputs[2] = _status_val; yield outputs; return
 
 
565
 
566
  if not owner_to_use or not ui_space_name:
567
- _status_val = "Load Error: Owner and Space Name are required."; outputs[2] = _status_val; yield outputs; return
 
 
568
 
569
  sdk, file_list, err = get_space_repository_info(hf_api_key_ui, ui_space_name, owner_to_use)
570
 
571
- outputs[4] = gr.update(value=owner_to_use)
572
- outputs[5] = gr.update(value=ui_space_name)
 
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
- outputs[0], outputs[1], outputs[2], outputs[7] = _formatted, _detected, _status_val, _download
579
- outputs[3] = gr.update(visible=False, choices=[], value=None)
580
- outputs[6] = gr.update(value=None, visible=False)
581
- yield outputs; return
 
 
 
 
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
- outputs[3] = gr.update(visible=True, choices=sorted(file_list or []), value=None)
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
- outputs[6] = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="500px"></iframe>', visible=True)
604
  else:
605
- outputs[6] = gr.update(value=None, visible=False)
606
 
607
- yield outputs
 
 
 
 
 
 
 
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
- yield (_build_status, _iframe_html, _file_browser_update, gr.update(value=ui_owner_name_part), gr.update(value=ui_space_name_part),
618
- _changeset_clear, _changeset_summary_clear, _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
619
- gr.update(), gr.update(), gr.update())
 
 
 
 
 
 
 
 
 
 
 
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, _iframe_html, _file_browser_update, gr.update(), gr.update(),
625
- gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
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, _iframe_html, _file_browser_update, gr.update(), gr.update(),
640
- gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
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
- _formatted_md, _detected_preview, _download = formatted_markdown_content, "*Loading files after build...*", gr.update(interactive=False, value=None)
 
 
 
650
 
651
- yield (_build_status, _iframe_html, _file_browser_update, gr.update(value=owner_to_use), gr.update(value=space_to_use),
652
- _changeset_clear, _changeset_summary_clear, _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
653
- _formatted_md, _detected_preview, _download)
 
 
 
 
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
- _iframe_html = gr.update(value=None, visible=False)
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
- _iframe_html = gr.update(value=f'<iframe src="{iframe_url}?__theme=light&embed=true" width="100%" height="700px"></iframe>', visible=True)
679
  else:
680
- _iframe_html = gr.update(value=None, visible=False)
 
681
 
682
- _formatted_md, _detected_preview, _download = _generate_ui_outputs_from_cache(owner_to_use, space_to_use)
683
 
684
- yield (_build_status, _iframe_html, _file_browser_update, gr.update(value=owner_to_use), gr.update(value=space_to_use),
685
- _changeset_clear, _changeset_summary_clear, _confirm_ui_hidden, _confirm_ui_hidden, _confirm_ui_hidden,
686
- _formatted_md, _detected_preview, _download
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)