Files
Comfyui-JSON-Manager/app.py
Ethanfel 8cc244e8be Add case-insensitive path resolution for Current Path input
Walks each path component and matches against actual directory entries
when an exact match fails on Linux. Widget resyncs to the corrected
canonical path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 15:52:48 +01:00

240 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import streamlit as st
import random
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, ALLOWED_BASE_DIR,
KEY_BATCH_DATA, KEY_PROMPT_HISTORY, KEY_SEQUENCE_NUMBER,
resolve_path_case_insensitive,
)
from tab_single import render_single_editor
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,
"edit_history_idx": lambda: None,
"single_editor_cache": lambda: DEFAULTS.copy(),
"ui_reset_token": lambda: 0,
"active_tab_name": lambda: "📝 Single Editor",
}
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.session_state.append_prompt = content
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 'append_prompt' in st.session_state: del st.session_state.append_prompt
if 'rand_seed' in st.session_state: del st.session_state.rand_seed
if 'restored_indicator' in st.session_state: del st.session_state.restored_indicator
st.session_state.edit_history_idx = None
# --- AUTO-SWITCH TAB LOGIC ---
is_batch = KEY_BATCH_DATA in data or isinstance(data, list)
if is_batch:
st.session_state.active_tab_name = "🚀 Batch Processor"
else:
st.session_state.active_tab_name = "📝 Single Editor"
else:
data = st.session_state.data_cache
st.title(f"Editing: {selected_file_name}")
# --- CONTROLLED NAVIGATION ---
# Removed "🔌 Comfy Monitor" from this list
tabs_list = [
"📝 Single Editor",
"🚀 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 == "📝 Single Editor":
render_single_editor(data, file_path)
elif 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()