Update app.py

This commit is contained in:
2026-01-03 16:17:22 +01:00
committed by GitHub
parent 5f3f8e2076
commit 2ed1c8a3cd

151
app.py
View File

@@ -1,49 +1,59 @@
import streamlit as st import streamlit as st
import random import random
from pathlib import Path from pathlib import Path
import time
# --- Import Custom Modules --- # --- Import Custom Modules ---
from utils import ( from db_manager import DatabaseManager
load_config, save_config, load_snippets, save_snippets, # We retain your UI modules, but they should now just receive data/dicts
load_json, save_json, generate_templates, DEFAULTS
)
from tab_single import render_single_editor from tab_single import render_single_editor
from tab_batch import render_batch_processor from tab_batch import render_batch_processor
from tab_timeline import render_timeline_tab from tab_timeline import render_timeline_tab
from tab_timeline_wip import render_timeline_wip from tab_timeline_wip import render_timeline_wip
from tab_comfy import render_comfy_monitor from tab_comfy import render_comfy_monitor
# Defined locally to avoid missing utils import, or keep your utils for DEFAULTS only
DEFAULTS = {
"positive_prompt": "",
"negative_prompt": "",
"seed": -1,
"steps": 20,
"cfg": 7.0,
}
# ========================================== # ==========================================
# 1. PAGE CONFIGURATION # 1. PAGE CONFIGURATION & DB INIT
# ========================================== # ==========================================
st.set_page_config(layout="wide", page_title="AI Settings Manager") st.set_page_config(layout="wide", page_title="AI Settings Manager (DB)")
# Initialize Database Manager
if 'db' not in st.session_state:
st.session_state.db = DatabaseManager()
# ========================================== # ==========================================
# 2. SESSION STATE INITIALIZATION # 2. SESSION STATE INITIALIZATION
# ========================================== # ==========================================
# Load App Config from DB
if 'config' not in st.session_state: if 'config' not in st.session_state:
st.session_state.config = load_config() st.session_state.config = st.session_state.db.load_app_config()
# --- MIGRATION CHECK ---
# If DB is empty, try to migrate from the folder in 'last_dir' or current cwd
if st.session_state.db.is_empty():
migration_target = st.session_state.config.get("last_dir", Path.cwd())
st.session_state.db.migrate_from_json(migration_target)
# Reload config after potential migration
st.session_state.config = st.session_state.db.load_app_config()
st.session_state.current_dir = Path(st.session_state.config.get("last_dir", Path.cwd())) st.session_state.current_dir = Path(st.session_state.config.get("last_dir", Path.cwd()))
if 'snippets' not in st.session_state: if 'snippets' not in st.session_state:
st.session_state.snippets = load_snippets() st.session_state.snippets = st.session_state.db.load_snippets()
if 'loaded_file' not in st.session_state: if 'loaded_file' not in st.session_state:
st.session_state.loaded_file = None st.session_state.loaded_file = None
if 'last_mtime' not in st.session_state: # Track the active tab state
st.session_state.last_mtime = 0
if 'edit_history_idx' not in st.session_state:
st.session_state.edit_history_idx = None
if 'single_editor_cache' not in st.session_state:
st.session_state.single_editor_cache = DEFAULTS.copy()
if 'ui_reset_token' not in st.session_state:
st.session_state.ui_reset_token = 0
# Track the active tab state for programmatic switching
if 'active_tab_name' not in st.session_state: if 'active_tab_name' not in st.session_state:
st.session_state.active_tab_name = "📝 Single Editor" st.session_state.active_tab_name = "📝 Single Editor"
@@ -54,20 +64,21 @@ with st.sidebar:
st.header("📂 Navigator") st.header("📂 Navigator")
# --- Path Navigator --- # --- Path Navigator ---
# Note: User still navigates folders to FIND projects, but projects live in DB.
new_path = st.text_input("Current Path", value=str(st.session_state.current_dir)) new_path = st.text_input("Current Path", value=str(st.session_state.current_dir))
if new_path != str(st.session_state.current_dir): if new_path != str(st.session_state.current_dir):
p = Path(new_path) p = Path(new_path)
if p.exists() and p.is_dir(): if p.exists() and p.is_dir():
st.session_state.current_dir = p st.session_state.current_dir = p
st.session_state.config['last_dir'] = str(p) st.session_state.config['last_dir'] = str(p)
save_config(st.session_state.current_dir, st.session_state.config['favorites']) st.session_state.db.save_app_config(st.session_state.config)
st.rerun() st.rerun()
# --- Favorites System --- # --- Favorites System ---
if st.button("📌 Pin Current Folder"): if st.button("📌 Pin Current Folder"):
if str(st.session_state.current_dir) not in st.session_state.config['favorites']: 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)) 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.session_state.db.save_app_config(st.session_state.config)
st.rerun() st.rerun()
fav_selection = st.radio( fav_selection = st.radio(
@@ -82,15 +93,15 @@ with st.sidebar:
st.markdown("---") st.markdown("---")
# --- Snippet Library --- # --- Snippet Library (DB Backed) ---
st.subheader("🧩 Snippet Library") st.subheader("🧩 Snippet Library")
with st.expander("Add New Snippet"): with st.expander("Add New Snippet"):
snip_name = st.text_input("Name", placeholder="e.g. Cinematic") snip_name = st.text_input("Name", placeholder="e.g. Cinematic")
snip_content = st.text_area("Content", placeholder="4k, high quality...") snip_content = st.text_area("Content", placeholder="4k, high quality...")
if st.button("Save Snippet"): if st.button("Save Snippet"):
if snip_name and snip_content: if snip_name and snip_content:
st.session_state.snippets[snip_name] = snip_content st.session_state.db.save_snippet(snip_name, snip_content)
save_snippets(st.session_state.snippets) st.session_state.snippets = st.session_state.db.load_snippets() # Reload
st.success(f"Saved '{snip_name}'") st.success(f"Saved '{snip_name}'")
st.rerun() st.rerun()
@@ -102,43 +113,47 @@ with st.sidebar:
st.session_state.append_prompt = content st.session_state.append_prompt = content
st.rerun() st.rerun()
if col_s2.button("🗑️", key=f"del_snip_{name}"): if col_s2.button("🗑️", key=f"del_snip_{name}"):
del st.session_state.snippets[name] st.session_state.db.delete_snippet(name)
save_snippets(st.session_state.snippets) st.session_state.snippets = st.session_state.db.load_snippets()
st.rerun() st.rerun()
st.markdown("---") st.markdown("---")
# --- File List & Creation --- # --- File List (From DB) ---
json_files = sorted(list(st.session_state.current_dir.glob("*.json"))) # We query the DB for projects that belong to the current directory
json_files = [f for f in json_files if f.name != ".editor_config.json" and f.name != ".editor_snippets.json"] db_files = st.session_state.db.get_projects_in_dir(st.session_state.current_dir)
if not json_files:
if st.button("Generate Templates"):
generate_templates(st.session_state.current_dir)
st.rerun()
with st.expander("Create New JSON"): with st.expander("Create New Project"):
new_filename = st.text_input("Filename", placeholder="my_prompt_vace") new_filename = st.text_input("Project Name", placeholder="my_prompt_vace")
is_batch = st.checkbox("Is Batch File?") is_batch = st.checkbox("Is Batch File?")
if st.button("Create"): if st.button("Create"):
if not new_filename.endswith(".json"): new_filename += ".json" if not new_filename.endswith(".json"): new_filename += ".json" # Keep extension for legacy feel/compatibility
path = st.session_state.current_dir / new_filename
# Init Data
if is_batch: if is_batch:
data = {"batch_data": []} data = {"batch_data": []}
else: else:
data = DEFAULTS.copy() data = DEFAULTS.copy()
if "vace" in new_filename: data.update({"frame_to_skip": 81, "vace schedule": 1, "video file path": ""}) if "vace" in new_filename: data.update({"frame_to_skip": 81, "vace schedule": 1, "video file path": ""})
elif "i2v" in new_filename: data.update({"reference image path": "", "flf image path": ""})
save_json(path, data) # Save to DB
full_path = st.session_state.current_dir / new_filename
st.session_state.db.save_project(full_path, data, is_batch)
st.rerun() st.rerun()
# --- File Selector --- # --- File Selector ---
if 'file_selector' not in st.session_state: if 'file_selector' not in st.session_state:
st.session_state.file_selector = json_files[0].name if json_files else None st.session_state.file_selector = db_files[0] if db_files else None
if st.session_state.file_selector not in [f.name for f in json_files] and json_files:
st.session_state.file_selector = json_files[0].name
selected_file_name = st.radio("Select File", [f.name for f in json_files], key="file_selector") # If list is empty or current selection not in list, reset
if db_files and (st.session_state.file_selector not in db_files):
st.session_state.file_selector = db_files[0]
selected_file_name = None
if db_files:
selected_file_name = st.radio("Select Project", db_files, key="file_selector")
else:
st.info("No projects found in this folder.")
# ========================================== # ==========================================
# 4. MAIN APP LOGIC # 4. MAIN APP LOGIC
@@ -146,22 +161,17 @@ with st.sidebar:
if selected_file_name: if selected_file_name:
file_path = st.session_state.current_dir / selected_file_name file_path = st.session_state.current_dir / selected_file_name
# --- FILE LOADING & AUTO-SWITCH LOGIC --- # --- LOAD FROM DB ---
# We reload if the file changed OR if we just performed a save (to get updates)
if st.session_state.loaded_file != str(file_path): if st.session_state.loaded_file != str(file_path):
data, mtime = load_json(file_path) data = st.session_state.db.load_project(file_path)
st.session_state.data_cache = data st.session_state.data_cache = data
st.session_state.last_mtime = mtime
st.session_state.loaded_file = str(file_path) st.session_state.loaded_file = str(file_path)
# Clear transient states # Clear transient states
if 'append_prompt' in st.session_state: del st.session_state.append_prompt 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 --- # --- AUTO-SWITCH TAB LOGIC ---
# If the file has 'batch_data' or is a list, force Batch tab.
# Otherwise, force Single tab.
is_batch = "batch_data" in data or isinstance(data, list) is_batch = "batch_data" in data or isinstance(data, list)
if is_batch: if is_batch:
st.session_state.active_tab_name = "🚀 Batch Processor" st.session_state.active_tab_name = "🚀 Batch Processor"
@@ -173,8 +183,7 @@ if selected_file_name:
st.title(f"Editing: {selected_file_name}") st.title(f"Editing: {selected_file_name}")
# --- CONTROLLED NAVIGATION (REPLACES ST.TABS) --- # --- NAVIGATION ---
# Using radio buttons allows us to change 'active_tab_name' programmatically above.
tabs_list = [ tabs_list = [
"📝 Single Editor", "📝 Single Editor",
"🚀 Batch Processor", "🚀 Batch Processor",
@@ -183,26 +192,40 @@ if selected_file_name:
"🔌 Comfy Monitor" "🔌 Comfy Monitor"
] ]
# Ensure active tab is valid (safety check) # Sync radio with session state
if st.session_state.active_tab_name not in tabs_list:
st.session_state.active_tab_name = tabs_list[0]
current_tab = st.radio( current_tab = st.radio(
"Navigation", "Navigation",
tabs_list, tabs_list,
key="active_tab_name", # Binds to session state index=tabs_list.index(st.session_state.active_tab_name) if st.session_state.active_tab_name in tabs_list else 0,
horizontal=True, horizontal=True,
label_visibility="collapsed" label_visibility="collapsed"
) )
st.session_state.active_tab_name = current_tab
st.markdown("---") st.markdown("---")
# --- RENDER SELECTED TAB --- # --- RENDER TABS ---
# Note: Inside your render functions (tab_single.py etc), you likely have a "Save" button.
# You must update those files to call st.session_state.db.save_project() instead of save_json().
# OR, we can handle the save here if we pass a callback or check for state changes.
# For now, assuming render functions modify 'data' dictionary in place:
if current_tab == "📝 Single Editor": if current_tab == "📝 Single Editor":
render_single_editor(data, file_path) # Pass data. Return value could be ignored if render modifies dict in place
render_single_editor(data, file_path)
# SAVE BUTTON for Single Editor (Global override)
if st.button("💾 Save Changes to DB", key="save_main"):
st.session_state.db.save_project(file_path, data)
st.success("Saved to Database!")
elif current_tab == "🚀 Batch Processor": elif current_tab == "🚀 Batch Processor":
render_batch_processor(data, file_path, json_files, st.session_state.current_dir, selected_file_name) # We pass the list of 'db_files' instead of 'json_files'
render_batch_processor(data, file_path, db_files, st.session_state.current_dir, selected_file_name)
if st.button("💾 Save Batch to DB", key="save_batch"):
st.session_state.db.save_project(file_path, data, is_batch=True)
st.success("Batch Saved!")
elif current_tab == "🕒 Timeline": elif current_tab == "🕒 Timeline":
render_timeline_tab(data, file_path) render_timeline_tab(data, file_path)