import streamlit as st from pathlib import Path # --- Import Custom Modules --- from utils import ( load_config, save_config, load_snippets, save_snippets, load_json, save_json, generate_templates, DEFAULTS, KEY_BATCH_DATA, KEY_SEQUENCE_NUMBER, resolve_path_case_insensitive, ) from tab_batch import render_batch_processor from tab_timeline import render_timeline_tab from tab_comfy import render_comfy_monitor from tab_raw import render_raw_editor # ========================================== # 1. PAGE CONFIGURATION # ========================================== st.set_page_config(layout="wide", page_title="AI Settings Manager") # ========================================== # 2. SESSION STATE INITIALIZATION # ========================================== _SESSION_DEFAULTS = { "snippets": load_snippets, "loaded_file": lambda: None, "last_mtime": lambda: 0, "ui_reset_token": lambda: 0, "active_tab_name": lambda: "🚀 Batch Processor", } if 'config' not in st.session_state: st.session_state.config = load_config() st.session_state.current_dir = Path(st.session_state.config.get("last_dir", Path.cwd())) for key, factory in _SESSION_DEFAULTS.items(): if key not in st.session_state: st.session_state[key] = factory() # ========================================== # 3. SIDEBAR (NAVIGATOR & TOOLS) # ========================================== with st.sidebar: st.header("📂 Navigator") # --- Path Navigator --- # Sync widget to current_dir on first load or after external change if "nav_path_input" not in st.session_state or st.session_state.get("_sync_nav_path"): st.session_state.nav_path_input = str(st.session_state.current_dir) st.session_state._sync_nav_path = False def _on_path_change(): new_path = st.session_state.nav_path_input p = resolve_path_case_insensitive(new_path) if p is not None and p.is_dir(): st.session_state.current_dir = p st.session_state.config['last_dir'] = str(p) save_config(st.session_state.current_dir, st.session_state.config['favorites']) st.session_state.loaded_file = None # Always resync widget to canonical path form st.session_state._sync_nav_path = True st.text_input("Current Path", key="nav_path_input", on_change=_on_path_change) # --- Favorites System --- if st.button("📌 Pin Folder", use_container_width=True): if str(st.session_state.current_dir) not in st.session_state.config['favorites']: st.session_state.config['favorites'].append(str(st.session_state.current_dir)) save_config(st.session_state.current_dir, st.session_state.config['favorites']) st.rerun() favorites = st.session_state.config['favorites'] if favorites: def _on_fav_jump(): sel = st.session_state._fav_radio if sel != "Select..." and sel != str(st.session_state.current_dir): st.session_state.current_dir = Path(sel) st.session_state._sync_nav_path = True st.radio( "Jump to:", ["Select..."] + favorites, index=0, key="_fav_radio", label_visibility="collapsed", on_change=_on_fav_jump, ) # Unpin buttons for each favorite for fav in favorites: fc1, fc2 = st.columns([4, 1]) fc1.caption(fav) if fc2.button("❌", key=f"unpin_{fav}"): st.session_state.config['favorites'].remove(fav) save_config(st.session_state.current_dir, st.session_state.config['favorites']) st.rerun() st.markdown("---") # --- Snippet Library --- st.subheader("🧩 Snippet Library") with st.expander("Add New Snippet"): snip_name = st.text_input("Name", placeholder="e.g. Cinematic") snip_content = st.text_area("Content", placeholder="4k, high quality...") if st.button("Save Snippet"): if snip_name and snip_content: st.session_state.snippets[snip_name] = snip_content save_snippets(st.session_state.snippets) st.success(f"Saved '{snip_name}'") st.rerun() if st.session_state.snippets: st.caption("Click to Append to Prompt:") for name, content in st.session_state.snippets.items(): col_s1, col_s2 = st.columns([4, 1]) if col_s1.button(f"➕ {name}", use_container_width=True): st.rerun() if col_s2.button("🗑️", key=f"del_snip_{name}"): del st.session_state.snippets[name] save_snippets(st.session_state.snippets) st.rerun() st.markdown("---") # --- File List & Creation --- json_files = sorted(list(st.session_state.current_dir.glob("*.json"))) json_files = [f for f in json_files if f.name != ".editor_config.json" and f.name != ".editor_snippets.json"] if not json_files: if st.button("Generate Templates"): generate_templates(st.session_state.current_dir) st.rerun() with st.expander("Create New JSON"): new_filename = st.text_input("Filename", placeholder="my_prompt_vace") if st.button("Create"): if not new_filename.endswith(".json"): new_filename += ".json" path = st.session_state.current_dir / new_filename first_item = DEFAULTS.copy() first_item[KEY_SEQUENCE_NUMBER] = 1 data = {KEY_BATCH_DATA: [first_item]} save_json(path, data) st.rerun() # --- File Selector --- selected_file_name = None if json_files: file_names = [f.name for f in json_files] if 'file_selector' not in st.session_state: st.session_state.file_selector = file_names[0] if st.session_state.file_selector not in file_names: st.session_state.file_selector = file_names[0] selected_file_name = st.radio("Select File", file_names, key="file_selector") else: st.info("No JSON files in this folder.") if 'file_selector' in st.session_state: del st.session_state.file_selector st.session_state.loaded_file = None # --- GLOBAL MONITOR TOGGLE (NEW) --- st.markdown("---") show_monitor = st.checkbox("Show Comfy Monitor", value=True) # ========================================== # 4. MAIN APP LOGIC # ========================================== if selected_file_name: file_path = st.session_state.current_dir / selected_file_name # --- FILE LOADING & AUTO-SWITCH LOGIC --- if st.session_state.loaded_file != str(file_path): data, mtime = load_json(file_path) st.session_state.data_cache = data st.session_state.last_mtime = mtime st.session_state.loaded_file = str(file_path) # Clear transient states if 'restored_indicator' in st.session_state: del st.session_state.restored_indicator # --- AUTO-SWITCH TAB LOGIC --- st.session_state.active_tab_name = "🚀 Batch Processor" else: data = st.session_state.data_cache st.title(f"Editing: {selected_file_name}") # --- CONTROLLED NAVIGATION --- # Removed "🔌 Comfy Monitor" from this list tabs_list = [ "🚀 Batch Processor", "🕒 Timeline", "💻 Raw Editor" ] if st.session_state.active_tab_name not in tabs_list: st.session_state.active_tab_name = tabs_list[0] current_tab = st.radio( "Navigation", tabs_list, key="active_tab_name", horizontal=True, label_visibility="collapsed" ) st.markdown("---") # --- RENDER EDITOR TABS --- if current_tab == "🚀 Batch Processor": render_batch_processor(data, file_path, json_files, st.session_state.current_dir, selected_file_name) elif current_tab == "🕒 Timeline": render_timeline_tab(data, file_path) elif current_tab == "💻 Raw Editor": render_raw_editor(data, file_path) # --- GLOBAL PERSISTENT MONITOR --- if show_monitor: st.markdown("---") with st.expander("🔌 ComfyUI Monitor", expanded=True): render_comfy_monitor()