diff --git a/tab_timeline_wip.py b/tab_timeline_wip.py index d98cd02..d7f31cf 100644 --- a/tab_timeline_wip.py +++ b/tab_timeline_wip.py @@ -1,7 +1,7 @@ import streamlit as st import json from history_tree import HistoryTree -from utils import save_json +from utils import save_json, DEFAULTS from streamlit_agraph import agraph, Node, Edge, Config def render_timeline_wip(data, file_path): @@ -12,7 +12,9 @@ def render_timeline_wip(data, file_path): htree = HistoryTree(tree_data) - # --- 1. PREPARE NODES & EDGES --- + # ========================================== + # 1. BUILD GRAPH DATA (NODES & EDGES) + # ========================================== nodes = [] edges = [] @@ -23,16 +25,16 @@ def render_timeline_wip(data, file_path): note = n.get('note', 'Step') short_note = (note[:15] + '..') if len(note) > 15 else note - # Styles + # Default Styles color = "#ffffff" border = "#666666" - # Highlight Head + # Highlight Head (Current Pointer) if nid == htree.head_id: color = "#fff6cd" border = "#eebb00" - # Highlight Tips + # Highlight Tips (Ends of branches) if nid in htree.branches.values(): if color == "#ffffff": color = "#e6ffe6" @@ -57,7 +59,9 @@ def render_timeline_wip(data, file_path): type="STRAIGHT" )) - # --- 2. CONFIGURATION --- + # ========================================== + # 2. RENDER INTERACTIVE GRAPH + # ========================================== config = Config( width="100%", height="500px", @@ -77,15 +81,15 @@ def render_timeline_wip(data, file_path): ) st.subheader("✨ Interactive Timeline") - st.caption("Click any node to inspect changes.") + st.caption("Click any node to preview its settings and compare changes.") - # RENDER GRAPH - # key="interactive_graph" ensures the selection persists clicked_node_id = agraph(nodes=nodes, edges=edges, config=config) st.markdown("---") - # --- 3. IMPROVED INSPECTOR --- + # ========================================== + # 3. INSPECTION PANEL + # ========================================== target_node_id = clicked_node_id if clicked_node_id else htree.head_id if target_node_id and target_node_id in htree.nodes: @@ -96,63 +100,51 @@ def render_timeline_wip(data, file_path): c_h1.markdown(f"### 🔎 Inspecting: {selected_node.get('note', 'Step')}") c_h1.caption(f"ID: {target_node_id}") - # --- BETTER DIFF LOGIC --- - with st.expander(f"Compare with Current State", expanded=True): + # --- A. COMPARE CHANGES (DIFF) --- + with st.expander(f"📊 Compare Changes", expanded=False): diffs = [] - - # 1. Gather all unique keys from both sides all_keys = set(data.keys()) | set(node_data.keys()) - # 2. Define keys to strictly ignore (System / Metadata) + # Keys to ignore in diff view to reduce noise ignore_keys = { "history_tree", "prompt_history", "batch_data", "ui_reset_token", "sequence_number", - "input_a_frames", "input_b_frames" # Add any other noisy keys here + "input_a_frames", "input_b_frames" } for k in all_keys: if k in ignore_keys: continue - - # 3. Get values, treating Missing as Empty String for comparison val_now = data.get(k, "") val_then = node_data.get(k, "") - # 4. Normalize types (float 1.0 == int 1 == str "1") - # We convert everything to string and strip whitespace + # Convert to string and strip whitespace for clean comparison str_now = str(val_now).strip() str_then = str(val_then).strip() - # Handle Float/Int mismatch (e.g., "20" vs "20.0") if str_now != str_then: + # Fuzzy match for numbers (ignore 1 vs 1.0) try: - # Try converting both to float and compare with tolerance f_now = float(str_now) f_then = float(str_then) - if abs(f_now - f_then) < 0.001: - continue # They are effectively the same number + if abs(f_now - f_then) < 0.001: continue except ValueError: - pass # Not numbers, so the string difference is real + pass - # If we get here, they are different diffs.append((k, str_now, str_then)) if not diffs: - st.caption("✅ Identical to current state (or only ignored keys differ)") + st.caption("✅ Identical to current state") else: for k, v_now, v_then in diffs: dc1, dc2, dc3 = st.columns([1, 2, 2]) dc1.markdown(f"**{k}**") - # Highlight 'Missing' or empty values clearly - disp_now = v_now if v_now else "*(empty)*" - disp_then = v_then if v_then else "*(empty)*" - - dc2.markdown(f"🔴 Current: `{disp_now[:30]}`") - dc3.markdown(f"🟢 Selected: `{disp_then[:30]}`") - - # Actions + dc2.markdown(f"🔴 `{str(v_now)[:30]}`") + dc3.markdown(f"🟢 `{str(v_then)[:30]}`") + + # --- B. RESTORE ACTION --- with c_h2: st.write(""); st.write("") - if st.button("⏪ Restore Version", type="primary", use_container_width=True): + if st.button("⏪ Restore This Version", type="primary", use_container_width=True): data.update(node_data) htree.head_id = target_node_id @@ -166,3 +158,40 @@ def render_timeline_wip(data, file_path): st.toast(f"Restored {target_node_id}!", icon="🔄") st.rerun() + + # --- C. SNAPSHOT PREVIEW (READ ONLY FORM) --- + st.markdown("#### 📄 Snapshot Preview") + + # 1. Prompts (Handles both old 'positive_prompt' and new 'general_prompt' keys) + p_col1, p_col2 = st.columns(2) + with p_col1: + val_p = node_data.get("positive_prompt", "") or node_data.get("general_prompt", "") + st.text_area("Positive Prompt", value=val_p, height=100, disabled=True, key="prev_pp") + with p_col2: + val_n = node_data.get("negative_prompt", "") or node_data.get("general_negative", "") + st.text_area("Negative Prompt", value=val_n, height=100, disabled=True, key="prev_np") + + # 2. Key Settings + s_col1, s_col2, s_col3, s_col4 = st.columns(4) + s_col1.text_input("Seed", value=str(node_data.get("seed", "")), disabled=True, key="prev_seed") + s_col2.text_input("Steps", value=str(node_data.get("steps", "")), disabled=True, key="prev_steps") + s_col3.text_input("CFG", value=str(node_data.get("cfg", "")), disabled=True, key="prev_cfg") + s_col4.text_input("Denoise", value=str(node_data.get("denoise", "")), disabled=True, key="prev_den") + + # 3. Models + m_col1, m_col2 = st.columns(2) + m_col1.text_input("Checkpoint", value=node_data.get("model_name", ""), disabled=True, key="prev_ckpt") + m_col2.text_input("VAE", value=node_data.get("vae_name", ""), disabled=True, key="prev_vae") + + # 4. LoRAs + with st.expander("💊 LoRA Configuration"): + l1, l2, l3 = st.columns(3) + with l1: + st.text_input("L1 Name", value=node_data.get("lora 1 high", ""), disabled=True, key="prev_l1h") + st.text_input("L1 Str", value=str(node_data.get("lora 1 low", "")), disabled=True, key="prev_l1l") + with l2: + st.text_input("L2 Name", value=node_data.get("lora 2 high", ""), disabled=True, key="prev_l2h") + st.text_input("L2 Str", value=str(node_data.get("lora 2 low", "")), disabled=True, key="prev_l2l") + with l3: + st.text_input("L3 Name", value=node_data.get("lora 3 high", ""), disabled=True, key="prev_l3h") + st.text_input("L3 Str", value=str(node_data.get("lora 3 low", "")), disabled=True, key="prev_l3l")