Files
Comfyui-JSON-Manager/stream_editor.py
ethanfel 16563db2f6 Add general prompts and seed management to defaults
Added new keys for general prompts and seed management in the DEFAULTS dictionary. Updated various sections to incorporate these changes, including the history and editing functionalities.
2025-12-31 12:13:32 +01:00

446 lines
20 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 json
import os
import time
import random
from pathlib import Path
# --- Configuration ---
CONFIG_FILE = Path(".editor_config.json")
SNIPPETS_FILE = Path(".editor_snippets.json")
st.set_page_config(layout="wide", page_title="AI Settings Manager")
# Defaults
DEFAULTS = {
"camera": "Camera stand still. Motion starts immediately.",
"flf": 0,
"seed": 0,
"frame_to_skip": 81,
"input_a_frames": "",
"input_b_frames": "",
"reference path": "",
"reference switch": 1,
"vace schedule": 1,
"video file path": "",
"reference image path": "",
"flf image path": "",
# --- PROMPTS ---
"general_prompt": "", # NEW: The global layer
"general_negative": "Vivid tones, overexposed, static, blurry details, subtitles, style, artwork, painting, picture, still image, overall gray, worst quality, low quality, JPEG compression artifacts, ugly, deformed, extra fingers, poorly drawn hands, poorly drawn face, distorted, disfigured, malformed limbs, fused fingers, unmoving frame, cluttered background, three legs,",
"current_prompt": "", # The specific layer
"negative": "", # The specific layer
# --- LORAS ---
"lora 1 high": "", "lora 1 low": "",
"lora 2 high": "", "lora 2 low": "",
"lora 3 high": "", "lora 3 low": "",
"prompt_history": []
}
# Only these two types exist now
GENERIC_TEMPLATES = ["prompt_i2v.json", "prompt_vace_extend.json"]
# --- Helper Functions ---
def load_config():
if CONFIG_FILE.exists():
try:
with open(CONFIG_FILE, 'r') as f:
return json.load(f)
except: pass
return {"last_dir": str(Path.cwd()), "favorites": []}
def save_config(current_dir, favorites):
with open(CONFIG_FILE, 'w') as f:
json.dump({"last_dir": str(current_dir), "favorites": favorites}, f, indent=4)
def load_snippets():
if SNIPPETS_FILE.exists():
try:
with open(SNIPPETS_FILE, 'r') as f:
return json.load(f)
except: pass
return {}
def save_snippets(snippets):
with open(SNIPPETS_FILE, 'w') as f:
json.dump(snippets, f, indent=4)
def get_file_mtime(path):
if path.exists(): return os.path.getmtime(path)
return 0
def load_json(path):
with open(path, 'r') as f:
data = json.load(f)
return data, get_file_mtime(path)
def save_json(path, data):
clean_data = {k: v for k, v in data.items() if k in DEFAULTS or k == "prompt_history"}
if path.exists():
try:
with open(path, 'r') as f:
existing = json.load(f)
existing.update(clean_data)
clean_data = existing
except: pass
with open(path, 'w') as f:
json.dump(clean_data, f, indent=4)
return get_file_mtime(path)
def generate_templates(directory):
for filename in GENERIC_TEMPLATES:
path = directory / filename
data = DEFAULTS.copy()
if "vace" in filename:
data.update({"frame_to_skip": 81, "vace schedule": 1, "video file path": ""})
elif "i2v" in filename:
data.update({"reference image path": "", "flf image path": ""})
save_json(path, data)
# --- Initialization ---
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()))
if 'snippets' not in st.session_state:
st.session_state.snippets = load_snippets()
if 'loaded_file' not in st.session_state:
st.session_state.loaded_file = None
if 'last_mtime' not in st.session_state:
st.session_state.last_mtime = 0
if 'edit_history_idx' not in st.session_state:
st.session_state.edit_history_idx = None
# --- Sidebar ---
with st.sidebar:
st.header("📂 Navigator")
new_path = st.text_input("Current Path", value=str(st.session_state.current_dir))
if new_path != str(st.session_state.current_dir):
p = Path(new_path)
if p.exists() 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.rerun()
if st.button("📌 Pin Current Folder"):
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()
fav_selection = st.radio("Jump to:", ["Select..."] + st.session_state.config['favorites'], index=0, label_visibility="collapsed")
if fav_selection != "Select..." and fav_selection != str(st.session_state.current_dir):
st.session_state.current_dir = Path(fav_selection)
st.rerun()
st.markdown("---")
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, dramatic lighting...")
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("---")
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 (I2V / VACE)"):
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
data = DEFAULTS.copy()
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)
st.rerun()
selected_file_name = st.radio("Select File", [f.name for f in json_files])
# --- Main Editor Area ---
if selected_file_name:
file_path = st.session_state.current_dir / selected_file_name
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)
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
st.session_state.edit_history_idx = None
else:
data = st.session_state.data_cache
st.title(f"Editing: {selected_file_name}")
col1, col2 = st.columns([2, 1])
with col1:
# --- GENERAL SECTION (Collapsible) ---
with st.expander("🌍 General Prompts (Global Layer)", expanded=False):
gen_prompt = st.text_area("General Prompt", value=data.get("general_prompt", ""), height=100)
gen_negative = st.text_area("General Negative", value=data.get("general_negative", DEFAULTS["general_negative"]), height=100)
# --- SPECIFIC SECTION ---
st.write("📝 **Specific Prompts**")
current_prompt_val = data.get("current_prompt", "")
if 'append_prompt' in st.session_state:
current_prompt_val = (current_prompt_val.strip() + ", " + st.session_state.append_prompt).strip(', ')
del st.session_state.append_prompt
new_prompt = st.text_area("Specific Prompt", value=current_prompt_val, height=150)
new_negative = st.text_area("Specific Negative", value=data.get("negative", ""), height=100)
# --- SEED ---
col_seed_val, col_seed_btn = st.columns([4, 1])
with col_seed_btn:
st.write("")
st.write("")
if st.button("🎲 Randomize"):
st.session_state.rand_seed = random.randint(0, 999999999999)
st.rerun()
with col_seed_val:
seed_val = st.session_state.get('rand_seed', int(data.get("seed", 0)))
new_seed = st.number_input("Seed", value=seed_val, step=1, min_value=0, format="%d")
data["seed"] = new_seed
st.subheader("LoRAs")
st.code("<lora::1.0>", language="text")
l_col1, l_col2 = st.columns(2)
loras = {}
keys = ["lora 1 high", "lora 1 low", "lora 2 high", "lora 2 low", "lora 3 high", "lora 3 low"]
for i, k in enumerate(keys):
with (l_col1 if i % 2 == 0 else l_col2):
loras[k] = st.text_input(k, value=data.get(k, ""))
st.subheader("Settings")
spec_fields = {}
fields = ["camera", "flf"]
fname = selected_file_name
if "vace" in fname:
fields += ["frame_to_skip", "input_a_frames", "input_b_frames", "reference path", "reference switch", "vace schedule", "video file path"]
elif "i2v" in fname:
fields += ["reference image path", "flf image path"]
for f in fields:
val = data.get(f, DEFAULTS.get(f, ""))
spec_fields[f] = st.text_input(f, value=str(val))
with col2:
st.subheader("Actions")
current_disk_mtime = get_file_mtime(file_path)
is_conflict = current_disk_mtime > st.session_state.last_mtime
if is_conflict:
st.error("⚠️ CONFLICT: File changed on disk!")
c_col1, c_col2 = st.columns(2)
if c_col1.button("Force Overwrite", type="primary"):
data.update({
"current_prompt": new_prompt, "negative": new_negative,
"general_prompt": gen_prompt, "general_negative": gen_negative,
"seed": new_seed
})
data.update(loras)
data.update(spec_fields)
st.session_state.last_mtime = save_json(file_path, data)
st.session_state.data_cache = data
st.toast("Forced overwrite success!", icon="⚠️")
st.rerun()
if c_col2.button("Reload File"):
st.session_state.loaded_file = None
st.rerun()
else:
if st.button("💾 Update File", use_container_width=True):
data.update({
"current_prompt": new_prompt, "negative": new_negative,
"general_prompt": gen_prompt, "general_negative": gen_negative,
"seed": new_seed
})
data.update(loras)
data.update(spec_fields)
st.session_state.last_mtime = save_json(file_path, data)
st.session_state.data_cache = data
st.toast("File updated successfully!", icon="")
st.markdown("---")
archive_note = st.text_input("Archive Note (Optional)", placeholder="e.g. V1 with high motion")
if st.button("📦 Snapshot to History", use_container_width=True):
entry = {
"general_prompt": gen_prompt, "general_negative": gen_negative,
"prompt": new_prompt, "negative": new_negative,
"seed": new_seed,
"note": archive_note if archive_note else f"Snapshot {len(data.get('prompt_history', [])) + 1}",
"loras": loras,
**spec_fields
}
if "prompt_history" not in data: data["prompt_history"] = []
data["prompt_history"].insert(0, entry)
# Update main state too
data.update(entry)
data["current_prompt"] = new_prompt
st.session_state.last_mtime = save_json(file_path, data)
st.session_state.data_cache = data
st.toast("Archived & Saved!", icon="📦")
st.rerun()
st.markdown("---")
st.subheader("Media Preview")
preview_path = None
for k in ["reference path", "video file path", "reference image path", "flf image path"]:
if spec_fields.get(k):
preview_path = spec_fields[k]
break
if preview_path:
full_prev_path = Path(preview_path) if os.path.isabs(preview_path) else st.session_state.current_dir / preview_path
if full_prev_path.exists():
ext = full_prev_path.suffix.lower()
if ext in ['.mp4', '.avi', '.mov']:
st.video(str(full_prev_path))
else:
st.image(str(full_prev_path))
else:
st.warning(f"File not found: {preview_path}")
else:
st.info("No media path set.")
# ---------------- HISTORY SECTION ----------------
st.markdown("---")
history = data.get("prompt_history", [])
h_head_1, h_head_2 = st.columns([1, 2])
h_head_1.subheader(f"History ({len(history)})")
search_term = h_head_2.text_input("🔍 Search History", placeholder="Filter...").lower()
if history:
for idx, h in enumerate(history):
note_text = str(h.get('note', '')).lower()
if search_term and search_term not in note_text: continue
note_title = h.get('note', 'No Note') or "No Note"
expander_label = f"#{idx+1}: {note_title}"
with st.container():
if st.session_state.edit_history_idx == idx:
with st.expander(f"📝 EDITING: {note_title}", expanded=True):
st.info("Editing History Entry")
edit_note = st.text_input("Note", value=h.get('note', ''), key=f"edit_note_{idx}")
ec_seed1, ec_seed2 = st.columns([1, 3])
edit_seed = ec_seed1.number_input("Seed", value=int(h.get('seed', 0)), step=1, key=f"edit_seed_{idx}")
# EDIT ALL PROMPTS
st.caption("General Layer")
edit_gen_p = st.text_area("Gen Prompt", value=h.get('general_prompt', ''), height=60, key=f"egp_{idx}")
edit_gen_n = st.text_area("Gen Negative", value=h.get('general_negative', ''), height=60, key=f"egn_{idx}")
st.caption("Specific Layer")
edit_prompt = st.text_area("Prompt", value=h.get('prompt', ''), height=100, key=f"edit_prompt_{idx}")
edit_negative = st.text_area("Negative", value=h.get('negative', ''), height=60, key=f"edit_neg_{idx}")
ec1, ec2 = st.columns([1, 4])
if ec1.button("💾 Save", key=f"save_edit_{idx}", type="primary"):
h.update({
'note': edit_note, 'seed': edit_seed,
'general_prompt': edit_gen_p, 'general_negative': edit_gen_n,
'prompt': edit_prompt, 'negative': edit_negative
})
st.session_state.last_mtime = save_json(file_path, data)
st.session_state.data_cache = data
st.session_state.edit_history_idx = None
st.toast("History entry updated!", icon="✏️")
st.rerun()
if ec2.button("Cancel", key=f"cancel_edit_{idx}"):
st.session_state.edit_history_idx = None
st.rerun()
else:
with st.expander(expander_label):
col_h1, col_h2 = st.columns([3, 1])
with col_h1:
st.caption(f"📝 Prompts (Seed: {h.get('seed', 0)})")
# Show combined snippet
st.text(f"GEN: {h.get('general_prompt', '')[:50]}...\nSPEC: {h.get('prompt', '')[:50]}...")
st.caption("🧩 LoRAs & Files")
info_dict = {k:v for k,v in h.items() if k not in ['prompt', 'negative', 'general_prompt', 'general_negative', 'note', 'seed'] and v}
if 'loras' in h and isinstance(h['loras'], dict):
info_dict.update({k:v for k,v in h['loras'].items() if v})
if 'loras' in info_dict: del info_dict['loras']
st.json(info_dict, expanded=False)
with col_h2:
if st.button("Restore", key=f"rest_{idx}", use_container_width=True):
if is_conflict:
st.error("Resolve conflict first.")
else:
data["current_prompt"] = h.get("prompt", "")
data["negative"] = h.get("negative", "")
data["general_prompt"] = h.get("general_prompt", "")
data["general_negative"] = h.get("general_negative", "")
data["seed"] = int(h.get("seed", 0))
if "loras" in h and isinstance(h["loras"], dict):
data.update(h["loras"])
for k, v in h.items():
if k not in ["note", "prompt", "loras", "negative", "seed", "general_prompt", "general_negative"]:
data[k] = v
st.session_state.last_mtime = save_json(file_path, data)
st.session_state.data_cache = data
st.toast("Restored settings from history!", icon="")
st.rerun()
if st.button("✏️ Edit", key=f"open_edit_{idx}", use_container_width=True):
st.session_state.edit_history_idx = idx
st.rerun()
if st.button("Delete", key=f"del_{idx}", use_container_width=True):
if is_conflict:
st.error("Resolve conflict first.")
else:
data["prompt_history"].pop(idx)
st.session_state.last_mtime = save_json(file_path, data)
st.session_state.data_cache = data
st.rerun()