2121 TK_AVAILABLE = False
2222
2323from src .common .captcha_ import captcha_control
24+ from src .common .admin import (
25+ is_admin_configured ,
26+ verify_admin_password ,
27+ demo_exists ,
28+ save_workspace_as_demo ,
29+ )
2430
2531# Detect system platform
2632OS_PLATFORM = sys .platform
@@ -122,6 +128,8 @@ def _symlink_tree(source: Path, target: Path) -> None:
122128
123129 Creates real directories but symlinks individual files, allowing users to
124130 add new files to workspace directories without affecting the original.
131+ params.json and .ini files are copied instead of symlinked so they can be
132+ modified independently.
125133
126134 Args:
127135 source: Source directory path.
@@ -132,6 +140,9 @@ def _symlink_tree(source: Path, target: Path) -> None:
132140 target_item = target / item .name
133141 if item .is_dir ():
134142 _symlink_tree (item , target_item )
143+ elif item .name == "params.json" or item .suffix == ".ini" :
144+ # Copy config files so they can be modified independently
145+ shutil .copy2 (item , target_item )
135146 else :
136147 # Create symlink to the source file
137148 target_item .symlink_to (item .resolve ())
@@ -610,14 +621,83 @@ def change_workspace():
610621 else :
611622 if target .exists ():
612623 target .unlink ()
613- if OS_PLATFORM == "linux" :
624+ # Copy config files so they can be modified independently
625+ if OS_PLATFORM == "linux" and item .name != "params.json" and item .suffix != ".ini" :
614626 target .symlink_to (item .resolve ())
615627 else :
616628 shutil .copy2 (item , target )
617629 st .success (f"Demo data '{ selected_demo } ' loaded!" )
618630 time .sleep (1 )
619631 st .rerun ()
620632
633+ # Save as Demo section (online mode only)
634+ with st .expander ("💾 **Save as Demo**" ):
635+ st .caption ("Save current workspace as a demo for others to use" )
636+
637+ demo_name_input = st .text_input (
638+ "Demo name" ,
639+ key = "save-demo-name" ,
640+ placeholder = "e.g., workshop-2024" ,
641+ help = "Name for the demo workspace (no spaces or special characters)"
642+ )
643+
644+ # Check if demo already exists
645+ demo_name_clean = demo_name_input .strip () if demo_name_input else ""
646+ existing_demo = demo_exists (demo_name_clean ) if demo_name_clean else False
647+
648+ if existing_demo :
649+ st .warning (f"Demo '{ demo_name_clean } ' already exists and will be overwritten." )
650+ confirm_overwrite = st .checkbox (
651+ "Confirm overwrite" ,
652+ key = "confirm-demo-overwrite"
653+ )
654+ else :
655+ confirm_overwrite = True # No confirmation needed for new demos
656+
657+ if st .button ("Save as Demo" , key = "save-demo-btn" , disabled = not demo_name_clean ):
658+ if not is_admin_configured ():
659+ st .error (
660+ "Admin not configured. Create `.streamlit/secrets.toml` with "
661+ "an `[admin]` section containing `password = \" your-password\" `"
662+ )
663+ elif existing_demo and not confirm_overwrite :
664+ st .error ("Please confirm overwrite to continue." )
665+ else :
666+ # Show password dialog
667+ st .session_state ["show_admin_password_dialog" ] = True
668+
669+ # Password dialog (shown after clicking Save as Demo)
670+ if st .session_state .get ("show_admin_password_dialog" , False ):
671+ admin_password = st .text_input (
672+ "Admin password" ,
673+ type = "password" ,
674+ key = "admin-password-input" ,
675+ help = "Enter the admin password to save this workspace as a demo"
676+ )
677+
678+ col1 , col2 = st .columns (2 )
679+ with col1 :
680+ if st .button ("Confirm" , key = "confirm-save-demo" ):
681+ if verify_admin_password (admin_password ):
682+ success , message = save_workspace_as_demo (
683+ st .session_state .workspace ,
684+ demo_name_clean
685+ )
686+ if success :
687+ st .success (message )
688+ st .session_state ["show_admin_password_dialog" ] = False
689+ time .sleep (1 )
690+ st .rerun ()
691+ else :
692+ st .error (message )
693+ else :
694+ st .error ("Invalid admin password." )
695+
696+ with col2 :
697+ if st .button ("Cancel" , key = "cancel-save-demo" ):
698+ st .session_state ["show_admin_password_dialog" ] = False
699+ st .rerun ()
700+
621701 # All pages have settings, workflow indicator and logo
622702 with st .expander ("⚙️ **Settings**" ):
623703 img_formats = ["svg" , "png" , "jpeg" , "webp" ]
0 commit comments